mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-02-04 17:09:33 +00:00
feat: expanded file deletion/trashing (#409)
* feat: send deleted files to system trash This refactors the file deletion code to send files to the system trash instead of performing a hard deletion. It also fixes deleting video files and GIFs loaded in the Preview Panel. * feat(ui): add file deletion confirmation boxes * feat(ui): add delete file menu option + shortcut * ui: update file deletion message boxes * fix(ui): same default confirm button on win/mac - Make "Yes" the default choice in the delete file modal for both Windows and macOS (Linux untested) - Change status messages to be more broad, since they also are displayed when cancelling the operation * ui: show perm deletion warning on all platforms
This commit is contained in:
committed by
GitHub
parent
85d62e6519
commit
8219ffc416
BIN
tagstudio/resources/qt/videos/placeholder.mp4
Normal file
BIN
tagstudio/resources/qt/videos/placeholder.mp4
Normal file
Binary file not shown.
@@ -23,6 +23,7 @@ class MediaType(str, Enum):
|
||||
DISK_IMAGE: str = "disk_image"
|
||||
DOCUMENT: str = "document"
|
||||
FONT: str = "font"
|
||||
IMAGE_ANIMATED: str = "image_animated"
|
||||
IMAGE_RAW: str = "image_raw"
|
||||
IMAGE_VECTOR: str = "image_vector"
|
||||
IMAGE: str = "image"
|
||||
@@ -169,6 +170,12 @@ class MediaCategories:
|
||||
".woff",
|
||||
".woff2",
|
||||
}
|
||||
_IMAGE_ANIMATED_SET: set[str] = {
|
||||
".apng",
|
||||
".gif",
|
||||
".webp",
|
||||
".jxl",
|
||||
}
|
||||
_IMAGE_RAW_SET: set[str] = {
|
||||
".arw",
|
||||
".cr2",
|
||||
@@ -335,6 +342,11 @@ class MediaCategories:
|
||||
extensions=_FONT_SET,
|
||||
is_iana=True,
|
||||
)
|
||||
IMAGE_ANIMATED_TYPES: MediaCategory = MediaCategory(
|
||||
media_type=MediaType.IMAGE_ANIMATED,
|
||||
extensions=_IMAGE_ANIMATED_SET,
|
||||
is_iana=False,
|
||||
)
|
||||
IMAGE_RAW_TYPES: MediaCategory = MediaCategory(
|
||||
media_type=MediaType.IMAGE_RAW,
|
||||
extensions=_IMAGE_RAW_SET,
|
||||
@@ -427,6 +439,7 @@ class MediaCategories:
|
||||
DISK_IMAGE_TYPES,
|
||||
DOCUMENT_TYPES,
|
||||
FONT_TYPES,
|
||||
IMAGE_ANIMATED_TYPES,
|
||||
IMAGE_RAW_TYPES,
|
||||
IMAGE_TYPES,
|
||||
IMAGE_VECTOR_TYPES,
|
||||
|
||||
30
tagstudio/src/qt/helpers/file_deleter.py
Normal file
30
tagstudio/src/qt/helpers/file_deleter.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from send2trash import send2trash
|
||||
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
|
||||
def delete_file(path: str | Path) -> bool:
|
||||
"""Sends a file to the system trash.
|
||||
|
||||
Args:
|
||||
path (str | Path): The path of the file to delete.
|
||||
"""
|
||||
_path = Path(path)
|
||||
try:
|
||||
logging.info(f"[delete_file] Sending to Trash: {_path}")
|
||||
send2trash(_path)
|
||||
return True
|
||||
except PermissionError as e:
|
||||
logging.error(f"[delete_file][ERROR] PermissionError: {e}")
|
||||
except FileNotFoundError:
|
||||
logging.error(f"[delete_file][ERROR] File Not Found: {_path}")
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
return False
|
||||
@@ -31,6 +31,20 @@ class ResourceManager:
|
||||
)
|
||||
ResourceManager._initialized = True
|
||||
|
||||
@staticmethod
|
||||
def get_path(id: str) -> Path | None:
|
||||
"""Get a resource's path from the ResourceManager.
|
||||
Args:
|
||||
id (str): The name of the resource.
|
||||
|
||||
Returns:
|
||||
Path: The resource path if found, else None.
|
||||
"""
|
||||
res: dict = ResourceManager._map.get(id)
|
||||
if res:
|
||||
return Path(__file__).parents[2] / "resources" / res.get("path")
|
||||
return None
|
||||
|
||||
def get(self, id: str) -> Any:
|
||||
"""Get a resource from the ResourceManager.
|
||||
This can include resources inside and outside of QResources, and will return
|
||||
|
||||
@@ -90,5 +90,9 @@
|
||||
"thumb_loading": {
|
||||
"path": "qt/images/thumb_loading.png",
|
||||
"mode": "pil"
|
||||
},
|
||||
"placeholder_mp4": {
|
||||
"path": "qt/videos/placeholder.mp4",
|
||||
"mode": "rb"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import copy
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import time
|
||||
import typing
|
||||
@@ -53,6 +54,7 @@ from PySide6.QtWidgets import (
|
||||
QMenu,
|
||||
QMenuBar,
|
||||
QComboBox,
|
||||
QMessageBox,
|
||||
)
|
||||
from humanfriendly import format_timespan
|
||||
|
||||
@@ -69,9 +71,11 @@ from src.core.constants import (
|
||||
TAG_FAVORITE,
|
||||
TAG_ARCHIVED,
|
||||
)
|
||||
from src.core.palette import ColorType, get_ui_color
|
||||
from src.core.utils.web import strip_web_protocol
|
||||
from src.qt.flowlayout import FlowLayout
|
||||
from src.qt.main_window import Ui_MainWindow
|
||||
from src.qt.helpers.file_deleter import delete_file
|
||||
from src.qt.helpers.function_iterator import FunctionIterator
|
||||
from src.qt.helpers.custom_runnable import CustomRunnable
|
||||
from src.qt.resource_manager import ResourceManager
|
||||
@@ -446,6 +450,15 @@ class QtDriver(QObject):
|
||||
|
||||
edit_menu.addSeparator()
|
||||
|
||||
self.delete_file_action = QAction("Delete Selected File(s)", menu_bar)
|
||||
self.delete_file_action.triggered.connect(
|
||||
lambda f="": self.delete_files_callback(f)
|
||||
)
|
||||
self.delete_file_action.setShortcut(QtCore.Qt.Key.Key_Delete)
|
||||
edit_menu.addAction(self.delete_file_action)
|
||||
|
||||
edit_menu.addSeparator()
|
||||
|
||||
manage_file_extensions_action = QAction("Manage File Extensions", menu_bar)
|
||||
manage_file_extensions_action.triggered.connect(
|
||||
lambda: self.show_file_extension_modal()
|
||||
@@ -532,13 +545,13 @@ class QtDriver(QObject):
|
||||
folders_to_tags_action.triggered.connect(lambda: ftt_modal.show())
|
||||
macros_menu.addAction(folders_to_tags_action)
|
||||
|
||||
# Help Menu ==========================================================
|
||||
# Help Menu ============================================================
|
||||
self.repo_action = QAction("Visit GitHub Repository", menu_bar)
|
||||
self.repo_action.triggered.connect(
|
||||
lambda: webbrowser.open("https://github.com/TagStudioDev/TagStudio")
|
||||
)
|
||||
help_menu.addAction(self.repo_action)
|
||||
self.set_macro_menu_viability()
|
||||
self.set_menu_action_viability()
|
||||
|
||||
self.update_clipboard_actions()
|
||||
|
||||
@@ -549,6 +562,9 @@ class QtDriver(QObject):
|
||||
menu_bar.addMenu(window_menu)
|
||||
menu_bar.addMenu(help_menu)
|
||||
|
||||
# ======================================================================
|
||||
|
||||
# Preview Panel --------------------------------------------------------
|
||||
self.preview_panel = PreviewPanel(self.lib, self)
|
||||
l: QHBoxLayout = self.main_window.splitter
|
||||
l.addWidget(self.preview_panel)
|
||||
@@ -758,7 +774,7 @@ class QtDriver(QObject):
|
||||
self.copied_fields.clear()
|
||||
self.is_buffer_merged = False
|
||||
self.update_clipboard_actions()
|
||||
self.set_macro_menu_viability()
|
||||
self.set_menu_action_viability()
|
||||
self.preview_panel.update_widgets()
|
||||
self.filter_items()
|
||||
self.main_window.toggle_landing_page(True)
|
||||
@@ -796,7 +812,7 @@ class QtDriver(QObject):
|
||||
self.selected.append((item.mode, item.item_id))
|
||||
item.thumb_button.set_selected(True)
|
||||
|
||||
self.set_macro_menu_viability()
|
||||
self.set_menu_action_viability()
|
||||
self.preview_panel.update_widgets()
|
||||
|
||||
def clear_select_action_callback(self):
|
||||
@@ -804,7 +820,7 @@ class QtDriver(QObject):
|
||||
for item in self.item_thumbs:
|
||||
item.thumb_button.set_selected(False)
|
||||
|
||||
self.set_macro_menu_viability()
|
||||
self.set_menu_action_viability()
|
||||
self.preview_panel.update_widgets()
|
||||
|
||||
def show_tag_database(self):
|
||||
@@ -827,6 +843,114 @@ class QtDriver(QObject):
|
||||
self.modal.saved.connect(lambda: (panel.save(), self.filter_items("")))
|
||||
self.modal.show()
|
||||
|
||||
def delete_files_callback(self, origin_path: str | Path):
|
||||
"""Callback to send on or more files to the system trash.
|
||||
|
||||
If 0-1 items are currently selected, the origin_path is used to delete the file
|
||||
from the originating context menu item.
|
||||
If there are currently multiple items selected,
|
||||
then the selection buffer is used to determine the files to be deleted.
|
||||
|
||||
Args:
|
||||
origin_path(str): The file path associated with the widget making the call.
|
||||
May or may not be the file targeted, depending on the selection rules.
|
||||
"""
|
||||
entry = None
|
||||
pending: list[Path] = []
|
||||
deleted_count: int = 0
|
||||
|
||||
if len(self.selected) <= 1 and origin_path:
|
||||
pending.append(Path(origin_path))
|
||||
elif (len(self.selected) > 1) or (len(self.selected) <= 1 and not origin_path):
|
||||
for i, item_pair in enumerate(self.selected):
|
||||
if item_pair[0] == ItemType.ENTRY:
|
||||
entry = self.lib.get_entry(item_pair[1])
|
||||
filepath: Path = self.lib.library_dir / entry.path / entry.filename
|
||||
pending.append(filepath)
|
||||
|
||||
if pending:
|
||||
return_code = self.delete_file_confirmation(len(pending), pending[0])
|
||||
logging.info(return_code)
|
||||
# If there was a confirmation and not a cancellation
|
||||
if return_code == 2 and return_code != 3:
|
||||
for i, f in enumerate(pending):
|
||||
if (origin_path == f) or (not origin_path):
|
||||
self.preview_panel.stop_file_use()
|
||||
if delete_file(f):
|
||||
self.main_window.statusbar.showMessage(
|
||||
f'Deleting file [{i}/{len(pending)}]: "{f}"...'
|
||||
)
|
||||
self.main_window.statusbar.repaint()
|
||||
|
||||
entry_id = self.lib.get_entry_id_from_filepath(f)
|
||||
self.lib.remove_entry(entry_id)
|
||||
self.purge_item_from_navigation(ItemType.ENTRY, entry_id)
|
||||
deleted_count += 1
|
||||
self.selected.clear()
|
||||
|
||||
if deleted_count > 0:
|
||||
self.filter_items()
|
||||
self.preview_panel.update_widgets()
|
||||
|
||||
if len(self.selected) <= 1 and deleted_count == 0:
|
||||
self.main_window.statusbar.showMessage("No files deleted.")
|
||||
elif len(self.selected) <= 1 and deleted_count == 1:
|
||||
self.main_window.statusbar.showMessage(f"Deleted {deleted_count} file!")
|
||||
elif len(self.selected) > 1 and deleted_count == 0:
|
||||
self.main_window.statusbar.showMessage("No files deleted.")
|
||||
elif len(self.selected) > 1 and deleted_count < len(self.selected):
|
||||
self.main_window.statusbar.showMessage(
|
||||
f"Only deleted {deleted_count} file{'' if deleted_count == 1 else 's'}! Check if any of the files are currently missing or in use."
|
||||
)
|
||||
elif len(self.selected) > 1 and deleted_count == len(self.selected):
|
||||
self.main_window.statusbar.showMessage(f"Deleted {deleted_count} files!")
|
||||
self.main_window.statusbar.repaint()
|
||||
|
||||
def delete_file_confirmation(self, count: int, filename: Path | None = None) -> int:
|
||||
"""A confirmation dialogue box for deleting files.
|
||||
|
||||
Args:
|
||||
count(int): The number of files to be deleted.
|
||||
filename(Path | None): The filename to show if only one file is to be deleted.
|
||||
"""
|
||||
trash_term: str = "Trash"
|
||||
if platform.system() == "Windows":
|
||||
trash_term = "Recycle Bin"
|
||||
# NOTE: Windows + send2trash will PERMANENTLY delete files which cannot be moved to the Recycle Bin.
|
||||
# This is done without any warning, so this message is currently the best way I've got to inform the user.
|
||||
# https://github.com/arsenetar/send2trash/issues/28
|
||||
# This warning is applied to all platforms until at least macOS and Linux can be verified to
|
||||
# not exhibit this same behavior.
|
||||
perm_warning: str = (
|
||||
f"<h4 style='color: {get_ui_color(ColorType.PRIMARY, 'red')}'>"
|
||||
f"<b>WARNING!</b> If this file can't be moved to the {trash_term}, "
|
||||
f"</b>it will be <b>permanently deleted!</b></h4>"
|
||||
)
|
||||
|
||||
msg = QMessageBox()
|
||||
msg.setTextFormat(Qt.TextFormat.RichText)
|
||||
msg.setWindowTitle("Delete File" if count == 1 else "Delete Files")
|
||||
msg.setIcon(QMessageBox.Icon.Warning)
|
||||
if count <= 1:
|
||||
msg.setText(
|
||||
f"<h3>Are you sure you want to move this file to the {trash_term}?</h3>"
|
||||
"<h4>This will remove it from TagStudio <i>AND</i> your file system!</h4>"
|
||||
f"{filename if filename else ''}"
|
||||
f"{perm_warning}<br>"
|
||||
)
|
||||
elif count > 1:
|
||||
msg.setText(
|
||||
f"<h3>Are you sure you want to move these {count} files to the {trash_term}?</h3>"
|
||||
"<h4>This will remove them from TagStudio <i>AND</i> your file system!</h4>"
|
||||
f"{perm_warning}<br>"
|
||||
)
|
||||
|
||||
yes_button: QPushButton = msg.addButton("&Yes", QMessageBox.ButtonRole.YesRole)
|
||||
msg.addButton("&No", QMessageBox.ButtonRole.NoRole)
|
||||
msg.setDefaultButton(yes_button)
|
||||
|
||||
return msg.exec()
|
||||
|
||||
def add_new_files_callback(self):
|
||||
"""Runs when user initiates adding new files to the Library."""
|
||||
# # if self.lib.files_not_in_library:
|
||||
@@ -1397,17 +1521,19 @@ class QtDriver(QObject):
|
||||
if it.mode == type and it.item_id == id:
|
||||
self.preview_panel.set_tags_updated_slot(it.update_badges)
|
||||
|
||||
self.set_macro_menu_viability()
|
||||
self.set_menu_action_viability()
|
||||
self.update_clipboard_actions()
|
||||
self.preview_panel.update_widgets()
|
||||
|
||||
def set_macro_menu_viability(self):
|
||||
def set_menu_action_viability(self):
|
||||
if len([x[1] for x in self.selected if x[0] == ItemType.ENTRY]) == 0:
|
||||
self.autofill_action.setDisabled(True)
|
||||
self.sort_fields_action.setDisabled(True)
|
||||
self.delete_file_action.setDisabled(True)
|
||||
else:
|
||||
self.autofill_action.setDisabled(False)
|
||||
self.sort_fields_action.setDisabled(False)
|
||||
self.delete_file_action.setDisabled(False)
|
||||
|
||||
def update_thumbs(self):
|
||||
"""Updates search thumbnails."""
|
||||
@@ -1456,12 +1582,20 @@ class QtDriver(QObject):
|
||||
|
||||
for i, item_thumb in enumerate(self.item_thumbs, start=0):
|
||||
if i < len(self.nav_frames[self.cur_frame_idx].contents):
|
||||
filepath = ""
|
||||
filepath: Path = None # Initialize
|
||||
if self.nav_frames[self.cur_frame_idx].contents[i][0] == ItemType.ENTRY:
|
||||
entry = self.lib.get_entry(
|
||||
self.nav_frames[self.cur_frame_idx].contents[i][1]
|
||||
)
|
||||
filepath = self.lib.library_dir / entry.path / entry.filename
|
||||
filepath: Path = self.lib.library_dir / entry.path / entry.filename
|
||||
|
||||
try:
|
||||
item_thumb.delete_action.triggered.disconnect()
|
||||
except RuntimeWarning:
|
||||
pass
|
||||
item_thumb.delete_action.triggered.connect(
|
||||
lambda checked=False, f=filepath: self.delete_files_callback(f)
|
||||
)
|
||||
|
||||
item_thumb.set_item_id(entry.id)
|
||||
item_thumb.assign_archived(entry.has_tag(self.lib, TAG_ARCHIVED))
|
||||
@@ -1506,7 +1640,9 @@ class QtDriver(QObject):
|
||||
else collation.e_ids_and_pages[0][0]
|
||||
)
|
||||
cover_e = self.lib.get_entry(cover_id)
|
||||
filepath = self.lib.library_dir / cover_e.path / cover_e.filename
|
||||
filepath: Path = (
|
||||
self.lib.library_dir / cover_e.path / cover_e.filename
|
||||
)
|
||||
item_thumb.set_count(str(len(collation.e_ids_and_pages)))
|
||||
item_thumb.update_clickable(
|
||||
clickable=(
|
||||
|
||||
@@ -124,6 +124,7 @@ class FieldContainer(QWidget):
|
||||
def set_copy_callback(self, callback: Optional[MethodType]):
|
||||
if self.copy_button.is_connected:
|
||||
self.copy_button.clicked.disconnect()
|
||||
self.copy_button.is_connected = False
|
||||
|
||||
self.copy_callback = callback
|
||||
self.copy_button.clicked.connect(callback)
|
||||
@@ -133,6 +134,7 @@ class FieldContainer(QWidget):
|
||||
def set_edit_callback(self, callback: Optional[MethodType]):
|
||||
if self.edit_button.is_connected:
|
||||
self.edit_button.clicked.disconnect()
|
||||
self.edit_button.is_connected = False
|
||||
|
||||
self.edit_callback = callback
|
||||
self.edit_button.clicked.connect(callback)
|
||||
@@ -142,10 +144,12 @@ class FieldContainer(QWidget):
|
||||
def set_remove_callback(self, callback: Optional[Callable]):
|
||||
if self.remove_button.is_connected:
|
||||
self.remove_button.clicked.disconnect()
|
||||
self.remove_button.is_connected = False
|
||||
|
||||
self.remove_callback = callback
|
||||
self.remove_button.clicked.connect(callback)
|
||||
self.remove_button.is_connected = True
|
||||
if callback is not None:
|
||||
self.remove_button.is_connected = True
|
||||
|
||||
def set_inner_widget(self, widget: "FieldWidget"):
|
||||
if self.field_layout.itemAt(0):
|
||||
|
||||
@@ -9,6 +9,7 @@ import time
|
||||
import typing
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import platform
|
||||
|
||||
from PIL import Image, ImageQt
|
||||
from PySide6.QtCore import Qt, QSize, QEvent, QMimeData, QUrl
|
||||
@@ -207,8 +208,15 @@ class ItemThumb(FlowWidget):
|
||||
open_explorer_action = QAction("Open in Explorer", self)
|
||||
|
||||
open_explorer_action.triggered.connect(self.opener.open_explorer)
|
||||
|
||||
trash_term: str = "Trash"
|
||||
if platform.system() == "Windows":
|
||||
trash_term = "Recycle Bin"
|
||||
self.delete_action = QAction(f"Send file to {trash_term}", self)
|
||||
|
||||
self.thumb_button.addAction(open_file_action)
|
||||
self.thumb_button.addAction(open_explorer_action)
|
||||
self.thumb_button.addAction(self.delete_action)
|
||||
|
||||
# Static Badges ========================================================
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import cv2
|
||||
import rawpy
|
||||
from PIL import Image, UnidentifiedImageError, ImageFont
|
||||
from PIL.Image import DecompressionBombError
|
||||
from PySide6.QtCore import QModelIndex, Signal, Qt, QSize
|
||||
from PySide6.QtCore import QModelIndex, Signal, Qt, QSize, QByteArray, QBuffer
|
||||
from PySide6.QtGui import QGuiApplication, QResizeEvent, QAction, QMovie
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
@@ -46,6 +46,7 @@ from src.qt.widgets.text_line_edit import EditTextLine
|
||||
from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper
|
||||
from src.qt.widgets.video_player import VideoPlayer
|
||||
from src.qt.helpers.file_tester import is_readable_video
|
||||
from src.qt.resource_manager import ResourceManager
|
||||
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
@@ -98,14 +99,17 @@ class PreviewPanel(QWidget):
|
||||
image_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self.open_file_action = QAction("Open file", self)
|
||||
self.trash_term: str = "Trash"
|
||||
if platform.system() == "Windows":
|
||||
self.trash_term = "Recycle Bin"
|
||||
self.delete_action = QAction(f"Send file to {self.trash_term}", self)
|
||||
|
||||
system = platform.system()
|
||||
self.open_explorer_action = QAction(
|
||||
"Open in explorer", self
|
||||
) # Default (mainly going to be for linux)
|
||||
if system == "Darwin":
|
||||
) # Default text (Linux, etc.)
|
||||
if platform.system() == "Darwin":
|
||||
self.open_explorer_action = QAction("Reveal in Finder", self)
|
||||
elif system == "Windows":
|
||||
elif platform.system() == "Windows":
|
||||
self.open_explorer_action = QAction("Open in Explorer", self)
|
||||
|
||||
self.preview_img = QPushButtonWrapper()
|
||||
@@ -114,6 +118,7 @@ class PreviewPanel(QWidget):
|
||||
self.preview_img.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||
self.preview_img.addAction(self.open_file_action)
|
||||
self.preview_img.addAction(self.open_explorer_action)
|
||||
self.preview_img.addAction(self.delete_action)
|
||||
|
||||
self.preview_gif = QLabel()
|
||||
self.preview_gif.setMinimumSize(*self.img_button_size)
|
||||
@@ -121,10 +126,13 @@ class PreviewPanel(QWidget):
|
||||
self.preview_gif.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
self.preview_gif.addAction(self.open_file_action)
|
||||
self.preview_gif.addAction(self.open_explorer_action)
|
||||
self.preview_gif.addAction(self.delete_action)
|
||||
self.preview_gif.hide()
|
||||
self.gif_buffer: QBuffer = QBuffer()
|
||||
|
||||
self.preview_vid = VideoPlayer(driver)
|
||||
self.preview_vid.hide()
|
||||
self.preview_vid.addAction(self.delete_action)
|
||||
self.thumb_renderer = ThumbRenderer()
|
||||
self.thumb_renderer.updated.connect(
|
||||
lambda ts, i, s: (self.preview_img.setIcon(i))
|
||||
@@ -499,6 +507,14 @@ class PreviewPanel(QWidget):
|
||||
)
|
||||
if self.preview_img.is_connected:
|
||||
self.preview_img.clicked.disconnect()
|
||||
self.preview_img.is_connected = False
|
||||
|
||||
try:
|
||||
self.delete_action.triggered.disconnect()
|
||||
except RuntimeWarning:
|
||||
pass
|
||||
self.delete_action.setEnabled(False)
|
||||
|
||||
for i, c in enumerate(self.containers):
|
||||
c.setHidden(True)
|
||||
self.preview_img.show()
|
||||
@@ -538,6 +554,17 @@ class PreviewPanel(QWidget):
|
||||
)
|
||||
self.preview_img.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
|
||||
try:
|
||||
self.delete_action.triggered.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
self.delete_action.setText(f"Send file to {self.trash_term}")
|
||||
self.delete_action.triggered.connect(
|
||||
lambda checked=False,
|
||||
f=filepath: self.driver.delete_files_callback(f)
|
||||
)
|
||||
self.delete_action.setEnabled(True)
|
||||
|
||||
self.opener = FileOpenerHelper(filepath)
|
||||
self.open_file_action.triggered.connect(self.opener.open_file)
|
||||
self.open_explorer_action.triggered.connect(
|
||||
@@ -548,16 +575,24 @@ class PreviewPanel(QWidget):
|
||||
ext: str = filepath.suffix.lower()
|
||||
try:
|
||||
if filepath.suffix.lower() in [".gif"]:
|
||||
movie = QMovie(str(filepath))
|
||||
with open(filepath, mode="rb") as f:
|
||||
if self.preview_gif.movie():
|
||||
self.preview_gif.movie().stop()
|
||||
self.gif_buffer.close()
|
||||
|
||||
ba = f.read()
|
||||
self.gif_buffer.setData(ba)
|
||||
movie = QMovie(self.gif_buffer, QByteArray())
|
||||
self.preview_gif.setMovie(movie)
|
||||
movie.start()
|
||||
|
||||
image = Image.open(str(filepath))
|
||||
self.preview_gif.setMovie(movie)
|
||||
self.resizeEvent(
|
||||
QResizeEvent(
|
||||
QSize(image.width, image.height),
|
||||
QSize(image.width, image.height),
|
||||
)
|
||||
)
|
||||
movie.start()
|
||||
self.preview_img.hide()
|
||||
self.preview_vid.hide()
|
||||
self.preview_gif.show()
|
||||
@@ -669,6 +704,7 @@ class PreviewPanel(QWidget):
|
||||
# TODO: Implement a clickable label to use for the GIF preview.
|
||||
if self.preview_img.is_connected:
|
||||
self.preview_img.clicked.disconnect()
|
||||
self.preview_img.is_connected = False
|
||||
self.preview_img.clicked.connect(
|
||||
lambda checked=False, filepath=filepath: open_file(filepath)
|
||||
)
|
||||
@@ -710,6 +746,16 @@ class PreviewPanel(QWidget):
|
||||
)
|
||||
self.preview_img.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
|
||||
try:
|
||||
self.delete_action.triggered.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
self.delete_action.setText(f"Send files to {self.trash_term}")
|
||||
self.delete_action.triggered.connect(
|
||||
lambda checked=False, f=None: self.driver.delete_files_callback(f)
|
||||
)
|
||||
self.delete_action.setEnabled(True)
|
||||
|
||||
ratio: float = self.devicePixelRatio()
|
||||
self.thumb_renderer.render(
|
||||
time.time(),
|
||||
@@ -1149,3 +1195,26 @@ class PreviewPanel(QWidget):
|
||||
# logging.info(result)
|
||||
if result == 3:
|
||||
callback()
|
||||
|
||||
def stop_file_use(self):
|
||||
"""Stops the use of the currently previewed file. Used to release file permissions."""
|
||||
logging.info("[PreviewPanel] Stopping file use in video playback...")
|
||||
# This swaps the video out for a placeholder so the previous video's file
|
||||
# is no longer in use by this object.
|
||||
self.preview_vid.play(ResourceManager.get_path("placeholder_mp4"), QSize(8, 8))
|
||||
self.preview_vid.hide()
|
||||
|
||||
# NOTE: I'm keeping this here until #357 is merged in the case it still needs to be used.
|
||||
# logging.info("[PreviewPanel] Stopping file use for animated image playback...")
|
||||
# logging.info(self.preview_gif.movie())
|
||||
# if self.preview_gif.movie():
|
||||
# self.preview_gif.movie().stop()
|
||||
# with open(ResourceManager.get_path("placeholder_gif"), mode="rb") as f:
|
||||
# ba = f.read()
|
||||
# self.gif_buffer.setData(ba)
|
||||
# movie = QMovie(self.gif_buffer, QByteArray())
|
||||
# self.preview_gif.setMovie(movie)
|
||||
# movie.start()
|
||||
|
||||
# self.preview_gif.hide()
|
||||
# logging.info(self.preview_gif.movie())
|
||||
|
||||
Reference in New Issue
Block a user