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.
@@ -7,6 +7,7 @@ from PyQt6.QtWidgets import (
7
7
  QMainWindow,
8
8
  QWidget,
9
9
  QHBoxLayout,
10
+ QVBoxLayout,
10
11
  QFileDialog,
11
12
  QApplication,
12
13
  QGraphicsEllipseItem,
@@ -15,6 +16,8 @@ from PyQt6.QtWidgets import (
15
16
  QTableWidgetItem,
16
17
  QTableWidgetSelectionRange,
17
18
  QHeaderView,
19
+ QSplitter,
20
+ QDialog,
18
21
  )
19
22
  from PyQt6.QtGui import (
20
23
  QIcon,
@@ -27,7 +30,7 @@ from PyQt6.QtGui import (
27
30
  QPolygonF,
28
31
  QImage,
29
32
  )
30
- from PyQt6.QtCore import Qt, QTimer, QModelIndex, QPointF
33
+ from PyQt6.QtCore import Qt, QTimer, QModelIndex, QPointF, pyqtSignal
31
34
 
32
35
  from .control_panel import ControlPanel
33
36
  from .right_panel import RightPanel
@@ -40,6 +43,36 @@ from ..core import SegmentManager, ModelManager, FileManager
40
43
  from ..config import Settings, Paths, HotkeyManager
41
44
  from ..utils import CustomFileSystemModel, mask_to_pixmap
42
45
  from .hotkey_dialog import HotkeyDialog
46
+ from .widgets import StatusBar
47
+
48
+
49
+ class PanelPopoutWindow(QDialog):
50
+ """Pop-out window for draggable panels."""
51
+
52
+ panel_closed = pyqtSignal(QWidget) # Signal emitted when panel window is closed
53
+
54
+ def __init__(self, panel_widget, title="Panel", parent=None):
55
+ super().__init__(parent)
56
+ self.panel_widget = panel_widget
57
+ self.setWindowTitle(title)
58
+ self.setWindowFlags(Qt.WindowType.Window) # Allow moving to other monitors
59
+
60
+ # Make window resizable
61
+ self.setMinimumSize(200, 300)
62
+ self.resize(400, 600)
63
+
64
+ # Set up layout
65
+ layout = QVBoxLayout(self)
66
+ layout.setContentsMargins(5, 5, 5, 5)
67
+ layout.addWidget(panel_widget)
68
+
69
+ # Store original parent for restoration
70
+ self.original_parent = parent
71
+
72
+ def closeEvent(self, event):
73
+ """Handle window close - emit signal to return panel to main window."""
74
+ self.panel_closed.emit(self.panel_widget)
75
+ super().closeEvent(event)
43
76
 
44
77
 
45
78
  class MainWindow(QMainWindow):
@@ -48,22 +81,33 @@ class MainWindow(QMainWindow):
48
81
  def __init__(self):
49
82
  super().__init__()
50
83
 
84
+ print("[3/20] Starting LazyLabel...")
85
+ print("[4/20] Loading configuration and settings...")
86
+
51
87
  # Initialize configuration
52
88
  self.paths = Paths()
53
89
  self.settings = Settings.load_from_file(str(self.paths.settings_file))
54
90
  self.hotkey_manager = HotkeyManager(str(self.paths.config_dir))
55
91
 
92
+ print("[5/20] Initializing core managers...")
93
+
56
94
  # Initialize managers
57
95
  self.segment_manager = SegmentManager()
58
96
  self.model_manager = ModelManager(self.paths)
59
97
  self.file_manager = FileManager(self.segment_manager)
60
98
 
99
+ print("[6/20] Setting up user interface...")
100
+
61
101
  # Initialize UI state
62
102
  self.mode = "sam_points"
63
103
  self.previous_mode = "sam_points"
64
104
  self.current_image_path = None
65
105
  self.current_file_index = QModelIndex()
66
106
 
107
+ # Panel pop-out state
108
+ self.left_panel_popout = None
109
+ self.right_panel_popout = None
110
+
67
111
  # Annotation state
68
112
  self.point_radius = self.settings.point_radius
69
113
  self.line_thickness = self.settings.line_thickness
@@ -83,12 +127,19 @@ class MainWindow(QMainWindow):
83
127
  {},
84
128
  )
85
129
 
130
+ # Update state flags to prevent recursion
131
+ self._updating_lists = False
132
+
86
133
  self._setup_ui()
87
134
  self._setup_model()
135
+
136
+ print("[17/20] Connecting UI signals and shortcuts...")
88
137
  self._setup_connections()
89
138
  self._setup_shortcuts()
90
139
  self._load_settings()
91
140
 
141
+ print("[18/20] LazyLabel initialization complete!")
142
+
92
143
  def _setup_ui(self):
93
144
  """Setup the user interface."""
94
145
  self.setWindowTitle("LazyLabel by DNC")
@@ -110,11 +161,37 @@ class MainWindow(QMainWindow):
110
161
  self.file_model = CustomFileSystemModel()
111
162
  self.right_panel.setup_file_model(self.file_model)
112
163
 
113
- # Layout
114
- main_layout = QHBoxLayout()
115
- main_layout.addWidget(self.control_panel)
116
- main_layout.addWidget(self.viewer, 1)
117
- main_layout.addWidget(self.right_panel)
164
+ # Create status bar
165
+ self.status_bar = StatusBar()
166
+ self.setStatusBar(self.status_bar)
167
+
168
+ # Create horizontal splitter for main panels
169
+ self.main_splitter = QSplitter(Qt.Orientation.Horizontal)
170
+ self.main_splitter.addWidget(self.control_panel)
171
+ self.main_splitter.addWidget(self.viewer)
172
+ self.main_splitter.addWidget(self.right_panel)
173
+
174
+ # Set minimum sizes for panels to prevent shrinking below preferred width
175
+ self.control_panel.setMinimumWidth(self.control_panel.preferred_width)
176
+ self.right_panel.setMinimumWidth(self.right_panel.preferred_width)
177
+
178
+ # Set splitter sizes - give most space to viewer
179
+ self.main_splitter.setSizes([250, 800, 350])
180
+ self.main_splitter.setStretchFactor(0, 0) # Control panel doesn't stretch
181
+ self.main_splitter.setStretchFactor(1, 1) # Viewer stretches
182
+ self.main_splitter.setStretchFactor(2, 0) # Right panel doesn't stretch
183
+
184
+ # Set splitter child sizes policy
185
+ self.main_splitter.setChildrenCollapsible(True)
186
+
187
+ # Connect splitter signals for intelligent expand/collapse
188
+ self.main_splitter.splitterMoved.connect(self._handle_splitter_moved)
189
+
190
+ # Main vertical layout to accommodate status bar
191
+ main_layout = QVBoxLayout()
192
+ main_layout.setContentsMargins(0, 0, 0, 0)
193
+ main_layout.setSpacing(0)
194
+ main_layout.addWidget(self.main_splitter, 1)
118
195
 
119
196
  central_widget = QWidget()
120
197
  central_widget.setLayout(main_layout)
@@ -122,17 +199,29 @@ class MainWindow(QMainWindow):
122
199
 
123
200
  def _setup_model(self):
124
201
  """Setup the SAM model."""
202
+ print("[7/20] Initializing SAM model (this may take a moment)...")
203
+
125
204
  sam_model = self.model_manager.initialize_default_model(
126
205
  self.settings.default_model_type
127
206
  )
128
207
 
129
208
  if sam_model and sam_model.is_loaded:
130
- self.control_panel.set_device_text(str(sam_model.device))
209
+ device_text = str(sam_model.device).upper()
210
+ print(f"[14/20] SAM model loaded successfully on {device_text}")
211
+ self.status_bar.set_permanent_message(f"Device: {device_text}")
131
212
  self._enable_sam_functionality(True)
213
+ elif sam_model is None:
214
+ print(
215
+ "[14/20] SAM model initialization failed. Point mode will be disabled."
216
+ )
217
+ self.status_bar.set_permanent_message("Model initialization failed")
218
+ self._enable_sam_functionality(False)
132
219
  else:
133
- self.control_panel.set_device_text("No model loaded")
220
+ print("[14/20] SAM model failed to load. Point mode will be disabled.")
221
+ self.status_bar.set_permanent_message("Model loading failed")
134
222
  self._enable_sam_functionality(False)
135
- print("SAM model failed to load. Point mode will be disabled.")
223
+
224
+ print("[15/20] Scanning available models...")
136
225
 
137
226
  # Setup model change callback
138
227
  self.model_manager.on_model_changed = self.control_panel.set_current_model
@@ -141,6 +230,9 @@ class MainWindow(QMainWindow):
141
230
  models = self.model_manager.get_available_models(str(self.paths.models_dir))
142
231
  self.control_panel.populate_models(models)
143
232
 
233
+ if models:
234
+ print(f"[16/20] Found {len(models)} model(s) in models directory")
235
+
144
236
  def _enable_sam_functionality(self, enabled: bool):
145
237
  """Enable or disable SAM point functionality."""
146
238
  self.control_panel.set_sam_mode_enabled(enabled)
@@ -183,14 +275,11 @@ class MainWindow(QMainWindow):
183
275
  self.right_panel.class_alias_changed.connect(self._handle_alias_change)
184
276
  self.right_panel.reassign_classes_requested.connect(self._reassign_class_ids)
185
277
  self.right_panel.class_filter_changed.connect(self._update_segment_table)
278
+ self.right_panel.class_toggled.connect(self._handle_class_toggle)
186
279
 
187
- # Panel visibility
188
- self.control_panel.btn_toggle_visibility.clicked.connect(
189
- self.control_panel.toggle_visibility
190
- )
191
- self.right_panel.btn_toggle_visibility.clicked.connect(
192
- self.right_panel.toggle_visibility
193
- )
280
+ # Panel pop-out functionality
281
+ self.control_panel.pop_out_requested.connect(self._pop_out_left_panel)
282
+ self.right_panel.pop_out_requested.connect(self._pop_out_right_panel)
194
283
 
195
284
  # Mouse events (will be implemented in a separate handler)
196
285
  self._setup_mouse_events()
@@ -355,9 +444,9 @@ class MainWindow(QMainWindow):
355
444
  if folder and os.path.exists(folder):
356
445
  models = self.model_manager.get_available_models(folder)
357
446
  self.control_panel.populate_models(models)
358
- self._show_notification("Models list refreshed.")
447
+ self._show_success_notification("Models list refreshed.")
359
448
  else:
360
- self._show_notification("No models folder selected.")
449
+ self._show_warning_notification("No models folder selected.")
361
450
 
362
451
  def _load_selected_model(self, model_text):
363
452
  """Load the selected model."""
@@ -367,7 +456,7 @@ class MainWindow(QMainWindow):
367
456
 
368
457
  model_path = self.control_panel.model_widget.get_selected_model_path()
369
458
  if not model_path or not os.path.exists(model_path):
370
- self._show_notification("Selected model file not found.")
459
+ self._show_error_notification("Selected model file not found.")
371
460
  return
372
461
 
373
462
  self.control_panel.set_current_model("Loading model...")
@@ -379,17 +468,18 @@ class MainWindow(QMainWindow):
379
468
  # Re-enable SAM functionality if model loaded successfully
380
469
  self._enable_sam_functionality(True)
381
470
  if self.model_manager.sam_model:
382
- self.control_panel.set_device_text(
383
- str(self.model_manager.sam_model.device)
384
- )
471
+ device_text = str(self.model_manager.sam_model.device).upper()
472
+ self.status_bar.set_permanent_message(f"Device: {device_text}")
385
473
  else:
386
474
  self.control_panel.set_current_model("Current: Default SAM Model")
387
- self._show_notification("Failed to load selected model. Using default.")
475
+ self._show_error_notification(
476
+ "Failed to load selected model. Using default."
477
+ )
388
478
  self.control_panel.model_widget.reset_to_default()
389
479
  self._enable_sam_functionality(False)
390
480
  except Exception as e:
391
481
  self.control_panel.set_current_model("Current: Default SAM Model")
392
- self._show_notification(f"Error loading model: {str(e)}")
482
+ self._show_error_notification(f"Error loading model: {str(e)}")
393
483
  self.control_panel.model_widget.reset_to_default()
394
484
  self._enable_sam_functionality(False)
395
485
 
@@ -539,6 +629,8 @@ class MainWindow(QMainWindow):
539
629
 
540
630
  def _handle_alias_change(self, class_id, alias):
541
631
  """Handle class alias change."""
632
+ if self._updating_lists:
633
+ return # Prevent recursion
542
634
  self.segment_manager.set_class_alias(class_id, alias)
543
635
  self._update_all_lists()
544
636
 
@@ -624,16 +716,27 @@ class MainWindow(QMainWindow):
624
716
  table.blockSignals(False)
625
717
  self.viewer.setFocus()
626
718
 
719
+ # Update active class display
720
+ active_class = self.segment_manager.get_active_class()
721
+ self.right_panel.update_active_class_display(active_class)
722
+
627
723
  def _update_all_lists(self):
628
724
  """Update all UI lists."""
629
- self._update_class_list()
630
- self._update_segment_table()
631
- self._update_class_filter()
632
- self._display_all_segments()
633
- if self.mode == "edit":
634
- self._display_edit_handles()
635
- else:
636
- self._clear_edit_handles()
725
+ if self._updating_lists:
726
+ return # Prevent recursion
727
+
728
+ self._updating_lists = True
729
+ try:
730
+ self._update_class_list()
731
+ self._update_segment_table()
732
+ self._update_class_filter()
733
+ self._display_all_segments()
734
+ if self.mode == "edit":
735
+ self._display_edit_handles()
736
+ else:
737
+ self._clear_edit_handles()
738
+ finally:
739
+ self._updating_lists = False
637
740
 
638
741
  def _update_class_list(self):
639
742
  """Update the class list in the right panel."""
@@ -658,6 +761,10 @@ class MainWindow(QMainWindow):
658
761
  class_table.setItem(row, 0, alias_item)
659
762
  class_table.setItem(row, 1, id_item)
660
763
 
764
+ # Update active class display BEFORE re-enabling signals
765
+ active_class = self.segment_manager.get_active_class()
766
+ self.right_panel.update_active_class_display(active_class)
767
+
661
768
  class_table.blockSignals(False)
662
769
 
663
770
  def _update_class_filter(self):
@@ -790,7 +897,7 @@ class MainWindow(QMainWindow):
790
897
  def _save_output_to_npz(self):
791
898
  """Save output to NPZ and TXT files as enabled, and update file list tickboxes/highlight. If no segments, delete associated files."""
792
899
  if not self.current_image_path:
793
- self._show_notification("No image loaded.")
900
+ self._show_warning_notification("No image loaded.")
794
901
  return
795
902
 
796
903
  # If no segments, delete associated files
@@ -805,13 +912,15 @@ class MainWindow(QMainWindow):
805
912
  deleted_files.append(file_path)
806
913
  self.file_model.update_cache_for_path(file_path)
807
914
  except Exception as e:
808
- self._show_notification(f"Error deleting {file_path}: {e}")
915
+ self._show_error_notification(
916
+ f"Error deleting {file_path}: {e}"
917
+ )
809
918
  if deleted_files:
810
919
  self._show_notification(
811
920
  f"Deleted: {', '.join(os.path.basename(f) for f in deleted_files)}"
812
921
  )
813
922
  else:
814
- self._show_notification("No segments to save.")
923
+ self._show_warning_notification("No segments to save.")
815
924
  return
816
925
 
817
926
  try:
@@ -828,9 +937,11 @@ class MainWindow(QMainWindow):
828
937
  npz_path = self.file_manager.save_npz(
829
938
  self.current_image_path, (h, w), class_order
830
939
  )
831
- self._show_notification(f"Saved: {os.path.basename(npz_path)}")
940
+ self._show_success_notification(
941
+ f"Saved: {os.path.basename(npz_path)}"
942
+ )
832
943
  else:
833
- self._show_notification("No classes defined for saving.")
944
+ self._show_warning_notification("No classes defined for saving.")
834
945
  if settings.get("save_txt", True):
835
946
  h, w = (
836
947
  self.viewer._pixmap_item.pixmap().height(),
@@ -861,7 +972,7 @@ class MainWindow(QMainWindow):
861
972
  ),
862
973
  )
863
974
  except Exception as e:
864
- self._show_notification(f"Error saving: {str(e)}")
975
+ self._show_error_notification(f"Error saving: {str(e)}")
865
976
 
866
977
  def _handle_merge_press(self):
867
978
  """Handle merge key press."""
@@ -897,8 +1008,19 @@ class MainWindow(QMainWindow):
897
1008
 
898
1009
  def _show_notification(self, message, duration=3000):
899
1010
  """Show notification message."""
900
- self.control_panel.show_notification(message)
901
- QTimer.singleShot(duration, self.control_panel.clear_notification)
1011
+ self.status_bar.show_message(message, duration)
1012
+
1013
+ def _show_error_notification(self, message, duration=8000):
1014
+ """Show error notification message."""
1015
+ self.status_bar.show_error_message(message, duration)
1016
+
1017
+ def _show_success_notification(self, message, duration=3000):
1018
+ """Show success notification message."""
1019
+ self.status_bar.show_success_message(message, duration)
1020
+
1021
+ def _show_warning_notification(self, message, duration=5000):
1022
+ """Show warning notification message."""
1023
+ self.status_bar.show_warning_message(message, duration)
902
1024
 
903
1025
  def _show_hotkey_dialog(self):
904
1026
  """Show the hotkey configuration dialog."""
@@ -945,6 +1067,12 @@ class MainWindow(QMainWindow):
945
1067
 
946
1068
  def closeEvent(self, event):
947
1069
  """Handle application close."""
1070
+ # Close any popped-out panels first
1071
+ if self.left_panel_popout is not None:
1072
+ self.left_panel_popout.close()
1073
+ if self.right_panel_popout is not None:
1074
+ self.right_panel_popout.close()
1075
+
948
1076
  # Save settings
949
1077
  self.settings.save_to_file(str(self.paths.settings_file))
950
1078
  super().closeEvent(event)
@@ -1262,3 +1390,157 @@ class MainWindow(QMainWindow):
1262
1390
  QPolygonF(self.segment_manager.segments[segment_index]["vertices"])
1263
1391
  )
1264
1392
  return
1393
+
1394
+ def _handle_class_toggle(self, class_id):
1395
+ """Handle class toggle."""
1396
+ is_active = self.segment_manager.toggle_active_class(class_id)
1397
+
1398
+ if is_active:
1399
+ self._show_notification(f"Class {class_id} activated for new segments")
1400
+ # Update visual display
1401
+ self.right_panel.update_active_class_display(class_id)
1402
+ else:
1403
+ self._show_notification(
1404
+ "No active class - new segments will create new classes"
1405
+ )
1406
+ # Update visual display to clear active class
1407
+ self.right_panel.update_active_class_display(None)
1408
+
1409
+ def _pop_out_left_panel(self):
1410
+ """Pop out the left control panel into a separate window."""
1411
+ if self.left_panel_popout is not None:
1412
+ # Panel is already popped out, return it to main window
1413
+ self._return_left_panel(self.control_panel)
1414
+ return
1415
+
1416
+ # Remove panel from main splitter
1417
+ self.control_panel.setParent(None)
1418
+
1419
+ # Create pop-out window
1420
+ self.left_panel_popout = PanelPopoutWindow(
1421
+ self.control_panel, "Control Panel", self
1422
+ )
1423
+ self.left_panel_popout.panel_closed.connect(self._return_left_panel)
1424
+ self.left_panel_popout.show()
1425
+
1426
+ # Update panel's pop-out button
1427
+ self.control_panel.set_popout_mode(True)
1428
+
1429
+ # Make pop-out window resizable
1430
+ self.left_panel_popout.setMinimumSize(200, 400)
1431
+ self.left_panel_popout.resize(self.control_panel.preferred_width + 20, 600)
1432
+
1433
+ def _pop_out_right_panel(self):
1434
+ """Pop out the right panel into a separate window."""
1435
+ if self.right_panel_popout is not None:
1436
+ # Panel is already popped out, return it to main window
1437
+ self._return_right_panel(self.right_panel)
1438
+ return
1439
+
1440
+ # Remove panel from main splitter
1441
+ self.right_panel.setParent(None)
1442
+
1443
+ # Create pop-out window
1444
+ self.right_panel_popout = PanelPopoutWindow(
1445
+ self.right_panel, "File Explorer & Segments", self
1446
+ )
1447
+ self.right_panel_popout.panel_closed.connect(self._return_right_panel)
1448
+ self.right_panel_popout.show()
1449
+
1450
+ # Update panel's pop-out button
1451
+ self.right_panel.set_popout_mode(True)
1452
+
1453
+ # Make pop-out window resizable
1454
+ self.right_panel_popout.setMinimumSize(250, 400)
1455
+ self.right_panel_popout.resize(self.right_panel.preferred_width + 20, 600)
1456
+
1457
+ def _return_left_panel(self, panel_widget):
1458
+ """Return the left panel to the main window."""
1459
+ if self.left_panel_popout is not None:
1460
+ # Close the pop-out window
1461
+ self.left_panel_popout.close()
1462
+
1463
+ # Return panel to main splitter
1464
+ self.main_splitter.insertWidget(0, self.control_panel)
1465
+ self.left_panel_popout = None
1466
+
1467
+ # Update panel's pop-out button
1468
+ self.control_panel.set_popout_mode(False)
1469
+
1470
+ # Restore splitter sizes
1471
+ self.main_splitter.setSizes([250, 800, 350])
1472
+
1473
+ def _handle_splitter_moved(self, pos, index):
1474
+ """Handle splitter movement for intelligent expand/collapse behavior."""
1475
+ sizes = self.main_splitter.sizes()
1476
+
1477
+ # Left panel (index 0) - expand/collapse logic
1478
+ if index == 1: # Splitter between left panel and viewer
1479
+ left_size = sizes[0]
1480
+ # Only snap to collapsed if user drags very close to collapse
1481
+ if left_size < 50: # Collapsed threshold
1482
+ # Panel is being collapsed, snap to collapsed state
1483
+ new_sizes = [0] + sizes[1:]
1484
+ new_sizes[1] = new_sizes[1] + left_size # Give space back to viewer
1485
+ self.main_splitter.setSizes(new_sizes)
1486
+ # Temporarily override minimum width to allow collapsing
1487
+ self.control_panel.setMinimumWidth(0)
1488
+
1489
+ # Right panel (index 2) - expand/collapse logic
1490
+ elif index == 2: # Splitter between viewer and right panel
1491
+ right_size = sizes[2]
1492
+ # Only snap to collapsed if user drags very close to collapse
1493
+ if right_size < 50: # Collapsed threshold
1494
+ # Panel is being collapsed, snap to collapsed state
1495
+ new_sizes = sizes[:-1] + [0]
1496
+ new_sizes[1] = new_sizes[1] + right_size # Give space back to viewer
1497
+ self.main_splitter.setSizes(new_sizes)
1498
+ # Temporarily override minimum width to allow collapsing
1499
+ self.right_panel.setMinimumWidth(0)
1500
+
1501
+ def _expand_left_panel(self):
1502
+ """Expand the left panel to its preferred width."""
1503
+ sizes = self.main_splitter.sizes()
1504
+ if sizes[0] < 50: # Only expand if currently collapsed
1505
+ # Restore minimum width first
1506
+ self.control_panel.setMinimumWidth(self.control_panel.preferred_width)
1507
+
1508
+ space_needed = self.control_panel.preferred_width
1509
+ viewer_width = sizes[1] - space_needed
1510
+ if viewer_width > 400: # Ensure viewer has minimum space
1511
+ new_sizes = [self.control_panel.preferred_width, viewer_width] + sizes[
1512
+ 2:
1513
+ ]
1514
+ self.main_splitter.setSizes(new_sizes)
1515
+
1516
+ def _expand_right_panel(self):
1517
+ """Expand the right panel to its preferred width."""
1518
+ sizes = self.main_splitter.sizes()
1519
+ if sizes[2] < 50: # Only expand if currently collapsed
1520
+ # Restore minimum width first
1521
+ self.right_panel.setMinimumWidth(self.right_panel.preferred_width)
1522
+
1523
+ space_needed = self.right_panel.preferred_width
1524
+ viewer_width = sizes[1] - space_needed
1525
+ if viewer_width > 400: # Ensure viewer has minimum space
1526
+ new_sizes = sizes[:-1] + [
1527
+ viewer_width,
1528
+ self.right_panel.preferred_width,
1529
+ ]
1530
+ self.main_splitter.setSizes(new_sizes)
1531
+
1532
+ def _return_right_panel(self, panel_widget):
1533
+ """Return the right panel to the main window."""
1534
+ if self.right_panel_popout is not None:
1535
+ # Close the pop-out window
1536
+ self.right_panel_popout.close()
1537
+
1538
+ # Return panel to main splitter
1539
+ self.main_splitter.addWidget(self.right_panel)
1540
+ self.right_panel_popout = None
1541
+
1542
+ # Update panel's pop-out button
1543
+ self.right_panel.set_popout_mode(False)
1544
+
1545
+ # Restore splitter sizes
1546
+ self.main_splitter.setSizes([250, 800, 350])