meeting-noter 2.0.0__py3-none-any.whl → 3.0.1__py3-none-any.whl
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.
Potentially problematic release.
This version of meeting-noter might be problematic. Click here for more details.
- meeting_noter/__init__.py +1 -1
- meeting_noter/cli.py +41 -0
- meeting_noter/gui/__init__.py +51 -0
- meeting_noter/gui/main_window.py +219 -0
- meeting_noter/gui/menubar.py +248 -0
- meeting_noter/gui/screens/__init__.py +17 -0
- meeting_noter/gui/screens/dashboard.py +262 -0
- meeting_noter/gui/screens/logs.py +184 -0
- meeting_noter/gui/screens/recordings.py +279 -0
- meeting_noter/gui/screens/search.py +229 -0
- meeting_noter/gui/screens/settings.py +232 -0
- meeting_noter/gui/screens/viewer.py +140 -0
- meeting_noter/gui/theme/__init__.py +5 -0
- meeting_noter/gui/theme/dark_theme.py +53 -0
- meeting_noter/gui/theme/styles.qss +504 -0
- meeting_noter/gui/utils/__init__.py +15 -0
- meeting_noter/gui/utils/signals.py +82 -0
- meeting_noter/gui/utils/workers.py +258 -0
- meeting_noter/gui/widgets/__init__.py +6 -0
- meeting_noter/gui/widgets/sidebar.py +210 -0
- meeting_noter/gui/widgets/status_indicator.py +108 -0
- {meeting_noter-2.0.0.dist-info → meeting_noter-3.0.1.dist-info}/METADATA +2 -1
- {meeting_noter-2.0.0.dist-info → meeting_noter-3.0.1.dist-info}/RECORD +26 -7
- {meeting_noter-2.0.0.dist-info → meeting_noter-3.0.1.dist-info}/WHEEL +0 -0
- {meeting_noter-2.0.0.dist-info → meeting_noter-3.0.1.dist-info}/entry_points.txt +0 -0
- {meeting_noter-2.0.0.dist-info → meeting_noter-3.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""Settings screen for Meeting Noter GUI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from PySide6.QtCore import Qt
|
|
8
|
+
from PySide6.QtWidgets import (
|
|
9
|
+
QCheckBox,
|
|
10
|
+
QComboBox,
|
|
11
|
+
QFileDialog,
|
|
12
|
+
QFrame,
|
|
13
|
+
QGridLayout,
|
|
14
|
+
QHBoxLayout,
|
|
15
|
+
QLabel,
|
|
16
|
+
QLineEdit,
|
|
17
|
+
QPushButton,
|
|
18
|
+
QSpinBox,
|
|
19
|
+
QVBoxLayout,
|
|
20
|
+
QWidget,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from meeting_noter.config import get_config
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
WHISPER_MODELS = [
|
|
27
|
+
("tiny.en", "tiny.en (fastest)"),
|
|
28
|
+
("base.en", "base.en (balanced)"),
|
|
29
|
+
("small.en", "small.en (better)"),
|
|
30
|
+
("medium.en", "medium.en (high quality)"),
|
|
31
|
+
("large-v3", "large-v3 (best, multilingual)"),
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SettingsScreen(QWidget):
|
|
36
|
+
"""Screen for configuring application settings."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, parent=None):
|
|
39
|
+
super().__init__(parent)
|
|
40
|
+
self._setup_ui()
|
|
41
|
+
self._connect_signals()
|
|
42
|
+
self._load_settings()
|
|
43
|
+
|
|
44
|
+
def _setup_ui(self):
|
|
45
|
+
"""Set up the settings UI."""
|
|
46
|
+
layout = QVBoxLayout(self)
|
|
47
|
+
layout.setContentsMargins(30, 30, 30, 30)
|
|
48
|
+
layout.setSpacing(20)
|
|
49
|
+
|
|
50
|
+
# Title
|
|
51
|
+
title = QLabel("Settings")
|
|
52
|
+
title.setStyleSheet("font-size: 24px; font-weight: 600; color: #ffffff;")
|
|
53
|
+
layout.addWidget(title)
|
|
54
|
+
|
|
55
|
+
# Directories section
|
|
56
|
+
dirs_section = self._create_section("Directories")
|
|
57
|
+
dirs_layout = QGridLayout()
|
|
58
|
+
dirs_layout.setSpacing(10)
|
|
59
|
+
|
|
60
|
+
# Recordings directory
|
|
61
|
+
dirs_layout.addWidget(QLabel("Recordings:"), 0, 0)
|
|
62
|
+
self._recordings_dir = QLineEdit()
|
|
63
|
+
dirs_layout.addWidget(self._recordings_dir, 0, 1)
|
|
64
|
+
recordings_browse = QPushButton("...")
|
|
65
|
+
recordings_browse.setFixedWidth(40)
|
|
66
|
+
recordings_browse.clicked.connect(lambda: self._browse_dir(self._recordings_dir))
|
|
67
|
+
dirs_layout.addWidget(recordings_browse, 0, 2)
|
|
68
|
+
|
|
69
|
+
# Transcripts directory
|
|
70
|
+
dirs_layout.addWidget(QLabel("Transcripts:"), 1, 0)
|
|
71
|
+
self._transcripts_dir = QLineEdit()
|
|
72
|
+
dirs_layout.addWidget(self._transcripts_dir, 1, 1)
|
|
73
|
+
transcripts_browse = QPushButton("...")
|
|
74
|
+
transcripts_browse.setFixedWidth(40)
|
|
75
|
+
transcripts_browse.clicked.connect(lambda: self._browse_dir(self._transcripts_dir))
|
|
76
|
+
dirs_layout.addWidget(transcripts_browse, 1, 2)
|
|
77
|
+
|
|
78
|
+
dirs_section.layout().addLayout(dirs_layout)
|
|
79
|
+
layout.addWidget(dirs_section)
|
|
80
|
+
|
|
81
|
+
# Transcription section
|
|
82
|
+
trans_section = self._create_section("Transcription")
|
|
83
|
+
trans_layout = QGridLayout()
|
|
84
|
+
trans_layout.setSpacing(10)
|
|
85
|
+
|
|
86
|
+
trans_layout.addWidget(QLabel("Whisper model:"), 0, 0)
|
|
87
|
+
self._model_combo = QComboBox()
|
|
88
|
+
for model_id, model_name in WHISPER_MODELS:
|
|
89
|
+
self._model_combo.addItem(model_name, model_id)
|
|
90
|
+
self._model_combo.setMinimumWidth(250)
|
|
91
|
+
trans_layout.addWidget(self._model_combo, 0, 1)
|
|
92
|
+
trans_layout.setColumnStretch(2, 1)
|
|
93
|
+
|
|
94
|
+
trans_section.layout().addLayout(trans_layout)
|
|
95
|
+
layout.addWidget(trans_section)
|
|
96
|
+
|
|
97
|
+
# Options section
|
|
98
|
+
options_section = self._create_section("Options")
|
|
99
|
+
options_layout = QVBoxLayout()
|
|
100
|
+
options_layout.setSpacing(10)
|
|
101
|
+
|
|
102
|
+
self._auto_transcribe = QCheckBox("Auto-transcribe after recording")
|
|
103
|
+
options_layout.addWidget(self._auto_transcribe)
|
|
104
|
+
|
|
105
|
+
self._capture_system = QCheckBox("Capture system audio (meeting participants)")
|
|
106
|
+
options_layout.addWidget(self._capture_system)
|
|
107
|
+
|
|
108
|
+
self._auto_update = QCheckBox("Auto-update when new version available")
|
|
109
|
+
options_layout.addWidget(self._auto_update)
|
|
110
|
+
|
|
111
|
+
# Silence timeout
|
|
112
|
+
timeout_row = QHBoxLayout()
|
|
113
|
+
timeout_row.addWidget(QLabel("Silence timeout (minutes):"))
|
|
114
|
+
self._silence_timeout = QSpinBox()
|
|
115
|
+
self._silence_timeout.setRange(1, 60)
|
|
116
|
+
self._silence_timeout.setFixedWidth(80)
|
|
117
|
+
timeout_row.addWidget(self._silence_timeout)
|
|
118
|
+
timeout_row.addStretch()
|
|
119
|
+
options_layout.addLayout(timeout_row)
|
|
120
|
+
|
|
121
|
+
options_section.layout().addLayout(options_layout)
|
|
122
|
+
layout.addWidget(options_section)
|
|
123
|
+
|
|
124
|
+
# Buttons
|
|
125
|
+
button_row = QHBoxLayout()
|
|
126
|
+
button_row.setSpacing(10)
|
|
127
|
+
|
|
128
|
+
self._save_btn = QPushButton("Save")
|
|
129
|
+
self._save_btn.setProperty("class", "success")
|
|
130
|
+
self._save_btn.setStyleSheet("background-color: #107c10;")
|
|
131
|
+
|
|
132
|
+
self._reset_btn = QPushButton("Reset")
|
|
133
|
+
self._reset_btn.setProperty("class", "secondary")
|
|
134
|
+
self._reset_btn.setStyleSheet("background-color: #3c3c3c;")
|
|
135
|
+
|
|
136
|
+
button_row.addWidget(self._save_btn)
|
|
137
|
+
button_row.addWidget(self._reset_btn)
|
|
138
|
+
button_row.addStretch()
|
|
139
|
+
layout.addLayout(button_row)
|
|
140
|
+
|
|
141
|
+
# Status label
|
|
142
|
+
self._status_label = QLabel("")
|
|
143
|
+
self._status_label.setStyleSheet("color: #4ec9b0;")
|
|
144
|
+
layout.addWidget(self._status_label)
|
|
145
|
+
|
|
146
|
+
# Spacer
|
|
147
|
+
layout.addStretch()
|
|
148
|
+
|
|
149
|
+
def _create_section(self, title: str) -> QFrame:
|
|
150
|
+
"""Create a section frame with title."""
|
|
151
|
+
frame = QFrame()
|
|
152
|
+
frame.setStyleSheet(
|
|
153
|
+
"QFrame { background-color: #2d2d2d; border: 1px solid #3c3c3c; border-radius: 8px; }"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
layout = QVBoxLayout(frame)
|
|
157
|
+
layout.setContentsMargins(15, 15, 15, 15)
|
|
158
|
+
layout.setSpacing(15)
|
|
159
|
+
|
|
160
|
+
section_title = QLabel(title)
|
|
161
|
+
section_title.setStyleSheet(
|
|
162
|
+
"font-size: 12px; font-weight: 600; color: #858585; text-transform: uppercase;"
|
|
163
|
+
)
|
|
164
|
+
layout.addWidget(section_title)
|
|
165
|
+
|
|
166
|
+
return frame
|
|
167
|
+
|
|
168
|
+
def _connect_signals(self):
|
|
169
|
+
"""Connect button signals."""
|
|
170
|
+
self._save_btn.clicked.connect(self._save_settings)
|
|
171
|
+
self._reset_btn.clicked.connect(self._load_settings)
|
|
172
|
+
|
|
173
|
+
def _browse_dir(self, line_edit: QLineEdit):
|
|
174
|
+
"""Open directory browser."""
|
|
175
|
+
current = line_edit.text() or str(Path.home())
|
|
176
|
+
directory = QFileDialog.getExistingDirectory(self, "Select Directory", current)
|
|
177
|
+
if directory:
|
|
178
|
+
line_edit.setText(directory)
|
|
179
|
+
|
|
180
|
+
def _load_settings(self):
|
|
181
|
+
"""Load settings from config."""
|
|
182
|
+
config = get_config()
|
|
183
|
+
|
|
184
|
+
self._recordings_dir.setText(str(config.recordings_dir))
|
|
185
|
+
self._transcripts_dir.setText(str(config.transcripts_dir))
|
|
186
|
+
|
|
187
|
+
# Set model combo
|
|
188
|
+
model = config.whisper_model
|
|
189
|
+
for i in range(self._model_combo.count()):
|
|
190
|
+
if self._model_combo.itemData(i) == model:
|
|
191
|
+
self._model_combo.setCurrentIndex(i)
|
|
192
|
+
break
|
|
193
|
+
|
|
194
|
+
self._auto_transcribe.setChecked(config.auto_transcribe)
|
|
195
|
+
self._capture_system.setChecked(config.capture_system_audio)
|
|
196
|
+
self._auto_update.setChecked(config.auto_update)
|
|
197
|
+
self._silence_timeout.setValue(config.silence_timeout)
|
|
198
|
+
|
|
199
|
+
self._status_label.setText("")
|
|
200
|
+
|
|
201
|
+
def _save_settings(self):
|
|
202
|
+
"""Save settings to config."""
|
|
203
|
+
config = get_config()
|
|
204
|
+
|
|
205
|
+
# Validate and create directories
|
|
206
|
+
recordings_path = Path(self._recordings_dir.text()).expanduser()
|
|
207
|
+
transcripts_path = Path(self._transcripts_dir.text()).expanduser()
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
recordings_path.mkdir(parents=True, exist_ok=True)
|
|
211
|
+
transcripts_path.mkdir(parents=True, exist_ok=True)
|
|
212
|
+
except Exception as e:
|
|
213
|
+
self._status_label.setText(f"Error creating directories: {e}")
|
|
214
|
+
self._status_label.setStyleSheet("color: #f14c4c;")
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
config.recordings_dir = recordings_path
|
|
218
|
+
config.transcripts_dir = transcripts_path
|
|
219
|
+
config.whisper_model = self._model_combo.currentData()
|
|
220
|
+
config.auto_transcribe = self._auto_transcribe.isChecked()
|
|
221
|
+
config.capture_system_audio = self._capture_system.isChecked()
|
|
222
|
+
config.auto_update = self._auto_update.isChecked()
|
|
223
|
+
config.silence_timeout = self._silence_timeout.value()
|
|
224
|
+
|
|
225
|
+
config.save()
|
|
226
|
+
|
|
227
|
+
self._status_label.setText("Settings saved successfully")
|
|
228
|
+
self._status_label.setStyleSheet("color: #4ec9b0;")
|
|
229
|
+
|
|
230
|
+
def refresh(self):
|
|
231
|
+
"""Refresh settings."""
|
|
232
|
+
self._load_settings()
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Transcript viewer screen for Meeting Noter GUI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from PySide6.QtWidgets import (
|
|
9
|
+
QHBoxLayout,
|
|
10
|
+
QLabel,
|
|
11
|
+
QPushButton,
|
|
12
|
+
QTextEdit,
|
|
13
|
+
QVBoxLayout,
|
|
14
|
+
QWidget,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from meeting_noter.config import get_config
|
|
18
|
+
from meeting_noter.gui.utils.signals import get_app_state
|
|
19
|
+
from meeting_noter.output.writer import format_size
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ViewerScreen(QWidget):
|
|
23
|
+
"""Screen for viewing transcripts."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, parent=None):
|
|
26
|
+
super().__init__(parent)
|
|
27
|
+
self._current_path: Path | None = None
|
|
28
|
+
self._setup_ui()
|
|
29
|
+
self._connect_signals()
|
|
30
|
+
|
|
31
|
+
def _setup_ui(self):
|
|
32
|
+
"""Set up the viewer UI."""
|
|
33
|
+
layout = QVBoxLayout(self)
|
|
34
|
+
layout.setContentsMargins(30, 30, 30, 30)
|
|
35
|
+
layout.setSpacing(15)
|
|
36
|
+
|
|
37
|
+
# Title row
|
|
38
|
+
title_row = QHBoxLayout()
|
|
39
|
+
|
|
40
|
+
self._title = QLabel("Transcript Viewer")
|
|
41
|
+
self._title.setStyleSheet("font-size: 24px; font-weight: 600; color: #ffffff;")
|
|
42
|
+
title_row.addWidget(self._title)
|
|
43
|
+
|
|
44
|
+
title_row.addStretch()
|
|
45
|
+
|
|
46
|
+
self._back_btn = QPushButton("Back")
|
|
47
|
+
self._back_btn.setProperty("class", "secondary")
|
|
48
|
+
self._back_btn.setStyleSheet("background-color: #3c3c3c;")
|
|
49
|
+
title_row.addWidget(self._back_btn)
|
|
50
|
+
|
|
51
|
+
layout.addLayout(title_row)
|
|
52
|
+
|
|
53
|
+
# File info
|
|
54
|
+
self._file_info = QLabel("")
|
|
55
|
+
self._file_info.setStyleSheet("color: #858585;")
|
|
56
|
+
layout.addWidget(self._file_info)
|
|
57
|
+
|
|
58
|
+
# Content area
|
|
59
|
+
self._content = QTextEdit()
|
|
60
|
+
self._content.setReadOnly(True)
|
|
61
|
+
self._content.setStyleSheet(
|
|
62
|
+
"font-family: 'Menlo', 'Monaco', 'Courier New', monospace; font-size: 13px;"
|
|
63
|
+
)
|
|
64
|
+
layout.addWidget(self._content, 1)
|
|
65
|
+
|
|
66
|
+
# Action buttons
|
|
67
|
+
button_row = QHBoxLayout()
|
|
68
|
+
button_row.setSpacing(10)
|
|
69
|
+
|
|
70
|
+
self._favorite_btn = QPushButton("\u2605 Favorite")
|
|
71
|
+
button_row.addWidget(self._favorite_btn)
|
|
72
|
+
button_row.addStretch()
|
|
73
|
+
|
|
74
|
+
layout.addLayout(button_row)
|
|
75
|
+
|
|
76
|
+
def _connect_signals(self):
|
|
77
|
+
"""Connect button signals."""
|
|
78
|
+
self._back_btn.clicked.connect(self._go_back)
|
|
79
|
+
self._favorite_btn.clicked.connect(self._toggle_favorite)
|
|
80
|
+
|
|
81
|
+
def load_transcript(self, filepath: str | Path):
|
|
82
|
+
"""Load and display a transcript."""
|
|
83
|
+
self._current_path = Path(filepath)
|
|
84
|
+
|
|
85
|
+
if not self._current_path.exists():
|
|
86
|
+
self._title.setText("File Not Found")
|
|
87
|
+
self._file_info.setText("")
|
|
88
|
+
self._content.setText(f"Could not find: {filepath}")
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
# Update title
|
|
92
|
+
config = get_config()
|
|
93
|
+
is_favorite = config.is_favorite(self._current_path.name)
|
|
94
|
+
star = "\u2605 " if is_favorite else ""
|
|
95
|
+
self._title.setText(f"{star}{self._current_path.name}")
|
|
96
|
+
|
|
97
|
+
# Update file info
|
|
98
|
+
stat = self._current_path.stat()
|
|
99
|
+
mtime = datetime.fromtimestamp(stat.st_mtime)
|
|
100
|
+
size = format_size(stat.st_size)
|
|
101
|
+
self._file_info.setText(f"Modified: {mtime.strftime('%Y-%m-%d %H:%M')} | Size: {size}")
|
|
102
|
+
|
|
103
|
+
# Update favorite button
|
|
104
|
+
if is_favorite:
|
|
105
|
+
self._favorite_btn.setText("\u2606 Unfavorite")
|
|
106
|
+
self._favorite_btn.setStyleSheet("background-color: #ca5010;")
|
|
107
|
+
else:
|
|
108
|
+
self._favorite_btn.setText("\u2605 Favorite")
|
|
109
|
+
self._favorite_btn.setStyleSheet("")
|
|
110
|
+
|
|
111
|
+
# Load content
|
|
112
|
+
try:
|
|
113
|
+
content = self._current_path.read_text()
|
|
114
|
+
self._content.setText(content)
|
|
115
|
+
except Exception as e:
|
|
116
|
+
self._content.setText(f"Error reading file: {e}")
|
|
117
|
+
|
|
118
|
+
def _go_back(self):
|
|
119
|
+
"""Go back to recordings."""
|
|
120
|
+
app_state = get_app_state()
|
|
121
|
+
app_state.navigate_to.emit("recordings")
|
|
122
|
+
|
|
123
|
+
def _toggle_favorite(self):
|
|
124
|
+
"""Toggle favorite status."""
|
|
125
|
+
if not self._current_path:
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
config = get_config()
|
|
129
|
+
if config.is_favorite(self._current_path.name):
|
|
130
|
+
config.remove_favorite(self._current_path.name)
|
|
131
|
+
else:
|
|
132
|
+
config.add_favorite(self._current_path.name)
|
|
133
|
+
|
|
134
|
+
# Reload to update display
|
|
135
|
+
self.load_transcript(self._current_path)
|
|
136
|
+
|
|
137
|
+
def refresh(self):
|
|
138
|
+
"""Refresh the viewer."""
|
|
139
|
+
if self._current_path:
|
|
140
|
+
self.load_transcript(self._current_path)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Dark theme colors and styling for Meeting Noter GUI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from PySide6.QtWidgets import QApplication
|
|
8
|
+
|
|
9
|
+
# Color palette (Discord/VS Code inspired)
|
|
10
|
+
COLORS = {
|
|
11
|
+
# Background colors
|
|
12
|
+
"bg_primary": "#1e1e1e", # Main content background
|
|
13
|
+
"bg_secondary": "#252526", # Sidebar, panels
|
|
14
|
+
"bg_tertiary": "#2d2d2d", # Elevated panels, cards
|
|
15
|
+
"bg_hover": "#3c3c3c", # Hover states
|
|
16
|
+
"bg_selected": "#094771", # Selected items (blue tint)
|
|
17
|
+
# Text colors
|
|
18
|
+
"text_primary": "#cccccc", # Primary text
|
|
19
|
+
"text_secondary": "#858585", # Muted/secondary text
|
|
20
|
+
"text_disabled": "#5a5a5a", # Disabled text
|
|
21
|
+
"text_white": "#ffffff", # White text
|
|
22
|
+
# Accent colors
|
|
23
|
+
"accent_primary": "#0078d4", # Primary actions (blue)
|
|
24
|
+
"accent_success": "#4ec9b0", # Success states (green-cyan)
|
|
25
|
+
"accent_warning": "#dcdcaa", # Warnings (yellow)
|
|
26
|
+
"accent_error": "#f14c4c", # Errors (red)
|
|
27
|
+
"accent_info": "#9cdcfe", # Info (light blue)
|
|
28
|
+
# Status colors
|
|
29
|
+
"status_recording": "#f14c4c", # Red dot when recording
|
|
30
|
+
"status_ready": "#4ec9b0", # Green dot when ready
|
|
31
|
+
"status_idle": "#5a5a5a", # Gray dot when stopped
|
|
32
|
+
# Border colors
|
|
33
|
+
"border_default": "#3c3c3c",
|
|
34
|
+
"border_focus": "#007acc",
|
|
35
|
+
# Sidebar
|
|
36
|
+
"sidebar_bg": "#252526",
|
|
37
|
+
"sidebar_item_hover": "#2a2d2e",
|
|
38
|
+
"sidebar_item_selected": "#37373d",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_stylesheet() -> str:
|
|
43
|
+
"""Load the QSS stylesheet."""
|
|
44
|
+
stylesheet_path = Path(__file__).parent / "styles.qss"
|
|
45
|
+
if stylesheet_path.exists():
|
|
46
|
+
return stylesheet_path.read_text()
|
|
47
|
+
return ""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def apply_theme(app: QApplication) -> None:
|
|
51
|
+
"""Apply the dark theme to the application."""
|
|
52
|
+
stylesheet = get_stylesheet()
|
|
53
|
+
app.setStyleSheet(stylesheet)
|