lazylabel-gui 1.1.4__py3-none-any.whl → 1.1.6__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.
- lazylabel/__main__.py +6 -0
- lazylabel/config/settings.py +72 -65
- lazylabel/core/file_manager.py +123 -122
- lazylabel/core/model_manager.py +96 -95
- lazylabel/core/segment_manager.py +188 -183
- lazylabel/main.py +39 -37
- lazylabel/models/sam_model.py +211 -200
- lazylabel/ui/control_panel.py +283 -245
- lazylabel/ui/editable_vertex.py +2 -2
- lazylabel/ui/hotkey_dialog.py +417 -416
- lazylabel/ui/main_window.py +2020 -1860
- lazylabel/ui/photo_viewer.py +55 -1
- lazylabel/ui/widgets/adjustments_widget.py +372 -108
- lazylabel/ui/widgets/settings_widget.py +125 -113
- lazylabel/utils/logger.py +45 -0
- {lazylabel_gui-1.1.4.dist-info → lazylabel_gui-1.1.6.dist-info}/METADATA +2 -1
- {lazylabel_gui-1.1.4.dist-info → lazylabel_gui-1.1.6.dist-info}/RECORD +21 -19
- {lazylabel_gui-1.1.4.dist-info → lazylabel_gui-1.1.6.dist-info}/WHEEL +0 -0
- {lazylabel_gui-1.1.4.dist-info → lazylabel_gui-1.1.6.dist-info}/entry_points.txt +0 -0
- {lazylabel_gui-1.1.4.dist-info → lazylabel_gui-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {lazylabel_gui-1.1.4.dist-info → lazylabel_gui-1.1.6.dist-info}/top_level.txt +0 -0
lazylabel/ui/hotkey_dialog.py
CHANGED
@@ -1,416 +1,417 @@
|
|
1
|
-
"""Hotkey configuration dialog."""
|
2
|
-
|
3
|
-
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
|
4
|
-
from PyQt6.QtGui import QColor, QFont, QKeySequence
|
5
|
-
from PyQt6.QtWidgets import (
|
6
|
-
QDialog,
|
7
|
-
QHBoxLayout,
|
8
|
-
QHeaderView,
|
9
|
-
QLabel,
|
10
|
-
QLineEdit,
|
11
|
-
QMessageBox,
|
12
|
-
QPushButton,
|
13
|
-
QTableWidget,
|
14
|
-
QTableWidgetItem,
|
15
|
-
QTabWidget,
|
16
|
-
QVBoxLayout,
|
17
|
-
QWidget,
|
18
|
-
)
|
19
|
-
|
20
|
-
from ..config import HotkeyAction, HotkeyManager
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
self.
|
32
|
-
self.
|
33
|
-
self.
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
self.timeout_timer
|
38
|
-
self.timeout_timer.
|
39
|
-
self.
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
self.
|
51
|
-
self.
|
52
|
-
self.
|
53
|
-
self.
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
self.
|
59
|
-
self.
|
60
|
-
self.
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
self.
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
Qt.Key.
|
78
|
-
Qt.Key.
|
79
|
-
Qt.Key.
|
80
|
-
Qt.Key.
|
81
|
-
Qt.Key.
|
82
|
-
Qt.Key.
|
83
|
-
Qt.Key.
|
84
|
-
Qt.Key.
|
85
|
-
Qt.Key.
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
self.
|
113
|
-
self.
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
self.
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
self.
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
self.
|
146
|
-
self.
|
147
|
-
|
148
|
-
|
149
|
-
self.
|
150
|
-
self.
|
151
|
-
|
152
|
-
|
153
|
-
self.
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
title_font
|
163
|
-
title_font.
|
164
|
-
|
165
|
-
title.
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
"
|
172
|
-
|
173
|
-
|
174
|
-
instructions.
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
self.save_button
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
self.defaults_button
|
195
|
-
self.defaults_button.
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
self.close_button
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
table
|
217
|
-
table.
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
header.
|
225
|
-
header.setSectionResizeMode(
|
226
|
-
header.setSectionResizeMode(
|
227
|
-
header.setSectionResizeMode(
|
228
|
-
|
229
|
-
|
230
|
-
table.
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
name_item
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
desc_item
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
primary_edit
|
247
|
-
primary_edit.
|
248
|
-
|
249
|
-
|
250
|
-
primary_edit.
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
secondary_edit
|
261
|
-
secondary_edit.
|
262
|
-
|
263
|
-
|
264
|
-
secondary_edit.
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
"
|
290
|
-
"
|
291
|
-
"
|
292
|
-
"
|
293
|
-
"
|
294
|
-
"
|
295
|
-
"
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
self.
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
self.
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
"
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
self.
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
"
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
"Hotkeys
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
"
|
379
|
-
|
380
|
-
QMessageBox.StandardButton.No,
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
self.
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
"
|
403
|
-
|
404
|
-
|
405
|
-
| QMessageBox.StandardButton.
|
406
|
-
QMessageBox.StandardButton.
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
1
|
+
"""Hotkey configuration dialog."""
|
2
|
+
|
3
|
+
from PyQt6.QtCore import Qt, QTimer, pyqtSignal
|
4
|
+
from PyQt6.QtGui import QColor, QFont, QKeySequence
|
5
|
+
from PyQt6.QtWidgets import (
|
6
|
+
QDialog,
|
7
|
+
QHBoxLayout,
|
8
|
+
QHeaderView,
|
9
|
+
QLabel,
|
10
|
+
QLineEdit,
|
11
|
+
QMessageBox,
|
12
|
+
QPushButton,
|
13
|
+
QTableWidget,
|
14
|
+
QTableWidgetItem,
|
15
|
+
QTabWidget,
|
16
|
+
QVBoxLayout,
|
17
|
+
QWidget,
|
18
|
+
)
|
19
|
+
|
20
|
+
from ..config import HotkeyAction, HotkeyManager
|
21
|
+
from ..utils.logger import logger
|
22
|
+
|
23
|
+
|
24
|
+
class HotkeyLineEdit(QLineEdit):
|
25
|
+
"""Custom line edit that captures key sequences."""
|
26
|
+
|
27
|
+
key_captured = pyqtSignal(str)
|
28
|
+
|
29
|
+
def __init__(self, parent=None):
|
30
|
+
super().__init__(parent)
|
31
|
+
self.setReadOnly(True)
|
32
|
+
self.setPlaceholderText("Click and press a key (Esc to cancel)")
|
33
|
+
self.capturing = False
|
34
|
+
self.original_style = self.styleSheet()
|
35
|
+
|
36
|
+
# Timeout timer to auto-cancel capture
|
37
|
+
self.timeout_timer = QTimer()
|
38
|
+
self.timeout_timer.setSingleShot(True)
|
39
|
+
self.timeout_timer.timeout.connect(self._timeout_capture)
|
40
|
+
self.timeout_duration = 15000 # 15 seconds
|
41
|
+
|
42
|
+
def mousePressEvent(self, event):
|
43
|
+
"""Start capturing keys when clicked."""
|
44
|
+
if event.button() == Qt.MouseButton.LeftButton:
|
45
|
+
self.start_capture()
|
46
|
+
super().mousePressEvent(event)
|
47
|
+
|
48
|
+
def start_capture(self):
|
49
|
+
"""Start capturing key input."""
|
50
|
+
self.capturing = True
|
51
|
+
self.setText("Press a key (Esc to cancel)")
|
52
|
+
self.setStyleSheet("background-color: #ffeb3b; color: black;")
|
53
|
+
self.setFocus()
|
54
|
+
self.timeout_timer.start(self.timeout_duration)
|
55
|
+
|
56
|
+
def stop_capture(self):
|
57
|
+
"""Stop capturing key input."""
|
58
|
+
self.capturing = False
|
59
|
+
self.setStyleSheet(self.original_style)
|
60
|
+
self.clearFocus()
|
61
|
+
self.timeout_timer.stop()
|
62
|
+
|
63
|
+
def keyPressEvent(self, event):
|
64
|
+
"""Capture key press events."""
|
65
|
+
if not self.capturing:
|
66
|
+
super().keyPressEvent(event)
|
67
|
+
return
|
68
|
+
|
69
|
+
# Handle Escape key to cancel capture
|
70
|
+
if event.key() == Qt.Key.Key_Escape:
|
71
|
+
self.setText("") # Clear the field
|
72
|
+
self.stop_capture()
|
73
|
+
return
|
74
|
+
|
75
|
+
# Ignore modifier-only keys and other problematic keys
|
76
|
+
ignored_keys = {
|
77
|
+
Qt.Key.Key_Control,
|
78
|
+
Qt.Key.Key_Shift,
|
79
|
+
Qt.Key.Key_Alt,
|
80
|
+
Qt.Key.Key_Meta,
|
81
|
+
Qt.Key.Key_CapsLock,
|
82
|
+
Qt.Key.Key_NumLock,
|
83
|
+
Qt.Key.Key_ScrollLock,
|
84
|
+
Qt.Key.Key_unknown,
|
85
|
+
Qt.Key.Key_Tab,
|
86
|
+
Qt.Key.Key_Backtab,
|
87
|
+
}
|
88
|
+
|
89
|
+
if event.key() in ignored_keys:
|
90
|
+
return
|
91
|
+
|
92
|
+
try:
|
93
|
+
# Create key sequence - properly handle modifiers
|
94
|
+
modifiers = event.modifiers()
|
95
|
+
key = event.key()
|
96
|
+
|
97
|
+
# Skip invalid keys
|
98
|
+
if key == 0 or key == Qt.Key.Key_unknown:
|
99
|
+
return
|
100
|
+
|
101
|
+
# Convert modifiers to int and combine with key
|
102
|
+
modifier_int = (
|
103
|
+
int(modifiers.value) if hasattr(modifiers, "value") else int(modifiers)
|
104
|
+
)
|
105
|
+
key_combination = key | modifier_int
|
106
|
+
|
107
|
+
key_sequence = QKeySequence(key_combination)
|
108
|
+
key_string = key_sequence.toString()
|
109
|
+
|
110
|
+
# Only accept valid, non-empty key strings
|
111
|
+
if key_string and key_string.strip():
|
112
|
+
self.setText(key_string)
|
113
|
+
self.key_captured.emit(key_string)
|
114
|
+
self.stop_capture()
|
115
|
+
else:
|
116
|
+
# Invalid key combination, just ignore
|
117
|
+
return
|
118
|
+
|
119
|
+
except Exception as e:
|
120
|
+
logger.error(f"Error capturing key sequence: {e}")
|
121
|
+
# Cancel capture on any error
|
122
|
+
self.setText("")
|
123
|
+
self.stop_capture()
|
124
|
+
|
125
|
+
def focusOutEvent(self, event):
|
126
|
+
"""Stop capturing when focus is lost."""
|
127
|
+
if self.capturing:
|
128
|
+
self.stop_capture()
|
129
|
+
if not self.text() or self.text().startswith("Press a key"):
|
130
|
+
self.setText("")
|
131
|
+
super().focusOutEvent(event)
|
132
|
+
|
133
|
+
def _timeout_capture(self):
|
134
|
+
"""Handle capture timeout."""
|
135
|
+
if self.capturing:
|
136
|
+
self.setText("")
|
137
|
+
self.stop_capture()
|
138
|
+
|
139
|
+
|
140
|
+
class HotkeyDialog(QDialog):
|
141
|
+
"""Dialog for configuring hotkeys."""
|
142
|
+
|
143
|
+
def __init__(self, hotkey_manager: HotkeyManager, parent=None):
|
144
|
+
super().__init__(parent)
|
145
|
+
self.hotkey_manager = hotkey_manager
|
146
|
+
self.modified = False
|
147
|
+
self.key_widgets = {} # Maps (action_name, key_type) to widget
|
148
|
+
|
149
|
+
self.setWindowTitle("Hotkey Configuration")
|
150
|
+
self.setModal(True)
|
151
|
+
self.resize(800, 600)
|
152
|
+
|
153
|
+
self._setup_ui()
|
154
|
+
self._populate_hotkeys()
|
155
|
+
|
156
|
+
def _setup_ui(self):
|
157
|
+
"""Setup the dialog UI."""
|
158
|
+
layout = QVBoxLayout(self)
|
159
|
+
|
160
|
+
# Title
|
161
|
+
title = QLabel("Hotkey Configuration")
|
162
|
+
title_font = QFont()
|
163
|
+
title_font.setPointSize(16)
|
164
|
+
title_font.setBold(True)
|
165
|
+
title.setFont(title_font)
|
166
|
+
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
167
|
+
layout.addWidget(title)
|
168
|
+
|
169
|
+
# Instructions
|
170
|
+
instructions = QLabel(
|
171
|
+
"Click on a hotkey field and press the desired key combination. "
|
172
|
+
"Mouse-related actions cannot be modified."
|
173
|
+
)
|
174
|
+
instructions.setWordWrap(True)
|
175
|
+
instructions.setStyleSheet("color: #666; margin: 10px;")
|
176
|
+
layout.addWidget(instructions)
|
177
|
+
|
178
|
+
# Tab widget for categories
|
179
|
+
self.tab_widget = QTabWidget()
|
180
|
+
layout.addWidget(self.tab_widget)
|
181
|
+
|
182
|
+
# Button layout
|
183
|
+
button_layout = QHBoxLayout()
|
184
|
+
|
185
|
+
# Save button
|
186
|
+
self.save_button = QPushButton("Save Hotkeys")
|
187
|
+
self.save_button.setToolTip(
|
188
|
+
"Save hotkeys to file for persistence between sessions"
|
189
|
+
)
|
190
|
+
self.save_button.clicked.connect(self._save_hotkeys)
|
191
|
+
button_layout.addWidget(self.save_button)
|
192
|
+
|
193
|
+
# Defaults button
|
194
|
+
self.defaults_button = QPushButton("Reset to Defaults")
|
195
|
+
self.defaults_button.setToolTip("Reset all hotkeys to default values")
|
196
|
+
self.defaults_button.clicked.connect(self._reset_to_defaults)
|
197
|
+
button_layout.addWidget(self.defaults_button)
|
198
|
+
|
199
|
+
button_layout.addStretch()
|
200
|
+
|
201
|
+
# Close button
|
202
|
+
self.close_button = QPushButton("Close")
|
203
|
+
self.close_button.clicked.connect(self.accept)
|
204
|
+
button_layout.addWidget(self.close_button)
|
205
|
+
|
206
|
+
layout.addLayout(button_layout)
|
207
|
+
|
208
|
+
def _create_category_tab(
|
209
|
+
self, category_name: str, actions: list[HotkeyAction]
|
210
|
+
) -> QWidget:
|
211
|
+
"""Create a tab for a category of hotkeys."""
|
212
|
+
widget = QWidget()
|
213
|
+
layout = QVBoxLayout(widget)
|
214
|
+
|
215
|
+
# Create table
|
216
|
+
table = QTableWidget()
|
217
|
+
table.setColumnCount(4)
|
218
|
+
table.setHorizontalHeaderLabels(
|
219
|
+
["Action", "Description", "Primary Key", "Secondary Key"]
|
220
|
+
)
|
221
|
+
table.setRowCount(len(actions))
|
222
|
+
|
223
|
+
# Configure table
|
224
|
+
header = table.horizontalHeader()
|
225
|
+
header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
|
226
|
+
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
|
227
|
+
header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents)
|
228
|
+
header.setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents)
|
229
|
+
|
230
|
+
table.setAlternatingRowColors(True)
|
231
|
+
table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
|
232
|
+
|
233
|
+
# Populate table
|
234
|
+
for row, action in enumerate(actions):
|
235
|
+
# Action name
|
236
|
+
name_item = QTableWidgetItem(action.name.replace("_", " ").title())
|
237
|
+
name_item.setFlags(name_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
|
238
|
+
table.setItem(row, 0, name_item)
|
239
|
+
|
240
|
+
# Description
|
241
|
+
desc_item = QTableWidgetItem(action.description)
|
242
|
+
desc_item.setFlags(desc_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
|
243
|
+
table.setItem(row, 1, desc_item)
|
244
|
+
|
245
|
+
# Primary key
|
246
|
+
primary_edit = HotkeyLineEdit()
|
247
|
+
primary_edit.setText(action.primary_key or "")
|
248
|
+
primary_edit.setEnabled(not action.mouse_related)
|
249
|
+
if action.mouse_related:
|
250
|
+
primary_edit.setStyleSheet("background-color: #f0f0f0; color: #666;")
|
251
|
+
primary_edit.setToolTip("Mouse-related actions cannot be modified")
|
252
|
+
else:
|
253
|
+
primary_edit.key_captured.connect(
|
254
|
+
lambda key, name=action.name: self._update_primary_key(name, key)
|
255
|
+
)
|
256
|
+
table.setCellWidget(row, 2, primary_edit)
|
257
|
+
self.key_widgets[(action.name, "primary")] = primary_edit
|
258
|
+
|
259
|
+
# Secondary key
|
260
|
+
secondary_edit = HotkeyLineEdit()
|
261
|
+
secondary_edit.setText(action.secondary_key or "")
|
262
|
+
secondary_edit.setEnabled(not action.mouse_related)
|
263
|
+
if action.mouse_related:
|
264
|
+
secondary_edit.setStyleSheet("background-color: #f0f0f0; color: #666;")
|
265
|
+
secondary_edit.setToolTip("Mouse-related actions cannot be modified")
|
266
|
+
else:
|
267
|
+
secondary_edit.key_captured.connect(
|
268
|
+
lambda key, name=action.name: self._update_secondary_key(name, key)
|
269
|
+
)
|
270
|
+
table.setCellWidget(row, 3, secondary_edit)
|
271
|
+
self.key_widgets[(action.name, "secondary")] = secondary_edit
|
272
|
+
|
273
|
+
# Style mouse-related rows
|
274
|
+
if action.mouse_related:
|
275
|
+
for col in range(4):
|
276
|
+
item = table.item(row, col)
|
277
|
+
if item:
|
278
|
+
item.setBackground(QColor("#f8f8f8"))
|
279
|
+
|
280
|
+
layout.addWidget(table)
|
281
|
+
return widget
|
282
|
+
|
283
|
+
def _populate_hotkeys(self):
|
284
|
+
"""Populate the hotkey tabs."""
|
285
|
+
categories = self.hotkey_manager.get_actions_by_category()
|
286
|
+
|
287
|
+
# Define tab order
|
288
|
+
tab_order = [
|
289
|
+
"Modes",
|
290
|
+
"Actions",
|
291
|
+
"Navigation",
|
292
|
+
"Segments",
|
293
|
+
"View",
|
294
|
+
"Movement",
|
295
|
+
"Mouse",
|
296
|
+
"General",
|
297
|
+
]
|
298
|
+
|
299
|
+
for category in tab_order:
|
300
|
+
if category in categories:
|
301
|
+
tab_widget = self._create_category_tab(category, categories[category])
|
302
|
+
self.tab_widget.addTab(tab_widget, category)
|
303
|
+
|
304
|
+
# Add any remaining categories
|
305
|
+
for category, actions in categories.items():
|
306
|
+
if category not in tab_order:
|
307
|
+
tab_widget = self._create_category_tab(category, actions)
|
308
|
+
self.tab_widget.addTab(tab_widget, category)
|
309
|
+
|
310
|
+
def _update_primary_key(self, action_name: str, key: str):
|
311
|
+
"""Update primary key for an action."""
|
312
|
+
# Check for conflicts
|
313
|
+
conflict = self.hotkey_manager.is_key_in_use(key, exclude_action=action_name)
|
314
|
+
if conflict:
|
315
|
+
QMessageBox.warning(
|
316
|
+
self,
|
317
|
+
"Key Conflict",
|
318
|
+
f"The key '{key}' is already used by '{conflict.replace('_', ' ').title()}'. "
|
319
|
+
"Please choose a different key.",
|
320
|
+
)
|
321
|
+
# Reset the field
|
322
|
+
widget = self.key_widgets.get((action_name, "primary"))
|
323
|
+
if widget:
|
324
|
+
action = self.hotkey_manager.get_action(action_name)
|
325
|
+
widget.setText(action.primary_key if action else "")
|
326
|
+
return
|
327
|
+
|
328
|
+
# Update the hotkey
|
329
|
+
if self.hotkey_manager.set_primary_key(action_name, key):
|
330
|
+
self.modified = True
|
331
|
+
|
332
|
+
def _update_secondary_key(self, action_name: str, key: str):
|
333
|
+
"""Update secondary key for an action."""
|
334
|
+
# Allow empty key for secondary
|
335
|
+
if not key:
|
336
|
+
self.hotkey_manager.set_secondary_key(action_name, None)
|
337
|
+
self.modified = True
|
338
|
+
return
|
339
|
+
|
340
|
+
# Check for conflicts
|
341
|
+
conflict = self.hotkey_manager.is_key_in_use(key, exclude_action=action_name)
|
342
|
+
if conflict:
|
343
|
+
QMessageBox.warning(
|
344
|
+
self,
|
345
|
+
"Key Conflict",
|
346
|
+
f"The key '{key}' is already used by '{conflict.replace('_', ' ').title()}'. "
|
347
|
+
"Please choose a different key.",
|
348
|
+
)
|
349
|
+
# Reset the field
|
350
|
+
widget = self.key_widgets.get((action_name, "secondary"))
|
351
|
+
if widget:
|
352
|
+
action = self.hotkey_manager.get_action(action_name)
|
353
|
+
widget.setText(action.secondary_key or "")
|
354
|
+
return
|
355
|
+
|
356
|
+
# Update the hotkey
|
357
|
+
if self.hotkey_manager.set_secondary_key(action_name, key):
|
358
|
+
self.modified = True
|
359
|
+
|
360
|
+
def _save_hotkeys(self):
|
361
|
+
"""Save hotkeys to file."""
|
362
|
+
try:
|
363
|
+
self.hotkey_manager.save_hotkeys()
|
364
|
+
QMessageBox.information(
|
365
|
+
self,
|
366
|
+
"Hotkeys Saved",
|
367
|
+
"Hotkeys have been saved and will persist between sessions.",
|
368
|
+
)
|
369
|
+
except Exception as e:
|
370
|
+
QMessageBox.critical(
|
371
|
+
self, "Save Error", f"Failed to save hotkeys: {str(e)}"
|
372
|
+
)
|
373
|
+
|
374
|
+
def _reset_to_defaults(self):
|
375
|
+
"""Reset all hotkeys to defaults."""
|
376
|
+
reply = QMessageBox.question(
|
377
|
+
self,
|
378
|
+
"Reset Hotkeys",
|
379
|
+
"Are you sure you want to reset all hotkeys to their default values?",
|
380
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
381
|
+
QMessageBox.StandardButton.No,
|
382
|
+
)
|
383
|
+
|
384
|
+
if reply == QMessageBox.StandardButton.Yes:
|
385
|
+
self.hotkey_manager.reset_to_defaults()
|
386
|
+
self.modified = True
|
387
|
+
|
388
|
+
# Update all widgets
|
389
|
+
for (action_name, key_type), widget in self.key_widgets.items():
|
390
|
+
action = self.hotkey_manager.get_action(action_name)
|
391
|
+
if action:
|
392
|
+
if key_type == "primary":
|
393
|
+
widget.setText(action.primary_key or "")
|
394
|
+
else:
|
395
|
+
widget.setText(action.secondary_key or "")
|
396
|
+
|
397
|
+
def closeEvent(self, event):
|
398
|
+
"""Handle dialog close."""
|
399
|
+
if self.modified:
|
400
|
+
reply = QMessageBox.question(
|
401
|
+
self,
|
402
|
+
"Unsaved Changes",
|
403
|
+
"You have unsaved hotkey changes. Do you want to apply them for this session?",
|
404
|
+
QMessageBox.StandardButton.Yes
|
405
|
+
| QMessageBox.StandardButton.No
|
406
|
+
| QMessageBox.StandardButton.Cancel,
|
407
|
+
QMessageBox.StandardButton.Yes,
|
408
|
+
)
|
409
|
+
|
410
|
+
if reply == QMessageBox.StandardButton.Cancel:
|
411
|
+
event.ignore()
|
412
|
+
return
|
413
|
+
elif reply == QMessageBox.StandardButton.No:
|
414
|
+
# Reload from file to discard changes
|
415
|
+
self.hotkey_manager.load_hotkeys()
|
416
|
+
|
417
|
+
super().closeEvent(event)
|