mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-08-20 19:12:34 +02:00
build: bump python version to 3.14 (#1485)
* build: bump python to 3.14 * ci: bump python to 3.14, update docs * build: remove version condition for audioop-lts * chore: formatting pass * fix: disallow 3.15.0 * build: bump pyside version to 6.11.2 * fix(ui): allow valid None value for cancelButtonText to hide button
This commit is contained in:
committed by
GitHub
parent
112b27f059
commit
f5a30489d7
@@ -18,7 +18,7 @@ runs:
|
||||
if: inputs.skip-setup != 'true'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.12'
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Setup uv install
|
||||
if: inputs.skip-setup != 'true'
|
||||
|
||||
+3
-3
@@ -16,7 +16,7 @@ If you wish to develop for TagStudio, you'll need to create a development enviro
|
||||
|
||||
## Installing Python
|
||||
|
||||
Python [3.12](https://www.python.org/downloads) is required to develop for TagStudio. Any version matching "Python 3.12.x" should work, with "x" being any number. Alternatively you can use a tool such as [pyenv](https://github.com/pyenv/pyenv) to install this version of Python without affecting any existing Python installations on your system. Tools such as [uv](#installing-with-uv) can also install Python versions.
|
||||
Python [3.14](https://www.python.org/downloads) is required to develop for TagStudio. Any version matching "Python 3.14.x" should work, with "x" being any number. Alternatively you can use a tool such as [pyenv](https://github.com/pyenv/pyenv) to install this version of Python without affecting any existing Python installations on your system. Tools such as [uv](#installing-with-uv) can also install Python versions.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! info "Python Aliases"
|
||||
@@ -35,8 +35,8 @@ python --version
|
||||
If you choose to install Python using pyenv, please refer to the following instructions:
|
||||
|
||||
1. Follow pyenv's [install instructions](https://github.com/pyenv/pyenv/?tab=readme-ov-file#installation) for your system.
|
||||
2. Install the appropriate Python version with pyenv by running `pyenv install 3.12` (This will **not** mess with your existing Python installation).
|
||||
3. Navigate to the repository root folder in your terminal and run `pyenv local 3.12`. You could alternatively use `pyenv shell 3.12` or `pyenv global 3.12` instead to set the Python version for the current terminal session or the entire system respectively, however using `local` is recommended.
|
||||
2. Install the appropriate Python version with pyenv by running `pyenv install 3.14` (This will **not** mess with your existing Python installation).
|
||||
3. Navigate to the repository root folder in your terminal and run `pyenv local 3.14`. You could alternatively use `pyenv shell 3.14` or `pyenv global 3.14` instead to set the Python version for the current terminal session or the entire system respectively, however using `local` is recommended.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ TagStudio has builds for :fontawesome-brands-windows: **Windows**, :fontawesome-
|
||||
|
||||
### :fontawesome-brands-python: Installing with PIP
|
||||
|
||||
TagStudio is installable via [PIP](https://pip.pypa.io/). Note that since we don't currently distribute on PyPI, the repository needs to be cloned and installed locally. Make sure you have Python 3.12 and PIP installed if you choose to install using this method.
|
||||
TagStudio is installable via [PIP](https://pip.pypa.io/). Note that since we don't currently distribute on PyPI, the repository needs to be cloned and installed locally. Make sure you have Python 3.14 and PIP installed if you choose to install using this method.
|
||||
|
||||
The repository can be cloned/downloaded via `git` in your terminal, or by downloading the zip file from the "Code" button on the [repository page](https://github.com/TagStudioDev/TagStudio).
|
||||
|
||||
|
||||
+4
-4
@@ -12,9 +12,9 @@ description = "A User-Focused Photo & File Management System."
|
||||
version = "9.6.4"
|
||||
license = "GPL-3.0-only"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12,<3.14"
|
||||
requires-python = ">=3.14,<3.15"
|
||||
dependencies = [
|
||||
"audioop-lts; python_version >= '3.13'",
|
||||
"audioop-lts~=0.2.2",
|
||||
"chardet~=5.2",
|
||||
"ffmpeg-python~=0.2",
|
||||
"humanfriendly==10.*",
|
||||
@@ -22,12 +22,12 @@ dependencies = [
|
||||
"numpy~=2.2",
|
||||
"opencv_python~=4.11",
|
||||
"Pillow>=10.2,<12",
|
||||
"pillow-heif~=0.22",
|
||||
"pillow-heif~=1.5.0",
|
||||
"pillow-jxl-plugin~=1.3",
|
||||
"py7zr~=1.1.3",
|
||||
"pydantic~=2.10",
|
||||
"pydub~=0.25",
|
||||
"PySide6==6.8.0.*",
|
||||
"PySide6==6.11.2",
|
||||
"rarfile==4.2",
|
||||
"rawpy~=0.27",
|
||||
"Send2Trash>=1.8,<3",
|
||||
|
||||
@@ -57,7 +57,7 @@ class TagColorEnum(enum.IntEnum):
|
||||
OLIVE = 37
|
||||
|
||||
@staticmethod
|
||||
def get_color_from_str(color_name: str) -> "TagColorEnum":
|
||||
def get_color_from_str(color_name: str) -> TagColorEnum:
|
||||
for color in TagColorEnum:
|
||||
if color.name == color_name.upper().replace(" ", "_"):
|
||||
return color
|
||||
@@ -99,17 +99,15 @@ class BrowsingState:
|
||||
return Parser(self.query).parse()
|
||||
|
||||
@classmethod
|
||||
def show_all(cls) -> "BrowsingState":
|
||||
def show_all(cls) -> BrowsingState:
|
||||
return BrowsingState()
|
||||
|
||||
@classmethod
|
||||
def from_search_query(cls, search_query: str) -> "BrowsingState":
|
||||
def from_search_query(cls, search_query: str) -> BrowsingState:
|
||||
return cls(query=search_query)
|
||||
|
||||
@classmethod
|
||||
def from_tag_id(
|
||||
cls, tag_id: int | str, state: "BrowsingState | None" = None
|
||||
) -> "BrowsingState":
|
||||
def from_tag_id(cls, tag_id: int | str, state: BrowsingState | None = None) -> BrowsingState:
|
||||
"""Create and return a BrowsingState object given a tag ID.
|
||||
|
||||
Args:
|
||||
@@ -124,35 +122,35 @@ class BrowsingState:
|
||||
return cls(query=f"tag_id:{str(tag_id)}")
|
||||
|
||||
@classmethod
|
||||
def from_path(cls, path: Path | str) -> "BrowsingState":
|
||||
def from_path(cls, path: Path | str) -> BrowsingState:
|
||||
return cls(query=f'path:"{str(path).strip()}"')
|
||||
|
||||
@classmethod
|
||||
def from_mediatype(cls, mediatype: str) -> "BrowsingState":
|
||||
def from_mediatype(cls, mediatype: str) -> BrowsingState:
|
||||
return cls(query=f"mediatype:{mediatype}")
|
||||
|
||||
@classmethod
|
||||
def from_filetype(cls, filetype: str) -> "BrowsingState":
|
||||
def from_filetype(cls, filetype: str) -> BrowsingState:
|
||||
return cls(query=f"filetype:{filetype}")
|
||||
|
||||
@classmethod
|
||||
def from_tag_name(cls, tag_name: str) -> "BrowsingState":
|
||||
def from_tag_name(cls, tag_name: str) -> BrowsingState:
|
||||
return cls(query=f'tag:"{tag_name}"')
|
||||
|
||||
def with_page_index(self, index: int) -> "BrowsingState":
|
||||
def with_page_index(self, index: int) -> BrowsingState:
|
||||
return replace(self, page_index=index)
|
||||
|
||||
def with_sorting_mode(self, mode: SortingModeEnum) -> "BrowsingState":
|
||||
def with_sorting_mode(self, mode: SortingModeEnum) -> BrowsingState:
|
||||
seed = self.random_seed
|
||||
if mode == SortingModeEnum.RANDOM:
|
||||
seed = random.random()
|
||||
return replace(self, sorting_mode=mode, random_seed=seed)
|
||||
|
||||
def with_sorting_direction(self, ascending: bool) -> "BrowsingState":
|
||||
def with_sorting_direction(self, ascending: bool) -> BrowsingState:
|
||||
return replace(self, ascending=ascending)
|
||||
|
||||
def with_search_query(self, search_query: str) -> "BrowsingState":
|
||||
def with_search_query(self, search_query: str) -> BrowsingState:
|
||||
return replace(self, query=search_query)
|
||||
|
||||
def with_show_hidden_entries(self, show_hidden_entries: bool) -> "BrowsingState":
|
||||
def with_show_hidden_entries(self, show_hidden_entries: bool) -> BrowsingState:
|
||||
return replace(self, show_hidden_entries=show_hidden_entries)
|
||||
|
||||
@@ -41,7 +41,7 @@ class TagAlias(Base):
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(nullable=False)
|
||||
tag_id: Mapped[int] = mapped_column(ForeignKey("tags.id"))
|
||||
tag: Mapped["Tag"] = relationship(back_populates="aliases")
|
||||
tag: Mapped[Tag] = relationship(back_populates="aliases")
|
||||
|
||||
def __init__(self, name: str, tag_id: int | None = None):
|
||||
self.name = name
|
||||
@@ -97,14 +97,14 @@ class Tag(Base):
|
||||
is_hidden: Mapped[bool]
|
||||
icon: Mapped[str | None]
|
||||
aliases: Mapped[set[TagAlias]] = relationship(back_populates="tag")
|
||||
parent_tags: Mapped[set["Tag"]] = relationship(
|
||||
parent_tags: Mapped[set[Tag]] = relationship(
|
||||
secondary=TagParent.__tablename__,
|
||||
primaryjoin="Tag.id == TagParent.child_id",
|
||||
secondaryjoin="Tag.id == TagParent.parent_id",
|
||||
back_populates="parent_tags",
|
||||
)
|
||||
disambiguation_id: Mapped[int | None]
|
||||
category_exclusions: Mapped[set["Tag"]] = relationship(
|
||||
category_exclusions: Mapped[set[Tag]] = relationship(
|
||||
secondary=CategoryExclusion.__tablename__,
|
||||
primaryjoin="Tag.id == CategoryExclusion.tag_id",
|
||||
secondaryjoin="Tag.id == CategoryExclusion.category_id",
|
||||
@@ -140,14 +140,14 @@ class Tag(Base):
|
||||
id: int | None = None,
|
||||
shorthand: str | None = None,
|
||||
aliases: set[TagAlias] | None = None,
|
||||
parent_tags: set["Tag"] | None = None,
|
||||
parent_tags: set[Tag] | None = None,
|
||||
icon: str | None = None,
|
||||
color_namespace: str | None = None,
|
||||
color_slug: str | None = None,
|
||||
disambiguation_id: int | None = None,
|
||||
is_category: bool = False,
|
||||
is_hidden: bool = False,
|
||||
category_exclusions: set["Tag"] | None = None,
|
||||
category_exclusions: set[Tag] | None = None,
|
||||
):
|
||||
self.name = name
|
||||
self.aliases = aliases or set()
|
||||
@@ -181,16 +181,16 @@ class Tag(Base):
|
||||
return False
|
||||
return self.id == value.id
|
||||
|
||||
def __lt__(self, other: "Tag") -> bool:
|
||||
def __lt__(self, other: Tag) -> bool:
|
||||
return self.name < other.name
|
||||
|
||||
def __le__(self, other: "Tag") -> bool:
|
||||
def __le__(self, other: Tag) -> bool:
|
||||
return self.name <= other.name
|
||||
|
||||
def __gt__(self, other: "Tag") -> bool:
|
||||
def __gt__(self, other: Tag) -> bool:
|
||||
return self.name > other.name
|
||||
|
||||
def __ge__(self, other: "Tag") -> bool:
|
||||
def __ge__(self, other: Tag) -> bool:
|
||||
return self.name >= other.name
|
||||
|
||||
|
||||
|
||||
@@ -861,7 +861,7 @@ class Library:
|
||||
self.files_not_in_library,
|
||||
key=lambda t: -(self.library_dir / t).stat().st_ctime,
|
||||
)
|
||||
except (FileExistsError, FileNotFoundError):
|
||||
except FileExistsError, FileNotFoundError:
|
||||
print(
|
||||
"[LIBRARY] [ERROR] Couldn't sort files, some were moved during the scanning/sorting process."
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ class ConstraintType(Enum):
|
||||
Special = 5
|
||||
|
||||
@staticmethod
|
||||
def from_string(text: str) -> "ConstraintType | None":
|
||||
def from_string(text: str) -> ConstraintType | None:
|
||||
return {
|
||||
"tag": ConstraintType.Tag,
|
||||
"tag_id": ConstraintType.TagID,
|
||||
@@ -28,7 +28,7 @@ class ConstraintType(Enum):
|
||||
|
||||
|
||||
class AST:
|
||||
parent: "AST | None" = None
|
||||
parent: AST | None = None
|
||||
|
||||
@override
|
||||
def __str__(self):
|
||||
@@ -65,9 +65,9 @@ class ORList(AST):
|
||||
class Constraint(AST):
|
||||
type: ConstraintType
|
||||
value: str
|
||||
properties: list["Property"]
|
||||
properties: list[Property]
|
||||
|
||||
def __init__(self, type: ConstraintType, value: str, properties: list["Property"]) -> None:
|
||||
def __init__(self, type: ConstraintType, value: str, properties: list[Property]) -> None:
|
||||
super().__init__()
|
||||
for prop in properties:
|
||||
prop.parent = self
|
||||
|
||||
@@ -39,11 +39,11 @@ class Token:
|
||||
self.end = end
|
||||
|
||||
@staticmethod
|
||||
def from_type(type: TokenType, pos: int) -> "Token":
|
||||
def from_type(type: TokenType, pos: int) -> Token:
|
||||
return Token(type, None, pos, pos)
|
||||
|
||||
@staticmethod
|
||||
def EOF(pos: int) -> "Token": # noqa: N802
|
||||
def EOF(pos: int) -> Token: # noqa: N802
|
||||
return Token.from_type(TokenType.EOF, pos)
|
||||
|
||||
@override
|
||||
|
||||
@@ -58,5 +58,5 @@ def format_duration(duration: int | float) -> str:
|
||||
hours, seconds = divmod(seconds, 3600)
|
||||
minutes, seconds = divmod(seconds, 60)
|
||||
return f"{hours}:{minutes:02}:{seconds:02}" if hours else f"{minutes}:{seconds:02}"
|
||||
except (OverflowError, ValueError):
|
||||
except OverflowError, ValueError:
|
||||
return "-:--"
|
||||
|
||||
@@ -86,7 +86,7 @@ class Translator:
|
||||
def __format(self, text: str, **kwargs: ...) -> str:
|
||||
try:
|
||||
return text.format(**kwargs)
|
||||
except (KeyError, ValueError):
|
||||
except KeyError, ValueError:
|
||||
logger.error(
|
||||
"[Translations] Error while formatting translation.",
|
||||
text=text,
|
||||
|
||||
@@ -52,7 +52,7 @@ class TarFile:
|
||||
def read(self, name: str) -> bytes:
|
||||
return unwrap(self.tar.extractfile(name)).read()
|
||||
|
||||
def __enter__(self) -> "TarFile":
|
||||
def __enter__(self) -> TarFile:
|
||||
self.tar = tarfile.open(name=self.filepath, mode=self.mode).__enter__()
|
||||
return self
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ def audio_album_thumb(filepath: Path, ext: str) -> Image.Image | None:
|
||||
image = artwork
|
||||
except (
|
||||
FileNotFoundError,
|
||||
id3.ID3NoHeaderError, # pyright: ignore[reportPrivateImportUsage]
|
||||
id3.ID3NoHeaderError,
|
||||
mp4.MP4MetadataError,
|
||||
mp4.MP4StreamInfoError,
|
||||
MutagenError,
|
||||
|
||||
@@ -88,7 +88,7 @@ class AppSettings(BaseModel):
|
||||
loaded_from: Path = Field(default=DEFAULT_GLOBAL_SETTINGS_PATH, exclude=True)
|
||||
|
||||
@staticmethod
|
||||
def read_settings(path: Path = DEFAULT_GLOBAL_SETTINGS_PATH) -> "AppSettings":
|
||||
def read_settings(path: Path = DEFAULT_GLOBAL_SETTINGS_PATH) -> AppSettings:
|
||||
if path.exists():
|
||||
with open(path) as file:
|
||||
filecontents = file.read()
|
||||
|
||||
@@ -77,4 +77,4 @@ class AutofillLineEdit(QLineEdit):
|
||||
# Filter out icon action(s)
|
||||
if action.text():
|
||||
menu.addAction(action)
|
||||
menu.exec(self.mapToGlobal(pos)) # pyright: ignore[reportArgumentType]
|
||||
menu.exec(self.mapToGlobal(pos))
|
||||
|
||||
@@ -11,6 +11,7 @@ from PySide6.QtWidgets import QWidget
|
||||
|
||||
from tagstudio.core.library.alchemy.fields import BaseFieldTemplate
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.i18n.translations import Translations
|
||||
from tagstudio.qt.app_settings import AppSettings
|
||||
from tagstudio.qt.controllers.edit_field_template_modal import EditFieldTemplateModal
|
||||
@@ -155,6 +156,7 @@ class FieldSuggestBox(SuggestBox[BaseFieldTemplate]):
|
||||
widget.setHidden(True)
|
||||
self.layout().content_layout.addWidget(widget)
|
||||
|
||||
widget_: QWidget = self.layout().content_layout.itemAt(index).widget()
|
||||
item = unwrap(self.layout().content_layout.itemAt(index))
|
||||
widget_: QWidget = unwrap(item.widget())
|
||||
assert isinstance(widget_, UnderlinedWidget)
|
||||
return widget_
|
||||
|
||||
@@ -11,6 +11,7 @@ from PySide6.QtWidgets import QMessageBox, QWidget
|
||||
|
||||
from tagstudio.core.library.alchemy.fields import BaseFieldTemplate
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.i18n.translations import Translations
|
||||
from tagstudio.qt.controllers.edit_field_template_modal import EditFieldTemplateModal
|
||||
from tagstudio.qt.controllers.field_template_widget import FieldTemplateWidget
|
||||
@@ -173,6 +174,7 @@ class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]):
|
||||
pad_field_template_widget.setHidden(True)
|
||||
self.layout().scroll_layout.addWidget(pad_field_template_widget)
|
||||
|
||||
field_template_widget: QWidget = self.layout().scroll_layout.itemAt(index).widget()
|
||||
item = unwrap(self.layout().scroll_layout.itemAt(index))
|
||||
field_template_widget: QWidget = unwrap(item.widget())
|
||||
assert isinstance(field_template_widget, FieldTemplateWidget)
|
||||
return field_template_widget
|
||||
|
||||
@@ -24,7 +24,7 @@ logger = structlog.get_logger(__name__)
|
||||
|
||||
# TODO: Use newer MVC style guidelines
|
||||
class FixIgnoredEntriesModal(FixIgnoredEntriesModalView):
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
def __init__(self, library: Library, driver: QtDriver):
|
||||
super().__init__(library, driver)
|
||||
self.tracker = IgnoredRegistry(self.lib)
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ class _ItemMode(IntEnum):
|
||||
|
||||
|
||||
class Inspector(QWidget):
|
||||
def __init__(self, driver: "QtDriver") -> None:
|
||||
def __init__(self, driver: QtDriver) -> None:
|
||||
super().__init__()
|
||||
self._driver = driver
|
||||
self._lib = self._driver.lib
|
||||
|
||||
@@ -33,7 +33,7 @@ logger = structlog.get_logger(__name__)
|
||||
|
||||
# TODO: Use newer MVC style guidelines
|
||||
class LibraryInfoWindow(LibraryInfoWindowView):
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
def __init__(self, library: Library, driver: QtDriver):
|
||||
super().__init__(library, driver)
|
||||
|
||||
# Statistics Buttons
|
||||
|
||||
@@ -458,7 +458,7 @@ class MainWindow(QMainWindow):
|
||||
(Translations["home.thumbnail_size.mini"], 76),
|
||||
]
|
||||
|
||||
def __init__(self, driver: "QtDriver", parent: QWidget | None = None) -> None:
|
||||
def __init__(self, driver: QtDriver, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent)
|
||||
self.rm = ResourceManager()
|
||||
|
||||
@@ -529,7 +529,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# endregion
|
||||
|
||||
def setup_central_widget(self, driver: "QtDriver"):
|
||||
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)
|
||||
@@ -651,7 +651,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self.central_layout.addLayout(self.extra_input_layout, 5, 0, 1, 1)
|
||||
|
||||
def setup_content(self, driver: "QtDriver"):
|
||||
def setup_content(self, driver: QtDriver):
|
||||
self.content_layout = QHBoxLayout()
|
||||
self.content_layout.setObjectName("content_layout")
|
||||
|
||||
@@ -667,7 +667,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self.central_layout.addLayout(self.content_layout, 10, 0, 1, 1)
|
||||
|
||||
def setup_entry_list(self, driver: "QtDriver"):
|
||||
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)
|
||||
@@ -700,7 +700,7 @@ class MainWindow(QMainWindow):
|
||||
self.entry_list_layout.addWidget(self.pagination)
|
||||
self.content_splitter.addWidget(self.entry_list_container)
|
||||
|
||||
def setup_preview_panel(self, driver: "QtDriver"):
|
||||
def setup_preview_panel(self, driver: QtDriver):
|
||||
self.preview_panel = Inspector(driver)
|
||||
self.content_splitter.addWidget(self.preview_panel)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ if typing.TYPE_CHECKING:
|
||||
class MergeDuplicateEntriesProgress(QObject):
|
||||
done = Signal()
|
||||
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
def __init__(self, library: Library, driver: QtDriver):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
|
||||
@@ -33,7 +33,7 @@ Image.MAX_IMAGE_PIXELS = None
|
||||
|
||||
# TODO: Use newer MVC style guidelines
|
||||
class PreviewThumb(PreviewThumbView):
|
||||
def __init__(self, library: Library, driver: "QtDriver"):
|
||||
def __init__(self, library: Library, driver: QtDriver):
|
||||
super().__init__(library, driver)
|
||||
|
||||
self.__driver: QtDriver = driver
|
||||
|
||||
@@ -27,10 +27,10 @@ class ProgressWidget(QWidget):
|
||||
super().__init__()
|
||||
self.root = QVBoxLayout(self)
|
||||
self.pb = QProgressDialog(
|
||||
labelText=label_text,
|
||||
minimum=minimum,
|
||||
cancelButtonText=cancel_button_text, # pyright: ignore[reportArgumentType]
|
||||
maximum=maximum,
|
||||
label_text,
|
||||
cancel_button_text, # pyright: ignore[reportArgumentType]
|
||||
minimum,
|
||||
maximum,
|
||||
)
|
||||
self.root.addWidget(self.pb)
|
||||
self.setFixedSize(432, 112)
|
||||
|
||||
@@ -78,7 +78,7 @@ class SearchPanel[T](ModalContent):
|
||||
self.setMinimumSize(300, 400)
|
||||
self.connect_callbacks(self)
|
||||
|
||||
def connect_callbacks(self, controller: "SearchPanel[Any]") -> None: # pyright: ignore[reportExplicitAny]
|
||||
def connect_callbacks(self, controller: SearchPanel[Any]) -> None: # pyright: ignore[reportExplicitAny]
|
||||
self.layout().limit_combobox.currentIndexChanged.connect(controller.on_limit_changed)
|
||||
self.layout().search_field.textChanged.connect(controller.on_search_query_changed)
|
||||
self.layout().search_field.returnPressed.connect(
|
||||
@@ -166,7 +166,7 @@ class SearchPanel[T](ModalContent):
|
||||
if not query:
|
||||
self.layout().search_field.setFocus()
|
||||
parent: QWidget | None = self.parentWidget()
|
||||
if parent is not None: # pyright: ignore[reportUnnecessaryComparison]
|
||||
if parent is not None:
|
||||
parent.hide()
|
||||
return
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from PySide6.QtGui import QAction, QPixmap, QShowEvent, Qt
|
||||
from PySide6.QtWidgets import QGraphicsOpacityEffect, QWidget
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.i18n.translations import Translations
|
||||
from tagstudio.qt.app_settings import AppSettings
|
||||
from tagstudio.qt.controllers.autofill_line_edit import QtCore, QtGui
|
||||
@@ -110,7 +111,7 @@ class SuggestBox[T](QWidget):
|
||||
def _on_shift_held(self, held: bool) -> None:
|
||||
self._is_shift_held = held
|
||||
for i in range(0, self.layout().content_layout.count()):
|
||||
underlined_widget = self.layout().content_layout.itemAt(i).widget()
|
||||
underlined_widget = self.layout().content_layout.itemAt(i).widget() # pyright: ignore
|
||||
assert isinstance(underlined_widget, UnderlinedWidget)
|
||||
|
||||
if held and i == self._selection_index:
|
||||
@@ -128,7 +129,7 @@ class SuggestBox[T](QWidget):
|
||||
# Initialize the widget count (non-hidden)
|
||||
widget_count = 0
|
||||
for i in range(0, self.layout().content_layout.count()):
|
||||
widget = self.layout().content_layout.itemAt(i).widget()
|
||||
widget = unwrap(self.layout().content_layout.itemAt(i).widget()) # pyright: ignore
|
||||
if not widget.isHidden():
|
||||
widget_count += 1
|
||||
|
||||
@@ -149,7 +150,7 @@ class SuggestBox[T](QWidget):
|
||||
|
||||
# Draw the correct underline for the selected widget
|
||||
for i in range(0, widget_count):
|
||||
underlined_widget = self.layout().content_layout.itemAt(i).widget()
|
||||
underlined_widget = self.layout().content_layout.itemAt(i).widget() # pyright: ignore
|
||||
assert isinstance(underlined_widget, UnderlinedWidget)
|
||||
if i == self._selection_index:
|
||||
underlined_widget.toggle_underline(is_hidden=False)
|
||||
@@ -216,7 +217,9 @@ class SuggestBox[T](QWidget):
|
||||
self._selection_index = 0
|
||||
if self.layout().content_layout.count() > 0:
|
||||
self.layout().scroll_area.ensureWidgetVisible(
|
||||
self.layout().content_layout.itemAt(0).widget(), xmargin=16, ymargin=0
|
||||
self.layout().content_layout.itemAt(0).widget(), # pyright: ignore
|
||||
xmargin=16,
|
||||
ymargin=0,
|
||||
)
|
||||
|
||||
# Get results for the search query
|
||||
|
||||
@@ -28,7 +28,7 @@ class TagBoxWidget(TagBoxWidgetView):
|
||||
|
||||
__entries: list[int] = []
|
||||
|
||||
def __init__(self, title: str, driver: "QtDriver"):
|
||||
def __init__(self, title: str, driver: QtDriver):
|
||||
super().__init__(title, driver)
|
||||
self.__driver = driver
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from PySide6.QtWidgets import QMessageBox, QWidget
|
||||
from tagstudio.core.constants import RESERVED_TAG_END, RESERVED_TAG_START
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Tag
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.i18n.translations import Translations
|
||||
from tagstudio.qt.controllers.modal import Modal
|
||||
from tagstudio.qt.controllers.modal_content import ModalContent
|
||||
@@ -207,6 +208,7 @@ class TagSearchPanel(SearchPanel[Tag]):
|
||||
pad_tag_widget.setHidden(True)
|
||||
self.layout().scroll_layout.addWidget(pad_tag_widget)
|
||||
|
||||
tag_widget: QWidget = self.layout().scroll_layout.itemAt(index).widget()
|
||||
item = unwrap(self.layout().scroll_layout.itemAt(index))
|
||||
tag_widget: QWidget = unwrap(item.widget())
|
||||
assert isinstance(tag_widget, TagWidget)
|
||||
return tag_widget
|
||||
|
||||
@@ -12,6 +12,7 @@ from PySide6.QtWidgets import QGraphicsOpacityEffect, QWidget
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Tag
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.i18n.translations import Translations
|
||||
from tagstudio.qt.app_settings import AppSettings
|
||||
from tagstudio.qt.controllers.modal import Modal
|
||||
@@ -191,6 +192,7 @@ class TagSuggestBox(SuggestBox[Tag]):
|
||||
widget.setHidden(True)
|
||||
self.layout().content_layout.addWidget(widget)
|
||||
|
||||
widget_: QWidget = self.layout().content_layout.itemAt(index).widget()
|
||||
item = unwrap(self.layout().content_layout.itemAt(index))
|
||||
widget_: QWidget = unwrap(item.widget())
|
||||
assert isinstance(widget_, UnderlinedWidget)
|
||||
return widget_
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import override
|
||||
|
||||
from PySide6.QtWidgets import QWidget
|
||||
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.qt.views.underlined_widget_view import UnderlinedWidgetView
|
||||
|
||||
|
||||
@@ -21,7 +22,7 @@ class UnderlinedWidget(QWidget):
|
||||
|
||||
@property
|
||||
def widget(self) -> QWidget:
|
||||
return self.layout().itemAt(0).widget()
|
||||
return unwrap(unwrap(self.layout().itemAt(0)).widget())
|
||||
|
||||
@override
|
||||
def layout(self) -> UnderlinedWidgetView:
|
||||
|
||||
@@ -359,7 +359,7 @@ class BuildTagPanel(ModalContent):
|
||||
|
||||
def set_categories(self, added_parent_id: int | None = None, removed_parent: bool = False):
|
||||
while self.category_scroll_layout.itemAt(0):
|
||||
self.category_scroll_layout.takeAt(0).widget().deleteLater()
|
||||
self.category_scroll_layout.takeAt(0).widget().deleteLater() # pyright: ignore[reportOptionalMemberAccess]
|
||||
|
||||
c = QWidget()
|
||||
layout = QVBoxLayout(c)
|
||||
@@ -480,7 +480,7 @@ class BuildTagPanel(ModalContent):
|
||||
|
||||
def set_parent_tags(self):
|
||||
while self.parent_tags_scroll_layout.itemAt(0):
|
||||
self.parent_tags_scroll_layout.takeAt(0).widget().deleteLater()
|
||||
self.parent_tags_scroll_layout.takeAt(0).widget().deleteLater() # pyright: ignore[reportOptionalMemberAccess]
|
||||
|
||||
c = QWidget()
|
||||
layout = QVBoxLayout(c)
|
||||
|
||||
@@ -33,8 +33,8 @@ class ColorBoxWidget(FieldWidget):
|
||||
def __init__(
|
||||
self,
|
||||
group: str,
|
||||
colors: list["TagColorGroup"],
|
||||
library: "Library",
|
||||
colors: list[TagColorGroup],
|
||||
library: Library,
|
||||
) -> None:
|
||||
self.namespace = group
|
||||
self.colors: list[TagColorGroup] = colors
|
||||
@@ -60,7 +60,7 @@ class ColorBoxWidget(FieldWidget):
|
||||
color_widgets: list[TagColorLabel] = []
|
||||
|
||||
while self.base_layout.itemAt(0):
|
||||
unwrap(self.base_layout.takeAt(0)).widget().deleteLater()
|
||||
unwrap(self.base_layout.takeAt(0)).widget().deleteLater() # pyright: ignore[reportOptionalMemberAccess]
|
||||
|
||||
for color in colors_:
|
||||
color_widget = TagColorLabel(
|
||||
|
||||
@@ -41,7 +41,7 @@ def qdtf2dtf(dtf: str) -> str:
|
||||
|
||||
# TODO: Split to use MVC guidelines.
|
||||
class DatetimePicker(ModalContent):
|
||||
def __init__(self, driver: "QtDriver", name: str, datetime: dt | str):
|
||||
def __init__(self, driver: QtDriver, name: str, datetime: dt | str):
|
||||
super().__init__()
|
||||
self.setMinimumSize(300, 60)
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
|
||||
@@ -34,7 +34,7 @@ class DuplicateChoice(enum.StrEnum):
|
||||
class DropImportModal(QWidget):
|
||||
DUPE_NAME_LIMT: int = 5
|
||||
|
||||
def __init__(self, driver: "QtDriver"):
|
||||
def __init__(self, driver: QtDriver):
|
||||
super().__init__()
|
||||
|
||||
self.driver: QtDriver = driver
|
||||
|
||||
@@ -50,7 +50,7 @@ class FieldContainers(QWidget):
|
||||
|
||||
on_tags_update = Signal()
|
||||
|
||||
def __init__(self, library: Library, driver: "QtDriver") -> None:
|
||||
def __init__(self, library: Library, driver: QtDriver) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.lib = library
|
||||
@@ -321,7 +321,7 @@ class FieldContainers(QWidget):
|
||||
text = self.driver.settings.format_datetime(
|
||||
DatetimePicker.string2dt(field.value)
|
||||
)
|
||||
except (ValueError, AssertionError):
|
||||
except ValueError, AssertionError:
|
||||
text = str(field.value)
|
||||
else:
|
||||
text = f"<i>{Translations['field.mixed_data']}</i>"
|
||||
|
||||
@@ -12,6 +12,7 @@ from PySide6.QtCore import QEvent, QSize, Qt
|
||||
from PySide6.QtGui import QEnterEvent, QPixmap, QResizeEvent
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
|
||||
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
from tagstudio.qt.views.styles.color_overlay import auto_theme_overlay
|
||||
from tagstudio.qt.views.styles.stylesheets import container_style, header
|
||||
@@ -136,17 +137,19 @@ class FieldContainer(QWidget):
|
||||
if callback:
|
||||
self.remove_button.clicked.connect(callback)
|
||||
|
||||
def set_inner_widget(self, widget: "FieldWidget") -> None:
|
||||
if self.field_layout.itemAt(0):
|
||||
old: QWidget = self.field_layout.itemAt(0).widget()
|
||||
def set_inner_widget(self, widget: FieldWidget) -> None:
|
||||
item = self.field_layout.itemAt(0)
|
||||
if item:
|
||||
old: QWidget = unwrap(item.widget())
|
||||
self.field_layout.removeWidget(old)
|
||||
old.deleteLater()
|
||||
|
||||
self.field_layout.addWidget(widget)
|
||||
|
||||
def get_inner_widget(self) -> QWidget | None:
|
||||
if self.field_layout.itemAt(0):
|
||||
return self.field_layout.itemAt(0).widget()
|
||||
item = self.field_layout.itemAt(0)
|
||||
if item:
|
||||
return item.widget()
|
||||
return None
|
||||
|
||||
def set_title(self, title: str) -> None:
|
||||
|
||||
@@ -42,7 +42,7 @@ class FileAttributeData:
|
||||
|
||||
# TODO: Split to use MVC guidelines.
|
||||
class FileAttributes(QWidget):
|
||||
def __init__(self, library: Library, driver: "QtDriver"):
|
||||
def __init__(self, library: Library, driver: QtDriver):
|
||||
super().__init__()
|
||||
root_layout = QVBoxLayout(self)
|
||||
root_layout.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -130,7 +130,7 @@ class FileAttributes(QWidget):
|
||||
stats = FileAttributeData()
|
||||
|
||||
if not filepath:
|
||||
self.layout().setSpacing(0)
|
||||
self.layout().setSpacing(0) # pyright: ignore[reportOptionalMemberAccess]
|
||||
self.file_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.file_label.setText(f"<i>{Translations['preview.no_selection']}</i>")
|
||||
self.file_label.set_file_path(Path())
|
||||
@@ -147,7 +147,7 @@ class FileAttributes(QWidget):
|
||||
elif self.driver.settings.show_filepath == ShowFilepathOption.SHOW_FILENAMES_ONLY:
|
||||
display_path = Path(filepath.name)
|
||||
|
||||
self.layout().setSpacing(6)
|
||||
self.layout().setSpacing(6) # pyright: ignore[reportOptionalMemberAccess]
|
||||
self.file_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||
self.file_label.set_file_path(filepath)
|
||||
self.dimensions_label.setHidden(False)
|
||||
@@ -234,7 +234,7 @@ class FileAttributes(QWidget):
|
||||
|
||||
def update_multi_selection(self, count: int):
|
||||
"""Format attributes for multiple selected items."""
|
||||
self.layout().setSpacing(0)
|
||||
self.layout().setSpacing(0) # pyright: ignore[reportOptionalMemberAccess]
|
||||
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)
|
||||
|
||||
@@ -28,7 +28,7 @@ if TYPE_CHECKING:
|
||||
|
||||
# TODO: Split to use MVC guidelines.
|
||||
class FixDupeFilesModal(QWidget):
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
def __init__(self, library: Library, driver: QtDriver):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
|
||||
@@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
||||
|
||||
# TODO: Split to use MVC guidelines.
|
||||
class FixUnlinkedEntriesModal(QWidget):
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
def __init__(self, library: Library, driver: QtDriver):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
# pyright: reportOptionalMemberAccess=false
|
||||
|
||||
import math
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, override
|
||||
from warnings import deprecated
|
||||
|
||||
import structlog
|
||||
from PySide6 import QtCore, QtGui
|
||||
@@ -19,7 +21,6 @@ from PySide6.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from typing_extensions import deprecated
|
||||
|
||||
from tagstudio.core.constants import TAG_ARCHIVED, TAG_FAVORITE
|
||||
from tagstudio.core.library.alchemy.enums import TagColorEnum
|
||||
@@ -39,7 +40,7 @@ logger = structlog.get_logger(__name__)
|
||||
|
||||
@dataclass
|
||||
class BranchData:
|
||||
dirs: dict[str, "BranchData"] = field(default_factory=dict)
|
||||
dirs: dict[str, BranchData] = field(default_factory=dict)
|
||||
files: list[str] = field(default_factory=list)
|
||||
tag: Tag | None = None
|
||||
|
||||
@@ -165,7 +166,7 @@ def generate_preview_data(library: Library) -> BranchData:
|
||||
|
||||
|
||||
class FoldersToTagsModal(QWidget):
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
def __init__(self, library: Library, driver: QtDriver):
|
||||
super().__init__()
|
||||
self.library = library
|
||||
self.driver = driver
|
||||
|
||||
@@ -96,7 +96,7 @@ class ItemThumb(FlowWidget):
|
||||
self,
|
||||
mode: ItemType | None,
|
||||
library: Library,
|
||||
driver: "QtDriver",
|
||||
driver: QtDriver,
|
||||
thumb_size: tuple[int, int],
|
||||
show_filename_label: bool = False,
|
||||
):
|
||||
@@ -440,7 +440,7 @@ class ItemThumb(FlowWidget):
|
||||
self.thumb_button.setMinimumSize(size)
|
||||
self.thumb_button.setMaximumSize(size)
|
||||
|
||||
def set_item(self, entry: "Entry"):
|
||||
def set_item(self, entry: Entry):
|
||||
self.set_item_id(entry.id)
|
||||
path = unwrap(self.lib.library_dir) / entry.path
|
||||
self.set_item_path(path)
|
||||
|
||||
@@ -29,7 +29,7 @@ class LandingWidget(QWidget):
|
||||
mono_logo: Image.Image = rm.ts_logo_text_mono
|
||||
color_logo: Image.Image = rm.ts_logo_text_color
|
||||
|
||||
def __init__(self, driver: "QtDriver", pixel_ratio: float):
|
||||
def __init__(self, driver: QtDriver, pixel_ratio: float):
|
||||
super().__init__()
|
||||
self.driver = driver
|
||||
self.logo_label: ClickableLabel = ClickableLabel()
|
||||
|
||||
@@ -53,9 +53,9 @@ class MediaPlayer(QGraphicsView):
|
||||
Gives a basic control set to manage media playback.
|
||||
"""
|
||||
|
||||
video_preview: "VideoPreview | None" = None
|
||||
video_preview: VideoPreview | None = None
|
||||
|
||||
def __init__(self, driver: "QtDriver") -> None:
|
||||
def __init__(self, driver: QtDriver) -> None:
|
||||
super().__init__()
|
||||
self.driver = driver
|
||||
self.play_icon = QPixmap.fromImage(
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
from warnings import deprecated
|
||||
|
||||
import structlog
|
||||
import wcmatch.fnmatch as fnmatch
|
||||
@@ -23,7 +24,6 @@ from PySide6.QtWidgets import (
|
||||
)
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
from typing_extensions import deprecated
|
||||
|
||||
from tagstudio.core.constants import (
|
||||
IGNORE_NAME,
|
||||
@@ -98,8 +98,8 @@ class JsonMigrationModal(QObject):
|
||||
body_label = QLabel(Translations["json_migration.info.description"])
|
||||
body_label.setWordWrap(True)
|
||||
body_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
body_wrapper.layout().addWidget(body_label)
|
||||
body_wrapper.layout().setContentsMargins(0, 36, 0, 0)
|
||||
body_wrapper.layout().addWidget(body_label) # pyright: ignore[reportOptionalMemberAccess]
|
||||
body_wrapper.layout().setContentsMargins(0, 36, 0, 0) # pyright: ignore[reportOptionalMemberAccess]
|
||||
|
||||
cancel_button = QPushButton(Translations["generic.cancel"])
|
||||
next_button = QPushButton(Translations["generic.continue"])
|
||||
@@ -287,9 +287,9 @@ class JsonMigrationModal(QObject):
|
||||
body_container_layout.addStretch(1)
|
||||
body_container_layout.addWidget(new_lib_container)
|
||||
body_container_layout.addStretch(2)
|
||||
self.body_wrapper_01.layout().addWidget(body_container)
|
||||
self.body_wrapper_01.layout().addWidget(desc_label)
|
||||
self.body_wrapper_01.layout().setSpacing(12)
|
||||
self.body_wrapper_01.layout().addWidget(body_container) # pyright: ignore[reportOptionalMemberAccess]
|
||||
self.body_wrapper_01.layout().addWidget(desc_label) # pyright: ignore[reportOptionalMemberAccess]
|
||||
self.body_wrapper_01.layout().setSpacing(12) # pyright: ignore[reportOptionalMemberAccess]
|
||||
|
||||
back_button = QPushButton(Translations["generic.navigation.back"])
|
||||
start_button = QPushButton(Translations["json_migration.start_and_preview"])
|
||||
@@ -352,14 +352,9 @@ class JsonMigrationModal(QObject):
|
||||
|
||||
def migration_progress(self, skip_ui: bool = False):
|
||||
"""Initialize the progress bar and iterator for the library migration."""
|
||||
pb = QProgressDialog(
|
||||
labelText="",
|
||||
cancelButtonText="",
|
||||
minimum=0,
|
||||
maximum=0,
|
||||
)
|
||||
pb.setCancelButton(None) # pyright: ignore[reportArgumentType]
|
||||
self.body_wrapper_01.layout().addWidget(pb)
|
||||
pb = QProgressDialog("", "", 0, 0)
|
||||
pb.setCancelButton(None)
|
||||
self.body_wrapper_01.layout().addWidget(pb) # pyright: ignore[reportOptionalMemberAccess]
|
||||
|
||||
try:
|
||||
iterator = FunctionIterator(self.migration_iterator)
|
||||
@@ -480,26 +475,26 @@ class JsonMigrationModal(QObject):
|
||||
|
||||
def update_json_entry_count(self, value: int):
|
||||
self.old_entry_count = value
|
||||
label: QLabel = self.old_content_layout.itemAtPosition(self.entries_row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
label: QLabel = self.old_content_layout.itemAtPosition(self.entries_row, 1).widget() # pyright: ignore
|
||||
label.setText(self.color_value_default(value))
|
||||
|
||||
def update_json_tag_count(self, value: int):
|
||||
self.old_tag_count = value
|
||||
label: QLabel = self.old_content_layout.itemAtPosition(self.tags_row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
label: QLabel = self.old_content_layout.itemAtPosition(self.tags_row, 1).widget() # pyright: ignore
|
||||
label.setText(self.color_value_default(value))
|
||||
|
||||
def update_sql_value(self, row: int, value: int | bool, old_value: int | bool):
|
||||
label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore[reportAssignmentType]
|
||||
label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore
|
||||
warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore
|
||||
label.setText(self.color_value_conditional(old_value, value))
|
||||
warning_icon.setText("" if old_value == value else self.warning)
|
||||
|
||||
def update_parity_value(self, row: int, value: bool):
|
||||
result: str = self.match_text if value else self.differ_text
|
||||
old_label: QLabel = self.old_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
new_label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
old_warning_icon: QLabel = self.old_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore[reportAssignmentType]
|
||||
new_warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore[reportAssignmentType]
|
||||
old_label: QLabel = self.old_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore
|
||||
new_label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore
|
||||
old_warning_icon: QLabel = self.old_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore
|
||||
new_warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore
|
||||
old_label.setText(self.color_value_conditional(self.match_text, result))
|
||||
new_label.setText(self.color_value_conditional(self.match_text, result))
|
||||
old_warning_icon.setText("" if value else self.warning)
|
||||
|
||||
@@ -22,7 +22,7 @@ if typing.TYPE_CHECKING:
|
||||
class MirrorEntriesModal(QWidget):
|
||||
done = Signal()
|
||||
|
||||
def __init__(self, driver: "QtDriver", tracker: DupeFilesRegistry):
|
||||
def __init__(self, driver: QtDriver, tracker: DupeFilesRegistry):
|
||||
super().__init__()
|
||||
self.driver = driver
|
||||
self.setWindowTitle(Translations["entries.mirror.window_title"])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
# pyright: reportOptionalMemberAccess=false
|
||||
from typing import override
|
||||
|
||||
import structlog
|
||||
@@ -9,6 +9,7 @@ from PySide6 import QtCore, QtGui
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget
|
||||
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.qt.mixed.paged_panel_state import PagedPanelState
|
||||
from tagstudio.qt.views.styles.stylesheets import header
|
||||
|
||||
@@ -96,7 +97,7 @@ class PagedPanel(QWidget):
|
||||
# Update Body Widget
|
||||
if self.body_layout.itemAt(0):
|
||||
self.body_layout.itemAt(0).widget().setHidden(True)
|
||||
self.body_layout.removeWidget(self.body_layout.itemAt(0).widget())
|
||||
self.body_layout.removeWidget(unwrap(self.body_layout.itemAt(0).widget()))
|
||||
self.body_layout.addWidget(frame.body_wrapper)
|
||||
self.body_layout.itemAt(0).widget().setHidden(False)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
# pyright: reportOptionalMemberAccess=false
|
||||
|
||||
from typing import cast, override
|
||||
from warnings import catch_warnings
|
||||
|
||||
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
|
||||
class RemoveIgnoredModal(QWidget):
|
||||
done = Signal()
|
||||
|
||||
def __init__(self, driver: "QtDriver", tracker: IgnoredRegistry):
|
||||
def __init__(self, driver: QtDriver, tracker: IgnoredRegistry):
|
||||
super().__init__()
|
||||
self.driver = driver
|
||||
self.tracker = tracker
|
||||
|
||||
@@ -22,7 +22,7 @@ if TYPE_CHECKING:
|
||||
class RemoveUnlinkedEntriesModal(QWidget):
|
||||
done = Signal()
|
||||
|
||||
def __init__(self, driver: "QtDriver", tracker: UnlinkedRegistry):
|
||||
def __init__(self, driver: QtDriver, tracker: UnlinkedRegistry):
|
||||
super().__init__()
|
||||
self.driver = driver
|
||||
self.tracker = tracker
|
||||
|
||||
@@ -41,7 +41,7 @@ logger = structlog.get_logger(__name__)
|
||||
|
||||
# TODO: Split to use MVC guidelines.
|
||||
class SettingsPanel(ModalContent):
|
||||
driver: "QtDriver"
|
||||
driver: QtDriver
|
||||
|
||||
filepath_option_map: dict[ShowFilepathOption, str] = {
|
||||
ShowFilepathOption.SHOW_FULL_PATHS: Translations["settings.filepath.option.full"],
|
||||
@@ -88,7 +88,7 @@ class SettingsPanel(ModalContent):
|
||||
"%Y.%m.%d": "2024.08.21",
|
||||
}
|
||||
|
||||
def __init__(self, driver: "QtDriver"):
|
||||
def __init__(self, driver: QtDriver):
|
||||
super().__init__()
|
||||
# set these "constants" because language will be loaded from config shortly after startup
|
||||
# and we want to use the current language for the dropdowns
|
||||
@@ -400,7 +400,7 @@ class SettingsPanel(ModalContent):
|
||||
"splash": self.splash_combobox.currentData(),
|
||||
}
|
||||
|
||||
def update_settings(self, driver: "QtDriver"):
|
||||
def update_settings(self, driver: QtDriver):
|
||||
settings = self.get_settings()
|
||||
|
||||
driver.settings.language = settings["language"]
|
||||
@@ -440,7 +440,7 @@ class SettingsPanel(ModalContent):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def build_modal(cls, driver: "QtDriver") -> Modal:
|
||||
def build_modal(cls, driver: QtDriver) -> Modal:
|
||||
settings_panel = cls(driver)
|
||||
|
||||
modal = Modal(
|
||||
|
||||
@@ -41,7 +41,7 @@ class TagColorLabel(QWidget):
|
||||
color: TagColorGroup | None,
|
||||
has_edit: bool,
|
||||
has_remove: bool,
|
||||
library: "Library | None" = None,
|
||||
library: Library | None = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.color = color
|
||||
|
||||
@@ -23,6 +23,7 @@ from PySide6.QtWidgets import (
|
||||
|
||||
from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX
|
||||
from tagstudio.core.enums import Theme
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.i18n.translations import Translations
|
||||
from tagstudio.qt.controllers.modal import Modal
|
||||
from tagstudio.qt.mixed.build_namespace import BuildNamespacePanel
|
||||
@@ -42,7 +43,7 @@ class TagColorManager(QWidget):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
driver: "QtDriver",
|
||||
driver: QtDriver,
|
||||
):
|
||||
super().__init__()
|
||||
self.driver = driver
|
||||
@@ -166,7 +167,8 @@ class TagColorManager(QWidget):
|
||||
|
||||
def reset(self):
|
||||
while self.scroll_layout.count():
|
||||
widget = self.scroll_layout.itemAt(0).widget()
|
||||
item = unwrap(self.scroll_layout.itemAt(0))
|
||||
widget = unwrap(item.widget())
|
||||
self.scroll_layout.removeWidget(widget)
|
||||
widget.deleteLater()
|
||||
self.is_initialized = False
|
||||
|
||||
@@ -32,7 +32,7 @@ class TagColorPreview(QWidget):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
library: "Library",
|
||||
library: Library,
|
||||
tag_color_group: TagColorGroup | None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
@@ -111,7 +111,7 @@ class TagWidget(QWidget):
|
||||
tag: Tag | None
|
||||
|
||||
def __init__(
|
||||
self, tag: Tag | None, has_edit: bool, has_remove: bool, library: "Library | None" = None
|
||||
self, tag: Tag | None, has_edit: bool, has_remove: bool, library: Library | None = None
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.tag = tag
|
||||
|
||||
@@ -20,7 +20,7 @@ class ResourceManager:
|
||||
|
||||
_map: dict[str, dict[str, str]] = {}
|
||||
_cache: dict[str, bytes | str | Image.Image | QPixmap] = {}
|
||||
_instance: "ResourceManager | None" = None
|
||||
_instance: ResourceManager | None = None
|
||||
|
||||
def __new__(cls):
|
||||
if ResourceManager._instance is None:
|
||||
|
||||
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
|
||||
|
||||
# TODO: Use newer MVC style guidelines
|
||||
class FixIgnoredEntriesModalView(QWidget):
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
def __init__(self, library: Library, driver: QtDriver):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
|
||||
@@ -28,7 +28,7 @@ logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class InspectorView(QVBoxLayout):
|
||||
def __init__(self, driver: "QtDriver", pixel_ratio: float) -> None:
|
||||
def __init__(self, driver: QtDriver, pixel_ratio: float) -> None:
|
||||
super().__init__()
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setSpacing(6)
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
# SPDX-FileCopyrightText: (C) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
# pyright: reportOptionalMemberAccess=false
|
||||
|
||||
|
||||
"""PySide6 port of the widgets/layouts/flowlayout example from Qt v6.x."""
|
||||
|
||||
@@ -44,14 +46,14 @@ class FlowLayout(QLayout):
|
||||
return len(self._item_list)
|
||||
|
||||
@override
|
||||
def itemAt(self, index: int) -> QLayoutItem | None: # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
def itemAt(self, index: int) -> QLayoutItem | None:
|
||||
if 0 <= index < len(self._item_list):
|
||||
return self._item_list[index]
|
||||
|
||||
return None
|
||||
|
||||
@override
|
||||
def takeAt(self, index: int) -> QLayoutItem | None: # pyright: ignore[reportIncompatibleMethodOverride]
|
||||
def takeAt(self, index: int) -> QLayoutItem | None:
|
||||
if 0 <= index < len(self._item_list):
|
||||
return self._item_list.pop(index)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class ThumbGridLayout(QLayout):
|
||||
# Id of first visible entry
|
||||
visible_changed = Signal(int)
|
||||
|
||||
def __init__(self, driver: "QtDriver", scroll_area: QScrollArea) -> None:
|
||||
def __init__(self, driver: QtDriver, scroll_area: QScrollArea) -> None:
|
||||
super().__init__(None)
|
||||
self.driver: QtDriver = driver
|
||||
self.scroll_area: QScrollArea = scroll_area
|
||||
@@ -198,7 +198,7 @@ class ThumbGridLayout(QLayout):
|
||||
return
|
||||
|
||||
per_row, width_offset, height_offset = self._size(rect.right())
|
||||
view_height = self.parentWidget().parentWidget().height()
|
||||
view_height = self.parentWidget().parentWidget().height() # pyright: ignore[reportOptionalMemberAccess]
|
||||
offset = self.scroll_area.verticalScrollBar().value()
|
||||
if self._scroll_to is not None:
|
||||
try:
|
||||
|
||||
@@ -34,7 +34,7 @@ if TYPE_CHECKING:
|
||||
|
||||
# TODO: Use newer MVC style guidelines
|
||||
class LibraryInfoWindowView(QWidget):
|
||||
def __init__(self, library: "Library", driver: "QtDriver"):
|
||||
def __init__(self, library: Library, driver: QtDriver):
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.driver = driver
|
||||
|
||||
@@ -44,7 +44,7 @@ class PreviewThumbView(QWidget):
|
||||
__should_render_on_resize: bool
|
||||
__rendered_res: tuple[int, int]
|
||||
|
||||
def __init__(self, library: Library, driver: "QtDriver") -> None:
|
||||
def __init__(self, library: Library, driver: QtDriver) -> None:
|
||||
super().__init__()
|
||||
self._driver = driver
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ logger = structlog.get_logger(__name__)
|
||||
class TagBoxWidgetView(FieldWidget):
|
||||
__lib: Library
|
||||
|
||||
def __init__(self, title: str, driver: "QtDriver") -> None:
|
||||
def __init__(self, title: str, driver: QtDriver) -> None:
|
||||
super().__init__(title)
|
||||
self.__lib = driver.lib
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
# pyright: reportPrivateUsage = false
|
||||
# pyright: reportOptionalMemberAccess=false
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import cast
|
||||
|
||||
Reference in New Issue
Block a user