diff --git a/src/tagstudio/core/enums.py b/src/tagstudio/core/enums.py index 027a78725..467dab7fd 100644 --- a/src/tagstudio/core/enums.py +++ b/src/tagstudio/core/enums.py @@ -24,6 +24,15 @@ class ShowFilepathOption(int, enum.Enum): DEFAULT = SHOW_RELATIVE_PATHS +class TagClickActionOption(int, enum.Enum): + """Values representing the options for the "tag_click_action" setting.""" + + OPEN_EDIT = 0 + SET_SEARCH = 1 + ADD_TO_SEARCH = 2 + DEFAULT = OPEN_EDIT + + class Theme(str, enum.Enum): COLOR_BG_DARK = "#65000000" COLOR_BG_LIGHT = "#22000000" diff --git a/src/tagstudio/core/global_settings.py b/src/tagstudio/core/global_settings.py index a30fdeec1..441d3ab8f 100644 --- a/src/tagstudio/core/global_settings.py +++ b/src/tagstudio/core/global_settings.py @@ -11,7 +11,7 @@ import toml from pydantic import BaseModel, Field -from tagstudio.core.enums import ShowFilepathOption +from tagstudio.core.enums import ShowFilepathOption, TagClickActionOption if platform.system() == "Windows": DEFAULT_GLOBAL_SETTINGS_PATH = ( @@ -50,6 +50,7 @@ class GlobalSettings(BaseModel): page_size: int = Field(default=100) show_filepath: ShowFilepathOption = Field(default=ShowFilepathOption.DEFAULT) theme: Theme = Field(default=Theme.SYSTEM) + tag_click_action: TagClickActionOption = Field(default=TagClickActionOption.DEFAULT) date_format: str = Field(default="%x") hour_format: bool = Field(default=True) diff --git a/src/tagstudio/core/library/alchemy/enums.py b/src/tagstudio/core/library/alchemy/enums.py index 980ddd2dc..f1d3106c3 100644 --- a/src/tagstudio/core/library/alchemy/enums.py +++ b/src/tagstudio/core/library/alchemy/enums.py @@ -125,6 +125,9 @@ def with_sorting_mode(self, mode: SortingModeEnum) -> "BrowsingState": def with_sorting_direction(self, ascending: bool) -> "BrowsingState": return replace(self, ascending=ascending) + def with_search_query(self, search_query: str) -> "BrowsingState": + return replace(self, query=search_query) + class FieldTypeEnum(enum.Enum): TEXT_LINE = "Text Line" diff --git a/src/tagstudio/qt/modals/settings_panel.py b/src/tagstudio/qt/modals/settings_panel.py index 820be0288..b4b07b44f 100644 --- a/src/tagstudio/qt/modals/settings_panel.py +++ b/src/tagstudio/qt/modals/settings_panel.py @@ -17,7 +17,7 @@ QWidget, ) -from tagstudio.core.enums import ShowFilepathOption +from tagstudio.core.enums import ShowFilepathOption, TagClickActionOption from tagstudio.core.global_settings import Theme from tagstudio.qt.translations import DEFAULT_TRANSLATION, LANGUAGES, Translations from tagstudio.qt.widgets.panel import PanelModal, PanelWidget @@ -25,17 +25,11 @@ if TYPE_CHECKING: from tagstudio.qt.ts_qt import QtDriver -FILEPATH_OPTION_MAP: dict[ShowFilepathOption, str] = { - ShowFilepathOption.SHOW_FULL_PATHS: Translations["settings.filepath.option.full"], - ShowFilepathOption.SHOW_RELATIVE_PATHS: Translations["settings.filepath.option.relative"], - ShowFilepathOption.SHOW_FILENAMES_ONLY: Translations["settings.filepath.option.name"], -} +FILEPATH_OPTION_MAP: dict[ShowFilepathOption, str] = {} -THEME_MAP: dict[Theme, str] = { - Theme.DARK: Translations["settings.theme.dark"], - Theme.LIGHT: Translations["settings.theme.light"], - Theme.SYSTEM: Translations["settings.theme.system"], -} +THEME_MAP: dict[Theme, str] = {} + +TAG_CLICK_ACTION_MAP: dict[TagClickActionOption, str] = {} DATE_FORMAT_MAP: dict[str, str] = { "%d/%m/%y": "21/08/24", @@ -61,6 +55,29 @@ class SettingsPanel(PanelWidget): def __init__(self, driver: "QtDriver"): super().__init__() + # set these "constants" because language will be loaded from config shortly after startup + # and we want to use the current language for the dropdowns + global FILEPATH_OPTION_MAP, THEME_MAP, TAG_CLICK_ACTION_MAP + FILEPATH_OPTION_MAP = { + ShowFilepathOption.SHOW_FULL_PATHS: Translations["settings.filepath.option.full"], + ShowFilepathOption.SHOW_RELATIVE_PATHS: Translations[ + "settings.filepath.option.relative" + ], + ShowFilepathOption.SHOW_FILENAMES_ONLY: Translations["settings.filepath.option.name"], + } + THEME_MAP = { + Theme.DARK: Translations["settings.theme.dark"], + Theme.LIGHT: Translations["settings.theme.light"], + Theme.SYSTEM: Translations["settings.theme.system"], + } + TAG_CLICK_ACTION_MAP = { + TagClickActionOption.OPEN_EDIT: Translations["settings.tag_click_action.open_edit"], + TagClickActionOption.SET_SEARCH: Translations["settings.tag_click_action.set_search"], + TagClickActionOption.ADD_TO_SEARCH: Translations[ + "settings.tag_click_action.add_to_search" + ], + } + self.driver = driver self.setMinimumSize(400, 300) @@ -158,13 +175,27 @@ def on_page_size_changed(): self.theme_combobox = QComboBox() for k in THEME_MAP: self.theme_combobox.addItem(THEME_MAP[k], k) - theme: Theme = self.driver.settings.theme + theme = self.driver.settings.theme if theme not in THEME_MAP: theme = Theme.DEFAULT self.theme_combobox.setCurrentIndex(list(THEME_MAP.keys()).index(theme)) self.theme_combobox.currentIndexChanged.connect(self.__update_restart_label) form_layout.addRow(Translations["settings.theme.label"], self.theme_combobox) + # Tag Click Action + self.tag_click_action_combobox = QComboBox() + for k in TAG_CLICK_ACTION_MAP: + self.tag_click_action_combobox.addItem(TAG_CLICK_ACTION_MAP[k], k) + tag_click_action = self.driver.settings.tag_click_action + if tag_click_action not in TAG_CLICK_ACTION_MAP: + tag_click_action = TagClickActionOption.DEFAULT + self.tag_click_action_combobox.setCurrentIndex( + list(TAG_CLICK_ACTION_MAP.keys()).index(tag_click_action) + ) + form_layout.addRow( + Translations["settings.tag_click_action.label"], self.tag_click_action_combobox + ) + # Date Format self.dateformat_combobox = QComboBox() for k in DATE_FORMAT_MAP: @@ -206,6 +237,7 @@ def get_settings(self) -> dict: "page_size": int(self.page_size_line_edit.text()), "show_filepath": self.filepath_combobox.currentData(), "theme": self.theme_combobox.currentData(), + "tag_click_action": self.tag_click_action_combobox.currentData(), "date_format": self.dateformat_combobox.currentData(), "hour_format": self.hourformat_checkbox.isChecked(), "zero_padding": self.zeropadding_checkbox.isChecked(), @@ -221,6 +253,7 @@ def update_settings(self, driver: "QtDriver"): driver.settings.page_size = settings["page_size"] driver.settings.show_filepath = settings["show_filepath"] driver.settings.theme = settings["theme"] + driver.settings.tag_click_action = settings["tag_click_action"] 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/widgets/tag.py b/src/tagstudio/qt/widgets/tag.py index ca124070f..31e686890 100644 --- a/src/tagstudio/qt/widgets/tag.py +++ b/src/tagstudio/qt/widgets/tag.py @@ -98,15 +98,17 @@ class TagWidget(QWidget): on_click = Signal() on_edit = Signal() + tag: Tag | None + def __init__( self, tag: Tag | None, has_edit: bool, has_remove: bool, library: "Library | None" = None, - on_remove_callback: FunctionType = None, - on_click_callback: FunctionType = None, - on_edit_callback: FunctionType = None, + on_remove_callback: FunctionType | None = None, + on_click_callback: FunctionType | None = None, + on_edit_callback: FunctionType | None = None, ) -> None: super().__init__() self.tag = tag @@ -123,10 +125,18 @@ def __init__( self.bg_button = QPushButton(self) self.bg_button.setFlat(True) + # add callbacks + if on_remove_callback is not None: + self.on_remove.connect(on_remove_callback) + if on_click_callback is not None: + self.on_click.connect(on_click_callback) + if on_edit_callback is not None: + self.on_edit.connect(on_edit_callback) + + # add edit action if has_edit: edit_action = QAction(self) edit_action.setText(Translations["generic.edit"]) - edit_action.triggered.connect(on_edit_callback) edit_action.triggered.connect(self.on_edit.emit) self.bg_button.addAction(edit_action) # if on_click_callback: diff --git a/src/tagstudio/qt/widgets/tag_box.py b/src/tagstudio/qt/widgets/tag_box.py index dbeea052b..70ea24bd3 100644 --- a/src/tagstudio/qt/widgets/tag_box.py +++ b/src/tagstudio/qt/widgets/tag_box.py @@ -8,6 +8,7 @@ import structlog from PySide6.QtCore import Signal +from tagstudio.core.enums import TagClickActionOption from tagstudio.core.library.alchemy.enums import BrowsingState from tagstudio.core.library.alchemy.models import Tag from tagstudio.qt.flowlayout import FlowLayout @@ -26,6 +27,8 @@ class TagBoxWidget(FieldWidget): updated = Signal() error_occurred = Signal(Exception) + driver: "QtDriver" + def __init__( self, tags: set[Tag], @@ -50,11 +53,11 @@ def set_tags(self, tags: typing.Iterable[Tag]): tags_ = sorted(list(tags), key=lambda tag: self.driver.lib.tag_display_name(tag.id)) logger.info("[TagBoxWidget] Tags:", tags=tags) while self.base_layout.itemAt(0): - self.base_layout.takeAt(0).widget().deleteLater() + self.base_layout.takeAt(0).widget().deleteLater() # pyright: ignore[reportOptionalMemberAccess] for tag in tags_: tag_widget = TagWidget(tag, library=self.driver.lib, has_edit=True, has_remove=True) - tag_widget.on_click.connect(lambda t=tag: self.edit_tag(t)) + tag_widget.on_click.connect(lambda t=tag: self.__on_tag_clicked(t)) tag_widget.on_remove.connect( lambda tag_id=tag.id: ( @@ -73,6 +76,26 @@ def set_tags(self, tags: typing.Iterable[Tag]): self.base_layout.addWidget(tag_widget) + def __on_tag_clicked(self, tag: Tag): + match self.driver.settings.tag_click_action: + case TagClickActionOption.OPEN_EDIT: + self.edit_tag(tag) + case TagClickActionOption.SET_SEARCH: + self.driver.update_browsing_state(BrowsingState.from_tag_id(tag.id)) + case TagClickActionOption.ADD_TO_SEARCH: + # NOTE: modifying the ast and then setting that would be nicer + # than this string manipulation, but also much more complex, + # due to needing to implement a visitor that turns an AST to a string + # So if that exists when you read this, change the following accordingly. + current = self.driver.browsing_history.current + suffix = BrowsingState.from_tag_id(tag.id).query + assert suffix is not None + self.driver.update_browsing_state( + current.with_search_query( + f"{current.query} {suffix}" if current.query else suffix + ) + ) + def edit_tag(self, tag: Tag): assert isinstance(tag, Tag), f"tag is {type(tag)}" build_tag_panel = BuildTagPanel(self.driver.lib, tag=tag) diff --git a/src/tagstudio/resources/translations/de.json b/src/tagstudio/resources/translations/de.json index 4727ebade..e48bf9d74 100644 --- a/src/tagstudio/resources/translations/de.json +++ b/src/tagstudio/resources/translations/de.json @@ -243,6 +243,10 @@ "settings.restart_required": "Bitte TagStudio neustarten, um Änderungen anzuwenden.", "settings.show_filenames_in_grid": "Dateinamen in Raster darstellen", "settings.show_recent_libraries": "Zuletzt verwendete Bibliotheken anzeigen", + "settings.tag_click_action.label": "Tag Klick Aktion", + "settings.tag_click_action.add_to_search": "Tag zu Suche hinzufügen", + "settings.tag_click_action.open_edit": "Tag bearbeiten", + "settings.tag_click_action.set_search": "Nach Tag suchen", "settings.theme.dark": "Dunkel", "settings.theme.label": "Design:", "settings.theme.light": "Hell", diff --git a/src/tagstudio/resources/translations/en.json b/src/tagstudio/resources/translations/en.json index 281f69c70..a5bc23c9a 100644 --- a/src/tagstudio/resources/translations/en.json +++ b/src/tagstudio/resources/translations/en.json @@ -243,6 +243,10 @@ "settings.restart_required": "Please restart TagStudio for changes to take effect.", "settings.show_filenames_in_grid": "Show Filenames in Grid", "settings.show_recent_libraries": "Show Recent Libraries", + "settings.tag_click_action.label": "Tag Click Action", + "settings.tag_click_action.add_to_search": "Add Tag to Search", + "settings.tag_click_action.open_edit": "Edit Tag", + "settings.tag_click_action.set_search": "Search for Tag", "settings.theme.dark": "Dark", "settings.theme.label": "Theme:", "settings.theme.light": "Light",