mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-02-02 16:19:10 +00:00
feat: dynamically load macros into menu
This commit is contained in:
@@ -106,6 +106,37 @@ class AddTagInstruction(Instruction):
|
||||
return str(self.tag_strings)
|
||||
|
||||
|
||||
def get_macro_name(
|
||||
macro_path: Path,
|
||||
) -> str:
|
||||
"""Return the name of a macro, as read from the file.
|
||||
|
||||
Defaults to the filename if no name is declared or able to be read.
|
||||
|
||||
Args:
|
||||
macro_path (Path): The full path of the macro file.
|
||||
"""
|
||||
name = macro_path.name
|
||||
logger.info("[MacroParser] Parsing Macro for Name", macro_path=macro_path)
|
||||
|
||||
if not macro_path.exists():
|
||||
logger.error("[MacroParser] Macro path does not exist", macro_path=macro_path)
|
||||
return name
|
||||
|
||||
if not macro_path.exists():
|
||||
logger.error("[MacroParser] Filepath does not exist", macro_path=macro_path)
|
||||
return name
|
||||
|
||||
with open(macro_path) as f:
|
||||
try:
|
||||
macro = toml.load(f)
|
||||
name = str(macro.get("name", name))
|
||||
except toml.TomlDecodeError as e:
|
||||
logger.error("[MacroParser] Could not parse macro", macro_path=macro_path, error=e)
|
||||
logger.info("[MacroParser] Macro Name:", name=name, macro_path=macro_path)
|
||||
return name
|
||||
|
||||
|
||||
def parse_macro_file(
|
||||
macro_path: Path,
|
||||
filepath: Path,
|
||||
@@ -123,7 +154,7 @@ def parse_macro_file(
|
||||
logger.error("[MacroParser] Macro path does not exist", macro_path=macro_path)
|
||||
return results
|
||||
|
||||
if not macro_path.exists():
|
||||
if not filepath.exists():
|
||||
logger.error("[MacroParser] Filepath does not exist", filepath=filepath)
|
||||
return results
|
||||
|
||||
@@ -131,11 +162,7 @@ def parse_macro_file(
|
||||
try:
|
||||
macro = toml.load(f)
|
||||
except toml.TomlDecodeError as e:
|
||||
logger.error(
|
||||
"[MacroParser] Could not parse macro",
|
||||
path=macro_path,
|
||||
error=e,
|
||||
)
|
||||
logger.error("[MacroParser] Could not parse macro", macro_path=macro_path, error=e)
|
||||
return results
|
||||
|
||||
logger.info(macro)
|
||||
@@ -156,15 +183,15 @@ def parse_macro_file(
|
||||
logger.info(f"[MacroParser] Schema Version: {schema_ver}")
|
||||
|
||||
# Load Triggers
|
||||
triggers = macro[TRIGGERS]
|
||||
if not isinstance(triggers, list):
|
||||
triggers = macro.get(TRIGGERS)
|
||||
if triggers and not isinstance(triggers, list):
|
||||
logger.error(
|
||||
f"[MacroParser] Incorrect type for {TRIGGERS}, expected list", triggers=triggers
|
||||
)
|
||||
|
||||
# Parse each action table
|
||||
for table_key in macro:
|
||||
if table_key in {SCHEMA_VERSION, TRIGGERS}:
|
||||
if table_key in {SCHEMA_VERSION, TRIGGERS, NAME}:
|
||||
continue
|
||||
|
||||
logger.info("[MacroParser] Parsing Table", table_key=table_key)
|
||||
@@ -175,7 +202,7 @@ def parse_macro_file(
|
||||
source_filters: list[str] = table.get(SOURCE_FILER, [])
|
||||
conditions_met: bool = False
|
||||
if not source_filters:
|
||||
logger.info('[MacroParser] No "{SOURCE_FILER}" provided')
|
||||
conditions_met = True
|
||||
else:
|
||||
for filter_ in source_filters:
|
||||
if glob.globmatch(filepath, filter_, flags=glob.GLOBSTAR):
|
||||
|
||||
@@ -21,12 +21,14 @@ from pathlib import Path
|
||||
from queue import Queue
|
||||
from shutil import which
|
||||
from typing import Generic, TypeVar
|
||||
from unittest.mock import Mock
|
||||
from warnings import catch_warnings
|
||||
|
||||
import structlog
|
||||
from humanfriendly import format_size, format_timespan
|
||||
from PySide6.QtCore import QObject, QSettings, Qt, QThread, QThreadPool, QTimer, Signal
|
||||
from PySide6.QtGui import (
|
||||
QAction,
|
||||
QColor,
|
||||
QDragEnterEvent,
|
||||
QDragMoveEvent,
|
||||
@@ -68,6 +70,7 @@ from tagstudio.core.library.refresh import RefreshTracker
|
||||
from tagstudio.core.macro_parser import (
|
||||
Instruction,
|
||||
exec_instructions,
|
||||
get_macro_name,
|
||||
parse_macro_file,
|
||||
)
|
||||
from tagstudio.core.media_types import MediaCategories
|
||||
@@ -75,8 +78,6 @@ from tagstudio.core.query_lang.util import ParsingError
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.qt.cache_manager import CacheManager
|
||||
from tagstudio.qt.controllers.ffmpeg_missing_message_box import FfmpegMissingMessageBox
|
||||
|
||||
# this import has side-effect of import PySide resources
|
||||
from tagstudio.qt.controllers.fix_ignored_modal_controller import FixIgnoredEntriesModal
|
||||
from tagstudio.qt.controllers.ignore_modal_controller import IgnoreModal
|
||||
from tagstudio.qt.controllers.library_info_window_controller import LibraryInfoWindow
|
||||
@@ -105,6 +106,7 @@ from tagstudio.qt.resource_manager import ResourceManager
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.utils.custom_runnable import CustomRunnable
|
||||
from tagstudio.qt.utils.file_deleter import delete_file
|
||||
from tagstudio.qt.utils.file_opener import open_file
|
||||
from tagstudio.qt.utils.function_iterator import FunctionIterator
|
||||
from tagstudio.qt.views.main_window import MainWindow
|
||||
from tagstudio.qt.views.panel_modal import PanelModal
|
||||
@@ -553,20 +555,8 @@ class QtDriver(DriverMixin, QObject):
|
||||
# endregion
|
||||
|
||||
# region Macros Menu ==========================================================
|
||||
|
||||
self.main_window.menu_bar.test_macro_1_action.triggered.connect(
|
||||
lambda: (
|
||||
self.run_macros(self.main_window.menu_bar.test_macro_1, self.selected),
|
||||
# self.main_window.preview_panel.update_widgets(update_preview=False),
|
||||
)
|
||||
)
|
||||
|
||||
self.main_window.menu_bar.test_macro_2_action.triggered.connect(
|
||||
lambda: (
|
||||
self.run_macros(self.main_window.menu_bar.test_macro_2, self.selected),
|
||||
# self.preview_panel.update_widgets(update_preview=False),
|
||||
)
|
||||
)
|
||||
self.main_window.menu_bar.macros_menu.aboutToShow.connect(self.update_macros_menu)
|
||||
self.update_macros_menu()
|
||||
|
||||
def create_folders_tags_modal():
|
||||
if not hasattr(self, "folders_modal"):
|
||||
@@ -589,8 +579,6 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
# endregion
|
||||
|
||||
# endregion
|
||||
|
||||
self.main_window.search_field.textChanged.connect(self.update_completions_list)
|
||||
|
||||
self.main_window.preview_panel.field_containers_widget.archived_updated.connect(
|
||||
@@ -782,6 +770,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
self.set_clipboard_menu_viability()
|
||||
self.set_select_actions_visibility()
|
||||
self.update_macros_menu(clear=True)
|
||||
|
||||
if hasattr(self, "library_info_window"):
|
||||
self.library_info_window.close()
|
||||
@@ -1114,6 +1103,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
"""Run a specific Macro on a group of given entry_ids."""
|
||||
for entry_id in entry_ids:
|
||||
self.run_macro(macro_name, entry_id)
|
||||
self.main_window.preview_panel.update_preview()
|
||||
|
||||
def run_macro(self, macro_name: str, entry_id: int):
|
||||
"""Run a specific Macro on an Entry given a Macro name."""
|
||||
@@ -1268,8 +1258,11 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
self.main_window.preview_panel.set_selection(self.selected)
|
||||
|
||||
# TODO: Remove?
|
||||
def set_macro_menu_viability(self):
|
||||
self.main_window.menu_bar.test_macro_1_action.setDisabled(not self.selected)
|
||||
# for action in self.macros_menu.actions():
|
||||
# action.setDisabled(not self.selected)
|
||||
pass
|
||||
|
||||
def set_clipboard_menu_viability(self):
|
||||
if len(self.selected) == 1:
|
||||
@@ -1543,6 +1536,53 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.cached_values.sync()
|
||||
self.update_recent_lib_menu()
|
||||
|
||||
def update_macros_menu(self, clear: bool = False):
|
||||
if not self.main_window.menu_bar.macros_menu or isinstance(
|
||||
self.main_window.menu_bar.macros_menu, Mock
|
||||
): # NOTE: Needed for tests?
|
||||
return
|
||||
|
||||
# Create actions for each macro
|
||||
actions: list[QAction] = []
|
||||
if self.lib.library_dir and not clear:
|
||||
macros_path = self.lib.library_dir / TS_FOLDER_NAME / MACROS_FOLDER_NAME
|
||||
for f in macros_path.glob("*"):
|
||||
logger.info(f)
|
||||
if f.suffix != ".toml" or f.is_dir() or f.name.startswith("._"):
|
||||
continue
|
||||
action = QAction(
|
||||
get_macro_name(f), self.main_window.menu_bar.macros_menu.parentWidget()
|
||||
)
|
||||
action.triggered.connect(
|
||||
lambda checked=False, name=f.name: (self.run_macros(name, self.selected)),
|
||||
)
|
||||
actions.append(action)
|
||||
|
||||
open_folder = QAction("Open Macros Folder...", self.main_window.menu_bar.macros_menu)
|
||||
open_folder.triggered.connect(self.open_macros_folder)
|
||||
actions.append(open_folder)
|
||||
|
||||
if clear:
|
||||
open_folder.setEnabled(False)
|
||||
|
||||
# Clear previous actions
|
||||
for action in self.main_window.menu_bar.macros_menu.actions():
|
||||
self.main_window.menu_bar.macros_menu.removeAction(action)
|
||||
|
||||
# Add new actions
|
||||
for action in actions:
|
||||
self.main_window.menu_bar.macros_menu.addAction(action)
|
||||
|
||||
self.main_window.menu_bar.macros_menu.addSeparator()
|
||||
self.main_window.menu_bar.macros_menu.addAction(open_folder)
|
||||
|
||||
def open_macros_folder(self):
|
||||
if not self.lib.library_dir:
|
||||
return
|
||||
path = self.lib.library_dir / TS_FOLDER_NAME / MACROS_FOLDER_NAME
|
||||
path.mkdir(exist_ok=True)
|
||||
open_file(path, file_manager=True, is_dir=True)
|
||||
|
||||
def open_settings_modal(self):
|
||||
SettingsPanel.build_modal(self).show()
|
||||
|
||||
@@ -1625,6 +1665,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
library_dir_display = self.lib.library_dir.name
|
||||
|
||||
self.update_libs_list(path)
|
||||
self.update_macros_menu()
|
||||
self.main_window.setWindowTitle(
|
||||
Translations.format(
|
||||
"app.title",
|
||||
@@ -1651,7 +1692,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.main_window.menu_bar.folders_to_tags_action.setEnabled(True)
|
||||
self.main_window.menu_bar.library_info_action.setEnabled(True)
|
||||
|
||||
self.main_window.preview_panel.set_selection(self.selected)
|
||||
self.main_window.preview_panel.set_selection()
|
||||
|
||||
# page (re)rendering, extract eventually
|
||||
initial_state = BrowsingState(
|
||||
|
||||
@@ -20,13 +20,19 @@ from tagstudio.core.utils.types import unwrap
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
def open_file(path: str | Path, file_manager: bool = False, windows_start_command: bool = False):
|
||||
def open_file(
|
||||
path: str | Path,
|
||||
file_manager: bool = False,
|
||||
is_dir: bool = False,
|
||||
windows_start_command: bool = False,
|
||||
):
|
||||
"""Open a file in the default application or file explorer.
|
||||
|
||||
Args:
|
||||
path (str): The path to the file to open.
|
||||
file_manager (bool, optional): Whether to open the file in the file manager
|
||||
(e.g. Finder on macOS). Defaults to False.
|
||||
is_dir (bool): True if the path points towards a directory, false if a file.
|
||||
windows_start_command (bool): Flag to determine if the older 'start' command should be used
|
||||
on Windows for opening files. This fixes issues on some systems in niche cases.
|
||||
"""
|
||||
@@ -77,7 +83,7 @@ def open_file(path: str | Path, file_manager: bool = False, windows_start_comman
|
||||
if sys.platform == "darwin":
|
||||
command_name = "open"
|
||||
command_args = [str(path)]
|
||||
if file_manager:
|
||||
if file_manager and not is_dir:
|
||||
# will reveal in Finder
|
||||
command_args.append("-R")
|
||||
else:
|
||||
|
||||
@@ -133,6 +133,10 @@ class PreviewPanelView(QWidget):
|
||||
def _set_selection_callback(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def update_preview(self):
|
||||
"""Refresh the panel's widgets to use current library data."""
|
||||
self.set_selection(self._selected)
|
||||
|
||||
def set_selection(self, selected: list[int] | None = None, update_preview: bool = True):
|
||||
"""Render the panel widgets with the newest data from the Library.
|
||||
|
||||
|
||||
@@ -145,6 +145,7 @@ def test_title_update(
|
||||
qt_driver.main_window.menu_bar.fix_dupe_files_action = QAction(menu_bar)
|
||||
qt_driver.main_window.menu_bar.clear_thumb_cache_action = QAction(menu_bar)
|
||||
qt_driver.main_window.menu_bar.folders_to_tags_action = QAction(menu_bar)
|
||||
qt_driver.main_window.menu_bar.macros_menu = None
|
||||
|
||||
# Trigger the update
|
||||
qt_driver._init_library(library_dir, open_status) # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
Reference in New Issue
Block a user