diff --git a/src/tagstudio/qt/controllers/field_suggest_box.py b/src/tagstudio/qt/controllers/field_suggest_box.py index 42250dfee..87b2346cc 100644 --- a/src/tagstudio/qt/controllers/field_suggest_box.py +++ b/src/tagstudio/qt/controllers/field_suggest_box.py @@ -36,10 +36,10 @@ def __init__(self, library: Library, settings: GlobalSettings, placeholder_text: self.layout().search_field.addAction(edit_field_on_add_action) edit_field_on_add_action.setChecked(self._settings.edit_field_on_add) edit_field_on_add_action.triggered.connect( - lambda checked: self.toggle_edit_on_field_add(checked) + lambda checked: self._toggle_edit_on_field_add(checked) ) - def toggle_edit_on_field_add(self, checked: bool) -> None: + def _toggle_edit_on_field_add(self, checked: bool) -> None: """Toggle the setting for opening the edit window after adding a field.""" self._settings.edit_field_on_add = checked self._settings.save() @@ -77,7 +77,7 @@ def _on_item_edit(self, item: BaseFieldTemplate) -> None: @override def _on_item_chosen(self, item: BaseFieldTemplate) -> None: self.item_chosen.emit(item) - self.done.emit() + self.done.emit("*") @override def _search_items(self, query: str) -> tuple[list[BaseFieldTemplate], list[BaseFieldTemplate]]: diff --git a/src/tagstudio/qt/controllers/preview_panel_controller.py b/src/tagstudio/qt/controllers/preview_panel_controller.py index 2fa94c296..1d13baba1 100644 --- a/src/tagstudio/qt/controllers/preview_panel_controller.py +++ b/src/tagstudio/qt/controllers/preview_panel_controller.py @@ -49,6 +49,7 @@ def __init__(self, driver: "QtDriver") -> None: super().__init__() self._driver = driver self._lib = self._driver.lib + self._settings = self._driver.settings self._selected: list[int] self._current_stats: FileAttributeData | None = None @@ -75,13 +76,13 @@ def _connect_callbacks(self) -> None: # Tag Search 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().tag_search_box.done.connect(partial(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._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(partial(self._field_added_callback)) # Previews self.layout().preview_thumb.stats_updated.connect(self._thumb_stats_updated_callback) @@ -122,13 +123,20 @@ def _open_field_search_callback(self) -> None: 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() + def _tag_added_callback(self, query: str): + if not query or not self._settings.keep_suggest_boxes_open: + self._set_item_mode(None) + self.layout().add_tag_button.setFocus() + else: + self._update_added_callback() + self.layout().tag_search_box.layout().search_field.setFocus() - def _field_added_callback(self): - self._set_item_mode(None) - self.layout().add_field_button.setFocus() + def _field_added_callback(self, query: str): + if not query or not self._settings.keep_suggest_boxes_open: + self._set_item_mode(None) + self.layout().add_field_button.setFocus() + else: + self.layout().field_search_box.layout().search_field.setFocus() def _update_added_callback(self): self.layout().tag_search_box.added = self.layout().containers.tags diff --git a/src/tagstudio/qt/controllers/suggest_box.py b/src/tagstudio/qt/controllers/suggest_box.py index 7b3bd13f2..a496f5014 100644 --- a/src/tagstudio/qt/controllers/suggest_box.py +++ b/src/tagstudio/qt/controllers/suggest_box.py @@ -5,7 +5,7 @@ import structlog from PySide6.QtCore import Signal -from PySide6.QtGui import QShowEvent +from PySide6.QtGui import QAction, QShowEvent, Qt from PySide6.QtWidgets import QGraphicsOpacityEffect, QWidget from tagstudio.core.library.alchemy.library import Library @@ -13,6 +13,7 @@ from tagstudio.qt.controllers.modal_content import ModalContent from tagstudio.qt.controllers.underlined_widget import UnderlinedWidget from tagstudio.qt.global_settings import GlobalSettings +from tagstudio.qt.translations import Translations from tagstudio.qt.views.stylesheets.stylesheets import ( autofill_line_edit_style, autofill_line_edit_top_style, @@ -42,7 +43,7 @@ def _item_name(item: object) -> str: class SuggestBox[T](QWidget): item_chosen = Signal(object) - done = Signal() + done = Signal(str) # Query def __init__( self, library: Library, settings: GlobalSettings, placeholder_text: str = "" @@ -57,8 +58,21 @@ def __init__( self.excluded: list[int] = [] self.setLayout(SuggestBoxView(placeholder_text)) + self.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) self._connect_callbacks() + self._keep_box_open_action = QAction(Translations["settings.keep_suggest_boxes_open"], self) + self._keep_box_open_action.setCheckable(True) + self.addAction(self._keep_box_open_action) + self.layout().search_field.addAction(self._keep_box_open_action) + self._keep_box_open_action.triggered.connect( + lambda checked: self._toggle_keep_open(checked) + ) + + def _toggle_keep_open(self, checked: bool) -> None: + self._settings.keep_suggest_boxes_open = checked + self._settings.save() + def set_placeholder_text(self, text: str) -> None: self.layout().search_field.setPlaceholderText(text) @@ -108,10 +122,8 @@ def _on_search_query_changed(self, query: str) -> None: def _on_search_query_submitted(self, query: str, always_create: bool = False) -> None: # Focus search field if no query - logger.info("Query submitted") if not query: - self.done.emit() - self.hide_and_reset() + self.done.emit(query) return elif not self.isHidden(): self.layout().search_field.setFocus() @@ -197,9 +209,10 @@ def _set_item_widget(self, item: T | None, index: int) -> None: # pyright: igno raise NotImplementedError() def _editing_finished_callback(self) -> None: - if self.layout().search_field.text() == "": - self.done.emit() - self.hide_and_reset() + # Only trigger when the search field is clicked off of and there's no query. + # NOTE: The search field is already cleared by this point when pressing enter. + if self.layout().search_field.text() == "" and not self.layout().search_field.hasFocus(): + self.done.emit("") def _create_item_from_modal(self, edit_item_panel: ModalContent) -> None: # pyright: ignore[reportUnusedParameter] raise NotImplementedError() @@ -213,6 +226,7 @@ def showEvent(self, event: QShowEvent) -> None: self._on_shift_held(held=False) self.layout().search_field.setDisabled(False) self._clear_search_query() + self._keep_box_open_action.setChecked(self._settings.keep_suggest_boxes_open) return super().showEvent(event) @override @@ -222,9 +236,7 @@ def layout(self) -> SuggestBoxView: @override def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: # When Escape is pressed, focus back on the search box. - if event.key() in { - QtCore.Qt.Key.Key_Escape, - QtCore.Qt.Key.Key_Enter, - QtCore.Qt.Key.Key_Return, - }: - self.hide_and_reset() + if event.key() in {QtCore.Qt.Key.Key_Enter, QtCore.Qt.Key.Key_Return}: + self.done.emit("*") + elif event.key() == QtCore.Qt.Key.Key_Escape: + self.done.emit("") diff --git a/src/tagstudio/qt/controllers/tag_suggest_box.py b/src/tagstudio/qt/controllers/tag_suggest_box.py index 27f9030a8..2f69f97c6 100644 --- a/src/tagstudio/qt/controllers/tag_suggest_box.py +++ b/src/tagstudio/qt/controllers/tag_suggest_box.py @@ -7,7 +7,7 @@ import structlog from PySide6.QtCore import Signal -from PySide6.QtGui import QAction, Qt +from PySide6.QtGui import QAction from PySide6.QtWidgets import QGraphicsOpacityEffect, QWidget from tagstudio.core.library.alchemy.library import Library @@ -33,18 +33,17 @@ def __init__( super().__init__(library, settings, placeholder_text) # Context Menu Actions + edit_tag_on_create_action = QAction(Translations["settings.edit_tag_on_create"], self) edit_tag_on_create_action.setCheckable(True) - self.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) self.addAction(edit_tag_on_create_action) - self.layout().search_field.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) self.layout().search_field.addAction(edit_tag_on_create_action) edit_tag_on_create_action.setChecked(self._settings.edit_tag_on_create) edit_tag_on_create_action.triggered.connect( - lambda checked: self.toggle_edit_on_tag_create(checked) + lambda checked: self._toggle_edit_on_tag_create(checked) ) - def toggle_edit_on_tag_create(self, checked: bool) -> None: + def _toggle_edit_on_tag_create(self, checked: bool) -> None: """Toggle the setting for opening the edit window after creating a tag.""" self._settings.edit_tag_on_create = checked self._settings.save() @@ -89,7 +88,8 @@ def _on_item_edit(self, item: Tag) -> None: @override def _on_item_chosen(self, item: Tag) -> None: self.item_chosen.emit(item.id) - self.done.emit() + self._clear_search_query() + self.done.emit("*") # The query does not matter @override def _search_items(self, query: str) -> tuple[list[Tag], list[Tag]]: @@ -144,7 +144,6 @@ def _create_item_from_modal(self, edit_item_panel: ModalContent) -> None: tag, parent_ids=edit_item_panel.parent_ids, aliases=edit_item_panel.aliases ) self._on_item_chosen(tag) - self._clear_search_query() edit_item_panel.hide() self._on_search_query_changed(self.layout().search_field.text()) diff --git a/src/tagstudio/qt/global_settings.py b/src/tagstudio/qt/global_settings.py index 0e9cda7b5..f1a4f1a3a 100644 --- a/src/tagstudio/qt/global_settings.py +++ b/src/tagstudio/qt/global_settings.py @@ -76,6 +76,7 @@ class GlobalSettings(BaseModel): tag_click_action: TagClickActionOption = Field(default=TagClickActionOption.DEFAULT) edit_tag_on_create: bool = Field(default=False) edit_field_on_add: bool = Field(default=True) + keep_suggest_boxes_open: bool = Field(default=True) theme: Theme = Field(default=Theme.SYSTEM) splash: Splash = Field(default=Splash.DEFAULT) windows_start_command: bool = Field(default=False) diff --git a/src/tagstudio/qt/mixed/settings_panel.py b/src/tagstudio/qt/mixed/settings_panel.py index c43f843c9..2304ba23c 100644 --- a/src/tagstudio/qt/mixed/settings_panel.py +++ b/src/tagstudio/qt/mixed/settings_panel.py @@ -231,6 +231,15 @@ def on_page_size_changed(): Translations["settings.edit_field_on_add"], self.edit_field_on_add_checkbox ) + # Keep Tag/Field Suggest Boxes Open After Adding Items + self.keep_suggest_boxes_open_checkbox = QCheckBox() + self.keep_suggest_boxes_open_checkbox.setChecked( + self.driver.settings.keep_suggest_boxes_open + ) + form_layout.addRow( + Translations["settings.keep_suggest_boxes_open"], self.keep_suggest_boxes_open_checkbox + ) + # TODO: Implement Library Settings def __build_library_settings(self): # pyright: ignore[reportUnusedFunction] form_layout = QFormLayout(self.library_settings_container) @@ -383,6 +392,7 @@ def get_settings(self) -> dict[str, Any]: # pyright: ignore[reportExplicitAny] "tag_click_action": self.tag_click_action_combobox.currentData(), "edit_tag_on_create": self.edit_tag_on_create_checkbox.isChecked(), "edit_field_on_add": self.edit_field_on_add_checkbox.isChecked(), + "keep_suggest_boxes_open": self.keep_suggest_boxes_open_checkbox.isChecked(), "date_format": self.dateformat_combobox.currentData(), "hour_format": self.hourformat_checkbox.isChecked(), "zero_padding": self.zeropadding_checkbox.isChecked(), @@ -407,6 +417,7 @@ def update_settings(self, driver: "QtDriver"): driver.settings.tag_click_action = settings["tag_click_action"] driver.settings.edit_tag_on_create = settings["edit_tag_on_create"] driver.settings.edit_field_on_add = settings["edit_field_on_add"] + driver.settings.keep_suggest_boxes_open = settings["keep_suggest_boxes_open"] driver.settings.date_format = settings["date_format"] driver.settings.hour_format = settings["hour_format"] driver.settings.zero_padding = settings["zero_padding"] diff --git a/src/tagstudio/qt/views/suggest_box_view.py b/src/tagstudio/qt/views/suggest_box_view.py index 39184e61d..a3389fd3d 100644 --- a/src/tagstudio/qt/views/suggest_box_view.py +++ b/src/tagstudio/qt/views/suggest_box_view.py @@ -71,6 +71,7 @@ def __init__(self, placeholder_text: str = "") -> None: self.search_field.setMinimumHeight(28) self.search_field.setPlaceholderText(placeholder_text) self.scroll_area.setFocusProxy(self.search_field) + self.search_field.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) # Finalize Layout self.addWidget(scroll_area_container) diff --git a/src/tagstudio/resources/translations/en.json b/src/tagstudio/resources/translations/en.json index 2766729e7..ddb523022 100644 --- a/src/tagstudio/resources/translations/en.json +++ b/src/tagstudio/resources/translations/en.json @@ -167,7 +167,7 @@ "home.search_or_create_fields": "Search or Create Fields…", "home.search_or_create_tags": "Search or Create Tags…", "home.search_tags": "Search Tags…", - "home.search.how_to_exit": "(Esc to Exit)", + "home.search.how_to_exit": "(Enter to Exit)", "home.search.view_limit": "View Limit:", "home.show_hidden_entries": "Show Hidden Entries", "home.thumbnail_size": "Thumbnail Size", @@ -329,6 +329,7 @@ "settings.global": "Global Settings", "settings.hourformat.label": "24-Hour Time", "settings.infinite_scroll": "Infinite Scrolling", + "settings.keep_suggest_boxes_open": "Keep Search Boxes Open After Adding Items", "settings.language": "Language", "settings.library": "Library Settings", "settings.localization": "Localization",