lazylabel-gui 1.1.2__py3-none-any.whl → 1.1.4__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/__init__.py +9 -9
- lazylabel/config/__init__.py +7 -7
- lazylabel/config/hotkeys.py +96 -57
- lazylabel/config/paths.py +40 -41
- lazylabel/config/settings.py +65 -66
- lazylabel/core/__init__.py +7 -7
- lazylabel/core/file_manager.py +122 -106
- lazylabel/core/model_manager.py +95 -97
- lazylabel/core/segment_manager.py +183 -171
- lazylabel/main.py +37 -36
- lazylabel/models/__init__.py +5 -5
- lazylabel/models/sam_model.py +200 -195
- lazylabel/ui/__init__.py +8 -8
- lazylabel/ui/control_panel.py +245 -241
- lazylabel/ui/editable_vertex.py +64 -64
- lazylabel/ui/hotkey_dialog.py +416 -384
- lazylabel/ui/hoverable_pixelmap_item.py +22 -22
- lazylabel/ui/hoverable_polygon_item.py +38 -39
- lazylabel/ui/main_window.py +1860 -1659
- lazylabel/ui/numeric_table_widget_item.py +9 -9
- lazylabel/ui/photo_viewer.py +51 -54
- lazylabel/ui/reorderable_class_table.py +60 -61
- lazylabel/ui/right_panel.py +332 -315
- lazylabel/ui/widgets/__init__.py +8 -8
- lazylabel/ui/widgets/adjustments_widget.py +108 -108
- lazylabel/ui/widgets/model_selection_widget.py +101 -94
- lazylabel/ui/widgets/settings_widget.py +113 -106
- lazylabel/ui/widgets/status_bar.py +109 -109
- lazylabel/utils/__init__.py +6 -6
- lazylabel/utils/custom_file_system_model.py +133 -132
- lazylabel/utils/utils.py +12 -12
- {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.4.dist-info}/METADATA +243 -197
- lazylabel_gui-1.1.4.dist-info/RECORD +37 -0
- {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.4.dist-info}/licenses/LICENSE +21 -21
- lazylabel_gui-1.1.2.dist-info/RECORD +0 -37
- {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.4.dist-info}/WHEEL +0 -0
- {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.4.dist-info}/entry_points.txt +0 -0
- {lazylabel_gui-1.1.2.dist-info → lazylabel_gui-1.1.4.dist-info}/top_level.txt +0 -0
lazylabel/ui/right_panel.py
CHANGED
@@ -1,315 +1,332 @@
|
|
1
|
-
"""Right panel with file explorer and segment management."""
|
2
|
-
|
3
|
-
from PyQt6.
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
)
|
16
|
-
|
17
|
-
from
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
self.
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
self.btn_popout
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
self.
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
#
|
67
|
-
v_splitter
|
68
|
-
|
69
|
-
#
|
70
|
-
self.
|
71
|
-
|
72
|
-
#
|
73
|
-
self.
|
74
|
-
|
75
|
-
#
|
76
|
-
self.
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
self.
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
self.
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
self.
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
def
|
103
|
-
"""Setup
|
104
|
-
|
105
|
-
layout = QVBoxLayout(
|
106
|
-
layout.setContentsMargins(0, 0, 0, 0)
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
self.
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
)
|
122
|
-
|
123
|
-
|
124
|
-
)
|
125
|
-
|
126
|
-
|
127
|
-
)
|
128
|
-
self.
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
self.
|
135
|
-
|
136
|
-
|
137
|
-
)
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
"
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
self.
|
158
|
-
|
159
|
-
)
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
)
|
168
|
-
|
169
|
-
layout.addWidget(
|
170
|
-
|
171
|
-
self.
|
172
|
-
self.
|
173
|
-
"
|
174
|
-
)
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
self.
|
184
|
-
|
185
|
-
|
186
|
-
self.
|
187
|
-
self.
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
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
|
-
def
|
297
|
-
"""
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
def
|
309
|
-
"""
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
1
|
+
"""Right panel with file explorer and segment management."""
|
2
|
+
|
3
|
+
from PyQt6.QtCore import Qt, pyqtSignal
|
4
|
+
from PyQt6.QtWidgets import (
|
5
|
+
QComboBox,
|
6
|
+
QHBoxLayout,
|
7
|
+
QHeaderView,
|
8
|
+
QLabel,
|
9
|
+
QPushButton,
|
10
|
+
QSplitter,
|
11
|
+
QTableWidget,
|
12
|
+
QTreeView,
|
13
|
+
QVBoxLayout,
|
14
|
+
QWidget,
|
15
|
+
)
|
16
|
+
|
17
|
+
from .reorderable_class_table import ReorderableClassTable
|
18
|
+
|
19
|
+
|
20
|
+
class RightPanel(QWidget):
|
21
|
+
"""Right panel with file explorer and segment management."""
|
22
|
+
|
23
|
+
# Signals
|
24
|
+
open_folder_requested = pyqtSignal()
|
25
|
+
image_selected = pyqtSignal("QModelIndex")
|
26
|
+
merge_selection_requested = pyqtSignal()
|
27
|
+
delete_selection_requested = pyqtSignal()
|
28
|
+
segments_selection_changed = pyqtSignal()
|
29
|
+
class_alias_changed = pyqtSignal(int, str) # class_id, alias
|
30
|
+
reassign_classes_requested = pyqtSignal()
|
31
|
+
class_filter_changed = pyqtSignal()
|
32
|
+
class_toggled = pyqtSignal(int) # class_id
|
33
|
+
pop_out_requested = pyqtSignal()
|
34
|
+
|
35
|
+
def __init__(self, parent=None):
|
36
|
+
super().__init__(parent)
|
37
|
+
self.setMinimumWidth(50) # Allow collapsing but maintain minimum
|
38
|
+
self.preferred_width = 350 # Store preferred width for expansion
|
39
|
+
self._setup_ui()
|
40
|
+
self._connect_signals()
|
41
|
+
|
42
|
+
def _setup_ui(self):
|
43
|
+
"""Setup the UI layout."""
|
44
|
+
self.v_layout = QVBoxLayout(self)
|
45
|
+
|
46
|
+
# Top button row
|
47
|
+
toggle_layout = QHBoxLayout()
|
48
|
+
|
49
|
+
self.btn_popout = QPushButton("⋯")
|
50
|
+
self.btn_popout.setToolTip("Pop out panel to separate window")
|
51
|
+
self.btn_popout.setMaximumWidth(30)
|
52
|
+
toggle_layout.addWidget(self.btn_popout)
|
53
|
+
|
54
|
+
toggle_layout.addStretch()
|
55
|
+
|
56
|
+
self.v_layout.addLayout(toggle_layout)
|
57
|
+
|
58
|
+
# Main controls widget
|
59
|
+
self.main_controls_widget = QWidget()
|
60
|
+
main_layout = QVBoxLayout(self.main_controls_widget)
|
61
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
62
|
+
|
63
|
+
# Vertical splitter for sections
|
64
|
+
v_splitter = QSplitter(Qt.Orientation.Vertical)
|
65
|
+
|
66
|
+
# File explorer section
|
67
|
+
self._setup_file_explorer(v_splitter)
|
68
|
+
|
69
|
+
# Segment management section
|
70
|
+
self._setup_segment_management(v_splitter)
|
71
|
+
|
72
|
+
# Class management section
|
73
|
+
self._setup_class_management(v_splitter)
|
74
|
+
|
75
|
+
# Action history section (new)
|
76
|
+
self.action_pane_widget = QWidget() # Placeholder for ActionPane
|
77
|
+
action_pane_layout = QVBoxLayout(self.action_pane_widget)
|
78
|
+
action_pane_layout.setContentsMargins(0, 0, 0, 0)
|
79
|
+
v_splitter.addWidget(self.action_pane_widget)
|
80
|
+
|
81
|
+
main_layout.addWidget(v_splitter)
|
82
|
+
|
83
|
+
# Status label
|
84
|
+
self.status_label = QLabel("")
|
85
|
+
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
86
|
+
main_layout.addWidget(self.status_label)
|
87
|
+
|
88
|
+
self.v_layout.addWidget(self.main_controls_widget)
|
89
|
+
|
90
|
+
def set_action_pane(self, action_pane_widget):
|
91
|
+
"""Sets the ActionPane widget into the right panel."""
|
92
|
+
# Clear existing layout in the placeholder widget
|
93
|
+
while self.action_pane_widget.layout().count():
|
94
|
+
item = self.action_pane_widget.layout().takeAt(0)
|
95
|
+
if item.widget():
|
96
|
+
item.widget().setParent(None)
|
97
|
+
self.action_pane_widget.layout().addWidget(action_pane_widget)
|
98
|
+
action_pane_widget.setParent(
|
99
|
+
self.action_pane_widget
|
100
|
+
) # Ensure correct parentage
|
101
|
+
|
102
|
+
def _setup_file_explorer(self, splitter):
|
103
|
+
"""Setup file explorer section."""
|
104
|
+
file_explorer_widget = QWidget()
|
105
|
+
layout = QVBoxLayout(file_explorer_widget)
|
106
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
107
|
+
|
108
|
+
self.btn_open_folder = QPushButton("Open Image Folder")
|
109
|
+
self.btn_open_folder.setToolTip("Open a directory of images")
|
110
|
+
layout.addWidget(self.btn_open_folder)
|
111
|
+
|
112
|
+
self.file_tree = QTreeView()
|
113
|
+
layout.addWidget(self.file_tree)
|
114
|
+
|
115
|
+
splitter.addWidget(file_explorer_widget)
|
116
|
+
|
117
|
+
def _setup_segment_management(self, splitter):
|
118
|
+
"""Setup segment management section."""
|
119
|
+
segment_widget = QWidget()
|
120
|
+
layout = QVBoxLayout(segment_widget)
|
121
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
122
|
+
|
123
|
+
# Class filter
|
124
|
+
filter_layout = QHBoxLayout()
|
125
|
+
filter_layout.addWidget(QLabel("Filter Class:"))
|
126
|
+
self.class_filter_combo = QComboBox()
|
127
|
+
self.class_filter_combo.setToolTip("Filter segments list by class")
|
128
|
+
filter_layout.addWidget(self.class_filter_combo)
|
129
|
+
layout.addLayout(filter_layout)
|
130
|
+
|
131
|
+
# Segment table
|
132
|
+
self.segment_table = QTableWidget()
|
133
|
+
self.segment_table.setColumnCount(3)
|
134
|
+
self.segment_table.setHorizontalHeaderLabels(
|
135
|
+
["Segment ID", "Class ID", "Alias"]
|
136
|
+
)
|
137
|
+
self.segment_table.horizontalHeader().setSectionResizeMode(
|
138
|
+
QHeaderView.ResizeMode.Stretch
|
139
|
+
)
|
140
|
+
self.segment_table.setSelectionBehavior(
|
141
|
+
QTableWidget.SelectionBehavior.SelectRows
|
142
|
+
)
|
143
|
+
self.segment_table.setSortingEnabled(True)
|
144
|
+
self.segment_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
|
145
|
+
layout.addWidget(self.segment_table)
|
146
|
+
|
147
|
+
# Action buttons
|
148
|
+
action_layout = QHBoxLayout()
|
149
|
+
self.btn_merge_selection = QPushButton("Merge to Class")
|
150
|
+
self.btn_merge_selection.setToolTip(
|
151
|
+
"Merge selected segments into a single class (M)"
|
152
|
+
)
|
153
|
+
self.btn_delete_selection = QPushButton("Delete")
|
154
|
+
self.btn_delete_selection.setToolTip(
|
155
|
+
"Delete selected segments (Delete/Backspace)"
|
156
|
+
)
|
157
|
+
action_layout.addWidget(self.btn_merge_selection)
|
158
|
+
action_layout.addWidget(self.btn_delete_selection)
|
159
|
+
layout.addLayout(action_layout)
|
160
|
+
|
161
|
+
splitter.addWidget(segment_widget)
|
162
|
+
|
163
|
+
def _setup_class_management(self, splitter):
|
164
|
+
"""Setup class management section."""
|
165
|
+
class_widget = QWidget()
|
166
|
+
layout = QVBoxLayout(class_widget)
|
167
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
168
|
+
|
169
|
+
layout.addWidget(QLabel("Class Order:"))
|
170
|
+
|
171
|
+
self.class_table = ReorderableClassTable()
|
172
|
+
self.class_table.setToolTip(
|
173
|
+
"Double-click to set class aliases and drag to reorder channels for saving.\nClick once to toggle as active class for new segments."
|
174
|
+
)
|
175
|
+
self.class_table.setColumnCount(2)
|
176
|
+
self.class_table.setHorizontalHeaderLabels(["Alias", "Class ID"])
|
177
|
+
self.class_table.horizontalHeader().setSectionResizeMode(
|
178
|
+
0, QHeaderView.ResizeMode.Stretch
|
179
|
+
)
|
180
|
+
self.class_table.horizontalHeader().setSectionResizeMode(
|
181
|
+
1, QHeaderView.ResizeMode.ResizeToContents
|
182
|
+
)
|
183
|
+
self.class_table.setEditTriggers(QTableWidget.EditTrigger.DoubleClicked)
|
184
|
+
layout.addWidget(self.class_table)
|
185
|
+
|
186
|
+
self.btn_reassign_classes = QPushButton("Reassign Class IDs")
|
187
|
+
self.btn_reassign_classes.setToolTip(
|
188
|
+
"Re-index class channels based on the current order in this table"
|
189
|
+
)
|
190
|
+
layout.addWidget(self.btn_reassign_classes)
|
191
|
+
|
192
|
+
splitter.addWidget(class_widget)
|
193
|
+
|
194
|
+
def _connect_signals(self):
|
195
|
+
"""Connect internal signals."""
|
196
|
+
self.btn_open_folder.clicked.connect(self.open_folder_requested)
|
197
|
+
self.file_tree.doubleClicked.connect(self.image_selected)
|
198
|
+
self.btn_merge_selection.clicked.connect(self.merge_selection_requested)
|
199
|
+
self.btn_delete_selection.clicked.connect(self.delete_selection_requested)
|
200
|
+
self.segment_table.itemSelectionChanged.connect(self.segments_selection_changed)
|
201
|
+
self.class_table.itemChanged.connect(self._handle_class_alias_change)
|
202
|
+
self.class_table.cellClicked.connect(self._handle_class_toggle)
|
203
|
+
self.btn_reassign_classes.clicked.connect(self.reassign_classes_requested)
|
204
|
+
self.class_filter_combo.currentIndexChanged.connect(self.class_filter_changed)
|
205
|
+
self.btn_popout.clicked.connect(self.pop_out_requested)
|
206
|
+
|
207
|
+
def mouseDoubleClickEvent(self, event):
|
208
|
+
"""Handle double-click to expand collapsed panel."""
|
209
|
+
if (
|
210
|
+
self.width() < 50
|
211
|
+
and self.parent()
|
212
|
+
and hasattr(self.parent(), "_expand_right_panel")
|
213
|
+
):
|
214
|
+
self.parent()._expand_right_panel()
|
215
|
+
super().mouseDoubleClickEvent(event)
|
216
|
+
|
217
|
+
def _handle_class_alias_change(self, item):
|
218
|
+
"""Handle class alias change in table."""
|
219
|
+
if item.column() != 0: # Only handle alias column
|
220
|
+
return
|
221
|
+
|
222
|
+
class_table = self.class_table
|
223
|
+
id_item = class_table.item(item.row(), 1)
|
224
|
+
if id_item:
|
225
|
+
try:
|
226
|
+
class_id = int(id_item.text())
|
227
|
+
self.class_alias_changed.emit(class_id, item.text())
|
228
|
+
except (ValueError, AttributeError):
|
229
|
+
pass
|
230
|
+
|
231
|
+
def _handle_class_toggle(self, row, column):
|
232
|
+
"""Handle class table cell click for toggling active class."""
|
233
|
+
# Get the class ID from the clicked row
|
234
|
+
id_item = self.class_table.item(row, 1)
|
235
|
+
if id_item:
|
236
|
+
try:
|
237
|
+
class_id = int(id_item.text())
|
238
|
+
self.class_toggled.emit(class_id)
|
239
|
+
except (ValueError, AttributeError):
|
240
|
+
pass
|
241
|
+
|
242
|
+
def update_active_class_display(self, active_class_id):
|
243
|
+
"""Update the visual display to show which class is active."""
|
244
|
+
# Block signals to prevent triggering change events during update
|
245
|
+
self.class_table.blockSignals(True)
|
246
|
+
|
247
|
+
for row in range(self.class_table.rowCount()):
|
248
|
+
id_item = self.class_table.item(row, 1)
|
249
|
+
alias_item = self.class_table.item(row, 0)
|
250
|
+
if id_item and alias_item:
|
251
|
+
try:
|
252
|
+
class_id = int(id_item.text())
|
253
|
+
if class_id == active_class_id:
|
254
|
+
# Make active class bold and add indicator
|
255
|
+
font = alias_item.font()
|
256
|
+
font.setBold(True)
|
257
|
+
alias_item.setFont(font)
|
258
|
+
id_item.setFont(font)
|
259
|
+
# Add visual indicator
|
260
|
+
if not alias_item.text().startswith("🔸 "):
|
261
|
+
alias_item.setText(f"🔸 {alias_item.text()}")
|
262
|
+
else:
|
263
|
+
# Make inactive classes normal
|
264
|
+
font = alias_item.font()
|
265
|
+
font.setBold(False)
|
266
|
+
alias_item.setFont(font)
|
267
|
+
id_item.setFont(font)
|
268
|
+
# Remove visual indicator
|
269
|
+
if alias_item.text().startswith("🔸 "):
|
270
|
+
alias_item.setText(alias_item.text()[2:])
|
271
|
+
except (ValueError, AttributeError):
|
272
|
+
pass
|
273
|
+
|
274
|
+
# Re-enable signals
|
275
|
+
self.class_table.blockSignals(False)
|
276
|
+
|
277
|
+
def setup_file_model(self, file_model):
|
278
|
+
"""Setup the file model for the tree view."""
|
279
|
+
self.file_tree.setModel(file_model)
|
280
|
+
self.file_tree.setColumnWidth(0, 200)
|
281
|
+
|
282
|
+
def set_folder(self, folder_path, file_model):
|
283
|
+
"""Set the folder for file browsing."""
|
284
|
+
self.file_tree.setRootIndex(file_model.setRootPath(folder_path))
|
285
|
+
|
286
|
+
def get_selected_segment_indices(self):
|
287
|
+
"""Get indices of selected segments."""
|
288
|
+
selected_items = self.segment_table.selectedItems()
|
289
|
+
selected_rows = sorted({item.row() for item in selected_items})
|
290
|
+
return [
|
291
|
+
self.segment_table.item(row, 0).data(Qt.ItemDataRole.UserRole)
|
292
|
+
for row in selected_rows
|
293
|
+
if self.segment_table.item(row, 0)
|
294
|
+
]
|
295
|
+
|
296
|
+
def get_class_order(self):
|
297
|
+
"""Get the current class order from the class table."""
|
298
|
+
ordered_ids = []
|
299
|
+
for row in range(self.class_table.rowCount()):
|
300
|
+
id_item = self.class_table.item(row, 1)
|
301
|
+
if id_item and id_item.text():
|
302
|
+
try:
|
303
|
+
ordered_ids.append(int(id_item.text()))
|
304
|
+
except ValueError:
|
305
|
+
continue
|
306
|
+
return ordered_ids
|
307
|
+
|
308
|
+
def clear_selections(self):
|
309
|
+
"""Clear all selections."""
|
310
|
+
self.segment_table.clearSelection()
|
311
|
+
self.class_table.clearSelection()
|
312
|
+
|
313
|
+
def select_all_segments(self):
|
314
|
+
"""Select all segments."""
|
315
|
+
self.segment_table.selectAll()
|
316
|
+
|
317
|
+
def set_status(self, message):
|
318
|
+
"""Set status message."""
|
319
|
+
self.status_label.setText(message)
|
320
|
+
|
321
|
+
def clear_status(self):
|
322
|
+
"""Clear status message."""
|
323
|
+
self.status_label.clear()
|
324
|
+
|
325
|
+
def set_popout_mode(self, is_popped_out: bool):
|
326
|
+
"""Update the pop-out button based on panel state."""
|
327
|
+
if is_popped_out:
|
328
|
+
self.btn_popout.setText("⇤")
|
329
|
+
self.btn_popout.setToolTip("Return panel to main window")
|
330
|
+
else:
|
331
|
+
self.btn_popout.setText("⋯")
|
332
|
+
self.btn_popout.setToolTip("Pop out panel to separate window")
|