PayPerTranscript 0.2.4__tar.gz → 0.2.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/PKG-INFO +1 -1
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/PayPerTranscript.egg-info/PKG-INFO +1 -1
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/PayPerTranscript.egg-info/SOURCES.txt +15 -5
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/__init__.py +1 -1
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/__main__.py +9 -0
- paypertranscript-0.2.8/paypertranscript/assets/icons/app.ico +0 -0
- paypertranscript-0.2.8/paypertranscript/assets/icons/app.png +0 -0
- paypertranscript-0.2.8/paypertranscript/assets/icons/app_big.png +0 -0
- paypertranscript-0.2.8/paypertranscript/assets/icons/tray.png +0 -0
- paypertranscript-0.2.8/paypertranscript/assets/icons/tray_green.png +0 -0
- paypertranscript-0.2.8/paypertranscript/assets/icons/tray_orange.png +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/assets/styles/dark.qss +78 -23
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/config.py +12 -1
- paypertranscript-0.2.8/paypertranscript/ui/animated.py +30 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/ui/app.py +34 -3
- paypertranscript-0.2.8/paypertranscript/ui/constants.py +49 -0
- paypertranscript-0.2.8/paypertranscript/ui/main_window.py +181 -0
- paypertranscript-0.2.8/paypertranscript/ui/pages/__init__.py +1 -0
- paypertranscript-0.2.8/paypertranscript/ui/pages/home_page.py +336 -0
- paypertranscript-0.2.4/paypertranscript/ui/settings.py → paypertranscript-0.2.8/paypertranscript/ui/pages/settings_page.py +127 -231
- paypertranscript-0.2.8/paypertranscript/ui/pages/statistics_page.py +221 -0
- paypertranscript-0.2.8/paypertranscript/ui/pages/window_mapping_page.py +711 -0
- paypertranscript-0.2.8/paypertranscript/ui/pages/word_list_page.py +308 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/ui/setup_wizard.py +30 -67
- paypertranscript-0.2.8/paypertranscript/ui/sidebar.py +352 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/ui/tray.py +60 -102
- paypertranscript-0.2.8/paypertranscript/ui/widgets.py +165 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/pyproject.toml +1 -1
- paypertranscript-0.2.4/paypertranscript/assets/icons/app.ico +0 -0
- paypertranscript-0.2.4/paypertranscript/assets/icons/app.png +0 -0
- paypertranscript-0.2.4/paypertranscript/ui/dashboard.py +0 -92
- paypertranscript-0.2.4/paypertranscript/ui/statistics.py +0 -412
- paypertranscript-0.2.4/paypertranscript/ui/window_mapping.py +0 -460
- paypertranscript-0.2.4/paypertranscript/ui/word_list.py +0 -183
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/LICENSE +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/PayPerTranscript.egg-info/dependency_links.txt +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/PayPerTranscript.egg-info/entry_points.txt +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/PayPerTranscript.egg-info/requires.txt +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/PayPerTranscript.egg-info/top_level.txt +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/README.md +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/assets/icons/arrow_down.svg +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/assets/sounds/start.wav +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/assets/sounds/stop.wav +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/__init__.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/audio_manager.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/cost_tracker.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/hotkey.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/logging.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/paths.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/recorder.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/session_logger.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/text_inserter.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/updater.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/window_detector.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/pipeline/__init__.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/pipeline/transcription.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/providers/__init__.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/providers/base.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/providers/groq_provider.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/ui/__init__.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/ui/overlay.py +0 -0
- {paypertranscript-0.2.4 → paypertranscript-0.2.8}/setup.cfg +0 -0
|
@@ -11,7 +11,11 @@ paypertranscript/__init__.py
|
|
|
11
11
|
paypertranscript/__main__.py
|
|
12
12
|
paypertranscript/assets/icons/app.ico
|
|
13
13
|
paypertranscript/assets/icons/app.png
|
|
14
|
+
paypertranscript/assets/icons/app_big.png
|
|
14
15
|
paypertranscript/assets/icons/arrow_down.svg
|
|
16
|
+
paypertranscript/assets/icons/tray.png
|
|
17
|
+
paypertranscript/assets/icons/tray_green.png
|
|
18
|
+
paypertranscript/assets/icons/tray_orange.png
|
|
15
19
|
paypertranscript/assets/sounds/start.wav
|
|
16
20
|
paypertranscript/assets/sounds/stop.wav
|
|
17
21
|
paypertranscript/assets/styles/dark.qss
|
|
@@ -33,12 +37,18 @@ paypertranscript/providers/__init__.py
|
|
|
33
37
|
paypertranscript/providers/base.py
|
|
34
38
|
paypertranscript/providers/groq_provider.py
|
|
35
39
|
paypertranscript/ui/__init__.py
|
|
40
|
+
paypertranscript/ui/animated.py
|
|
36
41
|
paypertranscript/ui/app.py
|
|
37
|
-
paypertranscript/ui/
|
|
42
|
+
paypertranscript/ui/constants.py
|
|
43
|
+
paypertranscript/ui/main_window.py
|
|
38
44
|
paypertranscript/ui/overlay.py
|
|
39
|
-
paypertranscript/ui/settings.py
|
|
40
45
|
paypertranscript/ui/setup_wizard.py
|
|
41
|
-
paypertranscript/ui/
|
|
46
|
+
paypertranscript/ui/sidebar.py
|
|
42
47
|
paypertranscript/ui/tray.py
|
|
43
|
-
paypertranscript/ui/
|
|
44
|
-
paypertranscript/ui/
|
|
48
|
+
paypertranscript/ui/widgets.py
|
|
49
|
+
paypertranscript/ui/pages/__init__.py
|
|
50
|
+
paypertranscript/ui/pages/home_page.py
|
|
51
|
+
paypertranscript/ui/pages/settings_page.py
|
|
52
|
+
paypertranscript/ui/pages/statistics_page.py
|
|
53
|
+
paypertranscript/ui/pages/window_mapping_page.py
|
|
54
|
+
paypertranscript/ui/pages/word_list_page.py
|
|
@@ -16,6 +16,15 @@ try:
|
|
|
16
16
|
except Exception:
|
|
17
17
|
pass
|
|
18
18
|
|
|
19
|
+
# AppUserModelID setzen, damit Windows das App-Icon in der Taskbar zeigt
|
|
20
|
+
# statt des Python-Logos.
|
|
21
|
+
try:
|
|
22
|
+
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
|
|
23
|
+
"PayPerTranscript.PayPerTranscript"
|
|
24
|
+
)
|
|
25
|
+
except Exception:
|
|
26
|
+
pass
|
|
27
|
+
|
|
19
28
|
|
|
20
29
|
def _load_dotenv() -> None:
|
|
21
30
|
"""Laedt .env-Datei aus dem Arbeitsverzeichnis (falls vorhanden)."""
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/* PayPerTranscript - Dark Theme Stylesheet
|
|
2
2
|
*
|
|
3
|
-
* Farbpalette (
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Akzent Rot/Pink: #e94560
|
|
8
|
-
* Akzent Weiß: #ffffff
|
|
9
|
-
* Text primär: #e0e0e0
|
|
10
|
-
* Text sekundär: #a0a0a0
|
|
3
|
+
* Farbpalette (monochrom, basierend auf Overlay-Design):
|
|
4
|
+
* Background: #121218
|
|
5
|
+
* Surface: #1c1c24
|
|
6
|
+
* Elevated: #2a2a34
|
|
11
7
|
* Border: #333340
|
|
8
|
+
* Text Primary: #ffffff
|
|
9
|
+
* Text Secondary: #e0e0e0
|
|
10
|
+
* Text Tertiary: #a0a0a0
|
|
11
|
+
* Text Disabled: #606060
|
|
12
|
+
* Success (Emerald): #34d399
|
|
13
|
+
* Error: #f87171
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
16
|
/* === Basis === */
|
|
@@ -48,19 +50,20 @@ QPushButton:disabled {
|
|
|
48
50
|
|
|
49
51
|
/* Primärer Button (via property oder objectName) */
|
|
50
52
|
QPushButton[primary="true"] {
|
|
51
|
-
background-color: #
|
|
52
|
-
border-color: #
|
|
53
|
-
color: #
|
|
53
|
+
background-color: #ffffff;
|
|
54
|
+
border-color: #ffffff;
|
|
55
|
+
color: #121218;
|
|
54
56
|
font-weight: bold;
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
QPushButton[primary="true"]:hover {
|
|
58
|
-
background-color: #
|
|
59
|
-
border-color: #
|
|
60
|
+
background-color: #e0e0e0;
|
|
61
|
+
border-color: #e0e0e0;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
QPushButton[primary="true"]:pressed {
|
|
63
|
-
background-color: #
|
|
65
|
+
background-color: #c0c0c0;
|
|
66
|
+
border-color: #c0c0c0;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
/* === Labels === */
|
|
@@ -232,12 +235,23 @@ QListWidget::item {
|
|
|
232
235
|
QListWidget::item:selected {
|
|
233
236
|
background-color: #2a2a34;
|
|
234
237
|
color: #ffffff;
|
|
238
|
+
border: none;
|
|
239
|
+
outline: none;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
QListWidget::item:focus {
|
|
243
|
+
border: none;
|
|
244
|
+
outline: none;
|
|
235
245
|
}
|
|
236
246
|
|
|
237
247
|
QListWidget::item:hover {
|
|
238
248
|
background-color: #121218;
|
|
239
249
|
}
|
|
240
250
|
|
|
251
|
+
QListWidget:focus {
|
|
252
|
+
outline: none;
|
|
253
|
+
}
|
|
254
|
+
|
|
241
255
|
/* === Kontextmenü (Tray) === */
|
|
242
256
|
|
|
243
257
|
QMenu {
|
|
@@ -270,45 +284,68 @@ QMenu::item:disabled {
|
|
|
270
284
|
/* === Scrollbars === */
|
|
271
285
|
|
|
272
286
|
QScrollBar:vertical {
|
|
273
|
-
background-color:
|
|
274
|
-
width:
|
|
287
|
+
background-color: transparent;
|
|
288
|
+
width: 8px;
|
|
289
|
+
margin: 4px 2px;
|
|
275
290
|
border: none;
|
|
276
291
|
}
|
|
277
292
|
|
|
278
293
|
QScrollBar::handle:vertical {
|
|
279
294
|
background-color: #333340;
|
|
280
|
-
border-radius:
|
|
295
|
+
border-radius: 3px;
|
|
281
296
|
min-height: 30px;
|
|
282
297
|
}
|
|
283
298
|
|
|
284
299
|
QScrollBar::handle:vertical:hover {
|
|
285
|
-
background-color: #
|
|
300
|
+
background-color: #4a4a58;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
QScrollBar::handle:vertical:pressed {
|
|
304
|
+
background-color: #5a5a68;
|
|
286
305
|
}
|
|
287
306
|
|
|
288
307
|
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
|
|
289
308
|
height: 0px;
|
|
290
309
|
}
|
|
291
310
|
|
|
311
|
+
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
|
|
312
|
+
background: none;
|
|
313
|
+
}
|
|
314
|
+
|
|
292
315
|
QScrollBar:horizontal {
|
|
293
|
-
background-color:
|
|
294
|
-
height:
|
|
316
|
+
background-color: transparent;
|
|
317
|
+
height: 8px;
|
|
318
|
+
margin: 2px 4px;
|
|
295
319
|
border: none;
|
|
296
320
|
}
|
|
297
321
|
|
|
298
322
|
QScrollBar::handle:horizontal {
|
|
299
323
|
background-color: #333340;
|
|
300
|
-
border-radius:
|
|
324
|
+
border-radius: 3px;
|
|
301
325
|
min-width: 30px;
|
|
302
326
|
}
|
|
303
327
|
|
|
304
328
|
QScrollBar::handle:horizontal:hover {
|
|
305
|
-
background-color: #
|
|
329
|
+
background-color: #4a4a58;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
QScrollBar::handle:horizontal:pressed {
|
|
333
|
+
background-color: #5a5a68;
|
|
306
334
|
}
|
|
307
335
|
|
|
308
336
|
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
|
|
309
337
|
width: 0px;
|
|
310
338
|
}
|
|
311
339
|
|
|
340
|
+
QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal {
|
|
341
|
+
background: none;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/* Corner-Widget (Ecke wenn beide Scrollbars sichtbar) */
|
|
345
|
+
QAbstractScrollArea::corner {
|
|
346
|
+
background-color: transparent;
|
|
347
|
+
}
|
|
348
|
+
|
|
312
349
|
/* === Tooltips === */
|
|
313
350
|
|
|
314
351
|
QToolTip {
|
|
@@ -325,7 +362,7 @@ QGroupBox {
|
|
|
325
362
|
border: 1px solid #333340;
|
|
326
363
|
border-radius: 6px;
|
|
327
364
|
margin-top: 12px;
|
|
328
|
-
padding-top:
|
|
365
|
+
padding-top: 4px;
|
|
329
366
|
font-weight: bold;
|
|
330
367
|
}
|
|
331
368
|
|
|
@@ -386,3 +423,21 @@ QProgressBar::chunk {
|
|
|
386
423
|
background-color: #ffffff;
|
|
387
424
|
border-radius: 5px;
|
|
388
425
|
}
|
|
426
|
+
|
|
427
|
+
/* === MainWindow === */
|
|
428
|
+
|
|
429
|
+
QScrollArea {
|
|
430
|
+
background-color: transparent;
|
|
431
|
+
border: none;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/* Stat-Card Frames */
|
|
435
|
+
QFrame[stat-card="true"] {
|
|
436
|
+
background-color: #1c1c24;
|
|
437
|
+
border: 1px solid #333340;
|
|
438
|
+
border-radius: 8px;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
QFrame[stat-card="true"] QLabel {
|
|
442
|
+
border: none;
|
|
443
|
+
}
|
|
@@ -97,11 +97,22 @@ _SCHEMA: dict[str, type | tuple[type, ...]] = {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
# Keys whose dict values represent user data collections (not config structure).
|
|
101
|
+
# These are replaced entirely by the user's saved value, not recursively merged,
|
|
102
|
+
# so that deletions of default entries persist across restarts.
|
|
103
|
+
_REPLACE_KEYS = frozenset({"window_mappings", "categories"})
|
|
104
|
+
|
|
105
|
+
|
|
100
106
|
def _deep_merge(base: dict, override: dict) -> dict:
|
|
101
107
|
"""Merge override in base (rekursiv). Gibt neues Dict zurück."""
|
|
102
108
|
result = copy.deepcopy(base)
|
|
103
109
|
for key, value in override.items():
|
|
104
|
-
if
|
|
110
|
+
if (
|
|
111
|
+
key in result
|
|
112
|
+
and isinstance(result[key], dict)
|
|
113
|
+
and isinstance(value, dict)
|
|
114
|
+
and key not in _REPLACE_KEYS
|
|
115
|
+
):
|
|
105
116
|
result[key] = _deep_merge(result[key], value)
|
|
106
117
|
else:
|
|
107
118
|
result[key] = copy.deepcopy(value)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Animierte Basis-Dialoge fuer PayPerTranscript.
|
|
2
|
+
|
|
3
|
+
Stellt AnimatedDialog bereit: QDialog mit Fade-In-Animation beim Erscheinen,
|
|
4
|
+
konsistent mit dem Overlay-Design (OutCubic Easing, 150ms).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from PySide6.QtCore import QEasingCurve, QPropertyAnimation
|
|
8
|
+
from PySide6.QtWidgets import QDialog, QWidget
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AnimatedDialog(QDialog):
|
|
12
|
+
"""QDialog mit Fade-In-Animation beim Erscheinen."""
|
|
13
|
+
|
|
14
|
+
_FADE_IN_MS = 150
|
|
15
|
+
|
|
16
|
+
def __init__(self, parent: QWidget | None = None) -> None:
|
|
17
|
+
super().__init__(parent)
|
|
18
|
+
self._fade_anim: QPropertyAnimation | None = None
|
|
19
|
+
|
|
20
|
+
def showEvent(self, event: object) -> None:
|
|
21
|
+
super().showEvent(event)
|
|
22
|
+
self._fade_in()
|
|
23
|
+
|
|
24
|
+
def _fade_in(self) -> None:
|
|
25
|
+
self._fade_anim = QPropertyAnimation(self, b"windowOpacity")
|
|
26
|
+
self._fade_anim.setDuration(self._FADE_IN_MS)
|
|
27
|
+
self._fade_anim.setStartValue(0.0)
|
|
28
|
+
self._fade_anim.setEndValue(1.0)
|
|
29
|
+
self._fade_anim.setEasingCurve(QEasingCurve.Type.OutCubic)
|
|
30
|
+
self._fade_anim.start()
|
|
@@ -6,9 +6,17 @@ Zentrale App-Klasse: Single-Instance, Service-Init, Signal-Bridge, System Tray.
|
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
8
|
|
|
9
|
-
from PySide6.QtCore import QObject, QSharedMemory, QTimer, Signal
|
|
10
|
-
from PySide6.QtGui import QIcon
|
|
11
|
-
from PySide6.QtWidgets import
|
|
9
|
+
from PySide6.QtCore import QEvent, QObject, QSharedMemory, Qt, QTimer, Signal
|
|
10
|
+
from PySide6.QtGui import QCursor, QIcon
|
|
11
|
+
from PySide6.QtWidgets import (
|
|
12
|
+
QAbstractButton,
|
|
13
|
+
QAbstractSpinBox,
|
|
14
|
+
QApplication,
|
|
15
|
+
QComboBox,
|
|
16
|
+
QDialog,
|
|
17
|
+
QMessageBox,
|
|
18
|
+
QTabBar,
|
|
19
|
+
)
|
|
12
20
|
|
|
13
21
|
from paypertranscript.core.audio_manager import AudioManager
|
|
14
22
|
from paypertranscript.core.config import ConfigManager, load_api_key
|
|
@@ -31,6 +39,25 @@ from paypertranscript.ui.tray import SystemTray
|
|
|
31
39
|
|
|
32
40
|
log = get_logger("ui.app")
|
|
33
41
|
|
|
42
|
+
# Klickbare Widget-Typen, die einen Pointer-Cursor bekommen sollen
|
|
43
|
+
_CLICKABLE_TYPES = (QAbstractButton, QComboBox, QTabBar, QAbstractSpinBox)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _GlobalEventFilter(QObject):
|
|
47
|
+
"""Event-Filter: Pointer-Cursor auf klickbare Widgets, Scroll auf ComboBoxen blockieren."""
|
|
48
|
+
|
|
49
|
+
def eventFilter(self, obj: QObject, event: QEvent) -> bool:
|
|
50
|
+
if event.type() == QEvent.Type.ChildAdded:
|
|
51
|
+
child = event.child()
|
|
52
|
+
if isinstance(child, _CLICKABLE_TYPES):
|
|
53
|
+
child.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
|
|
54
|
+
elif event.type() == QEvent.Type.Wheel and isinstance(obj, QComboBox):
|
|
55
|
+
if obj.parent():
|
|
56
|
+
QApplication.sendEvent(obj.parent(), event)
|
|
57
|
+
return True
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
|
|
34
61
|
# Minimale Aufnahmedauer in Sekunden (kürzere Aufnahmen werden verworfen)
|
|
35
62
|
MIN_RECORDING_DURATION = 0.3
|
|
36
63
|
# Schwellwert für Warnung bei langer Aufnahme (Sekunden)
|
|
@@ -81,6 +108,10 @@ class PayPerTranscriptApp:
|
|
|
81
108
|
# Dark Theme laden
|
|
82
109
|
self._load_stylesheet()
|
|
83
110
|
|
|
111
|
+
# Globaler Event-Filter: Pointer-Cursor + Scroll-Schutz fuer ComboBoxen
|
|
112
|
+
self._global_filter = _GlobalEventFilter()
|
|
113
|
+
self._qt_app.installEventFilter(self._global_filter)
|
|
114
|
+
|
|
84
115
|
# Signal-Bridge
|
|
85
116
|
self._signals = AppSignals()
|
|
86
117
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Gemeinsame UI-Konstanten fuer PayPerTranscript.
|
|
2
|
+
|
|
3
|
+
Zentralisiert Sprachen, Hotkey-Presets und Modell-Listen,
|
|
4
|
+
die in Setup-Wizard und Einstellungen benoetigt werden.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Sprachen fuer Whisper (ISO-639-1 → Anzeigename)
|
|
8
|
+
LANGUAGES: list[tuple[str, str]] = [
|
|
9
|
+
("de", "Deutsch"),
|
|
10
|
+
("en", "English"),
|
|
11
|
+
("fr", "Fran\u00e7ais"),
|
|
12
|
+
("es", "Espa\u00f1ol"),
|
|
13
|
+
("it", "Italiano"),
|
|
14
|
+
("pt", "Portugu\u00eas"),
|
|
15
|
+
("nl", "Nederlands"),
|
|
16
|
+
("pl", "Polski"),
|
|
17
|
+
("ru", "\u0420\u0443\u0441\u0441\u043a\u0438\u0439"),
|
|
18
|
+
("ja", "\u65e5\u672c\u8a9e"),
|
|
19
|
+
("zh", "\u4e2d\u6587"),
|
|
20
|
+
("ko", "\ud55c\uad6d\uc5b4"),
|
|
21
|
+
("tr", "T\u00fcrk\u00e7e"),
|
|
22
|
+
("ar", "\u0627\u0644\u0639\u0631\u0628\u064a\u0629"),
|
|
23
|
+
("uk", "\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430"),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
# Hotkey-Presets (Anzeigename, pynput-Keys)
|
|
27
|
+
HOLD_PRESETS: list[tuple[str, list[str]]] = [
|
|
28
|
+
("Ctrl + Win", ["ctrl", "cmd"]),
|
|
29
|
+
("Ctrl + Alt", ["ctrl", "alt"]),
|
|
30
|
+
("Ctrl + Shift", ["ctrl", "shift"]),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
TOGGLE_PRESETS: list[tuple[str, list[str] | None]] = [
|
|
34
|
+
("Keiner", None),
|
|
35
|
+
("Ctrl + Win", ["ctrl", "cmd"]),
|
|
36
|
+
("Ctrl + Alt", ["ctrl", "alt"]),
|
|
37
|
+
("Ctrl + Shift", ["ctrl", "shift"]),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# STT-Modelle
|
|
41
|
+
STT_MODELS: list[str] = [
|
|
42
|
+
"whisper-large-v3-turbo",
|
|
43
|
+
"whisper-large-v3",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
# LLM-Modelle
|
|
47
|
+
LLM_MODELS: list[str] = [
|
|
48
|
+
"openai/gpt-oss-20b",
|
|
49
|
+
]
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Unified MainWindow fuer PayPerTranscript.
|
|
2
|
+
|
|
3
|
+
Einzelnes Fenster mit Sidebar-Navigation und gestapelten Seiten.
|
|
4
|
+
Ersetzt die fragmentierten Einzel-Dialoge.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
|
|
9
|
+
from PySide6.QtCore import Qt, Signal
|
|
10
|
+
from PySide6.QtGui import QCloseEvent, QIcon
|
|
11
|
+
from PySide6.QtWidgets import QDialog, QHBoxLayout, QStackedWidget, QWidget
|
|
12
|
+
|
|
13
|
+
from paypertranscript.core.config import ConfigManager
|
|
14
|
+
from paypertranscript.core.hotkey import HotkeyListener
|
|
15
|
+
from paypertranscript.core.logging import get_logger
|
|
16
|
+
from paypertranscript.core.paths import get_icons_dir
|
|
17
|
+
from paypertranscript.core.session_logger import SessionLogger
|
|
18
|
+
from paypertranscript.ui.pages.home_page import HomePage
|
|
19
|
+
from paypertranscript.ui.sidebar import Sidebar
|
|
20
|
+
|
|
21
|
+
log = get_logger("ui.main_window")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MainWindow(QDialog):
|
|
25
|
+
"""Unified MainWindow mit Sidebar + gestapelten Seiten."""
|
|
26
|
+
|
|
27
|
+
update_check_requested = Signal()
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
config: ConfigManager,
|
|
32
|
+
hotkey_listener: HotkeyListener | None = None,
|
|
33
|
+
session_logger: SessionLogger | None = None,
|
|
34
|
+
get_last_transcription: Callable[[], str | None] | None = None,
|
|
35
|
+
parent: QWidget | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
super().__init__(parent)
|
|
38
|
+
self._config = config
|
|
39
|
+
self._setup_ui(config, hotkey_listener, session_logger, get_last_transcription)
|
|
40
|
+
log.info("MainWindow erstellt")
|
|
41
|
+
|
|
42
|
+
def _setup_ui(
|
|
43
|
+
self,
|
|
44
|
+
config: ConfigManager,
|
|
45
|
+
hotkey_listener: HotkeyListener | None,
|
|
46
|
+
session_logger: SessionLogger | None,
|
|
47
|
+
get_last_transcription: Callable[[], str | None] | None = None,
|
|
48
|
+
) -> None:
|
|
49
|
+
self.setWindowTitle("PayPerTranscript")
|
|
50
|
+
self.setFixedSize(780, 560)
|
|
51
|
+
self.setWindowFlags(
|
|
52
|
+
Qt.WindowType.Window
|
|
53
|
+
| Qt.WindowType.WindowCloseButtonHint
|
|
54
|
+
| Qt.WindowType.WindowMinimizeButtonHint
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# App-Icon setzen
|
|
58
|
+
icon_path = get_icons_dir() / "app.ico"
|
|
59
|
+
if icon_path.exists():
|
|
60
|
+
self.setWindowIcon(QIcon(str(icon_path)))
|
|
61
|
+
|
|
62
|
+
# Haupt-Layout: Sidebar + Content
|
|
63
|
+
layout = QHBoxLayout(self)
|
|
64
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
65
|
+
layout.setSpacing(0)
|
|
66
|
+
|
|
67
|
+
# Sidebar
|
|
68
|
+
self._sidebar = Sidebar()
|
|
69
|
+
self._sidebar.page_changed.connect(self._on_page_changed)
|
|
70
|
+
self._sidebar.restart_requested.connect(self._on_restart)
|
|
71
|
+
self._sidebar.quit_requested.connect(self._on_quit)
|
|
72
|
+
self._sidebar.update_check_requested.connect(self.update_check_requested)
|
|
73
|
+
layout.addWidget(self._sidebar)
|
|
74
|
+
|
|
75
|
+
# Trennlinie
|
|
76
|
+
separator = QWidget()
|
|
77
|
+
separator.setFixedWidth(1)
|
|
78
|
+
separator.setStyleSheet("background-color: #333340;")
|
|
79
|
+
layout.addWidget(separator)
|
|
80
|
+
|
|
81
|
+
# Content-Stack
|
|
82
|
+
self._stack = QStackedWidget()
|
|
83
|
+
self._stack.setStyleSheet("QStackedWidget { background-color: #121218; }")
|
|
84
|
+
|
|
85
|
+
# Lazy Page Loading: Nur HomePage sofort erstellen, Rest on-demand.
|
|
86
|
+
self._pages: dict[int, QWidget] = {}
|
|
87
|
+
self._page_factories: dict[int, Callable[[], QWidget]] = {
|
|
88
|
+
1: lambda: self._create_statistics_page(session_logger),
|
|
89
|
+
2: lambda: self._create_settings_page(config, hotkey_listener),
|
|
90
|
+
3: lambda: self._create_word_list_page(config),
|
|
91
|
+
4: lambda: self._create_window_mapping_page(config),
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# HomePage (Index 0) sofort — wird bei jedem Oeffnen angezeigt
|
|
95
|
+
home = HomePage(config, session_logger, get_last_transcription)
|
|
96
|
+
self._pages[0] = home
|
|
97
|
+
self._stack.addWidget(home) # 0
|
|
98
|
+
|
|
99
|
+
# Placeholders fuer restliche Seiten
|
|
100
|
+
for _ in range(1, 5):
|
|
101
|
+
self._stack.addWidget(QWidget())
|
|
102
|
+
|
|
103
|
+
layout.addWidget(self._stack, 1)
|
|
104
|
+
|
|
105
|
+
# -- Lazy Page Factories --
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def _create_statistics_page(session_logger: SessionLogger | None) -> QWidget:
|
|
109
|
+
from paypertranscript.ui.pages.statistics_page import StatisticsPage
|
|
110
|
+
|
|
111
|
+
return StatisticsPage(session_logger) if session_logger else QWidget()
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _create_settings_page(
|
|
115
|
+
config: ConfigManager, hotkey_listener: HotkeyListener | None
|
|
116
|
+
) -> QWidget:
|
|
117
|
+
from paypertranscript.ui.pages.settings_page import SettingsPage
|
|
118
|
+
|
|
119
|
+
return SettingsPage(config, hotkey_listener)
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def _create_word_list_page(config: ConfigManager) -> QWidget:
|
|
123
|
+
from paypertranscript.ui.pages.word_list_page import WordListPage
|
|
124
|
+
|
|
125
|
+
return WordListPage(config)
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def _create_window_mapping_page(config: ConfigManager) -> QWidget:
|
|
129
|
+
from paypertranscript.ui.pages.window_mapping_page import WindowMappingPage
|
|
130
|
+
|
|
131
|
+
return WindowMappingPage(config)
|
|
132
|
+
|
|
133
|
+
def _ensure_page(self, index: int) -> None:
|
|
134
|
+
"""Erstellt die Seite beim ersten Zugriff (lazy loading)."""
|
|
135
|
+
if index in self._pages:
|
|
136
|
+
return
|
|
137
|
+
factory = self._page_factories.pop(index, None)
|
|
138
|
+
if factory is None:
|
|
139
|
+
return
|
|
140
|
+
page = factory()
|
|
141
|
+
self._pages[index] = page
|
|
142
|
+
old = self._stack.widget(index)
|
|
143
|
+
self._stack.removeWidget(old)
|
|
144
|
+
self._stack.insertWidget(index, page)
|
|
145
|
+
old.deleteLater()
|
|
146
|
+
log.debug("Seite %d lazy erstellt: %s", index, type(page).__name__)
|
|
147
|
+
|
|
148
|
+
# -- Navigation --
|
|
149
|
+
|
|
150
|
+
def _on_page_changed(self, index: int) -> None:
|
|
151
|
+
"""Wechselt zur angeforderten Seite (lazy creation bei Erstaufruf)."""
|
|
152
|
+
self._ensure_page(index)
|
|
153
|
+
self._stack.setCurrentIndex(index)
|
|
154
|
+
self._sidebar.set_active(index)
|
|
155
|
+
|
|
156
|
+
def navigate_to(self, page_index: int) -> None:
|
|
157
|
+
"""Navigiert zu einer bestimmten Seite und zeigt das Fenster."""
|
|
158
|
+
if 0 <= page_index < self._stack.count():
|
|
159
|
+
self._ensure_page(page_index)
|
|
160
|
+
self._stack.setCurrentIndex(page_index)
|
|
161
|
+
self._sidebar.set_active(page_index)
|
|
162
|
+
self.show()
|
|
163
|
+
self.raise_()
|
|
164
|
+
self.activateWindow()
|
|
165
|
+
|
|
166
|
+
def closeEvent(self, event: QCloseEvent) -> None:
|
|
167
|
+
"""Schliessen versteckt das Fenster (App laeuft im Tray weiter)."""
|
|
168
|
+
event.ignore()
|
|
169
|
+
self.hide()
|
|
170
|
+
|
|
171
|
+
def _on_restart(self) -> None:
|
|
172
|
+
"""Neustart ueber Sidebar."""
|
|
173
|
+
log.info("Neustart ueber MainWindow-Sidebar")
|
|
174
|
+
from paypertranscript.core.updater import restart_app
|
|
175
|
+
restart_app()
|
|
176
|
+
|
|
177
|
+
def _on_quit(self) -> None:
|
|
178
|
+
"""Beenden ueber Sidebar."""
|
|
179
|
+
log.info("Beenden ueber MainWindow-Sidebar")
|
|
180
|
+
from PySide6.QtWidgets import QApplication
|
|
181
|
+
QApplication.quit()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""UI-Seiten fuer das MainWindow."""
|