v0.9.12
This commit is contained in:
473
imageviewer.py
473
imageviewer.py
@@ -172,14 +172,14 @@ class FastTagManager:
|
||||
def __init__(self, viewer):
|
||||
self.viewer = viewer
|
||||
self.main_win = viewer.main_win
|
||||
self.controller = viewer.controller
|
||||
|
||||
def show_menu(self):
|
||||
"""Builds and shows a context menu for quickly adding/removing tags."""
|
||||
if not self.main_win or not self.controller.get_current_path():
|
||||
controller = self.viewer.controller
|
||||
if not self.main_win or not controller or not controller.get_current_path():
|
||||
return
|
||||
|
||||
current_path = self.controller.get_current_path()
|
||||
current_path = controller.get_current_path()
|
||||
try:
|
||||
raw_tags = os.getxattr(current_path, XATTR_NAME).decode('utf-8')
|
||||
current_tags = {t.strip() for t in raw_tags.split(',') if t.strip()}
|
||||
@@ -233,10 +233,11 @@ class FastTagManager:
|
||||
return
|
||||
tag_name = action.text()
|
||||
is_checked = action.isChecked()
|
||||
current_path = self.controller.get_current_path()
|
||||
controller = self.viewer.controller
|
||||
current_path = controller.get_current_path() if controller else None
|
||||
if not current_path:
|
||||
return
|
||||
self.controller.toggle_tag(tag_name, is_checked)
|
||||
controller.toggle_tag(tag_name, is_checked)
|
||||
self.viewer.update_status_bar()
|
||||
if self.main_win:
|
||||
if is_checked:
|
||||
@@ -289,18 +290,23 @@ class FilmStripWidget(QListWidget):
|
||||
allowing the user to quickly jump to an image by clicking its thumbnail.
|
||||
It also supports dragging files out of the application.
|
||||
"""
|
||||
def __init__(self, controller, parent=None):
|
||||
def __init__(self, viewer, parent=None):
|
||||
"""
|
||||
Initializes the FilmStripWidget.
|
||||
|
||||
Args:
|
||||
controller (ImageController): The controller managing the image list.
|
||||
viewer (ImageViewer): The viewer that owns this filmstrip.
|
||||
parent (QWidget, optional): The parent widget. Defaults to None.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self.controller = controller
|
||||
self.viewer = viewer
|
||||
self.setDragEnabled(True)
|
||||
|
||||
@property
|
||||
def controller(self):
|
||||
"""Returns the controller of the active pane in the viewer."""
|
||||
return self.viewer.controller
|
||||
|
||||
def startDrag(self, supportedActions):
|
||||
"""
|
||||
Initiates a drag-and-drop operation for the selected image(s).
|
||||
@@ -312,7 +318,7 @@ class FilmStripWidget(QListWidget):
|
||||
urls = []
|
||||
for item in items:
|
||||
row = self.row(item)
|
||||
if 0 <= row < len(self.controller.image_list):
|
||||
if self.controller and 0 <= row < len(self.controller.image_list):
|
||||
path = self.controller.image_list[row]
|
||||
urls.append(QUrl.fromLocalFile(path))
|
||||
|
||||
@@ -332,6 +338,63 @@ class FilmStripWidget(QListWidget):
|
||||
|
||||
drag.exec(Qt.CopyAction)
|
||||
|
||||
def _get_selected_paths(self):
|
||||
"""Helper to get paths of all selected items."""
|
||||
return [item.data(Qt.UserRole)
|
||||
for item in self.selectedItems() if item.data(Qt.UserRole)]
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
"""Shows a context menu for the selected items."""
|
||||
selected_items = self.selectedItems()
|
||||
if not selected_items:
|
||||
return
|
||||
|
||||
menu = QMenu(self)
|
||||
|
||||
# Clipboard Submenu
|
||||
clipboard_menu = menu.addMenu(
|
||||
QIcon.fromTheme("edit-copy"), UITexts.CONTEXT_MENU_CLIPBOARD)
|
||||
|
||||
# Copy Image
|
||||
action_copy_image = clipboard_menu.addAction(
|
||||
QIcon.fromTheme("image-x-generic"), UITexts.VIEWER_MENU_COPY_IMAGE)
|
||||
action_copy_image.triggered.connect(self._copy_image_to_clipboard)
|
||||
if len(selected_items) > 1:
|
||||
action_copy_image.setEnabled(False)
|
||||
|
||||
# Copy Path
|
||||
action_copy_path = clipboard_menu.addAction(
|
||||
QIcon.fromTheme("document-properties"), UITexts.VIEWER_MENU_COPY_PATH)
|
||||
action_copy_path.triggered.connect(self._copy_path_to_clipboard)
|
||||
|
||||
# Copy Directory Path
|
||||
action_copy_dir = clipboard_menu.addAction(
|
||||
QIcon.fromTheme("folder"), UITexts.CONTEXT_MENU_COPY_DIR)
|
||||
action_copy_dir.triggered.connect(self._copy_dir_to_clipboard)
|
||||
|
||||
menu.exec(event.globalPos())
|
||||
|
||||
def _copy_image_to_clipboard(self):
|
||||
"""Copies the currently selected image to the system clipboard."""
|
||||
paths = self._get_selected_paths()
|
||||
if len(paths) == 1 and paths[0] and os.path.exists(paths[0]):
|
||||
img = QImage(paths[0])
|
||||
if not img.isNull():
|
||||
QApplication.clipboard().setImage(img)
|
||||
|
||||
def _copy_path_to_clipboard(self):
|
||||
"""Copies the file path(s) of the selected image(s) to the clipboard."""
|
||||
paths = self._get_selected_paths()
|
||||
if paths:
|
||||
QApplication.clipboard().setText("\n".join(paths))
|
||||
|
||||
def _copy_dir_to_clipboard(self):
|
||||
"""Copies the directory path(s) of the selected image(s) to the clipboard."""
|
||||
paths = self._get_selected_paths()
|
||||
if paths:
|
||||
dir_paths = sorted(list(set(os.path.dirname(p) for p in paths)))
|
||||
QApplication.clipboard().setText("\n".join(dir_paths))
|
||||
|
||||
|
||||
class FaceCanvas(QLabel):
|
||||
"""
|
||||
@@ -1222,8 +1285,8 @@ class ImagePane(QWidget):
|
||||
self.scroll_area = QScrollArea()
|
||||
self.scroll_area.setAlignment(Qt.AlignCenter)
|
||||
self.scroll_area.setStyleSheet("background-color: #000; border: none;")
|
||||
# self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
# self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.scroll_area.setWidget(self.canvas)
|
||||
layout.addWidget(self.scroll_area)
|
||||
|
||||
@@ -1413,7 +1476,7 @@ class ImageViewer(QWidget):
|
||||
# self.canvas = FaceCanvas(self) ... Moved to ImagePane
|
||||
# self.scroll_area.setWidget(self.canvas)
|
||||
|
||||
self.filmstrip = FilmStripWidget(self.controller)
|
||||
self.filmstrip = FilmStripWidget(self)
|
||||
self.filmstrip.setSpacing(2)
|
||||
self.filmstrip.itemClicked.connect(self.on_filmstrip_clicked)
|
||||
|
||||
@@ -1564,7 +1627,6 @@ class ImageViewer(QWidget):
|
||||
pane.scrolled.connect(self._sync_scroll)
|
||||
pane.zoom_manager.zoomed.connect(self._sync_zoom)
|
||||
|
||||
self.filmstrip.controller = pane.controller
|
||||
self.populate_filmstrip()
|
||||
self.sync_filmstrip_selection(pane.controller.index)
|
||||
self.update_status_bar()
|
||||
@@ -1618,6 +1680,10 @@ class ImageViewer(QWidget):
|
||||
if count == current_panes:
|
||||
return
|
||||
|
||||
if count > 1 and self.slideshow_manager.is_running():
|
||||
self.slideshow_manager.stop()
|
||||
self.update_title()
|
||||
|
||||
if count > current_panes:
|
||||
# Add panes
|
||||
base_controller = self.active_pane.controller
|
||||
@@ -1726,6 +1792,9 @@ class ImageViewer(QWidget):
|
||||
"properties": self.show_properties,
|
||||
"toggle_visibility": self.toggle_main_window_visibility,
|
||||
"toggle_crop": self.toggle_crop_mode,
|
||||
"copy_image": self.copy_image_to_clipboard,
|
||||
"copy_path": self.copy_file_path_to_clipboard,
|
||||
"copy_dir_path": self.copy_dir_path_to_clipboard,
|
||||
"save_crop": self.save_cropped_image,
|
||||
"compare_1": lambda: self.set_comparison_mode(1),
|
||||
"compare_2": lambda: self.set_comparison_mode(2),
|
||||
@@ -1735,6 +1804,11 @@ class ImageViewer(QWidget):
|
||||
|
||||
def _execute_action(self, action):
|
||||
"""Executes the method corresponding to the action name."""
|
||||
if self.slideshow_manager.is_running():
|
||||
allowed_actions = ('slideshow', 'slideshow_reverse', 'close', 'fullscreen')
|
||||
if action not in allowed_actions:
|
||||
return
|
||||
|
||||
if action in self._actions:
|
||||
self._actions[action]()
|
||||
|
||||
@@ -2383,6 +2457,81 @@ class ImageViewer(QWidget):
|
||||
if self.main_win:
|
||||
self.main_win.update_metadata_for_path(path, metadata)
|
||||
|
||||
def copy_image_to_clipboard(self):
|
||||
"""Copies the currently displayed image to the system clipboard."""
|
||||
if self.controller and not self.controller.pixmap_original.isNull():
|
||||
QApplication.clipboard().setImage(self.controller.pixmap_original.toImage())
|
||||
|
||||
def copy_file_path_to_clipboard(self):
|
||||
"""Copies the current image's file path to the system clipboard."""
|
||||
if self.controller:
|
||||
path = self.controller.get_current_path()
|
||||
if path:
|
||||
QApplication.clipboard().setText(path)
|
||||
|
||||
def copy_dir_path_to_clipboard(self):
|
||||
"""Copies the directory path of the current image to the system clipboard."""
|
||||
if self.controller:
|
||||
path = self.controller.get_current_path()
|
||||
if path:
|
||||
QApplication.clipboard().setText(os.path.dirname(path))
|
||||
|
||||
def _populate_open_with_menu(self, menu):
|
||||
"""Populates the 'Open With' submenu."""
|
||||
if self.main_win:
|
||||
path = self.controller.get_current_path()
|
||||
if path:
|
||||
self.main_win.populate_open_with_submenu(menu, path)
|
||||
|
||||
def _build_menu_from_data(self, target_menu, items):
|
||||
"""Builds a menu or submenu from a list of dictionary items."""
|
||||
for item in items:
|
||||
if item == "separator":
|
||||
target_menu.addSeparator()
|
||||
continue
|
||||
|
||||
# Handle dynamic submenus that need to be populated by a function
|
||||
if "dynamic_submenu" in item:
|
||||
icon = QIcon.fromTheme(item.get("icon", ""))
|
||||
submenu = target_menu.addMenu(icon, item["text"])
|
||||
item["dynamic_submenu"](submenu)
|
||||
continue
|
||||
|
||||
action_name = item.get("action")
|
||||
display_text = item["text"]
|
||||
|
||||
# Add shortcut string to display text if available
|
||||
if action_name and "submenu" not in item and \
|
||||
action_name in self.action_to_shortcut:
|
||||
key, mods = self.action_to_shortcut[action_name]
|
||||
try:
|
||||
mod_val = int(mods)
|
||||
except TypeError:
|
||||
mod_val = mods.value
|
||||
seq = QKeySequence(mod_val | key)
|
||||
shortcut_str = seq.toString(QKeySequence.NativeText)
|
||||
if shortcut_str:
|
||||
display_text += f"\t{shortcut_str}"
|
||||
|
||||
icon = QIcon.fromTheme(item.get("icon", ""))
|
||||
|
||||
if "submenu" in item:
|
||||
submenu = target_menu.addMenu(icon, item["text"])
|
||||
self._build_menu_from_data(submenu, item["submenu"])
|
||||
else:
|
||||
action = target_menu.addAction(icon, display_text)
|
||||
slot = item.get("slot")
|
||||
if action_name:
|
||||
action.triggered.connect(
|
||||
lambda checked=False, name=action_name:
|
||||
self._execute_action(name))
|
||||
elif slot:
|
||||
action.triggered.connect(slot)
|
||||
|
||||
if item.get("checkable"):
|
||||
action.setCheckable(True)
|
||||
action.setChecked(item.get("checked", False))
|
||||
|
||||
def restore_scroll_for_pane(self, pane, config):
|
||||
"""
|
||||
Applies the saved scrollbar positions from a layout configuration.
|
||||
@@ -2451,11 +2600,15 @@ class ImageViewer(QWidget):
|
||||
|
||||
def toggle_slideshow(self):
|
||||
"""Starts or stops the automatic slideshow timer."""
|
||||
if len(self.panes) > 1:
|
||||
return
|
||||
self.slideshow_manager.toggle(reverse=False)
|
||||
self.update_view(resize_win=False)
|
||||
|
||||
def toggle_slideshow_reverse(self):
|
||||
"""Starts or stops the automatic reverse slideshow timer."""
|
||||
if len(self.panes) > 1:
|
||||
return
|
||||
self.slideshow_manager.toggle(reverse=True)
|
||||
self.update_view(resize_win=False)
|
||||
|
||||
@@ -2604,168 +2757,148 @@ class ImageViewer(QWidget):
|
||||
"""Builds and returns the general viewer context menu."""
|
||||
menu = QMenu(self)
|
||||
|
||||
# Add "Open With" submenu
|
||||
if self.main_win:
|
||||
path = self.controller.get_current_path()
|
||||
if path:
|
||||
open_submenu = menu.addMenu(QIcon.fromTheme("document-open"),
|
||||
UITexts.CONTEXT_MENU_OPEN)
|
||||
self.main_win.populate_open_with_submenu(open_submenu, path)
|
||||
menu.addSeparator()
|
||||
menu_structure = []
|
||||
if self.slideshow_manager.is_running():
|
||||
is_fwd = self.slideshow_manager.is_forward()
|
||||
is_rev = self.slideshow_manager.is_reverse()
|
||||
menu_structure = [
|
||||
{"text": UITexts.VIEWER_MENU_STOP_SLIDESHOW if is_fwd
|
||||
else UITexts.VIEWER_MENU_START_SLIDESHOW, "action": "slideshow",
|
||||
"icon": "media-playback-stop" if is_fwd else "media-playback-start"},
|
||||
{"text": UITexts.VIEWER_MENU_STOP_REVERSE_SLIDESHOW if is_rev
|
||||
else UITexts.VIEWER_MENU_START_REVERSE_SLIDESHOW,
|
||||
"action": "slideshow_reverse",
|
||||
"icon": "media-playback-stop" if is_rev else "media-seek-backward"},
|
||||
{"text": UITexts.VIEWER_MENU_SET_INTERVAL,
|
||||
"slot": self.set_slideshow_interval, "icon": "preferences-system-time"}
|
||||
]
|
||||
|
||||
menu_items = [
|
||||
{"text": UITexts.VIEWER_MENU_TAGS, "action": "fast_tag",
|
||||
"icon": "document-properties"},
|
||||
"separator",
|
||||
{"text": UITexts.DETECT_FACES, "action": "detect_faces",
|
||||
"icon": "edit-image-face-recognize"},
|
||||
"separator",
|
||||
{"text": UITexts.DETECT_PETS, "action": "detect_pets",
|
||||
"icon": "edit-image-face-recognize"},
|
||||
"separator",
|
||||
{"text": UITexts.DETECT_BODIES, "action": "detect_bodies",
|
||||
"icon": "edit-image-face-recognize"},
|
||||
"separator",
|
||||
{"text": UITexts.VIEWER_MENU_ROTATE, "icon": "transform-rotate",
|
||||
"submenu": [
|
||||
{"text": UITexts.VIEWER_MENU_ROTATE_LEFT,
|
||||
"action": "rotate_left", "icon": "object-rotate-left"},
|
||||
{"text": UITexts.VIEWER_MENU_ROTATE_RIGHT,
|
||||
"action": "rotate_right", "icon": "object-rotate-right"}
|
||||
]},
|
||||
"separator",
|
||||
{"text": UITexts.VIEWER_MENU_FLIP, "icon": "transform-flip", "submenu": [
|
||||
{"text": UITexts.VIEWER_MENU_FLIP_H,
|
||||
"action": "flip_horizontal",
|
||||
"icon": "object-flip-horizontal"},
|
||||
{"text": UITexts.VIEWER_MENU_FLIP_V,
|
||||
"action": "flip_vertical", "icon": "object-flip-vertical"}
|
||||
]},
|
||||
"separator",
|
||||
{"text": UITexts.VIEWER_MENU_RENAME, "action": "rename",
|
||||
"icon": "edit-rename"},
|
||||
"separator",
|
||||
{"text": UITexts.VIEWER_MENU_FIT_SCREEN,
|
||||
"slot": self.zoom_manager.toggle_fit_to_screen,
|
||||
"icon": "zoom-fit-best"},
|
||||
"separator",
|
||||
{"text": UITexts.VIEWER_MENU_CROP, "action": "toggle_crop",
|
||||
"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"},
|
||||
else:
|
||||
# Build the normal menu structure
|
||||
is_fwd = self.slideshow_manager.is_forward()
|
||||
is_rev = self.slideshow_manager.is_reverse()
|
||||
|
||||
slideshow_submenu = [
|
||||
{"text": UITexts.VIEWER_MENU_STOP_SLIDESHOW if is_fwd
|
||||
else UITexts.VIEWER_MENU_START_SLIDESHOW, "action": "slideshow",
|
||||
"icon": "media-playback-stop" if is_fwd else "media-playback-start"},
|
||||
{"text": UITexts.VIEWER_MENU_STOP_REVERSE_SLIDESHOW if is_rev
|
||||
else UITexts.VIEWER_MENU_START_REVERSE_SLIDESHOW,
|
||||
"action": "slideshow_reverse",
|
||||
"icon": "media-playback-stop" if is_rev else "media-seek-backward"},
|
||||
{"text": UITexts.VIEWER_MENU_SET_INTERVAL,
|
||||
"slot": self.set_slideshow_interval, "icon": "preferences-system-time"}
|
||||
]
|
||||
|
||||
menu_structure = [
|
||||
{"text": UITexts.CONTEXT_MENU_OPEN, "icon": "document-open",
|
||||
"dynamic_submenu": self._populate_open_with_menu},
|
||||
"separator",
|
||||
{"text": UITexts.VIEWER_MENU_LINK_PANES,
|
||||
"action": "link_panes", "icon": "object-link",
|
||||
"checkable": True, "checked": self.panes_linked}
|
||||
]},
|
||||
"separator",
|
||||
]
|
||||
{"text": UITexts.VIEWER_MENU_TAGS,
|
||||
"action": "fast_tag", "icon": "document-properties"},
|
||||
{"text": UITexts.VIEWER_MENU_DETECT_AREAS,
|
||||
"icon": "edit-image-face-recognize", "submenu": [
|
||||
{"text": UITexts.VIEWER_MENU_DETECT_FACES,
|
||||
"action": "detect_faces"},
|
||||
{"text": UITexts.VIEWER_MENU_DETECT_PETS, "action": "detect_pets"},
|
||||
]},
|
||||
"separator",
|
||||
{"text": UITexts.VIEWER_MENU_MANIPULATE,
|
||||
"icon": "transform", "submenu": [
|
||||
{"text": UITexts.VIEWER_MENU_ROTATE,
|
||||
"icon": "transform-rotate", "submenu": [
|
||||
{"text": UITexts.VIEWER_MENU_ROTATE_LEFT,
|
||||
"action": "rotate_left", "icon": "object-rotate-left"},
|
||||
{"text": UITexts.VIEWER_MENU_ROTATE_RIGHT,
|
||||
"action": "rotate_right", "icon": "object-rotate-right"}
|
||||
]},
|
||||
{"text": UITexts.VIEWER_MENU_FLIP,
|
||||
"icon": "transform-flip", "submenu": [
|
||||
{"text": UITexts.VIEWER_MENU_FLIP_H,
|
||||
"action": "flip_horizontal", "icon": "object-flip-horizontal"},
|
||||
{"text": UITexts.VIEWER_MENU_FLIP_V,
|
||||
"action": "flip_vertical", "icon": "object-flip-vertical"}
|
||||
]}
|
||||
]},
|
||||
{"text": UITexts.VIEWER_MENU_ZOOM, "icon": "zoom", "submenu": [
|
||||
{"text": UITexts.VIEWER_MENU_ZOOM_IN,
|
||||
"action": "zoom_in", "icon": "zoom-in"},
|
||||
{"text": UITexts.VIEWER_MENU_ZOOM_OUT,
|
||||
"action": "zoom_out", "icon": "zoom-out"},
|
||||
"separator",
|
||||
{"text": UITexts.VIEWER_MENU_FIT_SCREEN,
|
||||
"slot": self.zoom_manager.toggle_fit_to_screen,
|
||||
"icon": "zoom-fit-best"},
|
||||
]},
|
||||
"separator",
|
||||
{"text": UITexts.VIEWER_MENU_RENAME,
|
||||
"action": "rename", "icon": "edit-rename"},
|
||||
{"text": UITexts.CONTEXT_MENU_CLIPBOARD,
|
||||
"icon": "edit-copy", "submenu": [
|
||||
{"text": UITexts.VIEWER_MENU_COPY_IMAGE,
|
||||
"action": "copy_image", "icon": "image-x-generic"},
|
||||
{"text": UITexts.VIEWER_MENU_COPY_PATH,
|
||||
"action": "copy_path", "icon": "document-properties"},
|
||||
{"text": UITexts.CONTEXT_MENU_COPY_DIR,
|
||||
"action": "copy_dir_path", "icon": "folder"},
|
||||
]},
|
||||
{"text": UITexts.VIEWER_MENU_CROP,
|
||||
"action": "toggle_crop", "icon": "transform-crop", "checkable": True,
|
||||
"checked": self.crop_mode},
|
||||
"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"},
|
||||
"separator",
|
||||
{"text": UITexts.VIEWER_MENU_LINK_PANES,
|
||||
"action": "link_panes", "icon": "object-link",
|
||||
"checkable": True, "checked": self.panes_linked}
|
||||
]},
|
||||
"separator",
|
||||
]
|
||||
|
||||
if self.movie:
|
||||
is_paused = self.movie.state() == QMovie.Paused_
|
||||
pause_text = (UITexts.VIEWER_MENU_RESUME_ANIMATION if is_paused
|
||||
else UITexts.VIEWER_MENU_PAUSE_ANIMATION)
|
||||
pause_icon = ("media-playback-start" if is_paused
|
||||
else "media-playback-pause")
|
||||
menu_items.append({"text": pause_text, "action": "toggle_animation",
|
||||
"icon": pause_icon})
|
||||
if self.movie:
|
||||
is_paused = self.movie.state() == QMovie.Paused
|
||||
pause_text = UITexts.VIEWER_MENU_RESUME_ANIMATION \
|
||||
if is_paused else UITexts.VIEWER_MENU_PAUSE_ANIMATION
|
||||
pause_icon = "media-playback-start" \
|
||||
if is_paused else "media-playback-pause"
|
||||
menu_structure.append(
|
||||
{"text": pause_text, "action": "toggle_animation",
|
||||
"icon": pause_icon})
|
||||
|
||||
is_fwd_slideshow = self.slideshow_manager.is_forward()
|
||||
is_rev_slideshow = self.slideshow_manager.is_reverse()
|
||||
menu_structure.extend([
|
||||
{"text": UITexts.VIEWER_MENU_SLIDESHOW,
|
||||
"icon": "view-presentation", "submenu": slideshow_submenu},
|
||||
"separator",
|
||||
{"text": UITexts.SHOW_FACES,
|
||||
"action": "toggle_faces", "icon": "edit-image-face-show",
|
||||
"checkable": True,
|
||||
"checked": self.controller.show_faces if self.controller else False},
|
||||
{"text": UITexts.VIEWER_MENU_SHOW_FILMSTRIP,
|
||||
"action": "toggle_filmstrip", "icon": "view-filmstrip",
|
||||
"checkable": True, "checked": self.filmstrip.isVisible()},
|
||||
{"text": UITexts.VIEWER_MENU_SHOW_STATUSBAR,
|
||||
"action": "toggle_statusbar", "icon": "view-bottom-panel",
|
||||
"checkable": True, "checked": self.status_bar_container.isVisible()},
|
||||
"separator",
|
||||
{"text": UITexts.VIEWER_MENU_EXIT_FULLSCREEN
|
||||
if self.isFullScreen() else UITexts.VIEWER_MENU_ENTER_FULLSCREEN,
|
||||
"action": "fullscreen", "icon": "view-fullscreen"
|
||||
if not self.isFullScreen() else "view-restore"},
|
||||
"separator",
|
||||
{"text": "Show/hide main window",
|
||||
"action": "toggle_visibility", "icon": "view-restore"},
|
||||
"separator",
|
||||
{"text": UITexts.CONTEXT_MENU_PROPERTIES,
|
||||
"action": "properties", "icon": "document-properties"}
|
||||
])
|
||||
|
||||
slideshow_submenu = [
|
||||
{"text": UITexts.VIEWER_MENU_STOP_SLIDESHOW if is_fwd_slideshow
|
||||
else UITexts.VIEWER_MENU_START_SLIDESHOW, "action": "slideshow",
|
||||
"icon": "media-playback-stop" if is_fwd_slideshow
|
||||
else "media-playback-start"},
|
||||
{"text": UITexts.VIEWER_MENU_STOP_REVERSE_SLIDESHOW if is_rev_slideshow
|
||||
else UITexts.VIEWER_MENU_START_REVERSE_SLIDESHOW,
|
||||
"action": "slideshow_reverse",
|
||||
"icon": "media-playback-stop" if is_rev_slideshow
|
||||
else "media-seek-backward"},
|
||||
{"text": UITexts.VIEWER_MENU_SET_INTERVAL,
|
||||
"slot": self.set_slideshow_interval, "icon": "preferences-system-time"}
|
||||
]
|
||||
|
||||
menu_items.extend([
|
||||
{"text": UITexts.VIEWER_MENU_SLIDESHOW, "icon": "view-presentation",
|
||||
"submenu": slideshow_submenu},
|
||||
"separator",
|
||||
{"text": UITexts.SHOW_FACES, "action": "toggle_faces",
|
||||
"icon": "edit-image-face-show",
|
||||
"checkable": True, "checked": self.controller.show_faces},
|
||||
{"text": UITexts.VIEWER_MENU_SHOW_FILMSTRIP,
|
||||
"action": "toggle_filmstrip", "icon": "view-filmstrip", "checkable": True,
|
||||
"checked": self.filmstrip.isVisible()},
|
||||
{"text": UITexts.VIEWER_MENU_SHOW_STATUSBAR,
|
||||
"action": "toggle_statusbar", "icon": "view-bottom-panel",
|
||||
"checkable": True,
|
||||
"checked": self.status_bar_container.isVisible()},
|
||||
"separator",
|
||||
{"text": UITexts.VIEWER_MENU_EXIT_FULLSCREEN
|
||||
if self.isFullScreen() else UITexts.VIEWER_MENU_ENTER_FULLSCREEN,
|
||||
"action": "fullscreen",
|
||||
"icon": "view-fullscreen" if not self.isFullScreen() else "view-restore"},
|
||||
"separator",
|
||||
{"text": "Show/hide main window",
|
||||
"action": "toggle_visibility",
|
||||
"icon": "view-restore"},
|
||||
"separator",
|
||||
{"text": UITexts.CONTEXT_MENU_PROPERTIES, "action": "properties",
|
||||
"icon": "document-properties"}
|
||||
])
|
||||
|
||||
def build_actions(target_menu, items):
|
||||
for item in items:
|
||||
if item == "separator":
|
||||
target_menu.addSeparator()
|
||||
continue
|
||||
|
||||
action_name = item.get("action")
|
||||
display_text = item["text"]
|
||||
|
||||
# Only add shortcut to final actions, not to submenus
|
||||
if action_name and "submenu" not in item and \
|
||||
action_name in self.action_to_shortcut:
|
||||
key, mods = self.action_to_shortcut[action_name]
|
||||
# Handle both Qt.KeyboardModifier (enum) and Qt.KeyboardModifiers
|
||||
# (flags) by ensuring we have an integer value for the modifier
|
||||
# before the bitwise OR.
|
||||
try:
|
||||
mod_val = int(mods)
|
||||
except TypeError:
|
||||
mod_val = mods.value
|
||||
seq = QKeySequence(mod_val | key)
|
||||
shortcut_str = seq.toString(QKeySequence.NativeText)
|
||||
if shortcut_str:
|
||||
display_text += f"\t{shortcut_str}"
|
||||
|
||||
icon = QIcon.fromTheme(item.get("icon", ""))
|
||||
|
||||
if "submenu" in item:
|
||||
submenu = target_menu.addMenu(icon, item["text"])
|
||||
build_actions(submenu, item["submenu"])
|
||||
else:
|
||||
action = target_menu.addAction(icon, display_text)
|
||||
slot = item.get("slot")
|
||||
if action_name:
|
||||
action.triggered.connect(
|
||||
lambda checked=False, name=action_name:
|
||||
self._execute_action(name))
|
||||
elif slot:
|
||||
action.triggered.connect(slot)
|
||||
|
||||
if item.get("checkable"):
|
||||
action.setCheckable(True)
|
||||
action.setChecked(item.get("checked", False))
|
||||
|
||||
build_actions(menu, menu_items)
|
||||
self._build_menu_from_data(menu, menu_structure)
|
||||
|
||||
return menu
|
||||
|
||||
|
||||
Reference in New Issue
Block a user