diff --git a/pyproject.toml b/pyproject.toml index 22503b28..c173364f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,7 @@ reportUnknownMemberType = false reportUnusedCallResult = false [tool.ruff] -exclude = ["main_window.py", "home_ui.py", "resources.py", "resources_rc.py"] +exclude = ["home_ui.py", "resources.py", "resources_rc.py"] line-length = 100 [tool.ruff.lint] diff --git a/src/tagstudio/qt/main_window.py b/src/tagstudio/qt/main_window.py index 485a7a1c..88fdfb4f 100644 --- a/src/tagstudio/qt/main_window.py +++ b/src/tagstudio/qt/main_window.py @@ -3,10 +3,13 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import logging import typing +from pathlib import Path -from PySide6.QtCore import QMetaObject, QRect, QSize, QStringListModel, Qt +import structlog +from PySide6 import QtCore +from PySide6.QtCore import QMetaObject, QSize, QStringListModel, Qt +from PySide6.QtGui import QAction from PySide6.QtWidgets import ( QComboBox, QCompleter, @@ -16,6 +19,8 @@ from PySide6.QtWidgets import ( QLayout, QLineEdit, QMainWindow, + QMenu, + QMenuBar, QPushButton, QScrollArea, QSizePolicy, @@ -26,23 +31,388 @@ from PySide6.QtWidgets import ( QWidget, ) +from tagstudio.core.enums import ShowFilepathOption +from tagstudio.core.library.alchemy.enums import SortingModeEnum +from tagstudio.qt.flowlayout import FlowLayout from tagstudio.qt.pagination import Pagination +from tagstudio.qt.platform_strings import trash_term from tagstudio.qt.translations import Translations from tagstudio.qt.widgets.landing import LandingWidget +from tagstudio.qt.widgets.preview_panel import PreviewPanel # Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from tagstudio.qt.ts_qt import QtDriver -logging.basicConfig(format="%(message)s", level=logging.INFO) + +logger = structlog.get_logger(__name__) -class Ui_MainWindow(QMainWindow): - +class MainMenuBar(QMenuBar): + file_menu: QMenu + open_library_action: QAction + open_recent_library_menu: QMenu + save_library_backup_action: QAction + settings_action: QAction + open_on_start_action: QAction + refresh_dir_action: QAction + close_library_action: QAction + + edit_menu: QMenu + new_tag_action: QAction + select_all_action: QAction + select_inverse_action: QAction + clear_select_action: QAction + copy_fields_action: QAction + paste_fields_action: QAction + add_tag_to_selected_action: QAction + delete_file_action: QAction + manage_file_ext_action: QAction + tag_manager_action: QAction + color_manager_action: QAction + + view_menu: QMenu + show_filenames_action: QAction + + tools_menu: QMenu + fix_unlinked_entries_action: QAction + fix_dupe_files_action: QAction + clear_thumb_cache_action: QAction + + macros_menu: QMenu + folders_to_tags_action: QAction + + help_menu: QMenu + about_action: QAction + + def __init__(self, parent=...): + super().__init__(parent) + + self.setup_file_menu() + self.setup_edit_menu() + self.setup_view_menu() + self.setup_tools_menu() + self.setup_macros_menu() + self.setup_help_menu() + + def setup_file_menu(self): + self.file_menu = QMenu(Translations["menu.file"], self) + + # Open/Create Library + self.open_library_action = QAction(Translations["menu.file.open_create_library"], self) + self.open_library_action.setShortcut( + QtCore.QKeyCombination( + QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), + QtCore.Qt.Key.Key_O, + ) + ) + self.open_library_action.setToolTip("Ctrl+O") + self.file_menu.addAction(self.open_library_action) + + # Open Recent + self.open_recent_library_menu = QMenu(Translations["menu.file.open_recent_library"], self) + self.file_menu.addMenu(self.open_recent_library_menu) + + # Save Library Backup + self.save_library_backup_action = QAction(Translations["menu.file.save_backup"], self) + self.save_library_backup_action.setShortcut( + QtCore.QKeyCombination( + QtCore.Qt.KeyboardModifier( + QtCore.Qt.KeyboardModifier.ControlModifier + | QtCore.Qt.KeyboardModifier.ShiftModifier + ), + QtCore.Qt.Key.Key_S, + ) + ) + self.save_library_backup_action.setStatusTip("Ctrl+Shift+S") + self.save_library_backup_action.setEnabled(False) + self.file_menu.addAction(self.save_library_backup_action) + + self.file_menu.addSeparator() + + # Settings... + self.settings_action = QAction(Translations["menu.settings"], self) + self.file_menu.addAction(self.settings_action) + + # Open Library on Start + self.open_on_start_action = QAction(Translations["settings.open_library_on_start"], self) + self.open_on_start_action.setCheckable(True) + self.file_menu.addAction(self.open_on_start_action) + + self.file_menu.addSeparator() + + # Refresh Directories + self.refresh_dir_action = QAction(Translations["menu.file.refresh_directories"], self) + self.refresh_dir_action.setShortcut( + QtCore.QKeyCombination( + QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), + QtCore.Qt.Key.Key_R, + ) + ) + self.refresh_dir_action.setStatusTip("Ctrl+R") + self.refresh_dir_action.setEnabled(False) + self.file_menu.addAction(self.refresh_dir_action) + + self.file_menu.addSeparator() + + # Close Library + self.close_library_action = QAction(Translations["menu.file.close_library"], self) + self.close_library_action.setEnabled(False) + self.file_menu.addAction(self.close_library_action) + + self.file_menu.addSeparator() + + self.addMenu(self.file_menu) + + def setup_edit_menu(self): + self.edit_menu = QMenu(Translations["generic.edit_alt"], self) + + # New Tag + self.new_tag_action = QAction(Translations["menu.edit.new_tag"], self) + self.new_tag_action.setShortcut( + QtCore.QKeyCombination( + QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), + QtCore.Qt.Key.Key_T, + ) + ) + self.new_tag_action.setToolTip("Ctrl+T") + self.new_tag_action.setEnabled(False) + self.edit_menu.addAction(self.new_tag_action) + + self.edit_menu.addSeparator() + + # Select All + self.select_all_action = QAction(Translations["select.all"], self) + self.select_all_action.setShortcut( + QtCore.QKeyCombination( + QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), + QtCore.Qt.Key.Key_A, + ) + ) + self.select_all_action.setToolTip("Ctrl+A") + self.select_all_action.setEnabled(False) + self.edit_menu.addAction(self.select_all_action) + + # Invert Selection + self.select_inverse_action = QAction(Translations["select.inverse"], self) + self.select_inverse_action.setShortcut( + QtCore.QKeyCombination( + QtCore.Qt.KeyboardModifier( + QtCore.Qt.KeyboardModifier.ControlModifier + ^ QtCore.Qt.KeyboardModifier.ShiftModifier + ), + QtCore.Qt.Key.Key_I, + ) + ) + self.select_inverse_action.setToolTip("Ctrl+Shift+I") + self.select_inverse_action.setEnabled(False) + self.edit_menu.addAction(self.select_inverse_action) + + # Clear Selection + self.clear_select_action = QAction(Translations["select.clear"], self) + self.clear_select_action.setShortcut(QtCore.Qt.Key.Key_Escape) + self.clear_select_action.setToolTip("Esc") + self.clear_select_action.setEnabled(False) + self.edit_menu.addAction(self.clear_select_action) + + # Copy Fields + self.copy_fields_action = QAction(Translations["edit.copy_fields"], self) + self.copy_fields_action.setShortcut( + QtCore.QKeyCombination( + QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), + QtCore.Qt.Key.Key_C, + ) + ) + self.copy_fields_action.setToolTip("Ctrl+C") + self.copy_fields_action.setEnabled(False) + self.edit_menu.addAction(self.copy_fields_action) + + # Paste Fields + self.paste_fields_action = QAction(Translations["edit.paste_fields"], self) + self.paste_fields_action.setShortcut( + QtCore.QKeyCombination( + QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), + QtCore.Qt.Key.Key_V, + ) + ) + self.paste_fields_action.setToolTip("Ctrl+V") + self.paste_fields_action.setEnabled(False) + self.edit_menu.addAction(self.paste_fields_action) + + # Add Tag to Selected + self.add_tag_to_selected_action = QAction(Translations["select.add_tag_to_selected"], self) + 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) + self.edit_menu.addAction(self.add_tag_to_selected_action) + + self.edit_menu.addSeparator() + + # Move Files to trash + self.delete_file_action = QAction( + Translations.format("menu.delete_selected_files_ambiguous", trash_term=trash_term()), + self, + ) + self.delete_file_action.setShortcut(QtCore.Qt.Key.Key_Delete) + self.delete_file_action.setEnabled(False) + self.edit_menu.addAction(self.delete_file_action) + + self.edit_menu.addSeparator() + + # Manage File Extensions + self.manage_file_ext_action = QAction( + Translations["menu.edit.manage_file_extensions"], self + ) + self.manage_file_ext_action.setEnabled(False) + self.edit_menu.addAction(self.manage_file_ext_action) + + # Manage Tags + self.tag_manager_action = QAction(Translations["menu.edit.manage_tags"], self) + self.tag_manager_action.setShortcut( + QtCore.QKeyCombination( + QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), + QtCore.Qt.Key.Key_M, + ) + ) + self.tag_manager_action.setEnabled(False) + self.tag_manager_action.setToolTip("Ctrl+M") + self.edit_menu.addAction(self.tag_manager_action) + + # Color Manager + self.color_manager_action = QAction(Translations["edit.color_manager"], self) + self.color_manager_action.setEnabled(False) + self.edit_menu.addAction(self.color_manager_action) + + self.addMenu(self.edit_menu) + + def setup_view_menu(self): + self.view_menu = QMenu(Translations["menu.view"], self) + + # show_libs_list_action = QAction(Translations["settings.show_recent_libraries"], menu_bar) + # show_libs_list_action.setCheckable(True) + # show_libs_list_action.setChecked(self.settings.show_library_list) + + self.show_filenames_action = QAction(Translations["settings.show_filenames_in_grid"], self) + self.show_filenames_action.setCheckable(True) + self.view_menu.addAction(self.show_filenames_action) + + self.addMenu(self.view_menu) + + def setup_tools_menu(self): + self.tools_menu = QMenu(Translations["menu.tools"], self) + + # Fix Unlinked Entries + self.fix_unlinked_entries_action = QAction( + Translations["menu.tools.fix_unlinked_entries"], self + ) + self.fix_unlinked_entries_action.setEnabled(False) + self.tools_menu.addAction(self.fix_unlinked_entries_action) + + # Fix Duplicate Files + self.fix_dupe_files_action = QAction(Translations["menu.tools.fix_duplicate_files"], self) + self.fix_dupe_files_action.setEnabled(False) + self.tools_menu.addAction(self.fix_dupe_files_action) + + self.tools_menu.addSeparator() + + # Clear Thumbnail Cache + self.clear_thumb_cache_action = QAction( + Translations["settings.clear_thumb_cache.title"], self + ) + self.clear_thumb_cache_action.setEnabled(False) + self.tools_menu.addAction(self.clear_thumb_cache_action) + + self.addMenu(self.tools_menu) + + def setup_macros_menu(self): + self.macros_menu = QMenu(Translations["menu.macros"], self) + + self.folders_to_tags_action = QAction(Translations["menu.macros.folders_to_tags"], self) + self.folders_to_tags_action.setEnabled(False) + self.macros_menu.addAction(self.folders_to_tags_action) + + self.addMenu(self.macros_menu) + + def setup_help_menu(self): + self.help_menu = QMenu(Translations["menu.help"], self) + + self.about_action = QAction(Translations["menu.help.about"], self) + self.help_menu.addAction(self.about_action) + + self.addMenu(self.help_menu) + + def rebuild_open_recent_library_menu( + self, + libraries: list[Path], + show_filepath: ShowFilepathOption, + open_library_callback, + clear_libraries_callback, + ): + actions: list[QAction] = [] + for path in libraries: + action = QAction(self.open_recent_library_menu) + if show_filepath == ShowFilepathOption.SHOW_FULL_PATHS: + action.setText(str(path)) + else: + action.setText(str(path.name)) + action.triggered.connect(lambda checked=False, p=path: open_library_callback(p)) + actions.append(action) + + clear_recent_action = QAction( + Translations["menu.file.clear_recent_libraries"], self.open_recent_library_menu + ) + clear_recent_action.triggered.connect(clear_libraries_callback) + actions.append(clear_recent_action) + + # Clear previous actions + for action in self.open_recent_library_menu.actions(): + self.open_recent_library_menu.removeAction(action) + + # Add new actions + for action in actions: + self.open_recent_library_menu.addAction(action) + + # Only enable add "clear recent" if there are still recent libraries. + if len(actions) > 1: + self.open_recent_library_menu.setDisabled(False) + self.open_recent_library_menu.addSeparator() + self.open_recent_library_menu.addAction(clear_recent_action) + else: + self.open_recent_library_menu.setDisabled(True) + + +# View Component +class MainWindow(QMainWindow): + THUMB_SIZES: list[tuple[str, int]] = [ + (Translations["home.thumbnail_size.extra_large"], 256), + (Translations["home.thumbnail_size.large"], 192), + (Translations["home.thumbnail_size.medium"], 128), + (Translations["home.thumbnail_size.small"], 96), + (Translations["home.thumbnail_size.mini"], 76), + ] + def __init__(self, driver: "QtDriver", parent=None) -> None: super().__init__(parent) - self.driver: "QtDriver" = driver - self.setupUi(self) + + if not self.objectName(): + self.setObjectName("MainWindow") + self.resize(1300, 720) + + self.setup_menu_bar() + + self.setup_central_widget(driver) + + self.setup_status_bar() + + QMetaObject.connectSlotsByName(self) # NOTE: These are old attempts to allow for a translucent/acrylic # window effect. This may be attempted again in the future. @@ -54,155 +424,215 @@ class Ui_MainWindow(QMainWindow): # self.windowFX = WindowEffect() # self.windowFX.setAcrylicEffect(self.winId(), isEnableShadow=False) + # region UI Setup Methods - def setupUi(self, MainWindow): - if not MainWindow.objectName(): - MainWindow.setObjectName(u"MainWindow") - MainWindow.resize(1300, 720) - - self.centralwidget = QWidget(MainWindow) - self.centralwidget.setObjectName(u"centralwidget") - self.gridLayout = QGridLayout(self.centralwidget) - self.gridLayout.setObjectName(u"gridLayout") - self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.setObjectName(u"horizontalLayout") - - # ComboBox group for search type and thumbnail size - self.horizontalLayout_3 = QHBoxLayout() - self.horizontalLayout_3.setObjectName("horizontalLayout_3") - - # left side spacer - spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) - self.horizontalLayout_3.addItem(spacerItem) - - # Sorting Dropdowns - self.sorting_mode_combobox = QComboBox(self.centralwidget) - self.sorting_mode_combobox.setObjectName(u"sortingModeComboBox") - self.horizontalLayout_3.addWidget(self.sorting_mode_combobox) - - self.sorting_direction_combobox = QComboBox(self.centralwidget) - self.sorting_direction_combobox.setObjectName(u"sortingDirectionCombobox") - self.horizontalLayout_3.addWidget(self.sorting_direction_combobox) + # region Menu Bar - # Thumbnail Size placeholder - self.thumb_size_combobox = QComboBox(self.centralwidget) - self.thumb_size_combobox.setObjectName(u"thumbSizeComboBox") + def setup_menu_bar(self): + self.menu_bar = MainMenuBar(self) + + self.setMenuBar(self.menu_bar) + self.menu_bar.setNativeMenuBar(True) + + # endregion + + def setup_central_widget(self, driver: "QtDriver"): + self.central_widget = QWidget(self) + self.central_widget.setObjectName("central_widget") + self.central_layout = QGridLayout(self.central_widget) + self.central_layout.setObjectName("central_layout") + + self.setup_search_bar() + + self.setup_extra_input_bar() + + self.setup_content(driver) + + self.setCentralWidget(self.central_widget) + + def setup_search_bar(self): + """Sets up Nav Buttons, Search Field, Search Button.""" + nav_button_style = "font-size:14;font-weight:bold;" + self.search_bar_layout = QHBoxLayout() + self.search_bar_layout.setObjectName("search_bar_layout") + self.search_bar_layout.setSizeConstraint(QLayout.SizeConstraint.SetMinimumSize) + + self.back_button = QPushButton("<", self.central_widget) + self.back_button.setObjectName("back_button") + self.back_button.setMinimumSize(QSize(0, 32)) + self.back_button.setMaximumSize(QSize(32, 16777215)) + self.back_button.setStyleSheet(nav_button_style) + self.search_bar_layout.addWidget(self.back_button) + + self.forward_button = QPushButton(">", self.central_widget) + self.forward_button.setObjectName("forward_button") + self.forward_button.setMinimumSize(QSize(0, 32)) + self.forward_button.setMaximumSize(QSize(32, 16777215)) + self.forward_button.setStyleSheet(nav_button_style) + self.search_bar_layout.addWidget(self.forward_button) + + self.search_field = QLineEdit(self.central_widget) + self.search_field.setPlaceholderText(Translations["home.search_entries"]) + self.search_field.setObjectName("search_field") + self.search_field.setMinimumSize(QSize(0, 32)) + self.search_field_completion_list = QStringListModel() + self.search_field_completer = QCompleter( + self.search_field_completion_list, self.search_field + ) + self.search_field_completer.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) + self.search_field.setCompleter(self.search_field_completer) + self.search_bar_layout.addWidget(self.search_field) + + self.search_button = QPushButton(Translations["home.search"], self.central_widget) + self.search_button.setObjectName("search_button") + self.search_button.setMinimumSize(QSize(0, 32)) + self.search_bar_layout.addWidget(self.search_button) + + self.central_layout.addLayout(self.search_bar_layout, 3, 0, 1, 1) + + def setup_extra_input_bar(self): + """Sets up inputs for sorting settings and thumbnail size.""" + self.extra_input_layout = QHBoxLayout() + self.extra_input_layout.setObjectName("extra_input_layout") + + ## left side spacer + self.extra_input_layout.addItem( + QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) + ) + + ## Sorting Mode Dropdown + self.sorting_mode_combobox = QComboBox(self.central_widget) + self.sorting_mode_combobox.setObjectName("sorting_mode_combobox") + for sort_mode in SortingModeEnum: + self.sorting_mode_combobox.addItem(Translations[sort_mode.value], sort_mode) + self.extra_input_layout.addWidget(self.sorting_mode_combobox) + + ## Sorting Direction Dropdown + self.sorting_direction_combobox = QComboBox(self.central_widget) + self.sorting_direction_combobox.setObjectName("sorting_direction_combobox") + self.sorting_direction_combobox.addItem( + Translations["sorting.direction.ascending"], userData=True + ) + self.sorting_direction_combobox.addItem( + Translations["sorting.direction.descending"], userData=False + ) + self.sorting_direction_combobox.setCurrentIndex(1) # Default: Descending + self.extra_input_layout.addWidget(self.sorting_direction_combobox) + + ## Thumbnail Size placeholder + self.thumb_size_combobox = QComboBox(self.central_widget) + self.thumb_size_combobox.setObjectName("thumb_size_combobox") self.thumb_size_combobox.setPlaceholderText(Translations["home.thumbnail_size"]) self.thumb_size_combobox.setCurrentText("") - sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth( - self.thumb_size_combobox.sizePolicy().hasHeightForWidth()) - self.thumb_size_combobox.setSizePolicy(sizePolicy) + size_policy = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed) + size_policy.setHorizontalStretch(0) + size_policy.setVerticalStretch(0) + size_policy.setHeightForWidth(self.thumb_size_combobox.sizePolicy().hasHeightForWidth()) + self.thumb_size_combobox.setSizePolicy(size_policy) self.thumb_size_combobox.setMinimumWidth(128) self.thumb_size_combobox.setMaximumWidth(352) - self.horizontalLayout_3.addWidget(self.thumb_size_combobox) - self.gridLayout.addLayout(self.horizontalLayout_3, 5, 0, 1, 1) + self.extra_input_layout.addWidget(self.thumb_size_combobox) + for size in MainWindow.THUMB_SIZES: + self.thumb_size_combobox.addItem(size[0], size[1]) + self.thumb_size_combobox.setCurrentIndex(2) # Default: Medium - self.splitter = QSplitter() - self.splitter.setObjectName(u"splitter") - self.splitter.setHandleWidth(12) + self.central_layout.addLayout(self.extra_input_layout, 5, 0, 1, 1) - self.frame_container = QWidget() - self.frame_layout = QVBoxLayout(self.frame_container) - self.frame_layout.setSpacing(0) + def setup_content(self, driver: "QtDriver"): + self.content_layout = QHBoxLayout() + self.content_layout.setObjectName("content_layout") - self.scrollArea = QScrollArea() - self.scrollArea.setObjectName(u"scrollArea") - self.scrollArea.setFocusPolicy(Qt.WheelFocus) - self.scrollArea.setFrameShape(QFrame.NoFrame) - self.scrollArea.setFrameShadow(QFrame.Plain) - self.scrollArea.setWidgetResizable(True) - self.scrollAreaWidgetContents = QWidget() - self.scrollAreaWidgetContents.setObjectName( - u"scrollAreaWidgetContents") - self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 1260, 590)) - self.gridLayout_2 = QGridLayout(self.scrollAreaWidgetContents) - self.gridLayout_2.setSpacing(8) - self.gridLayout_2.setObjectName(u"gridLayout_2") - self.gridLayout_2.setContentsMargins(0, 0, 0, 8) - self.scrollArea.setWidget(self.scrollAreaWidgetContents) - self.frame_layout.addWidget(self.scrollArea) - - self.landing_widget: LandingWidget = LandingWidget(self.driver, self.devicePixelRatio()) - self.frame_layout.addWidget(self.landing_widget) + self.content_splitter = QSplitter() + self.content_splitter.setObjectName("content_splitter") + self.content_splitter.setHandleWidth(12) + + self.setup_entry_list(driver) + + self.setup_preview_panel(driver) + + self.content_splitter.setStretchFactor(0, 1) + self.content_layout.addWidget(self.content_splitter) + + self.central_layout.addLayout(self.content_layout, 10, 0, 1, 1) + + def setup_entry_list(self, driver: "QtDriver"): + self.entry_list_container = QWidget() + self.entry_list_layout = QVBoxLayout(self.entry_list_container) + self.entry_list_layout.setSpacing(0) + + self.entry_scroll_area = QScrollArea() + self.entry_scroll_area.setObjectName("entry_scroll_area") + self.entry_scroll_area.setFocusPolicy(Qt.FocusPolicy.WheelFocus) + self.entry_scroll_area.setFrameShape(QFrame.Shape.NoFrame) + self.entry_scroll_area.setFrameShadow(QFrame.Shadow.Plain) + self.entry_scroll_area.setWidgetResizable(True) + self.entry_scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + + self.thumb_grid: QWidget = QWidget() + self.thumb_grid.setObjectName("thumb_grid") + self.thumb_layout = FlowLayout() + self.thumb_layout.enable_grid_optimizations(value=True) + self.thumb_layout.setSpacing(min(self.thumb_size // 10, 12)) + self.thumb_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.thumb_grid.setLayout(self.thumb_layout) + self.entry_scroll_area.setWidget(self.thumb_grid) + + self.entry_list_layout.addWidget(self.entry_scroll_area) + + self.landing_widget: LandingWidget = LandingWidget(driver, self.devicePixelRatio()) + self.entry_list_layout.addWidget(self.landing_widget) self.pagination = Pagination() - self.frame_layout.addWidget(self.pagination) + self.entry_list_layout.addWidget(self.pagination) + self.content_splitter.addWidget(self.entry_list_container) - self.horizontalLayout.addWidget(self.splitter) - self.splitter.addWidget(self.frame_container) - self.splitter.setStretchFactor(0, 1) - self.gridLayout.addLayout(self.horizontalLayout, 10, 0, 1, 1) + def setup_preview_panel(self, driver: "QtDriver"): + self.preview_panel = PreviewPanel(driver.lib, driver) + self.content_splitter.addWidget(self.preview_panel) - nav_button_style = "font-size:14;font-weight:bold;" - self.horizontalLayout_2 = QHBoxLayout() - self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") - self.horizontalLayout_2.setSizeConstraint(QLayout.SetMinimumSize) - self.backButton = QPushButton("<", self.centralwidget) - self.backButton.setObjectName(u"backButton") - self.backButton.setMinimumSize(QSize(0, 32)) - self.backButton.setMaximumSize(QSize(32, 16777215)) - self.backButton.setStyleSheet(nav_button_style) - self.horizontalLayout_2.addWidget(self.backButton) + def setup_status_bar(self): + self.status_bar = QStatusBar(self) + self.status_bar.setObjectName("status_bar") + status_bar_size_policy = QSizePolicy( + QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum + ) + status_bar_size_policy.setHorizontalStretch(0) + status_bar_size_policy.setVerticalStretch(0) + status_bar_size_policy.setHeightForWidth(self.status_bar.sizePolicy().hasHeightForWidth()) + self.status_bar.setSizePolicy(status_bar_size_policy) + self.status_bar.setSizeGripEnabled(False) + self.setStatusBar(self.status_bar) - self.forwardButton = QPushButton(">", self.centralwidget) - self.forwardButton.setObjectName(u"forwardButton") - self.forwardButton.setMinimumSize(QSize(0, 32)) - self.forwardButton.setMaximumSize(QSize(32, 16777215)) - self.forwardButton.setStyleSheet(nav_button_style) - self.horizontalLayout_2.addWidget(self.forwardButton) + # endregion - self.searchField = QLineEdit(self.centralwidget) - self.searchField.setPlaceholderText(Translations["home.search_entries"]) - self.searchField.setObjectName(u"searchField") - self.searchField.setMinimumSize(QSize(0, 32)) - - self.searchFieldCompletionList = QStringListModel() - self.searchFieldCompleter = QCompleter(self.searchFieldCompletionList, self.searchField) - self.searchFieldCompleter.setCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive) - self.searchField.setCompleter(self.searchFieldCompleter) - self.horizontalLayout_2.addWidget(self.searchField) - - self.searchButton = QPushButton(Translations["home.search"], self.centralwidget) - self.searchButton.setObjectName(u"searchButton") - self.searchButton.setMinimumSize(QSize(0, 32)) - - self.horizontalLayout_2.addWidget(self.searchButton) - self.gridLayout.addLayout(self.horizontalLayout_2, 3, 0, 1, 1) - self.gridLayout_2.setContentsMargins(6, 6, 6, 6) - - MainWindow.setCentralWidget(self.centralwidget) - self.statusbar = QStatusBar(MainWindow) - self.statusbar.setObjectName(u"statusbar") - sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) - sizePolicy1.setHorizontalStretch(0) - sizePolicy1.setVerticalStretch(0) - sizePolicy1.setHeightForWidth( - self.statusbar.sizePolicy().hasHeightForWidth()) - self.statusbar.setSizePolicy(sizePolicy1) - self.statusbar.setSizeGripEnabled(False) - MainWindow.setStatusBar(self.statusbar) - - QMetaObject.connectSlotsByName(MainWindow) - # setupUi - - def moveEvent(self, event) -> None: + def moveEvent(self, event) -> None: # noqa: N802 # time.sleep(0.02) # sleep for 20ms pass - def resizeEvent(self, event) -> None: + def resizeEvent(self, event) -> None: # noqa: N802 # time.sleep(0.02) # sleep for 20ms pass def toggle_landing_page(self, enabled: bool): if enabled: - self.scrollArea.setHidden(True) + self.entry_scroll_area.setHidden(True) self.landing_widget.setHidden(False) self.landing_widget.animate_logo_in() else: self.landing_widget.setHidden(True) self.landing_widget.set_status_label("") - self.scrollArea.setHidden(False) - + self.entry_scroll_area.setHidden(False) + + @property + def sorting_mode(self) -> SortingModeEnum: + """What to sort by.""" + return self.sorting_mode_combobox.currentData() + + @property + def sorting_direction(self) -> bool: + """Whether to Sort the results in ascending order.""" + return self.sorting_direction_combobox.currentData() + + @property + def thumb_size(self) -> int: + return self.thumb_size_combobox.currentData() diff --git a/src/tagstudio/qt/modals/folders_to_tags.py b/src/tagstudio/qt/modals/folders_to_tags.py index 1952f335..f9d837c5 100644 --- a/src/tagstudio/qt/modals/folders_to_tags.py +++ b/src/tagstudio/qt/modals/folders_to_tags.py @@ -227,7 +227,7 @@ class FoldersToTagsModal(QWidget): def on_apply(self, event): folders_to_tags(self.library) self.close() - self.driver.preview_panel.update_widgets(update_preview=False) + self.driver.main_window.preview_panel.update_widgets(update_preview=False) def on_open(self, event): for i in reversed(range(self.scroll_layout.count())): diff --git a/src/tagstudio/qt/modals/mirror_entities.py b/src/tagstudio/qt/modals/mirror_entities.py index 198b2aea..327f6705 100644 --- a/src/tagstudio/qt/modals/mirror_entities.py +++ b/src/tagstudio/qt/modals/mirror_entities.py @@ -87,7 +87,7 @@ class MirrorEntriesModal(QWidget): pw.from_iterable_function( self.mirror_entries_runnable, displayed_text, - self.driver.preview_panel.update_widgets, + self.driver.main_window.preview_panel.update_widgets, self.done.emit, ) diff --git a/src/tagstudio/qt/modals/settings_panel.py b/src/tagstudio/qt/modals/settings_panel.py index 820be028..9202813d 100644 --- a/src/tagstudio/qt/modals/settings_panel.py +++ b/src/tagstudio/qt/modals/settings_panel.py @@ -230,7 +230,7 @@ class SettingsPanel(PanelWidget): # Apply changes # Show File Path driver.update_recent_lib_menu() - driver.preview_panel.update_widgets() + driver.main_window.preview_panel.update_widgets() library_directory = driver.lib.library_dir if settings["show_filepath"] == ShowFilepathOption.SHOW_FULL_PATHS: display_path = library_directory or "" diff --git a/src/tagstudio/qt/modals/tag_color_manager.py b/src/tagstudio/qt/modals/tag_color_manager.py index 8a1403dc..8bd034f2 100644 --- a/src/tagstudio/qt/modals/tag_color_manager.py +++ b/src/tagstudio/qt/modals/tag_color_manager.py @@ -124,7 +124,7 @@ class TagColorManager(QWidget): self.setup_color_groups(), () if len(self.driver.selected) < 1 - else self.driver.preview_panel.fields.update_from_entry( + else self.driver.main_window.preview_panel.fields.update_from_entry( self.driver.selected[0], update_badges=False ), ) @@ -141,7 +141,7 @@ class TagColorManager(QWidget): self.setup_color_groups(), () if len(self.driver.selected) < 1 - else self.driver.preview_panel.fields.update_from_entry( + else self.driver.main_window.preview_panel.fields.update_from_entry( self.driver.selected[0], update_badges=False ), ), diff --git a/src/tagstudio/qt/modals/tag_search.py b/src/tagstudio/qt/modals/tag_search.py index 6b2ed4d7..fbb45523 100644 --- a/src/tagstudio/qt/modals/tag_search.py +++ b/src/tagstudio/qt/modals/tag_search.py @@ -28,7 +28,6 @@ from tagstudio.core.library.alchemy.enums import BrowsingState, TagColorEnum from tagstudio.core.library.alchemy.library import Library from tagstudio.core.library.alchemy.models import Tag from tagstudio.core.palette import ColorType, get_tag_color -from tagstudio.qt.modals import build_tag from tagstudio.qt.translations import Translations from tagstudio.qt.widgets.panel import PanelModal, PanelWidget from tagstudio.qt.widgets.tag import TagWidget @@ -37,7 +36,6 @@ logger = structlog.get_logger(__name__) # Only import for type checking/autocompletion, will not be imported at runtime. if TYPE_CHECKING: - from tagstudio.qt.modals.build_tag import BuildTagPanel from tagstudio.qt.ts_qt import QtDriver @@ -177,7 +175,9 @@ class TagSearchPanel(PanelWidget): self.search_field.setFocus() self.update_tags() - self.build_tag_modal: BuildTagPanel = build_tag.BuildTagPanel(self.lib) + from tagstudio.qt.modals.build_tag import BuildTagPanel # here due to circular imports + + self.build_tag_modal: BuildTagPanel = BuildTagPanel(self.lib) self.add_tag_modal: PanelModal = PanelModal(self.build_tag_modal, has_save=True) self.add_tag_modal.setTitle(Translations["tag.new"]) self.add_tag_modal.setWindowTitle(Translations["tag.add"]) @@ -291,7 +291,7 @@ class TagSearchPanel(PanelWidget): if self.driver: tag_widget.search_for_tag_action.triggered.connect( lambda checked=False, tag_id=tag.id: ( - self.driver.main_window.searchField.setText(f"tag_id:{tag_id}"), + self.driver.main_window.search_field.setText(f"tag_id:{tag_id}"), self.driver.update_browsing_state(BrowsingState.from_tag_id(tag_id)), ) ) @@ -352,13 +352,15 @@ class TagSearchPanel(PanelWidget): pass def edit_tag(self, tag: Tag): - def callback(btp: build_tag.BuildTagPanel): + from tagstudio.qt.modals.build_tag import BuildTagPanel + + def callback(btp: BuildTagPanel): self.lib.update_tag( btp.build_tag(), set(btp.parent_ids), set(btp.alias_names), set(btp.alias_ids) ) self.update_tags(self.search_field.text()) - build_tag_panel = build_tag.BuildTagPanel(self.lib, tag=tag) + build_tag_panel = BuildTagPanel(self.lib, tag=tag) self.edit_modal = PanelModal( build_tag_panel, diff --git a/src/tagstudio/qt/ts_qt.py b/src/tagstudio/qt/ts_qt.py index 4ed07a33..d25789d3 100644 --- a/src/tagstudio/qt/ts_qt.py +++ b/src/tagstudio/qt/ts_qt.py @@ -25,10 +25,8 @@ from warnings import catch_warnings import structlog from humanfriendly import format_size, format_timespan -from PySide6 import QtCore from PySide6.QtCore import QObject, QSettings, Qt, QThread, QThreadPool, QTimer, Signal from PySide6.QtGui import ( - QAction, QColor, QDragEnterEvent, QDragMoveEvent, @@ -42,15 +40,10 @@ from PySide6.QtGui import ( from PySide6.QtUiTools import QUiLoader from PySide6.QtWidgets import ( QApplication, - QComboBox, QFileDialog, - QLineEdit, - QMenu, - QMenuBar, QMessageBox, QPushButton, QScrollArea, - QWidget, ) # this import has side-effect of import PySide resources @@ -75,12 +68,11 @@ from tagstudio.core.ts_core import TagStudioCore from tagstudio.core.utils.refresh_dir import RefreshDirTracker from tagstudio.core.utils.web import strip_web_protocol from tagstudio.qt.cache_manager import CacheManager -from tagstudio.qt.flowlayout import FlowLayout from tagstudio.qt.helpers.custom_runnable import CustomRunnable from tagstudio.qt.helpers.file_deleter import delete_file from tagstudio.qt.helpers.function_iterator import FunctionIterator from tagstudio.qt.helpers.vendored.ffmpeg import FFMPEG_CMD, FFPROBE_CMD -from tagstudio.qt.main_window import Ui_MainWindow +from tagstudio.qt.main_window import MainWindow from tagstudio.qt.modals.about import AboutModal from tagstudio.qt.modals.build_tag import BuildTagPanel from tagstudio.qt.modals.drop_import import DropImportModal @@ -100,7 +92,6 @@ from tagstudio.qt.translations import Translations from tagstudio.qt.widgets.item_thumb import BadgeType, ItemThumb from tagstudio.qt.widgets.migration_modal import JsonMigrationModal from tagstudio.qt.widgets.panel import PanelModal -from tagstudio.qt.widgets.preview_panel import PreviewPanel from tagstudio.qt.widgets.progress import ProgressWidget from tagstudio.qt.widgets.thumb_renderer import ThumbRenderer @@ -180,7 +171,6 @@ class QtDriver(DriverMixin, QObject): SIGTERM = Signal() - preview_panel: PreviewPanel tag_manager_panel: PanelModal | None = None color_manager_panel: TagColorManager | None = None file_extension_panel: PanelModal | None = None @@ -208,7 +198,6 @@ class QtDriver(DriverMixin, QObject): self.pages_count = 0 self.scrollbar_pos = 0 - self.thumb_size = 128 self.spacing = None self.branch: str = (" (" + VERSION_BRANCH + ")") if VERSION_BRANCH else "" @@ -275,8 +264,6 @@ class QtDriver(DriverMixin, QObject): f"[Config] Thumbnail cache size limit: {format_size(CacheManager.size_limit)}", ) - self.add_tag_to_selected_action: QAction | None = None - def __reset_navigation(self) -> None: self.browsing_history = History(BrowsingState.show_all()) @@ -347,7 +334,7 @@ class QtDriver(DriverMixin, QObject): timer.timeout.connect(lambda: None) # self.main_window = loader.load(home_path) - self.main_window = Ui_MainWindow(self) + self.main_window = MainWindow(self) self.main_window.setWindowTitle(self.base_title) self.main_window.mousePressEvent = self.mouse_navigation self.main_window.dragEnterEvent = self.drag_enter_event @@ -379,7 +366,9 @@ class QtDriver(DriverMixin, QObject): # Initialize the Tag Manager panel self.tag_manager_panel = PanelModal( widget=TagDatabasePanel(self, self.lib), - done_callback=lambda: self.preview_panel.update_widgets(update_preview=False), + done_callback=lambda: self.main_window.preview_panel.update_widgets( + update_preview=False + ), has_save=False, ) self.tag_manager_panel.setTitle(Translations["tag_manager.title"]) @@ -399,347 +388,174 @@ class QtDriver(DriverMixin, QObject): self.tag_search_panel.tag_chosen.connect( lambda t: ( self.add_tags_to_selected_callback(t), - self.preview_panel.update_widgets(), + self.main_window.preview_panel.update_widgets(), ) ) - menu_bar = QMenuBar(self.main_window) - self.main_window.setMenuBar(menu_bar) - menu_bar.setNativeMenuBar(True) + # region Menu Bar - file_menu = QMenu(Translations["menu.file"], menu_bar) - edit_menu = QMenu(Translations["generic.edit_alt"], menu_bar) - view_menu = QMenu(Translations["menu.view"], menu_bar) - tools_menu = QMenu(Translations["menu.tools"], menu_bar) - macros_menu = QMenu(Translations["menu.macros"], menu_bar) - help_menu = QMenu(Translations["menu.help"], menu_bar) - - # File Menu ============================================================ - open_library_action = QAction(Translations["menu.file.open_create_library"], menu_bar) - open_library_action.triggered.connect(lambda: self.open_library_from_dialog()) - open_library_action.setShortcut( - QtCore.QKeyCombination( - QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), - QtCore.Qt.Key.Key_O, - ) + # region File Menu ============================================================ + # Open/Create Library + self.main_window.menu_bar.open_library_action.triggered.connect( + self.open_library_from_dialog ) - open_library_action.setToolTip("Ctrl+O") - file_menu.addAction(open_library_action) - self.open_recent_library_menu = QMenu( - Translations["menu.file.open_recent_library"], menu_bar - ) - file_menu.addMenu(self.open_recent_library_menu) + # Open Recent self.update_recent_lib_menu() - self.save_library_backup_action = QAction(Translations["menu.file.save_backup"], menu_bar) - self.save_library_backup_action.triggered.connect( - lambda: self.callback_library_needed_check(self.backup_library) + # Save Library Backup + self.main_window.menu_bar.save_library_backup_action.triggered.connect( + lambda: self.call_if_library_open(self.backup_library) ) - self.save_library_backup_action.setShortcut( - QtCore.QKeyCombination( - QtCore.Qt.KeyboardModifier( - QtCore.Qt.KeyboardModifier.ControlModifier - | QtCore.Qt.KeyboardModifier.ShiftModifier - ), - QtCore.Qt.Key.Key_S, - ) + + # Settings... + self.main_window.menu_bar.settings_action.triggered.connect(self.open_settings_modal) + + # Open Library on Start + self.main_window.menu_bar.open_on_start_action.setChecked( + self.settings.open_last_loaded_on_startup ) - self.save_library_backup_action.setStatusTip("Ctrl+Shift+S") - self.save_library_backup_action.setEnabled(False) - file_menu.addAction(self.save_library_backup_action) - - file_menu.addSeparator() - settings_action = QAction(Translations["menu.settings"], self) - settings_action.triggered.connect(self.open_settings_modal) - file_menu.addAction(settings_action) - - open_on_start_action = QAction(Translations["settings.open_library_on_start"], self) - open_on_start_action.setCheckable(True) - open_on_start_action.setChecked(self.settings.open_last_loaded_on_startup) def set_open_last_loaded_on_startup(checked: bool): self.settings.open_last_loaded_on_startup = checked self.settings.save() - open_on_start_action.triggered.connect(set_open_last_loaded_on_startup) - file_menu.addAction(open_on_start_action) - - file_menu.addSeparator() - - self.refresh_dir_action = QAction(Translations["menu.file.refresh_directories"], menu_bar) - self.refresh_dir_action.triggered.connect( - lambda: self.callback_library_needed_check(self.add_new_files_callback) + self.main_window.menu_bar.open_on_start_action.triggered.connect( + set_open_last_loaded_on_startup ) - self.refresh_dir_action.setShortcut( - QtCore.QKeyCombination( - QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), - QtCore.Qt.Key.Key_R, - ) + + # Refresh Directories + self.main_window.menu_bar.refresh_dir_action.triggered.connect( + lambda: self.call_if_library_open(self.add_new_files_callback) ) - self.refresh_dir_action.setStatusTip("Ctrl+R") - self.refresh_dir_action.setEnabled(False) - file_menu.addAction(self.refresh_dir_action) - file_menu.addSeparator() - self.close_library_action = QAction(Translations["menu.file.close_library"], menu_bar) - self.close_library_action.triggered.connect(self.close_library) - self.close_library_action.setEnabled(False) - file_menu.addAction(self.close_library_action) - file_menu.addSeparator() + # Close Library + self.main_window.menu_bar.close_library_action.triggered.connect(self.close_library) - # Edit Menu ============================================================ - self.new_tag_action = QAction(Translations["menu.edit.new_tag"], menu_bar) - self.new_tag_action.triggered.connect(lambda: self.add_tag_action_callback()) - self.new_tag_action.setShortcut( - QtCore.QKeyCombination( - QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), - QtCore.Qt.Key.Key_T, - ) + # endregion + + # region Edit Menu ============================================================ + self.main_window.menu_bar.new_tag_action.triggered.connect( + lambda: self.add_tag_action_callback() ) - self.new_tag_action.setToolTip("Ctrl+T") - self.new_tag_action.setEnabled(False) - edit_menu.addAction(self.new_tag_action) - edit_menu.addSeparator() - - self.select_all_action = QAction(Translations["select.all"], menu_bar) - self.select_all_action.triggered.connect(self.select_all_action_callback) - self.select_all_action.setShortcut( - QtCore.QKeyCombination( - QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), - QtCore.Qt.Key.Key_A, - ) + self.main_window.menu_bar.select_all_action.triggered.connect( + self.select_all_action_callback ) - self.select_all_action.setToolTip("Ctrl+A") - self.select_all_action.setEnabled(False) - edit_menu.addAction(self.select_all_action) - self.select_inverse_action = QAction(Translations["select.inverse"], menu_bar) - self.select_inverse_action.triggered.connect(self.select_inverse_action_callback) - self.select_inverse_action.setShortcut( - QtCore.QKeyCombination( - QtCore.Qt.KeyboardModifier( - QtCore.Qt.KeyboardModifier.ControlModifier - ^ QtCore.Qt.KeyboardModifier.ShiftModifier - ), - QtCore.Qt.Key.Key_I, - ) + self.main_window.menu_bar.select_inverse_action.triggered.connect( + self.select_inverse_action_callback ) - self.select_inverse_action.setToolTip("Ctrl+Shift+I") - self.select_inverse_action.setEnabled(False) - edit_menu.addAction(self.select_inverse_action) - self.clear_select_action = QAction(Translations["select.clear"], menu_bar) - self.clear_select_action.triggered.connect(self.clear_select_action_callback) - self.clear_select_action.setShortcut(QtCore.Qt.Key.Key_Escape) - self.clear_select_action.setToolTip("Esc") - self.clear_select_action.setEnabled(False) - edit_menu.addAction(self.clear_select_action) + self.main_window.menu_bar.clear_select_action.triggered.connect( + self.clear_select_action_callback + ) self.copy_buffer: dict = {"fields": [], "tags": []} - self.copy_fields_action = QAction(Translations["edit.copy_fields"], menu_bar) - self.copy_fields_action.triggered.connect(self.copy_fields_action_callback) - self.copy_fields_action.setShortcut( - QtCore.QKeyCombination( - QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), - QtCore.Qt.Key.Key_C, - ) + self.main_window.menu_bar.copy_fields_action.triggered.connect( + self.copy_fields_action_callback ) - self.copy_fields_action.setToolTip("Ctrl+C") - self.copy_fields_action.setEnabled(False) - edit_menu.addAction(self.copy_fields_action) - self.paste_fields_action = QAction(Translations["edit.paste_fields"], menu_bar) - self.paste_fields_action.triggered.connect(self.paste_fields_action_callback) - self.paste_fields_action.setShortcut( - QtCore.QKeyCombination( - QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), - QtCore.Qt.Key.Key_V, - ) + self.main_window.menu_bar.paste_fields_action.triggered.connect( + self.paste_fields_action_callback ) - self.paste_fields_action.setToolTip("Ctrl+V") - self.paste_fields_action.setEnabled(False) - edit_menu.addAction(self.paste_fields_action) - self.add_tag_to_selected_action = QAction( - Translations["select.add_tag_to_selected"], menu_bar + self.main_window.menu_bar.add_tag_to_selected_action.triggered.connect( + self.add_tag_modal.show ) - 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.main_window.menu_bar.delete_file_action.triggered.connect( + lambda f="": self.delete_files_callback(f) ) - 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() + self.main_window.menu_bar.tag_manager_action.triggered.connect(self.tag_manager_panel.show) - self.delete_file_action = QAction( - Translations.format("menu.delete_selected_files_ambiguous", trash_term=trash_term()), - menu_bar, + self.main_window.menu_bar.color_manager_action.triggered.connect( + self.color_manager_panel.show ) - self.delete_file_action.triggered.connect(lambda f="": self.delete_files_callback(f)) - self.delete_file_action.setShortcut(QtCore.Qt.Key.Key_Delete) - self.delete_file_action.setEnabled(False) - edit_menu.addAction(self.delete_file_action) - edit_menu.addSeparator() + # endregion - self.manage_file_ext_action = QAction( - Translations["menu.edit.manage_file_extensions"], menu_bar - ) - edit_menu.addAction(self.manage_file_ext_action) - self.manage_file_ext_action.setEnabled(False) - - self.tag_manager_action = QAction(Translations["menu.edit.manage_tags"], menu_bar) - self.tag_manager_action.triggered.connect(self.tag_manager_panel.show) - self.tag_manager_action.setShortcut( - QtCore.QKeyCombination( - QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), - QtCore.Qt.Key.Key_M, - ) - ) - self.tag_manager_action.setEnabled(False) - self.tag_manager_action.setToolTip("Ctrl+M") - edit_menu.addAction(self.tag_manager_action) - - self.color_manager_action = QAction(Translations["edit.color_manager"], menu_bar) - self.color_manager_action.triggered.connect(self.color_manager_panel.show) - self.color_manager_action.setEnabled(False) - edit_menu.addAction(self.color_manager_action) - - # View Menu ============================================================ - # show_libs_list_action = QAction(Translations["settings.show_recent_libraries"], menu_bar) - # show_libs_list_action.setCheckable(True) - # show_libs_list_action.setChecked(self.settings.show_library_list) + # region View Menu ============================================================ def on_show_filenames_action(checked: bool): self.settings.show_filenames_in_grid = checked self.settings.save() self.show_grid_filenames(checked) - show_filenames_action = QAction(Translations["settings.show_filenames_in_grid"], menu_bar) - show_filenames_action.setCheckable(True) - show_filenames_action.setChecked(self.settings.show_filenames_in_grid) - show_filenames_action.triggered.connect(on_show_filenames_action) - view_menu.addAction(show_filenames_action) + self.main_window.menu_bar.show_filenames_action.triggered.connect(on_show_filenames_action) + self.main_window.menu_bar.show_filenames_action.setChecked( + self.settings.show_filenames_in_grid + ) - # Tools Menu =========================================================== + # endregion + + # region Tools Menu =========================================================== def create_fix_unlinked_entries_modal(): if not hasattr(self, "unlinked_modal"): self.unlinked_modal = FixUnlinkedEntriesModal(self.lib, self) self.unlinked_modal.show() - self.fix_unlinked_entries_action = QAction( - Translations["menu.tools.fix_unlinked_entries"], menu_bar + self.main_window.menu_bar.fix_unlinked_entries_action.triggered.connect( + create_fix_unlinked_entries_modal ) - self.fix_unlinked_entries_action.triggered.connect(create_fix_unlinked_entries_modal) - self.fix_unlinked_entries_action.setEnabled(False) - tools_menu.addAction(self.fix_unlinked_entries_action) def create_dupe_files_modal(): if not hasattr(self, "dupe_modal"): self.dupe_modal = FixDupeFilesModal(self.lib, self) self.dupe_modal.show() - self.fix_dupe_files_action = QAction( - Translations["menu.tools.fix_duplicate_files"], menu_bar - ) - self.fix_dupe_files_action.triggered.connect(create_dupe_files_modal) - self.fix_dupe_files_action.setEnabled(False) - tools_menu.addAction(self.fix_dupe_files_action) - - tools_menu.addSeparator() + self.main_window.menu_bar.fix_dupe_files_action.triggered.connect(create_dupe_files_modal) # TODO: Move this to a settings screen. - self.clear_thumb_cache_action = QAction( - Translations["settings.clear_thumb_cache.title"], menu_bar - ) - self.clear_thumb_cache_action.triggered.connect( + self.main_window.menu_bar.clear_thumb_cache_action.triggered.connect( lambda: CacheManager.clear_cache(self.lib.library_dir) ) - self.clear_thumb_cache_action.setEnabled(False) - tools_menu.addAction(self.clear_thumb_cache_action) - # create_collage_action = QAction("Create Collage", menu_bar) - # create_collage_action.triggered.connect(lambda: self.create_collage()) - # tools_menu.addAction(create_collage_action) - - # Macros Menu ========================================================== - # self.autofill_action = QAction("Autofill", menu_bar) - # self.autofill_action.triggered.connect( - # lambda: ( - # self.run_macros(MacroID.AUTOFILL, self.selected), - # self.preview_panel.update_widgets(update_preview=False), - # ) - # ) - # macros_menu.addAction(self.autofill_action) + # endregion + # region Macros Menu ========================================================== def create_folders_tags_modal(): if not hasattr(self, "folders_modal"): self.folders_modal = FoldersToTagsModal(self.lib, self) self.folders_modal.show() - self.folders_to_tags_action = QAction(Translations["menu.macros.folders_to_tags"], menu_bar) - self.folders_to_tags_action.triggered.connect(create_folders_tags_modal) - self.folders_to_tags_action.setEnabled(False) - macros_menu.addAction(self.folders_to_tags_action) + self.main_window.menu_bar.folders_to_tags_action.triggered.connect( + create_folders_tags_modal + ) - # Help Menu ============================================================ + # endregion + + # region Help Menu ============================================================ def create_about_modal(): if not hasattr(self, "about_modal"): self.about_modal = AboutModal(self.global_settings_path) self.about_modal.show() - self.about_action = QAction(Translations["menu.help.about"], menu_bar) - self.about_action.triggered.connect(create_about_modal) - help_menu.addAction(self.about_action) - self.set_macro_menu_viability() + self.main_window.menu_bar.about_action.triggered.connect(create_about_modal) - menu_bar.addMenu(file_menu) - menu_bar.addMenu(edit_menu) - menu_bar.addMenu(view_menu) - menu_bar.addMenu(tools_menu) - menu_bar.addMenu(macros_menu) - menu_bar.addMenu(help_menu) + # endregion - self.main_window.searchField.textChanged.connect(self.update_completions_list) + # endregion - self.preview_panel = PreviewPanel(self.lib, self) - self.preview_panel.fields.archived_updated.connect( + self.main_window.search_field.textChanged.connect(self.update_completions_list) + + self.main_window.preview_panel.fields.archived_updated.connect( lambda hidden: self.update_badges( {BadgeType.ARCHIVED: hidden}, origin_id=0, add_tags=False ) ) - self.preview_panel.fields.favorite_updated.connect( + self.main_window.preview_panel.fields.favorite_updated.connect( lambda hidden: self.update_badges( {BadgeType.FAVORITE: hidden}, origin_id=0, add_tags=False ) ) - splitter = self.main_window.splitter - splitter.addWidget(self.preview_panel) - QFontDatabase.addApplicationFont( str(Path(__file__).parents[1] / "resources/qt/fonts/Oxanium-Bold.ttf") ) - # TODO this doesn't update when the language is changed - self.thumb_sizes: list[tuple[str, int]] = [ - (Translations["home.thumbnail_size.extra_large"], 256), - (Translations["home.thumbnail_size.large"], 192), - (Translations["home.thumbnail_size.medium"], 128), - (Translations["home.thumbnail_size.small"], 96), - (Translations["home.thumbnail_size.mini"], 76), - ] self.item_thumbs: list[ItemThumb] = [] self.thumb_renderers: list[ThumbRenderer] = [] self.init_library_window() @@ -762,7 +578,7 @@ class QtDriver(DriverMixin, QObject): self.shutdown() def show_error_message(self, error_name: str, error_desc: str | None = None): - self.main_window.statusbar.showMessage(error_name, Qt.AlignmentFlag.AlignLeft) + self.main_window.status_bar.showMessage(error_name, Qt.AlignmentFlag.AlignLeft) self.main_window.landing_widget.set_status_label(error_name) self.main_window.setWindowTitle(f"{self.base_title} - {error_name}") @@ -788,54 +604,43 @@ class QtDriver(DriverMixin, QObject): def _update_browsing_state(): try: self.update_browsing_state( - BrowsingState.from_search_query(self.main_window.searchField.text()) - .with_sorting_mode(self.sorting_mode) - .with_sorting_direction(self.sorting_direction) + BrowsingState.from_search_query(self.main_window.search_field.text()) + .with_sorting_mode(self.main_window.sorting_mode) + .with_sorting_direction(self.main_window.sorting_direction) ) except ParsingError as e: - self.main_window.statusbar.showMessage( + self.main_window.status_bar.showMessage( f"{Translations['status.results.invalid_syntax']} " - f'"{self.main_window.searchField.text()}"' + f'"{self.main_window.search_field.text()}"' ) logger.error("[QtDriver] Could not update BrowsingState", error=e) # Search Button - search_button: QPushButton = self.main_window.searchButton - search_button.clicked.connect(_update_browsing_state) - # Search Field - search_field: QLineEdit = self.main_window.searchField - search_field.returnPressed.connect(_update_browsing_state) - # Sorting Dropdowns - sort_mode_dropdown: QComboBox = self.main_window.sorting_mode_combobox - for sort_mode in SortingModeEnum: - sort_mode_dropdown.addItem(Translations[sort_mode.value], sort_mode) - sort_mode_dropdown.setCurrentIndex( - list(SortingModeEnum).index(self.browsing_history.current.sorting_mode) - ) # set according to navigation state - sort_mode_dropdown.currentIndexChanged.connect(self.sorting_mode_callback) + self.main_window.search_button.clicked.connect(_update_browsing_state) - sort_dir_dropdown: QComboBox = self.main_window.sorting_direction_combobox - sort_dir_dropdown.addItem("Ascending", userData=True) - sort_dir_dropdown.addItem("Descending", userData=False) - sort_dir_dropdown.setItemText(0, Translations["sorting.direction.ascending"]) - sort_dir_dropdown.setItemText(1, Translations["sorting.direction.descending"]) - sort_dir_dropdown.setCurrentIndex(1) # Default: Descending - sort_dir_dropdown.currentIndexChanged.connect(self.sorting_direction_callback) + # Search Field + self.main_window.search_field.returnPressed.connect(_update_browsing_state) + + # Sorting Dropdowns + self.main_window.sorting_mode_combobox.setCurrentIndex( + list(SortingModeEnum).index(self.browsing_history.current.sorting_mode) + ) + self.main_window.sorting_mode_combobox.currentIndexChanged.connect( + self.sorting_mode_callback + ) + + self.main_window.sorting_direction_combobox.currentIndexChanged.connect( + self.sorting_direction_callback + ) # Thumbnail Size ComboBox - thumb_size_combobox: QComboBox = self.main_window.thumb_size_combobox - for size in self.thumb_sizes: - thumb_size_combobox.addItem(size[0]) - thumb_size_combobox.setCurrentIndex(2) # Default: Medium - thumb_size_combobox.currentIndexChanged.connect( - lambda: self.thumb_size_callback(thumb_size_combobox.currentIndex()) + self.main_window.thumb_size_combobox.currentIndexChanged.connect( + lambda: self.thumb_size_callback(self.main_window.thumb_size_combobox.currentData()) ) - self._init_thumb_grid() + self._update_thumb_count() - back_button: QPushButton = self.main_window.backButton - back_button.clicked.connect(lambda: self.navigation_callback(-1)) - forward_button: QPushButton = self.main_window.forwardButton - forward_button.clicked.connect(lambda: self.navigation_callback(1)) + self.main_window.back_button.clicked.connect(lambda: self.navigation_callback(-1)) + self.main_window.forward_button.clicked.connect(lambda: self.navigation_callback(1)) # NOTE: Putting this early will result in a white non-responsive # window until everything is loaded. Consider adding a splash screen @@ -852,7 +657,7 @@ class QtDriver(DriverMixin, QObject): """Initialize the File Extension panel.""" if self.file_extension_panel: with catch_warnings(record=True): - self.manage_file_ext_action.triggered.disconnect() + self.main_window.menu_bar.manage_file_ext_action.triggered.disconnect() self.file_extension_panel.saved.disconnect() self.file_extension_panel.deleteLater() self.file_extension_panel = None @@ -867,13 +672,15 @@ class QtDriver(DriverMixin, QObject): self.file_extension_panel.saved.connect( lambda: (panel.save(), self.update_browsing_state()) ) - self.manage_file_ext_action.triggered.connect(self.file_extension_panel.show) + self.main_window.menu_bar.manage_file_ext_action.triggered.connect( + self.file_extension_panel.show + ) def show_grid_filenames(self, value: bool): for thumb in self.item_thumbs: thumb.set_filename_visibility(value) - def callback_library_needed_check(self, func): + def call_if_library_open(self, func): """Check if loaded library has valid path before executing the button function.""" if self.lib.library_dir: func() @@ -901,16 +708,16 @@ class QtDriver(DriverMixin, QObject): return logger.info("Closing Library...") - self.main_window.statusbar.showMessage(Translations["status.library_closing"]) + self.main_window.status_bar.showMessage(Translations["status.library_closing"]) start_time = time.time() self.cached_values.setValue(SettingItems.LAST_LIBRARY, str(self.lib.library_dir)) self.cached_values.sync() # Reset library state - self.preview_panel.update_widgets() - self.main_window.searchField.setText("") - scrollbar: QScrollArea = self.main_window.scrollArea + self.main_window.preview_panel.update_widgets() + self.main_window.search_field.setText("") + scrollbar: QScrollArea = self.main_window.entry_scroll_area scrollbar.verticalScrollBar().setValue(0) self.__reset_navigation() @@ -932,32 +739,32 @@ class QtDriver(DriverMixin, QObject): self.set_clipboard_menu_viability() self.set_select_actions_visibility() - self.preview_panel.update_widgets() + self.main_window.preview_panel.update_widgets() self.main_window.toggle_landing_page(enabled=True) self.main_window.pagination.setHidden(True) try: - self.save_library_backup_action.setEnabled(False) - self.close_library_action.setEnabled(False) - self.refresh_dir_action.setEnabled(False) - self.tag_manager_action.setEnabled(False) - self.color_manager_action.setEnabled(False) - self.manage_file_ext_action.setEnabled(False) - self.new_tag_action.setEnabled(False) - self.fix_unlinked_entries_action.setEnabled(False) - self.fix_dupe_files_action.setEnabled(False) - self.clear_thumb_cache_action.setEnabled(False) - self.folders_to_tags_action.setEnabled(False) + self.main_window.menu_bar.save_library_backup_action.setEnabled(False) + self.main_window.menu_bar.close_library_action.setEnabled(False) + self.main_window.menu_bar.refresh_dir_action.setEnabled(False) + self.main_window.menu_bar.tag_manager_action.setEnabled(False) + self.main_window.menu_bar.color_manager_action.setEnabled(False) + self.main_window.menu_bar.manage_file_ext_action.setEnabled(False) + self.main_window.menu_bar.new_tag_action.setEnabled(False) + self.main_window.menu_bar.fix_unlinked_entries_action.setEnabled(False) + self.main_window.menu_bar.fix_dupe_files_action.setEnabled(False) + self.main_window.menu_bar.clear_thumb_cache_action.setEnabled(False) + self.main_window.menu_bar.folders_to_tags_action.setEnabled(False) except AttributeError: logger.warning( "[Library] Could not disable library management menu actions. Is this in a test?" ) # NOTE: Doesn't try to disable during tests - if self.add_tag_to_selected_action: - self.add_tag_to_selected_action.setEnabled(False) + if self.main_window.menu_bar.add_tag_to_selected_action: + self.main_window.menu_bar.add_tag_to_selected_action.setEnabled(False) end_time = time.time() - self.main_window.statusbar.showMessage( + self.main_window.status_bar.showMessage( Translations.format( "status.library_closed", time_span=format_timespan(end_time - start_time) ) @@ -965,11 +772,11 @@ class QtDriver(DriverMixin, QObject): def backup_library(self): logger.info("Backing Up Library...") - self.main_window.statusbar.showMessage(Translations["status.library_backup_in_progress"]) + self.main_window.status_bar.showMessage(Translations["status.library_backup_in_progress"]) start_time = time.time() target_path = self.lib.save_library_backup_to_disk() end_time = time.time() - self.main_window.statusbar.showMessage( + self.main_window.status_bar.showMessage( Translations.format( "status.library_backup_success", path=target_path, @@ -1007,11 +814,10 @@ class QtDriver(DriverMixin, QObject): self.selected.append(item.item_id) item.thumb_button.set_selected(True) - self.set_macro_menu_viability() self.set_clipboard_menu_viability() self.set_select_actions_visibility() - self.preview_panel.update_widgets(update_preview=False) + self.main_window.preview_panel.update_widgets(update_preview=False) def select_inverse_action_callback(self): """Invert the selection of all visible items.""" @@ -1027,11 +833,10 @@ class QtDriver(DriverMixin, QObject): self.selected = new_selected - self.set_macro_menu_viability() self.set_clipboard_menu_viability() self.set_select_actions_visibility() - self.preview_panel.update_widgets(update_preview=False) + self.main_window.preview_panel.update_widgets(update_preview=False) def clear_select_action_callback(self): self.selected.clear() @@ -1039,9 +844,8 @@ class QtDriver(DriverMixin, QObject): for item in self.item_thumbs: item.thumb_button.set_selected(False) - self.set_macro_menu_viability() self.set_clipboard_menu_viability() - self.preview_panel.update_widgets() + self.main_window.preview_panel.update_widgets() def add_tags_to_selected_callback(self, tag_ids: list[int]): self.lib.add_tags_to_entries(self.selected, tag_ids) @@ -1086,14 +890,14 @@ class QtDriver(DriverMixin, QObject): for i, tup in enumerate(pending): e_id, f = tup if (origin_path == f) or (not origin_path): - self.preview_panel.thumb.media_player.stop() + self.main_window.preview_panel.thumb.media_player.stop() if delete_file(self.lib.library_dir / f): - self.main_window.statusbar.showMessage( + self.main_window.status_bar.showMessage( Translations.format( "status.deleting_file", i=i, count=len(pending), path=f ) ) - self.main_window.statusbar.repaint() + self.main_window.status_bar.repaint() self.lib.remove_entries([e_id]) deleted_count += 1 @@ -1101,25 +905,25 @@ class QtDriver(DriverMixin, QObject): if deleted_count > 0: self.update_browsing_state() - self.preview_panel.update_widgets() + self.main_window.preview_panel.update_widgets() if len(self.selected) <= 1 and deleted_count == 0: - self.main_window.statusbar.showMessage(Translations["status.deleted_none"]) + self.main_window.status_bar.showMessage(Translations["status.deleted_none"]) elif len(self.selected) <= 1 and deleted_count == 1: - self.main_window.statusbar.showMessage( + self.main_window.status_bar.showMessage( Translations.format("status.deleted_file_plural", count=deleted_count) ) elif len(self.selected) > 1 and deleted_count == 0: - self.main_window.statusbar.showMessage(Translations["status.deleted_none"]) + self.main_window.status_bar.showMessage(Translations["status.deleted_none"]) elif len(self.selected) > 1 and deleted_count < len(self.selected): - self.main_window.statusbar.showMessage( + self.main_window.status_bar.showMessage( Translations.format("status.deleted_partial_warning", count=deleted_count) ) elif len(self.selected) > 1 and deleted_count == len(self.selected): - self.main_window.statusbar.showMessage( + self.main_window.status_bar.showMessage( Translations.format("status.deleted_file_plural", count=deleted_count) ) - self.main_window.statusbar.repaint() + self.main_window.status_bar.repaint() def delete_file_confirmation(self, count: int, filename: Path | None = None) -> int: """A confirmation dialogue box for deleting files. @@ -1316,54 +1120,34 @@ class QtDriver(DriverMixin, QObject): content=strip_web_protocol(field.value), ) - @property - def sorting_direction(self) -> bool: - """Whether to Sort the results in ascending order.""" - return self.main_window.sorting_direction_combobox.currentData() - def sorting_direction_callback(self): - logger.info("Sorting Direction Changed", ascending=self.sorting_direction) + logger.info("Sorting Direction Changed", ascending=self.main_window.sorting_direction) self.update_browsing_state( - self.browsing_history.current.with_sorting_direction(self.sorting_direction) + self.browsing_history.current.with_sorting_direction(self.main_window.sorting_direction) ) - @property - def sorting_mode(self) -> SortingModeEnum: - """What to sort by.""" - return self.main_window.sorting_mode_combobox.currentData() - def sorting_mode_callback(self): - logger.info("Sorting Mode Changed", mode=self.sorting_mode) + logger.info("Sorting Mode Changed", mode=self.main_window.sorting_mode) self.update_browsing_state( - self.browsing_history.current.with_sorting_mode(self.sorting_mode) + self.browsing_history.current.with_sorting_mode(self.main_window.sorting_mode) ) - def thumb_size_callback(self, index: int): - """Perform actions needed when the thumbnail size selection is changed. - - Args: - index (int): The index of the item_thumbs/ComboBox list to use. - """ + def thumb_size_callback(self, size: int): + """Perform actions needed when the thumbnail size selection is changed.""" spacing_divisor: int = 10 min_spacing: int = 12 - # Index 2 is the default (Medium) - if index < len(self.thumb_sizes) and index >= 0: - self.thumb_size = self.thumb_sizes[index][1] - else: - logger.error(f"ERROR: Invalid thumbnail size index ({index}). Defaulting to 128px.") - self.thumb_size = 128 self.update_thumbs() blank_icon: QIcon = QIcon() for it in self.item_thumbs: it.thumb_button.setIcon(blank_icon) - it.resize(self.thumb_size, self.thumb_size) - it.thumb_size = (self.thumb_size, self.thumb_size) - it.setFixedSize(self.thumb_size, self.thumb_size) - it.thumb_button.thumb_size = (self.thumb_size, self.thumb_size) + it.resize(self.main_window.thumb_size, self.main_window.thumb_size) + it.thumb_size = (self.main_window.thumb_size, self.main_window.thumb_size) + it.setFixedSize(self.main_window.thumb_size, self.main_window.thumb_size) + it.thumb_button.thumb_size = (self.main_window.thumb_size, self.main_window.thumb_size) it.set_filename_visibility(it.show_filename_label) - self.flow_container.layout().setSpacing( - min(self.thumb_size // spacing_divisor, min_spacing) + self.main_window.thumb_layout.setSpacing( + min(self.main_window.thumb_size // spacing_divisor, min_spacing) ) def mouse_navigation(self, event: QMouseEvent): @@ -1405,36 +1189,19 @@ class QtDriver(DriverMixin, QObject): def _update_thumb_count(self): missing_count = max(0, self.settings.page_size - len(self.item_thumbs)) - layout = self.flow_container.layout() + layout = self.main_window.thumb_layout for _ in range(missing_count): item_thumb = ItemThumb( None, self.lib, self, - (self.thumb_size, self.thumb_size), + (self.main_window.thumb_size, self.main_window.thumb_size), self.settings.show_filenames_in_grid, ) layout.addWidget(item_thumb) self.item_thumbs.append(item_thumb) - def _init_thumb_grid(self): - layout = FlowLayout() - layout.enable_grid_optimizations(value=True) - layout.setSpacing(min(self.thumb_size // 10, 12)) - layout.setAlignment(Qt.AlignmentFlag.AlignCenter) - - self.flow_container: QWidget = QWidget() - self.flow_container.setObjectName("flowContainer") - self.flow_container.setLayout(layout) - - self._update_thumb_count() - - sa: QScrollArea = self.main_window.scrollArea - sa.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) - sa.setWidgetResizable(True) - sa.setWidget(self.flow_container) - def copy_fields_action_callback(self): if len(self.selected) > 0: entry = self.lib.get_entry_full(self.selected[0]) @@ -1463,7 +1230,7 @@ class QtDriver(DriverMixin, QObject): if TAG_FAVORITE in self.copy_buffer["tags"]: self.update_badges({BadgeType.FAVORITE: True}, origin_id=0, add_tags=False) else: - self.preview_panel.update_widgets() + self.main_window.preview_panel.update_widgets() def toggle_item_selection(self, item_id: int, append: bool, bridge: bool): """Toggle the selection of an item in the Thumbnail Grid. @@ -1534,45 +1301,40 @@ class QtDriver(DriverMixin, QObject): else: it.thumb_button.set_selected(False) - self.set_macro_menu_viability() self.set_clipboard_menu_viability() self.set_select_actions_visibility() - self.preview_panel.update_widgets() - - def set_macro_menu_viability(self): - # self.autofill_action.setDisabled(not self.selected) - pass + self.main_window.preview_panel.update_widgets() def set_clipboard_menu_viability(self): if len(self.selected) == 1: - self.copy_fields_action.setEnabled(True) + self.main_window.menu_bar.copy_fields_action.setEnabled(True) else: - self.copy_fields_action.setEnabled(False) + self.main_window.menu_bar.copy_fields_action.setEnabled(False) if self.selected and (self.copy_buffer["fields"] or self.copy_buffer["tags"]): - self.paste_fields_action.setEnabled(True) + self.main_window.menu_bar.paste_fields_action.setEnabled(True) else: - self.paste_fields_action.setEnabled(False) + self.main_window.menu_bar.paste_fields_action.setEnabled(False) def set_select_actions_visibility(self): - if not self.add_tag_to_selected_action: + if not self.main_window.menu_bar.add_tag_to_selected_action: return if self.frame_content: - self.select_all_action.setEnabled(True) - self.select_inverse_action.setEnabled(True) + self.main_window.menu_bar.select_all_action.setEnabled(True) + self.main_window.menu_bar.select_inverse_action.setEnabled(True) else: - self.select_all_action.setEnabled(False) - self.select_inverse_action.setEnabled(False) + self.main_window.menu_bar.select_all_action.setEnabled(False) + self.main_window.menu_bar.select_inverse_action.setEnabled(False) if self.selected: - self.add_tag_to_selected_action.setEnabled(True) - self.clear_select_action.setEnabled(True) - self.delete_file_action.setEnabled(True) + self.main_window.menu_bar.add_tag_to_selected_action.setEnabled(True) + self.main_window.menu_bar.clear_select_action.setEnabled(True) + self.main_window.menu_bar.delete_file_action.setEnabled(True) else: - self.add_tag_to_selected_action.setEnabled(False) - self.clear_select_action.setEnabled(False) - self.delete_file_action.setEnabled(False) + self.main_window.menu_bar.add_tag_to_selected_action.setEnabled(False) + self.main_window.menu_bar.clear_select_action.setEnabled(False) + self.main_window.menu_bar.delete_file_action.setEnabled(False) def update_completions_list(self, text: str) -> None: matches = re.search( @@ -1589,7 +1351,7 @@ class QtDriver(DriverMixin, QObject): "tag_id:", "special:untagged", ] - self.main_window.searchFieldCompletionList.setStringList(completion_list) + self.main_window.search_field_completion_list.setStringList(completion_list) if not matches: return @@ -1638,16 +1400,15 @@ class QtDriver(DriverMixin, QObject): ) update_completion_list: bool = ( - completion_list != self.main_window.searchFieldCompletionList.stringList() - or self.main_window.searchFieldCompletionList == [] + completion_list != self.main_window.search_field_completion_list.stringList() + or self.main_window.search_field_completion_list == [] ) if update_completion_list: - self.main_window.searchFieldCompletionList.setStringList(completion_list) + self.main_window.search_field_completion_list.setStringList(completion_list) def update_thumbs(self): """Update search thumbnails.""" self._update_thumb_count() - # start_time = time.time() # logger.info(f'Current Page: {self.cur_page_idx}, Stack Length:{len(self.nav_stack)}') with self.thumb_job_queue.mutex: # Cancels all thumb jobs waiting to be started @@ -1658,11 +1419,9 @@ class QtDriver(DriverMixin, QObject): ItemThumb.update_cutoff = time.time() ratio: float = self.main_window.devicePixelRatio() - base_size: tuple[int, int] = (self.thumb_size, self.thumb_size) + base_size: tuple[int, int] = (self.main_window.thumb_size, self.main_window.thumb_size) - # scrollbar: QScrollArea = self.main_window.scrollArea - # scrollbar.verticalScrollBar().setValue(scrollbar_pos) - self.flow_container.layout().update() + self.main_window.thumb_layout.update() self.main_window.update() is_grid_thumb = True @@ -1798,11 +1557,11 @@ class QtDriver(DriverMixin, QObject): if state: self.browsing_history.push(state) - self.main_window.searchField.setText(self.browsing_history.current.query or "") + self.main_window.search_field.setText(self.browsing_history.current.query or "") # inform user about running search - self.main_window.statusbar.showMessage(Translations["status.library_search_query"]) - self.main_window.statusbar.repaint() + self.main_window.status_bar.showMessage(Translations["status.library_search_query"]) + self.main_window.status_bar.repaint() # search the library start_time = time.time() @@ -1811,7 +1570,7 @@ class QtDriver(DriverMixin, QObject): end_time = time.time() # inform user about completed search - self.main_window.statusbar.showMessage( + self.main_window.status_bar.showMessage( Translations.format( "status.results_found", count=results.total_count, @@ -1864,13 +1623,12 @@ class QtDriver(DriverMixin, QObject): def update_recent_lib_menu(self): """Updates the recent library menu from the latest values from the settings file.""" - actions: list[QAction] = [] lib_items: dict[str, tuple[str, str]] = {} - settings = self.cached_values - settings.beginGroup(SettingItems.LIBS_LIST) - for item_tstamp in settings.allKeys(): - val = str(settings.value(item_tstamp, type=str)) + # get recent libraries sorted by timestamp + self.cached_values.beginGroup(SettingItems.LIBS_LIST) + for item_tstamp in self.cached_values.allKeys(): + val = str(self.cached_values.value(item_tstamp, type=str)) cut_val = val if len(val) > 45: cut_val = f"{val[0:10]} ... {val[-10:]}" @@ -1878,40 +1636,14 @@ class QtDriver(DriverMixin, QObject): # Sort lib_items by the key libs_sorted = sorted(lib_items.items(), key=lambda item: item[0], reverse=True) - settings.endGroup() + self.cached_values.endGroup() - # Create actions for each library - for library_key in libs_sorted: - path = Path(library_key[1][0]) - action = QAction(self.open_recent_library_menu) - if self.settings.show_filepath == ShowFilepathOption.SHOW_FULL_PATHS: - action.setText(str(path)) - else: - action.setText(str(path.name)) - action.triggered.connect(lambda checked=False, p=path: self.open_library(p)) - actions.append(action) - - clear_recent_action = QAction( - Translations["menu.file.clear_recent_libraries"], self.open_recent_library_menu + self.main_window.menu_bar.rebuild_open_recent_library_menu( + [Path(key[1][0]) for key in libs_sorted], + self.settings.show_filepath, # TODO: once QtDriver is a Singleton remove this parameter + self.open_library, + self.clear_recent_libs, ) - clear_recent_action.triggered.connect(self.clear_recent_libs) - actions.append(clear_recent_action) - - # Clear previous actions - for action in self.open_recent_library_menu.actions(): - self.open_recent_library_menu.removeAction(action) - - # Add new actions - for action in actions: - self.open_recent_library_menu.addAction(action) - - # Only enable add "clear recent" if there are still recent libraries. - if len(actions) > 1: - self.open_recent_library_menu.setDisabled(False) - self.open_recent_library_menu.addSeparator() - self.open_recent_library_menu.addAction(clear_recent_action) - else: - self.open_recent_library_menu.setDisabled(True) def clear_recent_libs(self): """Clear the list of recent libraries from the settings file.""" @@ -1932,7 +1664,7 @@ class QtDriver(DriverMixin, QObject): ) message = Translations.format("splash.opening_library", library_path=library_dir_display) self.main_window.landing_widget.set_status_label(message) - self.main_window.statusbar.showMessage(message, 3) + self.main_window.status_bar.showMessage(message, 3) self.main_window.repaint() if self.lib.library_dir: @@ -2004,19 +1736,19 @@ class QtDriver(DriverMixin, QObject): self.selected.clear() self.set_select_actions_visibility() - self.save_library_backup_action.setEnabled(True) - self.close_library_action.setEnabled(True) - self.refresh_dir_action.setEnabled(True) - self.tag_manager_action.setEnabled(True) - self.color_manager_action.setEnabled(True) - self.manage_file_ext_action.setEnabled(True) - self.new_tag_action.setEnabled(True) - self.fix_dupe_files_action.setEnabled(True) - self.fix_unlinked_entries_action.setEnabled(True) - self.clear_thumb_cache_action.setEnabled(True) - self.folders_to_tags_action.setEnabled(True) + self.main_window.menu_bar.save_library_backup_action.setEnabled(True) + self.main_window.menu_bar.close_library_action.setEnabled(True) + self.main_window.menu_bar.refresh_dir_action.setEnabled(True) + self.main_window.menu_bar.tag_manager_action.setEnabled(True) + self.main_window.menu_bar.color_manager_action.setEnabled(True) + self.main_window.menu_bar.manage_file_ext_action.setEnabled(True) + self.main_window.menu_bar.new_tag_action.setEnabled(True) + self.main_window.menu_bar.fix_unlinked_entries_action.setEnabled(True) + self.main_window.menu_bar.fix_dupe_files_action.setEnabled(True) + self.main_window.menu_bar.clear_thumb_cache_action.setEnabled(True) + self.main_window.menu_bar.folders_to_tags_action.setEnabled(True) - self.preview_panel.update_widgets() + self.main_window.preview_panel.update_widgets() # page (re)rendering, extract eventually self.update_browsing_state() diff --git a/src/tagstudio/qt/widgets/item_thumb.py b/src/tagstudio/qt/widgets/item_thumb.py index 52719ea4..f4d9fd68 100644 --- a/src/tagstudio/qt/widgets/item_thumb.py +++ b/src/tagstudio/qt/widgets/item_thumb.py @@ -498,9 +498,11 @@ class ItemThumb(FlowWidget): toggle_value: bool, tag_id: int, ): - if entry_id in self.driver.selected and self.driver.preview_panel.is_open: + if entry_id in self.driver.selected and self.driver.main_window.preview_panel.is_open: if len(self.driver.selected) == 1: - self.driver.preview_panel.fields.update_toggled_tag(tag_id, toggle_value) + self.driver.main_window.preview_panel.fields.update_toggled_tag( + tag_id, toggle_value + ) else: pass diff --git a/src/tagstudio/qt/widgets/tag_box.py b/src/tagstudio/qt/widgets/tag_box.py index dbeea052..e437c880 100644 --- a/src/tagstudio/qt/widgets/tag_box.py +++ b/src/tagstudio/qt/widgets/tag_box.py @@ -59,14 +59,14 @@ class TagBoxWidget(FieldWidget): tag_widget.on_remove.connect( lambda tag_id=tag.id: ( self.remove_tag(tag_id), - self.driver.preview_panel.update_widgets(update_preview=False), + self.driver.main_window.preview_panel.update_widgets(update_preview=False), ) ) tag_widget.on_edit.connect(lambda t=tag: self.edit_tag(t)) tag_widget.search_for_tag_action.triggered.connect( lambda checked=False, tag_id=tag.id: ( - self.driver.main_window.searchField.setText(f"tag_id:{tag_id}"), + self.driver.main_window.search_field.setText(f"tag_id:{tag_id}"), self.driver.update_browsing_state(BrowsingState.from_tag_id(tag_id)), ) ) @@ -81,7 +81,9 @@ class TagBoxWidget(FieldWidget): build_tag_panel, self.driver.lib.tag_display_name(tag.id), "Edit Tag", - done_callback=lambda: self.driver.preview_panel.update_widgets(update_preview=False), + done_callback=lambda: self.driver.main_window.preview_panel.update_widgets( + update_preview=False + ), has_save=True, ) # TODO - this was update_tag() diff --git a/tests/conftest.py b/tests/conftest.py index 64f96d41..2f4859fb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -155,14 +155,15 @@ def qt_driver(qtbot, library, library_dir: Path): driver.app = Mock() driver.main_window = Mock() - driver.preview_panel = Mock() - driver.flow_container = Mock() + driver.main_window.preview_panel = Mock() + driver.main_window.thumb_grid = Mock() + driver.main_window.thumb_size = 128 driver.item_thumbs = [] - driver.autofill_action = Mock() + driver.main_window.menu_bar.autofill_action = Mock() driver.copy_buffer = {"fields": [], "tags": []} - driver.copy_fields_action = Mock() - driver.paste_fields_action = Mock() + driver.main_window.menu_bar.copy_fields_action = Mock() + driver.main_window.menu_bar.paste_fields_action = Mock() driver.lib = library # TODO - downsize this method and use it diff --git a/tests/qt/test_file_path_options.py b/tests/qt/test_file_path_options.py index c903b7f6..bd59f537 100644 --- a/tests/qt/test_file_path_options.py +++ b/tests/qt/test_file_path_options.py @@ -121,18 +121,18 @@ def test_title_update( qt_driver.settings.show_filepath = filepath_option menu_bar = QMenuBar() - qt_driver.open_recent_library_menu = QMenu(menu_bar) - qt_driver.manage_file_ext_action = QAction(menu_bar) - qt_driver.save_library_backup_action = QAction(menu_bar) - qt_driver.close_library_action = QAction(menu_bar) - qt_driver.refresh_dir_action = QAction(menu_bar) - qt_driver.tag_manager_action = QAction(menu_bar) - qt_driver.color_manager_action = QAction(menu_bar) - qt_driver.new_tag_action = QAction(menu_bar) - qt_driver.fix_dupe_files_action = QAction(menu_bar) - qt_driver.fix_unlinked_entries_action = QAction(menu_bar) - qt_driver.clear_thumb_cache_action = QAction(menu_bar) - qt_driver.folders_to_tags_action = QAction(menu_bar) + qt_driver.main_window.menu_bar.open_recent_library_menu = QMenu(menu_bar) + qt_driver.main_window.menu_bar.manage_file_ext_action = QAction(menu_bar) + qt_driver.main_window.menu_bar.save_library_backup_action = QAction(menu_bar) + qt_driver.main_window.menu_bar.close_library_action = QAction(menu_bar) + qt_driver.main_window.menu_bar.refresh_dir_action = QAction(menu_bar) + qt_driver.main_window.menu_bar.tag_manager_action = QAction(menu_bar) + qt_driver.main_window.menu_bar.color_manager_action = QAction(menu_bar) + qt_driver.main_window.menu_bar.new_tag_action = QAction(menu_bar) + qt_driver.main_window.menu_bar.fix_unlinked_entries_action = QAction(menu_bar) + qt_driver.main_window.menu_bar.fix_dupe_files_action = QAction(menu_bar) + qt_driver.main_window.menu_bar.clear_thumb_cache_action = QAction(menu_bar) + qt_driver.main_window.menu_bar.folders_to_tags_action = QAction(menu_bar) # Trigger the update qt_driver._init_library(library_dir, open_status)