Compare commits

..

1 Commits

Author SHA1 Message Date
Ignacio Serantes
0349155fd2 v0.9.11 2026-03-24 09:14:57 +01:00
3 changed files with 51 additions and 29 deletions

View File

@@ -29,7 +29,7 @@ if FORCE_X11:
# --- CONFIGURATION ---
PROG_NAME = "Bagheera Image Viewer"
PROG_ID = "bagheeraview"
PROG_VERSION = "0.9.11-dev"
PROG_VERSION = "0.9.11"
PROG_AUTHOR = "Ignacio Serantes"
# --- CACHE SETTINGS ---

View File

@@ -64,7 +64,8 @@ class ThreadPoolManager:
)
self.pool.setMaxThreadCount(self.default_thread_count)
self.is_user_active = False
logger.info(f"ThreadPoolManager initialized with {self.default_thread_count} threads.")
logger.info(f"ThreadPoolManager initialized with "
f"{self.default_thread_count} threads.")
def get_pool(self):
"""Returns the managed QThreadPool instance."""
@@ -87,7 +88,8 @@ class ThreadPoolManager:
else:
# User is idle, restore to default thread count.
self.pool.setMaxThreadCount(self.default_thread_count)
logger.debug(f"User is idle, restoring thread pool to {self.default_thread_count}.")
logger.debug(f"User is idle, restoring thread pool to "
f"{self.default_thread_count}.")
def update_default_thread_count(self):
"""Updates the default thread count from application settings."""
@@ -1329,6 +1331,7 @@ class ImageScanner(QThread):
progress_percent = Signal(int)
finished_scan = Signal(int) # Total images found
more_files_available = Signal(int, int) # Last loaded index, remainder
def __init__(self, cache, paths, is_file_list=False, viewers=None,
thread_pool_manager=None):
# is_file_list is not used

View File

@@ -15,8 +15,8 @@ import json
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QScrollArea, QLabel, QHBoxLayout, QListWidget,
QListWidgetItem, QAbstractItemView, QMenu, QInputDialog, QDialog, QDialogButtonBox, QGridLayout,
QApplication, QMessageBox, QLineEdit, QFileDialog
QListWidgetItem, QAbstractItemView, QMenu, QInputDialog, QDialog, QDialogButtonBox,
QGridLayout, QApplication, QMessageBox, QLineEdit, QFileDialog
)
from PySide6.QtGui import (
QPixmap, QIcon, QTransform, QDrag, QPainter, QPen, QColor, QAction, QCursor,
@@ -39,6 +39,7 @@ from imagecontroller import ImageController
from widgets import FaceNameInputWidget
from propertiesdialog import PropertiesDialog
class HighlightWidget(QWidget):
"""Widget to show a highlight border around the active pane."""
def __init__(self, parent=None):
@@ -47,6 +48,7 @@ class HighlightWidget(QWidget):
self.setStyleSheet("border: 2px solid #3498db; background: transparent;")
self.hide()
class FaceNameDialog(QDialog):
"""A dialog to get a face name using the FaceNameInputWidget."""
def __init__(self, parent=None, history=None, current_name="", main_win=None,
@@ -1329,6 +1331,7 @@ class ImagePane(QWidget):
if self.controller.get_current_path() != current_path:
self.load_and_fit_image()
class ImageViewer(QWidget):
"""
A standalone window for viewing and manipulating a single image.
@@ -1410,7 +1413,6 @@ class ImageViewer(QWidget):
# self.canvas = FaceCanvas(self) ... Moved to ImagePane
# self.scroll_area.setWidget(self.canvas)
self.filmstrip = FilmStripWidget(self.controller)
self.filmstrip.setSpacing(2)
self.filmstrip.itemClicked.connect(self.on_filmstrip_clicked)
@@ -1540,7 +1542,8 @@ class ImageViewer(QWidget):
return self.active_pane.movie if self.active_pane else None
def add_pane(self, image_list, index, initial_tags, initial_rating):
pane = ImagePane(self, self.cache, image_list, index, initial_tags, initial_rating)
pane = ImagePane(self, self.cache, image_list, index, initial_tags,
initial_rating)
self.panes.append(pane)
self.update_grid_layout()
return pane
@@ -1636,8 +1639,10 @@ class ImageViewer(QWidget):
# Restore default behavior (auto-resize) if we go back to single view
if count == 1 and self.active_pane:
# Allow layout to settle before resizing window to ensure accurate sizing
QTimer.singleShot(0, lambda: self.active_pane.update_view(resize_win=True))
# Allow layout to settle before resizing window to ensure accurate
# sizing
QTimer.singleShot(
0, lambda: self.active_pane.update_view(resize_win=True))
def toggle_link_panes(self):
"""Toggles the synchronized zoom/scroll for comparison mode."""
@@ -1883,7 +1888,8 @@ class ImageViewer(QWidget):
"""
kwinoutputconfig.json
"""
return self.screen().availableGeometry().width(), self.screen().availableGeometry().height()
return self.screen().availableGeometry().width(), \
self.screen().availableGeometry().height()
# We run kscreen-doctor and look for the primary monitor line.
if FORCE_X11:
if os.path.exists(KWINOUTPUTCONFIG_PATH):
@@ -1986,7 +1992,8 @@ class ImageViewer(QWidget):
if self.filmstrip.isVisible():
self.populate_filmstrip()
pane.update_view(resize_win=False)
QTimer.singleShot(0, lambda: self.restore_scroll_for_pane(pane, restore_config))
QTimer.singleShot(
0, lambda: self.restore_scroll_for_pane(pane, restore_config))
elif reloaded:
# Calculate zoom to fit the image on the screen
if self.isFullScreen():
@@ -2004,7 +2011,8 @@ class ImageViewer(QWidget):
else:
# Tried to guess
screen_width, screen_height = self.get_desktop_resolution()
if pane == self.panes[0]: self._first_load = False
if pane == self.panes[0]:
self._first_load = False
else:
screen_geo = self.screen().availableGeometry()
screen_width = screen_geo.width()
@@ -2274,11 +2282,13 @@ class ImageViewer(QWidget):
def save_cropped_image(self):
"""Saves the area currently selected in crop mode as a new image."""
if not self.active_pane or not self.active_pane.crop_mode or self.active_pane.canvas.crop_rect.isNull():
if not self.active_pane or not self.active_pane.crop_mode \
or self.active_pane.canvas.crop_rect.isNull():
return
# Get normalized coordinates from the canvas rect
nx, ny, nw, nh = self.active_pane.canvas.map_to_source(self.active_pane.canvas.crop_rect)
nx, ny, nw, nh = self.active_pane.canvas.map_to_source(
self.active_pane.canvas.crop_rect)
# Use original pixmap to extract high-quality crop
orig = self.active_pane.controller.pixmap_original
@@ -2403,8 +2413,10 @@ class ImageViewer(QWidget):
"show_faces": self.controller.show_faces,
"flip_h": self.controller.flip_h,
"flip_v": self.controller.flip_v,
"scroll_x": self.scroll_area.horizontalScrollBar().value() if self.scroll_area else 0,
"scroll_y": self.scroll_area.verticalScrollBar().value() if self.scroll_area else 0,
"scroll_x": self.scroll_area.horizontalScrollBar().value()
if self.scroll_area else 0,
"scroll_y": self.scroll_area.verticalScrollBar().value()
if self.scroll_area else 0,
"status_bar_visible": self.status_bar_container.isVisible(),
"filmstrip_visible": self.filmstrip.isVisible()
}
@@ -2493,7 +2505,8 @@ class ImageViewer(QWidget):
return False
pos = self.canvas.mapFromGlobal(event.globalPos()) if self.canvas else QPoint()
clicked_face = self.active_pane._get_clicked_face(pos) if self.active_pane else None
clicked_face = self.active_pane._get_clicked_face(pos) \
if self.active_pane else None
if not clicked_face:
return False
@@ -2640,11 +2653,16 @@ class ImageViewer(QWidget):
"icon": "transform-crop", "checkable": True}, # checked updated later
"separator",
{"text": UITexts.VIEWER_MENU_COMPARE, "icon": "view-grid", "submenu": [
{"text": UITexts.VIEWER_MENU_COMPARE_1, "action": "compare_1", "icon": "view-restore"},
{"text": UITexts.VIEWER_MENU_COMPARE_2, "action": "compare_2", "icon": "view-split-left-right"},
{"text": UITexts.VIEWER_MENU_COMPARE_4, "action": "compare_4", "icon": "view-grid"},
{"text": UITexts.VIEWER_MENU_COMPARE_1,
"action": "compare_1", "icon": "view-restore"},
{"text": UITexts.VIEWER_MENU_COMPARE_2,
"action": "compare_2", "icon": "view-split-left-right"},
{"text": UITexts.VIEWER_MENU_COMPARE_4,
"action": "compare_4", "icon": "view-grid"},
"separator",
{"text": UITexts.VIEWER_MENU_LINK_PANES, "action": "link_panes", "icon": "object-link", "checkable": True, "checked": self.panes_linked}
{"text": UITexts.VIEWER_MENU_LINK_PANES,
"action": "link_panes", "icon": "object-link",
"checkable": True, "checked": self.panes_linked}
]},
"separator",
]
@@ -2808,7 +2826,8 @@ class ImageViewer(QWidget):
Args:
event (QContextMenuEvent): The context menu event.
"""
if self.active_pane and self.active_pane.crop_mode and not self.active_pane.canvas.crop_rect.isNull():
if self.active_pane and self.active_pane.crop_mode \
and not self.active_pane.canvas.crop_rect.isNull():
pos = self.active_pane.canvas.mapFromGlobal(event.globalPos())
if self.active_pane.canvas.crop_rect.contains(pos):
self.show_crop_menu(event.globalPos())