From 40756469ec99fd2f3c3fbb6db109e4528636dd7c Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Sun, 23 Feb 2025 18:19:41 +0000 Subject: [PATCH] WIP query tabs --- app/musicmuster.py | 689 +++++++++++++++++++-------------------------- 1 file changed, 283 insertions(+), 406 deletions(-) diff --git a/app/musicmuster.py b/app/musicmuster.py index 2c7ab27..3d10000 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -1,15 +1,18 @@ #!/usr/bin/env python3 # Standard library imports +from __future__ import annotations from slugify import slugify # type: ignore from typing import Callable, Optional import argparse +from dataclasses import dataclass import datetime as dt import os import subprocess import sys import urllib.parse import webbrowser +import yaml # PyQt imports from PyQt6.QtCore import ( @@ -22,14 +25,15 @@ from PyQt6.QtGui import ( QAction, QCloseEvent, QColor, + QFont, QIcon, QKeySequence, QPalette, QShortcut, ) from PyQt6.QtWidgets import ( - QAbstractItemView, QApplication, + QCheckBox, QComboBox, QDialog, QFileDialog, @@ -39,10 +43,11 @@ from PyQt6.QtWidgets import ( QLineEdit, QListWidgetItem, QMainWindow, + QMenu, QMessageBox, QPushButton, - QSizePolicy, - QTableView, + QTableWidget, + QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -61,21 +66,13 @@ from classes import ( from config import Config from dialogs import TrackSelectDialog from file_importer import FileImporter -from helpers import ask_yes_no, file_is_unreadable, ms_to_mmss, show_OK +from helpers import file_is_unreadable from log import log -from models import ( - db, - Playdates, - PlaylistRows, - Playlists, - Queries, - Settings, - Tracks, -) +from models import db, Playdates, PlaylistRows, Playlists, Settings, Tracks from music_manager import RowAndTrack, track_sequence from playlistmodel import PlaylistModel, PlaylistProxyModel -from querylistmodel import QuerylistModel from playlists import PlaylistTab +from ui import icons_rc # noqa F401 from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore from ui.downloadcsv_ui import Ui_DateSelect # type: ignore from ui.main_window_header_ui import Ui_HeaderSection # type: ignore @@ -83,18 +80,7 @@ from ui.main_window_playlist_ui import Ui_PlaylistSection # type: ignore from ui.main_window_footer_ui import Ui_FooterSection # type: ignore from utilities import check_db, update_bitrates - - -class DownloadCSV(QDialog): - def __init__(self, parent=None): - super().__init__() - - self.ui = Ui_DateSelect() - self.ui.setupUi(self) - self.ui.dateTimeEdit.setDate(QDate.currentDate()) - self.ui.dateTimeEdit.setTime(QTime(19, 59, 0)) - self.ui.buttonBox.accepted.connect(self.accept) - self.ui.buttonBox.rejected.connect(self.reject) +import helpers class Current: @@ -110,6 +96,18 @@ class Current: ) +class DownloadCSV(QDialog): + def __init__(self, parent=None): + super().__init__() + + self.ui = Ui_DateSelect() + self.ui.setupUi(self) + self.ui.dateTimeEdit.setDate(QDate.currentDate()) + self.ui.dateTimeEdit.setTime(QTime(19, 59, 0)) + self.ui.buttonBox.accepted.connect(self.accept) + self.ui.buttonBox.rejected.connect(self.reject) + + class EditDeleteDialog(QDialog): def __init__(self, templates: list[tuple[str, int]]) -> None: super().__init__() @@ -168,6 +166,129 @@ class EditDeleteDialog(QDialog): self.reject() +@dataclass +class ItemlistItem: + id: int + name: str + favourite: bool = False + + +class ItemlistManager(QDialog): + def __init__(self, items: list[ItemlistItem], callbacks: ItemlistManagerCallbacks) -> None: + super().__init__() + self.setWindowTitle("Item Manager") + self.setMinimumSize(600, 400) + + self.items = items + self.callbacks = callbacks + + layout = QVBoxLayout(self) + self.table = QTableWidget(len(items), 2, self) + self.table.setHorizontalHeaderLabels(["Item", "Actions"]) + hh = self.table.horizontalHeader() + if not hh: + raise ApplicationError("ItemlistManager failed to create horizontalHeader") + hh.setStretchLastSection(True) + self.table.setColumnWidth(0, 200) + self.table.setColumnWidth(1, 300) + + self.populate_table() + + layout.addWidget(self.table) + + button_layout = QHBoxLayout() + self.new_button = QPushButton("New") + self.new_button.clicked.connect(self.new_item) + button_layout.addWidget(self.new_button) + + self.close_button = QPushButton("Close") + self.close_button.clicked.connect(self.close) + button_layout.addWidget(self.close_button) + + layout.addLayout(button_layout) + + def populate_table(self) -> None: + """Populates the table with items and action buttons.""" + self.table.setRowCount(len(self.items)) + + for row, item in enumerate(self.items): + item_text = QTableWidgetItem(item.name) + if item.favourite: + item_text.setFont(QFont("Arial", weight=QFont.Weight.Bold)) + self.table.setItem(row, 0, item_text) + + # Action Buttons and Checkbox in a widget + widget = QWidget() + h_layout = QHBoxLayout(widget) + h_layout.setContentsMargins(0, 0, 0, 0) + h_layout.setSpacing(5) + + rename_button = QPushButton("Rename") + rename_button.clicked.connect(lambda _, i=item.id: self.rename_item(i)) + h_layout.addWidget(rename_button) + + edit_button = QPushButton("Edit") + edit_button.clicked.connect(lambda _, i=item.id: self.edit_item(i)) + h_layout.addWidget(edit_button) + + delete_button = QPushButton("Delete") + delete_button.clicked.connect(lambda _, i=item.id: self.delete_item(i)) + h_layout.addWidget(delete_button) + + fav_checkbox = QCheckBox() + fav_checkbox.setChecked(item.favourite) + fav_checkbox.stateChanged.connect( + lambda state, cb=fav_checkbox, i=item.id: self.toggle_favourite( + i, cb.isChecked() + ) + ) + h_layout.addWidget(fav_checkbox) + + self.table.setCellWidget(row, 1, widget) + + def rename_item(self, item_id: int) -> None: + print(f"Rename item {item_id}") + + def edit_item(self, item_id: int) -> None: + print(f"Edit item {item_id}") + self.callbacks.edit(item_id) + + def delete_item(self, item_id: int) -> None: + print(f"Delete item {item_id}") + self.callbacks.delete(item_id) + + def toggle_favourite(self, item_id: int, checked: bool) -> None: + print(f"Toggle favourite for item {item_id}: {checked}") + self.callbacks.favourite(item_id, checked) + + for row in range(self.table.rowCount()): + item = self.table.item(row, 0) + if item and self.items[row].id == item_id: + font = QFont( + "Arial", + weight=QFont.Weight.Bold if checked else QFont.Weight.Normal, + ) + item.setFont(font) + self.items[row].favourite = checked + break + + def new_item(self) -> None: + print("New item") + + # test_items = [ + # {"id": 1, "text": "Item 1", "favourite": False}, + # {"id": 2, "text": "Item 2", "favourite": True}, + # {"id": 3, "text": "Item 3", "favourite": False} + # ] + + +@dataclass +class ItemlistManagerCallbacks: + edit: Callable[[int], None] + delete: Callable[[int], None] + favourite: Callable[[int, bool], None] + + class PreviewManager: """ Manage track preview player @@ -279,234 +400,6 @@ class PreviewManager: self.start_time = None -class QueryDialog(QDialog): - """Dialog box to handle selecting track from a SQL query""" - - def __init__(self, session: Session) -> None: - super().__init__() - self.session = session - - # Build a list of (query-name, playlist-id) tuples - self.selected_tracks: list[int] = [] - - self.query_list: list[tuple[str, int]] = [] - self.query_list.append((Config.NO_QUERY_NAME, 0)) - for query in Queries.get_all(self.session): - self.query_list.append((query.name, query.id)) - - self.setWindowTitle("Query Selector") - - # Create label - query_label = QLabel("Query:") - - # Top layout (Query label, combo box, and info label) - top_layout = QHBoxLayout() - - # Query label - query_label = QLabel("Query:") - top_layout.addWidget(query_label) - - # Combo Box with fixed width - self.combo_box = QComboBox() - self.combo_box.setFixedWidth(150) # Adjust as necessary for 20 characters - for text, id_ in self.query_list: - self.combo_box.addItem(text, id_) - top_layout.addWidget(self.combo_box) - - # Information label (two-row height, wrapping) - self.description_label = QLabel("") - self.description_label.setWordWrap(True) - self.description_label.setMinimumHeight(40) # Approximate height for two rows - self.description_label.setSizePolicy( - QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred - ) - top_layout.addWidget(self.description_label) - - # Table (middle part) - self.table_view = QTableView() - self.table_view.setSelectionMode( - QAbstractItemView.SelectionMode.ExtendedSelection - ) - self.table_view.setSelectionBehavior( - QAbstractItemView.SelectionBehavior.SelectRows - ) - self.table_view.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) - self.table_view.setAlternatingRowColors(True) - self.table_view.setVerticalScrollMode( - QAbstractItemView.ScrollMode.ScrollPerPixel - ) - self.table_view.clicked.connect(self.handle_row_click) - - # Bottom layout (buttons) - bottom_layout = QHBoxLayout() - bottom_layout.addStretch() # Push buttons to the right - - self.add_tracks_button = QPushButton("Add tracks") - self.add_tracks_button.setEnabled(False) # Disabled by default - self.add_tracks_button.clicked.connect(self.add_tracks_clicked) - bottom_layout.addWidget(self.add_tracks_button) - - self.cancel_button = QPushButton("Cancel") - self.cancel_button.clicked.connect(self.cancel_clicked) - bottom_layout.addWidget(self.cancel_button) - - # Main layout - main_layout = QVBoxLayout() - main_layout.addLayout(top_layout) - main_layout.addWidget(self.table_view) - main_layout.addLayout(bottom_layout) - - self.combo_box.currentIndexChanged.connect(self.query_changed) - self.setLayout(main_layout) - - # Stretch last column *after* setting column widths which is - # *much* faster - h_header = self.table_view.horizontalHeader() - if h_header: - h_header.sectionResized.connect(self._column_resize) - h_header.setStretchLastSection(True) - # Resize on vertical header click - v_header = self.table_view.verticalHeader() - if v_header: - v_header.setMinimumSectionSize(5) - v_header.sectionHandleDoubleClicked.disconnect() - v_header.sectionHandleDoubleClicked.connect( - self.table_view.resizeRowToContents - ) - - self.set_window_size() - self.resizeRowsToContents() - - def add_tracks_clicked(self): - self.selected_tracks = self.table_view.model().get_selected_track_ids() - self.accept() - - def cancel_clicked(self): - self.selected_tracks = [] - self.reject() - - def closeEvent(self, event: QCloseEvent | None) -> None: - """ - Record size and columns - """ - - self.save_sizes() - super().closeEvent(event) - - def accept(self) -> None: - self.save_sizes() - super().accept() - - def reject(self) -> None: - self.save_sizes() - super().reject() - - def save_sizes(self) -> None: - """ - Save window size - """ - - # Save dialog box attributes - attributes_to_save = dict( - querylist_height=self.height(), - querylist_width=self.width(), - querylist_x=self.x(), - querylist_y=self.y(), - ) - for name, value in attributes_to_save.items(): - record = Settings.get_setting(self.session, name) - record.f_int = value - - header = self.table_view.horizontalHeader() - if header is None: - return - column_count = header.count() - if column_count < 2: - return - for column_number in range(column_count - 1): - attr_name = f"querylist_col_{column_number}_width" - record = Settings.get_setting(self.session, attr_name) - record.f_int = self.table_view.columnWidth(column_number) - - self.session.commit() - - def _column_resize(self, column_number: int, _old: int, _new: int) -> None: - """ - Called when column width changes. - """ - - header = self.table_view.horizontalHeader() - if not header: - return - - # Resize rows if necessary - self.resizeRowsToContents() - - def resizeRowsToContents(self): - header = self.table_view.verticalHeader() - model = self.table_view.model() - if model: - for row in model.rowCount(): - hint = self.sizeHintForRow(row) - header.resizeSection(row, hint) - - def query_changed(self, idx: int) -> None: - """ - Called when user selects query - """ - - # Get query - query = self.session.get(Queries, idx) - if not query: - return - - # Create model - base_model = QuerylistModel(self.session, query.sql) - - # Create table - self.table_view.setModel(base_model) - self.set_column_sizes() - self.description_label.setText(query.description) - - def handle_row_click(self, index): - self.table_view.model().toggle_row_selection(index.row()) - self.table_view.clearSelection() - - # Enable 'Add tracks' button only when a row is selected - selected = self.table_view.model().get_selected_track_ids() - self.add_tracks_button.setEnabled(selected != []) - - def set_window_size(self) -> None: - """Set window sizes""" - - x = Settings.get_setting(self.session, "querylist_x").f_int or 100 - y = Settings.get_setting(self.session, "querylist_y").f_int or 100 - width = Settings.get_setting(self.session, "querylist_width").f_int or 100 - height = Settings.get_setting(self.session, "querylist_height").f_int or 100 - self.setGeometry(x, y, width, height) - - def set_column_sizes(self) -> None: - """Set column sizes""" - - header = self.table_view.horizontalHeader() - if header is None: - return - column_count = header.count() - if column_count < 2: - return - - # Last column is set to stretch so ignore it here - for column_number in range(column_count - 1): - attr_name = f"querylist_col_{column_number}_width" - record = Settings.get_setting(self.session, attr_name) - if record.f_int is not None: - self.table_view.setColumnWidth(column_number, record.f_int) - else: - self.table_view.setColumnWidth( - column_number, Config.DEFAULT_COLUMN_WIDTH - ) - - class SelectPlaylistDialog(QDialog): def __init__(self, parent=None, playlists=None, session=None): super().__init__() @@ -713,106 +606,85 @@ 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("Open Querylist", self.open_querylist)) - 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)) + self.menu_actions = {} # Store reference for enabling/disabling actions + self.dynamic_submenus = {} # Store submenus for dynamic population - # 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)) + # Check whether this is a submenu first + if action_item.get("submenu"): + submenu = QMenu(action_item["text"], self) + menu.addMenu(submenu) + + # Store submenu reference for dynamic population + self.dynamic_submenus[action_item["handler"]] = submenu + submenu.aboutToShow.connect(self.populate_dynamic_submenu) + continue # Skip the rest of the loop (no handler needed) + + # Now check for a normal menu action + handler = getattr(self, action_item["handler"], None) + if handler is None: + print(f"Warning: No handler found for {action_item['text']}") + continue + + action = self.create_action( + action_item["text"], handler, action_item.get("shortcut") + ) + # Store reference to "Clear Selection" so we can enable/disable it + if action_item.get("store_reference"): + self.menu_actions[action_item["handler"]] = action + + menu.addAction(action) + + def populate_dynamic_submenu(self): + """Dynamically populates submenus when they are selected.""" + submenu = self.sender() # Get the submenu that triggered the event + + # Find which submenu it is + for key, stored_submenu in self.dynamic_submenus.items(): + if submenu == stored_submenu: + submenu.clear() + # Dynamically call the correct function + items = getattr(self, f"get_{key}_items")() + for item in items: + action = QAction(item["text"], self) + action.triggered.connect( + lambda _, i=item["handler"]: getattr(self, i)() + ) + submenu.addAction(action) + break + + def get_new_playlist_dynamic_submenu_items(self): + """Returns dynamically generated menu items for Submenu 1.""" + return [ + {"text": "Option A", "handler": "option_a_handler"}, + {"text": "Option B", "handler": "option_b_handler"}, + ] + + def get_query_dynamic_submenu_items(self): + """Returns dynamically generated menu items for Submenu 2.""" + return [ + {"text": "Action X", "handler": "action_x_handler"}, + {"text": "Action Y", "handler": "action_y_handler"}, + ] + + def select_duplicate_rows(self) -> None: + """Call playlist to select duplicate rows""" + + self.active_tab().select_duplicate_rows() def about(self) -> None: """Get git tag and database name""" @@ -868,8 +740,8 @@ class Window(QMainWindow): # Don't allow window to close when a track is playing if track_sequence.current and track_sequence.current.is_playing(): event.ignore() - self.show_warning( - "Track playing", "Can't close application while track is playing" + helpers.show_warning( + self, "Track playing", "Can't close application while track is playing" ) else: with db.Session() as session: @@ -926,7 +798,7 @@ class Window(QMainWindow): current_track_playlist_id = track_sequence.current.playlist_id if current_track_playlist_id: if closing_tab_playlist_id == current_track_playlist_id: - show_OK( + helpers.show_OK( "Current track", "Can't close current track playlist", self ) return False @@ -936,7 +808,7 @@ class Window(QMainWindow): next_track_playlist_id = track_sequence.next.playlist_id if next_track_playlist_id: if closing_tab_playlist_id == next_track_playlist_id: - show_OK( + helpers.show_OK( "Next track", "Can't close next track playlist", self ) return False @@ -1052,7 +924,7 @@ class Window(QMainWindow): playlist_id = self.current.playlist_id playlist = session.get(Playlists, playlist_id) if playlist: - if ask_yes_no( + if helpers.ask_yes_no( "Delete playlist", f"Delete playlist '{playlist.name}': " "Are you sure?", ): @@ -1103,7 +975,8 @@ class Window(QMainWindow): log.debug(f"enable_escape({enabled=})") - self.action_Clear_selection.setEnabled(enabled) + if "clear_selection" in self.menu_actions: + self.menu_actions["clear_selection"].setEnabled(enabled) def end_of_track_actions(self) -> None: """ @@ -1297,42 +1170,60 @@ class Window(QMainWindow): Delete / edit templates """ - # Build a list of (template-name, playlist-id) tuples - template_list: list[tuple[str, int]] = [] + def edit(template_id: int) -> None: + """Edit template""" + + print(f"manage_templates.edit({template_id=}") + + def delete(template_id: int) -> None: + """delete template""" + + print(f"manage_templates.delete({template_id=}") + + def favourite(template_id: int, favourite: bool) -> None: + """favourite template""" + + print(f"manage_templates.favourite({template_id=}") + + callbacks = ItemlistManagerCallbacks(edit, delete, favourite) + + # Build a list of templates + template_list: list[ItemlistItem] = [] with db.Session() as session: for template in Playlists.get_all_templates(session): - template_list.append((template.name, template.id)) + # TODO: need to add in favourites + template_list.append(ItemlistItem(name=template.name, id=template.id)) - # Get user's selection - dlg = EditDeleteDialog(template_list) - if not dlg.exec(): - return # User cancelled + # # Get user's selection + # dlg = EditDeleteDialog(template_list) + # if not dlg.exec(): + # return # User cancelled - action, template_id = dlg.selection + # action, template_id = dlg.selection - playlist = session.get(Playlists, template_id) - if not playlist: - log.error(f"Error opening {template_id=}") + # playlist = session.get(Playlists, template_id) + # if not playlist: + # log.error(f"Error opening {template_id=}") - if action == "Edit": - # Simply load the template as a playlist. Any changes - # made will persist - idx = self.create_playlist_tab(playlist) - self.playlist_section.tabPlaylist.setCurrentIndex(idx) + # if action == "Edit": + # # Simply load the template as a playlist. Any changes + # # made will persist + # idx = self.create_playlist_tab(playlist) + # self.playlist_section.tabPlaylist.setCurrentIndex(idx) - elif action == "Delete": - if ask_yes_no( - "Delete template", - f"Delete template '{playlist.name}': " "Are you sure?", - ): - if self.close_playlist_tab(): - playlist.delete(session) - session.commit() - else: - raise ApplicationError( - f"Unrecognised action from EditDeleteDialog: {action=}" - ) + # elif action == "Delete": + # if helpers.ask_yes_no( + # "Delete template", + # f"Delete template '{playlist.name}': " "Are you sure?", + # ): + # if self.close_playlist_tab(): + # playlist.delete(session) + # session.commit() + # else: + # raise ApplicationError( + # f"Unrecognised action from EditDeleteDialog: {action=}" + # ) def mark_rows_for_moving(self) -> None: """ @@ -1474,21 +1365,6 @@ class Window(QMainWindow): self.playlist_section.tabPlaylist.setCurrentIndex(idx) - def open_querylist(self) -> None: - """Open existing querylist""" - - try: - with db.Session() as session: - dlg = QueryDialog(session) - if dlg.exec(): - new_row_number = self.current_row_or_end() - for track_id in dlg.selected_tracks: - self.current.base_model.insert_row(new_row_number, track_id) - else: - return # User cancelled - except ApplicationError as e: - self.show_warning("Query error", f"Your query gave an error:\n\n{e}") - def open_songfacts_browser(self, title: str) -> None: """Search Songfacts for title""" @@ -1761,7 +1637,7 @@ class Window(QMainWindow): msg = "Hit return to play next track now" else: msg = "Press tab to select Yes and hit return to play next track" - if not ask_yes_no( + if not helpers.ask_yes_no( "Play next track", msg, default_yes=default_yes, @@ -1831,12 +1707,12 @@ class Window(QMainWindow): template_name = dlg.textValue() if template_name not in template_names: break - self.show_warning( - "Duplicate template", "Template name already in use" + helpers.show_warning( + self, "Duplicate template", "Template name already in use" ) Playlists.save_as_template(session, self.current.playlist_id, template_name) session.commit() - show_OK("Template", "Template saved", self) + helpers.show_OK("Template", "Template saved", self) def search_playlist(self) -> None: """Show text box to search playlist""" @@ -1982,7 +1858,8 @@ class Window(QMainWindow): if Playlists.name_is_available(session, proposed_name): return proposed_name else: - self.show_warning( + helpers.show_warning( + self, "Name in use", f"There's already a playlist called '{proposed_name}'", ) @@ -2085,16 +1962,16 @@ class Window(QMainWindow): if track_sequence.current and track_sequence.current.is_playing(): # Elapsed time self.header_section.label_elapsed_timer.setText( - ms_to_mmss(track_sequence.current.time_playing()) + helpers.ms_to_mmss(track_sequence.current.time_playing()) + " / " - + ms_to_mmss(track_sequence.current.duration) + + helpers.ms_to_mmss(track_sequence.current.duration) ) # Time to fade time_to_fade = track_sequence.current.time_to_fade() time_to_silence = track_sequence.current.time_to_silence() self.footer_section.label_fade_timer.setText( - ms_to_mmss(time_to_fade) + helpers.ms_to_mmss(time_to_fade) ) # If silent in the next 5 seconds, put warning colour on @@ -2133,7 +2010,7 @@ class Window(QMainWindow): self.footer_section.frame_fade.setStyleSheet("") self.footer_section.label_silent_timer.setText( - ms_to_mmss(time_to_silence) + helpers.ms_to_mmss(time_to_silence) ) def update_headers(self) -> None: