v0.9.20
This commit is contained in:
@@ -14,7 +14,7 @@ Classes:
|
||||
MainWindow: The main application window containing the thumbnail grid and docks.
|
||||
"""
|
||||
__appname__ = "BagheeraView"
|
||||
__version__ = "0.9.19"
|
||||
__version__ = "0.9.20"
|
||||
__author__ = "Ignacio Serantes"
|
||||
__email__ = "kde@aynoa.net"
|
||||
__license__ = "LGPL"
|
||||
@@ -3998,14 +3998,14 @@ class MainWindow(QMainWindow):
|
||||
self.thumbnail_view.setGridSize(QSize())
|
||||
else:
|
||||
self.thumbnail_view.setGridSize(self.delegate.sizeHint(None, None))
|
||||
self.rebuild_view()
|
||||
self.rebuild_view(full_reset=True)
|
||||
|
||||
self.save_config()
|
||||
self.setFocus()
|
||||
|
||||
def on_sort_changed(self):
|
||||
"""Callback for when the sort order dropdown changes."""
|
||||
self.rebuild_view()
|
||||
self.rebuild_view(full_reset=True)
|
||||
self.save_config()
|
||||
if hasattr(self, 'history_tab'):
|
||||
self.history_tab.refresh_list()
|
||||
|
||||
@@ -29,7 +29,7 @@ if FORCE_X11:
|
||||
# --- CONFIGURATION ---
|
||||
PROG_NAME = "Bagheera Image Viewer"
|
||||
PROG_ID = "bagheeraview"
|
||||
PROG_VERSION = "0.9.19-dev"
|
||||
PROG_VERSION = "0.9.20"
|
||||
PROG_AUTHOR = "Ignacio Serantes"
|
||||
|
||||
# --- CACHE SETTINGS ---
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
"""
|
||||
Duplicate Management Dialog Module for Bagheera.
|
||||
|
||||
This module implements the DuplicateManagerDialog, a specialized interface
|
||||
for comparing and resolving duplicate image pairs identified by the
|
||||
DuplicateDetector. It provides side-by-side viewing with synchronized
|
||||
zooming and scrolling.
|
||||
|
||||
Classes:
|
||||
DuplicateManagerDialog: A dialog to review and manage duplicate image pairs.
|
||||
"""
|
||||
import os
|
||||
from datetime import datetime
|
||||
from PySide6.QtWidgets import (
|
||||
@@ -17,6 +28,15 @@ class DuplicateManagerDialog(QDialog):
|
||||
A dialog to review and manage duplicate image pairs found by the detector.
|
||||
"""
|
||||
def __init__(self, duplicates, duplicate_cache, main_win, review_mode=False):
|
||||
"""
|
||||
Initializes the DuplicateManagerDialog.
|
||||
|
||||
Args:
|
||||
duplicates (list): List of DuplicateResult tuples.
|
||||
duplicate_cache (DuplicateCache): The persistent hash/exception cache.
|
||||
main_win (MainWindow): Reference to the main application window.
|
||||
review_mode (bool, optional): If True, shows previously ignored duplicates.
|
||||
"""
|
||||
super().__init__(main_win)
|
||||
self.duplicates = duplicates # List of DuplicateResult
|
||||
self.cache = duplicate_cache
|
||||
@@ -164,7 +184,12 @@ class DuplicateManagerDialog(QDialog):
|
||||
self.right_pane = self.right_pane_widget.pane
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Disconnects signals and performs cleanup when closing."""
|
||||
"""
|
||||
Handles the dialog close event.
|
||||
|
||||
Disconnects external signals from the file system watcher and
|
||||
performs cleanup on the image panes to free resources.
|
||||
"""
|
||||
if self.main_win and hasattr(self.main_win, 'fs_watcher'):
|
||||
try:
|
||||
self.main_win.fs_watcher.file_deleted.disconnect(
|
||||
@@ -181,7 +206,12 @@ class DuplicateManagerDialog(QDialog):
|
||||
super().closeEvent(event)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
"""Resizes the images to fill available space when the dialog is resized."""
|
||||
"""
|
||||
Handles the dialog resize event.
|
||||
|
||||
Triggers a recalculation of the image scaling to ensure they fit
|
||||
optimally within the new available space.
|
||||
"""
|
||||
super().resizeEvent(event) # Call base class resizeEvent
|
||||
self._apply_linked_scaling()
|
||||
|
||||
@@ -257,7 +287,12 @@ class DuplicateManagerDialog(QDialog):
|
||||
self._is_syncing = False
|
||||
|
||||
def wheelEvent(self, event):
|
||||
"""Handles mouse wheel events for zooming (with Ctrl)."""
|
||||
"""
|
||||
Handles mouse wheel events for zooming.
|
||||
|
||||
If the Control modifier is held, zooms the active image pane
|
||||
centered on the mouse cursor position.
|
||||
"""
|
||||
if event.modifiers() & Qt.ControlModifier and self.active_pane:
|
||||
# Calculate the focus point relative to the active pane.
|
||||
focus_pos = self.active_pane.mapFromGlobal(event.globalPosition().toPoint())
|
||||
@@ -270,7 +305,12 @@ class DuplicateManagerDialog(QDialog):
|
||||
super().wheelEvent(event)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""Handles keyboard shortcuts for zooming and duplicate management."""
|
||||
"""
|
||||
Handles keyboard shortcuts for navigation and actions.
|
||||
|
||||
Provides quick access to deletion (U/I), ignore (O), and skip (P),
|
||||
as well as standard zoom controls (Z/+/ -).
|
||||
"""
|
||||
key = event.key()
|
||||
if key == Qt.Key_U:
|
||||
self._delete_left()
|
||||
@@ -306,18 +346,29 @@ class DuplicateManagerDialog(QDialog):
|
||||
# --- Viewer API Implementation for ImagePane ---
|
||||
|
||||
def set_active_pane(self, pane):
|
||||
"""Sets the currently focused pane for synchronization reference."""
|
||||
"""
|
||||
Sets the currently focused pane for synchronization reference.
|
||||
|
||||
Args:
|
||||
pane (ImagePane): The pane that gained focus.
|
||||
"""
|
||||
self.active_pane = pane
|
||||
self.update_highlight()
|
||||
|
||||
def update_highlight(self):
|
||||
"""Visual feedback for the active pane."""
|
||||
"""Applies visual feedback (border) to the active pane."""
|
||||
for pw in [self.left_pane_widget, self.right_pane_widget]:
|
||||
pw.setStyleSheet("border: 2px solid #3498db;" if pw.pane == self.active_pane
|
||||
else "border: 2px solid transparent;")
|
||||
|
||||
def on_metadata_changed(self, path, metadata=None):
|
||||
"""Updates labels when image metadata (like tags) is modified."""
|
||||
"""
|
||||
Updates labels when image metadata is modified.
|
||||
|
||||
Args:
|
||||
path (str): The file path.
|
||||
metadata (dict, optional): The updated metadata.
|
||||
"""
|
||||
# Find the widget displaying this path and update its info
|
||||
for pw in [self.left_pane_widget, self.right_pane_widget]:
|
||||
if pw.pane.controller.get_current_path() == path:
|
||||
@@ -331,18 +382,30 @@ class DuplicateManagerDialog(QDialog):
|
||||
self.main_win.update_metadata_for_path(path, metadata)
|
||||
|
||||
def on_controller_list_updated(self, index):
|
||||
"""Required by ImagePane API, no-op in dialog context."""
|
||||
"""Required by ImagePane API, no-op in this dialog."""
|
||||
pass
|
||||
|
||||
def update_view_for_pane(self, pane, resize_win=False):
|
||||
"""Refreshes the canvas for a specific pane."""
|
||||
"""
|
||||
Refreshes the canvas for a specific pane.
|
||||
|
||||
Args:
|
||||
pane (ImagePane): The pane to update.
|
||||
resize_win (bool): Ignored in this context.
|
||||
"""
|
||||
pixmap = pane.controller.get_display_pixmap()
|
||||
if not pixmap.isNull():
|
||||
pane.canvas.setPixmap(pixmap)
|
||||
pane.canvas.adjustSize()
|
||||
|
||||
def load_and_fit_image_for_pane(self, pane, restore_config=None):
|
||||
"""Loads and calculates initial zoom to fit the pane viewport."""
|
||||
"""
|
||||
Loads and calculates initial zoom to fit the pane viewport.
|
||||
|
||||
Args:
|
||||
pane (ImagePane): The target pane.
|
||||
restore_config (dict, optional): Unused here.
|
||||
"""
|
||||
success, _ = pane.controller.load_image()
|
||||
if success:
|
||||
viewport = pane.scroll_area.viewport()
|
||||
@@ -355,21 +418,29 @@ class DuplicateManagerDialog(QDialog):
|
||||
self.update_view_for_pane(pane)
|
||||
|
||||
def reset_inactivity_timer(self):
|
||||
"""Required by ImagePane API, no-op in this dialog."""
|
||||
pass
|
||||
|
||||
def sync_filmstrip_selection(self, index):
|
||||
"""Required by ImagePane API, no-op in this dialog."""
|
||||
pass
|
||||
|
||||
def _get_clicked_face_for_pane(self, pane, pos):
|
||||
"""Required by ImagePane API, no-op in this dialog."""
|
||||
return None
|
||||
|
||||
def rename_face(self, face):
|
||||
"""Required by ImagePane API, no-op in this dialog."""
|
||||
pass
|
||||
|
||||
def toggle_fullscreen(self):
|
||||
"""Required by ImagePane API, no-op in this dialog."""
|
||||
pass
|
||||
|
||||
def _create_comparison_pane_widget(self):
|
||||
"""
|
||||
Factory method to create a pane widget containing an ImagePane and info labels.
|
||||
"""
|
||||
widget = QWidget()
|
||||
v_layout = QVBoxLayout(widget)
|
||||
v_layout.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -450,10 +521,14 @@ class DuplicateManagerDialog(QDialog):
|
||||
self.table_widget.sortItems(0, Qt.DescendingOrder)
|
||||
|
||||
def _on_cell_changed(self, row, col, prev_row, prev_col):
|
||||
"""Slot triggered when the selected row in the pairs table changes."""
|
||||
if row >= 0:
|
||||
self._load_pair(row)
|
||||
|
||||
def _load_pair(self, row):
|
||||
"""
|
||||
Loads the duplicate pair corresponding to the specified table row.
|
||||
"""
|
||||
if row < 0 or row >= self.table_widget.rowCount():
|
||||
return
|
||||
|
||||
@@ -579,7 +654,20 @@ class DuplicateManagerDialog(QDialog):
|
||||
|
||||
def _set_pane_data(self, pane_widget, path, filename_color, dir_color,
|
||||
filename_text, dir_text) -> bool:
|
||||
"""Updates an ImagePane and its labels with file data."""
|
||||
"""
|
||||
Updates an ImagePane and its labels with file data.
|
||||
|
||||
Args:
|
||||
pane_widget (QWidget): The container widget for the pane.
|
||||
path (str): File path to load.
|
||||
filename_color (str): Hex color for filename label.
|
||||
dir_color (str): Hex color for directory label.
|
||||
filename_text (str): Name to display.
|
||||
dir_text (str): Directory path to display.
|
||||
|
||||
Returns:
|
||||
bool: True if linked scaling should be disabled (e.g., animation).
|
||||
"""
|
||||
pane = pane_widget.pane
|
||||
info_lbl = pane_widget.info_lbl
|
||||
filename_lbl = pane_widget.filename_lbl
|
||||
@@ -625,7 +713,12 @@ class DuplicateManagerDialog(QDialog):
|
||||
return disable_linking
|
||||
|
||||
def _show_pane_context_menu(self, pos):
|
||||
"""Displays a context menu for the pane that requested it."""
|
||||
"""
|
||||
Displays a context menu for the pane that requested it.
|
||||
|
||||
Args:
|
||||
pos (QPoint): Local coordinates of the request.
|
||||
"""
|
||||
pane = self.sender()
|
||||
path = pane.controller.get_current_path()
|
||||
if not path or not os.path.exists(path):
|
||||
@@ -685,7 +778,12 @@ class DuplicateManagerDialog(QDialog):
|
||||
menu.exec(pane.mapToGlobal(pos))
|
||||
|
||||
def _handle_permanent_delete(self, path):
|
||||
"""Prompts for and executes permanent deletion of a file."""
|
||||
"""
|
||||
Prompts for and executes permanent deletion of a file.
|
||||
|
||||
Args:
|
||||
path (str): File path to delete.
|
||||
"""
|
||||
confirm = QMessageBox(self)
|
||||
confirm.setIcon(QMessageBox.Warning)
|
||||
confirm.setText(UITexts.CONFIRM_DELETE_TEXT)
|
||||
@@ -697,7 +795,13 @@ class DuplicateManagerDialog(QDialog):
|
||||
self._handle_action(delete_path=path, permanent=True)
|
||||
|
||||
def _show_properties(self, path, pane):
|
||||
"""Shows the file properties dialog for a pane's image."""
|
||||
"""
|
||||
Shows the file properties dialog for a pane's image.
|
||||
|
||||
Args:
|
||||
path (str): File path.
|
||||
pane (ImagePane): The pane containing the image.
|
||||
"""
|
||||
tags = pane.controller._current_tags
|
||||
rating = pane.controller._current_rating
|
||||
dlg = PropertiesDialog(
|
||||
@@ -705,7 +809,11 @@ class DuplicateManagerDialog(QDialog):
|
||||
dlg.exec()
|
||||
|
||||
def _on_pane_activated(self):
|
||||
"""Handles pane activation to synchronize viewing state if linked."""
|
||||
"""
|
||||
Handles pane activation to synchronize viewing state if linked.
|
||||
|
||||
Ensures that the activated pane becomes the leader for synchronization.
|
||||
"""
|
||||
# When a pane is activated, ensure its zoom/scroll is the reference for linking
|
||||
if self.panes_linked:
|
||||
active_pane = self.sender() # The pane that emitted activated signal
|
||||
@@ -720,7 +828,13 @@ class DuplicateManagerDialog(QDialog):
|
||||
other_pane.set_scroll_relative(x_pct, y_pct)
|
||||
|
||||
def _sync_scroll(self, x_pct, y_pct):
|
||||
"""Synchronizes scroll position between panes if linked."""
|
||||
"""
|
||||
Synchronizes scroll position between panes if linked.
|
||||
|
||||
Args:
|
||||
x_pct (float): Horizontal scroll percentage.
|
||||
y_pct (float): Vertical scroll percentage.
|
||||
"""
|
||||
if not self.panes_linked:
|
||||
return
|
||||
source_pane = self.sender()
|
||||
@@ -730,7 +844,13 @@ class DuplicateManagerDialog(QDialog):
|
||||
self.left_pane.set_scroll_relative(x_pct, y_pct)
|
||||
|
||||
def _sync_zoom(self, factor, source_pane=None):
|
||||
"""Synchronizes zoom factor between panes if linked."""
|
||||
"""
|
||||
Synchronizes zoom factor between panes if linked.
|
||||
|
||||
Args:
|
||||
factor (float): New zoom factor.
|
||||
source_pane (ImagePane, optional): The leader pane.
|
||||
"""
|
||||
if not self.panes_linked or self._is_syncing:
|
||||
return
|
||||
if source_pane is None:
|
||||
@@ -788,7 +908,12 @@ class DuplicateManagerDialog(QDialog):
|
||||
self._is_syncing = False
|
||||
|
||||
def _format_size(self, size):
|
||||
"""Formats a file size in bytes to a human-readable string."""
|
||||
"""
|
||||
Formats a file size in bytes to a human-readable string.
|
||||
|
||||
Args:
|
||||
size (int): Size in bytes.
|
||||
"""
|
||||
for unit in ['B', 'KiB', 'MiB', 'GiB']:
|
||||
if size < 1024:
|
||||
return f"{size:.1f} {unit}"
|
||||
@@ -808,7 +933,7 @@ class DuplicateManagerDialog(QDialog):
|
||||
self._handle_action(delete_path=path_to_delete)
|
||||
|
||||
def _toggle_link_panes(self):
|
||||
"""Toggles the link state between panes."""
|
||||
"""Toggles the synchronized viewing state between panes."""
|
||||
self._user_link_preference = self.btn_link_panes.isChecked()
|
||||
self.panes_linked = self._user_link_preference
|
||||
if self.panes_linked:
|
||||
@@ -823,7 +948,12 @@ class DuplicateManagerDialog(QDialog):
|
||||
self.right_pane.set_scroll_relative(x_pct, y_pct)
|
||||
|
||||
def _on_file_deleted_externally(self, path):
|
||||
"""Handles file deletion events from the FileSystemWatcher."""
|
||||
"""
|
||||
Handles file deletion events from the FileSystemWatcher.
|
||||
|
||||
Args:
|
||||
path (str): The deleted path.
|
||||
"""
|
||||
path = os.path.abspath(path)
|
||||
|
||||
# 1. Identify pairs to remove and clean up the pending DB
|
||||
@@ -849,7 +979,13 @@ class DuplicateManagerDialog(QDialog):
|
||||
self.table_widget.setCurrentCell(new_row, 0)
|
||||
|
||||
def _on_file_moved_externally(self, old_path, new_path):
|
||||
"""Handles file move/rename events from the FileSystemWatcher."""
|
||||
"""
|
||||
Handles file move/rename events from the FileSystemWatcher.
|
||||
|
||||
Args:
|
||||
old_path (str): Original path.
|
||||
new_path (str): Target path.
|
||||
"""
|
||||
old_path = os.path.abspath(old_path)
|
||||
new_path = os.path.abspath(new_path)
|
||||
|
||||
@@ -901,10 +1037,12 @@ class DuplicateManagerDialog(QDialog):
|
||||
def _handle_action(self, delete_path=None, skip=False, permanent=None):
|
||||
"""
|
||||
Handles management actions (delete, skip, keep) for duplicate pairs.
|
||||
Updates the local list and the persistent databases.
|
||||
|
||||
Args:
|
||||
delete_path: Path to delete, if any.
|
||||
skip: Whether to skip the current pair.
|
||||
delete_path (str, optional): Path to delete.
|
||||
skip (bool): Whether to skip without ignoring permanently.
|
||||
permanent (bool, optional): If True, deletes without trash.
|
||||
"""
|
||||
current_row = self.table_widget.currentRow()
|
||||
if current_row < 0:
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
"""
|
||||
File System Watcher Module for Bagheera Image Viewer.
|
||||
|
||||
This module provides functionality to monitor file system changes in real-time
|
||||
using the watchdog library. It notifies the application about new, deleted, or
|
||||
modified image files within watched directories, handling debouncing to ensure
|
||||
stability during rapid file operations.
|
||||
|
||||
Classes:
|
||||
FileSystemWatcher: Coordinates file system monitoring and emits Qt signals.
|
||||
"""
|
||||
import os
|
||||
try:
|
||||
from watchdog.observers import Observer
|
||||
@@ -14,6 +25,10 @@ class FileSystemWatcher(QObject):
|
||||
Monitors file system events (created, deleted, modified) for specified directories.
|
||||
Emits signals to notify the main application thread of changes.
|
||||
"""
|
||||
|
||||
# Signals emitted to the rest of the application
|
||||
# ---------------------------------------------
|
||||
|
||||
file_created = Signal(str)
|
||||
file_deleted = Signal(str)
|
||||
file_modified = Signal(str)
|
||||
@@ -24,10 +39,18 @@ class FileSystemWatcher(QObject):
|
||||
directory_modified = Signal(str) # For changes that might not be specific files
|
||||
|
||||
_modified_events_queue = {} # {path: QTimer}
|
||||
"""Queue to manage debouncing of modification events."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""
|
||||
Initializes the FileSystemWatcher.
|
||||
|
||||
Args:
|
||||
parent (QObject, optional): The parent object. Defaults to None.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self._watched_directories = set()
|
||||
self._debounce_interval = 500 # milliseconds
|
||||
|
||||
if HAVE_WATCHDOG:
|
||||
self._observer = Observer()
|
||||
@@ -36,16 +59,21 @@ class FileSystemWatcher(QObject):
|
||||
else:
|
||||
self._observer = None # Keep observer as None if watchdog is not available
|
||||
|
||||
# Debounce timer for modified events to avoid multiple signals for a single save
|
||||
self._debounce_interval = 500 # milliseconds
|
||||
|
||||
# Connect the internal signal to the debouncing slot
|
||||
if HAVE_WATCHDOG:
|
||||
self._file_modified_from_handler.connect(self._on_file_modified_debounced)
|
||||
|
||||
def _on_file_modified_debounced(self, path):
|
||||
"""Slot to handle modified events from the watchdog thread, debounced in the
|
||||
main thread."""
|
||||
"""
|
||||
Slot to handle modified events from the watchdog thread.
|
||||
|
||||
Implements a debouncing mechanism: if multiple modification events
|
||||
arrive for the same path within the interval, previous timers are
|
||||
reset to avoid redundant UI updates or heavy disk operations.
|
||||
|
||||
Args:
|
||||
path (str): The path of the modified file.
|
||||
"""
|
||||
# Debounce timer for modified events to avoid multiple signals for a single save
|
||||
if path in self._modified_events_queue:
|
||||
self._modified_events_queue[path].stop()
|
||||
@@ -59,7 +87,12 @@ class FileSystemWatcher(QObject):
|
||||
self._modified_events_queue[path].start()
|
||||
|
||||
def _emit_modified_after_debounce(self, path):
|
||||
"""Emits the file_modified signal after the debounce period."""
|
||||
"""
|
||||
Emits the file_modified signal after the debounce period.
|
||||
|
||||
Args:
|
||||
path (str): The path of the modified file.
|
||||
"""
|
||||
self.file_modified.emit(path)
|
||||
if path in self._modified_events_queue:
|
||||
# Safely delete the QTimer object when done
|
||||
@@ -67,7 +100,16 @@ class FileSystemWatcher(QObject):
|
||||
del self._modified_events_queue[path]
|
||||
|
||||
def add_path(self, path):
|
||||
"""Adds a directory to be monitored."""
|
||||
"""
|
||||
Adds a directory to be monitored.
|
||||
|
||||
This method ensures that redundant watches are avoided by checking if
|
||||
the path is already covered by an existing watch or if it should
|
||||
consolidate multiple sub-watches into a single parent watch.
|
||||
|
||||
Args:
|
||||
path (str): The directory path to monitor.
|
||||
"""
|
||||
if not HAVE_WATCHDOG or self._observer is None:
|
||||
return
|
||||
|
||||
@@ -111,7 +153,12 @@ class FileSystemWatcher(QObject):
|
||||
self.monitoring_status_changed.emit(True)
|
||||
|
||||
def remove_path(self, path):
|
||||
"""Removes a directory from monitoring."""
|
||||
"""
|
||||
Removes a directory from monitoring.
|
||||
|
||||
Args:
|
||||
path (str): The directory path to stop monitoring.
|
||||
"""
|
||||
if not HAVE_WATCHDOG or self._observer is None:
|
||||
return
|
||||
abs_path = os.path.normpath(os.path.abspath(os.path.expanduser(path)))
|
||||
@@ -138,7 +185,9 @@ class FileSystemWatcher(QObject):
|
||||
self.monitoring_status_changed.emit(False)
|
||||
|
||||
def stop(self):
|
||||
"""Stops the file system observer."""
|
||||
"""
|
||||
Stops the file system observer and cleans up active timers.
|
||||
"""
|
||||
if HAVE_WATCHDOG and self._observer:
|
||||
self._observer.stop()
|
||||
self._observer.join()
|
||||
@@ -150,10 +199,19 @@ class FileSystemWatcher(QObject):
|
||||
|
||||
if HAVE_WATCHDOG:
|
||||
class _Handler(FileSystemEventHandler):
|
||||
"""
|
||||
Custom event handler for watchdog events.
|
||||
|
||||
Translates low-level file system events into high-level application
|
||||
signals, filtering for supported image types.
|
||||
"""
|
||||
# Signal to communicate to main thread
|
||||
file_modified_from_thread = Signal(str)
|
||||
"""Custom event handler for watchdog events."""
|
||||
|
||||
def __init__(self, watcher):
|
||||
"""
|
||||
Initializes the handler with a reference to the main watcher.
|
||||
"""
|
||||
super().__init__()
|
||||
self.watcher = watcher
|
||||
|
||||
@@ -199,11 +257,21 @@ class FileSystemWatcher(QObject):
|
||||
self.watcher._file_modified_from_handler.emit(event.src_path)
|
||||
|
||||
def _emit_modified(self, path):
|
||||
"""Internal helper to emit the modified signal."""
|
||||
"""
|
||||
Internal helper to emit the modified signal.
|
||||
|
||||
Args:
|
||||
path (str): The modified path.
|
||||
"""
|
||||
self.watcher.file_modified.emit(path)
|
||||
if path in self.watcher._modified_events_queue:
|
||||
del self.watcher._modified_events_queue[path]
|
||||
|
||||
def _is_image_file(self, path):
|
||||
"""Checks if a given path has a supported image extension."""
|
||||
"""
|
||||
Checks if a given path has a supported image extension.
|
||||
|
||||
Args:
|
||||
path (str): The file path to check.
|
||||
"""
|
||||
return os.path.splitext(path)[1].lower() in IMAGE_EXTENSIONS
|
||||
|
||||
@@ -3317,7 +3317,8 @@ class ImageViewer(QWidget):
|
||||
# A standard tick is 120. We define a threshold based on speed.
|
||||
# Speed 1 (slowest) requires a full 120 delta.
|
||||
# Speed 10 (fastest) requires 120/10 = 12 delta.
|
||||
threshold = 120 / speed
|
||||
# Still too fast so speed / 2.
|
||||
threshold = 120 / speed / 2
|
||||
|
||||
self._wheel_scroll_accumulator += event.angleDelta().y()
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "bagheeraview"
|
||||
version = "0.9.19"
|
||||
version = "0.9.20"
|
||||
authors = [
|
||||
{ name = "Ignacio Serantes" }
|
||||
]
|
||||
|
||||
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="bagheeraview",
|
||||
version="0.9.19",
|
||||
version="0.9.20",
|
||||
author="Ignacio Serantes",
|
||||
description="Bagheera Image Viewer - An image viewer for KDE with Baloo in mind",
|
||||
long_description="A fast image viewer built with PySide6, featuring search and "
|
||||
|
||||
Reference in New Issue
Block a user