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
1 change: 1 addition & 0 deletions src/tagstudio/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
GITHUB_REPO_URL = "https://github.com/TagStudioDev/TagStudio"
GITHUB_RELEASE_URL = "https://github.com/TagStudioDev/TagStudio/releases/latest"
DOCS_URL = "https://docs.tagstud.io"
FFMPEG_HELP_URL = "https://docs.tagstud.io/help/ffmpeg"
DISCORD_URL = "https://discord.com/invite/hRNnVKhF2G"

# The folder & file names where TagStudio keeps its data relative to a library.
Expand Down
8 changes: 4 additions & 4 deletions src/tagstudio/core/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from PySide6.QtCore import QSettings

from tagstudio.core.constants import TS_FOLDER_NAME
from tagstudio.core.enums import SettingItems
from tagstudio.core.enums import AppCacheItems
from tagstudio.core.library.alchemy.library import LibraryStatus
from tagstudio.qt.global_settings import GlobalSettings

Expand All @@ -30,16 +30,16 @@ def evaluate_path(self, open_path: str | None) -> LibraryStatus:
logger.error("Path does not exist.", open_path=open_path)
return LibraryStatus(success=False, message="Path does not exist.")
elif self.settings.open_last_loaded_on_startup and self.cached_values.value(
SettingItems.LAST_LIBRARY
AppCacheItems.LAST_LIBRARY
):
library_path = Path(str(self.cached_values.value(SettingItems.LAST_LIBRARY)))
library_path = Path(str(self.cached_values.value(AppCacheItems.LAST_LIBRARY)))
if not (library_path / TS_FOLDER_NAME).exists():
logger.error(
"TagStudio folder does not exist.",
library_path=library_path,
ts_folder=TS_FOLDER_NAME,
)
self.cached_values.setValue(SettingItems.LAST_LIBRARY, "")
self.cached_values.setValue(AppCacheItems.LAST_LIBRARY, "")
# dont consider this a fatal error, just skip opening the library
library_path = None

Expand Down
3 changes: 2 additions & 1 deletion src/tagstudio/core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
import enum


class SettingItems(enum.StrEnum):
class AppCacheItems(enum.StrEnum):
"""List of setting item names."""

LAST_LIBRARY = "last_library"
LIBS_LIST = "libs_list"
DISMISSED_UPDATE = "dismissed_update"


class ShowFilepathOption(enum.IntEnum):
Expand Down
57 changes: 0 additions & 57 deletions src/tagstudio/qt/controllers/ffmpeg_missing_message_box.py

This file was deleted.

10 changes: 10 additions & 0 deletions src/tagstudio/qt/controllers/preview_panel_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@


import typing
from shutil import which
from warnings import catch_warnings

from tagstudio.core.library.alchemy.fields import BaseFieldTemplate
from tagstudio.core.library.alchemy.library import Library
from tagstudio.qt.controllers.field_template_search_panel_controller import FieldTemplateSearchModal
from tagstudio.qt.controllers.tag_search_panel_controller import TagSearchModal
from tagstudio.qt.previews.vendored.ffmpeg import FFMPEG_CMD, FFPROBE_CMD
from tagstudio.qt.views.preview_panel_view import PreviewPanelView

if typing.TYPE_CHECKING:
Expand All @@ -21,6 +23,7 @@ def __init__(self, library: Library, driver: "QtDriver") -> None:

self.__add_field_modal = FieldTemplateSearchModal(self.lib, is_field_template_chooser=True)
self.__add_tag_modal = TagSearchModal(self.lib, is_tag_chooser=True)
self._thumb.check_ffmpeg.connect(self._toggle_ffmpeg_warning)

@typing.override
def _add_field_button_callback(self) -> None:
Expand Down Expand Up @@ -50,3 +53,10 @@ def _add_tag_to_selected(self, tag_id: int) -> None:
self._containers.add_tags_to_selected(tag_id)
if len(self._selected) == 1:
self._containers.update_from_entry(self._selected[0])

def _toggle_ffmpeg_warning(self, enable_warning: bool = True) -> None:
if enable_warning and (not which(FFMPEG_CMD) or not which(FFPROBE_CMD)):
self._ffmpeg_warning_widget.show()
return

self._ffmpeg_warning_widget.hide()
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@


import math
from functools import partial

import structlog
from PIL import ImageQt
from PySide6.QtCore import Qt
from PySide6.QtGui import QPixmap
from PySide6.QtGui import QDesktopServices, QPixmap
from PySide6.QtWidgets import QMessageBox

from tagstudio.core.constants import GITHUB_RELEASE_URL, VERSION
Expand All @@ -20,14 +21,13 @@
logger = structlog.get_logger(__name__)


class OutOfDateMessageBox(QMessageBox):
class UpdateAvailableMessageBox(QMessageBox):
"""A warning dialog for if the TagStudio is not running under the latest release version."""

def __init__(self):
super().__init__()

rm = ResourceManager()

title = Translations["version_modal.title"]
self.setWindowTitle(title)
pixel_ratio = self.devicePixelRatio()
Expand All @@ -40,13 +40,19 @@ def __init__(self):
icon.setDevicePixelRatio(pixel_ratio)
self.setIconPixmap(icon)
self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.setStyleSheet("QPushButton {padding: 3px 8px;}")

self.setStandardButtons(
QMessageBox.StandardButton.Ignore | QMessageBox.StandardButton.Cancel
QMessageBox.StandardButton.Close
| QMessageBox.StandardButton.Ignore
| QMessageBox.StandardButton.Ok
)
self.setDefaultButton(QMessageBox.StandardButton.Ok)
self.button(QMessageBox.StandardButton.Ok).setText(Translations["update.view_update"])
self.button(QMessageBox.StandardButton.Ok).clicked.connect(
partial(QDesktopServices.openUrl, GITHUB_RELEASE_URL)
)
self.setDefaultButton(QMessageBox.StandardButton.Ignore)
# Enables the cancel button but hides it to allow for click X to close dialog
self.button(QMessageBox.StandardButton.Cancel).hide()
self.button(QMessageBox.StandardButton.Ignore).setText(Translations["generic.dont_remind"])

red = get_ui_color(ColorType.PRIMARY, UiColor.RED)
green = get_ui_color(ColorType.PRIMARY, UiColor.GREEN)
Expand Down
6 changes: 3 additions & 3 deletions src/tagstudio/qt/previews/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@
register_heif_opener()

try:
import pillow_jxl # noqa: F401 # pyright: ignore[reportUnusedImport]
except ImportError:
logger.exception('[ThumbRenderer] Could not import the "pillow_jxl" module')
import pillow_jxl # noqa: F401 # pyright: ignore
except ImportError as e:
logger.error('[ThumbRenderer] Could not import the "pillow_jxl" module', error=e)


class _SevenZipFile(py7zr.SevenZipFile):
Expand Down
1 change: 1 addition & 0 deletions src/tagstudio/qt/resource_manager.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ResourceManager:
adobe_illustrator: Image.Image
adobe_photoshop: Image.Image
affinity_photo: Image.Image
alert: QPixmap
archive: Image.Image
audio: Image.Image
broken_link_icon: Image.Image
Expand Down
4 changes: 4 additions & 0 deletions src/tagstudio/qt/resources.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
"mode": "pil",
"path": "qt/images/file_icons/affinity_photo.png"
},
"alert": {
"mode": "qpixmap",
"path": "qt/images/alert.png"
},
"archive": {
"mode": "pil",
"path": "qt/images/file_icons/archive.png"
Expand Down
69 changes: 32 additions & 37 deletions src/tagstudio/qt/ts_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
import time
from argparse import Namespace
from collections import OrderedDict
from functools import partial
from pathlib import Path
from queue import Queue
from shutil import which
from typing import TypeVar
from warnings import catch_warnings

Expand All @@ -37,23 +37,14 @@
QMouseEvent,
QPalette,
)
from PySide6.QtWidgets import (
QApplication,
QFileDialog,
QMessageBox,
QPushButton,
QScrollArea,
)
from PySide6.QtWidgets import QApplication, QFileDialog, QMessageBox, QPushButton, QScrollArea

# This import has side-effect of importing PySide resources
import tagstudio.qt.resources_rc # noqa: F401 # pyright: ignore[reportUnusedImport]
from tagstudio.core.constants import TAG_ARCHIVED, TAG_FAVORITE, VERSION, VERSION_BRANCH
from tagstudio.core.driver import DriverMixin
from tagstudio.core.enums import MacroID, SettingItems, ShowFilepathOption
from tagstudio.core.library.alchemy.enums import (
BrowsingState,
SortingModeEnum,
)
from tagstudio.core.enums import AppCacheItems, MacroID, ShowFilepathOption
from tagstudio.core.library.alchemy.enums import BrowsingState, SortingModeEnum
from tagstudio.core.library.alchemy.library import Library, LibraryStatus
from tagstudio.core.library.alchemy.models import Entry
from tagstudio.core.library.ignore import Ignore
Expand All @@ -64,18 +55,13 @@
from tagstudio.core.utils.str_formatting import is_version_outdated
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.cache_manager import CacheManager
from tagstudio.qt.controllers.ffmpeg_missing_message_box import FfmpegMissingMessageBox
from tagstudio.qt.controllers.field_template_search_panel_controller import FieldTemplateSearchPanel
from tagstudio.qt.controllers.fix_ignored_modal_controller import FixIgnoredEntriesModal
from tagstudio.qt.controllers.ignore_modal_controller import IgnoreModal
from tagstudio.qt.controllers.library_info_window_controller import LibraryInfoWindow
from tagstudio.qt.controllers.out_of_date_message_box import OutOfDateMessageBox
from tagstudio.qt.controllers.tag_search_panel_controller import TagSearchModal, TagSearchPanel
from tagstudio.qt.global_settings import (
DEFAULT_GLOBAL_SETTINGS_PATH,
GlobalSettings,
Theme,
)
from tagstudio.qt.controllers.update_available_message_box import UpdateAvailableMessageBox
from tagstudio.qt.global_settings import DEFAULT_GLOBAL_SETTINGS_PATH, GlobalSettings, Theme
from tagstudio.qt.mixed.about_modal import AboutModal
from tagstudio.qt.mixed.build_tag import BuildTagPanel
from tagstudio.qt.mixed.drop_import_modal import DropImportModal
Expand All @@ -89,7 +75,6 @@
from tagstudio.qt.mixed.tag_color_manager import TagColorManager
from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color
from tagstudio.qt.platform_strings import trash_term
from tagstudio.qt.previews.vendored.ffmpeg import FFMPEG_CMD, FFPROBE_CMD
from tagstudio.qt.resource_manager import ResourceManager
from tagstudio.qt.translations import Translations
from tagstudio.qt.utils.custom_runnable import CustomRunnable
Expand All @@ -112,9 +97,9 @@
if sys.platform == "win32":
from signal import SIGINT, SIGTERM, signal

SIGQUIT = SIGTERM
SIGQUIT = SIGTERM # pyright: ignore
else:
from signal import SIGINT, SIGQUIT, SIGTERM, signal
from signal import SIGINT, SIGQUIT, SIGTERM, signal # pyright: ignore

logger = structlog.get_logger(__name__)

Expand Down Expand Up @@ -636,14 +621,7 @@ def on_visible_changed(entry_id: int | None):
if path_result.success and path_result.library_path:
self.open_library(path_result.library_path)

# Check if FFmpeg or FFprobe are missing and show warning if so
if not which(FFMPEG_CMD) or not which(FFPROBE_CMD):
FfmpegMissingMessageBox().show()

latest_version = TagStudioCore.get_most_recent_release_version()
if latest_version and is_version_outdated(VERSION, latest_version):
OutOfDateMessageBox().exec()

self.check_for_update()
self.app.exec()
self.shutdown()

Expand Down Expand Up @@ -783,7 +761,7 @@ def close_library(self, is_shutdown: bool = False):
self.main_window.status_bar.showMessage(Translations["status.library_closing"])
start_time = time.time()

self.cached_values.setValue(SettingItems.LAST_LIBRARY, str(self.lib.library_dir))
self.cached_values.setValue(AppCacheItems.LAST_LIBRARY, str(self.lib.library_dir))
self.cached_values.sync()

# Reset library state
Expand Down Expand Up @@ -1513,7 +1491,7 @@ def update_browsing_state(self, state: BrowsingState | None = None) -> None:
)

def remove_recent_library(self, item_key: str):
self.cached_values.beginGroup(SettingItems.LIBS_LIST)
self.cached_values.beginGroup(AppCacheItems.LIBS_LIST)
self.cached_values.remove(item_key)
self.cached_values.endGroup()
self.cached_values.sync()
Expand All @@ -1523,7 +1501,7 @@ def update_libs_list(self, path: Path | str):
item_limit: int = 10
path = Path(path)

self.cached_values.beginGroup(SettingItems.LIBS_LIST)
self.cached_values.beginGroup(AppCacheItems.LIBS_LIST)

all_libs = {str(time.time()): str(path)}

Expand All @@ -1550,7 +1528,7 @@ def update_recent_lib_menu(self):
lib_items: dict[str, tuple[str, str]] = {}

# get recent libraries sorted by timestamp
self.cached_values.beginGroup(SettingItems.LIBS_LIST)
self.cached_values.beginGroup(AppCacheItems.LIBS_LIST)
for item_tstamp in self.cached_values.allKeys():
val = str(self.cached_values.value(item_tstamp, type=str))
cut_val = val
Expand All @@ -1571,8 +1549,8 @@ def update_recent_lib_menu(self):

def clear_recent_libs(self):
"""Clear the list of recent libraries from the settings file."""
settings = self.cached_values
settings.beginGroup(SettingItems.LIBS_LIST)
cache = self.cached_values
cache.beginGroup(AppCacheItems.LIBS_LIST)
self.cached_values.remove("")
self.cached_values.endGroup()
self.cached_values.sync()
Expand All @@ -1581,6 +1559,23 @@ def clear_recent_libs(self):
def open_settings_modal(self):
SettingsPanel.build_modal(self).show()

def check_for_update(self):
"""Check for an update to TagStudio and display a message box if there is one."""
latest_version = TagStudioCore.get_most_recent_release_version()
if latest_version == str(self.cached_values.value(AppCacheItems.DISMISSED_UPDATE)):
return

if latest_version and is_version_outdated(VERSION, latest_version):
update_box = UpdateAvailableMessageBox()
update_box.button(QMessageBox.StandardButton.Ignore).clicked.connect(
partial(self.dismiss_update, str(latest_version))
)
update_box.exec()

def dismiss_update(self, version: str):
"""Dismiss an update notification for a specific new version of TagStudio."""
self.cached_values.setValue(AppCacheItems.DISMISSED_UPDATE, version)

def open_library(self, path: Path) -> None:
"""Open a TagStudio library."""
library_dir_display = (
Expand Down
Loading
Loading