refactor: split QtDriver into View and Controller to follow MVC model (#935)

* fix: incorrect qt enum accesses

* refactor: clean up main_window.py and reenable ruff checks for it

* refactor: completely reorganise main_window.py

* refactor: factor out regions into separate methods

* refactor: move preview_panel to MainWindow

* refactor: move file menu construction to MainMenuBar

* refactor: move edit menu construction to MainMenuBar

* refactor: move view menu construction to MainMenuBar

* refactor: move tools menu construction to MainMenuBar

* refactor: move macros menu construction to MainMenuBar

* refactor: move help menu construction to MainMenuBar

* refactor: move ui construction code out of QtDriver and into MainWindow

* fix: remove unnecessary imports

* fix: tests were failing
This commit is contained in:
Jann Stute
2025-06-06 01:02:03 +02:00
committed by GitHub
parent da18bd0dd5
commit 14d1c2b618
12 changed files with 813 additions and 644 deletions

View File

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

View File

@@ -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()

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -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()

View File

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

View File

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