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.
Files changed (62) hide show
  1. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/PKG-INFO +1 -1
  2. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/PayPerTranscript.egg-info/PKG-INFO +1 -1
  3. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/PayPerTranscript.egg-info/SOURCES.txt +15 -5
  4. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/__init__.py +1 -1
  5. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/__main__.py +9 -0
  6. paypertranscript-0.2.8/paypertranscript/assets/icons/app.ico +0 -0
  7. paypertranscript-0.2.8/paypertranscript/assets/icons/app.png +0 -0
  8. paypertranscript-0.2.8/paypertranscript/assets/icons/app_big.png +0 -0
  9. paypertranscript-0.2.8/paypertranscript/assets/icons/tray.png +0 -0
  10. paypertranscript-0.2.8/paypertranscript/assets/icons/tray_green.png +0 -0
  11. paypertranscript-0.2.8/paypertranscript/assets/icons/tray_orange.png +0 -0
  12. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/assets/styles/dark.qss +78 -23
  13. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/config.py +12 -1
  14. paypertranscript-0.2.8/paypertranscript/ui/animated.py +30 -0
  15. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/ui/app.py +34 -3
  16. paypertranscript-0.2.8/paypertranscript/ui/constants.py +49 -0
  17. paypertranscript-0.2.8/paypertranscript/ui/main_window.py +181 -0
  18. paypertranscript-0.2.8/paypertranscript/ui/pages/__init__.py +1 -0
  19. paypertranscript-0.2.8/paypertranscript/ui/pages/home_page.py +336 -0
  20. paypertranscript-0.2.4/paypertranscript/ui/settings.py → paypertranscript-0.2.8/paypertranscript/ui/pages/settings_page.py +127 -231
  21. paypertranscript-0.2.8/paypertranscript/ui/pages/statistics_page.py +221 -0
  22. paypertranscript-0.2.8/paypertranscript/ui/pages/window_mapping_page.py +711 -0
  23. paypertranscript-0.2.8/paypertranscript/ui/pages/word_list_page.py +308 -0
  24. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/ui/setup_wizard.py +30 -67
  25. paypertranscript-0.2.8/paypertranscript/ui/sidebar.py +352 -0
  26. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/ui/tray.py +60 -102
  27. paypertranscript-0.2.8/paypertranscript/ui/widgets.py +165 -0
  28. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/pyproject.toml +1 -1
  29. paypertranscript-0.2.4/paypertranscript/assets/icons/app.ico +0 -0
  30. paypertranscript-0.2.4/paypertranscript/assets/icons/app.png +0 -0
  31. paypertranscript-0.2.4/paypertranscript/ui/dashboard.py +0 -92
  32. paypertranscript-0.2.4/paypertranscript/ui/statistics.py +0 -412
  33. paypertranscript-0.2.4/paypertranscript/ui/window_mapping.py +0 -460
  34. paypertranscript-0.2.4/paypertranscript/ui/word_list.py +0 -183
  35. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/LICENSE +0 -0
  36. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/PayPerTranscript.egg-info/dependency_links.txt +0 -0
  37. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/PayPerTranscript.egg-info/entry_points.txt +0 -0
  38. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/PayPerTranscript.egg-info/requires.txt +0 -0
  39. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/PayPerTranscript.egg-info/top_level.txt +0 -0
  40. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/README.md +0 -0
  41. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/assets/icons/arrow_down.svg +0 -0
  42. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/assets/sounds/start.wav +0 -0
  43. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/assets/sounds/stop.wav +0 -0
  44. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/__init__.py +0 -0
  45. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/audio_manager.py +0 -0
  46. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/cost_tracker.py +0 -0
  47. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/hotkey.py +0 -0
  48. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/logging.py +0 -0
  49. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/paths.py +0 -0
  50. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/recorder.py +0 -0
  51. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/session_logger.py +0 -0
  52. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/text_inserter.py +0 -0
  53. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/updater.py +0 -0
  54. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/core/window_detector.py +0 -0
  55. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/pipeline/__init__.py +0 -0
  56. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/pipeline/transcription.py +0 -0
  57. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/providers/__init__.py +0 -0
  58. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/providers/base.py +0 -0
  59. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/providers/groq_provider.py +0 -0
  60. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/ui/__init__.py +0 -0
  61. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/paypertranscript/ui/overlay.py +0 -0
  62. {paypertranscript-0.2.4 → paypertranscript-0.2.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PayPerTranscript
3
- Version: 0.2.4
3
+ Version: 0.2.8
4
4
  Summary: Open-Source Voice-to-Text mit Pay-per-Use Pricing
5
5
  Author: PayPerTranscript Contributors
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PayPerTranscript
3
- Version: 0.2.4
3
+ Version: 0.2.8
4
4
  Summary: Open-Source Voice-to-Text mit Pay-per-Use Pricing
5
5
  Author: PayPerTranscript Contributors
6
6
  License-Expression: MIT
@@ -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/dashboard.py
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/statistics.py
46
+ paypertranscript/ui/sidebar.py
42
47
  paypertranscript/ui/tray.py
43
- paypertranscript/ui/window_mapping.py
44
- paypertranscript/ui/word_list.py
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
@@ -1,3 +1,3 @@
1
1
  """PayPerTranscript - Voice-to-Text mit Pay-per-Use Pricing."""
2
2
 
3
- __version__ = "0.2.4"
3
+ __version__ = "0.2.8"
@@ -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)."""
@@ -1,14 +1,16 @@
1
1
  /* PayPerTranscript - Dark Theme Stylesheet
2
2
  *
3
- * Farbpalette (neutral, passend zum Overlay):
4
- * Hintergrund dunkel: #121218
5
- * Hintergrund mittel: #1c1c24
6
- * Hintergrund hell: #2a2a34
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: #e94560;
52
- border-color: #e94560;
53
- color: #ffffff;
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: #ff5a75;
59
- border-color: #ff5a75;
60
+ background-color: #e0e0e0;
61
+ border-color: #e0e0e0;
60
62
  }
61
63
 
62
64
  QPushButton[primary="true"]:pressed {
63
- background-color: #c73a52;
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: #121218;
274
- width: 10px;
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: 5px;
295
+ border-radius: 3px;
281
296
  min-height: 30px;
282
297
  }
283
298
 
284
299
  QScrollBar::handle:vertical:hover {
285
- background-color: #2a2a34;
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: #121218;
294
- height: 10px;
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: 5px;
324
+ border-radius: 3px;
301
325
  min-width: 30px;
302
326
  }
303
327
 
304
328
  QScrollBar::handle:horizontal:hover {
305
- background-color: #2a2a34;
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: 16px;
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 key in result and isinstance(result[key], dict) and isinstance(value, dict):
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 QApplication, QDialog, QMessageBox
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."""