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
# 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: