feat(ui): option to change tag primary click behavior (#945)

* feat: add settings field

* feat: change click behaviour based on settings value

* fix: make ignore comment pyright specific to shut up mypy

* fix: add german and english translations for new strings

* fix: settings dropdowns were always english not matter the selected language
This commit is contained in:
Jann Stute
2025-06-06 21:45:30 +02:00
committed by GitHub
parent 3999d5d39b
commit c100babd9f
8 changed files with 106 additions and 19 deletions

View File

@@ -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"

View File

@@ -11,7 +11,7 @@ import structlog
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)

View File

@@ -125,6 +125,9 @@ class 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"

View File

@@ -17,7 +17,7 @@ from PySide6.QtWidgets import (
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 @@ from tagstudio.qt.widgets.panel import PanelModal, PanelWidget
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 @@ class SettingsPanel(PanelWidget):
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 @@ class SettingsPanel(PanelWidget):
"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 @@ class SettingsPanel(PanelWidget):
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"]

View File

@@ -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 @@ class TagWidget(QWidget):
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:

View File

@@ -8,6 +8,7 @@ import typing
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 @@ class TagBoxWidget(FieldWidget):
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 @@ class TagBoxWidget(FieldWidget):
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)

View File

@@ -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",

View File

@@ -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",