From 111ea768845f6b41a669625f3ba650af9f3061e7 Mon Sep 17 00:00:00 2001 From: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com> Date: Sun, 19 Jul 2026 11:24:23 -0700 Subject: [PATCH] refactor(ui): use Qt's .layout() method instead of _layout references --- .../qt/controllers/field_suggest_box.py | 14 +- .../controllers/preview_panel_controller.py | 129 +++++++++--------- .../qt/controllers/search_panel_controller.py | 6 +- src/tagstudio/qt/controllers/suggest_box.py | 52 +++---- .../qt/controllers/tag_suggest_box.py | 14 +- tests/qt/test_file_path_options.py | 4 +- tests/qt/test_preview_panel.py | 14 +- 7 files changed, 119 insertions(+), 114 deletions(-) diff --git a/src/tagstudio/qt/controllers/field_suggest_box.py b/src/tagstudio/qt/controllers/field_suggest_box.py index d1cd7ce7..9e756f6f 100644 --- a/src/tagstudio/qt/controllers/field_suggest_box.py +++ b/src/tagstudio/qt/controllers/field_suggest_box.py @@ -58,7 +58,7 @@ class FieldSuggestBox(SuggestBox[BaseFieldTemplate]): """ # NOTE: Unlike tags, creating new field templates will ALWAYS spawn an edit window # since the user needs to decide what type of field it should be before it's created. - query: str = self._layout.search_field.text() + query: str = self.layout().search_field.text() panel = EditFieldTemplateModal() modal = PanelModal( panel, @@ -126,7 +126,7 @@ class FieldSuggestBox(SuggestBox[BaseFieldTemplate]): self._clear_search_query() edit_item_panel.hide() - self._on_search_query_changed(self._layout.search_field.text()) + self._on_search_query_changed(self.layout().search_field.text()) @override def _edit_item(self, edit_item_panel: PanelWidget) -> None: @@ -136,18 +136,18 @@ class FieldSuggestBox(SuggestBox[BaseFieldTemplate]): self._lib.update_field_template( edit_item_panel.old_field_type, edit_item_panel.build_field_template() ) - self._update_items(self._layout.search_field.text()) + self._update_items(self.layout().search_field.text()) @override def _get_item_widget(self, index: int, library: Library | None) -> FieldTemplateWidget: """Gets the item widget at a specific index.""" # Create any new item widgets needed up to the given index - if self._layout.content_layout.count() <= index: - while self._layout.content_layout.count() <= index: + if self.layout().content_layout.count() <= index: + while self.layout().content_layout.count() <= index: pad_field_template_widget = FieldTemplateWidget() pad_field_template_widget.setHidden(True) - self._layout.content_layout.addWidget(pad_field_template_widget) + self.layout().content_layout.addWidget(pad_field_template_widget) - field_template_widget: QWidget = self._layout.content_layout.itemAt(index).widget() + field_template_widget: QWidget = self.layout().content_layout.itemAt(index).widget() assert isinstance(field_template_widget, FieldTemplateWidget) return field_template_widget diff --git a/src/tagstudio/qt/controllers/preview_panel_controller.py b/src/tagstudio/qt/controllers/preview_panel_controller.py index 21891a21..638b0ca8 100644 --- a/src/tagstudio/qt/controllers/preview_panel_controller.py +++ b/src/tagstudio/qt/controllers/preview_panel_controller.py @@ -7,6 +7,7 @@ from datetime import datetime as dt from enum import IntEnum from functools import partial from pathlib import Path +from typing import override from warnings import catch_warnings import structlog @@ -47,7 +48,6 @@ class _ItemMode(IntEnum): class PreviewPanel(QWidget): def __init__(self, driver: "QtDriver") -> None: super().__init__() - self._layout = PreviewPanelView(driver=driver, pixel_ratio=self.devicePixelRatio()) self._driver = driver self._lib = self._driver.lib self._selected: list[int] @@ -68,77 +68,77 @@ class PreviewPanel(QWidget): self, ) - self.setLayout(self._layout) + self.setLayout(PreviewPanelView(driver=driver, pixel_ratio=self.devicePixelRatio())) self._set_item_mode(None) self._connect_callbacks() def _connect_callbacks(self) -> None: # Tag Search - self._layout.add_tag_button.clicked.connect(lambda: self._set_item_mode(_ItemMode.TAG)) + self.layout().add_tag_button.clicked.connect(lambda: self._set_item_mode(_ItemMode.TAG)) self._open_tag_search_action.activated.connect(self._open_tag_search_callback) - self._layout.tag_search_box.done.connect(self._tag_added_callback) - self._layout.containers.on_tags_update.connect(self._update_added_callback) + self.layout().tag_search_box.done.connect(self._tag_added_callback) + self.layout().containers.on_tags_update.connect(self._update_added_callback) # Field Search - self._layout.add_field_button.clicked.connect(lambda: self._set_item_mode(_ItemMode.FIELD)) + self.layout().add_field_button.clicked.connect(lambda: self._set_item_mode(_ItemMode.FIELD)) self._open_field_search_action.activated.connect(self._open_field_search_callback) - self._layout.field_search_box.done.connect(self._field_added_callback) + self.layout().field_search_box.done.connect(self._field_added_callback) # Previews - self._layout.preview_thumb.stats_updated.connect(self._thumb_stats_updated_callback) - self._layout.preview_thumb.check_ffmpeg.connect(self._toggle_ffmpeg_warning) + self.layout().preview_thumb.stats_updated.connect(self._thumb_stats_updated_callback) + self.layout().preview_thumb.check_ffmpeg.connect(self._toggle_ffmpeg_warning) def _set_item_mode(self, mode: _ItemMode | None): def hide_and_disable_buttons(): - self._layout.add_tag_button.setHidden(True) - self._layout.add_tag_button.setEnabled(False) - self._layout.add_field_button.setHidden(True) - self._layout.add_field_button.setEnabled(False) + self.layout().add_tag_button.setHidden(True) + self.layout().add_tag_button.setEnabled(False) + self.layout().add_field_button.setHidden(True) + self.layout().add_field_button.setEnabled(False) def restore_buttons(): - self._layout.add_tag_button.setHidden(False) - self._layout.add_tag_button.setEnabled(True) - self._layout.add_field_button.setHidden(False) - self._layout.add_field_button.setEnabled(True) + self.layout().add_tag_button.setHidden(False) + self.layout().add_tag_button.setEnabled(True) + self.layout().add_field_button.setHidden(False) + self.layout().add_field_button.setEnabled(True) if mode == _ItemMode.TAG: - self._layout.tag_search_box.added = self._layout.containers.tags - self._layout.field_search_box.hide_and_reset() - self._layout.tag_search_box.setHidden(False) + self.layout().tag_search_box.added = self.layout().containers.tags + self.layout().field_search_box.hide_and_reset() + self.layout().tag_search_box.setHidden(False) hide_and_disable_buttons() elif mode == _ItemMode.FIELD: - self._layout.tag_search_box.hide_and_reset() - self._layout.field_search_box.setHidden(False) + self.layout().tag_search_box.hide_and_reset() + self.layout().field_search_box.setHidden(False) hide_and_disable_buttons() else: - self._layout.tag_search_box.hide_and_reset() - self._layout.field_search_box.hide_and_reset() + self.layout().tag_search_box.hide_and_reset() + self.layout().field_search_box.hide_and_reset() restore_buttons() def _open_tag_search_callback(self) -> None: - self._layout.add_tag_button.setFocus() - self._layout.add_tag_button.click() + self.layout().add_tag_button.setFocus() + self.layout().add_tag_button.click() def _open_field_search_callback(self) -> None: - self._layout.add_field_button.setFocus() - self._layout.add_field_button.click() + self.layout().add_field_button.setFocus() + self.layout().add_field_button.click() def _tag_added_callback(self): self._set_item_mode(None) - self._layout.add_tag_button.setFocus() + self.layout().add_tag_button.setFocus() def _field_added_callback(self): self._set_item_mode(None) - self._layout.add_field_button.setFocus() + self.layout().add_field_button.setFocus() def _update_added_callback(self): - self._layout.tag_search_box.added = self._layout.containers.tags + self.layout().tag_search_box.added = self.layout().containers.tags def _thumb_stats_updated_callback(self, filepath: Path, stats: FileAttributeData) -> None: if len(self._selected) != 1: return - if filepath != self._layout.preview_thumb.current_file: + if filepath != self.layout().preview_thumb.current_file: return if self._current_stats is None: @@ -151,18 +151,18 @@ class PreviewPanel(QWidget): if stats.duration is not None: self._current_stats.duration = stats.duration - self._layout.file_attrs.update_stats(filepath, self._current_stats) + self.layout().file_attrs.update_stats(filepath, self._current_stats) def _set_selection_callback(self) -> None: with catch_warnings(record=True): - self._layout.field_search_box.item_chosen.disconnect() - self._layout.tag_search_box.item_chosen.disconnect() + self.layout().field_search_box.item_chosen.disconnect() + self.layout().tag_search_box.item_chosen.disconnect() - self._layout.field_search_box.item_chosen.connect(self._add_field_to_selected) - self._layout.tag_search_box.item_chosen.connect(self._add_tag_to_selected) + self.layout().field_search_box.item_chosen.connect(self._add_field_to_selected) + self.layout().tag_search_box.item_chosen.connect(self._add_tag_to_selected) def _add_field_to_selected(self, template: BaseFieldTemplate) -> None: - self._layout.containers.add_field_to_selected(template) + self.layout().containers.add_field_to_selected(template) # TODO: Allow editing of fields across multiple entries at once. if len(self._selected) == 1: if self._driver.settings.edit_field_on_add: @@ -175,7 +175,7 @@ class PreviewPanel(QWidget): if entry_field is not None: self._edit_field(entry.id, entry_field) - self._layout.containers.update_from_entry(self._selected[0]) + self.layout().containers.update_from_entry(self._selected[0]) def _edit_field(self, entry_id: int, field: BaseField) -> None: # TODO: A lot of this code is similar to or straight up shared with FieldContainers. @@ -190,7 +190,7 @@ class PreviewPanel(QWidget): inline_title=False, ) edit_modal.saved_data.connect( - partial(self._layout.containers.update_text_field_callback, field, entry_id) + partial(self.layout().containers.update_text_field_callback, field, entry_id) ) edit_modal.show() elif type(field) is DatetimeField: @@ -201,21 +201,21 @@ class PreviewPanel(QWidget): inline_title=False, ) edit_modal.saved_data.connect( - partial(self._layout.containers.update_datetime_field_callback, field, entry_id) + partial(self.layout().containers.update_datetime_field_callback, field, entry_id) ) edit_modal.show() def _add_tag_to_selected(self, tag_id: int) -> None: - self._layout.containers.add_tags_to_selected(tag_id) + self.layout().containers.add_tags_to_selected(tag_id) if len(self._selected) == 1: - self._layout.containers.update_from_entry(self._selected[0]) + self.layout().containers.update_from_entry(self._selected[0]) def _toggle_ffmpeg_warning(self, enable_warning: bool = True) -> None: if enable_warning and (not FfmpegStatus.which() or not FfprobeStatus.which()): - self._layout.warning_banner.show() + self.layout().warning_banner.show() return - self._layout.warning_banner.hide() + self.layout().warning_banner.hide() def set_selection(self, selected: list[int], update_preview: bool = True) -> None: """Render the panel widgets with the newest data from the Library. @@ -230,13 +230,13 @@ class PreviewPanel(QWidget): try: # No Items Selected if len(selected) == 0: - self._layout.preview_thumb.hide_preview() + self.layout().preview_thumb.hide_preview() self._current_stats = None - self._layout.file_attrs.update_stats() - self._layout.file_attrs.update_date_label() - self._layout.containers.hide_containers() - self._layout.add_tag_button.setEnabled(False) - self._layout.add_field_button.setEnabled(False) + self.layout().file_attrs.update_stats() + self.layout().file_attrs.update_date_label() + self.layout().containers.hide_containers() + self.layout().add_tag_button.setEnabled(False) + self.layout().add_field_button.setEnabled(False) # One Item Selected elif len(selected) == 1: @@ -244,25 +244,25 @@ class PreviewPanel(QWidget): entry: Entry = unwrap(self._lib.get_entry(entry_id)) filepath: Path = unwrap(self._lib.library_dir) / entry.path - if filepath != self._layout.preview_thumb.current_file: + if filepath != self.layout().preview_thumb.current_file: self._current_stats = None if update_preview: - stats: FileAttributeData = self._layout.preview_thumb.display_file(filepath) + stats: FileAttributeData = self.layout().preview_thumb.display_file(filepath) self._current_stats = stats - self._layout.file_attrs.update_stats(filepath, stats) - self._layout.file_attrs.update_date_label(filepath) - self._layout.containers.update_from_entry(entry_id) + self.layout().file_attrs.update_stats(filepath, stats) + self.layout().file_attrs.update_date_label(filepath) + self.layout().containers.update_from_entry(entry_id) self._set_selection_callback() # Multiple Selected Items elif len(selected) > 1: # items: list[Entry] = [self.lib.get_entry_full(x) for x in self.driver.selected] - self._layout.preview_thumb.hide_preview() # TODO: Render mixed selection + self.layout().preview_thumb.hide_preview() # TODO: Render mixed selection self._current_stats = None - self._layout.file_attrs.update_multi_selection(len(selected)) - self._layout.file_attrs.update_date_label() - self._layout.containers.hide_containers() # TODO: Allow for mixed editing + self.layout().file_attrs.update_multi_selection(len(selected)) + self.layout().file_attrs.update_date_label() + self.layout().containers.hide_containers() # TODO: Allow for mixed editing self._set_selection_callback() except Exception as e: @@ -270,8 +270,13 @@ class PreviewPanel(QWidget): def stop_media_playback(self) -> None: """Stop any media playback in the preview panel.""" - self._layout.preview_thumb.media_player.stop() + self.layout().preview_thumb.media_player.stop() @property def containers(self) -> FieldContainers: - return self._layout.containers + return self.layout().containers + + @override + def layout(self) -> PreviewPanelView: + """Return the typed layout for this widget.""" + return super().layout() # pyright: ignore[reportReturnType] diff --git a/src/tagstudio/qt/controllers/search_panel_controller.py b/src/tagstudio/qt/controllers/search_panel_controller.py index 0b743931..bcf535ec 100644 --- a/src/tagstudio/qt/controllers/search_panel_controller.py +++ b/src/tagstudio/qt/controllers/search_panel_controller.py @@ -52,9 +52,9 @@ class SearchPanel[T](PanelWidget): super().__init__() self.view = view self.is_chooser = is_chooser - self._layout = QVBoxLayout(self) - self._layout.setContentsMargins(0, 0, 0, 0) - self._layout.addWidget(self.view) + self.setLayout(QVBoxLayout(self)) + self.layout().setContentsMargins(0, 0, 0, 0) + self.layout().addWidget(self.view) self.view.connect_callbacks(self) self._driver: QtDriver | None = None self.exclude: list[int] = exclude or [] diff --git a/src/tagstudio/qt/controllers/suggest_box.py b/src/tagstudio/qt/controllers/suggest_box.py index 04cfe816..d4556fa2 100644 --- a/src/tagstudio/qt/controllers/suggest_box.py +++ b/src/tagstudio/qt/controllers/suggest_box.py @@ -47,7 +47,6 @@ class SuggestBox[T](QWidget): def __init__(self, driver: "QtDriver", view: SuggestBoxView) -> None: super().__init__() - self._layout = view self._driver = driver self._limit = 5 self._is_shift_held = False @@ -55,42 +54,42 @@ class SuggestBox[T](QWidget): self.added: list[int] = [] self.excluded: list[int] = [] - self.setLayout(self._layout) + self.setLayout(view) self._connect_callbacks() def hide_and_reset(self): self.hide() - self._layout.search_field.setDisabled(True) + self.layout().search_field.setDisabled(True) self._on_shift_held(held=False) def _connect_callbacks(self) -> None: - self._layout.search_field.textChanged.connect(self._on_search_query_changed) - self._layout.search_field.editingFinished.connect(self._editing_finished_callback) - self._layout.search_field.return_pressed.connect( - lambda: self._on_search_query_submitted(self._layout.search_field.text()) + self.layout().search_field.textChanged.connect(self._on_search_query_changed) + self.layout().search_field.editingFinished.connect(self._editing_finished_callback) + self.layout().search_field.return_pressed.connect( + lambda: self._on_search_query_submitted(self.layout().search_field.text()) ) - self._layout.search_field.shift_return_pressed.connect( + self.layout().search_field.shift_return_pressed.connect( lambda: self._on_search_query_submitted( - self._layout.search_field.text(), always_create=True + self.layout().search_field.text(), always_create=True ) ) - self._layout.search_field.shift_holding.connect(lambda held: self._on_shift_held(held)) + self.layout().search_field.shift_holding.connect(lambda held: self._on_shift_held(held)) def _on_shift_held(self, held: bool): if held: self._is_shift_held = True opacity_effect = QGraphicsOpacityEffect(self) opacity_effect.setOpacity(0.3) - if self._layout.content_layout.count() > 0: - self._layout.content_layout.itemAt(0).widget().setGraphicsEffect(opacity_effect) + if self.layout().content_layout.count() > 0: + self.layout().content_layout.itemAt(0).widget().setGraphicsEffect(opacity_effect) else: self._is_shift_held = False - if self._layout.content_layout.count() > 0: - self._layout.content_layout.itemAt(0).widget().setGraphicsEffect(None) # pyright: ignore[reportArgumentType] + if self.layout().content_layout.count() > 0: + self.layout().content_layout.itemAt(0).widget().setGraphicsEffect(None) # pyright: ignore[reportArgumentType] def _clear_search_query(self) -> None: - self._layout.search_field.setText("") + self.layout().search_field.setText("") def _get_item_widget(self, index: int, library: Library) -> Any: # pyright: ignore raise NotImplementedError() @@ -106,7 +105,7 @@ class SuggestBox[T](QWidget): self.hide_and_reset() return elif not self.isHidden(): - self._layout.search_field.setFocus() + self.layout().search_field.setFocus() # Create and add item if no search results if (len(self._search_results) <= 0) or always_create: @@ -173,14 +172,14 @@ class SuggestBox[T](QWidget): item: T | None = all_results[i] if i < len(all_results) else None self._set_item_widget(item=item, index=i) - if self._layout.content_layout.isEmpty(): - self._layout.scroll_area.setHidden(True) - self._layout.content_layout.setContentsMargins(0, 0, 0, 0) - self._layout.search_field.setStyleSheet(autofill_line_edit_style()) + if self.layout().content_layout.isEmpty(): + self.layout().scroll_area.setHidden(True) + self.layout().content_layout.setContentsMargins(0, 0, 0, 0) + self.layout().search_field.setStyleSheet(autofill_line_edit_style()) else: - self._layout.scroll_area.setHidden(False) - self._layout.content_layout.setContentsMargins(6, 6, 6, 6) - self._layout.search_field.setStyleSheet(autofill_line_edit_top_style()) + self.layout().scroll_area.setHidden(False) + self.layout().content_layout.setContentsMargins(6, 6, 6, 6) + self.layout().search_field.setStyleSheet(autofill_line_edit_top_style()) def _search_items(self, query: str) -> tuple[list[T], list[T]]: # pyright: ignore[reportUnusedParameter] raise NotImplementedError() @@ -189,7 +188,7 @@ class SuggestBox[T](QWidget): raise NotImplementedError() def _editing_finished_callback(self): - if self._layout.search_field.text() == "": + if self.layout().search_field.text() == "": self.done.emit() self.hide_and_reset() @@ -203,13 +202,14 @@ class SuggestBox[T](QWidget): def showEvent(self, event: QShowEvent) -> None: self._update_items() self._on_shift_held(held=False) - self._layout.search_field.setDisabled(False) + self.layout().search_field.setDisabled(False) self._clear_search_query() return super().showEvent(event) @override def layout(self) -> SuggestBoxView: - return self._layout + """Return the typed layout for this widget.""" + return super().layout() # pyright: ignore[reportReturnType] @override def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: diff --git a/src/tagstudio/qt/controllers/tag_suggest_box.py b/src/tagstudio/qt/controllers/tag_suggest_box.py index 87e9fde9..b59ae856 100644 --- a/src/tagstudio/qt/controllers/tag_suggest_box.py +++ b/src/tagstudio/qt/controllers/tag_suggest_box.py @@ -64,7 +64,7 @@ class TagSuggestBox(SuggestBox[Tag]): Args: add_to_entry (bool): Should this item be added to currently selected entries? """ - query: str = self._layout.search_field.text() + query: str = self.layout().search_field.text() if self._driver.settings.edit_tag_on_create: panel: BuildTagPanel = BuildTagPanel(self._lib) @@ -150,7 +150,7 @@ class TagSuggestBox(SuggestBox[Tag]): self._clear_search_query() edit_item_panel.hide() - self._on_search_query_changed(self._layout.search_field.text()) + self._on_search_query_changed(self.layout().search_field.text()) @override def _edit_item(self, edit_item_panel: PanelWidget) -> None: @@ -162,19 +162,19 @@ class TagSuggestBox(SuggestBox[Tag]): parent_ids=edit_item_panel.parent_ids, aliases=edit_item_panel.aliases, ) - self._update_items(self._layout.search_field.text()) + self._update_items(self.layout().search_field.text()) @override def _get_item_widget(self, index: int, library: Library | None) -> TagWidget: """Gets the item widget at a specific index.""" # Create any new item widgets needed up to the given index - if self._layout.content_layout.count() <= index: - while self._layout.content_layout.count() <= index: + if self.layout().content_layout.count() <= index: + while self.layout().content_layout.count() <= index: tag_widget = TagWidget(tag=None, has_edit=True, has_remove=True, library=library) tag_widget.on_remove.connect(self._update_items) tag_widget.setHidden(True) - self._layout.content_layout.addWidget(tag_widget) + self.layout().content_layout.addWidget(tag_widget) - tag_widget: QWidget = self._layout.content_layout.itemAt(index).widget() + tag_widget: QWidget = self.layout().content_layout.itemAt(index).widget() assert isinstance(tag_widget, TagWidget) return tag_widget diff --git a/tests/qt/test_file_path_options.py b/tests/qt/test_file_path_options.py index 54c86ced..252a960f 100644 --- a/tests/qt/test_file_path_options.py +++ b/tests/qt/test_file_path_options.py @@ -74,7 +74,7 @@ def test_file_path_display( entry = qt_driver.lib.get_entry(2) assert isinstance(entry, Entry) filename = entry.path - panel._layout.file_attrs.update_stats(filepath=unwrap(qt_driver.lib.library_dir) / filename) + panel.layout().file_attrs.update_stats(filepath=unwrap(qt_driver.lib.library_dir) / filename) # Generate the expected file string. # This is copied directly from the file_attributes.py file @@ -92,7 +92,7 @@ def test_file_path_display( file_str += f"{'\u200b'.join(part_)}" # Assert the file path is displayed correctly - assert panel._layout.file_attrs.file_label.text() == file_str + assert panel.layout().file_attrs.file_label.text() == file_str @pytest.mark.parametrize( diff --git a/tests/qt/test_preview_panel.py b/tests/qt/test_preview_panel.py index 5a6a9a00..05068c32 100644 --- a/tests/qt/test_preview_panel.py +++ b/tests/qt/test_preview_panel.py @@ -17,10 +17,10 @@ def test_update_selection_empty(qt_driver: QtDriver): panel.set_selection(qt_driver.selected) # Panel should disable UI that allows for entry modification - assert panel._layout.add_tag_button.isEnabled() == panel._layout.add_field_button.isEnabled() + assert panel.layout().add_tag_button.isEnabled() == panel.layout().add_field_button.isEnabled() assert ( - not panel._layout.add_tag_button.isEnabled() - and not panel._layout.add_field_button.isEnabled() + not panel.layout().add_tag_button.isEnabled() + and not panel.layout().add_field_button.isEnabled() ) @@ -32,8 +32,8 @@ def test_update_selection_single(qt_driver: QtDriver, entry_full: Entry): panel.set_selection(qt_driver.selected) # Panel should enable UI that allows for entry modification - assert panel._layout.add_tag_button.isEnabled() == panel._layout.add_field_button.isEnabled() - assert panel._layout.add_tag_button.isEnabled() and panel._layout.add_field_button.isEnabled() + assert panel.layout().add_tag_button.isEnabled() == panel.layout().add_field_button.isEnabled() + assert panel.layout().add_tag_button.isEnabled() and panel.layout().add_field_button.isEnabled() def test_update_selection_multiple(qt_driver: QtDriver): @@ -45,5 +45,5 @@ def test_update_selection_multiple(qt_driver: QtDriver): panel.set_selection(qt_driver.selected) # Panel should enable UI that allows for entry modification - assert panel._layout.add_tag_button.isEnabled() == panel._layout.add_field_button.isEnabled() - assert panel._layout.add_tag_button.isEnabled() and panel._layout.add_field_button.isEnabled() + assert panel.layout().add_tag_button.isEnabled() == panel.layout().add_field_button.isEnabled() + assert panel.layout().add_tag_button.isEnabled() and panel.layout().add_field_button.isEnabled()