WIP query tabs

This commit is contained in:
Keith Edmunds 2025-02-23 18:19:41 +00:00
parent 306ab103b6
commit 40756469ec

View File

@ -1,15 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Standard library imports # Standard library imports
from __future__ import annotations
from slugify import slugify # type: ignore from slugify import slugify # type: ignore
from typing import Callable, Optional from typing import Callable, Optional
import argparse import argparse
from dataclasses import dataclass
import datetime as dt import datetime as dt
import os import os
import subprocess import subprocess
import sys import sys
import urllib.parse import urllib.parse
import webbrowser import webbrowser
import yaml
# PyQt imports # PyQt imports
from PyQt6.QtCore import ( from PyQt6.QtCore import (
@ -22,14 +25,15 @@ from PyQt6.QtGui import (
QAction, QAction,
QCloseEvent, QCloseEvent,
QColor, QColor,
QFont,
QIcon, QIcon,
QKeySequence, QKeySequence,
QPalette, QPalette,
QShortcut, QShortcut,
) )
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemView,
QApplication, QApplication,
QCheckBox,
QComboBox, QComboBox,
QDialog, QDialog,
QFileDialog, QFileDialog,
@ -39,10 +43,11 @@ from PyQt6.QtWidgets import (
QLineEdit, QLineEdit,
QListWidgetItem, QListWidgetItem,
QMainWindow, QMainWindow,
QMenu,
QMessageBox, QMessageBox,
QPushButton, QPushButton,
QSizePolicy, QTableWidget,
QTableView, QTableWidgetItem,
QVBoxLayout, QVBoxLayout,
QWidget, QWidget,
) )
@ -61,21 +66,13 @@ from classes import (
from config import Config from config import Config
from dialogs import TrackSelectDialog from dialogs import TrackSelectDialog
from file_importer import FileImporter 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 log import log
from models import ( from models import db, Playdates, PlaylistRows, Playlists, Settings, Tracks
db,
Playdates,
PlaylistRows,
Playlists,
Queries,
Settings,
Tracks,
)
from music_manager import RowAndTrack, track_sequence from music_manager import RowAndTrack, track_sequence
from playlistmodel import PlaylistModel, PlaylistProxyModel from playlistmodel import PlaylistModel, PlaylistProxyModel
from querylistmodel import QuerylistModel
from playlists import PlaylistTab from playlists import PlaylistTab
from ui import icons_rc # noqa F401
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
from ui.downloadcsv_ui import Ui_DateSelect # type: ignore from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
from ui.main_window_header_ui import Ui_HeaderSection # 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 ui.main_window_footer_ui import Ui_FooterSection # type: ignore
from utilities import check_db, update_bitrates from utilities import check_db, update_bitrates
import helpers
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 Current: 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): class EditDeleteDialog(QDialog):
def __init__(self, templates: list[tuple[str, int]]) -> None: def __init__(self, templates: list[tuple[str, int]]) -> None:
super().__init__() super().__init__()
@ -168,6 +166,129 @@ class EditDeleteDialog(QDialog):
self.reject() 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: class PreviewManager:
""" """
Manage track preview player Manage track preview player
@ -279,234 +400,6 @@ class PreviewManager:
self.start_time = None 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): class SelectPlaylistDialog(QDialog):
def __init__(self, parent=None, playlists=None, session=None): def __init__(self, parent=None, playlists=None, session=None):
super().__init__() super().__init__()
@ -713,106 +606,85 @@ class Window(QMainWindow):
return action return action
def create_menu_bar(self): def create_menu_bar(self):
"""Dynamically creates the menu bar from a YAML file."""
menu_bar = self.menuBar() menu_bar = self.menuBar()
# File Menu # Load menu structure from YAML file
file_menu = menu_bar.addMenu("&File") with open("menu.yaml", "r") as file:
file_menu.addAction( menu_data = yaml.safe_load(file)
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))
# Playlist Menu self.menu_actions = {} # Store reference for enabling/disabling actions
playlist_menu = menu_bar.addMenu("&Playlist") self.dynamic_submenus = {} # Store submenus for dynamic population
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))
# Clear Selection with Escape key. Save in module so we can for menu_item in menu_data["menus"]:
# enable/disable it later menu = menu_bar.addMenu(menu_item["title"])
self.action_Clear_selection = self.create_action(
"Clear Selection", self.clear_selection, "Esc"
)
playlist_menu.addAction(self.action_Clear_selection)
# Music Menu for action_item in menu_item["actions"]:
music_menu = menu_bar.addMenu("&Music") if "separator" in action_item and action_item["separator"]:
music_menu.addAction( menu.addSeparator()
self.create_action("Set Next", self.set_selected_track_next, "Ctrl+N") continue
)
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"
)
)
# Help Menu # Check whether this is a submenu first
help_menu = menu_bar.addMenu("Help") if action_item.get("submenu"):
help_menu.addAction(self.create_action("About", self.about)) submenu = QMenu(action_item["text"], self)
help_menu.addAction(self.create_action("Debug", self.debug)) 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: def about(self) -> None:
"""Get git tag and database name""" """Get git tag and database name"""
@ -868,8 +740,8 @@ class Window(QMainWindow):
# Don't allow window to close when a track is playing # Don't allow window to close when a track is playing
if track_sequence.current and track_sequence.current.is_playing(): if track_sequence.current and track_sequence.current.is_playing():
event.ignore() event.ignore()
self.show_warning( helpers.show_warning(
"Track playing", "Can't close application while track is playing" self, "Track playing", "Can't close application while track is playing"
) )
else: else:
with db.Session() as session: with db.Session() as session:
@ -926,7 +798,7 @@ class Window(QMainWindow):
current_track_playlist_id = track_sequence.current.playlist_id current_track_playlist_id = track_sequence.current.playlist_id
if current_track_playlist_id: if current_track_playlist_id:
if closing_tab_playlist_id == 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 "Current track", "Can't close current track playlist", self
) )
return False return False
@ -936,7 +808,7 @@ class Window(QMainWindow):
next_track_playlist_id = track_sequence.next.playlist_id next_track_playlist_id = track_sequence.next.playlist_id
if next_track_playlist_id: if next_track_playlist_id:
if closing_tab_playlist_id == 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 "Next track", "Can't close next track playlist", self
) )
return False return False
@ -1052,7 +924,7 @@ class Window(QMainWindow):
playlist_id = self.current.playlist_id playlist_id = self.current.playlist_id
playlist = session.get(Playlists, playlist_id) playlist = session.get(Playlists, playlist_id)
if playlist: if playlist:
if ask_yes_no( if helpers.ask_yes_no(
"Delete playlist", "Delete playlist",
f"Delete playlist '{playlist.name}': " "Are you sure?", f"Delete playlist '{playlist.name}': " "Are you sure?",
): ):
@ -1103,7 +975,8 @@ class Window(QMainWindow):
log.debug(f"enable_escape({enabled=})") 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: def end_of_track_actions(self) -> None:
""" """
@ -1297,42 +1170,60 @@ class Window(QMainWindow):
Delete / edit templates Delete / edit templates
""" """
# Build a list of (template-name, playlist-id) tuples def edit(template_id: int) -> None:
template_list: list[tuple[str, int]] = [] """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: with db.Session() as session:
for template in Playlists.get_all_templates(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 # # Get user's selection
dlg = EditDeleteDialog(template_list) # dlg = EditDeleteDialog(template_list)
if not dlg.exec(): # if not dlg.exec():
return # User cancelled # return # User cancelled
action, template_id = dlg.selection # action, template_id = dlg.selection
playlist = session.get(Playlists, template_id) # playlist = session.get(Playlists, template_id)
if not playlist: # if not playlist:
log.error(f"Error opening {template_id=}") # log.error(f"Error opening {template_id=}")
if action == "Edit": # if action == "Edit":
# Simply load the template as a playlist. Any changes # # Simply load the template as a playlist. Any changes
# made will persist # # made will persist
idx = self.create_playlist_tab(playlist) # idx = self.create_playlist_tab(playlist)
self.playlist_section.tabPlaylist.setCurrentIndex(idx) # self.playlist_section.tabPlaylist.setCurrentIndex(idx)
elif action == "Delete": # elif action == "Delete":
if ask_yes_no( # if helpers.ask_yes_no(
"Delete template", # "Delete template",
f"Delete template '{playlist.name}': " "Are you sure?", # f"Delete template '{playlist.name}': " "Are you sure?",
): # ):
if self.close_playlist_tab(): # if self.close_playlist_tab():
playlist.delete(session) # playlist.delete(session)
session.commit() # session.commit()
else: # else:
raise ApplicationError( # raise ApplicationError(
f"Unrecognised action from EditDeleteDialog: {action=}" # f"Unrecognised action from EditDeleteDialog: {action=}"
) # )
def mark_rows_for_moving(self) -> None: def mark_rows_for_moving(self) -> None:
""" """
@ -1474,21 +1365,6 @@ class Window(QMainWindow):
self.playlist_section.tabPlaylist.setCurrentIndex(idx) 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: def open_songfacts_browser(self, title: str) -> None:
"""Search Songfacts for title""" """Search Songfacts for title"""
@ -1761,7 +1637,7 @@ class Window(QMainWindow):
msg = "Hit return to play next track now" msg = "Hit return to play next track now"
else: else:
msg = "Press tab to select Yes and hit return to play next track" 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", "Play next track",
msg, msg,
default_yes=default_yes, default_yes=default_yes,
@ -1831,12 +1707,12 @@ class Window(QMainWindow):
template_name = dlg.textValue() template_name = dlg.textValue()
if template_name not in template_names: if template_name not in template_names:
break break
self.show_warning( helpers.show_warning(
"Duplicate template", "Template name already in use" self, "Duplicate template", "Template name already in use"
) )
Playlists.save_as_template(session, self.current.playlist_id, template_name) Playlists.save_as_template(session, self.current.playlist_id, template_name)
session.commit() session.commit()
show_OK("Template", "Template saved", self) helpers.show_OK("Template", "Template saved", self)
def search_playlist(self) -> None: def search_playlist(self) -> None:
"""Show text box to search playlist""" """Show text box to search playlist"""
@ -1982,7 +1858,8 @@ class Window(QMainWindow):
if Playlists.name_is_available(session, proposed_name): if Playlists.name_is_available(session, proposed_name):
return proposed_name return proposed_name
else: else:
self.show_warning( helpers.show_warning(
self,
"Name in use", "Name in use",
f"There's already a playlist called '{proposed_name}'", 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(): if track_sequence.current and track_sequence.current.is_playing():
# Elapsed time # Elapsed time
self.header_section.label_elapsed_timer.setText( 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
time_to_fade = track_sequence.current.time_to_fade() time_to_fade = track_sequence.current.time_to_fade()
time_to_silence = track_sequence.current.time_to_silence() time_to_silence = track_sequence.current.time_to_silence()
self.footer_section.label_fade_timer.setText( 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 # 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.frame_fade.setStyleSheet("")
self.footer_section.label_silent_timer.setText( 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: def update_headers(self) -> None: