Skip to content

feat: open files from menu + double-click (port and extend #455) #858

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/tagstudio/qt/ts_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import sys
import time
from pathlib import Path
from platform import system
from queue import Queue
from warnings import catch_warnings

Expand Down Expand Up @@ -97,6 +98,7 @@
from tagstudio.qt.widgets.panel import PanelModal
from tagstudio.qt.widgets.preview_panel import PreviewPanel
from tagstudio.qt.widgets.progress import ProgressWidget
from tagstudio.qt.widgets.thumb_button import ThumbButton
from tagstudio.qt.widgets.thumb_renderer import ThumbRenderer

BADGE_TAGS = {
Expand Down Expand Up @@ -406,6 +408,21 @@ def start(self) -> None:
file_menu.addAction(self.refresh_dir_action)
file_menu.addSeparator()

self.open_selected_action = QAction(Translations["file.open_files.title.plural"], self)
self.open_selected_action.triggered.connect(self.open_selected_files)
if system() == "Darwin":
shortcut: QtCore.QKeyCombination | Qt.Key = QtCore.QKeyCombination(
QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier),
QtCore.Qt.Key.Key_Down,
)
else:
shortcut = Qt.Key.Key_Return

self.open_selected_action.setShortcut(shortcut)
self.open_selected_action.setEnabled(False)
file_menu.addAction(self.open_selected_action)
file_menu.addSeparator()

self.close_library_action = QAction(Translations["menu.file.close_library"], menu_bar)
self.close_library_action.triggered.connect(self.close_library)
self.close_library_action.setEnabled(False)
Expand Down Expand Up @@ -1426,6 +1443,40 @@ def toggle_item_selection(self, item_id: int, append: bool, bridge: bool):

self.preview_panel.update_widgets()

def open_selected_files(self):
if not (
QApplication.focusWidget() == self.main_window.scrollArea
or isinstance(QApplication.focusWidget(), ThumbButton)
):
return
count = len(self.selected)
result = QMessageBox.ButtonRole.ActionRole

if count >= 5: # Only confirm if we have lots of files
confirm_open = QMessageBox()
confirm_open.setText(Translations.format("file.open_files.warning", count=count))
confirm_open.setWindowTitle(Translations["file.open_files.title"])
confirm_open.setIcon(QMessageBox.Icon.Question)

cancel_button = confirm_open.addButton(
Translations["generic.cancel_alt"], QMessageBox.ButtonRole.RejectRole
)
confirm_open.setEscapeButton(cancel_button)

open_button = confirm_open.addButton(
Translations["generic.open"], QMessageBox.ButtonRole.ActionRole
)
confirm_open.setDefaultButton(open_button)

result = QMessageBox.ButtonRole(confirm_open.exec())

if result == QMessageBox.ButtonRole.ActionRole:
opened = []
for it in self.item_thumbs:
if it.item_id in self.selected and it.item_id not in opened:
it.opener.open_file()
opened.append(it.item_id)

def set_macro_menu_viability(self):
# self.autofill_action.setDisabled(not self.selected)
pass
Expand Down Expand Up @@ -1453,11 +1504,20 @@ def set_select_actions_visibility(self):
self.add_tag_to_selected_action.setEnabled(True)
self.clear_select_action.setEnabled(True)
self.delete_file_action.setEnabled(True)

self.open_selected_action.setEnabled(True)
if len(self.selected) == 1:
self.open_selected_action.setText(Translations["file.open_files.title.singular"])
else:
self.open_selected_action.setText(Translations["file.open_files.title.plural"])
else:
self.add_tag_to_selected_action.setEnabled(False)
self.clear_select_action.setEnabled(False)
self.delete_file_action.setEnabled(False)

self.open_selected_action.setEnabled(False)
self.open_selected_action.setText(Translations["file.open_files.title.plural"])

def update_completions_list(self, text: str) -> None:
matches = re.search(
r"((?:.* )?)(mediatype|filetype|path|tag|tag_id):(\"?[A-Za-z0-9\ \t]+\"?)?", text
Expand Down
1 change: 1 addition & 0 deletions src/tagstudio/qt/widgets/item_thumb.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ def __init__(
self.thumb_button.addAction(open_file_action)
self.thumb_button.addAction(open_explorer_action)
self.thumb_button.addAction(self.delete_action)
self.thumb_button.double_clicked.connect(self.opener.open_file)

# Static Badges ========================================================

Expand Down
35 changes: 28 additions & 7 deletions src/tagstudio/qt/widgets/thumb_button.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
# Copyright (C) 2025 Travis Abendshien (CyanVoxel).
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio


import sys
from typing import override

from PySide6 import QtCore
from PySide6.QtCore import QEvent
from PySide6.QtCore import QEvent, Signal
from PySide6.QtGui import (
QColor,
QEnterEvent,
QMouseEvent,
QPainter,
QPainterPath,
QPaintEvent,
QPalette,
QPen,
)
from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QPushButton, QWidget

from tagstudio.qt.helpers.qbutton_wrapper import QPushButtonWrapper

class ThumbButton(QPushButton):
double_clicked = Signal()

class ThumbButton(QPushButtonWrapper):
def __init__(self, parent: QWidget, thumb_size: tuple[int, int]) -> None: # noqa: N802
super().__init__(parent)
self.thumb_size: tuple[int, int] = thumb_size
self.hovered = False
self.selected = False
self.double_click = False

# NOTE: As of PySide 6.8.0.1, the QPalette.ColorRole.Accent role no longer works on Windows.
# The QPalette.ColorRole.AlternateBase does for some reason, but not on macOS.
Expand Down Expand Up @@ -85,8 +88,9 @@ def __init__(self, parent: QWidget, thumb_size: tuple[int, int]) -> None: # noq
self.hover_color.alpha(),
)

def paintEvent(self, event: QPaintEvent) -> None: # noqa: N802
super().paintEvent(event)
@override
def paintEvent(self, arg__1: QPaintEvent) -> None: # noqa: N802
super().paintEvent(arg__1)
if self.hovered or self.selected:
painter = QPainter()
painter.begin(self)
Expand Down Expand Up @@ -125,11 +129,13 @@ def paintEvent(self, event: QPaintEvent) -> None: # noqa: N802

painter.end()

@override
def enterEvent(self, event: QEnterEvent) -> None: # noqa: N802
self.hovered = True
self.repaint()
return super().enterEvent(event)

@override
def leaveEvent(self, event: QEvent) -> None: # noqa: N802
self.hovered = False
self.repaint()
Expand All @@ -138,3 +144,18 @@ def leaveEvent(self, event: QEvent) -> None: # noqa: N802
def set_selected(self, value: bool) -> None: # noqa: N802
self.selected = value
self.repaint()

@override
def mousePressEvent(self, e: QMouseEvent) -> None: # noqa: N802
self.double_click = False

@override
def mouseDoubleClickEvent(self, event: QMouseEvent) -> None: # noqa: N802
self.double_click = True

@override
def mouseReleaseEvent(self, e: QMouseEvent) -> None: # noqa: N802
if self.double_click:
self.double_clicked.emit()
else:
self.clicked.emit()
4 changes: 4 additions & 0 deletions src/tagstudio/resources/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@
"file.not_found": "File Not Found",
"file.open_file_with": "Open file with",
"file.open_file": "Open file",
"file.open_files.title.plural": "Open Selected Files",
"file.open_files.title.singular": "Open Selected File",
"file.open_files.warning": "Are you sure you want to open {count} files?",
"file.open_location.generic": "Show file in file explorer",
"file.open_location.mac": "Reveal in Finder",
"file.open_location.windows": "Show in File Explorer",
Expand Down Expand Up @@ -112,6 +115,7 @@
"generic.navigation.back": "Back",
"generic.navigation.next": "Next",
"generic.none": "None",
"generic.open": "Open",
"generic.overwrite_alt": "&Overwrite",
"generic.overwrite": "Overwrite",
"generic.paste": "Paste",
Expand Down