Slideshow toolbar, unsave from library, fix async error handling
- Add Favorite/Save/Unsave buttons to slideshow mode toolbar - Add "Unsave from Library" to grid and preview right-click menus - Fix silent exception swallowing in persistent event loop - Fix closeEvent race condition with async thread join
This commit is contained in:
parent
afa08ff007
commit
4675c0a691
@ -217,7 +217,15 @@ class BooruApp(QMainWindow):
|
||||
self._status.showMessage(f"Error: {e}")
|
||||
|
||||
def _run_async(self, coro_func, *args):
|
||||
asyncio.run_coroutine_threadsafe(coro_func(*args), self._async_loop)
|
||||
future = asyncio.run_coroutine_threadsafe(coro_func(*args), self._async_loop)
|
||||
future.add_done_callback(self._on_async_done)
|
||||
|
||||
@staticmethod
|
||||
def _on_async_done(future):
|
||||
try:
|
||||
future.result()
|
||||
except Exception as e:
|
||||
log.error(f"Async worker failed: {e}")
|
||||
|
||||
def _setup_ui(self) -> None:
|
||||
central = QWidget()
|
||||
@ -303,6 +311,7 @@ class BooruApp(QMainWindow):
|
||||
self._preview.open_in_browser.connect(self._open_preview_in_browser)
|
||||
self._preview.favorite_requested.connect(self._favorite_from_preview)
|
||||
self._preview.save_to_folder.connect(self._save_from_preview)
|
||||
self._preview.unsave_requested.connect(self._unsave_from_preview)
|
||||
self._preview.navigate.connect(self._navigate_preview)
|
||||
self._preview.fullscreen_requested.connect(self._open_fullscreen_preview)
|
||||
self._preview.set_folders_callback(self._db.get_folders)
|
||||
@ -841,6 +850,26 @@ class BooruApp(QMainWindow):
|
||||
self._db.add_folder(folder)
|
||||
self._save_to_library(self._posts[idx], target)
|
||||
|
||||
def _unsave_from_preview(self) -> None:
|
||||
idx = self._grid.selected_index
|
||||
if 0 <= idx < len(self._posts):
|
||||
post = self._posts[idx]
|
||||
from ..core.cache import delete_from_library
|
||||
site_id = self._site_combo.currentData()
|
||||
folder = None
|
||||
if site_id:
|
||||
favs = self._db.get_favorites(site_id=site_id)
|
||||
for f in favs:
|
||||
if f.post_id == post.id and f.folder:
|
||||
folder = f.folder
|
||||
break
|
||||
if delete_from_library(post.id, folder):
|
||||
self._status.showMessage(f"Removed #{post.id} from library")
|
||||
if 0 <= idx < len(self._grid._thumbs):
|
||||
self._grid._thumbs[idx].set_saved_locally(False)
|
||||
else:
|
||||
self._status.showMessage(f"#{post.id} not in library")
|
||||
|
||||
def _open_fullscreen_preview(self) -> None:
|
||||
path = self._preview._current_path
|
||||
if not path:
|
||||
@ -851,6 +880,9 @@ class BooruApp(QMainWindow):
|
||||
cols = self._grid._flow.columns
|
||||
self._fullscreen_window = FullscreenPreview(grid_cols=cols, parent=self)
|
||||
self._fullscreen_window.navigate.connect(self._navigate_fullscreen)
|
||||
self._fullscreen_window.favorite_requested.connect(self._favorite_from_preview)
|
||||
self._fullscreen_window.save_requested.connect(lambda: self._save_from_preview(""))
|
||||
self._fullscreen_window.unsave_requested.connect(self._unsave_from_preview)
|
||||
self._fullscreen_window.destroyed.connect(self._on_fullscreen_closed)
|
||||
self._fullscreen_window.set_media(path, self._preview._info_label.text())
|
||||
|
||||
@ -893,6 +925,7 @@ class BooruApp(QMainWindow):
|
||||
save_lib_menu.addSeparator()
|
||||
save_lib_new = save_lib_menu.addAction("+ New Folder...")
|
||||
|
||||
unsave_lib = menu.addAction("Unsave from Library")
|
||||
copy_url = menu.addAction("Copy Image URL")
|
||||
copy_tags = menu.addAction("Copy Tags")
|
||||
menu.addSeparator()
|
||||
@ -922,6 +955,22 @@ class BooruApp(QMainWindow):
|
||||
self._save_to_library(post, name.strip())
|
||||
elif id(action) in save_lib_folders:
|
||||
self._save_to_library(post, save_lib_folders[id(action)])
|
||||
elif action == unsave_lib:
|
||||
from ..core.cache import delete_from_library
|
||||
site_id = self._site_combo.currentData()
|
||||
folder = None
|
||||
if site_id:
|
||||
favs = self._db.get_favorites(site_id=site_id)
|
||||
for f in favs:
|
||||
if f.post_id == post.id and f.folder:
|
||||
folder = f.folder
|
||||
break
|
||||
if delete_from_library(post.id, folder):
|
||||
self._status.showMessage(f"Removed #{post.id} from library")
|
||||
if 0 <= index < len(self._grid._thumbs):
|
||||
self._grid._thumbs[index].set_saved_locally(False)
|
||||
else:
|
||||
self._status.showMessage(f"#{post.id} not in library")
|
||||
elif action == copy_url:
|
||||
QApplication.clipboard().setText(post.file_url)
|
||||
self._status.showMessage("URL copied")
|
||||
@ -1376,6 +1425,7 @@ class BooruApp(QMainWindow):
|
||||
|
||||
def closeEvent(self, event) -> None:
|
||||
self._async_loop.call_soon_threadsafe(self._async_loop.stop)
|
||||
self._async_thread.join(timeout=2)
|
||||
if self._db.get_setting_bool("clear_cache_on_exit"):
|
||||
from ..core.cache import clear_cache
|
||||
clear_cache(clear_images=True, clear_thumbnails=True)
|
||||
|
||||
@ -25,15 +25,50 @@ def _is_video(path: str) -> bool:
|
||||
class FullscreenPreview(QMainWindow):
|
||||
"""Fullscreen media viewer with navigation — images, GIFs, and video."""
|
||||
|
||||
navigate = Signal(int) # -1 = prev, +1 = next
|
||||
navigate = Signal(int) # direction: -1/+1 for left/right, -cols/+cols for up/down
|
||||
favorite_requested = Signal()
|
||||
save_requested = Signal()
|
||||
unsave_requested = Signal()
|
||||
|
||||
def __init__(self, grid_cols: int = 3, parent=None) -> None:
|
||||
super().__init__(parent, Qt.WindowType.Window)
|
||||
self.setWindowTitle("booru-viewer — Fullscreen")
|
||||
self._grid_cols = grid_cols
|
||||
|
||||
central = QWidget()
|
||||
main_layout = QVBoxLayout(central)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.setSpacing(0)
|
||||
|
||||
# Top toolbar
|
||||
toolbar = QHBoxLayout()
|
||||
toolbar.setContentsMargins(8, 4, 8, 4)
|
||||
|
||||
self._fav_btn = QPushButton("Favorite")
|
||||
self._fav_btn.setFixedWidth(80)
|
||||
self._fav_btn.clicked.connect(self.favorite_requested)
|
||||
toolbar.addWidget(self._fav_btn)
|
||||
|
||||
self._save_btn = QPushButton("Save")
|
||||
self._save_btn.setFixedWidth(60)
|
||||
self._save_btn.clicked.connect(self.save_requested)
|
||||
toolbar.addWidget(self._save_btn)
|
||||
|
||||
self._unsave_btn = QPushButton("Unsave")
|
||||
self._unsave_btn.setFixedWidth(70)
|
||||
self._unsave_btn.clicked.connect(self.unsave_requested)
|
||||
toolbar.addWidget(self._unsave_btn)
|
||||
|
||||
toolbar.addStretch()
|
||||
|
||||
self._info_label = QLabel()
|
||||
toolbar.addWidget(self._info_label)
|
||||
|
||||
main_layout.addLayout(toolbar)
|
||||
|
||||
# Media stack
|
||||
self._stack = QStackedWidget()
|
||||
self.setCentralWidget(self._stack)
|
||||
main_layout.addWidget(self._stack, stretch=1)
|
||||
|
||||
self._viewer = ImageViewer()
|
||||
self._viewer.close_requested.connect(self.close)
|
||||
@ -42,11 +77,14 @@ class FullscreenPreview(QMainWindow):
|
||||
self._video = VideoPlayer()
|
||||
self._stack.addWidget(self._video)
|
||||
|
||||
self.setCentralWidget(central)
|
||||
|
||||
from PySide6.QtWidgets import QApplication
|
||||
QApplication.instance().installEventFilter(self)
|
||||
self.showFullScreen()
|
||||
|
||||
def set_media(self, path: str, info: str = "") -> None:
|
||||
self._info_label.setText(info)
|
||||
ext = Path(path).suffix.lower()
|
||||
if _is_video(path):
|
||||
self._viewer.clear()
|
||||
@ -402,6 +440,7 @@ class ImagePreview(QWidget):
|
||||
open_in_default = Signal()
|
||||
open_in_browser = Signal()
|
||||
save_to_folder = Signal(str)
|
||||
unsave_requested = Signal()
|
||||
favorite_requested = Signal()
|
||||
navigate = Signal(int) # -1 = prev, +1 = next
|
||||
fullscreen_requested = Signal()
|
||||
@ -511,6 +550,9 @@ class ImagePreview(QWidget):
|
||||
if self._stack.currentIndex() == 0:
|
||||
reset_action = menu.addAction("Reset View")
|
||||
|
||||
menu.addSeparator()
|
||||
unsave_action = menu.addAction("Unsave from Library")
|
||||
|
||||
slideshow_action = None
|
||||
if self._current_path:
|
||||
slideshow_action = menu.addAction("Slideshow Mode")
|
||||
@ -539,6 +581,8 @@ class ImagePreview(QWidget):
|
||||
elif action == reset_action:
|
||||
self._image_viewer._fit_to_view()
|
||||
self._image_viewer.update()
|
||||
elif action == unsave_action:
|
||||
self.unsave_requested.emit()
|
||||
elif action == slideshow_action:
|
||||
self.fullscreen_requested.emit()
|
||||
elif action == clear_action:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user