diff --git a/app/musicmuster.py b/app/musicmuster.py index 8f6ddc4..e8969bb 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -10,6 +10,7 @@ import subprocess import sys import urllib.parse import webbrowser +import yaml # PyQt imports from PyQt6.QtCore import ( @@ -38,6 +39,7 @@ from PyQt6.QtWidgets import ( QLineEdit, QListWidgetItem, QMainWindow, + QMenu, QMessageBox, QPushButton, QVBoxLayout, @@ -475,104 +477,77 @@ class Window(QMainWindow): return action def create_menu_bar(self): + """Dynamically creates the menu bar from a YAML file.""" menu_bar = self.menuBar() - # File Menu - file_menu = menu_bar.addMenu("&File") - file_menu.addAction( - self.create_action("Open Playlist", self.open_playlist, "Ctrl+O") - ) - file_menu.addAction(self.create_action("New Playlist", self.new_playlist)) - file_menu.addAction( - self.create_action("Close Playlist", self.close_playlist_tab) - ) - file_menu.addAction(self.create_action("Rename Playlist", self.rename_playlist)) - file_menu.addAction(self.create_action("Delete Playlist", self.delete_playlist)) - file_menu.addSeparator() - file_menu.addAction( - self.create_action("Save as Template", self.save_as_template) - ) - file_menu.addAction( - self.create_action("Manage Templates", self.manage_templates) - ) - file_menu.addSeparator() - file_menu.addAction( - self.create_action( - "Import Files", self.import_files_wrapper, "Ctrl+Shift+I" - ) - ) - file_menu.addSeparator() - file_menu.addAction(self.create_action("Exit", self.close)) + # Load menu structure from YAML file + with open("menu.yaml", "r") as file: + menu_data = yaml.safe_load(file) - # Playlist Menu - playlist_menu = menu_bar.addMenu("&Playlist") - playlist_menu.addSeparator() - playlist_menu.addAction( - self.create_action("Insert Track", self.insert_track, "Ctrl+T") - ) - playlist_menu.addAction( - self.create_action("Insert Section Header", self.insert_header, "Ctrl+H") - ) - playlist_menu.addSeparator() - playlist_menu.addAction( - self.create_action("Mark for Moving", self.mark_rows_for_moving, "Ctrl+C") - ) - playlist_menu.addAction(self.create_action("Paste", self.paste_rows, "Ctrl+V")) - playlist_menu.addSeparator() - playlist_menu.addAction( - self.create_action("Export Playlist", self.export_playlist_tab) - ) - playlist_menu.addAction( - self.create_action( - "Download CSV of Played Tracks", self.download_played_tracks - ) - ) - playlist_menu.addSeparator() - playlist_menu.addAction( - self.create_action( - "Select Duplicate Rows", - lambda: self.active_tab().select_duplicate_rows(), - ) - ) - playlist_menu.addAction(self.create_action("Move Selected", self.move_selected)) - playlist_menu.addAction(self.create_action("Move Unplayed", self.move_unplayed)) + # Store references for enabling/disabling actions later + self.menu_actions = {} - # Clear Selection with Escape key. Save in module so we can - # enable/disable it later - self.action_Clear_selection = self.create_action( - "Clear Selection", self.clear_selection, "Esc" - ) - playlist_menu.addAction(self.action_Clear_selection) + for menu_item in menu_data["menus"]: + menu = menu_bar.addMenu(menu_item["title"]) - # Music Menu - music_menu = menu_bar.addMenu("&Music") - music_menu.addAction( - self.create_action("Set Next", self.set_selected_track_next, "Ctrl+N") - ) - music_menu.addAction(self.create_action("Play Next", self.play_next, "Return")) - music_menu.addAction(self.create_action("Fade", self.fade, "Ctrl+Z")) - music_menu.addAction(self.create_action("Stop", self.stop, "Ctrl+Alt+S")) - music_menu.addAction(self.create_action("Resume", self.resume, "Ctrl+R")) - music_menu.addAction( - self.create_action("Skip to Next", self.play_next, "Ctrl+Alt+Return") - ) - music_menu.addSeparator() - music_menu.addAction(self.create_action("Search", self.search_playlist, "/")) - music_menu.addAction( - self.create_action( - "Search Title in Wikipedia", self.lookup_row_in_wikipedia, "Ctrl+W" - ) - ) - music_menu.addAction( - self.create_action( - "Search Title in Songfacts", self.lookup_row_in_songfacts, "Ctrl+S" - ) - ) + for action_item in menu_item["actions"]: + if "separator" in action_item and action_item["separator"]: + menu.addSeparator() + continue - # Help Menu - help_menu = menu_bar.addMenu("Help") - help_menu.addAction(self.create_action("About", self.about)) - help_menu.addAction(self.create_action("Debug", self.debug)) + # Handle external function calls via lambda + if action_item.get("use_lambda"): + handler = lambda: self.active_tab().select_duplicate_rows() # ✅ Keeps lambda logic + else: + handler = getattr(self, action_item["handler"], None) + + if handler is None: + print(f"Warning: No handler found for {action_item['text']}") + continue # Skip adding the action if the handler is missing + + action = self.create_action( + action_item["text"], handler, action_item.get("shortcut") + ) + + if action_item.get("store_reference"): + self.menu_actions[action_item["handler"]] = action # Store reference + + + menu.addAction(action) + + # Handle dynamic submenu + if action_item.get("submenu"): + submenu = QMenu(action_item["text"], self) + action.setMenu(submenu) + menu.addMenu(submenu) + + # Store submenu reference for dynamic population + self.dynamic_submenus = getattr(self, "dynamic_submenus", {}) + self.dynamic_submenus[action_item["handler"]] = submenu + + # Connect submenu aboutToShow signal to population function + submenu.aboutToShow.connect(self.populate_dynamic_submenu) + + def populate_dynamic_submenu(self): + """Dynamically populates the submenu when selected.""" + submenu = self.dynamic_submenus["populate_dynamic_submenu"] + submenu.clear() # Remove previous items + + # Example: Populate submenu with dynamically generated items + items = self.get_dynamic_menu_items() # Assume this method provides a list of items + + for item in items: + action = QAction(item["text"], self) + action.triggered.connect(lambda _, i=item["handler"]: getattr(self, i)()) + submenu.addAction(action) + + def get_dynamic_menu_items(self): + """Returns dynamically generated menu items (replace with real logic).""" + return [ + {"text": "Dynamic Option 1", "handler": "dynamic_option_1"}, + {"text": "Dynamic Option 2", "handler": "dynamic_option_2"}, + {"text": "Dynamic Option 3", "handler": "dynamic_option_3"}, + ] def about(self) -> None: """Get git tag and database name""" diff --git a/menu.yaml b/menu.yaml new file mode 100644 index 0000000..20e73c4 --- /dev/null +++ b/menu.yaml @@ -0,0 +1,104 @@ +menus: + - title: "&File" + actions: + - text: "Open Playlist" + handler: "open_playlist" + shortcut: "Ctrl+O" + - text: "New Playlist" + handler: "new_playlist" + - text: "Close Playlist" + handler: "close_playlist_tab" + - text: "Rename Playlist" + handler: "rename_playlist" + - text: "Delete Playlist" + handler: "delete_playlist" + - separator: true + - text: "Save as Template" + handler: "save_as_template" + - text: "Manage Templates" + handler: "manage_templates" + - separator: true + - text: "Import Files" + handler: "import_files_wrapper" + shortcut: "Ctrl+Shift+I" + - separator: true + - text: "Dynamic Submenu" + handler: "populate_dynamic_submenu" + submenu: true + - separator: true + - text: "Exit" + handler: "close" + + - title: "&Playlist" + actions: + - separator: true + - text: "Insert Track" + handler: "insert_track" + shortcut: "Ctrl+T" + - text: "Insert Section Header" + handler: "insert_header" + shortcut: "Ctrl+H" + - separator: true + - text: "Mark for Moving" + handler: "mark_rows_for_moving" + shortcut: "Ctrl+C" + - text: "Paste" + handler: "paste_rows" + shortcut: "Ctrl+V" + - separator: true + - text: "Export Playlist" + handler: "export_playlist_tab" + - text: "Download CSV of Played Tracks" + handler: "download_played_tracks" + - separator: true + - text: "Select Duplicate Rows" + handler: "select_duplicate_rows" + use_lambda: true + - text: "Move Selected" + handler: "move_selected" + - text: "Move Unplayed" + handler: "move_unplayed" + - separator: true + - text: "Clear Selection" + handler: "clear_selection" + shortcut: "Esc" + store_reference: true # So we can enable/disable later + + - title: "&Music" + actions: + - text: "Set Next" + handler: "set_selected_track_next" + shortcut: "Ctrl+N" + - text: "Play Next" + handler: "play_next" + shortcut: "Return" + - text: "Fade" + handler: "fade" + shortcut: "Ctrl+Z" + - text: "Stop" + handler: "stop" + shortcut: "Ctrl+Alt+S" + - text: "Resume" + handler: "resume" + shortcut: "Ctrl+R" + - text: "Skip to Next" + handler: "play_next" + shortcut: "Ctrl+Alt+Return" + - separator: true + - text: "Search" + handler: "search_playlist" + shortcut: "/" + - text: "Search Title in Wikipedia" + handler: "lookup_row_in_wikipedia" + shortcut: "Ctrl+W" + - text: "Search Title in Songfacts" + handler: "lookup_row_in_songfacts" + shortcut: "Ctrl+S" + + - title: "Help" + actions: + - text: "About" + handler: "about" + - text: "Debug" + handler: "debug" +