Skip to content

Commit 07dd3a8

Browse files
committed
Fix: make properties panel action buttons always visible across all tabs
1 parent a17739a commit 07dd3a8

2 files changed

Lines changed: 66 additions & 44 deletions

File tree

datalab/gui/panel/base.py

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ class ProcessingReport:
172172
message: str | None = None
173173

174174

175-
class ObjectProp(QW.QTabWidget):
175+
class ObjectProp(QW.QWidget):
176176
"""Object handling panel properties
177177
178178
Args:
@@ -182,8 +182,16 @@ class ObjectProp(QW.QTabWidget):
182182

183183
def __init__(self, panel: BaseDataPanel, objclass: SignalObj | ImageObj) -> None:
184184
super().__init__(panel)
185-
self.setTabBarAutoHide(True)
186-
self.setTabPosition(QW.QTabWidget.West)
185+
186+
# Create vertical layout for the container
187+
layout = QW.QVBoxLayout(self)
188+
layout.setContentsMargins(0, 0, 0, 0)
189+
layout.setSpacing(0)
190+
191+
# Create the tab widget
192+
self.tabwidget = QW.QTabWidget(self)
193+
self.tabwidget.setTabBarAutoHide(True)
194+
self.tabwidget.setTabPosition(QW.QTabWidget.West)
187195

188196
self.panel = panel
189197
self.objclass = objclass
@@ -204,12 +212,6 @@ def __init__(self, panel: BaseDataPanel, objclass: SignalObj | ImageObj) -> None
204212
self.properties.setEnabled(False)
205213
self.__original_values: dict[str, Any] = {}
206214

207-
self.add_prop_layout = QW.QHBoxLayout()
208-
playout: QW.QGridLayout = self.properties.edit.layout
209-
playout.addLayout(
210-
self.add_prop_layout, playout.rowCount() - 1, 0, 1, 1, QC.Qt.AlignLeft
211-
)
212-
213215
# Create Analysis and History widgets
214216
font = Conf.proc.small_mono_font.get_font()
215217

@@ -221,25 +223,42 @@ def __init__(self, panel: BaseDataPanel, objclass: SignalObj | ImageObj) -> None
221223
self.analysis_parameters.setReadOnly(True)
222224
self.analysis_parameters.setFont(font)
223225

224-
self.addTab(self.processing_history, get_icon("history.svg"), _("History"))
225-
self.addTab(
226+
self.tabwidget.addTab(
227+
self.processing_history, get_icon("history.svg"), _("History")
228+
)
229+
self.tabwidget.addTab(
226230
self.analysis_parameters, get_icon("analysis.svg"), _("Analysis parameters")
227231
)
228-
self.addTab(self.properties, get_icon("properties.svg"), _("Properties"))
232+
self.tabwidget.addTab(
233+
self.properties, get_icon("properties.svg"), _("Properties")
234+
)
229235

230236
self.processing_history.textChanged.connect(self._update_tab_visibility)
231237
self.analysis_parameters.textChanged.connect(self._update_tab_visibility)
232238

239+
# Create a permanent button area at the bottom, always visible regardless of tab
240+
self.add_prop_layout = QW.QHBoxLayout()
241+
self.add_prop_layout.setContentsMargins(5, 5, 5, 5)
242+
self.add_prop_layout.setSpacing(10)
243+
self.add_prop_layout.addStretch()
244+
245+
# Add tab widget and button area to main layout
246+
layout.addWidget(self.tabwidget)
247+
layout.addLayout(self.add_prop_layout)
248+
233249
def _update_tab_visibility(self) -> None:
234250
"""Update visibility of tabs based on their content."""
235251
for textedit in (self.processing_history, self.analysis_parameters):
236-
tab_index = self.indexOf(textedit)
252+
tab_index = self.tabwidget.indexOf(textedit)
237253
if tab_index >= 0:
238254
has_content = bool(textedit.toPlainText().strip())
239-
self.setTabVisible(tab_index, has_content)
255+
self.tabwidget.setTabVisible(tab_index, has_content)
240256

241257
def add_button(self, button: QW.QPushButton) -> None:
242-
"""Add additional button on bottom of properties panel"""
258+
"""Add additional button to the permanent action bar.
259+
260+
Buttons added here are always visible regardless of which tab is active.
261+
"""
243262
self.add_prop_layout.addWidget(button)
244263

245264
def display_analysis_parameters(self, obj: SignalObj | ImageObj) -> bool:
@@ -380,13 +399,13 @@ def update_properties_from(self, obj: SignalObj | ImageObj | None = None) -> Non
380399
# Remove only Creation and Processing tabs (dynamic tabs)
381400
# Use widget references instead of text labels for reliable identification
382401
if self.creation_scroll is not None:
383-
index = self.indexOf(self.creation_scroll)
402+
index = self.tabwidget.indexOf(self.creation_scroll)
384403
if index >= 0:
385-
self.removeTab(index)
404+
self.tabwidget.removeTab(index)
386405
if self.processing_scroll is not None:
387-
index = self.indexOf(self.processing_scroll)
406+
index = self.tabwidget.indexOf(self.processing_scroll)
388407
if index >= 0:
389-
self.removeTab(index)
408+
self.tabwidget.removeTab(index)
390409

391410
# Reset references for dynamic tabs
392411
self.creation_param_editor = None
@@ -413,13 +432,13 @@ def update_properties_from(self, obj: SignalObj | ImageObj | None = None) -> Non
413432
# 3. Processing tab if it exists
414433
# 4. Properties tab
415434
if has_analysis_parameters:
416-
self.setCurrentWidget(self.analysis_parameters)
435+
self.tabwidget.setCurrentWidget(self.analysis_parameters)
417436
elif has_creation_tab:
418-
self.setCurrentWidget(self.creation_scroll)
437+
self.tabwidget.setCurrentWidget(self.creation_scroll)
419438
elif has_processing_tab:
420-
self.setCurrentWidget(self.processing_scroll)
439+
self.tabwidget.setCurrentWidget(self.processing_scroll)
421440
else:
422-
self.setCurrentWidget(self.properties)
441+
self.tabwidget.setCurrentWidget(self.properties)
423442

424443
def get_changed_properties(self) -> dict[str, Any]:
425444
"""Get dictionary of properties that have changed from original values.
@@ -504,21 +523,23 @@ def setup_creation_tab(
504523

505524
# Remove existing Creation tab if it exists
506525
if self.creation_scroll is not None:
507-
index = self.indexOf(self.creation_scroll)
526+
index = self.tabwidget.indexOf(self.creation_scroll)
508527
if index >= 0:
509-
self.removeTab(index)
528+
self.tabwidget.removeTab(index)
510529

511530
# Set the parameter editor as the scroll area widget
512531
# Creation tab is always at index 0 (before all other tabs)
513532
self.creation_scroll = QW.QScrollArea()
514533
self.creation_scroll.setWidgetResizable(True)
515534
self.creation_scroll.setWidget(editor)
516535
icon_name = "new_sig.svg" if isinstance(obj, SignalObj) else "new_ima.svg"
517-
self.insertTab(0, self.creation_scroll, get_icon(icon_name), _("Creation"))
536+
self.tabwidget.insertTab(
537+
0, self.creation_scroll, get_icon(icon_name), _("Creation")
538+
)
518539

519540
# Set as current tab if requested
520541
if set_current:
521-
self.setCurrentWidget(self.creation_scroll)
542+
self.tabwidget.setCurrentWidget(self.creation_scroll)
522543

523544
return True
524545

@@ -654,15 +675,16 @@ def setup_processing_tab(
654675

655676
# Remove existing Processing tab if it exists
656677
if self.processing_scroll is not None:
657-
index = self.indexOf(self.processing_scroll)
678+
index = self.tabwidget.indexOf(self.processing_scroll)
658679
if index >= 0:
659-
self.removeTab(index)
680+
self.tabwidget.removeTab(index)
660681

661682
# Processing tab comes after Creation tab (if it exists)
662683
# Find the correct insertion index: after Creation (index 0) if it exists,
663684
# otherwise at index 0
664685
has_creation = (
665-
self.creation_scroll is not None and self.indexOf(self.creation_scroll) >= 0
686+
self.creation_scroll is not None
687+
and self.tabwidget.indexOf(self.creation_scroll) >= 0
666688
)
667689
insert_index = 1 if has_creation else 0
668690

@@ -675,7 +697,7 @@ def setup_processing_tab(
675697
)
676698

677699
self.processing_scroll.setWidget(editor)
678-
self.insertTab(
700+
self.tabwidget.insertTab(
679701
insert_index,
680702
self.processing_scroll,
681703
get_icon("libre-tech-ram.svg"),
@@ -684,7 +706,7 @@ def setup_processing_tab(
684706

685707
# Set as current tab if requested
686708
if set_current:
687-
self.setCurrentWidget(self.processing_scroll)
709+
self.tabwidget.setCurrentWidget(self.processing_scroll)
688710

689711
return True
690712

datalab/tests/features/common/interactive_processing_test.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -281,14 +281,14 @@ def test_no_duplicate_creation_tabs():
281281
assert objprop.creation_scroll is not None
282282

283283
# Count how many Creation tabs exist initially using the widget reference
284-
initial_index = objprop.indexOf(objprop.creation_scroll)
284+
initial_index = objprop.tabwidget.indexOf(objprop.creation_scroll)
285285
assert initial_index >= 0, "Creation tab should be present"
286286

287287
# Count tabs by checking if they reference the same scroll widget
288288
initial_count = sum(
289289
1
290-
for i in range(objprop.count())
291-
if objprop.widget(i) is objprop.creation_scroll
290+
for i in range(objprop.tabwidget.count())
291+
if objprop.tabwidget.widget(i) is objprop.creation_scroll
292292
)
293293
assert initial_count == 1, "Should have exactly one Creation tab initially"
294294

@@ -309,16 +309,16 @@ def test_no_duplicate_creation_tabs():
309309
# Count Creation tabs again - should still be just one
310310
creation_count = sum(
311311
1
312-
for i in range(objprop.count())
313-
if objprop.widget(i) is objprop.creation_scroll
312+
for i in range(objprop.tabwidget.count())
313+
if objprop.tabwidget.widget(i) is objprop.creation_scroll
314314
)
315315
assert creation_count == 1, (
316316
f"Should still have exactly one Creation tab after "
317317
f"applying amplitude={amplitude}"
318318
)
319319

320320
# Verify that the Creation tab is the current tab
321-
assert objprop.currentWidget() is objprop.creation_scroll, (
321+
assert objprop.tabwidget.currentWidget() is objprop.creation_scroll, (
322322
f"Creation tab should remain current after "
323323
f"applying amplitude={amplitude}"
324324
)
@@ -523,14 +523,14 @@ def test_no_duplicate_processing_tabs():
523523
assert objprop.processing_scroll is not None
524524

525525
# Count how many Processing tabs exist initially
526-
initial_index = objprop.indexOf(objprop.processing_scroll)
526+
initial_index = objprop.tabwidget.indexOf(objprop.processing_scroll)
527527
assert initial_index >= 0, "Processing tab should be present"
528528

529529
# Count tabs by checking if they reference the same scroll widget
530530
initial_count = sum(
531531
1
532-
for i in range(objprop.count())
533-
if objprop.widget(i) is objprop.processing_scroll
532+
for i in range(objprop.tabwidget.count())
533+
if objprop.tabwidget.widget(i) is objprop.processing_scroll
534534
)
535535
assert initial_count == 1, (
536536
"Should have exactly one Processing tab initially"
@@ -554,16 +554,16 @@ def test_no_duplicate_processing_tabs():
554554
# Count Processing tabs again - should still be just one
555555
processing_count = sum(
556556
1
557-
for i in range(objprop.count())
558-
if objprop.widget(i) is objprop.processing_scroll
557+
for i in range(objprop.tabwidget.count())
558+
if objprop.tabwidget.widget(i) is objprop.processing_scroll
559559
)
560560
assert processing_count == 1, (
561561
f"Should still have exactly one Processing tab after "
562562
f"applying value={value}"
563563
)
564564

565565
# Verify that the Processing tab is the current tab
566-
assert objprop.currentWidget() is objprop.processing_scroll, (
566+
assert objprop.tabwidget.currentWidget() is objprop.processing_scroll, (
567567
f"Processing tab should remain current after applying value={value}"
568568
)
569569

0 commit comments

Comments
 (0)