v0.9.13
This commit is contained in:
110
bagheeraview.py
110
bagheeraview.py
@@ -14,7 +14,7 @@ Classes:
|
|||||||
MainWindow: The main application window containing the thumbnail grid and docks.
|
MainWindow: The main application window containing the thumbnail grid and docks.
|
||||||
"""
|
"""
|
||||||
__appname__ = "BagheeraView"
|
__appname__ = "BagheeraView"
|
||||||
__version__ = "0.9.12"
|
__version__ = "0.9.13"
|
||||||
__author__ = "Ignacio Serantes"
|
__author__ = "Ignacio Serantes"
|
||||||
__email__ = "kde@aynoa.net"
|
__email__ = "kde@aynoa.net"
|
||||||
__license__ = "LGPL"
|
__license__ = "LGPL"
|
||||||
@@ -55,7 +55,7 @@ from pathlib import Path
|
|||||||
from constants import (
|
from constants import (
|
||||||
APP_CONFIG, CONFIG_PATH, CURRENT_LANGUAGE, DEFAULT_GLOBAL_SHORTCUTS,
|
APP_CONFIG, CONFIG_PATH, CURRENT_LANGUAGE, DEFAULT_GLOBAL_SHORTCUTS,
|
||||||
DEFAULT_VIEWER_SHORTCUTS, GLOBAL_ACTIONS, HISTORY_PATH, ICON_THEME,
|
DEFAULT_VIEWER_SHORTCUTS, GLOBAL_ACTIONS, HISTORY_PATH, ICON_THEME,
|
||||||
ICON_THEME_FALLBACK, IMAGE_MIME_TYPES, LAYOUTS_DIR, PROG_AUTHOR,
|
ICON_THEME_FALLBACK, IMAGE_MIME_TYPES, LAYOUTS_DIR, FAVORITES_PATH, PROG_AUTHOR,
|
||||||
PROG_NAME, PROG_VERSION, RATING_XATTR_NAME, SCANNER_GENERATE_SIZES,
|
PROG_NAME, PROG_VERSION, RATING_XATTR_NAME, SCANNER_GENERATE_SIZES,
|
||||||
SCANNER_SETTINGS_DEFAULTS, SUPPORTED_LANGUAGES, TAGS_MENU_MAX_ITEMS_DEFAULT,
|
SCANNER_SETTINGS_DEFAULTS, SUPPORTED_LANGUAGES, TAGS_MENU_MAX_ITEMS_DEFAULT,
|
||||||
THUMBNAILS_BG_COLOR_DEFAULT, THUMBNAILS_DEFAULT_SIZE, VIEWER_ACTIONS,
|
THUMBNAILS_BG_COLOR_DEFAULT, THUMBNAILS_DEFAULT_SIZE, VIEWER_ACTIONS,
|
||||||
@@ -74,7 +74,8 @@ from imageviewer import ImageViewer
|
|||||||
from propertiesdialog import PropertiesDialog
|
from propertiesdialog import PropertiesDialog
|
||||||
from widgets import (
|
from widgets import (
|
||||||
CircularProgressBar,
|
CircularProgressBar,
|
||||||
TagEditWidget, LayoutsWidget, HistoryWidget, RatingWidget, CommentWidget
|
TagEditWidget, LayoutsWidget, HistoryWidget, RatingWidget, CommentWidget,
|
||||||
|
FavoritesWidget
|
||||||
)
|
)
|
||||||
from metadatamanager import XattrManager
|
from metadatamanager import XattrManager
|
||||||
|
|
||||||
@@ -255,16 +256,13 @@ class ShortcutHelpDialog(QDialog):
|
|||||||
new_mods = new_key_combo.keyboardModifiers()
|
new_mods = new_key_combo.keyboardModifiers()
|
||||||
new_key_tuple = (int(new_key), new_mods)
|
new_key_tuple = (int(new_key), new_mods)
|
||||||
|
|
||||||
# Check for conflicts in the same scope
|
# Check for conflicts globally
|
||||||
if new_key_tuple in source_dict and new_key_tuple != original_key_combo:
|
conflict_desc = self.main_win.shortcut_controller.check_conflict(
|
||||||
# Handle different value structures
|
new_key, new_mods)
|
||||||
val = source_dict[new_key_tuple]
|
|
||||||
# Global: (action, ignore, desc, category), Viewer: (action, desc)
|
|
||||||
if len(val) == 4:
|
|
||||||
conflict_desc = val[2]
|
|
||||||
else:
|
|
||||||
conflict_desc = val[1]
|
|
||||||
|
|
||||||
|
is_same = (new_key_tuple == original_key_combo)
|
||||||
|
|
||||||
|
if conflict_desc and not is_same:
|
||||||
QMessageBox.warning(self, UITexts.SHORTCUT_CONFLICT_TITLE,
|
QMessageBox.warning(self, UITexts.SHORTCUT_CONFLICT_TITLE,
|
||||||
UITexts.SHORTCUT_CONFLICT_TEXT.format(
|
UITexts.SHORTCUT_CONFLICT_TEXT.format(
|
||||||
new_sequence.toString(QKeySequence.NativeText),
|
new_sequence.toString(QKeySequence.NativeText),
|
||||||
@@ -300,6 +298,7 @@ class AppShortcutController(QObject):
|
|||||||
self.main_win = main_win
|
self.main_win = main_win
|
||||||
self._actions = self._get_actions()
|
self._actions = self._get_actions()
|
||||||
self._shortcuts = {}
|
self._shortcuts = {}
|
||||||
|
self._favorite_shortcuts = {}
|
||||||
self.action_to_shortcut = {}
|
self.action_to_shortcut = {}
|
||||||
self._register_shortcuts()
|
self._register_shortcuts()
|
||||||
|
|
||||||
@@ -317,6 +316,44 @@ class AppShortcutController(QObject):
|
|||||||
key_tuple = (k, Qt.KeyboardModifiers(m))
|
key_tuple = (k, Qt.KeyboardModifiers(m))
|
||||||
self._shortcuts[key_tuple] = (act, ignore, desc, cat)
|
self._shortcuts[key_tuple] = (act, ignore, desc, cat)
|
||||||
self.action_to_shortcut[act] = key_tuple
|
self.action_to_shortcut[act] = key_tuple
|
||||||
|
self.refresh_favorite_shortcuts()
|
||||||
|
|
||||||
|
def refresh_favorite_shortcuts(self):
|
||||||
|
"""Loads dynamic shortcuts assigned to favorite queries."""
|
||||||
|
self._favorite_shortcuts.clear()
|
||||||
|
if not os.path.exists(FAVORITES_PATH):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
with open(FAVORITES_PATH, 'r', encoding='utf-8') as f:
|
||||||
|
favorites = json.load(f)
|
||||||
|
for fav in favorites:
|
||||||
|
sc_str = fav.get('shortcut', '')
|
||||||
|
if sc_str:
|
||||||
|
seq = QKeySequence(sc_str)
|
||||||
|
if not seq.isEmpty():
|
||||||
|
self._favorite_shortcuts[
|
||||||
|
(seq[0].key(), seq[0].keyboardModifiers())
|
||||||
|
] = fav.get('query')
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def check_conflict(self, key, mods):
|
||||||
|
"""Checks if a shortcut is already assigned and returns its description."""
|
||||||
|
key_tuple = (int(key), mods)
|
||||||
|
|
||||||
|
# Global
|
||||||
|
if key_tuple in self._shortcuts:
|
||||||
|
return self._shortcuts[key_tuple][2]
|
||||||
|
|
||||||
|
# Viewer
|
||||||
|
if key_tuple in self.main_win.viewer_shortcuts:
|
||||||
|
return self.main_win.viewer_shortcuts[key_tuple][1]
|
||||||
|
|
||||||
|
# Favorites
|
||||||
|
if key_tuple in self._favorite_shortcuts:
|
||||||
|
return f"{UITexts.FAVORITES_TAB}: {self._favorite_shortcuts[key_tuple]}"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def _get_actions(self):
|
def _get_actions(self):
|
||||||
"""Returns a dictionary mapping action strings to callable functions."""
|
"""Returns a dictionary mapping action strings to callable functions."""
|
||||||
@@ -375,6 +412,12 @@ class AppShortcutController(QObject):
|
|||||||
if is_typing:
|
if is_typing:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# 1. Check Favorite Shortcuts FIRST (Priority Override)
|
||||||
|
if (key, mods) in self._favorite_shortcuts:
|
||||||
|
query = self._favorite_shortcuts[(key, mods)]
|
||||||
|
self.main_win.process_term(query)
|
||||||
|
return True
|
||||||
|
|
||||||
# Check if we have a handler for this combination
|
# Check if we have a handler for this combination
|
||||||
if (key, mods) in self._shortcuts:
|
if (key, mods) in self._shortcuts:
|
||||||
action_name, ignore_if_typing, _, _ = self._shortcuts[(key, mods)]
|
action_name, ignore_if_typing, _, _ = self._shortcuts[(key, mods)]
|
||||||
@@ -1172,16 +1215,26 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
self.tags_tabs.addTab(self.filter_widget, UITexts.TAG_FILTER_TAB)
|
self.tags_tabs.addTab(self.filter_widget, UITexts.TAG_FILTER_TAB)
|
||||||
|
|
||||||
# Tab 4: Layouts
|
# Tab 4: Favorites
|
||||||
|
self.favorites_tab = FavoritesWidget(self)
|
||||||
|
self.tags_tabs.addTab(self.favorites_tab, UITexts.FAVORITES_TAB)
|
||||||
|
|
||||||
|
# Tab 5: Layouts
|
||||||
self.is_xcb = QApplication.platformName() == "xcb"
|
self.is_xcb = QApplication.platformName() == "xcb"
|
||||||
if self.is_xcb:
|
if self.is_xcb:
|
||||||
self.layouts_tab = LayoutsWidget(self)
|
self.layouts_tab = LayoutsWidget(self)
|
||||||
self.tags_tabs.addTab(self.layouts_tab, UITexts.LAYOUTS_TAB)
|
self.tags_tabs.addTab(self.layouts_tab, UITexts.LAYOUTS_TAB)
|
||||||
|
|
||||||
# Tab 5: History
|
# Tab 6: History
|
||||||
self.history_tab = HistoryWidget(self)
|
self.history_tab = HistoryWidget(self)
|
||||||
self.tags_tabs.addTab(self.history_tab, UITexts.HISTORY_TAB)
|
self.tags_tabs.addTab(self.history_tab, UITexts.HISTORY_TAB)
|
||||||
|
|
||||||
|
# Initialize the shortcut controller
|
||||||
|
self.shortcut_controller = AppShortcutController(self)
|
||||||
|
|
||||||
|
self.favorites_tab.favorites_changed.connect(
|
||||||
|
self.shortcut_controller.refresh_favorite_shortcuts)
|
||||||
|
|
||||||
self.main_dock.setWidget(self.tags_tabs)
|
self.main_dock.setWidget(self.tags_tabs)
|
||||||
self.addDockWidget(Qt.RightDockWidgetArea, self.main_dock)
|
self.addDockWidget(Qt.RightDockWidgetArea, self.main_dock)
|
||||||
|
|
||||||
@@ -1233,6 +1286,11 @@ class MainWindow(QMainWindow):
|
|||||||
self.load_config()
|
self.load_config()
|
||||||
self.load_full_history()
|
self.load_full_history()
|
||||||
|
|
||||||
|
# Initialize the shortcut controller (after config is loaded)
|
||||||
|
self.shortcut_controller = AppShortcutController(self)
|
||||||
|
self.favorites_tab.favorites_changed.connect(
|
||||||
|
self.shortcut_controller.refresh_favorite_shortcuts)
|
||||||
|
|
||||||
self._apply_global_stylesheet()
|
self._apply_global_stylesheet()
|
||||||
# Set the initial thumbnail generation tier based on the loaded config size
|
# Set the initial thumbnail generation tier based on the loaded config size
|
||||||
self._current_thumb_tier = self._get_tier_for_size(self.current_thumb_size)
|
self._current_thumb_tier = self._get_tier_for_size(self.current_thumb_size)
|
||||||
@@ -1558,15 +1616,23 @@ class MainWindow(QMainWindow):
|
|||||||
# Actions to show different tabs in the dock
|
# Actions to show different tabs in the dock
|
||||||
show_tags_action = menu.addAction(QIcon.fromTheme("document-properties"),
|
show_tags_action = menu.addAction(QIcon.fromTheme("document-properties"),
|
||||||
UITexts.MENU_SHOW_TAGS)
|
UITexts.MENU_SHOW_TAGS)
|
||||||
show_tags_action.triggered.connect(lambda: self.open_sidebar_tab(0))
|
show_tags_action.triggered.connect(
|
||||||
|
lambda: self.open_sidebar_tab(self.tags_tabs.indexOf(self.tag_edit_widget)))
|
||||||
|
|
||||||
show_info_action = menu.addAction(QIcon.fromTheme("dialog-information"),
|
show_info_action = menu.addAction(QIcon.fromTheme("dialog-information"),
|
||||||
UITexts.MENU_SHOW_INFO)
|
UITexts.MENU_SHOW_INFO)
|
||||||
show_info_action.triggered.connect(lambda: self.open_sidebar_tab(1))
|
show_info_action.triggered.connect(
|
||||||
|
lambda: self.open_sidebar_tab(self.tags_tabs.indexOf(self.info_widget)))
|
||||||
|
|
||||||
|
show_favorites_action = menu.addAction(QIcon.fromTheme("bookmarks"),
|
||||||
|
UITexts.MENU_SHOW_FAVORITES)
|
||||||
|
f_idx = self.tags_tabs.indexOf(self.favorites_tab)
|
||||||
|
show_favorites_action.triggered.connect(lambda: self.open_sidebar_tab(f_idx))
|
||||||
|
|
||||||
show_filter_action = menu.addAction(QIcon.fromTheme("view-filter"),
|
show_filter_action = menu.addAction(QIcon.fromTheme("view-filter"),
|
||||||
UITexts.MENU_SHOW_FILTER)
|
UITexts.MENU_SHOW_FILTER)
|
||||||
show_filter_action.triggered.connect(lambda: self.open_sidebar_tab(2))
|
show_filter_action.triggered.connect(
|
||||||
|
lambda: self.open_sidebar_tab(self.tags_tabs.indexOf(self.filter_widget)))
|
||||||
|
|
||||||
if self.is_xcb:
|
if self.is_xcb:
|
||||||
show_layouts_action = menu.addAction(QIcon.fromTheme("view-grid"),
|
show_layouts_action = menu.addAction(QIcon.fromTheme("view-grid"),
|
||||||
@@ -2911,6 +2977,8 @@ class MainWindow(QMainWindow):
|
|||||||
self.update_tag_list()
|
self.update_tag_list()
|
||||||
elif widget == self.info_widget:
|
elif widget == self.info_widget:
|
||||||
self.update_info_widget()
|
self.update_info_widget()
|
||||||
|
elif widget == self.favorites_tab:
|
||||||
|
self.favorites_tab.refresh_list()
|
||||||
|
|
||||||
def update_tag_edit_widget(self):
|
def update_tag_edit_widget(self):
|
||||||
"""Updates the tag editor widget with data from the currently selected files."""
|
"""Updates the tag editor widget with data from the currently selected files."""
|
||||||
@@ -3852,7 +3920,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.proxy_model.data(index_at_pos, ITEM_TYPE_ROLE) == 'header':
|
self.proxy_model.data(index_at_pos, ITEM_TYPE_ROLE) == 'header':
|
||||||
group_name = self.proxy_model.data(index_at_pos, GROUP_NAME_ROLE)
|
group_name = self.proxy_model.data(index_at_pos, GROUP_NAME_ROLE)
|
||||||
if group_name:
|
if group_name:
|
||||||
action_toggle = menu.addAction("Collapse/Expand Group")
|
action_toggle = menu.addAction(UITexts.COLLAPSE_EXPAND_GROUP)
|
||||||
action_toggle.triggered.connect(
|
action_toggle.triggered.connect(
|
||||||
lambda: self.toggle_group_collapse(group_name))
|
lambda: self.toggle_group_collapse(group_name))
|
||||||
menu.exec(self.thumbnail_view.mapToGlobal(pos))
|
menu.exec(self.thumbnail_view.mapToGlobal(pos))
|
||||||
@@ -4006,7 +4074,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.on_high_res_generation_finished)
|
self.on_high_res_generation_finished)
|
||||||
self.thumbnail_generator.progress.connect(
|
self.thumbnail_generator.progress.connect(
|
||||||
lambda p, t: self.status_lbl.setText(
|
lambda p, t: self.status_lbl.setText(
|
||||||
f"Regenerating thumbnail: {p}/{t}")
|
UITexts.THUMBNAILS_REGENERATE_PROGRESS.format(p, t))
|
||||||
)
|
)
|
||||||
self.thumbnail_generator.start()
|
self.thumbnail_generator.start()
|
||||||
|
|
||||||
@@ -4397,9 +4465,7 @@ def main():
|
|||||||
path = path[6:]
|
path = path[6:]
|
||||||
|
|
||||||
win = MainWindow(cache, args, thread_pool_manager)
|
win = MainWindow(cache, args, thread_pool_manager)
|
||||||
shortcut_controller = AppShortcutController(win)
|
app.installEventFilter(win.shortcut_controller)
|
||||||
win.shortcut_controller = shortcut_controller
|
|
||||||
app.installEventFilter(shortcut_controller)
|
|
||||||
|
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec())
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ Ahora que la carga es rápida, ¿cómo puedo implementar una precarga inteligent
|
|||||||
¿Cómo puedo hacer que la selección de archivos sea persistente incluso después de recargar o filtrar la vista?
|
¿Cómo puedo hacer que la selección de archivos sea persistente incluso después de recargar o filtrar la vista?
|
||||||
|
|
||||||
|
|
||||||
|
v0.9.13 -
|
||||||
|
· Añadida la opción de favoritos.
|
||||||
|
|
||||||
v0.9.12 -
|
v0.9.12 -
|
||||||
· Al restaurar el layout no se restaura la posición y dimensiones de los thumbnails.
|
· Al restaurar el layout no se restaura la posición y dimensiones de los thumbnails.
|
||||||
· Mejoras en los menús de contexto.
|
· Mejoras en los menús de contexto.
|
||||||
|
|||||||
80
constants.py
80
constants.py
@@ -29,7 +29,7 @@ if FORCE_X11:
|
|||||||
# --- CONFIGURATION ---
|
# --- CONFIGURATION ---
|
||||||
PROG_NAME = "Bagheera Image Viewer"
|
PROG_NAME = "Bagheera Image Viewer"
|
||||||
PROG_ID = "bagheeraview"
|
PROG_ID = "bagheeraview"
|
||||||
PROG_VERSION = "0.9.12"
|
PROG_VERSION = "0.9.13"
|
||||||
PROG_AUTHOR = "Ignacio Serantes"
|
PROG_AUTHOR = "Ignacio Serantes"
|
||||||
|
|
||||||
# --- CACHE SETTINGS ---
|
# --- CACHE SETTINGS ---
|
||||||
@@ -49,6 +49,8 @@ CACHE_PATH = os.path.join(CONFIG_DIR, "thumbnails_lmdb")
|
|||||||
HISTORY_FILE = "history.json"
|
HISTORY_FILE = "history.json"
|
||||||
HISTORY_PATH = os.path.join(CONFIG_DIR, HISTORY_FILE)
|
HISTORY_PATH = os.path.join(CONFIG_DIR, HISTORY_FILE)
|
||||||
LAYOUTS_DIR = os.path.join(CONFIG_DIR, "layouts") # Layouts saving directory
|
LAYOUTS_DIR = os.path.join(CONFIG_DIR, "layouts") # Layouts saving directory
|
||||||
|
FAVORITES_FILE = "favorites.json"
|
||||||
|
FAVORITES_PATH = os.path.join(CONFIG_DIR, FAVORITES_FILE)
|
||||||
|
|
||||||
|
|
||||||
def save_app_config():
|
def save_app_config():
|
||||||
@@ -463,6 +465,17 @@ _UI_TEXTS = {
|
|||||||
"MENU_CLEAN_CACHE": "Clean up invalid cache entries",
|
"MENU_CLEAN_CACHE": "Clean up invalid cache entries",
|
||||||
"MENU_SHOW_TAGS": "Show Tags",
|
"MENU_SHOW_TAGS": "Show Tags",
|
||||||
"MENU_SHOW_INFO": "Show Information",
|
"MENU_SHOW_INFO": "Show Information",
|
||||||
|
"MENU_SHOW_FAVORITES": "Show Favorites",
|
||||||
|
"FAVORITES_TAB": "Favorites",
|
||||||
|
"FAVORITES_SEARCH_PLACEHOLDER": "Search favorites...",
|
||||||
|
"FAVORITES_TABLE_HEADER": ["Comment", "Query", "Shortcut"],
|
||||||
|
"ADD_FAVORITE_TOOLTIP": "Add current search to favorites",
|
||||||
|
"EDIT_COMMENT_TITLE": "Edit Comment",
|
||||||
|
"EDIT_COMMENT_TEXT": "Comment for '{}':",
|
||||||
|
"EDIT_SHORTCUT_TITLE": "Assign Shortcut",
|
||||||
|
"EDIT_SHORTCUT_TEXT": "Press keys for '{}':",
|
||||||
|
"MOVE_UP": "Move Up",
|
||||||
|
"MOVE_DOWN": "Move Down",
|
||||||
"MENU_SHOW_FILTER": "Show Filter",
|
"MENU_SHOW_FILTER": "Show Filter",
|
||||||
"MENU_SHOW_LAYOUTS": "Show Layouts",
|
"MENU_SHOW_LAYOUTS": "Show Layouts",
|
||||||
"MENU_SHOW_HISTORY": "Show History",
|
"MENU_SHOW_HISTORY": "Show History",
|
||||||
@@ -606,12 +619,26 @@ _UI_TEXTS = {
|
|||||||
"MEDIAPIPE_DOWNLOAD_ERROR_TEXT": "Failed to download the MediaPipe model: {}",
|
"MEDIAPIPE_DOWNLOAD_ERROR_TEXT": "Failed to download the MediaPipe model: {}",
|
||||||
"MENU_FILMSTRIP_POSITION": "Filmstrip Position",
|
"MENU_FILMSTRIP_POSITION": "Filmstrip Position",
|
||||||
"FILMSTRIP_BOTTOM": "Bottom",
|
"FILMSTRIP_BOTTOM": "Bottom",
|
||||||
|
"VIEWER_MENU_COMPARE": "Comparison Mode",
|
||||||
"FILMSTRIP_LEFT": "Left",
|
"FILMSTRIP_LEFT": "Left",
|
||||||
"FILMSTRIP_TOP": "Top",
|
"FILMSTRIP_TOP": "Top",
|
||||||
"FILMSTRIP_RIGHT": "Right",
|
"FILMSTRIP_RIGHT": "Right",
|
||||||
"FILMSTRIP_POS_CHANGED_INFO": "The new filmstrip position will be applied to "
|
"FILMSTRIP_POS_CHANGED_INFO": "The new filmstrip position will be applied to "
|
||||||
"newly opened viewers.",
|
"newly opened viewers.",
|
||||||
"MENU_SHOW_SHORTCUTS": "Configure Keyboard Shortcuts...",
|
"MENU_SHOW_SHORTCUTS": "Configure Keyboard Shortcuts...",
|
||||||
|
"VIEWER_MENU_MANIPULATE": "Manipulate",
|
||||||
|
"VIEWER_MENU_ZOOM": "Zoom",
|
||||||
|
"SAVE_CROP_TITLE": "Save Cropped Image",
|
||||||
|
"COMPARE_LINKED": " [Linked]",
|
||||||
|
"COMPARE_UNLINKED": " [Unlinked]",
|
||||||
|
"CROP_INDICATOR": " [CROP]",
|
||||||
|
"OPEN_WITH_OTHER": "Open with other application...",
|
||||||
|
"COLLAPSE_EXPAND_GROUP": "Collapse/Expand Group",
|
||||||
|
"MENU_TOGGLE_MAIN_WINDOW": "Show/Hide Main Window",
|
||||||
|
"LOADING_DATA": "Loading data...",
|
||||||
|
"SETTINGS_PLACEHOLDER_TAGS": "tag1, tag2, tag3/subtag",
|
||||||
|
"THUMBNAILS_GENERATE_PROGRESS": "Generating {}px thumbnails: {}/{}",
|
||||||
|
"THUMBNAILS_REGENERATE_PROGRESS": "Regenerating thumbnail: {}/{}",
|
||||||
"SHORTCUTS_TITLE": "Keyboard Shortcuts",
|
"SHORTCUTS_TITLE": "Keyboard Shortcuts",
|
||||||
"SHORTCUTS_ACTION": "Action",
|
"SHORTCUTS_ACTION": "Action",
|
||||||
"SHORTCUTS_KEY": "Shortcut",
|
"SHORTCUTS_KEY": "Shortcut",
|
||||||
@@ -620,6 +647,7 @@ _UI_TEXTS = {
|
|||||||
"SHORTCUT_EDIT_LABEL": "Enter new shortcut for '{}'",
|
"SHORTCUT_EDIT_LABEL": "Enter new shortcut for '{}'",
|
||||||
"SHORTCUT_CONFLICT_TITLE": "Shortcut Conflict",
|
"SHORTCUT_CONFLICT_TITLE": "Shortcut Conflict",
|
||||||
"SHORTCUT_CONFLICT_TEXT": "The shortcut '{}' is already assigned to '{}'.",
|
"SHORTCUT_CONFLICT_TEXT": "The shortcut '{}' is already assigned to '{}'.",
|
||||||
|
"SHORTCUT_OVERRIDE_QUESTION": "Do you want to override it?",
|
||||||
"SHORTCUT_SEARCH_PLACEHOLDER": "Search shortcuts...",
|
"SHORTCUT_SEARCH_PLACEHOLDER": "Search shortcuts...",
|
||||||
"CACHE_CLEANING": "Cleaning cache...",
|
"CACHE_CLEANING": "Cleaning cache...",
|
||||||
"CACHE_CLEANED": "Cache cleaned. Removed {} invalid entries.",
|
"CACHE_CLEANED": "Cache cleaned. Removed {} invalid entries.",
|
||||||
@@ -647,7 +675,7 @@ _UI_TEXTS = {
|
|||||||
"RENAME_ERROR_EXISTS": "File '{}' already exists.",
|
"RENAME_ERROR_EXISTS": "File '{}' already exists.",
|
||||||
"FILE_RENAMED": "File renamed to {}",
|
"FILE_RENAMED": "File renamed to {}",
|
||||||
"ERROR_RENAME": "Could not rename file: {}",
|
"ERROR_RENAME": "Could not rename file: {}",
|
||||||
"MAIN_DOCK_TITLE": "Main dock",
|
"MAIN_DOCK_TITLE": "",
|
||||||
"LAYOUTS_TAB": "Layouts",
|
"LAYOUTS_TAB": "Layouts",
|
||||||
"LAYOUTS_TABLE_HEADER": ["Name", "Last Modified"],
|
"LAYOUTS_TABLE_HEADER": ["Name", "Last Modified"],
|
||||||
"SAVE_LAYOUT_TITLE": "Save Layout",
|
"SAVE_LAYOUT_TITLE": "Save Layout",
|
||||||
@@ -901,6 +929,17 @@ _UI_TEXTS = {
|
|||||||
"MENU_CLEAN_CACHE": "Limpiar entradas de caché inválidas",
|
"MENU_CLEAN_CACHE": "Limpiar entradas de caché inválidas",
|
||||||
"MENU_SHOW_TAGS": "Mostrar Etiquetas",
|
"MENU_SHOW_TAGS": "Mostrar Etiquetas",
|
||||||
"MENU_SHOW_INFO": "Mostrar Información",
|
"MENU_SHOW_INFO": "Mostrar Información",
|
||||||
|
"MENU_SHOW_FAVORITES": "Mostrar Favoritos",
|
||||||
|
"FAVORITES_TAB": "Favoritos",
|
||||||
|
"FAVORITES_SEARCH_PLACEHOLDER": "Buscar favoritos...",
|
||||||
|
"FAVORITES_TABLE_HEADER": ["Comentario", "Consulta", "Atajo"],
|
||||||
|
"ADD_FAVORITE_TOOLTIP": "Añadir búsqueda actual a favoritos",
|
||||||
|
"EDIT_COMMENT_TITLE": "Editar Comentario",
|
||||||
|
"EDIT_COMMENT_TEXT": "Comentario para '{}':",
|
||||||
|
"EDIT_SHORTCUT_TITLE": "Asignar Atajo",
|
||||||
|
"EDIT_SHORTCUT_TEXT": "Pulsa las teclas para '{}':",
|
||||||
|
"MOVE_UP": "Subir",
|
||||||
|
"MOVE_DOWN": "Bajar",
|
||||||
"MENU_SHOW_FILTER": "Mostrar Filtro",
|
"MENU_SHOW_FILTER": "Mostrar Filtro",
|
||||||
"MENU_SHOW_LAYOUTS": "Mostrar Diseños",
|
"MENU_SHOW_LAYOUTS": "Mostrar Diseños",
|
||||||
"MENU_SHOW_HISTORY": "Mostrar Historial",
|
"MENU_SHOW_HISTORY": "Mostrar Historial",
|
||||||
@@ -1056,6 +1095,7 @@ _UI_TEXTS = {
|
|||||||
"MEDIAPIPE_DOWNLOAD_ERROR_TEXT": "Fallo al descargar el modelo de MediaPipe: "
|
"MEDIAPIPE_DOWNLOAD_ERROR_TEXT": "Fallo al descargar el modelo de MediaPipe: "
|
||||||
"{}",
|
"{}",
|
||||||
"MENU_VIEWER_SETTINGS": "Opciones del Visor",
|
"MENU_VIEWER_SETTINGS": "Opciones del Visor",
|
||||||
|
"VIEWER_MENU_COMPARE": "Modo Comparación",
|
||||||
"MENU_FILMSTRIP_POSITION": "Posición de la Tira de Imágenes",
|
"MENU_FILMSTRIP_POSITION": "Posición de la Tira de Imágenes",
|
||||||
"FILMSTRIP_BOTTOM": "Abajo",
|
"FILMSTRIP_BOTTOM": "Abajo",
|
||||||
"FILMSTRIP_LEFT": "Izquierda",
|
"FILMSTRIP_LEFT": "Izquierda",
|
||||||
@@ -1063,6 +1103,17 @@ _UI_TEXTS = {
|
|||||||
"FILMSTRIP_RIGHT": "Derecha",
|
"FILMSTRIP_RIGHT": "Derecha",
|
||||||
"FILMSTRIP_POS_CHANGED_INFO": "La nueva posición de la tira de imágenes se "
|
"FILMSTRIP_POS_CHANGED_INFO": "La nueva posición de la tira de imágenes se "
|
||||||
"aplicará a los nuevos visores que se abran.",
|
"aplicará a los nuevos visores que se abran.",
|
||||||
|
"SAVE_CROP_TITLE": "Guardar Imagen Recortada",
|
||||||
|
"COMPARE_LINKED": " [Vinculado]",
|
||||||
|
"COMPARE_UNLINKED": " [Desvinculado]",
|
||||||
|
"CROP_INDICATOR": " [RECORTE]",
|
||||||
|
"OPEN_WITH_OTHER": "Abrir con otra aplicación...",
|
||||||
|
"COLLAPSE_EXPAND_GROUP": "Contraer/Expandir Grupo",
|
||||||
|
"MENU_TOGGLE_MAIN_WINDOW": "Mostrar/Ocultar ventana principal",
|
||||||
|
"LOADING_DATA": "Cargando datos...",
|
||||||
|
"SETTINGS_PLACEHOLDER_TAGS": "etiqueta1, etiqueta2, carpeta/etiqueta",
|
||||||
|
"THUMBNAILS_GENERATE_PROGRESS": "Generando miniaturas de {}px: {}/{}",
|
||||||
|
"THUMBNAILS_REGENERATE_PROGRESS": "Regenerando miniatura: {}/{}",
|
||||||
"MENU_SHOW_SHORTCUTS": "Configurar Atajos de Teclado...",
|
"MENU_SHOW_SHORTCUTS": "Configurar Atajos de Teclado...",
|
||||||
"SHORTCUTS_TITLE": "Atajos de Teclado",
|
"SHORTCUTS_TITLE": "Atajos de Teclado",
|
||||||
"SHORTCUTS_ACTION": "Acción",
|
"SHORTCUTS_ACTION": "Acción",
|
||||||
@@ -1072,6 +1123,7 @@ _UI_TEXTS = {
|
|||||||
"SHORTCUT_EDIT_LABEL": "Nuevo atajo para '{}'",
|
"SHORTCUT_EDIT_LABEL": "Nuevo atajo para '{}'",
|
||||||
"SHORTCUT_CONFLICT_TITLE": "Conflicto de Atajos",
|
"SHORTCUT_CONFLICT_TITLE": "Conflicto de Atajos",
|
||||||
"SHORTCUT_CONFLICT_TEXT": "El atajo '{}' ya está asignado a '{}'.",
|
"SHORTCUT_CONFLICT_TEXT": "El atajo '{}' ya está asignado a '{}'.",
|
||||||
|
"SHORTCUT_OVERRIDE_QUESTION": "¿Deseas sobrescribirlo?",
|
||||||
"SHORTCUT_SEARCH_PLACEHOLDER": "Buscar atajos...",
|
"SHORTCUT_SEARCH_PLACEHOLDER": "Buscar atajos...",
|
||||||
"CACHE_CLEANING": "Limpiando caché...",
|
"CACHE_CLEANING": "Limpiando caché...",
|
||||||
"CACHE_CLEANED": "Caché limpiada. Se eliminaron {} entradas inválidas.",
|
"CACHE_CLEANED": "Caché limpiada. Se eliminaron {} entradas inválidas.",
|
||||||
@@ -1349,6 +1401,17 @@ _UI_TEXTS = {
|
|||||||
"MENU_CLEAN_CACHE": "Limpar entradas de caché inválidas",
|
"MENU_CLEAN_CACHE": "Limpar entradas de caché inválidas",
|
||||||
"MENU_SHOW_TAGS": "Amosar Etiquetas",
|
"MENU_SHOW_TAGS": "Amosar Etiquetas",
|
||||||
"MENU_SHOW_INFO": "Amosar Información",
|
"MENU_SHOW_INFO": "Amosar Información",
|
||||||
|
"MENU_SHOW_FAVORITES": "Amosar Favoritos",
|
||||||
|
"FAVORITES_TAB": "Favoritos",
|
||||||
|
"FAVORITES_SEARCH_PLACEHOLDER": "Buscar favoritos...",
|
||||||
|
"FAVORITES_TABLE_HEADER": ["Comentario", "Consulta", "Atallo"],
|
||||||
|
"ADD_FAVORITE_TOOLTIP": "Engadir busca actual a favoritos",
|
||||||
|
"EDIT_COMMENT_TITLE": "Editar Comentario",
|
||||||
|
"EDIT_COMMENT_TEXT": "Comentario para '{}':",
|
||||||
|
"EDIT_SHORTCUT_TITLE": "Asignar Atallo",
|
||||||
|
"EDIT_SHORTCUT_TEXT": "Preme as teclas para '{}':",
|
||||||
|
"MOVE_UP": "Subir",
|
||||||
|
"MOVE_DOWN": "Baixar",
|
||||||
"MENU_SHOW_FILTER": "Amosar Filtro",
|
"MENU_SHOW_FILTER": "Amosar Filtro",
|
||||||
"MENU_SHOW_LAYOUTS": "Amosar Deseños",
|
"MENU_SHOW_LAYOUTS": "Amosar Deseños",
|
||||||
"MENU_SHOW_HISTORY": "Amosar Historial",
|
"MENU_SHOW_HISTORY": "Amosar Historial",
|
||||||
@@ -1504,6 +1567,7 @@ _UI_TEXTS = {
|
|||||||
"MEDIAPIPE_DOWNLOAD_ERROR_TEXT": "Fallo ao descargar o modelo de MediaPipe: {}",
|
"MEDIAPIPE_DOWNLOAD_ERROR_TEXT": "Fallo ao descargar o modelo de MediaPipe: {}",
|
||||||
"MENU_VIEWER_SETTINGS": "Opcións do Visor",
|
"MENU_VIEWER_SETTINGS": "Opcións do Visor",
|
||||||
"MENU_FILMSTRIP_POSITION": "Posición da Tira de Imaxes",
|
"MENU_FILMSTRIP_POSITION": "Posición da Tira de Imaxes",
|
||||||
|
"VIEWER_MENU_COMPARE": "Modo Comparación",
|
||||||
"FILMSTRIP_BOTTOM": "Abaixo",
|
"FILMSTRIP_BOTTOM": "Abaixo",
|
||||||
"FILMSTRIP_LEFT": "Esquerda",
|
"FILMSTRIP_LEFT": "Esquerda",
|
||||||
"FILMSTRIP_TOP": "Arriba",
|
"FILMSTRIP_TOP": "Arriba",
|
||||||
@@ -1511,6 +1575,17 @@ _UI_TEXTS = {
|
|||||||
"FILMSTRIP_POS_CHANGED_INFO": "A nova posición da tira de imaxes aplicarase "
|
"FILMSTRIP_POS_CHANGED_INFO": "A nova posición da tira de imaxes aplicarase "
|
||||||
"aos novos visores que se abran.",
|
"aos novos visores que se abran.",
|
||||||
"MENU_SHOW_SHORTCUTS": "Configurar Atallos de Teclado...",
|
"MENU_SHOW_SHORTCUTS": "Configurar Atallos de Teclado...",
|
||||||
|
"COMPARE_LINKED": " [Vencellado]",
|
||||||
|
"COMPARE_UNLINKED": " [Desvencellado]",
|
||||||
|
"CROP_INDICATOR": " [RECORTE]",
|
||||||
|
"OPEN_WITH_OTHER": "Abrir con outra aplicación...",
|
||||||
|
"COLLAPSE_EXPAND_GROUP": "Contraer/Expandir Grupo",
|
||||||
|
"MENU_TOGGLE_MAIN_WINDOW": "Amosar/Ocultar xanela principal",
|
||||||
|
"LOADING_DATA": "Cargando datos...",
|
||||||
|
"SETTINGS_PLACEHOLDER_TAGS": "etiqueta1, etiqueta2, cartafol/etiqueta",
|
||||||
|
"THUMBNAILS_GENERATE_PROGRESS": "Xerando miniaturas de {}px: {}/{}",
|
||||||
|
"THUMBNAILS_REGENERATE_PROGRESS": "Rexerando miniatura: {}/{}",
|
||||||
|
"SAVE_CROP_TITLE": "Gardar Imaxe Recortada",
|
||||||
"SHORTCUTS_TITLE": "Atallos de Teclado",
|
"SHORTCUTS_TITLE": "Atallos de Teclado",
|
||||||
"SHORTCUTS_ACTION": "Acción",
|
"SHORTCUTS_ACTION": "Acción",
|
||||||
"SHORTCUTS_KEY": "Atallo",
|
"SHORTCUTS_KEY": "Atallo",
|
||||||
@@ -1519,6 +1594,7 @@ _UI_TEXTS = {
|
|||||||
"SHORTCUT_EDIT_LABEL": "Novo Atallo para '{}'",
|
"SHORTCUT_EDIT_LABEL": "Novo Atallo para '{}'",
|
||||||
"SHORTCUT_CONFLICT_TITLE": "Conflito de Atallos",
|
"SHORTCUT_CONFLICT_TITLE": "Conflito de Atallos",
|
||||||
"SHORTCUT_CONFLICT_TEXT": "O atallo '{}' xa está asignado a '{}'.",
|
"SHORTCUT_CONFLICT_TEXT": "O atallo '{}' xa está asignado a '{}'.",
|
||||||
|
"SHORTCUT_OVERRIDE_QUESTION": "Desexas sobrescribilo?",
|
||||||
"SHORTCUT_SEARCH_PLACEHOLDER": "Buscar atallos...",
|
"SHORTCUT_SEARCH_PLACEHOLDER": "Buscar atallos...",
|
||||||
"CACHE_CLEANING": "Limpando caché...",
|
"CACHE_CLEANING": "Limpando caché...",
|
||||||
"CACHE_CLEANED": "Caché limpada. Elimináronse {} entradas inválidas.",
|
"CACHE_CLEANED": "Caché limpada. Elimináronse {} entradas inválidas.",
|
||||||
|
|||||||
@@ -1279,6 +1279,7 @@ class ThumbnailGenerator(QThread):
|
|||||||
nonlocal processed_count
|
nonlocal processed_count
|
||||||
processed_count += 1
|
processed_count += 1
|
||||||
if processed_count % 5 == 0 or processed_count == total:
|
if processed_count % 5 == 0 or processed_count == total:
|
||||||
|
# Signal remains int, format in receiver
|
||||||
self.progress.emit(processed_count, total)
|
self.progress.emit(processed_count, total)
|
||||||
|
|
||||||
# Use a direct connection or queued connection depending on context,
|
# Use a direct connection or queued connection depending on context,
|
||||||
|
|||||||
@@ -2184,11 +2184,12 @@ class ImageViewer(QWidget):
|
|||||||
self.populate_filmstrip()
|
self.populate_filmstrip()
|
||||||
self.update_status_bar(index=new_index)
|
self.update_status_bar(index=new_index)
|
||||||
|
|
||||||
def _on_movie_frame(self):
|
def _on_movie_frame_for_pane(self, pane):
|
||||||
"""Updates the view with the current frame from the movie."""
|
"""Updates the view with the current frame from the movie for a specific
|
||||||
if self.movie and self.movie.isValid():
|
pane."""
|
||||||
self.controller.pixmap_original = self.movie.currentPixmap()
|
if pane.movie and pane.movie.isValid():
|
||||||
self.update_view(resize_win=False)
|
pane.controller.pixmap_original = pane.movie.currentPixmap()
|
||||||
|
pane.update_view(resize_win=False)
|
||||||
|
|
||||||
def toggle_animation_pause(self):
|
def toggle_animation_pause(self):
|
||||||
"""Pauses or resumes the current animation."""
|
"""Pauses or resumes the current animation."""
|
||||||
@@ -2335,7 +2336,8 @@ class ImageViewer(QWidget):
|
|||||||
|
|
||||||
if self.active_pane.crop_mode:
|
if self.active_pane.crop_mode:
|
||||||
self.active_pane.canvas.setCursor(Qt.CrossCursor)
|
self.active_pane.canvas.setCursor(Qt.CrossCursor)
|
||||||
self.sb_info_label.setText(f"{self.sb_info_label.text()} [CROP]")
|
self.sb_info_label.setText(
|
||||||
|
f"{self.sb_info_label.text()}{UITexts.CROP_INDICATOR}")
|
||||||
else:
|
else:
|
||||||
self.active_pane.canvas.setCursor(Qt.ArrowCursor)
|
self.active_pane.canvas.setCursor(Qt.ArrowCursor)
|
||||||
self.update_status_bar()
|
self.update_status_bar()
|
||||||
@@ -2432,7 +2434,8 @@ class ImageViewer(QWidget):
|
|||||||
info_text = f"{w} x {h} px | {zoom}%"
|
info_text = f"{w} x {h} px | {zoom}%"
|
||||||
|
|
||||||
if len(self.panes) > 1:
|
if len(self.panes) > 1:
|
||||||
info_text += " [Linked]" if self.panes_linked else " [Unlinked]"
|
info_text += UITexts.COMPARE_LINKED \
|
||||||
|
if self.panes_linked else UITexts.COMPARE_UNLINKED
|
||||||
|
|
||||||
self.sb_info_label.setText(info_text)
|
self.sb_info_label.setText(info_text)
|
||||||
|
|
||||||
@@ -2891,7 +2894,7 @@ class ImageViewer(QWidget):
|
|||||||
"action": "fullscreen", "icon": "view-fullscreen"
|
"action": "fullscreen", "icon": "view-fullscreen"
|
||||||
if not self.isFullScreen() else "view-restore"},
|
if not self.isFullScreen() else "view-restore"},
|
||||||
"separator",
|
"separator",
|
||||||
{"text": "Show/hide main window",
|
{"text": UITexts.MENU_TOGGLE_MAIN_WINDOW,
|
||||||
"action": "toggle_visibility", "icon": "view-restore"},
|
"action": "toggle_visibility", "icon": "view-restore"},
|
||||||
"separator",
|
"separator",
|
||||||
{"text": UITexts.CONTEXT_MENU_PROPERTIES,
|
{"text": UITexts.CONTEXT_MENU_PROPERTIES,
|
||||||
@@ -3288,13 +3291,13 @@ class ImageViewer(QWidget):
|
|||||||
Args:
|
Args:
|
||||||
event (QCloseEvent): The close event.
|
event (QCloseEvent): The close event.
|
||||||
"""
|
"""
|
||||||
if self.movie:
|
for pane in self.panes:
|
||||||
self.movie.stop()
|
pane.cleanup()
|
||||||
|
|
||||||
self.slideshow_manager.stop()
|
self.slideshow_manager.stop()
|
||||||
if self.filmstrip_loader and self.filmstrip_loader.isRunning():
|
if self.filmstrip_loader and self.filmstrip_loader.isRunning():
|
||||||
self.filmstrip_loader.stop()
|
self.filmstrip_loader.stop()
|
||||||
self.uninhibit_screensaver()
|
self.uninhibit_screensaver()
|
||||||
self.controller.cleanup()
|
|
||||||
# If we close the last viewer and the main window is hidden, quit.
|
# If we close the last viewer and the main window is hidden, quit.
|
||||||
if self.main_win and not self.main_win.isVisible():
|
if self.main_win and not self.main_win.isVisible():
|
||||||
# Check how many viewers are left
|
# Check how many viewers are left
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ class PropertiesDialog(QDialog):
|
|||||||
if exif_data is None:
|
if exif_data is None:
|
||||||
# Loading state
|
# Loading state
|
||||||
self.exif_table.setRowCount(1)
|
self.exif_table.setRowCount(1)
|
||||||
item = QTableWidgetItem("Loading data...")
|
item = QTableWidgetItem(UITexts.LOADING_DATA)
|
||||||
item.setFlags(Qt.ItemIsEnabled)
|
item.setFlags(Qt.ItemIsEnabled)
|
||||||
self.exif_table.setItem(0, 0, item)
|
self.exif_table.setItem(0, 0, item)
|
||||||
self.exif_table.blockSignals(False)
|
self.exif_table.blockSignals(False)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "bagheeraview"
|
name = "bagheeraview"
|
||||||
version = "0.9.12"
|
version = "0.9.13"
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "Ignacio Serantes" }
|
{ name = "Ignacio Serantes" }
|
||||||
]
|
]
|
||||||
|
|||||||
20
settings.py
20
settings.py
@@ -349,7 +349,7 @@ class SettingsDialog(QDialog):
|
|||||||
faces_layout = QVBoxLayout(faces_tab)
|
faces_layout = QVBoxLayout(faces_tab)
|
||||||
|
|
||||||
# Faces Header
|
# Faces Header
|
||||||
faces_header = QLabel("Faces")
|
faces_header = QLabel(UITexts.TYPE_FACE)
|
||||||
faces_header.setFont(QFont("Sans", 10, QFont.Bold))
|
faces_header.setFont(QFont("Sans", 10, QFont.Bold))
|
||||||
faces_layout.addWidget(faces_header)
|
faces_layout.addWidget(faces_header)
|
||||||
|
|
||||||
@@ -357,7 +357,7 @@ class SettingsDialog(QDialog):
|
|||||||
person_tags_layout = QHBoxLayout()
|
person_tags_layout = QHBoxLayout()
|
||||||
person_tags_label = QLabel(UITexts.SETTINGS_PERSON_TAGS_LABEL)
|
person_tags_label = QLabel(UITexts.SETTINGS_PERSON_TAGS_LABEL)
|
||||||
self.person_tags_edit = QLineEdit()
|
self.person_tags_edit = QLineEdit()
|
||||||
self.person_tags_edit.setPlaceholderText("tag1, tag2, tag3/subtag")
|
self.person_tags_edit.setPlaceholderText(UITexts.SETTINGS_PLACEHOLDER_TAGS)
|
||||||
self.person_tags_edit.setClearButtonEnabled(True)
|
self.person_tags_edit.setClearButtonEnabled(True)
|
||||||
person_tags_layout.addWidget(person_tags_label)
|
person_tags_layout.addWidget(person_tags_label)
|
||||||
person_tags_layout.addWidget(self.person_tags_edit)
|
person_tags_layout.addWidget(self.person_tags_edit)
|
||||||
@@ -411,14 +411,14 @@ class SettingsDialog(QDialog):
|
|||||||
|
|
||||||
# --- Pets Section ---
|
# --- Pets Section ---
|
||||||
faces_layout.addSpacing(10)
|
faces_layout.addSpacing(10)
|
||||||
pets_header = QLabel("Pets")
|
pets_header = QLabel(UITexts.TYPE_PET)
|
||||||
pets_header.setFont(QFont("Sans", 10, QFont.Bold))
|
pets_header.setFont(QFont("Sans", 10, QFont.Bold))
|
||||||
faces_layout.addWidget(pets_header)
|
faces_layout.addWidget(pets_header)
|
||||||
|
|
||||||
pet_tags_layout = QHBoxLayout()
|
pet_tags_layout = QHBoxLayout()
|
||||||
pet_tags_label = QLabel(UITexts.SETTINGS_PET_TAGS_LABEL)
|
pet_tags_label = QLabel(UITexts.SETTINGS_PET_TAGS_LABEL)
|
||||||
self.pet_tags_edit = QLineEdit()
|
self.pet_tags_edit = QLineEdit()
|
||||||
self.pet_tags_edit.setPlaceholderText("tag1, tag2, tag3/subtag")
|
self.pet_tags_edit.setPlaceholderText(UITexts.SETTINGS_PLACEHOLDER_TAGS)
|
||||||
self.pet_tags_edit.setClearButtonEnabled(True)
|
self.pet_tags_edit.setClearButtonEnabled(True)
|
||||||
pet_tags_layout.addWidget(pet_tags_label)
|
pet_tags_layout.addWidget(pet_tags_label)
|
||||||
pet_tags_layout.addWidget(self.pet_tags_edit)
|
pet_tags_layout.addWidget(self.pet_tags_edit)
|
||||||
@@ -467,14 +467,14 @@ class SettingsDialog(QDialog):
|
|||||||
|
|
||||||
# --- Body Section ---
|
# --- Body Section ---
|
||||||
faces_layout.addSpacing(10)
|
faces_layout.addSpacing(10)
|
||||||
body_header = QLabel("Body")
|
body_header = QLabel(UITexts.TYPE_BODY)
|
||||||
body_header.setFont(QFont("Sans", 10, QFont.Bold))
|
body_header.setFont(QFont("Sans", 10, QFont.Bold))
|
||||||
faces_layout.addWidget(body_header)
|
faces_layout.addWidget(body_header)
|
||||||
|
|
||||||
body_tags_layout = QHBoxLayout()
|
body_tags_layout = QHBoxLayout()
|
||||||
body_tags_label = QLabel(UITexts.SETTINGS_BODY_TAGS_LABEL)
|
body_tags_label = QLabel(UITexts.SETTINGS_BODY_TAGS_LABEL)
|
||||||
self.body_tags_edit = QLineEdit()
|
self.body_tags_edit = QLineEdit()
|
||||||
self.body_tags_edit.setPlaceholderText("tag1, tag2, tag3/subtag")
|
self.body_tags_edit.setPlaceholderText(UITexts.SETTINGS_PLACEHOLDER_TAGS)
|
||||||
self.body_tags_edit.setClearButtonEnabled(True)
|
self.body_tags_edit.setClearButtonEnabled(True)
|
||||||
body_tags_layout.addWidget(body_tags_label)
|
body_tags_layout.addWidget(body_tags_label)
|
||||||
body_tags_layout.addWidget(self.body_tags_edit)
|
body_tags_layout.addWidget(self.body_tags_edit)
|
||||||
@@ -514,14 +514,14 @@ class SettingsDialog(QDialog):
|
|||||||
|
|
||||||
# --- Object Section ---
|
# --- Object Section ---
|
||||||
faces_layout.addSpacing(10)
|
faces_layout.addSpacing(10)
|
||||||
object_header = QLabel("Object")
|
object_header = QLabel(UITexts.TYPE_OBJECT)
|
||||||
object_header.setFont(QFont("Sans", 10, QFont.Bold))
|
object_header.setFont(QFont("Sans", 10, QFont.Bold))
|
||||||
faces_layout.addWidget(object_header)
|
faces_layout.addWidget(object_header)
|
||||||
|
|
||||||
object_tags_layout = QHBoxLayout()
|
object_tags_layout = QHBoxLayout()
|
||||||
object_tags_label = QLabel(UITexts.SETTINGS_OBJECT_TAGS_LABEL)
|
object_tags_label = QLabel(UITexts.SETTINGS_OBJECT_TAGS_LABEL)
|
||||||
self.object_tags_edit = QLineEdit()
|
self.object_tags_edit = QLineEdit()
|
||||||
self.object_tags_edit.setPlaceholderText("tag1, tag2, tag3/subtag")
|
self.object_tags_edit.setPlaceholderText(UITexts.SETTINGS_PLACEHOLDER_TAGS)
|
||||||
self.object_tags_edit.setClearButtonEnabled(True)
|
self.object_tags_edit.setClearButtonEnabled(True)
|
||||||
object_tags_layout.addWidget(object_tags_label)
|
object_tags_layout.addWidget(object_tags_label)
|
||||||
object_tags_layout.addWidget(self.object_tags_edit)
|
object_tags_layout.addWidget(self.object_tags_edit)
|
||||||
@@ -560,14 +560,14 @@ class SettingsDialog(QDialog):
|
|||||||
|
|
||||||
# --- Landmark Section ---
|
# --- Landmark Section ---
|
||||||
faces_layout.addSpacing(10)
|
faces_layout.addSpacing(10)
|
||||||
landmark_header = QLabel("Landmark")
|
landmark_header = QLabel(UITexts.TYPE_LANDMARK)
|
||||||
landmark_header.setFont(QFont("Sans", 10, QFont.Bold))
|
landmark_header.setFont(QFont("Sans", 10, QFont.Bold))
|
||||||
faces_layout.addWidget(landmark_header)
|
faces_layout.addWidget(landmark_header)
|
||||||
|
|
||||||
landmark_tags_layout = QHBoxLayout()
|
landmark_tags_layout = QHBoxLayout()
|
||||||
landmark_tags_label = QLabel(UITexts.SETTINGS_LANDMARK_TAGS_LABEL)
|
landmark_tags_label = QLabel(UITexts.SETTINGS_LANDMARK_TAGS_LABEL)
|
||||||
self.landmark_tags_edit = QLineEdit()
|
self.landmark_tags_edit = QLineEdit()
|
||||||
self.landmark_tags_edit.setPlaceholderText("tag1, tag2, tag3/subtag")
|
self.landmark_tags_edit.setPlaceholderText(UITexts.SETTINGS_PLACEHOLDER_TAGS)
|
||||||
self.landmark_tags_edit.setClearButtonEnabled(True)
|
self.landmark_tags_edit.setClearButtonEnabled(True)
|
||||||
landmark_tags_layout.addWidget(landmark_tags_label)
|
landmark_tags_layout.addWidget(landmark_tags_label)
|
||||||
landmark_tags_layout.addWidget(self.landmark_tags_edit)
|
landmark_tags_layout.addWidget(self.landmark_tags_edit)
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="bagheeraview",
|
name="bagheeraview",
|
||||||
version="0.9.12",
|
version="0.9.13",
|
||||||
author="Ignacio Serantes",
|
author="Ignacio Serantes",
|
||||||
description="Bagheera Image Viewer - An image viewer for KDE with Baloo in mind",
|
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 "
|
long_description="A fast image viewer built with PySide6, featuring search and "
|
||||||
|
|||||||
236
widgets.py
236
widgets.py
@@ -12,6 +12,7 @@ including:
|
|||||||
import os
|
import os
|
||||||
import glob
|
import glob
|
||||||
import shutil
|
import shutil
|
||||||
|
import json
|
||||||
import lmdb
|
import lmdb
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from collections import deque
|
from collections import deque
|
||||||
@@ -20,11 +21,11 @@ from PySide6.QtWidgets import (
|
|||||||
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton,
|
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton,
|
||||||
QMessageBox, QSizePolicy, QInputDialog, QTableWidget, QTableWidgetItem,
|
QMessageBox, QSizePolicy, QInputDialog, QTableWidget, QTableWidgetItem,
|
||||||
QMenu, QHeaderView, QAbstractItemView, QTreeView, QLabel, QTextEdit,
|
QMenu, QHeaderView, QAbstractItemView, QTreeView, QLabel, QTextEdit,
|
||||||
QComboBox, QCompleter, QToolBar
|
QComboBox, QCompleter, QToolBar, QDialog
|
||||||
)
|
)
|
||||||
from PySide6.QtGui import (
|
from PySide6.QtGui import (
|
||||||
QIcon, QStandardItemModel, QStandardItem, QColor, QPainter, QPen,
|
QIcon, QStandardItemModel, QStandardItem, QColor, QPainter, QPen,
|
||||||
QPalette, QAction,
|
QPalette, QAction, QKeySequence
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import (
|
from PySide6.QtCore import (
|
||||||
Signal, QSortFilterProxyModel, Slot, QStringListModel, Qt
|
Signal, QSortFilterProxyModel, Slot, QStringListModel, Qt
|
||||||
@@ -33,7 +34,7 @@ from PySide6.QtCore import (
|
|||||||
from metadatamanager import XattrManager
|
from metadatamanager import XattrManager
|
||||||
from constants import (
|
from constants import (
|
||||||
LAYOUTS_DIR, RATING_XATTR_NAME, XATTR_COMMENT_NAME, XATTR_NAME, UITexts,
|
LAYOUTS_DIR, RATING_XATTR_NAME, XATTR_COMMENT_NAME, XATTR_NAME, UITexts,
|
||||||
FACES_MENU_MAX_ITEMS_DEFAULT, APP_CONFIG
|
FACES_MENU_MAX_ITEMS_DEFAULT, APP_CONFIG, FAVORITES_PATH
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -513,7 +514,7 @@ class TagEditWidget(QWidget):
|
|||||||
self.refresh_ui()
|
self.refresh_ui()
|
||||||
self.tags_updated.emit(updated_files_tags)
|
self.tags_updated.emit(updated_files_tags)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.critical(self, "Error", str(e))
|
QMessageBox.critical(self, UITexts.ERROR, str(e))
|
||||||
finally:
|
finally:
|
||||||
QApplication.restoreOverrideCursor()
|
QApplication.restoreOverrideCursor()
|
||||||
|
|
||||||
@@ -898,6 +899,233 @@ class HistoryWidget(QWidget):
|
|||||||
self.refresh_list()
|
self.refresh_list()
|
||||||
|
|
||||||
|
|
||||||
|
class FavoritesWidget(QWidget):
|
||||||
|
"""A widget for managing favorite search queries."""
|
||||||
|
favorites_changed = Signal()
|
||||||
|
|
||||||
|
def __init__(self, main_win):
|
||||||
|
super().__init__()
|
||||||
|
self.main_win = main_win
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
|
||||||
|
self.search_bar = QLineEdit()
|
||||||
|
self.search_bar.setPlaceholderText(UITexts.FAVORITES_SEARCH_PLACEHOLDER)
|
||||||
|
self.search_bar.setClearButtonEnabled(True)
|
||||||
|
self.search_bar.textChanged.connect(self.filter_favorites)
|
||||||
|
layout.addWidget(self.search_bar)
|
||||||
|
|
||||||
|
self.table = QTableWidget()
|
||||||
|
self.table.setColumnCount(3)
|
||||||
|
self.table.setHorizontalHeaderLabels(UITexts.FAVORITES_TABLE_HEADER)
|
||||||
|
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
|
||||||
|
self.table.horizontalHeader().setStretchLastSection(True)
|
||||||
|
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||||
|
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||||
|
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
||||||
|
self.table.verticalHeader().setVisible(False)
|
||||||
|
self.table.doubleClicked.connect(self.load_selected)
|
||||||
|
layout.addWidget(self.table)
|
||||||
|
|
||||||
|
toolbar = QToolBar()
|
||||||
|
layout.addWidget(toolbar)
|
||||||
|
|
||||||
|
load_action = QAction(QIcon.fromTheme("system-run"), UITexts.LOAD, self)
|
||||||
|
load_action.triggered.connect(self.load_selected)
|
||||||
|
toolbar.addAction(load_action)
|
||||||
|
|
||||||
|
add_action = QAction(QIcon.fromTheme("list-add"), UITexts.CREATE, self)
|
||||||
|
add_action.setToolTip(UITexts.ADD_FAVORITE_TOOLTIP)
|
||||||
|
add_action.triggered.connect(self.add_favorite)
|
||||||
|
toolbar.addAction(add_action)
|
||||||
|
|
||||||
|
edit_action = QAction(QIcon.fromTheme("edit-rename"), UITexts.RENAME, self)
|
||||||
|
edit_action.triggered.connect(self.edit_comment)
|
||||||
|
toolbar.addAction(edit_action)
|
||||||
|
|
||||||
|
shortcut_action = QAction(
|
||||||
|
QIcon.fromTheme("preferences-desktop-keyboard-shortcuts"),
|
||||||
|
UITexts.SHORTCUTS_KEY, self)
|
||||||
|
shortcut_action.triggered.connect(self.edit_shortcut)
|
||||||
|
toolbar.addAction(shortcut_action)
|
||||||
|
|
||||||
|
delete_action = QAction(QIcon.fromTheme("edit-delete"), UITexts.DELETE, self)
|
||||||
|
delete_action.triggered.connect(self.delete_favorite)
|
||||||
|
toolbar.addAction(delete_action)
|
||||||
|
|
||||||
|
toolbar.addSeparator()
|
||||||
|
|
||||||
|
up_action = QAction(QIcon.fromTheme("go-up"), UITexts.MOVE_UP, self)
|
||||||
|
up_action.triggered.connect(self.move_up)
|
||||||
|
toolbar.addAction(up_action)
|
||||||
|
|
||||||
|
down_action = QAction(QIcon.fromTheme("go-down"), UITexts.MOVE_DOWN, self)
|
||||||
|
down_action.triggered.connect(self.move_down)
|
||||||
|
toolbar.addAction(down_action)
|
||||||
|
|
||||||
|
self.refresh_list()
|
||||||
|
|
||||||
|
def resizeEvent(self, event):
|
||||||
|
width = self.table.viewport().width()
|
||||||
|
self.table.setColumnWidth(0, int(width * 0.60))
|
||||||
|
self.table.setColumnWidth(2, int(width * 0.15))
|
||||||
|
super().resizeEvent(event)
|
||||||
|
|
||||||
|
def refresh_list(self):
|
||||||
|
self.table.setRowCount(0)
|
||||||
|
if not os.path.exists(FAVORITES_PATH):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(FAVORITES_PATH, 'r', encoding='utf-8') as f:
|
||||||
|
favorites = json.load(f)
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
favorites = []
|
||||||
|
|
||||||
|
self.table.setRowCount(len(favorites))
|
||||||
|
for i, fav in enumerate(favorites):
|
||||||
|
query = fav.get('query', '')
|
||||||
|
comment = fav.get('comment', '')
|
||||||
|
shortcut = fav.get('shortcut', '')
|
||||||
|
self.table.setItem(i, 0, QTableWidgetItem(comment))
|
||||||
|
self.table.setItem(i, 1, QTableWidgetItem(query))
|
||||||
|
self.table.setItem(i, 2, QTableWidgetItem(shortcut))
|
||||||
|
|
||||||
|
def filter_favorites(self, text):
|
||||||
|
search_text = text.lower()
|
||||||
|
for row in range(self.table.rowCount()):
|
||||||
|
item = self.table.item(row, 0)
|
||||||
|
if item:
|
||||||
|
self.table.setRowHidden(row, search_text not in item.text().lower())
|
||||||
|
|
||||||
|
def save_favorites(self):
|
||||||
|
favorites = []
|
||||||
|
for i in range(self.table.rowCount()):
|
||||||
|
item_comment = self.table.item(i, 0)
|
||||||
|
item_query = self.table.item(i, 1)
|
||||||
|
item_shortcut = self.table.item(i, 2)
|
||||||
|
comment = item_comment.text() if item_comment else ""
|
||||||
|
query = item_query.text() if item_query else ""
|
||||||
|
shortcut = item_shortcut.text() if item_shortcut else ""
|
||||||
|
favorites.append({'query': query, 'comment': comment, 'shortcut': shortcut})
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(FAVORITES_PATH, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(favorites, f, indent=4)
|
||||||
|
self.favorites_changed.emit()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def load_selected(self):
|
||||||
|
row = self.table.currentRow()
|
||||||
|
if row >= 0:
|
||||||
|
query = self.table.item(row, 1).text()
|
||||||
|
self.main_win.process_term(query)
|
||||||
|
|
||||||
|
def add_favorite(self):
|
||||||
|
query = self.main_win.search_input.currentText().strip()
|
||||||
|
if not query:
|
||||||
|
return
|
||||||
|
|
||||||
|
row = self.table.rowCount()
|
||||||
|
self.table.insertRow(row)
|
||||||
|
self.table.setItem(row, 0, QTableWidgetItem(""))
|
||||||
|
self.table.setItem(row, 1, QTableWidgetItem(query))
|
||||||
|
self.table.setItem(row, 2, QTableWidgetItem(""))
|
||||||
|
self.table.setCurrentCell(row, 0)
|
||||||
|
self.save_favorites()
|
||||||
|
|
||||||
|
def edit_comment(self):
|
||||||
|
row = self.table.currentRow()
|
||||||
|
if row < 0:
|
||||||
|
return
|
||||||
|
comment_item = self.table.item(row, 0)
|
||||||
|
query = self.table.item(row, 1).text()
|
||||||
|
old_comment = comment_item.text() if comment_item else ""
|
||||||
|
|
||||||
|
new_comment, ok = QInputDialog.getText(
|
||||||
|
self, UITexts.EDIT_COMMENT_TITLE,
|
||||||
|
UITexts.EDIT_COMMENT_TEXT.format(query),
|
||||||
|
QLineEdit.Normal, old_comment)
|
||||||
|
|
||||||
|
if ok:
|
||||||
|
self.table.item(row, 0).setText(new_comment)
|
||||||
|
self.save_favorites()
|
||||||
|
|
||||||
|
def edit_shortcut(self):
|
||||||
|
row = self.table.currentRow()
|
||||||
|
if row < 0:
|
||||||
|
return
|
||||||
|
query = self.table.item(row, 1).text()
|
||||||
|
current_sc = self.table.item(row, 2).text()
|
||||||
|
|
||||||
|
dialog = QDialog(self)
|
||||||
|
dialog.setWindowTitle(UITexts.EDIT_SHORTCUT_TITLE)
|
||||||
|
dlg_layout = QVBoxLayout(dialog)
|
||||||
|
dlg_layout.addWidget(QLabel(UITexts.EDIT_SHORTCUT_TEXT.format(query)))
|
||||||
|
|
||||||
|
from PySide6.QtWidgets import QKeySequenceEdit, QDialogButtonBox
|
||||||
|
key_edit = QKeySequenceEdit(QKeySequence(current_sc))
|
||||||
|
dlg_layout.addWidget(key_edit)
|
||||||
|
|
||||||
|
buttons = QDialogButtonBox(
|
||||||
|
QDialogButtonBox.Ok | QDialogButtonBox.Cancel | QDialogButtonBox.Reset)
|
||||||
|
buttons.accepted.connect(dialog.accept)
|
||||||
|
buttons.rejected.connect(dialog.reject)
|
||||||
|
buttons.button(QDialogButtonBox.Reset).clicked.connect(
|
||||||
|
lambda: key_edit.setKeySequence(QKeySequence()))
|
||||||
|
dlg_layout.addWidget(buttons)
|
||||||
|
|
||||||
|
if dialog.exec() == QDialog.Accepted:
|
||||||
|
new_sequence = key_edit.keySequence()
|
||||||
|
new_sc_str = new_sequence.toString(QKeySequence.NativeText)
|
||||||
|
if not new_sequence.isEmpty():
|
||||||
|
new_key_combo = new_sequence[0]
|
||||||
|
new_key = new_key_combo.key()
|
||||||
|
new_mods = new_key_combo.keyboardModifiers()
|
||||||
|
|
||||||
|
conflict_desc = self.main_win.shortcut_controller.check_conflict(
|
||||||
|
new_key, new_mods)
|
||||||
|
if conflict_desc and new_sc_str != current_sc:
|
||||||
|
res = QMessageBox.question(self, UITexts.SHORTCUT_CONFLICT_TITLE,
|
||||||
|
UITexts.SHORTCUT_CONFLICT_TEXT.format(
|
||||||
|
new_sc_str, conflict_desc) +
|
||||||
|
"\n\n" +
|
||||||
|
UITexts.SHORTCUT_OVERRIDE_QUESTION,
|
||||||
|
QMessageBox.Yes | QMessageBox.No)
|
||||||
|
if res == QMessageBox.No:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.table.item(row, 2).setText(new_sc_str)
|
||||||
|
self.save_favorites()
|
||||||
|
|
||||||
|
def delete_favorite(self):
|
||||||
|
row = self.table.currentRow()
|
||||||
|
if row >= 0:
|
||||||
|
self.table.removeRow(row)
|
||||||
|
self.save_favorites()
|
||||||
|
|
||||||
|
def move_up(self):
|
||||||
|
row = self.table.currentRow()
|
||||||
|
if row > 0:
|
||||||
|
self._swap_rows(row, row - 1)
|
||||||
|
self.table.setCurrentCell(row - 1, 0)
|
||||||
|
self.save_favorites()
|
||||||
|
|
||||||
|
def move_down(self):
|
||||||
|
row = self.table.currentRow()
|
||||||
|
if row >= 0 and row < self.table.rowCount() - 1:
|
||||||
|
self._swap_rows(row, row + 1)
|
||||||
|
self.table.setCurrentCell(row + 1, 0)
|
||||||
|
self.save_favorites()
|
||||||
|
|
||||||
|
def _swap_rows(self, row1, row2):
|
||||||
|
for col in range(self.table.columnCount()):
|
||||||
|
item1 = self.table.takeItem(row1, col)
|
||||||
|
item2 = self.table.takeItem(row2, col)
|
||||||
|
self.table.setItem(row1, col, item2)
|
||||||
|
self.table.setItem(row2, col, item1)
|
||||||
|
|
||||||
|
|
||||||
class RatingStar(QLabel):
|
class RatingStar(QLabel):
|
||||||
"""An individual star label for the rating widget."""
|
"""An individual star label for the rating widget."""
|
||||||
# Emits the star index (1-5)
|
# Emits the star index (1-5)
|
||||||
|
|||||||
Reference in New Issue
Block a user