diff --git a/tagstudio/resources/translations/en.json b/tagstudio/resources/translations/en.json index 63771f8d9..cf3a4105f 100644 --- a/tagstudio/resources/translations/en.json +++ b/tagstudio/resources/translations/en.json @@ -183,6 +183,7 @@ "menu.view": "&View", "menu.window": "Window", "preview.no_selection": "No Items Selected", + "select.add_tag_to_selected": "Add Tag to Selected", "select.all": "Select All", "select.clear": "Clear Selection", "settings.clear_thumb_cache.title": "Clear Thumbnail Cache", diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index d878a7b71..9d3f320c8 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -81,6 +81,7 @@ from src.qt.modals.fix_unlinked import FixUnlinkedEntriesModal from src.qt.modals.folders_to_tags import FoldersToTagsModal from src.qt.modals.tag_database import TagDatabasePanel +from src.qt.modals.tag_search import TagSearchPanel from src.qt.resource_manager import ResourceManager from src.qt.splash import Splash from src.qt.translations import Translations @@ -132,6 +133,9 @@ class QtDriver(DriverMixin, QObject): SIGTERM = Signal() preview_panel: PreviewPanel + tag_search_panel: TagSearchPanel + add_tag_modal: PanelModal + lib: Library def __init__(self, backend, args): @@ -199,6 +203,8 @@ def __init__(self, backend, args): f"[Config] Thumbnail cache size limit: {format_size(CacheManager.size_limit)}", ) + self.add_tag_to_selected_action: QAction | None = None + def init_workers(self): """Init workers for rendering thumbnails.""" if not self.thumb_threads: @@ -270,6 +276,18 @@ def start(self) -> None: icon.addFile(str(icon_path)) app.setWindowIcon(icon) + # Initialize the main window's tag search panel + self.tag_search_panel = TagSearchPanel(self.lib, is_tag_chooser=True) + self.add_tag_modal = PanelModal( + self.tag_search_panel, Translations.translate_formatted("tag.add.plural") + ) + self.tag_search_panel.tag_chosen.connect( + lambda t: ( + self.add_tags_to_selected_callback(t), + self.preview_panel.update_widgets(), + ) + ) + menu_bar = QMenuBar(self.main_window) self.main_window.setMenuBar(menu_bar) menu_bar.setNativeMenuBar(True) @@ -394,6 +412,24 @@ def start(self) -> None: clear_select_action.setToolTip("Esc") edit_menu.addAction(clear_select_action) + self.add_tag_to_selected_action = QAction(menu_bar) + Translations.translate_qobject( + self.add_tag_to_selected_action, "select.add_tag_to_selected" + ) + self.add_tag_to_selected_action.triggered.connect(self.add_tag_modal.show) + self.add_tag_to_selected_action.setShortcut( + QtCore.QKeyCombination( + QtCore.Qt.KeyboardModifier( + QtCore.Qt.KeyboardModifier.ControlModifier + ^ QtCore.Qt.KeyboardModifier.ShiftModifier + ), + QtCore.Qt.Key.Key_T, + ) + ) + self.add_tag_to_selected_action.setToolTip("Ctrl+Shift+T") + self.add_tag_to_selected_action.setEnabled(False) + edit_menu.addAction(self.add_tag_to_selected_action) + edit_menu.addSeparator() manage_file_extensions_action = QAction(menu_bar) @@ -551,6 +587,7 @@ def create_about_modal(): self.open_library(path_result.library_path) # check ffmpeg and show warning if not + # NOTE: Does this need to use self? self.ffmpeg_checker = FfmpegChecker() if not self.ffmpeg_checker.installed(): self.ffmpeg_checker.show_warning() @@ -705,9 +742,12 @@ def close_library(self, is_shutdown: bool = False): self.preview_panel.update_widgets() self.main_window.toggle_landing_page(enabled=True) - self.main_window.pagination.setHidden(True) + # NOTE: Doesn't try to disable during tests + if self.add_tag_to_selected_action: + self.add_tag_to_selected_action.setEnabled(False) + end_time = time.time() self.main_window.statusbar.showMessage( Translations.translate_formatted( @@ -760,16 +800,22 @@ def select_all_action_callback(self): item.thumb_button.set_selected(True) self.set_macro_menu_viability() + self.set_add_to_selected_visibility() self.preview_panel.update_widgets(update_preview=False) def clear_select_action_callback(self): self.selected.clear() + self.set_add_to_selected_visibility() for item in self.item_thumbs: item.thumb_button.set_selected(False) self.set_macro_menu_viability() self.preview_panel.update_widgets() + def add_tags_to_selected_callback(self, tag_ids: list[int]): + for entry_id in self.selected: + self.lib.add_tags_to_entry(entry_id, tag_ids) + def show_tag_database(self): self.modal = PanelModal( widget=TagDatabasePanel(self.lib), @@ -1110,11 +1156,21 @@ def toggle_item_selection(self, item_id: int, append: bool, bridge: bool): it.thumb_button.set_selected(False) self.set_macro_menu_viability() + self.set_add_to_selected_visibility() self.preview_panel.update_widgets() def set_macro_menu_viability(self): self.autofill_action.setDisabled(not self.selected) + def set_add_to_selected_visibility(self): + if not self.add_tag_to_selected_action: + return + + if self.selected: + self.add_tag_to_selected_action.setEnabled(True) + else: + self.add_tag_to_selected_action.setEnabled(False) + def update_completions_list(self, text: str) -> None: matches = re.search( r"((?:.* )?)(mediatype|filetype|path|tag|tag_id):(\"?[A-Za-z0-9\ \t]+\"?)?", text @@ -1478,6 +1534,7 @@ def init_library(self, path: Path, open_status: LibraryStatus): self.main_window.setAcceptDrops(True) self.selected.clear() + self.set_add_to_selected_visibility() self.preview_panel.update_widgets() # page (re)rendering, extract eventually diff --git a/tagstudio/src/qt/widgets/tag_box.py b/tagstudio/src/qt/widgets/tag_box.py index bdb39c358..473233003 100644 --- a/tagstudio/src/qt/widgets/tag_box.py +++ b/tagstudio/src/qt/widgets/tag_box.py @@ -8,7 +8,6 @@ import structlog from PySide6.QtCore import Signal from src.core.library import Tag -from src.core.library.alchemy.enums import FilterState from src.qt.flowlayout import FlowLayout from src.qt.modals.build_tag import BuildTagPanel from src.qt.widgets.fields import FieldWidget @@ -53,12 +52,7 @@ def set_tags(self, tags: typing.Iterable[Tag]): for tag in tags_: tag_widget = TagWidget(tag, library=self.driver.lib, has_edit=True, has_remove=True) - tag_widget.on_click.connect( - lambda tag_id=tag.id: ( - self.driver.main_window.searchField.setText(f"tag_id:{tag_id}"), - self.driver.filter_items(FilterState.from_tag_id(tag_id)), - ) - ) + tag_widget.on_click.connect(lambda t=tag: self.edit_tag(t)) tag_widget.on_remove.connect( lambda tag_id=tag.id: (