Skip to content
Open
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
5 changes: 3 additions & 2 deletions src/tagstudio/core/library/alchemy/enums.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import enum
import random
from dataclasses import dataclass, replace
from dataclasses import dataclass, field, replace
from pathlib import Path

import structlog
Expand Down Expand Up @@ -78,8 +78,9 @@ class BrowsingState:
"""Represent a state of the Library grid view."""

page_index: int = 0
page_positions: dict[int, int] = field(default_factory=dict)
sorting_mode: SortingModeEnum = SortingModeEnum.DATE_ADDED
ascending: bool = True
ascending: bool = False
random_seed: float = 0

show_hidden_entries: bool = False
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

import structlog

from tagstudio.core.library.alchemy.enums import BrowsingState
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.alchemy.models import Entry
from tagstudio.core.utils.types import unwrap

logger = structlog.get_logger()

Expand All @@ -28,7 +28,7 @@ def refresh_dupe_files(self, results_filepath: str | Path):
A duplicate file is defined as an identical or near-identical file as determined
by a DupeGuru results file.
"""
library_dir = self.library.library_dir
library_dir = unwrap(self.library.library_dir)
if not isinstance(results_filepath, Path):
results_filepath = Path(results_filepath)

Expand All @@ -51,16 +51,12 @@ def refresh_dupe_files(self, results_filepath: str | Path):
# The file is not in the library directory
continue

results = self.library.search_library(
BrowsingState.from_path(path_relative), 500
)
entries = self.library.get_entries(results.ids)

if not results:
entry = self.library.get_entry_full_by_path(path_relative)
if entry is None:
# file not in library
continue

files.append(entries[0])
files.append(entry)

if not len(files) > 1:
# only one file in the group, nothing to do
Expand Down
12 changes: 5 additions & 7 deletions src/tagstudio/qt/mixed/item_thumb.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,13 +496,11 @@ def toggle_item_tag(
toggle_value: bool,
tag_id: int,
):
if entry_id in self.driver.selected:
if len(self.driver.selected) == 1:
self.driver.main_window.preview_panel.field_containers_widget.update_toggled_tag(
tag_id, toggle_value
)
else:
pass
selected = self.driver._selected
if len(selected) == 1 and entry_id in selected:
self.driver.main_window.preview_panel.field_containers_widget.update_toggled_tag(
tag_id, toggle_value
)

@override
def mouseMoveEvent(self, event: QMouseEvent) -> None: # type: ignore[misc]
Expand Down
116 changes: 30 additions & 86 deletions src/tagstudio/qt/thumb_grid_layout.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import math
import time
from collections.abc import Iterable
from pathlib import Path
from typing import TYPE_CHECKING, Any, override

from PySide6.QtCore import QPoint, QRect, QSize
from PySide6.QtCore import QPoint, QRect, QSize, Signal
from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import QLayout, QLayoutItem, QScrollArea

Expand All @@ -19,17 +20,16 @@


class ThumbGridLayout(QLayout):
# Id of first visible entry
visible_changed = Signal(int)

def __init__(self, driver: "QtDriver", scroll_area: QScrollArea) -> None:
super().__init__(None)
self.driver: QtDriver = driver
self.scroll_area: QScrollArea = scroll_area

self._item_thumbs: list[ItemThumb] = []
self._items: list[QLayoutItem] = []
# Entry.id -> _entry_ids[index]
self._selected: dict[int, int] = {}
# _entry_ids[index]
self._last_selected: int | None = None

self._entry_ids: list[int] = []
self._entries: dict[int, Entry] = {}
Expand All @@ -47,12 +47,14 @@ def __init__(self, driver: "QtDriver", scroll_area: QScrollArea) -> None:
# _entry_ids[StartIndex:EndIndex]
self._last_page_update: tuple[int, int] | None = None

self._scroll_to: int | None = None

def scroll_to(self, entry_id: int):
self._scroll_to = entry_id

def set_entries(self, entry_ids: list[int]):
self.scroll_area.verticalScrollBar().setValue(0)

self._selected.clear()
self._last_selected = None

self._entry_ids = entry_ids
self._entries.clear()
self._tag_entries.clear()
Expand Down Expand Up @@ -83,90 +85,20 @@ def set_entries(self, entry_ids: list[int]):

self._last_page_update = None

def select_all(self):
self._selected.clear()
for index, id in enumerate(self._entry_ids):
self._selected[id] = index
self._last_selected = index

for entry_id in self._entry_items:
self._set_selected(entry_id)

def select_inverse(self):
selected = {}
for index, id in enumerate(self._entry_ids):
if id not in self._selected:
selected[id] = index
self._last_selected = index

for id in self._selected:
if id not in selected:
self._set_selected(id, value=False)
for id in selected:
self._set_selected(id)

self._selected = selected

def select_entry(self, entry_id: int):
if entry_id in self._selected:
index = self._selected.pop(entry_id)
if index == self._last_selected:
self._last_selected = None
self._set_selected(entry_id, value=False)
else:
try:
index = self._entry_ids.index(entry_id)
except ValueError:
index = -1

self._selected[entry_id] = index
self._last_selected = index
self._set_selected(entry_id)

def select_to_entry(self, entry_id: int):
index = self._entry_ids.index(entry_id)
if len(self._selected) == 0:
self.select_entry(entry_id)
return
if self._last_selected is None:
self._last_selected = min(self._selected.values(), key=lambda i: abs(index - i))

start = self._last_selected
self._last_selected = index
def update_selected(self):
for item_thumb in self._item_thumbs:
value = item_thumb.item_id in self.driver._selected
item_thumb.thumb_button.set_selected(value)

if start > index:
index, start = start, index
else:
index += 1

for i in range(start, index):
entry_id = self._entry_ids[i]
self._selected[entry_id] = i
self._set_selected(entry_id)

def clear_selected(self):
for entry_id in self._entry_items:
self._set_selected(entry_id, value=False)

self._selected.clear()
self._last_selected = None

def _set_selected(self, entry_id: int, value: bool = True):
if entry_id not in self._entry_items:
return
index = self._entry_items[entry_id]
if index < len(self._item_thumbs):
self._item_thumbs[index].thumb_button.set_selected(value)

def add_tags(self, entry_ids: list[int], tag_ids: list[int]):
def add_tags(self, entry_ids: Iterable[int], tag_ids: Iterable[int]):
for tag_id in tag_ids:
self._tag_entries.setdefault(tag_id, set()).update(entry_ids)

def remove_tags(self, entry_ids: list[int], tag_ids: list[int]):
def remove_tags(self, entry_ids: Iterable[int], tag_ids: Iterable[int]):
for tag_id in tag_ids:
self._tag_entries.setdefault(tag_id, set()).difference_update(entry_ids)

def _fetch_entries(self, ids: list[int]):
def _fetch_entries(self, ids: Iterable[int]):
ids = [id for id in ids if id not in self._entries]
entries = self.driver.lib.get_entries(ids)
for entry in entries:
Expand Down Expand Up @@ -263,12 +195,24 @@ def setGeometry(self, arg__1: QRect) -> None:
per_row, width_offset, height_offset = self._size(rect.right())
view_height = self.parentWidget().parentWidget().height()
offset = self.scroll_area.verticalScrollBar().value()
if self._scroll_to is not None:
try:
index = self._entry_ids.index(self._scroll_to)
value = (index // per_row) * height_offset
self.scroll_area.verticalScrollBar().setMaximum(value)
self.scroll_area.verticalScrollBar().setSliderPosition(value)
offset = value
except ValueError:
pass
self._scroll_to = None

visible_rows = math.ceil((view_height + (offset % height_offset)) / height_offset)
offset = int(offset / height_offset)
start = offset * per_row
end = start + (visible_rows * per_row)

self.visible_changed.emit(self._entry_ids[start])

# Load closest off screen rows
start -= per_row * 3
end += per_row * 3
Expand Down Expand Up @@ -363,7 +307,7 @@ def setGeometry(self, arg__1: QRect) -> None:
entry_id = self._entry_ids[i]
item_index = self._entry_items[entry_id]
item_thumb = self._item_thumbs[item_index]
item_thumb.thumb_button.set_selected(entry_id in self._selected)
item_thumb.thumb_button.set_selected(entry_id in self.driver._selected)

item_thumb.assign_badge(BadgeType.ARCHIVED, entry_id in self._tag_entries[TAG_ARCHIVED])
item_thumb.assign_badge(BadgeType.FAVORITE, entry_id in self._tag_entries[TAG_FAVORITE])
Expand Down
Loading