mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-08-19 10:32:35 +02:00
feat: dismiss update notification, change missing ffmpeg popup to banner (#1400)
* feat: dismiss update notification, change ffmpeg to banner * chore: sort resources.pyi list * fix: remove unused asset * refactor: rename dismiss_update() * fix: remove *other* unused asset
This commit is contained in:
committed by
GitHub
parent
4919c972da
commit
6b15beefbd
@@ -11,6 +11,7 @@ COPYRIGHT_COMPACT: str = f"© {COPYRIGHT_YEARS} Travis Abendshien\n& TagStudio C
|
||||
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.
|
||||
|
||||
@@ -8,7 +8,7 @@ import structlog
|
||||
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
|
||||
|
||||
@@ -30,16 +30,16 @@ class DriverMixin:
|
||||
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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
from shutil import which
|
||||
|
||||
import structlog
|
||||
from PySide6.QtCore import Qt, QUrl
|
||||
from PySide6.QtGui import QDesktopServices
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color
|
||||
from tagstudio.qt.previews.vendored.ffmpeg import FFMPEG_CMD, FFPROBE_CMD
|
||||
from tagstudio.qt.translations import Translations
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
class FfmpegMissingMessageBox(QMessageBox):
|
||||
"""A warning dialog for if FFmpeg is missing."""
|
||||
|
||||
HELP_URL = "https://docs.tagstud.io/help/ffmpeg/"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
ffmpeg = "FFmpeg"
|
||||
ffprobe = "FFprobe"
|
||||
title = Translations.format("dependency.missing.title", dependency=ffmpeg)
|
||||
self.setWindowTitle(title)
|
||||
self.setIcon(QMessageBox.Icon.Warning)
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
|
||||
self.setStandardButtons(
|
||||
QMessageBox.StandardButton.Help
|
||||
| QMessageBox.StandardButton.Ignore
|
||||
| QMessageBox.StandardButton.Cancel
|
||||
)
|
||||
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.Help).clicked.connect(
|
||||
lambda: QDesktopServices.openUrl(QUrl(self.HELP_URL))
|
||||
)
|
||||
|
||||
red = get_ui_color(ColorType.PRIMARY, UiColor.RED)
|
||||
green = get_ui_color(ColorType.PRIMARY, UiColor.GREEN)
|
||||
missing = f"<span style='color:{red}'>{Translations['generic.missing']}</span>"
|
||||
found = f"<span style='color:{green}'>{Translations['about.module.found']}</span>"
|
||||
status = Translations.format(
|
||||
"ffmpeg.missing.status",
|
||||
ffmpeg=ffmpeg,
|
||||
ffmpeg_status=found if which(FFMPEG_CMD) else missing,
|
||||
ffprobe=ffprobe,
|
||||
ffprobe_status=found if which(FFPROBE_CMD) else missing,
|
||||
)
|
||||
self.setText(f"{Translations['ffmpeg.missing.description']}<br><br>{status}")
|
||||
@@ -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:
|
||||
@@ -21,6 +23,7 @@ class PreviewPanel(PreviewPanelView):
|
||||
|
||||
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:
|
||||
@@ -50,3 +53,10 @@ class PreviewPanel(PreviewPanelView):
|
||||
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()
|
||||
|
||||
+13
-7
@@ -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
|
||||
@@ -20,14 +21,13 @@ from tagstudio.qt.translations import Translations
|
||||
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()
|
||||
@@ -40,13 +40,19 @@ class OutOfDateMessageBox(QMessageBox):
|
||||
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.Ignore)
|
||||
# Enables the cancel button but hides it to allow for click X to close dialog
|
||||
self.button(QMessageBox.StandardButton.Cancel).hide()
|
||||
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.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)
|
||||
@@ -98,9 +98,9 @@ Image.MAX_IMAGE_PIXELS = None
|
||||
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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
+32
-37
@@ -17,9 +17,9 @@ import sys
|
||||
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
|
||||
|
||||
@@ -37,23 +37,14 @@ from PySide6.QtGui import (
|
||||
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
|
||||
@@ -64,18 +55,13 @@ from tagstudio.core.ts_core import TagStudioCore
|
||||
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
|
||||
@@ -89,7 +75,6 @@ from tagstudio.qt.mixed.settings_panel import SettingsPanel
|
||||
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
|
||||
@@ -112,9 +97,9 @@ BADGE_TAGS = {
|
||||
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__)
|
||||
|
||||
@@ -636,14 +621,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
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()
|
||||
|
||||
@@ -783,7 +761,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
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
|
||||
@@ -1513,7 +1491,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
)
|
||||
|
||||
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()
|
||||
@@ -1523,7 +1501,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
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)}
|
||||
|
||||
@@ -1550,7 +1528,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
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
|
||||
@@ -1571,8 +1549,8 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
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()
|
||||
@@ -1581,6 +1559,23 @@ class QtDriver(DriverMixin, QObject):
|
||||
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 = (
|
||||
|
||||
@@ -2,28 +2,33 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
import math
|
||||
import traceback
|
||||
import typing
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QDesktopServices
|
||||
from PySide6.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QSplitter,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from tagstudio.core.constants import FFMPEG_HELP_URL
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.qt.controllers.preview_thumb_controller import PreviewThumb
|
||||
from tagstudio.qt.mixed.field_containers import FieldContainers
|
||||
from tagstudio.qt.mixed.file_attributes import FileAttributeData, FileAttributes
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.views.stylesheets.stylesheets import button_style
|
||||
from tagstudio.qt.views.stylesheets.stylesheets import button_style, preview_warning_style
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
@@ -39,9 +44,10 @@ class PreviewPanelView(QWidget):
|
||||
def __init__(self, library: Library, driver: "QtDriver") -> None:
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
rm = ResourceManager()
|
||||
|
||||
self.__thumb = PreviewThumb(self.lib, driver)
|
||||
self.__file_attrs = FileAttributes(self.lib, driver)
|
||||
self._thumb = PreviewThumb(self.lib, driver)
|
||||
self._file_attrs = FileAttributes(self.lib, driver)
|
||||
self._containers = FieldContainers(
|
||||
self.lib, driver
|
||||
) # TODO: this should be name mangled, but is still needed on the controller side atm
|
||||
@@ -51,6 +57,33 @@ class PreviewPanelView(QWidget):
|
||||
preview_layout.setContentsMargins(0, 0, 0, 0)
|
||||
preview_layout.setSpacing(6)
|
||||
|
||||
self._ffmpeg_warning_widget = QWidget()
|
||||
self._ffmpeg_warning_widget.setObjectName("ffmpeg_widget")
|
||||
ffmpeg_warning_layout = QHBoxLayout(self._ffmpeg_warning_widget)
|
||||
ffmpeg_warning_layout.setContentsMargins(3, 3, 3, 3)
|
||||
self._ffmpeg_warning_widget.setStyleSheet(preview_warning_style())
|
||||
ffmpeg_warning_label = QLabel(
|
||||
Translations.format(
|
||||
"preview.missing_module.multimedia",
|
||||
module=f'<a href="{FFMPEG_HELP_URL}">FFmpeg</a>',
|
||||
)
|
||||
)
|
||||
ffmpeg_warning_label.setWordWrap(True)
|
||||
ffmpeg_warning_label.linkActivated.connect(
|
||||
lambda x: QDesktopServices.openUrl(FFMPEG_HELP_URL)
|
||||
)
|
||||
warning_icon = QLabel()
|
||||
warning_icon_pixmap = rm.alert.scaled(
|
||||
math.floor(20 * self.devicePixelRatio()), math.floor(20 * self.devicePixelRatio())
|
||||
)
|
||||
warning_icon_pixmap.setDevicePixelRatio(self.devicePixelRatio())
|
||||
warning_icon.setPixmap(warning_icon_pixmap)
|
||||
ffmpeg_warning_layout.addWidget(warning_icon)
|
||||
ffmpeg_warning_layout.addWidget(ffmpeg_warning_label)
|
||||
ffmpeg_warning_layout.setStretch(1, 1)
|
||||
|
||||
self._ffmpeg_warning_widget.hide()
|
||||
|
||||
info_section = QWidget()
|
||||
info_layout = QVBoxLayout(info_section)
|
||||
info_layout.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -80,8 +113,9 @@ class PreviewPanelView(QWidget):
|
||||
add_buttons_layout.addWidget(self.__add_tag_button)
|
||||
add_buttons_layout.addWidget(self.__add_field_button)
|
||||
|
||||
preview_layout.addWidget(self.__thumb)
|
||||
info_layout.addWidget(self.__file_attrs)
|
||||
preview_layout.addWidget(self._thumb)
|
||||
info_layout.addWidget(self._ffmpeg_warning_widget)
|
||||
info_layout.addWidget(self._file_attrs)
|
||||
info_layout.addWidget(self._containers)
|
||||
|
||||
splitter.addWidget(preview_section)
|
||||
@@ -120,9 +154,9 @@ class PreviewPanelView(QWidget):
|
||||
try:
|
||||
# No Items Selected
|
||||
if len(selected) == 0:
|
||||
self.__thumb.hide_preview()
|
||||
self.__file_attrs.update_stats()
|
||||
self.__file_attrs.update_date_label()
|
||||
self._thumb.hide_preview()
|
||||
self._file_attrs.update_stats()
|
||||
self._file_attrs.update_date_label()
|
||||
self._containers.hide_containers()
|
||||
|
||||
self.add_buttons_enabled = False
|
||||
@@ -135,9 +169,9 @@ class PreviewPanelView(QWidget):
|
||||
filepath: Path = unwrap(self.lib.library_dir) / entry.path
|
||||
|
||||
if update_preview:
|
||||
stats: FileAttributeData = self.__thumb.display_file(filepath)
|
||||
self.__file_attrs.update_stats(filepath, stats)
|
||||
self.__file_attrs.update_date_label(filepath)
|
||||
stats: FileAttributeData = self._thumb.display_file(filepath)
|
||||
self._file_attrs.update_stats(filepath, stats)
|
||||
self._file_attrs.update_date_label(filepath)
|
||||
self._containers.update_from_entry(entry_id)
|
||||
|
||||
self._set_selection_callback()
|
||||
@@ -147,9 +181,9 @@ class PreviewPanelView(QWidget):
|
||||
# Multiple Selected Items
|
||||
elif len(selected) > 1:
|
||||
# items: list[Entry] = [self.lib.get_entry_full(x) for x in self.driver.selected]
|
||||
self.__thumb.hide_preview() # TODO: Render mixed selection
|
||||
self.__file_attrs.update_multi_selection(len(selected))
|
||||
self.__file_attrs.update_date_label()
|
||||
self._thumb.hide_preview() # TODO: Render mixed selection
|
||||
self._file_attrs.update_multi_selection(len(selected))
|
||||
self._file_attrs.update_date_label()
|
||||
self._containers.hide_containers() # TODO: Allow for mixed editing
|
||||
|
||||
self._set_selection_callback()
|
||||
@@ -175,7 +209,7 @@ class PreviewPanelView(QWidget):
|
||||
@property
|
||||
def _file_attributes_widget(self) -> FileAttributes: # needed for the tests
|
||||
"""Getter for the file attributes widget."""
|
||||
return self.__file_attrs
|
||||
return self._file_attrs
|
||||
|
||||
@property
|
||||
def field_containers_widget(self) -> FieldContainers: # needed for the tests
|
||||
@@ -184,4 +218,4 @@ class PreviewPanelView(QWidget):
|
||||
|
||||
@property
|
||||
def preview_thumb(self) -> PreviewThumb:
|
||||
return self.__thumb
|
||||
return self._thumb
|
||||
|
||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
import structlog
|
||||
from PySide6.QtCore import QBuffer, QByteArray, QSize, Qt
|
||||
from PySide6.QtCore import QBuffer, QByteArray, QSize, Qt, Signal
|
||||
from PySide6.QtGui import QAction, QMovie, QPixmap, QResizeEvent
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QStackedLayout, QWidget
|
||||
|
||||
@@ -33,6 +33,8 @@ THUMB_SIZE_FACTOR = 2
|
||||
class PreviewThumbView(QWidget):
|
||||
"""The Preview Panel Widget."""
|
||||
|
||||
check_ffmpeg = Signal(bool)
|
||||
|
||||
__img_button_size: tuple[int, int]
|
||||
__image_ratio: float
|
||||
|
||||
@@ -133,12 +135,7 @@ class PreviewThumbView(QWidget):
|
||||
|
||||
def __thumb_renderer_updated_ratio_callback(self, ratio: float) -> None:
|
||||
self.__image_ratio = ratio
|
||||
self.__update_image_size(
|
||||
(
|
||||
self.size().width(),
|
||||
self.size().height(),
|
||||
)
|
||||
)
|
||||
self.__update_image_size((self.size().width(), self.size().height()))
|
||||
|
||||
def __stacked_page_setup(self, page: QWidget, widget: QWidget) -> None:
|
||||
layout = QHBoxLayout(page)
|
||||
@@ -186,9 +183,11 @@ class PreviewThumbView(QWidget):
|
||||
if preview in [MediaType.AUDIO, MediaType.VIDEO]:
|
||||
self.__media_player.show()
|
||||
self.__image_layout.setCurrentWidget(self.__media_player_page)
|
||||
self.check_ffmpeg.emit(True) # noqa: FBT003
|
||||
else:
|
||||
self.__media_player.stop()
|
||||
self.__media_player.hide()
|
||||
self.check_ffmpeg.emit(False) # noqa: FBT003
|
||||
|
||||
if preview in [MediaType.IMAGE, MediaType.AUDIO]:
|
||||
self.__button_wrapper.show()
|
||||
|
||||
@@ -372,6 +372,15 @@ def title_line_edit_style() -> str:
|
||||
"""
|
||||
|
||||
|
||||
def preview_warning_style() -> str:
|
||||
return f"""
|
||||
QWidget#ffmpeg_widget {{
|
||||
background: {get_ui_color(ColorType.DARK_ACCENT, UiColor.RED)};
|
||||
border-radius: 6px;
|
||||
}}
|
||||
"""
|
||||
|
||||
|
||||
def header(string: str, level: int, color: str | None = None) -> str:
|
||||
"""Wrap a string in HTML header tags.
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
@@ -71,7 +71,6 @@
|
||||
"entries.unlinked.search_and_relink": "&Search && Relink",
|
||||
"entries.unlinked.title": "Fix Unlinked Entries",
|
||||
"entries.unlinked.unlinked_count": "Unlinked Entries: {count}",
|
||||
"ffmpeg.missing.description": "FFmpeg and/or FFprobe were not found. FFmpeg is required for multimedia playback and thumbnails.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field_template_manager.title": "Library Field Templates",
|
||||
"field_template.all_field_templates": "All Field Templates",
|
||||
@@ -137,6 +136,7 @@
|
||||
"generic.delete_alt": "&Delete",
|
||||
"generic.done": "Done",
|
||||
"generic.done_alt": "&Done",
|
||||
"generic.dont_remind": "Don’t Remind Me Again",
|
||||
"generic.edit": "Edit",
|
||||
"generic.edit_alt": "&Edit",
|
||||
"generic.filename": "Filename",
|
||||
@@ -268,6 +268,8 @@
|
||||
"namespace.new.button": "New Namespace",
|
||||
"namespace.new.prompt": "Create a New Namespace to Start Adding Custom Colors!",
|
||||
"preview.ignored": "Ignored",
|
||||
"preview.missing_module.jxl": "{module} is required for JPEG XL previews",
|
||||
"preview.missing_module.multimedia": "{module} is required for multimedia playback",
|
||||
"preview.multiple_selection": "<b>{count}</b> Items Selected",
|
||||
"preview.no_selection": "No Items Selected",
|
||||
"preview.unlinked": "Unlinked",
|
||||
@@ -372,6 +374,7 @@
|
||||
"trash.dialog.title.singular": "Delete File",
|
||||
"trash.name.generic": "Trash",
|
||||
"trash.name.windows": "Recycle Bin",
|
||||
"update.view_update": "View Update",
|
||||
"version_modal.description": "A new version of TagStudio is available! You can download the latest release from <a href=\"{github_url}\">GitHub</a>.",
|
||||
"version_modal.status": "Installed Version: {installed_version}<br>Latest Release Version: {latest_release_version}",
|
||||
"version_modal.title": "TagStudio Update Available",
|
||||
|
||||
@@ -7,7 +7,7 @@ from pathlib import Path
|
||||
from PySide6.QtCore import QSettings
|
||||
|
||||
from tagstudio.core.driver import DriverMixin
|
||||
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
|
||||
|
||||
@@ -43,7 +43,7 @@ def test_evaluate_path_missing():
|
||||
def test_evaluate_path_last_lib_not_exists():
|
||||
# Given
|
||||
cache = QSettings()
|
||||
cache.setValue(SettingItems.LAST_LIBRARY, "/0/4/5/1/")
|
||||
cache.setValue(AppCacheItems.LAST_LIBRARY, "/0/4/5/1/")
|
||||
driver = TestDriver(GlobalSettings(), cache)
|
||||
|
||||
# When
|
||||
@@ -57,7 +57,7 @@ def test_evaluate_path_last_lib_present(library_dir: Path):
|
||||
# Given
|
||||
cache_file = library_dir / "test_settings.ini"
|
||||
cache = QSettings(str(cache_file), QSettings.Format.IniFormat)
|
||||
cache.setValue(SettingItems.LAST_LIBRARY, library_dir)
|
||||
cache.setValue(AppCacheItems.LAST_LIBRARY, library_dir)
|
||||
cache.sync()
|
||||
|
||||
settings = GlobalSettings()
|
||||
|
||||
Reference in New Issue
Block a user