Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions src/tagstudio/core/library/alchemy/library.py
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,14 @@ def search_library(
assert self.library_dir

with Session(unwrap(self.engine), expire_on_commit=False) as session:
statement = select(Entry.id, func.count().over())
if page_size:
statement = (
select(Entry.id, func.count().over())
.offset(search.page_index * page_size)
.limit(page_size)
)
else:
statement = select(Entry.id)

if search.ast:
start_time = time.time()
Expand All @@ -1017,8 +1024,6 @@ def search_library(
sort_on = func.sin(Entry.id * search.random_seed)

statement = statement.order_by(asc(sort_on) if search.ascending else desc(sort_on))
if page_size is not None:
statement = statement.limit(page_size).offset(search.page_index * page_size)

logger.info(
"searching library",
Expand All @@ -1027,17 +1032,21 @@ def search_library(
)

start_time = time.time()
rows = session.execute(statement).fetchall()
ids = []
count = 0
for row in rows:
id, count = row._tuple() # pyright: ignore[reportPrivateUsage]
ids.append(id)
if page_size:
rows = session.execute(statement).fetchall()
ids = []
total_count = 0
for row in rows:
ids.append(row[0])
total_count = row[1]
else:
ids = list(session.scalars(statement))
total_count = len(ids)
end_time = time.time()
logger.info(f"SQL Execution finished ({format_timespan(end_time - start_time)})")

res = SearchResult(
total_count=count,
total_count=total_count,
ids=ids,
)

Expand Down
1 change: 1 addition & 0 deletions src/tagstudio/qt/global_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class GlobalSettings(BaseModel):
loop: bool = Field(default=True)
show_filenames_in_grid: bool = Field(default=True)
page_size: int = Field(default=100)
infinite_scroll: bool = Field(default=True)
show_filepath: ShowFilepathOption = Field(default=ShowFilepathOption.DEFAULT)
tag_click_action: TagClickActionOption = Field(default=TagClickActionOption.DEFAULT)
theme: Theme = Field(default=Theme.SYSTEM)
Expand Down
4 changes: 2 additions & 2 deletions src/tagstudio/qt/mixed/file_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def update_stats(self, filepath: Path | None = None, stats: FileAttributeData |
self.layout().setSpacing(0)
self.file_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.file_label.setText(f"<i>{Translations['preview.no_selection']}</i>")
self.file_label.set_file_path("")
self.file_label.set_file_path(Path())
self.file_label.setCursor(Qt.CursorShape.ArrowCursor)
self.dimensions_label.setText("")
self.dimensions_label.setHidden(True)
Expand Down Expand Up @@ -264,6 +264,6 @@ def update_multi_selection(self, count: int):
self.file_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.file_label.setText(Translations.format("preview.multiple_selection", count=count))
self.file_label.setCursor(Qt.CursorShape.ArrowCursor)
self.file_label.set_file_path("")
self.file_label.set_file_path(Path())
self.dimensions_label.setText("")
self.dimensions_label.setHidden(True)
69 changes: 27 additions & 42 deletions src/tagstudio/qt/mixed/item_thumb.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio


import time
from enum import Enum
from functools import wraps
from pathlib import Path
Expand All @@ -21,13 +20,13 @@
from tagstudio.core.media_types import MediaCategories, MediaType
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.platform_strings import open_file_str, trash_term
from tagstudio.qt.previews.renderer import ThumbRenderer
from tagstudio.qt.translations import Translations
from tagstudio.qt.utils.file_opener import FileOpenerHelper
from tagstudio.qt.views.layouts.flow_layout import FlowWidget
from tagstudio.qt.views.thumb_button import ThumbButton

if TYPE_CHECKING:
from tagstudio.core.library.alchemy.models import Entry
from tagstudio.qt.ts_qt import QtDriver

logger = structlog.get_logger(__name__)
Expand Down Expand Up @@ -66,8 +65,6 @@ def wrapper(self, *args, **kwargs):
class ItemThumb(FlowWidget):
"""The thumbnail widget for a library item (Entry, Collation, Tag Group, etc.)."""

update_cutoff: float = time.time()

collation_icon_128: Image.Image = Image.open(
str(Path(__file__).parents[2] / "resources/qt/images/collation_icon_128.png")
)
Expand Down Expand Up @@ -119,6 +116,8 @@ def __init__(
self.mode: ItemType | None = mode
self.driver = driver
self.item_id: int = -1
self.item_path: Path | None = None
self.rendered_path: Path | None = None
self.thumb_size: tuple[int, int] = thumb_size
self.show_filename_label: bool = show_filename_label
self.label_height = 12
Expand Down Expand Up @@ -195,20 +194,11 @@ def __init__(
self.thumb_layout.addWidget(self.bottom_container)

self.thumb_button = ThumbButton(self.thumb_container, thumb_size)
self.renderer = ThumbRenderer(driver, self.lib)
self.renderer.updated.connect(
lambda timestamp, image, size, filename: (
self.update_thumb(image, timestamp),
self.update_size(size, timestamp),
self.set_filename_text(filename, timestamp),
self.set_extension(filename, timestamp),
)
)
self.thumb_button.setFlat(True)
self.thumb_button.setLayout(self.thumb_layout)
self.thumb_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)

self.opener = FileOpenerHelper("")
self.opener = FileOpenerHelper(Path())
open_file_action = QAction(Translations["file.open_file"], self)
open_file_action.triggered.connect(self.opener.open_file)
open_explorer_action = QAction(open_file_str(), self)
Expand All @@ -219,6 +209,12 @@ def __init__(
self,
)

def _on_delete():
if self.item_id != -1 and self.item_path is not None:
self.driver.delete_files_callback(self.item_path, self.item_id)

self.delete_action.triggered.connect(lambda checked=False: _on_delete())

self.thumb_button.addAction(open_file_action)
self.thumb_button.addAction(open_explorer_action)
self.thumb_button.addAction(self.delete_action)
Expand Down Expand Up @@ -338,7 +334,7 @@ def set_mode(self, mode: ItemType | None) -> None:
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=True)
self.thumb_button.unsetCursor()
self.thumb_button.setHidden(True)
elif mode == ItemType.ENTRY and self.mode != ItemType.ENTRY:
elif mode == ItemType.ENTRY:
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=False)
self.thumb_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.thumb_button.setHidden(False)
Expand All @@ -348,7 +344,7 @@ def set_mode(self, mode: ItemType | None) -> None:
self.count_badge.setStyleSheet(ItemThumb.small_text_style)
self.count_badge.setHidden(True)
self.ext_badge.setHidden(True)
elif mode == ItemType.COLLATION and self.mode != ItemType.COLLATION:
elif mode == ItemType.COLLATION:
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=False)
self.thumb_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.thumb_button.setHidden(False)
Expand All @@ -357,7 +353,7 @@ def set_mode(self, mode: ItemType | None) -> None:
self.count_badge.setStyleSheet(ItemThumb.med_text_style)
self.count_badge.setHidden(False)
self.item_type_badge.setHidden(False)
elif mode == ItemType.TAG_GROUP and self.mode != ItemType.TAG_GROUP:
elif mode == ItemType.TAG_GROUP:
self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=False)
self.thumb_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.thumb_button.setHidden(False)
Expand All @@ -366,15 +362,12 @@ def set_mode(self, mode: ItemType | None) -> None:
self.item_type_badge.setHidden(False)
self.mode = mode

def set_extension(self, filename: Path, timestamp: float | None = None) -> None:
if timestamp and timestamp < ItemThumb.update_cutoff:
return

def set_extension(self, filename: Path) -> None:
ext = filename.suffix.lower()
if ext and ext.startswith(".") is False:
ext = "." + ext
media_types: set[MediaType] = MediaCategories.get_types(ext)
if (
if ext and (
not MediaCategories.is_ext_in_category(ext, MediaCategories.IMAGE_TYPES)
or MediaCategories.is_ext_in_category(ext, MediaCategories.IMAGE_RAW_TYPES)
or MediaCategories.is_ext_in_category(ext, MediaCategories.IMAGE_VECTOR_TYPES)
Expand Down Expand Up @@ -408,11 +401,7 @@ def set_count(self, count: str) -> None:
self.ext_badge.setHidden(True)
self.count_badge.setHidden(True)

def set_filename_text(self, filename: Path, timestamp: float | None = None):
if timestamp and timestamp < ItemThumb.update_cutoff:
return

self.set_item_path(filename)
def set_filename_text(self, filename: Path):
self.file_label.setText(str(filename.name))

def set_filename_visibility(self, set_visible: bool):
Expand All @@ -430,48 +419,44 @@ def set_filename_visibility(self, set_visible: bool):
self.setFixedHeight(self.thumb_size[1])
self.show_filename_label = set_visible

def update_thumb(self, image: QPixmap | None = None, timestamp: float | None = None):
def update_thumb(self, image: QPixmap | None = None, file_path: Path | None = None):
"""Update attributes of a thumbnail element."""
if timestamp and timestamp < ItemThumb.update_cutoff:
return

self.thumb_button.setIcon(image if image else QPixmap())
self.rendered_path = file_path

def update_size(self, size: QSize, timestamp: float | None = None):
def update_size(self, size: QSize):
"""Updates attributes of a thumbnail element.

Args:
timestamp (float | None): The UTC timestamp for when this call was
originally dispatched. Used to skip outdated jobs.

size (QSize): The new thumbnail size to set.
"""
if timestamp and timestamp < ItemThumb.update_cutoff:
return

self.thumb_size = size.width(), size.height()
self.thumb_button.setIconSize(size)
self.thumb_button.setMinimumSize(size)
self.thumb_button.setMaximumSize(size)

def set_item(self, entry: "Entry"):
self.set_item_id(entry.id)
self.set_item_path(entry.path)

def set_item_id(self, item_id: int):
self.item_id = item_id

def set_item_path(self, path: Path | str):
def set_item_path(self, path: Path):
"""Set the absolute filepath for the item. Used for locating on disk."""
self.item_path = path
self.opener.set_filepath(path)

def assign_badge(self, badge_type: BadgeType, value: bool) -> None:
mode = self.mode
# blank mode to avoid recursive badge updates
self.mode = None
badge = self.badges[badge_type]
self.badge_active[badge_type] = value
if badge.isChecked() != value:
self.mode = None
badge.setChecked(value)
badge.setHidden(not value)

self.mode = mode
self.mode = mode

def show_check_badges(self, show: bool):
if self.mode != ItemType.TAG_GROUP:
Expand Down
9 changes: 8 additions & 1 deletion src/tagstudio/qt/mixed/settings_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,17 @@ def __build_global_settings(self):

def on_page_size_changed():
text = self.page_size_line_edit.text()
if not text.isdigit() or int(text) < 1:
if not text.isdigit():
self.page_size_line_edit.setText(str(self.driver.settings.page_size))

self.page_size_line_edit.editingFinished.connect(on_page_size_changed)
form_layout.addRow(Translations["settings.page_size"], self.page_size_line_edit)

# Infinite Scrolling
self.infinite_scroll = QCheckBox()
self.infinite_scroll.setChecked(self.driver.settings.infinite_scroll)
form_layout.addRow(Translations["settings.infinite_scroll"], self.infinite_scroll)

# Show Filepath
self.filepath_combobox = QComboBox()
for k in SettingsPanel.filepath_option_map:
Expand Down Expand Up @@ -288,6 +293,7 @@ def get_settings(self) -> dict[str, Any]: # pyright: ignore[reportExplicitAny]
"autoplay": self.autoplay_checkbox.isChecked(),
"show_filenames_in_grid": self.show_filenames_checkbox.isChecked(),
"page_size": int(self.page_size_line_edit.text()),
"infinite_scroll": self.infinite_scroll.isChecked(),
"show_filepath": self.filepath_combobox.currentData(),
"theme": self.theme_combobox.currentData(),
"tag_click_action": self.tag_click_action_combobox.currentData(),
Expand All @@ -307,6 +313,7 @@ def update_settings(self, driver: "QtDriver"):
driver.settings.thumb_cache_size = settings["thumb_cache_size"]
driver.settings.show_filenames_in_grid = settings["show_filenames_in_grid"]
driver.settings.page_size = settings["page_size"]
driver.settings.infinite_scroll = settings["infinite_scroll"]
driver.settings.show_filepath = settings["show_filepath"]
driver.settings.theme = settings["theme"]
driver.settings.tag_click_action = settings["tag_click_action"]
Expand Down
6 changes: 2 additions & 4 deletions src/tagstudio/qt/previews/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@
from tagstudio.qt.resource_manager import ResourceManager

if TYPE_CHECKING:
from tagstudio.core.library.alchemy.library import Library
from tagstudio.qt.ts_qt import QtDriver

ImageFile.LOAD_TRUNCATED_IMAGES = True
Expand Down Expand Up @@ -118,11 +117,10 @@ class ThumbRenderer(QObject):
updated_ratio = Signal(float)
cached_img_ext: str = ".webp"

def __init__(self, driver: "QtDriver", library: "Library") -> None:
def __init__(self, driver: "QtDriver") -> None:
"""Initialize the class."""
super().__init__()
self.driver = driver
self.lib = library

settings_res = self.driver.settings.cached_thumb_resolution
self.cached_img_res = (
Expand Down Expand Up @@ -1509,7 +1507,7 @@ def fetch_cached_image(file_name: Path):
image
and Ignore.compiled_patterns
and Ignore.compiled_patterns.match(
filepath.relative_to(unwrap(self.lib.library_dir))
filepath.relative_to(unwrap(self.driver.lib.library_dir))
)
):
image = render_ignored((adj_size, adj_size), pixel_ratio, image)
Expand Down
Loading