lazylabel-gui 1.1.0__py3-none-any.whl → 1.1.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.
- lazylabel/core/model_manager.py +22 -19
- lazylabel/core/segment_manager.py +65 -34
- lazylabel/main.py +17 -3
- lazylabel/models/sam_model.py +72 -31
- lazylabel/ui/control_panel.py +83 -66
- lazylabel/ui/main_window.py +322 -40
- lazylabel/ui/right_panel.py +149 -73
- lazylabel/ui/widgets/__init__.py +2 -1
- lazylabel/ui/widgets/status_bar.py +109 -0
- {lazylabel_gui-1.1.0.dist-info → lazylabel_gui-1.1.1.dist-info}/METADATA +1 -1
- {lazylabel_gui-1.1.0.dist-info → lazylabel_gui-1.1.1.dist-info}/RECORD +15 -14
- {lazylabel_gui-1.1.0.dist-info → lazylabel_gui-1.1.1.dist-info}/WHEEL +0 -0
- {lazylabel_gui-1.1.0.dist-info → lazylabel_gui-1.1.1.dist-info}/entry_points.txt +0 -0
- {lazylabel_gui-1.1.0.dist-info → lazylabel_gui-1.1.1.dist-info}/licenses/LICENSE +0 -0
- {lazylabel_gui-1.1.0.dist-info → lazylabel_gui-1.1.1.dist-info}/top_level.txt +0 -0
lazylabel/ui/right_panel.py
CHANGED
@@ -1,8 +1,17 @@
|
|
1
1
|
"""Right panel with file explorer and segment management."""
|
2
2
|
|
3
3
|
from PyQt6.QtWidgets import (
|
4
|
-
QWidget,
|
5
|
-
|
4
|
+
QWidget,
|
5
|
+
QVBoxLayout,
|
6
|
+
QPushButton,
|
7
|
+
QLabel,
|
8
|
+
QHBoxLayout,
|
9
|
+
QTableWidget,
|
10
|
+
QTreeView,
|
11
|
+
QComboBox,
|
12
|
+
QSplitter,
|
13
|
+
QSpacerItem,
|
14
|
+
QHeaderView,
|
6
15
|
)
|
7
16
|
from PyQt6.QtCore import Qt, pyqtSignal
|
8
17
|
from PyQt6.QtGui import QBrush, QColor
|
@@ -13,82 +22,89 @@ from .numeric_table_widget_item import NumericTableWidgetItem
|
|
13
22
|
|
14
23
|
class RightPanel(QWidget):
|
15
24
|
"""Right panel with file explorer and segment management."""
|
16
|
-
|
25
|
+
|
17
26
|
# Signals
|
18
27
|
open_folder_requested = pyqtSignal()
|
19
|
-
image_selected = pyqtSignal(
|
28
|
+
image_selected = pyqtSignal("QModelIndex")
|
20
29
|
merge_selection_requested = pyqtSignal()
|
21
30
|
delete_selection_requested = pyqtSignal()
|
22
31
|
segments_selection_changed = pyqtSignal()
|
23
32
|
class_alias_changed = pyqtSignal(int, str) # class_id, alias
|
24
33
|
reassign_classes_requested = pyqtSignal()
|
25
34
|
class_filter_changed = pyqtSignal()
|
26
|
-
|
35
|
+
class_toggled = pyqtSignal(int) # class_id
|
36
|
+
pop_out_requested = pyqtSignal()
|
37
|
+
|
27
38
|
def __init__(self, parent=None):
|
28
39
|
super().__init__(parent)
|
29
|
-
self.
|
40
|
+
self.setMinimumWidth(50) # Allow collapsing but maintain minimum
|
41
|
+
self.preferred_width = 350 # Store preferred width for expansion
|
30
42
|
self._setup_ui()
|
31
43
|
self._connect_signals()
|
32
|
-
|
44
|
+
|
33
45
|
def _setup_ui(self):
|
34
46
|
"""Setup the UI layout."""
|
35
47
|
self.v_layout = QVBoxLayout(self)
|
36
|
-
|
37
|
-
#
|
48
|
+
|
49
|
+
# Top button row
|
38
50
|
toggle_layout = QHBoxLayout()
|
51
|
+
|
52
|
+
self.btn_popout = QPushButton("⋯")
|
53
|
+
self.btn_popout.setToolTip("Pop out panel to separate window")
|
54
|
+
self.btn_popout.setMaximumWidth(30)
|
55
|
+
toggle_layout.addWidget(self.btn_popout)
|
56
|
+
|
39
57
|
toggle_layout.addStretch()
|
40
|
-
|
41
|
-
self.btn_toggle_visibility.setToolTip("Hide this panel")
|
42
|
-
toggle_layout.addWidget(self.btn_toggle_visibility)
|
58
|
+
|
43
59
|
self.v_layout.addLayout(toggle_layout)
|
44
|
-
|
60
|
+
|
45
61
|
# Main controls widget
|
46
62
|
self.main_controls_widget = QWidget()
|
47
63
|
main_layout = QVBoxLayout(self.main_controls_widget)
|
48
64
|
main_layout.setContentsMargins(0, 0, 0, 0)
|
49
|
-
|
65
|
+
|
50
66
|
# Vertical splitter for sections
|
51
67
|
v_splitter = QSplitter(Qt.Orientation.Vertical)
|
52
|
-
|
68
|
+
|
53
69
|
# File explorer section
|
54
70
|
self._setup_file_explorer(v_splitter)
|
55
|
-
|
71
|
+
|
56
72
|
# Segment management section
|
57
73
|
self._setup_segment_management(v_splitter)
|
58
|
-
|
74
|
+
|
59
75
|
# Class management section
|
60
76
|
self._setup_class_management(v_splitter)
|
61
|
-
|
77
|
+
|
62
78
|
main_layout.addWidget(v_splitter)
|
63
|
-
|
79
|
+
|
64
80
|
# Status label
|
65
81
|
self.status_label = QLabel("")
|
66
82
|
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
67
83
|
main_layout.addWidget(self.status_label)
|
68
|
-
|
84
|
+
|
69
85
|
self.v_layout.addWidget(self.main_controls_widget)
|
70
|
-
|
86
|
+
|
71
87
|
def _setup_file_explorer(self, splitter):
|
72
88
|
"""Setup file explorer section."""
|
73
89
|
file_explorer_widget = QWidget()
|
74
90
|
layout = QVBoxLayout(file_explorer_widget)
|
75
91
|
layout.setContentsMargins(0, 0, 0, 0)
|
76
|
-
|
92
|
+
|
77
93
|
self.btn_open_folder = QPushButton("Open Image Folder")
|
78
94
|
self.btn_open_folder.setToolTip("Open a directory of images")
|
79
95
|
layout.addWidget(self.btn_open_folder)
|
80
|
-
|
96
|
+
|
81
97
|
self.file_tree = QTreeView()
|
82
98
|
layout.addWidget(self.file_tree)
|
83
|
-
|
99
|
+
|
84
100
|
splitter.addWidget(file_explorer_widget)
|
85
|
-
|
101
|
+
|
86
102
|
def _setup_segment_management(self, splitter):
|
87
103
|
"""Setup segment management section."""
|
88
104
|
segment_widget = QWidget()
|
89
105
|
layout = QVBoxLayout(segment_widget)
|
90
106
|
layout.setContentsMargins(0, 0, 0, 0)
|
91
|
-
|
107
|
+
|
92
108
|
# Class filter
|
93
109
|
filter_layout = QHBoxLayout()
|
94
110
|
filter_layout.addWidget(QLabel("Filter Class:"))
|
@@ -96,56 +112,70 @@ class RightPanel(QWidget):
|
|
96
112
|
self.class_filter_combo.setToolTip("Filter segments list by class")
|
97
113
|
filter_layout.addWidget(self.class_filter_combo)
|
98
114
|
layout.addLayout(filter_layout)
|
99
|
-
|
115
|
+
|
100
116
|
# Segment table
|
101
117
|
self.segment_table = QTableWidget()
|
102
118
|
self.segment_table.setColumnCount(3)
|
103
|
-
self.segment_table.setHorizontalHeaderLabels(
|
104
|
-
|
105
|
-
|
119
|
+
self.segment_table.setHorizontalHeaderLabels(
|
120
|
+
["Segment ID", "Class ID", "Alias"]
|
121
|
+
)
|
122
|
+
self.segment_table.horizontalHeader().setSectionResizeMode(
|
123
|
+
QHeaderView.ResizeMode.Stretch
|
124
|
+
)
|
125
|
+
self.segment_table.setSelectionBehavior(
|
126
|
+
QTableWidget.SelectionBehavior.SelectRows
|
127
|
+
)
|
106
128
|
self.segment_table.setSortingEnabled(True)
|
107
129
|
self.segment_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
|
108
130
|
layout.addWidget(self.segment_table)
|
109
|
-
|
131
|
+
|
110
132
|
# Action buttons
|
111
133
|
action_layout = QHBoxLayout()
|
112
134
|
self.btn_merge_selection = QPushButton("Merge to Class")
|
113
|
-
self.btn_merge_selection.setToolTip(
|
135
|
+
self.btn_merge_selection.setToolTip(
|
136
|
+
"Merge selected segments into a single class (M)"
|
137
|
+
)
|
114
138
|
self.btn_delete_selection = QPushButton("Delete")
|
115
|
-
self.btn_delete_selection.setToolTip(
|
139
|
+
self.btn_delete_selection.setToolTip(
|
140
|
+
"Delete selected segments (Delete/Backspace)"
|
141
|
+
)
|
116
142
|
action_layout.addWidget(self.btn_merge_selection)
|
117
143
|
action_layout.addWidget(self.btn_delete_selection)
|
118
144
|
layout.addLayout(action_layout)
|
119
|
-
|
145
|
+
|
120
146
|
splitter.addWidget(segment_widget)
|
121
|
-
|
147
|
+
|
122
148
|
def _setup_class_management(self, splitter):
|
123
149
|
"""Setup class management section."""
|
124
150
|
class_widget = QWidget()
|
125
151
|
layout = QVBoxLayout(class_widget)
|
126
152
|
layout.setContentsMargins(0, 0, 0, 0)
|
127
|
-
|
153
|
+
|
128
154
|
layout.addWidget(QLabel("Class Order:"))
|
129
|
-
|
155
|
+
|
130
156
|
self.class_table = ReorderableClassTable()
|
131
157
|
self.class_table.setToolTip(
|
132
|
-
"Double-click to set class aliases and drag to reorder channels for saving."
|
158
|
+
"Double-click to set class aliases and drag to reorder channels for saving.\nClick once to toggle as active class for new segments."
|
133
159
|
)
|
134
160
|
self.class_table.setColumnCount(2)
|
135
161
|
self.class_table.setHorizontalHeaderLabels(["Alias", "Class ID"])
|
136
|
-
self.class_table.horizontalHeader().setSectionResizeMode(
|
137
|
-
|
162
|
+
self.class_table.horizontalHeader().setSectionResizeMode(
|
163
|
+
0, QHeaderView.ResizeMode.Stretch
|
164
|
+
)
|
165
|
+
self.class_table.horizontalHeader().setSectionResizeMode(
|
166
|
+
1, QHeaderView.ResizeMode.ResizeToContents
|
167
|
+
)
|
138
168
|
self.class_table.setEditTriggers(QTableWidget.EditTrigger.DoubleClicked)
|
139
169
|
layout.addWidget(self.class_table)
|
140
|
-
|
170
|
+
|
141
171
|
self.btn_reassign_classes = QPushButton("Reassign Class IDs")
|
142
172
|
self.btn_reassign_classes.setToolTip(
|
143
173
|
"Re-index class channels based on the current order in this table"
|
144
174
|
)
|
145
175
|
layout.addWidget(self.btn_reassign_classes)
|
146
|
-
|
176
|
+
|
147
177
|
splitter.addWidget(class_widget)
|
148
|
-
|
178
|
+
|
149
179
|
def _connect_signals(self):
|
150
180
|
"""Connect internal signals."""
|
151
181
|
self.btn_open_folder.clicked.connect(self.open_folder_requested)
|
@@ -154,14 +184,24 @@ class RightPanel(QWidget):
|
|
154
184
|
self.btn_delete_selection.clicked.connect(self.delete_selection_requested)
|
155
185
|
self.segment_table.itemSelectionChanged.connect(self.segments_selection_changed)
|
156
186
|
self.class_table.itemChanged.connect(self._handle_class_alias_change)
|
187
|
+
self.class_table.cellClicked.connect(self._handle_class_toggle)
|
157
188
|
self.btn_reassign_classes.clicked.connect(self.reassign_classes_requested)
|
158
189
|
self.class_filter_combo.currentIndexChanged.connect(self.class_filter_changed)
|
159
|
-
|
190
|
+
self.btn_popout.clicked.connect(self.pop_out_requested)
|
191
|
+
|
192
|
+
def mouseDoubleClickEvent(self, event):
|
193
|
+
"""Handle double-click to expand collapsed panel."""
|
194
|
+
if self.width() < 50: # If panel is collapsed
|
195
|
+
# Request expansion by calling parent method
|
196
|
+
if self.parent() and hasattr(self.parent(), "_expand_right_panel"):
|
197
|
+
self.parent()._expand_right_panel()
|
198
|
+
super().mouseDoubleClickEvent(event)
|
199
|
+
|
160
200
|
def _handle_class_alias_change(self, item):
|
161
201
|
"""Handle class alias change in table."""
|
162
202
|
if item.column() != 0: # Only handle alias column
|
163
203
|
return
|
164
|
-
|
204
|
+
|
165
205
|
class_table = self.class_table
|
166
206
|
id_item = class_table.item(item.row(), 1)
|
167
207
|
if id_item:
|
@@ -170,35 +210,62 @@ class RightPanel(QWidget):
|
|
170
210
|
self.class_alias_changed.emit(class_id, item.text())
|
171
211
|
except (ValueError, AttributeError):
|
172
212
|
pass
|
173
|
-
|
213
|
+
|
214
|
+
def _handle_class_toggle(self, row, column):
|
215
|
+
"""Handle class table cell click for toggling active class."""
|
216
|
+
# Get the class ID from the clicked row
|
217
|
+
id_item = self.class_table.item(row, 1)
|
218
|
+
if id_item:
|
219
|
+
try:
|
220
|
+
class_id = int(id_item.text())
|
221
|
+
self.class_toggled.emit(class_id)
|
222
|
+
except (ValueError, AttributeError):
|
223
|
+
pass
|
224
|
+
|
225
|
+
def update_active_class_display(self, active_class_id):
|
226
|
+
"""Update the visual display to show which class is active."""
|
227
|
+
# Block signals to prevent triggering change events during update
|
228
|
+
self.class_table.blockSignals(True)
|
229
|
+
|
230
|
+
for row in range(self.class_table.rowCount()):
|
231
|
+
id_item = self.class_table.item(row, 1)
|
232
|
+
alias_item = self.class_table.item(row, 0)
|
233
|
+
if id_item and alias_item:
|
234
|
+
try:
|
235
|
+
class_id = int(id_item.text())
|
236
|
+
if class_id == active_class_id:
|
237
|
+
# Make active class bold and add indicator
|
238
|
+
font = alias_item.font()
|
239
|
+
font.setBold(True)
|
240
|
+
alias_item.setFont(font)
|
241
|
+
id_item.setFont(font)
|
242
|
+
# Add visual indicator
|
243
|
+
if not alias_item.text().startswith("🔸 "):
|
244
|
+
alias_item.setText(f"🔸 {alias_item.text()}")
|
245
|
+
else:
|
246
|
+
# Make inactive classes normal
|
247
|
+
font = alias_item.font()
|
248
|
+
font.setBold(False)
|
249
|
+
alias_item.setFont(font)
|
250
|
+
id_item.setFont(font)
|
251
|
+
# Remove visual indicator
|
252
|
+
if alias_item.text().startswith("🔸 "):
|
253
|
+
alias_item.setText(alias_item.text()[2:])
|
254
|
+
except (ValueError, AttributeError):
|
255
|
+
pass
|
256
|
+
|
257
|
+
# Re-enable signals
|
258
|
+
self.class_table.blockSignals(False)
|
259
|
+
|
174
260
|
def setup_file_model(self, file_model):
|
175
261
|
"""Setup the file model for the tree view."""
|
176
262
|
self.file_tree.setModel(file_model)
|
177
263
|
self.file_tree.setColumnWidth(0, 200)
|
178
|
-
|
264
|
+
|
179
265
|
def set_folder(self, folder_path, file_model):
|
180
266
|
"""Set the folder for file browsing."""
|
181
267
|
self.file_tree.setRootIndex(file_model.setRootPath(folder_path))
|
182
|
-
|
183
|
-
def toggle_visibility(self):
|
184
|
-
"""Toggle panel visibility."""
|
185
|
-
is_visible = self.main_controls_widget.isVisible()
|
186
|
-
self.main_controls_widget.setVisible(not is_visible)
|
187
|
-
|
188
|
-
if is_visible: # Content is now hidden
|
189
|
-
self.v_layout.addStretch(1)
|
190
|
-
self.btn_toggle_visibility.setText("< Show")
|
191
|
-
self.setFixedWidth(self.btn_toggle_visibility.sizeHint().width() + 20)
|
192
|
-
else: # Content is now visible
|
193
|
-
# Remove the stretch
|
194
|
-
for i in range(self.v_layout.count()):
|
195
|
-
item = self.v_layout.itemAt(i)
|
196
|
-
if isinstance(item, QSpacerItem):
|
197
|
-
self.v_layout.removeItem(item)
|
198
|
-
break
|
199
|
-
self.btn_toggle_visibility.setText("Hide >")
|
200
|
-
self.setFixedWidth(350)
|
201
|
-
|
268
|
+
|
202
269
|
def get_selected_segment_indices(self):
|
203
270
|
"""Get indices of selected segments."""
|
204
271
|
selected_items = self.segment_table.selectedItems()
|
@@ -208,7 +275,7 @@ class RightPanel(QWidget):
|
|
208
275
|
for row in selected_rows
|
209
276
|
if self.segment_table.item(row, 0)
|
210
277
|
]
|
211
|
-
|
278
|
+
|
212
279
|
def get_class_order(self):
|
213
280
|
"""Get the current class order from the class table."""
|
214
281
|
ordered_ids = []
|
@@ -220,20 +287,29 @@ class RightPanel(QWidget):
|
|
220
287
|
except ValueError:
|
221
288
|
continue
|
222
289
|
return ordered_ids
|
223
|
-
|
290
|
+
|
224
291
|
def clear_selections(self):
|
225
292
|
"""Clear all selections."""
|
226
293
|
self.segment_table.clearSelection()
|
227
294
|
self.class_table.clearSelection()
|
228
|
-
|
295
|
+
|
229
296
|
def select_all_segments(self):
|
230
297
|
"""Select all segments."""
|
231
298
|
self.segment_table.selectAll()
|
232
|
-
|
299
|
+
|
233
300
|
def set_status(self, message):
|
234
301
|
"""Set status message."""
|
235
302
|
self.status_label.setText(message)
|
236
|
-
|
303
|
+
|
237
304
|
def clear_status(self):
|
238
305
|
"""Clear status message."""
|
239
|
-
self.status_label.clear()
|
306
|
+
self.status_label.clear()
|
307
|
+
|
308
|
+
def set_popout_mode(self, is_popped_out: bool):
|
309
|
+
"""Update the pop-out button based on panel state."""
|
310
|
+
if is_popped_out:
|
311
|
+
self.btn_popout.setText("⇤")
|
312
|
+
self.btn_popout.setToolTip("Return panel to main window")
|
313
|
+
else:
|
314
|
+
self.btn_popout.setText("⋯")
|
315
|
+
self.btn_popout.setToolTip("Pop out panel to separate window")
|
lazylabel/ui/widgets/__init__.py
CHANGED
@@ -3,5 +3,6 @@
|
|
3
3
|
from .model_selection_widget import ModelSelectionWidget
|
4
4
|
from .settings_widget import SettingsWidget
|
5
5
|
from .adjustments_widget import AdjustmentsWidget
|
6
|
+
from .status_bar import StatusBar
|
6
7
|
|
7
|
-
__all__ = [
|
8
|
+
__all__ = ["ModelSelectionWidget", "SettingsWidget", "AdjustmentsWidget", "StatusBar"]
|
@@ -0,0 +1,109 @@
|
|
1
|
+
"""Status bar widget for displaying active messages."""
|
2
|
+
|
3
|
+
from PyQt6.QtWidgets import QStatusBar, QLabel
|
4
|
+
from PyQt6.QtCore import QTimer, pyqtSignal, Qt
|
5
|
+
from PyQt6.QtGui import QFont
|
6
|
+
|
7
|
+
|
8
|
+
class StatusBar(QStatusBar):
|
9
|
+
"""Custom status bar for displaying messages and app status."""
|
10
|
+
|
11
|
+
def __init__(self, parent=None):
|
12
|
+
super().__init__(parent)
|
13
|
+
self._message_timer = QTimer()
|
14
|
+
self._message_timer.timeout.connect(self._clear_temporary_message)
|
15
|
+
self._setup_ui()
|
16
|
+
|
17
|
+
def _setup_ui(self):
|
18
|
+
"""Setup the status bar UI."""
|
19
|
+
# Set a reasonable height for the status bar
|
20
|
+
self.setFixedHeight(25)
|
21
|
+
|
22
|
+
# Main message label (centered)
|
23
|
+
self.message_label = QLabel()
|
24
|
+
self.message_label.setStyleSheet("color: #ffa500; padding: 2px 5px;")
|
25
|
+
self.message_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
26
|
+
font = QFont()
|
27
|
+
font.setPointSize(9)
|
28
|
+
self.message_label.setFont(font)
|
29
|
+
|
30
|
+
# Add the message label as the main widget
|
31
|
+
self.addWidget(self.message_label, 1) # stretch factor 1
|
32
|
+
|
33
|
+
# Permanent status label (right side)
|
34
|
+
self.permanent_label = QLabel()
|
35
|
+
self.permanent_label.setStyleSheet("color: #888; padding: 2px 5px;")
|
36
|
+
font = QFont()
|
37
|
+
font.setPointSize(9)
|
38
|
+
self.permanent_label.setFont(font)
|
39
|
+
self.addPermanentWidget(self.permanent_label)
|
40
|
+
|
41
|
+
# Default state
|
42
|
+
self.set_ready_message()
|
43
|
+
|
44
|
+
def show_message(self, message: str, duration: int = 5000):
|
45
|
+
"""Show a temporary message for specified duration."""
|
46
|
+
self.message_label.setText(message)
|
47
|
+
self.message_label.setStyleSheet("color: #ffa500; padding: 2px 5px;")
|
48
|
+
|
49
|
+
# Stop any existing timer
|
50
|
+
self._message_timer.stop()
|
51
|
+
|
52
|
+
# Start new timer if duration > 0
|
53
|
+
if duration > 0:
|
54
|
+
self._message_timer.start(duration)
|
55
|
+
|
56
|
+
def show_error_message(self, message: str, duration: int = 8000):
|
57
|
+
"""Show an error message with red color."""
|
58
|
+
self.message_label.setText(f"Error: {message}")
|
59
|
+
self.message_label.setStyleSheet("color: #ff6b6b; padding: 2px 5px;")
|
60
|
+
|
61
|
+
# Stop any existing timer
|
62
|
+
self._message_timer.stop()
|
63
|
+
|
64
|
+
# Start new timer if duration > 0
|
65
|
+
if duration > 0:
|
66
|
+
self._message_timer.start(duration)
|
67
|
+
|
68
|
+
def show_success_message(self, message: str, duration: int = 3000):
|
69
|
+
"""Show a success message with green color."""
|
70
|
+
self.message_label.setText(message)
|
71
|
+
self.message_label.setStyleSheet("color: #51cf66; padding: 2px 5px;")
|
72
|
+
|
73
|
+
# Stop any existing timer
|
74
|
+
self._message_timer.stop()
|
75
|
+
|
76
|
+
# Start new timer if duration > 0
|
77
|
+
if duration > 0:
|
78
|
+
self._message_timer.start(duration)
|
79
|
+
|
80
|
+
def show_warning_message(self, message: str, duration: int = 5000):
|
81
|
+
"""Show a warning message with yellow color."""
|
82
|
+
self.message_label.setText(f"Warning: {message}")
|
83
|
+
self.message_label.setStyleSheet("color: #ffd43b; padding: 2px 5px;")
|
84
|
+
|
85
|
+
# Stop any existing timer
|
86
|
+
self._message_timer.stop()
|
87
|
+
|
88
|
+
# Start new timer if duration > 0
|
89
|
+
if duration > 0:
|
90
|
+
self._message_timer.start(duration)
|
91
|
+
|
92
|
+
def set_permanent_message(self, message: str):
|
93
|
+
"""Set a permanent message (usually for status info)."""
|
94
|
+
self.permanent_label.setText(message)
|
95
|
+
|
96
|
+
def set_ready_message(self):
|
97
|
+
"""Set the default ready message."""
|
98
|
+
self.message_label.setText("") # Blank instead of "Ready"
|
99
|
+
self.message_label.setStyleSheet("color: #888; padding: 2px 5px;")
|
100
|
+
self._message_timer.stop()
|
101
|
+
|
102
|
+
def _clear_temporary_message(self):
|
103
|
+
"""Clear temporary message and return to ready state."""
|
104
|
+
self.set_ready_message()
|
105
|
+
self._message_timer.stop()
|
106
|
+
|
107
|
+
def clear_message(self):
|
108
|
+
"""Immediately clear any message."""
|
109
|
+
self.set_ready_message()
|
@@ -1,36 +1,37 @@
|
|
1
1
|
lazylabel/__init__.py,sha256=XTAPk-88MdkJX5IKg0f51CxNlARQ-SZIiqLneV6B3II,197
|
2
|
-
lazylabel/main.py,sha256=
|
2
|
+
lazylabel/main.py,sha256=U0_bYNiVmmwdhRgBOryRZE91sunDQYEJTGGMNFubj1M,766
|
3
3
|
lazylabel/config/__init__.py,sha256=ahAF6cneEVmq2zyGwydGgGAOzJKkv_aumq5cWmokOms,261
|
4
4
|
lazylabel/config/hotkeys.py,sha256=LrQ4nmLWVOZvNOe4mEARFU3Wd3bZ7nvvsxGeFY6N9wA,7618
|
5
5
|
lazylabel/config/paths.py,sha256=d6lJ0rwdlnLjrhR5fIzb3Pc9Fhl4lsGIf2IZNmi4vZ8,1320
|
6
6
|
lazylabel/config/settings.py,sha256=IkUGz-FdDapF71k_LRpUOLHScglGeYjdLbbELGJ4hOs,1860
|
7
7
|
lazylabel/core/__init__.py,sha256=YIj3IAI6Er5nN7gSuo9E8eoQKbBbSq2Jy2prtU8Fp50,230
|
8
8
|
lazylabel/core/file_manager.py,sha256=cXaSPSUiqTm57hfpUxkuzgqApOZCK1RAJdfjmKRJKRA,4734
|
9
|
-
lazylabel/core/model_manager.py,sha256=
|
10
|
-
lazylabel/core/segment_manager.py,sha256=
|
9
|
+
lazylabel/core/model_manager.py,sha256=pLTmQlVfambA3y4aypHu_jn_WrW0ZrMu_8j023mr_EM,3435
|
10
|
+
lazylabel/core/segment_manager.py,sha256=kLEj5S-fEmoXIiJ2Em97GJlre8OOpSotX5bIWMrBCvE,6006
|
11
11
|
lazylabel/models/__init__.py,sha256=qH0EvkWsou0boS85yM6DfRhJnrPOLc-QzWI0grAwnRI,89
|
12
|
-
lazylabel/models/sam_model.py,sha256=
|
12
|
+
lazylabel/models/sam_model.py,sha256=lMakV3_B-hNJ3BXy1sErkSzH9YvDsColevXLGaeLnP4,7762
|
13
13
|
lazylabel/ui/__init__.py,sha256=i5hgblzrydTkFSJDiFyfRZMNl4Z4J2dumuwx3bWEPvA,266
|
14
|
-
lazylabel/ui/control_panel.py,sha256=
|
14
|
+
lazylabel/ui/control_panel.py,sha256=Pfv4D3V9oBkTWWzTE944JAUCa0by7jv9h3EruKnJLVM,8894
|
15
15
|
lazylabel/ui/editable_vertex.py,sha256=pMuXvlCkbguUsFisEzj3bC2UZ_xKqfGuM8kyPug5yws,1852
|
16
16
|
lazylabel/ui/hotkey_dialog.py,sha256=3QkthYil_SWYWxRGL_U8cZCsQ4y-6SExQZXJuE5aO8s,15252
|
17
17
|
lazylabel/ui/hoverable_pixelmap_item.py,sha256=kJFOp7WXiyHpNf7l73TZjiob85jgP30b5MZvu_z5L3c,728
|
18
18
|
lazylabel/ui/hoverable_polygon_item.py,sha256=hQKax3vzhtrdB5ThwTINjOCwOYy718zw5YZPLCfLnGM,1251
|
19
|
-
lazylabel/ui/main_window.py,sha256=
|
19
|
+
lazylabel/ui/main_window.py,sha256=zjpTH-RqBx5mJ1i1Y0CC8KepgQF28mIJDYc68CUoBIk,64488
|
20
20
|
lazylabel/ui/numeric_table_widget_item.py,sha256=dQUlIFu9syCxTGAHVIlmbgkI7aJ3f3wmDPBz1AGK9Bg,283
|
21
21
|
lazylabel/ui/photo_viewer.py,sha256=PNgm0gU2gnIqvRkrGlQugdobGsKwAi3m3X6ZF487lCo,2055
|
22
22
|
lazylabel/ui/reorderable_class_table.py,sha256=4c-iuSkPcmk5Aey5n2zz49O85x9TQPujKG-JLxtuBCo,2406
|
23
|
-
lazylabel/ui/right_panel.py,sha256=
|
24
|
-
lazylabel/ui/widgets/__init__.py,sha256=
|
23
|
+
lazylabel/ui/right_panel.py,sha256=7ksUM7eItHrdXWkuDHohXEShdYYR2FYx-zeSmbfi620,12454
|
24
|
+
lazylabel/ui/widgets/__init__.py,sha256=5wJ09bTPlhqHyBIXGDeXiNJf33YelKKLu0aPrar9AxU,314
|
25
25
|
lazylabel/ui/widgets/adjustments_widget.py,sha256=Az8GZgu4Uvhm6nEnwjiYDyShOqwieinjFnTOiUfxdzo,3940
|
26
26
|
lazylabel/ui/widgets/model_selection_widget.py,sha256=X3qVH90yCaCpLDauj-DLWJgAwAkAVQrzhG9X5mESa-o,3590
|
27
27
|
lazylabel/ui/widgets/settings_widget.py,sha256=8zhjLxUxqFqxqMYDzkXWD1Ye5V-a7HNhPjPJjYBxeZw,4361
|
28
|
+
lazylabel/ui/widgets/status_bar.py,sha256=71paNOXlxXmWbCG6luyciXRmqC7DNFYDi_Eo2HDzjOg,4026
|
28
29
|
lazylabel/utils/__init__.py,sha256=SX_aZvmFojIJ4Lskat9ly0dmFBXgN7leBdmc68aDLpg,177
|
29
30
|
lazylabel/utils/custom_file_system_model.py,sha256=zK0Z4LY2hzYVtycDWsCLEhXZ0zKZz-Qy5XGwHYrqazQ,4867
|
30
31
|
lazylabel/utils/utils.py,sha256=sYSCoXL27OaLgOZaUkCAhgmKZ7YfhR3Cc5F8nDIa3Ig,414
|
31
|
-
lazylabel_gui-1.1.
|
32
|
-
lazylabel_gui-1.1.
|
33
|
-
lazylabel_gui-1.1.
|
34
|
-
lazylabel_gui-1.1.
|
35
|
-
lazylabel_gui-1.1.
|
36
|
-
lazylabel_gui-1.1.
|
32
|
+
lazylabel_gui-1.1.1.dist-info/licenses/LICENSE,sha256=kSDEIgrWAPd1u2UFGGpC9X71dhzrlzBFs8hbDlENnGE,1092
|
33
|
+
lazylabel_gui-1.1.1.dist-info/METADATA,sha256=RMI2kMGM08gRUJqkKXrMNbkKvmNDawmvjRCutT_Y0bM,8473
|
34
|
+
lazylabel_gui-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
35
|
+
lazylabel_gui-1.1.1.dist-info/entry_points.txt,sha256=Hd0WwEG9OPTa_ziYjiD0aRh7R6Fupt-wdQ3sspdc1mM,54
|
36
|
+
lazylabel_gui-1.1.1.dist-info/top_level.txt,sha256=YN4uIyrpDBq1wiJaBuZLDipIzyZY0jqJOmmXiPIOUkU,10
|
37
|
+
lazylabel_gui-1.1.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|