Major update: correct use of proxy model

Fixes #273
This commit is contained in:
Keith Edmunds 2024-12-26 14:09:21 +00:00
parent 937f3cd074
commit b14b90396f
8 changed files with 509 additions and 376 deletions

View File

@ -1,7 +1,7 @@
# Standard library imports # Standard library imports
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass, field
from enum import auto, Enum from enum import auto, Enum
import functools import functools
from typing import NamedTuple from typing import NamedTuple
@ -13,7 +13,6 @@ from PyQt6.QtCore import (
pyqtSignal, pyqtSignal,
QObject, QObject,
) )
from PyQt6.QtGui import QAction
# App imports # App imports
@ -94,6 +93,13 @@ class MusicMusterSignals(QObject):
super().__init__() super().__init__()
@singleton
@dataclass
class Selection:
playlist_id: int = 0
rows: list[int] = field(default_factory=list)
class Tags(NamedTuple): class Tags(NamedTuple):
artist: str artist: str
title: str title: str

View File

@ -106,6 +106,7 @@ class Config(object):
SECTION_STARTS = ("+", "+-", "-+") SECTION_STARTS = ("+", "+-", "-+")
SONGFACTS_ON_NEXT = False SONGFACTS_ON_NEXT = False
START_GAP_WARNING_THRESHOLD = 300 START_GAP_WARNING_THRESHOLD = 300
SUBTOTAL_ON_ROW_ZERO = "[No subtotal on first row]"
TEXT_NO_TRACK_NO_NOTE = "[Section header]" TEXT_NO_TRACK_NO_NOTE = "[Section header]"
TOD_TIME_FORMAT = "%H:%M:%S" TOD_TIME_FORMAT = "%H:%M:%S"
TRACK_TIME_FORMAT = "%H:%M:%S" TRACK_TIME_FORMAT = "%H:%M:%S"

View File

@ -40,7 +40,7 @@ class TrackSelectDialog(QDialog):
parent: QMainWindow, parent: QMainWindow,
session: Session, session: Session,
new_row_number: int, new_row_number: int,
source_model: PlaylistModel, base_model: PlaylistModel,
add_to_header: Optional[bool] = False, add_to_header: Optional[bool] = False,
*args: Qt.WindowType, *args: Qt.WindowType,
**kwargs: Qt.WindowType, **kwargs: Qt.WindowType,
@ -52,7 +52,7 @@ class TrackSelectDialog(QDialog):
super().__init__(parent, *args, **kwargs) super().__init__(parent, *args, **kwargs)
self.session = session self.session = session
self.new_row_number = new_row_number self.new_row_number = new_row_number
self.source_model = source_model self.base_model = base_model
self.add_to_header = add_to_header self.add_to_header = add_to_header
self.ui = dlg_TrackSelect_ui.Ui_Dialog() self.ui = dlg_TrackSelect_ui.Ui_Dialog()
self.ui.setupUi(self) self.ui.setupUi(self)
@ -96,7 +96,7 @@ class TrackSelectDialog(QDialog):
track_id = track.id track_id = track.id
if note and not track_id: if note and not track_id:
self.source_model.insert_row(self.new_row_number, track_id, note) self.base_model.insert_row(self.new_row_number, track_id, note)
self.ui.txtNote.clear() self.ui.txtNote.clear()
self.new_row_number += 1 self.new_row_number += 1
return return
@ -110,7 +110,7 @@ class TrackSelectDialog(QDialog):
# Check whether track is already in playlist # Check whether track is already in playlist
move_existing = False move_existing = False
existing_prd = self.source_model.is_track_in_playlist(track_id) existing_prd = self.base_model.is_track_in_playlist(track_id)
if existing_prd is not None: if existing_prd is not None:
if ask_yes_no( if ask_yes_no(
"Duplicate row", "Duplicate row",
@ -121,21 +121,21 @@ class TrackSelectDialog(QDialog):
if self.add_to_header: if self.add_to_header:
if move_existing and existing_prd: # "and existing_prd" for mypy's benefit if move_existing and existing_prd: # "and existing_prd" for mypy's benefit
self.source_model.move_track_to_header( self.base_model.move_track_to_header(
self.new_row_number, existing_prd, note self.new_row_number, existing_prd, note
) )
else: else:
self.source_model.add_track_to_header(self.new_row_number, track_id) self.base_model.add_track_to_header(self.new_row_number, track_id)
# Close dialog - we can only add one track to a header # Close dialog - we can only add one track to a header
self.accept() self.accept()
else: else:
# Adding a new track row # Adding a new track row
if move_existing and existing_prd: # "and existing_prd" for mypy's benefit if move_existing and existing_prd: # "and existing_prd" for mypy's benefit
self.source_model.move_track_add_note( self.base_model.move_track_add_note(
self.new_row_number, existing_prd, note self.new_row_number, existing_prd, note
) )
else: else:
self.source_model.insert_row(self.new_row_number, track_id, note) self.base_model.insert_row(self.new_row_number, track_id, note)
self.new_row_number += 1 self.new_row_number += 1

View File

@ -58,7 +58,7 @@ class DoTrackImport(QObject):
destination_track_path: str, destination_track_path: str,
track_id: int, track_id: int,
audio_metadata: AudioMetadata, audio_metadata: AudioMetadata,
source_model: PlaylistModel, base_model: PlaylistModel,
row_number: Optional[int], row_number: Optional[int],
) -> None: ) -> None:
""" """
@ -72,10 +72,10 @@ class DoTrackImport(QObject):
self.destination_track_path = destination_track_path self.destination_track_path = destination_track_path
self.track_id = track_id self.track_id = track_id
self.audio_metadata = audio_metadata self.audio_metadata = audio_metadata
self.source_model = source_model self.base_model = base_model
if row_number is None: if row_number is None:
self.next_row_number = source_model.rowCount() self.next_row_number = base_model.rowCount()
else: else:
self.next_row_number = row_number self.next_row_number = row_number
@ -130,7 +130,7 @@ class DoTrackImport(QObject):
session.commit() session.commit()
helpers.normalise_track(self.destination_track_path) helpers.normalise_track(self.destination_track_path)
self.source_model.insert_row(self.next_row_number, track.id, "imported") self.base_model.insert_row(self.next_row_number, track.id, "imported")
self.next_row_number += 1 self.next_row_number += 1
self.signals.status_message_signal.emit( self.signals.status_message_signal.emit(
@ -144,14 +144,19 @@ class FileImporter:
Manage importing of files Manage importing of files
""" """
def __init__(self, active_proxy_model: PlaylistModel, row_number: int) -> None: def __init__(
self, base_model: PlaylistModel, row_number: Optional[int] = None
) -> None:
""" """
Set up class Set up class
""" """
# Save parameters # Save parameters
self.active_proxy_model = active_proxy_model self.base_model = base_model
if row_number:
self.row_number = row_number self.row_number = row_number
else:
self.row_number = base_model.rowCount()
# Data structure to track files to import # Data structure to track files to import
self.import_files_data: list[TrackFileData] = [] self.import_files_data: list[TrackFileData] = []
# Dictionary of exsting tracks # Dictionary of exsting tracks
@ -279,7 +284,7 @@ class FileImporter:
destination_track_path=f.destination_track_path, destination_track_path=f.destination_track_path,
track_id=f.track_id, track_id=f.track_id,
audio_metadata=helpers.get_audio_metadata(f.import_file_path), audio_metadata=helpers.get_audio_metadata(f.import_file_path),
source_model=self.active_proxy_model, base_model=self.base_model,
row_number=self.row_number, row_number=self.row_number,
) )
@ -336,7 +341,7 @@ class FileImporter:
f"{self.existing_tracks[track_id].title} " f"{self.existing_tracks[track_id].title} "
f"({self.existing_tracks[track_id].artist})", f"({self.existing_tracks[track_id].artist})",
track_id, track_id,
str(self.existing_tracks[track_id].path) str(self.existing_tracks[track_id].path),
) )
) )
@ -448,7 +453,7 @@ class PickMatch(QDialog):
self.init_ui(items_with_ids) self.init_ui(items_with_ids)
self.selected_id = -1 self.selected_id = -1
def init_ui(self, items_with_ids: list[tuple[str, int]]) -> None: def init_ui(self, items_with_ids: list[tuple[str, int, str]]) -> None:
""" """
Set up dialog Set up dialog
""" """

View File

@ -1,11 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Standard library imports # Standard library imports
from dataclasses import dataclass, field
from slugify import slugify # type: ignore
from typing import List, Optional from typing import List, Optional
import argparse import argparse
import datetime as dt import datetime as dt
import os import os
from slugify import slugify # type: ignore
import subprocess import subprocess
import sys import sys
import urllib.parse import urllib.parse
@ -48,6 +49,7 @@ import stackprinter # type: ignore
# App imports # App imports
from classes import ( from classes import (
MusicMusterSignals, MusicMusterSignals,
Selection,
TrackInfo, TrackInfo,
) )
from config import Config from config import Config
@ -67,6 +69,27 @@ from utilities import check_db, update_bitrates
import helpers 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)
@dataclass
class PlaylistData:
base_model: PlaylistModel
proxy_model: PlaylistProxyModel
def __post_init__(self):
self.proxy_model.setSourceModel(self.base_model)
class PreviewManager: class PreviewManager:
""" """
Manage track preview player Manage track preview player
@ -178,6 +201,52 @@ class PreviewManager:
self.start_time = None self.start_time = None
class SelectPlaylistDialog(QDialog):
def __init__(self, parent=None, playlists=None, session=None):
super().__init__()
if playlists is None:
return
self.ui = Ui_dlgSelectPlaylist()
self.ui.setupUi(self)
self.ui.lstPlaylists.itemDoubleClicked.connect(self.list_doubleclick)
self.ui.buttonBox.accepted.connect(self.open)
self.ui.buttonBox.rejected.connect(self.close)
self.session = session
self.playlist = None
record = Settings.get_setting(self.session, "select_playlist_dialog_width")
width = record.f_int or 800
record = Settings.get_setting(self.session, "select_playlist_dialog_height")
height = record.f_int or 600
self.resize(width, height)
for playlist in playlists:
p = QListWidgetItem()
p.setText(playlist.name)
p.setData(Qt.ItemDataRole.UserRole, playlist)
self.ui.lstPlaylists.addItem(p)
def __del__(self): # review
record = Settings.get_setting(self.session, "select_playlist_dialog_height")
record.f_int = self.height()
record = Settings.get_setting(self.session, "select_playlist_dialog_width")
record.f_int = self.width()
self.session.commit()
def list_doubleclick(self, entry): # review
self.playlist = entry.data(Qt.ItemDataRole.UserRole)
self.accept()
def open(self): # review
if self.ui.lstPlaylists.selectedItems():
item = self.ui.lstPlaylists.currentItem()
self.playlist = item.data(Qt.ItemDataRole.UserRole)
self.accept()
class Window(QMainWindow, Ui_MainWindow): class Window(QMainWindow, Ui_MainWindow):
def __init__( def __init__(
self, parent: Optional[QWidget] = None, *args: list, **kwargs: dict self, parent: Optional[QWidget] = None, *args: list, **kwargs: dict
@ -205,7 +274,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.widgetFadeVolume.setBackground(Config.FADE_CURVE_BACKGROUND) self.widgetFadeVolume.setBackground(Config.FADE_CURVE_BACKGROUND)
self.move_source_rows: Optional[List[int]] = None self.move_source_rows: Optional[List[int]] = None
self.move_source_model: Optional[PlaylistProxyModel] = None self.move_source_model: Optional[PlaylistModel] = None
self.disable_selection_timing = False self.disable_selection_timing = False
self.clock_counter = 0 self.clock_counter = 0
@ -217,6 +286,8 @@ class Window(QMainWindow, Ui_MainWindow):
self.connect_signals_slots() self.connect_signals_slots()
self.catch_return_key = False self.catch_return_key = False
self.importer: Optional[FileImporter] = None self.importer: Optional[FileImporter] = None
self.selection = Selection()
self.playlists: dict[int, PlaylistData] = {}
if not Config.USE_INTERNAL_BROWSER: if not Config.USE_INTERNAL_BROWSER:
webbrowser.register( webbrowser.register(
@ -256,7 +327,7 @@ class Window(QMainWindow, Ui_MainWindow):
def active_tab(self) -> PlaylistTab: def active_tab(self) -> PlaylistTab:
return self.tabPlaylist.currentWidget() return self.tabPlaylist.currentWidget()
def active_proxy_model(self) -> PlaylistModel: def active_proxy_model(self) -> PlaylistProxyModel:
return self.tabPlaylist.currentWidget().model() return self.tabPlaylist.currentWidget().model()
def clear_next(self) -> None: def clear_next(self) -> None:
@ -473,15 +544,20 @@ class Window(QMainWindow, Ui_MainWindow):
def create_playlist_tab(self, playlist: Playlists) -> int: def create_playlist_tab(self, playlist: Playlists) -> int:
""" """
Take the passed playlist database object, create a playlist tab and Take the passed proxy model, create a playlist tab and
add tab to display. Return index number of tab. add tab to display. Return index number of tab.
""" """
log.debug(f"create_playlist_tab({playlist=})") log.debug(f"create_playlist_tab({playlist=})")
# Create model and proxy model
self.playlists[playlist.id] = PlaylistData(
base_model=PlaylistModel(playlist.id), proxy_model=PlaylistProxyModel()
)
# Create tab
playlist_tab = PlaylistTab( playlist_tab = PlaylistTab(
musicmuster=self, musicmuster=self, model=self.playlists[playlist.id].proxy_model
playlist_id=playlist.id,
) )
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name) idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
@ -648,6 +724,13 @@ class Window(QMainWindow, Ui_MainWindow):
if track_sequence.current: if track_sequence.current:
track_sequence.current.fade() track_sequence.current.fade()
def get_active_base_model(self) -> PlaylistModel:
"""
Return the model for the current tab
"""
return self.playlists[self.selection.playlist_id].base_model
def hide_played(self): def hide_played(self):
"""Toggle hide played tracks""" """Toggle hide played tracks"""
@ -673,7 +756,7 @@ class Window(QMainWindow, Ui_MainWindow):
# We need to keep a referent to the FileImporter else it will be # We need to keep a referent to the FileImporter else it will be
# garbage collected while import threads are still running # garbage collected while import threads are still running
self.importer = FileImporter( self.importer = FileImporter(
self.active_proxy_model(), self.get_active_base_model(),
self.active_tab().source_model_selected_row_number(), self.active_tab().source_model_selected_row_number(),
) )
self.importer.do_import() self.importer.do_import()
@ -681,11 +764,6 @@ class Window(QMainWindow, Ui_MainWindow):
def insert_header(self) -> None: def insert_header(self) -> None:
"""Show dialog box to enter header text and add to playlist""" """Show dialog box to enter header text and add to playlist"""
proxy_model = self.active_proxy_model()
if proxy_model is None:
log.error("No proxy model")
return
# Get header text # Get header text
dlg: QInputDialog = QInputDialog(self) dlg: QInputDialog = QInputDialog(self)
dlg.setInputMode(QInputDialog.InputMode.TextInput) dlg.setInputMode(QInputDialog.InputMode.TextInput)
@ -693,7 +771,7 @@ class Window(QMainWindow, Ui_MainWindow):
dlg.resize(500, 100) dlg.resize(500, 100)
ok = dlg.exec() ok = dlg.exec()
if ok: if ok:
proxy_model.insert_row( self.get_active_base_model().insert_row(
proposed_row_number=self.active_tab().source_model_selected_row_number(), proposed_row_number=self.active_tab().source_model_selected_row_number(),
note=dlg.textValue(), note=dlg.textValue(),
) )
@ -710,7 +788,7 @@ class Window(QMainWindow, Ui_MainWindow):
parent=self, parent=self,
session=session, session=session,
new_row_number=new_row_number, new_row_number=new_row_number,
source_model=self.active_proxy_model(), base_model=self.get_active_base_model(),
) )
dlg.exec() dlg.exec()
session.commit() session.commit()
@ -722,9 +800,10 @@ class Window(QMainWindow, Ui_MainWindow):
with db.Session() as session: with db.Session() as session:
for playlist in Playlists.get_open(session): for playlist in Playlists.get_open(session):
if playlist: if playlist:
_ = self.create_playlist_tab(playlist)
playlist_ids.append(playlist.id)
log.debug(f"load_last_playlists() loaded {playlist=}") log.debug(f"load_last_playlists() loaded {playlist=}")
# Create tab
playlist_ids.append(self.create_playlist_tab(playlist))
# Set active tab # Set active tab
record = Settings.get_setting(session, "active_tab") record = Settings.get_setting(session, "active_tab")
if record.f_int is not None and record.f_int >= 0: if record.f_int is not None and record.f_int >= 0:
@ -767,7 +846,7 @@ class Window(QMainWindow, Ui_MainWindow):
# Save the selected PlaylistRows items ready for a later # Save the selected PlaylistRows items ready for a later
# paste # paste
self.move_source_rows = self.active_tab().get_selected_rows() self.move_source_rows = self.active_tab().get_selected_rows()
self.move_source_model = self.active_proxy_model() self.move_source_model = self.get_active_base_model()
log.debug( log.debug(
f"mark_rows_for_moving(): {self.move_source_rows=} {self.move_source_model=}" f"mark_rows_for_moving(): {self.move_source_rows=} {self.move_source_model=}"
@ -804,7 +883,7 @@ class Window(QMainWindow, Ui_MainWindow):
to_row = 0 to_row = 0
# Move rows # Move rows
self.active_proxy_model().move_rows_between_playlists( self.get_active_base_model().move_rows_between_playlists(
row_numbers, to_row, to_playlist_id row_numbers, to_row, to_playlist_id
) )
@ -834,7 +913,7 @@ class Window(QMainWindow, Ui_MainWindow):
Move unplayed rows to another playlist Move unplayed rows to another playlist
""" """
unplayed_rows = self.active_proxy_model().get_unplayed_rows() unplayed_rows = self.get_active_base_model().get_unplayed_rows()
if not unplayed_rows: if not unplayed_rows:
return return
# We can get a race condition as selected rows change while # We can get a race condition as selected rows change while
@ -917,7 +996,7 @@ class Window(QMainWindow, Ui_MainWindow):
if not self.move_source_rows or not self.move_source_model: if not self.move_source_rows or not self.move_source_model:
return return
to_playlist_model: PlaylistModel = self.active_tab().source_model to_playlist_model = self.get_active_base_model()
selected_rows = self.active_tab().get_selected_rows() selected_rows = self.active_tab().get_selected_rows()
if selected_rows: if selected_rows:
destination_row = selected_rows[0] destination_row = selected_rows[0]
@ -934,10 +1013,7 @@ class Window(QMainWindow, Ui_MainWindow):
): ):
set_next_row = destination_row set_next_row = destination_row
if ( if to_playlist_model.playlist_id == self.move_source_model.playlist_id:
to_playlist_model.playlist_id
== self.move_source_model.source_model.playlist_id
):
self.move_source_model.move_rows(self.move_source_rows, destination_row) self.move_source_model.move_rows(self.move_source_rows, destination_row)
else: else:
self.move_source_model.move_rows_between_playlists( self.move_source_model.move_rows_between_playlists(
@ -1064,6 +1140,8 @@ class Window(QMainWindow, Ui_MainWindow):
) )
else: else:
return return
if not track_info:
return
self.preview_manager.set_track_info(track_info) self.preview_manager.set_track_info(track_info)
self.preview_manager.play() self.preview_manager.play()
else: else:
@ -1096,6 +1174,8 @@ class Window(QMainWindow, Ui_MainWindow):
if self.preview_manager.is_playing(): if self.preview_manager.is_playing():
track_id = self.preview_manager.track_id track_id = self.preview_manager.track_id
row_number = self.preview_manager.row_number row_number = self.preview_manager.row_number
if not row_number:
return
with db.Session() as session: with db.Session() as session:
track = session.get(Tracks, track_id) track = session.get(Tracks, track_id)
if track: if track:
@ -1106,8 +1186,8 @@ class Window(QMainWindow, Ui_MainWindow):
track.intro = intro track.intro = intro
session.commit() session.commit()
self.preview_manager.set_intro(intro) self.preview_manager.set_intro(intro)
self.active_tab().source_model.refresh_row(session, row_number) self.get_active_base_model().refresh_row(session, row_number)
self.active_tab().source_model.invalidate_row(row_number) self.get_active_base_model().invalidate_row(row_number)
def preview_start(self) -> None: def preview_start(self) -> None:
"""Restart preview""" """Restart preview"""
@ -1294,7 +1374,7 @@ class Window(QMainWindow, Ui_MainWindow):
if row_number is None: if row_number is None:
return None return None
track_info = self.active_proxy_model().get_row_info(row_number) track_info = self.get_active_base_model().get_row_info(row_number)
if track_info is None: if track_info is None:
return None return None
@ -1385,9 +1465,7 @@ class Window(QMainWindow, Ui_MainWindow):
display_row = ( display_row = (
self.active_proxy_model() self.active_proxy_model()
.mapFromSource( .mapFromSource(
self.active_proxy_model().source_model.index( self.get_active_base_model().index(playlist_track.row_number, 0)
playlist_track.row_number, 0
)
) )
.row() .row()
) )
@ -1430,7 +1508,7 @@ class Window(QMainWindow, Ui_MainWindow):
def tab_change(self) -> None: def tab_change(self) -> None:
"""Called when active tab changed""" """Called when active tab changed"""
self.active_tab().resize_rows() self.active_tab().tab_live()
def tick_10ms(self) -> None: def tick_10ms(self) -> None:
""" """
@ -1618,64 +1696,6 @@ class Window(QMainWindow, Ui_MainWindow):
self.tabPlaylist.setTabIcon(idx, QIcon()) self.tabPlaylist.setTabIcon(idx, QIcon())
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 SelectPlaylistDialog(QDialog):
def __init__(self, parent=None, playlists=None, session=None):
super().__init__()
if playlists is None:
return
self.ui = Ui_dlgSelectPlaylist()
self.ui.setupUi(self)
self.ui.lstPlaylists.itemDoubleClicked.connect(self.list_doubleclick)
self.ui.buttonBox.accepted.connect(self.open)
self.ui.buttonBox.rejected.connect(self.close)
self.session = session
self.playlist = None
record = Settings.get_setting(self.session, "select_playlist_dialog_width")
width = record.f_int or 800
record = Settings.get_setting(self.session, "select_playlist_dialog_height")
height = record.f_int or 600
self.resize(width, height)
for playlist in playlists:
p = QListWidgetItem()
p.setText(playlist.name)
p.setData(Qt.ItemDataRole.UserRole, playlist)
self.ui.lstPlaylists.addItem(p)
def __del__(self): # review
record = Settings.get_setting(self.session, "select_playlist_dialog_height")
record.f_int = self.height()
record = Settings.get_setting(self.session, "select_playlist_dialog_width")
record.f_int = self.width()
self.session.commit()
def list_doubleclick(self, entry): # review
self.playlist = entry.data(Qt.ItemDataRole.UserRole)
self.accept()
def open(self): # review
if self.ui.lstPlaylists.selectedItems():
item = self.ui.lstPlaylists.currentItem()
self.playlist = item.data(Qt.ItemDataRole.UserRole)
self.accept()
if __name__ == "__main__": if __name__ == "__main__":
""" """
If command line arguments given, carry out requested function and If command line arguments given, carry out requested function and

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from operator import attrgetter from operator import attrgetter
from random import shuffle from random import shuffle
from typing import Optional from typing import cast, Optional
import datetime as dt import datetime as dt
import re import re
@ -1340,8 +1340,9 @@ class PlaylistModel(QAbstractTableModel):
unplayed_count += 1 unplayed_count += 1
duration += row_rat.duration duration += row_rat.duration
# Should never get here # We should only get here if there were no rows in section (ie,
return f"Error calculating subtotal ({row_rat.note})" # this was row zero)
return Config.SUBTOTAL_ON_ROW_ZERO
def selection_is_sortable(self, row_numbers: list[int]) -> bool: def selection_is_sortable(self, row_numbers: list[int]) -> bool:
""" """
@ -1662,19 +1663,14 @@ class PlaylistProxyModel(QSortFilterProxyModel):
def __init__( def __init__(
self, self,
source_model: PlaylistModel,
*args: QObject,
**kwargs: QObject,
) -> None: ) -> None:
self.source_model = source_model super().__init__()
super().__init__(*args, **kwargs)
self.setSourceModel(source_model)
# Search all columns # Search all columns
self.setFilterKeyColumn(-1) self.setFilterKeyColumn(-1)
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<PlaylistProxyModel: source_model={self.source_model}>" return f"<PlaylistProxyModel: sourceModel={self.sourceModel}>"
def filterAcceptsRow(self, source_row: int, source_parent: QModelIndex) -> bool: def filterAcceptsRow(self, source_row: int, source_parent: QModelIndex) -> bool:
""" """
@ -1684,13 +1680,13 @@ class PlaylistProxyModel(QSortFilterProxyModel):
if Config.HIDE_PLAYED_MODE != Config.HIDE_PLAYED_MODE_TRACKS: if Config.HIDE_PLAYED_MODE != Config.HIDE_PLAYED_MODE_TRACKS:
return super().filterAcceptsRow(source_row, source_parent) return super().filterAcceptsRow(source_row, source_parent)
if self.source_model.played_tracks_hidden: if self.sourceModel().played_tracks_hidden:
if self.source_model.is_played_row(source_row): if self.sourceModel().is_played_row(source_row):
# Don't hide current track # Don't hide current track
if ( if (
track_sequence.current track_sequence.current
and track_sequence.current.playlist_id and track_sequence.current.playlist_id
== self.source_model.playlist_id == self.sourceModel().playlist_id
and track_sequence.current.row_number == source_row and track_sequence.current.row_number == source_row
): ):
return True return True
@ -1698,7 +1694,8 @@ class PlaylistProxyModel(QSortFilterProxyModel):
# Don't hide next track # Don't hide next track
if ( if (
track_sequence.next track_sequence.next
and track_sequence.next.playlist_id == self.source_model.playlist_id and track_sequence.next.playlist_id
== self.sourceModel().playlist_id
and track_sequence.next.row_number == source_row and track_sequence.next.row_number == source_row
): ):
return True return True
@ -1707,7 +1704,7 @@ class PlaylistProxyModel(QSortFilterProxyModel):
if track_sequence.previous: if track_sequence.previous:
if ( if (
track_sequence.previous.playlist_id track_sequence.previous.playlist_id
!= self.source_model.playlist_id != self.sourceModel().playlist_id
or track_sequence.previous.row_number != source_row or track_sequence.previous.row_number != source_row
): ):
# This row isn't our previous track: hide it # This row isn't our previous track: hide it
@ -1731,7 +1728,7 @@ class PlaylistProxyModel(QSortFilterProxyModel):
# true next time through. # true next time through.
QTimer.singleShot( QTimer.singleShot(
Config.HIDE_AFTER_PLAYING_OFFSET + 100, Config.HIDE_AFTER_PLAYING_OFFSET + 100,
lambda: self.source_model.invalidate_row(source_row), lambda: self.sourceModel().invalidate_row(source_row),
) )
return True return True
# Next track not playing yet so don't hide previous # Next track not playing yet so don't hide previous
@ -1754,105 +1751,9 @@ class PlaylistProxyModel(QSortFilterProxyModel):
) )
) )
# ###################################### def sourceModel(self) -> PlaylistModel:
# Forward functions not handled in proxy """
# ###################################### Override sourceModel to return correct type
"""
def current_track_started(self): return cast(PlaylistModel, super().sourceModel())
return self.source_model.current_track_started()
def delete_rows(self, row_numbers: list[int]) -> None:
return self.source_model.delete_rows(row_numbers)
def get_duplicate_rows(self) -> list[int]:
return self.source_model.get_duplicate_rows()
def get_rows_duration(self, row_numbers: list[int]) -> int:
return self.source_model.get_rows_duration(row_numbers)
def get_row_info(self, row_number: int) -> RowAndTrack:
return self.source_model.get_row_info(row_number)
def get_row_track_path(self, row_number: int) -> str:
return self.source_model.get_row_track_path(row_number)
def get_unplayed_rows(self) -> list[int]:
return self.source_model.get_unplayed_rows()
def hide_played_tracks(self, hide: bool) -> None:
return self.source_model.hide_played_tracks(hide)
def insert_row(
self,
proposed_row_number: Optional[int],
track_id: Optional[int] = None,
note: str = "",
) -> None:
return self.source_model.insert_row(proposed_row_number, track_id, note)
def is_header_row(self, row_number: int) -> bool:
return self.source_model.is_header_row(row_number)
def is_played_row(self, row_number: int) -> bool:
return self.source_model.is_played_row(row_number)
def is_track_in_playlist(self, track_id: int) -> Optional[RowAndTrack]:
return self.source_model.is_track_in_playlist(track_id)
def mark_unplayed(self, row_numbers: list[int]) -> None:
return self.source_model.mark_unplayed(row_numbers)
def move_rows(self, from_rows: list[int], to_row_number: int) -> None:
return self.source_model.move_rows(from_rows, to_row_number)
def move_rows_between_playlists(
self, from_rows: list[int], to_row_number: int, to_playlist_id: int
) -> None:
return self.source_model.move_rows_between_playlists(
from_rows, to_row_number, to_playlist_id
)
def move_track_add_note(
self, new_row_number: int, existing_rat: RowAndTrack, note: str
) -> None:
return self.source_model.move_track_add_note(new_row_number, existing_rat, note)
def move_track_to_header(
self,
header_row_number: int,
existing_rat: RowAndTrack,
note: Optional[str],
) -> None:
return self.source_model.move_track_to_header(
header_row_number, existing_rat, note
)
def previous_track_ended(self) -> None:
return self.source_model.previous_track_ended()
def remove_track(self, row_number: int) -> None:
return self.source_model.remove_track(row_number)
def rescan_track(self, row_number: int) -> None:
return self.source_model.rescan_track(row_number)
def set_next_row(self, row_number: Optional[int]) -> None:
self.source_model.set_next_row(row_number)
def sort_by_artist(self, row_numbers: list[int]) -> None:
return self.source_model.sort_by_artist(row_numbers)
def sort_by_duration(self, row_numbers: list[int]) -> None:
return self.source_model.sort_by_duration(row_numbers)
def sort_by_lastplayed(self, row_numbers: list[int]) -> None:
return self.source_model.sort_by_lastplayed(row_numbers)
def sort_randomly(self, row_numbers: list[int]) -> None:
return self.source_model.sort_randomly(row_numbers)
def sort_by_title(self, row_numbers: list[int]) -> None:
return self.source_model.sort_by_title(row_numbers)
def update_track_times(self) -> None:
return self.source_model.update_track_times()

View File

@ -37,7 +37,7 @@ import line_profiler
# App imports # App imports
from audacity_controller import AudacityController from audacity_controller import AudacityController
from classes import ApplicationError, Col, MusicMusterSignals, TrackInfo from classes import ApplicationError, Col, MusicMusterSignals, Selection, TrackInfo
from config import Config from config import Config
from dialogs import TrackSelectDialog from dialogs import TrackSelectDialog
from helpers import ( from helpers import (
@ -82,9 +82,9 @@ class PlaylistDelegate(QStyledItemDelegate):
QTimer.singleShot(0, resize_func) QTimer.singleShot(0, resize_func)
def __init__(self, parent: QWidget, source_model: PlaylistModel) -> None: def __init__(self, parent: QWidget, base_model: PlaylistModel) -> None:
super().__init__(parent) super().__init__(parent)
self.source_model = source_model self.base_model = base_model
self.signals = MusicMusterSignals() self.signals = MusicMusterSignals()
self.click_position = None self.click_position = None
self.current_editor: Optional[Any] = None self.current_editor: Optional[Any] = None
@ -239,7 +239,7 @@ class PlaylistDelegate(QStyledItemDelegate):
proxy_model = index.model() proxy_model = index.model()
edit_index = proxy_model.mapToSource(index) edit_index = proxy_model.mapToSource(index)
self.original_model_data = self.source_model.data( self.original_model_data = self.base_model.data(
edit_index, Qt.ItemDataRole.EditRole edit_index, Qt.ItemDataRole.EditRole
) )
if index.column() == Col.INTRO.value: if index.column() == Col.INTRO.value:
@ -256,7 +256,7 @@ class PlaylistDelegate(QStyledItemDelegate):
value = editor.toPlainText().strip() value = editor.toPlainText().strip()
elif isinstance(editor, QDoubleSpinBox): elif isinstance(editor, QDoubleSpinBox):
value = editor.value() value = editor.value()
self.source_model.setData(edit_index, value, Qt.ItemDataRole.EditRole) self.base_model.setData(edit_index, value, Qt.ItemDataRole.EditRole)
def updateEditorGeometry(self, editor, option, index): def updateEditorGeometry(self, editor, option, index):
editor.setGeometry(option.rect) editor.setGeometry(option.rect)
@ -285,22 +285,17 @@ class PlaylistTab(QTableView):
The playlist view The playlist view
""" """
def __init__( def __init__(self, musicmuster: "Window", model: PlaylistProxyModel) -> None:
self,
musicmuster: "Window",
playlist_id: int,
) -> None:
super().__init__() super().__init__()
# Save passed settings # Save passed settings
self.musicmuster = musicmuster self.musicmuster = (
self.playlist_id = playlist_id musicmuster # TODO: do we need to keep a reference to musicmuster?
log.debug(f"PlaylistTab.__init__({playlist_id=})") )
self.playlist_id = model.sourceModel().playlist_id
# Set up widget # Set up widget
self.source_model = PlaylistModel(playlist_id) self.setItemDelegate(PlaylistDelegate(self, model.sourceModel()))
self.proxy_model = PlaylistProxyModel(self.source_model)
self.setItemDelegate(PlaylistDelegate(self, self.source_model))
self.setAlternatingRowColors(True) self.setAlternatingRowColors(True)
self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove) self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
@ -328,9 +323,8 @@ class PlaylistTab(QTableView):
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
# Load playlist rows # Singleton object to store selection
self.setModel(self.proxy_model) self.selection = Selection()
self._set_column_widths()
# Set up for Audacity # Set up for Audacity
try: try:
@ -339,6 +333,10 @@ class PlaylistTab(QTableView):
self.ac = None self.ac = None
show_warning(self.musicmuster, "Audacity error", str(e)) show_warning(self.musicmuster, "Audacity error", str(e))
# Load model, set column widths
self.setModel(model)
self._set_column_widths()
# Stretch last column *after* setting column widths which is # Stretch last column *after* setting column widths which is
# *much* faster # *much* faster
h_header = self.horizontalHeader() h_header = self.horizontalHeader()
@ -373,7 +371,7 @@ class PlaylistTab(QTableView):
# Update start times in case a start time in a note has been # Update start times in case a start time in a note has been
# edited # edited
self.source_model.update_track_times() self.get_base_model().update_track_times()
# Deselect edited line # Deselect edited line
self.clear_selection() self.clear_selection()
@ -382,36 +380,50 @@ class PlaylistTab(QTableView):
def dropEvent( def dropEvent(
self, event: Optional[QDropEvent], dummy_for_profiling: Optional[int] = None self, event: Optional[QDropEvent], dummy_for_profiling: Optional[int] = None
) -> None: ) -> None:
"""
Move dropped rows
"""
if not event: if not event:
return return
if event.source() is not self or ( if event.source() is not self or (
event.dropAction() != Qt.DropAction.MoveAction event.dropAction() != Qt.DropAction.MoveAction
and self.dragDropMode() != QAbstractItemView.DragDropMode.InternalMove and self.dragDropMode() != QAbstractItemView.DragDropMode.InternalMove
): ):
super().dropEvent(event) return super().dropEvent(event)
from_rows = self.selected_model_row_numbers() from_rows = self.selected_model_row_numbers()
to_index = self.indexAt(event.position().toPoint()) to_index = self.indexAt(event.position().toPoint())
# The drop indicator can either be immediately below a row or
# immediately above a row. There's about a 1 pixel difference,
# but we always want to drop between rows regardless of where
# drop indicator is.
if ( if (
self.dropIndicatorPosition() self.dropIndicatorPosition()
== QAbstractItemView.DropIndicatorPosition.BelowItem == QAbstractItemView.DropIndicatorPosition.BelowItem
): ):
proxy_index = self.proxy_model.createIndex( # Drop on the row below
to_index.row() + 1, next_row = to_index.row() + 1
to_index.column(), if next_row < self.model().rowCount(): # Ensure the row exists
to_index.internalId(), destination_index = to_index.siblingAtRow(next_row)
)
else: else:
proxy_index = to_index # Handle edge case where next_row is beyond the last row
to_model_row = self.proxy_model.mapToSource(proxy_index).row() destination_index = to_index
else:
destination_index = to_index
to_model_row = self.model().mapToSource(destination_index).row()
log.debug( log.debug(
f"PlaylistTab.dropEvent(): {from_rows=}, {proxy_index=}, {to_model_row=}" f"PlaylistTab.dropEvent(): {from_rows=}, {destination_index=}, {to_model_row=}"
) )
# Sanity check
base_model_row_count = self.get_base_model().rowCount()
if ( if (
0 <= min(from_rows) <= self.source_model.rowCount() 0 <= min(from_rows) <= base_model_row_count
and 0 <= max(from_rows) <= self.source_model.rowCount() and 0 <= to_model_row <= base_model_row_count
and 0 <= to_model_row <= self.source_model.rowCount()
): ):
# If we move a row to immediately under the current track, make # If we move a row to immediately under the current track, make
# that moved row the next track # that moved row the next track
@ -422,7 +434,7 @@ class PlaylistTab(QTableView):
): ):
set_next_row = to_model_row set_next_row = to_model_row
self.source_model.move_rows(from_rows, to_model_row) self.get_base_model().move_rows(from_rows, to_model_row)
# Reset drag mode to allow row selection by dragging # Reset drag mode to allow row selection by dragging
self.setDragEnabled(False) self.setDragEnabled(False)
@ -435,7 +447,7 @@ class PlaylistTab(QTableView):
# Set next row if we are immediately under current row # Set next row if we are immediately under current row
if set_next_row: if set_next_row:
self.source_model.set_next_row(set_next_row) self.get_base_model().set_next_row(set_next_row)
event.accept() event.accept()
@ -469,12 +481,14 @@ class PlaylistTab(QTableView):
""" """
selected_rows = self.get_selected_rows() selected_rows = self.get_selected_rows()
self.selection.rows = selected_rows
# If no rows are selected, we have nothing to do # If no rows are selected, we have nothing to do
if len(selected_rows) == 0: if len(selected_rows) == 0:
self.musicmuster.lblSumPlaytime.setText("") self.musicmuster.lblSumPlaytime.setText("")
else: else:
if not self.musicmuster.disable_selection_timing: if not self.musicmuster.disable_selection_timing:
selected_duration = self.source_model.get_rows_duration( selected_duration = self.get_base_model().get_rows_duration(
self.get_selected_rows() self.get_selected_rows()
) )
if selected_duration > 0: if selected_duration > 0:
@ -525,7 +539,7 @@ class PlaylistTab(QTableView):
parent=self.musicmuster, parent=self.musicmuster,
session=session, session=session,
new_row_number=model_row_number, new_row_number=model_row_number,
source_model=self.source_model, base_model=self.get_base_model(),
add_to_header=True, add_to_header=True,
) )
dlg.exec() dlg.exec()
@ -535,12 +549,12 @@ class PlaylistTab(QTableView):
"""Used to process context (right-click) menu, which is defined here""" """Used to process context (right-click) menu, which is defined here"""
self.menu.clear() self.menu.clear()
proxy_model = self.proxy_model
index = proxy_model.index(item.row(), item.column()) index = self.model().index(item.row(), item.column())
model_row_number = proxy_model.mapToSource(index).row() model_row_number = self.model().mapToSource(index).row()
base_model = self.get_base_model()
header_row = proxy_model.is_header_row(model_row_number) header_row = self.get_base_model().is_header_row(model_row_number)
track_row = not header_row track_row = not header_row
if track_sequence.current: if track_sequence.current:
this_is_current_row = model_row_number == track_sequence.current.row_number this_is_current_row = model_row_number == track_sequence.current.row_number
@ -550,7 +564,7 @@ class PlaylistTab(QTableView):
this_is_next_row = model_row_number == track_sequence.next.row_number this_is_next_row = model_row_number == track_sequence.next.row_number
else: else:
this_is_next_row = False this_is_next_row = False
track_path = self.source_model.get_row_info(model_row_number).path track_path = base_model.get_row_info(model_row_number).path
# Open/import in/from Audacity # Open/import in/from Audacity
if track_row and not this_is_current_row: if track_row and not this_is_current_row:
@ -591,7 +605,7 @@ class PlaylistTab(QTableView):
if track_row and not this_is_current_row and not this_is_next_row: if track_row and not this_is_current_row and not this_is_next_row:
self._add_context_menu( self._add_context_menu(
"Remove track from row", "Remove track from row",
lambda: proxy_model.remove_track(model_row_number), lambda: base_model.remove_track(model_row_number),
) )
# Remove comments # Remove comments
@ -605,7 +619,7 @@ class PlaylistTab(QTableView):
self.menu.addSeparator() self.menu.addSeparator()
# Mark unplayed # Mark unplayed
if track_row and proxy_model.is_played_row(model_row_number): if track_row and base_model.is_played_row(model_row_number):
self._add_context_menu( self._add_context_menu(
"Mark unplayed", "Mark unplayed",
lambda: self._mark_as_unplayed(self.get_selected_rows()), lambda: self._mark_as_unplayed(self.get_selected_rows()),
@ -624,27 +638,27 @@ class PlaylistTab(QTableView):
sort_menu = self.menu.addMenu("Sort") sort_menu = self.menu.addMenu("Sort")
self._add_context_menu( self._add_context_menu(
"by title", "by title",
lambda: proxy_model.sort_by_title(self.get_selected_rows()), lambda: base_model.sort_by_title(self.get_selected_rows()),
parent_menu=sort_menu, parent_menu=sort_menu,
) )
self._add_context_menu( self._add_context_menu(
"by artist", "by artist",
lambda: proxy_model.sort_by_artist(self.get_selected_rows()), lambda: base_model.sort_by_artist(self.get_selected_rows()),
parent_menu=sort_menu, parent_menu=sort_menu,
) )
self._add_context_menu( self._add_context_menu(
"by duration", "by duration",
lambda: proxy_model.sort_by_duration(self.get_selected_rows()), lambda: base_model.sort_by_duration(self.get_selected_rows()),
parent_menu=sort_menu, parent_menu=sort_menu,
) )
self._add_context_menu( self._add_context_menu(
"by last played", "by last played",
lambda: proxy_model.sort_by_lastplayed(self.get_selected_rows()), lambda: base_model.sort_by_lastplayed(self.get_selected_rows()),
parent_menu=sort_menu, parent_menu=sort_menu,
) )
self._add_context_menu( self._add_context_menu(
"randomly", "randomly",
lambda: proxy_model.sort_randomly(self.get_selected_rows()), lambda: base_model.sort_randomly(self.get_selected_rows()),
parent_menu=sort_menu, parent_menu=sort_menu,
) )
@ -711,7 +725,7 @@ class PlaylistTab(QTableView):
to the clipboard. Otherwise, return None. to the clipboard. Otherwise, return None.
""" """
track_path = self.source_model.get_row_info(row_number).path track_path = self.get_base_model().get_row_info(row_number).path
if not track_path: if not track_path:
return return
@ -734,7 +748,7 @@ class PlaylistTab(QTableView):
Called when track starts playing Called when track starts playing
""" """
self.source_model.current_track_started() self.get_base_model().current_track_started()
# Scroll to current section if hide mode is by section # Scroll to current section if hide mode is by section
if ( if (
self.musicmuster.hide_played_tracks self.musicmuster.hide_played_tracks
@ -766,9 +780,18 @@ class PlaylistTab(QTableView):
if not ask_yes_no("Delete rows", f"Really delete {row_count} row{plural}?"): if not ask_yes_no("Delete rows", f"Really delete {row_count} row{plural}?"):
return return
self.source_model.delete_rows(self.selected_model_row_numbers()) base_model = self.get_base_model()
base_model.delete_rows(self.selected_model_row_numbers())
self.clear_selection() self.clear_selection()
def get_base_model(self) -> PlaylistModel:
"""
Return the base model for this proxy model
"""
return cast(PlaylistModel, self.model().sourceModel())
def get_selected_row_track_info(self) -> Optional[TrackInfo]: def get_selected_row_track_info(self) -> Optional[TrackInfo]:
""" """
Return the track_id and row number of the selected Return the track_id and row number of the selected
@ -780,11 +803,13 @@ class PlaylistTab(QTableView):
if selected_row is None: if selected_row is None:
return None return None
base_model = self.get_base_model()
model_row_number = self.source_model_selected_row_number() model_row_number = self.source_model_selected_row_number()
if model_row_number is None: if model_row_number is None:
return None return None
else: else:
track_id = self.source_model.get_row_track_id(model_row_number) track_id = base_model.get_row_track_id(model_row_number)
if not track_id: if not track_id:
return None return None
else: else:
@ -808,12 +833,7 @@ class PlaylistTab(QTableView):
# items in that row selected) # items in that row selected)
result = sorted( result = sorted(
list( list(
set( set([self.model().mapToSource(a).row() for a in self.selectedIndexes()])
[
self.proxy_model.mapToSource(a).row()
for a in self.selectedIndexes()
]
)
) )
) )
@ -825,7 +845,7 @@ class PlaylistTab(QTableView):
Scroll played sections off screen Scroll played sections off screen
""" """
self.scroll_to_top(self.source_model.active_section_header()) self.scroll_to_top(self.get_base_model().active_section_header())
def _import_from_audacity(self, row_number: int) -> None: def _import_from_audacity(self, row_number: int) -> None:
""" """
@ -844,7 +864,7 @@ class PlaylistTab(QTableView):
def _info_row(self, row_number: int) -> None: def _info_row(self, row_number: int) -> None:
"""Display popup with info re row""" """Display popup with info re row"""
prd = self.source_model.get_row_info(row_number) prd = self.get_base_model().get_row_info(row_number)
if prd: if prd:
txt = ( txt = (
f"Title: {prd.title}\n" f"Title: {prd.title}\n"
@ -863,7 +883,7 @@ class PlaylistTab(QTableView):
def _mark_as_unplayed(self, row_numbers: List[int]) -> None: def _mark_as_unplayed(self, row_numbers: List[int]) -> None:
"""Mark row as unplayed""" """Mark row as unplayed"""
self.source_model.mark_unplayed(row_numbers) self.get_base_model().mark_unplayed(row_numbers)
self.clear_selection() self.clear_selection()
def _mark_for_moving(self) -> None: def _mark_for_moving(self) -> None:
@ -873,6 +893,13 @@ class PlaylistTab(QTableView):
self.musicmuster.mark_rows_for_moving() self.musicmuster.mark_rows_for_moving()
def model(self) -> PlaylistProxyModel:
"""
Override return type to keep mypy happy in this module
"""
return cast(PlaylistProxyModel, super().model())
def _move_selected_rows(self) -> None: def _move_selected_rows(self) -> None:
""" """
Move selected rows here Move selected rows here
@ -885,7 +912,7 @@ class PlaylistTab(QTableView):
Open track in passed row in Audacity Open track in passed row in Audacity
""" """
path = self.source_model.get_row_track_path(row_number) path = self.get_base_model().get_row_track_path(row_number)
if not path: if not path:
log.error(f"_open_in_audacity: can't get path for {row_number=}") log.error(f"_open_in_audacity: can't get path for {row_number=}")
return return
@ -903,7 +930,7 @@ class PlaylistTab(QTableView):
""" """
# Let the model know # Let the model know
self.source_model.previous_track_ended() self.get_base_model().previous_track_ended()
def _remove_comments(self) -> None: def _remove_comments(self) -> None:
""" """
@ -914,12 +941,12 @@ class PlaylistTab(QTableView):
if not row_numbers: if not row_numbers:
return return
self.source_model.remove_comments(row_numbers) self.get_base_model().remove_comments(row_numbers)
def _rescan(self, row_number: int) -> None: def _rescan(self, row_number: int) -> None:
"""Rescan track""" """Rescan track"""
self.source_model.rescan_track(row_number) self.get_base_model().rescan_track(row_number)
self.clear_selection() self.clear_selection()
def resize_rows(self, playlist_id: Optional[int] = None) -> None: def resize_rows(self, playlist_id: Optional[int] = None) -> None:
@ -934,7 +961,7 @@ class PlaylistTab(QTableView):
# Suggestion from phind.com # Suggestion from phind.com
def resize_row(row, count=1): def resize_row(row, count=1):
row_count = self.source_model.rowCount() row_count = self.model().rowCount()
for todo in range(count): for todo in range(count):
if row < row_count: if row < row_count:
self.resizeRowToContents(row) self.resizeRowToContents(row)
@ -953,7 +980,7 @@ class PlaylistTab(QTableView):
if row_number is None: if row_number is None:
return return
row_index = self.proxy_model.index(row_number, 0) row_index = self.model().index(row_number, 0)
self.scrollTo(row_index, QAbstractItemView.ScrollHint.PositionAtTop) self.scrollTo(row_index, QAbstractItemView.ScrollHint.PositionAtTop)
def select_duplicate_rows(self) -> None: def select_duplicate_rows(self) -> None:
@ -968,7 +995,7 @@ class PlaylistTab(QTableView):
# We need to be in MultiSelection mode # We need to be in MultiSelection mode
self.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection) self.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
# Get the duplicate rows # Get the duplicate rows
duplicate_rows = self.source_model.get_duplicate_rows() duplicate_rows = self.get_base_model().get_duplicate_rows()
# Select the rows # Select the rows
for duplicate_row in duplicate_rows: for duplicate_row in duplicate_rows:
self.selectRow(duplicate_row) self.selectRow(duplicate_row)
@ -983,7 +1010,7 @@ class PlaylistTab(QTableView):
selected_index = self._selected_row_index() selected_index = self._selected_row_index()
if selected_index is None: if selected_index is None:
return None return None
return self.proxy_model.mapToSource(selected_index).row() return self.model().mapToSource(selected_index).row()
def selected_model_row_numbers(self) -> List[int]: def selected_model_row_numbers(self) -> List[int]:
""" """
@ -994,9 +1021,8 @@ class PlaylistTab(QTableView):
selected_indexes = self._selected_row_indexes() selected_indexes = self._selected_row_indexes()
if selected_indexes is None: if selected_indexes is None:
return [] return []
if hasattr(self.proxy_model, "mapToSource"):
return [self.proxy_model.mapToSource(a).row() for a in selected_indexes] return [self.model().mapToSource(a).row() for a in selected_indexes]
return [a.row() for a in selected_indexes]
def _selected_row_index(self) -> Optional[QModelIndex]: def _selected_row_index(self) -> Optional[QModelIndex]:
""" """
@ -1053,7 +1079,7 @@ class PlaylistTab(QTableView):
log.debug(f"set_row_as_next_track() {model_row_number=}") log.debug(f"set_row_as_next_track() {model_row_number=}")
if model_row_number is None: if model_row_number is None:
return return
self.source_model.set_next_row(model_row_number) self.get_base_model().set_next_row(model_row_number)
self.clearSelection() self.clearSelection()
def _span_cells( def _span_cells(
@ -1061,17 +1087,19 @@ class PlaylistTab(QTableView):
) -> None: ) -> None:
""" """
Implement spanning of cells, initiated by signal Implement spanning of cells, initiated by signal
row and column are from the base model so we need to translate
the row into this display row
""" """
if playlist_id != self.playlist_id: if playlist_id != self.playlist_id:
return return
proxy_model = self.proxy_model base_model = self.get_base_model()
edit_index = proxy_model.mapFromSource(
self.source_model.createIndex(row, column) cell_index = self.model().mapFromSource(base_model.createIndex(row, column))
) row = cell_index.row()
row = edit_index.row() column = cell_index.column()
column = edit_index.column()
# Don't set spanning if already in place because that is seen as # Don't set spanning if already in place because that is seen as
# a change to the view and thus it refreshes the data which # a change to the view and thus it refreshes the data which
@ -1084,6 +1112,16 @@ class PlaylistTab(QTableView):
self.setSpan(row, column, rowSpan, columnSpan) self.setSpan(row, column, rowSpan, columnSpan)
def tab_live(self) -> None:
"""
Called when tab gets focus
"""
self.selection.playlist_id = self.playlist_id
self.selection.rows = self.get_selected_rows()
self.resize_rows()
def _unmark_as_next(self) -> None: def _unmark_as_next(self) -> None:
"""Rescan track""" """Rescan track"""

View File

@ -15,7 +15,11 @@ class Ui_MainWindow(object):
MainWindow.resize(1280, 857) MainWindow.resize(1280, 857)
MainWindow.setMinimumSize(QtCore.QSize(1280, 0)) MainWindow.setMinimumSize(QtCore.QSize(1280, 0))
icon = QtGui.QIcon() icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/icons/musicmuster"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon.addPixmap(
QtGui.QPixmap(":/icons/musicmuster"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
MainWindow.setWindowIcon(icon) MainWindow.setWindowIcon(icon)
MainWindow.setStyleSheet("") MainWindow.setStyleSheet("")
self.centralwidget = QtWidgets.QWidget(parent=MainWindow) self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
@ -27,39 +31,62 @@ class Ui_MainWindow(object):
self.verticalLayout_3 = QtWidgets.QVBoxLayout() self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.verticalLayout_3.setObjectName("verticalLayout_3") self.verticalLayout_3.setObjectName("verticalLayout_3")
self.previous_track_2 = QtWidgets.QLabel(parent=self.centralwidget) self.previous_track_2 = QtWidgets.QLabel(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.previous_track_2.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(
self.previous_track_2.sizePolicy().hasHeightForWidth()
)
self.previous_track_2.setSizePolicy(sizePolicy) self.previous_track_2.setSizePolicy(sizePolicy)
self.previous_track_2.setMaximumSize(QtCore.QSize(230, 16777215)) self.previous_track_2.setMaximumSize(QtCore.QSize(230, 16777215))
font = QtGui.QFont() font = QtGui.QFont()
font.setFamily("Sans") font.setFamily("Sans")
font.setPointSize(20) font.setPointSize(20)
self.previous_track_2.setFont(font) self.previous_track_2.setFont(font)
self.previous_track_2.setStyleSheet("background-color: #f8d7da;\n" self.previous_track_2.setStyleSheet(
"border: 1px solid rgb(85, 87, 83);") "background-color: #f8d7da;\n" "border: 1px solid rgb(85, 87, 83);"
self.previous_track_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) )
self.previous_track_2.setAlignment(
QtCore.Qt.AlignmentFlag.AlignRight
| QtCore.Qt.AlignmentFlag.AlignTrailing
| QtCore.Qt.AlignmentFlag.AlignVCenter
)
self.previous_track_2.setObjectName("previous_track_2") self.previous_track_2.setObjectName("previous_track_2")
self.verticalLayout_3.addWidget(self.previous_track_2) self.verticalLayout_3.addWidget(self.previous_track_2)
self.current_track_2 = QtWidgets.QLabel(parent=self.centralwidget) self.current_track_2 = QtWidgets.QLabel(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.current_track_2.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(
self.current_track_2.sizePolicy().hasHeightForWidth()
)
self.current_track_2.setSizePolicy(sizePolicy) self.current_track_2.setSizePolicy(sizePolicy)
self.current_track_2.setMaximumSize(QtCore.QSize(230, 16777215)) self.current_track_2.setMaximumSize(QtCore.QSize(230, 16777215))
font = QtGui.QFont() font = QtGui.QFont()
font.setFamily("Sans") font.setFamily("Sans")
font.setPointSize(20) font.setPointSize(20)
self.current_track_2.setFont(font) self.current_track_2.setFont(font)
self.current_track_2.setStyleSheet("background-color: #d4edda;\n" self.current_track_2.setStyleSheet(
"border: 1px solid rgb(85, 87, 83);") "background-color: #d4edda;\n" "border: 1px solid rgb(85, 87, 83);"
self.current_track_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) )
self.current_track_2.setAlignment(
QtCore.Qt.AlignmentFlag.AlignRight
| QtCore.Qt.AlignmentFlag.AlignTrailing
| QtCore.Qt.AlignmentFlag.AlignVCenter
)
self.current_track_2.setObjectName("current_track_2") self.current_track_2.setObjectName("current_track_2")
self.verticalLayout_3.addWidget(self.current_track_2) self.verticalLayout_3.addWidget(self.current_track_2)
self.next_track_2 = QtWidgets.QLabel(parent=self.centralwidget) self.next_track_2 = QtWidgets.QLabel(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.next_track_2.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.next_track_2.sizePolicy().hasHeightForWidth())
@ -69,19 +96,29 @@ class Ui_MainWindow(object):
font.setFamily("Sans") font.setFamily("Sans")
font.setPointSize(20) font.setPointSize(20)
self.next_track_2.setFont(font) self.next_track_2.setFont(font)
self.next_track_2.setStyleSheet("background-color: #fff3cd;\n" self.next_track_2.setStyleSheet(
"border: 1px solid rgb(85, 87, 83);") "background-color: #fff3cd;\n" "border: 1px solid rgb(85, 87, 83);"
self.next_track_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) )
self.next_track_2.setAlignment(
QtCore.Qt.AlignmentFlag.AlignRight
| QtCore.Qt.AlignmentFlag.AlignTrailing
| QtCore.Qt.AlignmentFlag.AlignVCenter
)
self.next_track_2.setObjectName("next_track_2") self.next_track_2.setObjectName("next_track_2")
self.verticalLayout_3.addWidget(self.next_track_2) self.verticalLayout_3.addWidget(self.next_track_2)
self.horizontalLayout_3.addLayout(self.verticalLayout_3) self.horizontalLayout_3.addLayout(self.verticalLayout_3)
self.verticalLayout = QtWidgets.QVBoxLayout() self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setObjectName("verticalLayout")
self.hdrPreviousTrack = QtWidgets.QLabel(parent=self.centralwidget) self.hdrPreviousTrack = QtWidgets.QLabel(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.hdrPreviousTrack.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(
self.hdrPreviousTrack.sizePolicy().hasHeightForWidth()
)
self.hdrPreviousTrack.setSizePolicy(sizePolicy) self.hdrPreviousTrack.setSizePolicy(sizePolicy)
self.hdrPreviousTrack.setMinimumSize(QtCore.QSize(0, 0)) self.hdrPreviousTrack.setMinimumSize(QtCore.QSize(0, 0))
self.hdrPreviousTrack.setMaximumSize(QtCore.QSize(16777215, 16777215)) self.hdrPreviousTrack.setMaximumSize(QtCore.QSize(16777215, 16777215))
@ -89,32 +126,43 @@ class Ui_MainWindow(object):
font.setFamily("Sans") font.setFamily("Sans")
font.setPointSize(20) font.setPointSize(20)
self.hdrPreviousTrack.setFont(font) self.hdrPreviousTrack.setFont(font)
self.hdrPreviousTrack.setStyleSheet("background-color: #f8d7da;\n" self.hdrPreviousTrack.setStyleSheet(
"border: 1px solid rgb(85, 87, 83);") "background-color: #f8d7da;\n" "border: 1px solid rgb(85, 87, 83);"
)
self.hdrPreviousTrack.setText("") self.hdrPreviousTrack.setText("")
self.hdrPreviousTrack.setWordWrap(False) self.hdrPreviousTrack.setWordWrap(False)
self.hdrPreviousTrack.setObjectName("hdrPreviousTrack") self.hdrPreviousTrack.setObjectName("hdrPreviousTrack")
self.verticalLayout.addWidget(self.hdrPreviousTrack) self.verticalLayout.addWidget(self.hdrPreviousTrack)
self.hdrCurrentTrack = QtWidgets.QPushButton(parent=self.centralwidget) self.hdrCurrentTrack = QtWidgets.QPushButton(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.hdrCurrentTrack.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(
self.hdrCurrentTrack.sizePolicy().hasHeightForWidth()
)
self.hdrCurrentTrack.setSizePolicy(sizePolicy) self.hdrCurrentTrack.setSizePolicy(sizePolicy)
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(20) font.setPointSize(20)
self.hdrCurrentTrack.setFont(font) self.hdrCurrentTrack.setFont(font)
self.hdrCurrentTrack.setStyleSheet("background-color: #d4edda;\n" self.hdrCurrentTrack.setStyleSheet(
"background-color: #d4edda;\n"
"border: 1px solid rgb(85, 87, 83);\n" "border: 1px solid rgb(85, 87, 83);\n"
"text-align: left;\n" "text-align: left;\n"
"padding-left: 8px;\n" "padding-left: 8px;\n"
"") ""
)
self.hdrCurrentTrack.setText("") self.hdrCurrentTrack.setText("")
self.hdrCurrentTrack.setFlat(True) self.hdrCurrentTrack.setFlat(True)
self.hdrCurrentTrack.setObjectName("hdrCurrentTrack") self.hdrCurrentTrack.setObjectName("hdrCurrentTrack")
self.verticalLayout.addWidget(self.hdrCurrentTrack) self.verticalLayout.addWidget(self.hdrCurrentTrack)
self.hdrNextTrack = QtWidgets.QPushButton(parent=self.centralwidget) self.hdrNextTrack = QtWidgets.QPushButton(parent=self.centralwidget)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(0) sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.hdrNextTrack.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(self.hdrNextTrack.sizePolicy().hasHeightForWidth())
@ -122,10 +170,12 @@ class Ui_MainWindow(object):
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(20) font.setPointSize(20)
self.hdrNextTrack.setFont(font) self.hdrNextTrack.setFont(font)
self.hdrNextTrack.setStyleSheet("background-color: #fff3cd;\n" self.hdrNextTrack.setStyleSheet(
"background-color: #fff3cd;\n"
"border: 1px solid rgb(85, 87, 83);\n" "border: 1px solid rgb(85, 87, 83);\n"
"text-align: left;\n" "text-align: left;\n"
"padding-left: 8px;") "padding-left: 8px;"
)
self.hdrNextTrack.setText("") self.hdrNextTrack.setText("")
self.hdrNextTrack.setFlat(True) self.hdrNextTrack.setFlat(True)
self.hdrNextTrack.setObjectName("hdrNextTrack") self.hdrNextTrack.setObjectName("hdrNextTrack")
@ -172,7 +222,12 @@ class Ui_MainWindow(object):
self.cartsWidget.setObjectName("cartsWidget") self.cartsWidget.setObjectName("cartsWidget")
self.horizontalLayout_Carts = QtWidgets.QHBoxLayout(self.cartsWidget) self.horizontalLayout_Carts = QtWidgets.QHBoxLayout(self.cartsWidget)
self.horizontalLayout_Carts.setObjectName("horizontalLayout_Carts") self.horizontalLayout_Carts.setObjectName("horizontalLayout_Carts")
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) spacerItem = QtWidgets.QSpacerItem(
40,
20,
QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Minimum,
)
self.horizontalLayout_Carts.addItem(spacerItem) self.horizontalLayout_Carts.addItem(spacerItem)
self.gridLayout_4.addWidget(self.cartsWidget, 2, 0, 1, 1) self.gridLayout_4.addWidget(self.cartsWidget, 2, 0, 1, 1)
self.frame_6 = QtWidgets.QFrame(parent=self.centralwidget) self.frame_6 = QtWidgets.QFrame(parent=self.centralwidget)
@ -217,7 +272,11 @@ class Ui_MainWindow(object):
self.btnPreview = QtWidgets.QPushButton(parent=self.FadeStopInfoFrame) self.btnPreview = QtWidgets.QPushButton(parent=self.FadeStopInfoFrame)
self.btnPreview.setMinimumSize(QtCore.QSize(132, 41)) self.btnPreview.setMinimumSize(QtCore.QSize(132, 41))
icon1 = QtGui.QIcon() icon1 = QtGui.QIcon()
icon1.addPixmap(QtGui.QPixmap(":/icons/headphones"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon1.addPixmap(
QtGui.QPixmap(":/icons/headphones"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.btnPreview.setIcon(icon1) self.btnPreview.setIcon(icon1)
self.btnPreview.setIconSize(QtCore.QSize(30, 30)) self.btnPreview.setIconSize(QtCore.QSize(30, 30))
self.btnPreview.setCheckable(True) self.btnPreview.setCheckable(True)
@ -239,8 +298,16 @@ class Ui_MainWindow(object):
self.btnPreviewArm.setMaximumSize(QtCore.QSize(44, 23)) self.btnPreviewArm.setMaximumSize(QtCore.QSize(44, 23))
self.btnPreviewArm.setText("") self.btnPreviewArm.setText("")
icon2 = QtGui.QIcon() icon2 = QtGui.QIcon()
icon2.addPixmap(QtGui.QPixmap(":/icons/record-button.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon2.addPixmap(
icon2.addPixmap(QtGui.QPixmap(":/icons/record-red-button.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.On) QtGui.QPixmap(":/icons/record-button.png"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
icon2.addPixmap(
QtGui.QPixmap(":/icons/record-red-button.png"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.On,
)
self.btnPreviewArm.setIcon(icon2) self.btnPreviewArm.setIcon(icon2)
self.btnPreviewArm.setCheckable(True) self.btnPreviewArm.setCheckable(True)
self.btnPreviewArm.setObjectName("btnPreviewArm") self.btnPreviewArm.setObjectName("btnPreviewArm")
@ -261,8 +328,16 @@ class Ui_MainWindow(object):
self.btnPreviewMark.setMaximumSize(QtCore.QSize(44, 23)) self.btnPreviewMark.setMaximumSize(QtCore.QSize(44, 23))
self.btnPreviewMark.setText("") self.btnPreviewMark.setText("")
icon3 = QtGui.QIcon() icon3 = QtGui.QIcon()
icon3.addPixmap(QtGui.QPixmap(":/icons/star.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.On) icon3.addPixmap(
icon3.addPixmap(QtGui.QPixmap(":/icons/star_empty.png"), QtGui.QIcon.Mode.Disabled, QtGui.QIcon.State.Off) QtGui.QPixmap(":/icons/star.png"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.On,
)
icon3.addPixmap(
QtGui.QPixmap(":/icons/star_empty.png"),
QtGui.QIcon.Mode.Disabled,
QtGui.QIcon.State.Off,
)
self.btnPreviewMark.setIcon(icon3) self.btnPreviewMark.setIcon(icon3)
self.btnPreviewMark.setObjectName("btnPreviewMark") self.btnPreviewMark.setObjectName("btnPreviewMark")
self.btnPreviewFwd = QtWidgets.QPushButton(parent=self.groupBoxIntroControls) self.btnPreviewFwd = QtWidgets.QPushButton(parent=self.groupBoxIntroControls)
@ -363,10 +438,15 @@ class Ui_MainWindow(object):
self.verticalLayout_7.addWidget(self.label_silent_timer) self.verticalLayout_7.addWidget(self.label_silent_timer)
self.horizontalLayout.addWidget(self.frame_silent) self.horizontalLayout.addWidget(self.frame_silent)
self.widgetFadeVolume = PlotWidget(parent=self.InfoFooterFrame) self.widgetFadeVolume = PlotWidget(parent=self.InfoFooterFrame)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) sizePolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Policy.Preferred,
QtWidgets.QSizePolicy.Policy.Preferred,
)
sizePolicy.setHorizontalStretch(1) sizePolicy.setHorizontalStretch(1)
sizePolicy.setVerticalStretch(0) sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.widgetFadeVolume.sizePolicy().hasHeightForWidth()) sizePolicy.setHeightForWidth(
self.widgetFadeVolume.sizePolicy().hasHeightForWidth()
)
self.widgetFadeVolume.setSizePolicy(sizePolicy) self.widgetFadeVolume.setSizePolicy(sizePolicy)
self.widgetFadeVolume.setMinimumSize(QtCore.QSize(0, 0)) self.widgetFadeVolume.setMinimumSize(QtCore.QSize(0, 0))
self.widgetFadeVolume.setObjectName("widgetFadeVolume") self.widgetFadeVolume.setObjectName("widgetFadeVolume")
@ -383,7 +463,11 @@ class Ui_MainWindow(object):
self.btnFade.setMinimumSize(QtCore.QSize(132, 32)) self.btnFade.setMinimumSize(QtCore.QSize(132, 32))
self.btnFade.setMaximumSize(QtCore.QSize(164, 16777215)) self.btnFade.setMaximumSize(QtCore.QSize(164, 16777215))
icon4 = QtGui.QIcon() icon4 = QtGui.QIcon()
icon4.addPixmap(QtGui.QPixmap(":/icons/fade"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon4.addPixmap(
QtGui.QPixmap(":/icons/fade"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.btnFade.setIcon(icon4) self.btnFade.setIcon(icon4)
self.btnFade.setIconSize(QtCore.QSize(30, 30)) self.btnFade.setIconSize(QtCore.QSize(30, 30))
self.btnFade.setObjectName("btnFade") self.btnFade.setObjectName("btnFade")
@ -391,7 +475,11 @@ class Ui_MainWindow(object):
self.btnStop = QtWidgets.QPushButton(parent=self.frame) self.btnStop = QtWidgets.QPushButton(parent=self.frame)
self.btnStop.setMinimumSize(QtCore.QSize(0, 36)) self.btnStop.setMinimumSize(QtCore.QSize(0, 36))
icon5 = QtGui.QIcon() icon5 = QtGui.QIcon()
icon5.addPixmap(QtGui.QPixmap(":/icons/stopsign"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon5.addPixmap(
QtGui.QPixmap(":/icons/stopsign"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.btnStop.setIcon(icon5) self.btnStop.setIcon(icon5)
self.btnStop.setObjectName("btnStop") self.btnStop.setObjectName("btnStop")
self.verticalLayout_5.addWidget(self.btnStop) self.verticalLayout_5.addWidget(self.btnStop)
@ -415,39 +503,71 @@ class Ui_MainWindow(object):
MainWindow.setStatusBar(self.statusbar) MainWindow.setStatusBar(self.statusbar)
self.actionPlay_next = QtGui.QAction(parent=MainWindow) self.actionPlay_next = QtGui.QAction(parent=MainWindow)
icon6 = QtGui.QIcon() icon6 = QtGui.QIcon()
icon6.addPixmap(QtGui.QPixmap("app/ui/../../../../../../.designer/backup/icon-play.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon6.addPixmap(
QtGui.QPixmap("app/ui/../../../../../../.designer/backup/icon-play.png"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionPlay_next.setIcon(icon6) self.actionPlay_next.setIcon(icon6)
self.actionPlay_next.setObjectName("actionPlay_next") self.actionPlay_next.setObjectName("actionPlay_next")
self.actionSkipToNext = QtGui.QAction(parent=MainWindow) self.actionSkipToNext = QtGui.QAction(parent=MainWindow)
icon7 = QtGui.QIcon() icon7 = QtGui.QIcon()
icon7.addPixmap(QtGui.QPixmap(":/icons/next"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon7.addPixmap(
QtGui.QPixmap(":/icons/next"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionSkipToNext.setIcon(icon7) self.actionSkipToNext.setIcon(icon7)
self.actionSkipToNext.setObjectName("actionSkipToNext") self.actionSkipToNext.setObjectName("actionSkipToNext")
self.actionInsertTrack = QtGui.QAction(parent=MainWindow) self.actionInsertTrack = QtGui.QAction(parent=MainWindow)
icon8 = QtGui.QIcon() icon8 = QtGui.QIcon()
icon8.addPixmap(QtGui.QPixmap("app/ui/../../../../../../.designer/backup/icon_search_database.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon8.addPixmap(
QtGui.QPixmap(
"app/ui/../../../../../../.designer/backup/icon_search_database.png"
),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionInsertTrack.setIcon(icon8) self.actionInsertTrack.setIcon(icon8)
self.actionInsertTrack.setObjectName("actionInsertTrack") self.actionInsertTrack.setObjectName("actionInsertTrack")
self.actionAdd_file = QtGui.QAction(parent=MainWindow) self.actionAdd_file = QtGui.QAction(parent=MainWindow)
icon9 = QtGui.QIcon() icon9 = QtGui.QIcon()
icon9.addPixmap(QtGui.QPixmap("app/ui/../../../../../../.designer/backup/icon_open_file.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon9.addPixmap(
QtGui.QPixmap(
"app/ui/../../../../../../.designer/backup/icon_open_file.png"
),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionAdd_file.setIcon(icon9) self.actionAdd_file.setIcon(icon9)
self.actionAdd_file.setObjectName("actionAdd_file") self.actionAdd_file.setObjectName("actionAdd_file")
self.actionFade = QtGui.QAction(parent=MainWindow) self.actionFade = QtGui.QAction(parent=MainWindow)
icon10 = QtGui.QIcon() icon10 = QtGui.QIcon()
icon10.addPixmap(QtGui.QPixmap("app/ui/../../../../../../.designer/backup/icon-fade.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon10.addPixmap(
QtGui.QPixmap("app/ui/../../../../../../.designer/backup/icon-fade.png"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionFade.setIcon(icon10) self.actionFade.setIcon(icon10)
self.actionFade.setObjectName("actionFade") self.actionFade.setObjectName("actionFade")
self.actionStop = QtGui.QAction(parent=MainWindow) self.actionStop = QtGui.QAction(parent=MainWindow)
icon11 = QtGui.QIcon() icon11 = QtGui.QIcon()
icon11.addPixmap(QtGui.QPixmap(":/icons/stop"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon11.addPixmap(
QtGui.QPixmap(":/icons/stop"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.actionStop.setIcon(icon11) self.actionStop.setIcon(icon11)
self.actionStop.setObjectName("actionStop") self.actionStop.setObjectName("actionStop")
self.action_Clear_selection = QtGui.QAction(parent=MainWindow) self.action_Clear_selection = QtGui.QAction(parent=MainWindow)
self.action_Clear_selection.setObjectName("action_Clear_selection") self.action_Clear_selection.setObjectName("action_Clear_selection")
self.action_Resume_previous = QtGui.QAction(parent=MainWindow) self.action_Resume_previous = QtGui.QAction(parent=MainWindow)
icon12 = QtGui.QIcon() icon12 = QtGui.QIcon()
icon12.addPixmap(QtGui.QPixmap(":/icons/previous"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) icon12.addPixmap(
QtGui.QPixmap(":/icons/previous"),
QtGui.QIcon.Mode.Normal,
QtGui.QIcon.State.Off,
)
self.action_Resume_previous.setIcon(icon12) self.action_Resume_previous.setIcon(icon12)
self.action_Resume_previous.setObjectName("action_Resume_previous") self.action_Resume_previous.setObjectName("action_Resume_previous")
self.actionE_xit = QtGui.QAction(parent=MainWindow) self.actionE_xit = QtGui.QAction(parent=MainWindow)
@ -494,7 +614,9 @@ class Ui_MainWindow(object):
self.actionImport = QtGui.QAction(parent=MainWindow) self.actionImport = QtGui.QAction(parent=MainWindow)
self.actionImport.setObjectName("actionImport") self.actionImport.setObjectName("actionImport")
self.actionDownload_CSV_of_played_tracks = QtGui.QAction(parent=MainWindow) self.actionDownload_CSV_of_played_tracks = QtGui.QAction(parent=MainWindow)
self.actionDownload_CSV_of_played_tracks.setObjectName("actionDownload_CSV_of_played_tracks") self.actionDownload_CSV_of_played_tracks.setObjectName(
"actionDownload_CSV_of_played_tracks"
)
self.actionSearch = QtGui.QAction(parent=MainWindow) self.actionSearch = QtGui.QAction(parent=MainWindow)
self.actionSearch.setObjectName("actionSearch") self.actionSearch.setObjectName("actionSearch")
self.actionInsertSectionHeader = QtGui.QAction(parent=MainWindow) self.actionInsertSectionHeader = QtGui.QAction(parent=MainWindow)
@ -522,9 +644,13 @@ class Ui_MainWindow(object):
self.actionResume = QtGui.QAction(parent=MainWindow) self.actionResume = QtGui.QAction(parent=MainWindow)
self.actionResume.setObjectName("actionResume") self.actionResume.setObjectName("actionResume")
self.actionSearch_title_in_Wikipedia = QtGui.QAction(parent=MainWindow) self.actionSearch_title_in_Wikipedia = QtGui.QAction(parent=MainWindow)
self.actionSearch_title_in_Wikipedia.setObjectName("actionSearch_title_in_Wikipedia") self.actionSearch_title_in_Wikipedia.setObjectName(
"actionSearch_title_in_Wikipedia"
)
self.actionSearch_title_in_Songfacts = QtGui.QAction(parent=MainWindow) self.actionSearch_title_in_Songfacts = QtGui.QAction(parent=MainWindow)
self.actionSearch_title_in_Songfacts.setObjectName("actionSearch_title_in_Songfacts") self.actionSearch_title_in_Songfacts.setObjectName(
"actionSearch_title_in_Songfacts"
)
self.actionSelect_duplicate_rows = QtGui.QAction(parent=MainWindow) self.actionSelect_duplicate_rows = QtGui.QAction(parent=MainWindow)
self.actionSelect_duplicate_rows.setObjectName("actionSelect_duplicate_rows") self.actionSelect_duplicate_rows.setObjectName("actionSelect_duplicate_rows")
self.actionReplace_files = QtGui.QAction(parent=MainWindow) self.actionReplace_files = QtGui.QAction(parent=MainWindow)
@ -619,38 +745,58 @@ class Ui_MainWindow(object):
self.actionFade.setShortcut(_translate("MainWindow", "Ctrl+Z")) self.actionFade.setShortcut(_translate("MainWindow", "Ctrl+Z"))
self.actionStop.setText(_translate("MainWindow", "S&top")) self.actionStop.setText(_translate("MainWindow", "S&top"))
self.actionStop.setShortcut(_translate("MainWindow", "Ctrl+Alt+S")) self.actionStop.setShortcut(_translate("MainWindow", "Ctrl+Alt+S"))
self.action_Clear_selection.setText(_translate("MainWindow", "Clear &selection")) self.action_Clear_selection.setText(
_translate("MainWindow", "Clear &selection")
)
self.action_Clear_selection.setShortcut(_translate("MainWindow", "Esc")) self.action_Clear_selection.setShortcut(_translate("MainWindow", "Esc"))
self.action_Resume_previous.setText(_translate("MainWindow", "&Resume previous")) self.action_Resume_previous.setText(
_translate("MainWindow", "&Resume previous")
)
self.actionE_xit.setText(_translate("MainWindow", "E&xit")) self.actionE_xit.setText(_translate("MainWindow", "E&xit"))
self.actionTest.setText(_translate("MainWindow", "&Test")) self.actionTest.setText(_translate("MainWindow", "&Test"))
self.actionOpenPlaylist.setText(_translate("MainWindow", "O&pen...")) self.actionOpenPlaylist.setText(_translate("MainWindow", "O&pen..."))
self.actionNewPlaylist.setText(_translate("MainWindow", "&New...")) self.actionNewPlaylist.setText(_translate("MainWindow", "&New..."))
self.actionTestFunction.setText(_translate("MainWindow", "&Test function")) self.actionTestFunction.setText(_translate("MainWindow", "&Test function"))
self.actionSkipToFade.setText(_translate("MainWindow", "&Skip to start of fade")) self.actionSkipToFade.setText(
_translate("MainWindow", "&Skip to start of fade")
)
self.actionSkipToEnd.setText(_translate("MainWindow", "Skip to &end of track")) self.actionSkipToEnd.setText(_translate("MainWindow", "Skip to &end of track"))
self.actionClosePlaylist.setText(_translate("MainWindow", "&Close")) self.actionClosePlaylist.setText(_translate("MainWindow", "&Close"))
self.actionRenamePlaylist.setText(_translate("MainWindow", "&Rename...")) self.actionRenamePlaylist.setText(_translate("MainWindow", "&Rename..."))
self.actionDeletePlaylist.setText(_translate("MainWindow", "Dele&te...")) self.actionDeletePlaylist.setText(_translate("MainWindow", "Dele&te..."))
self.actionMoveSelected.setText(_translate("MainWindow", "Mo&ve selected tracks to...")) self.actionMoveSelected.setText(
_translate("MainWindow", "Mo&ve selected tracks to...")
)
self.actionExport_playlist.setText(_translate("MainWindow", "E&xport...")) self.actionExport_playlist.setText(_translate("MainWindow", "E&xport..."))
self.actionSetNext.setText(_translate("MainWindow", "Set &next")) self.actionSetNext.setText(_translate("MainWindow", "Set &next"))
self.actionSetNext.setShortcut(_translate("MainWindow", "Ctrl+N")) self.actionSetNext.setShortcut(_translate("MainWindow", "Ctrl+N"))
self.actionSelect_next_track.setText(_translate("MainWindow", "Select next track")) self.actionSelect_next_track.setText(
_translate("MainWindow", "Select next track")
)
self.actionSelect_next_track.setShortcut(_translate("MainWindow", "J")) self.actionSelect_next_track.setShortcut(_translate("MainWindow", "J"))
self.actionSelect_previous_track.setText(_translate("MainWindow", "Select previous track")) self.actionSelect_previous_track.setText(
_translate("MainWindow", "Select previous track")
)
self.actionSelect_previous_track.setShortcut(_translate("MainWindow", "K")) self.actionSelect_previous_track.setShortcut(_translate("MainWindow", "K"))
self.actionSelect_played_tracks.setText(_translate("MainWindow", "Select played tracks")) self.actionSelect_played_tracks.setText(
self.actionMoveUnplayed.setText(_translate("MainWindow", "Move &unplayed tracks to...")) _translate("MainWindow", "Select played tracks")
)
self.actionMoveUnplayed.setText(
_translate("MainWindow", "Move &unplayed tracks to...")
)
self.actionAdd_note.setText(_translate("MainWindow", "Add note...")) self.actionAdd_note.setText(_translate("MainWindow", "Add note..."))
self.actionAdd_note.setShortcut(_translate("MainWindow", "Ctrl+T")) self.actionAdd_note.setShortcut(_translate("MainWindow", "Ctrl+T"))
self.actionEnable_controls.setText(_translate("MainWindow", "Enable controls")) self.actionEnable_controls.setText(_translate("MainWindow", "Enable controls"))
self.actionImport.setText(_translate("MainWindow", "Import track...")) self.actionImport.setText(_translate("MainWindow", "Import track..."))
self.actionImport.setShortcut(_translate("MainWindow", "Ctrl+Shift+I")) self.actionImport.setShortcut(_translate("MainWindow", "Ctrl+Shift+I"))
self.actionDownload_CSV_of_played_tracks.setText(_translate("MainWindow", "Download CSV of played tracks...")) self.actionDownload_CSV_of_played_tracks.setText(
_translate("MainWindow", "Download CSV of played tracks...")
)
self.actionSearch.setText(_translate("MainWindow", "Search...")) self.actionSearch.setText(_translate("MainWindow", "Search..."))
self.actionSearch.setShortcut(_translate("MainWindow", "/")) self.actionSearch.setShortcut(_translate("MainWindow", "/"))
self.actionInsertSectionHeader.setText(_translate("MainWindow", "Insert &section header...")) self.actionInsertSectionHeader.setText(
_translate("MainWindow", "Insert &section header...")
)
self.actionInsertSectionHeader.setShortcut(_translate("MainWindow", "Ctrl+H")) self.actionInsertSectionHeader.setShortcut(_translate("MainWindow", "Ctrl+H"))
self.actionRemove.setText(_translate("MainWindow", "&Remove track")) self.actionRemove.setText(_translate("MainWindow", "&Remove track"))
self.actionFind_next.setText(_translate("MainWindow", "Find next")) self.actionFind_next.setText(_translate("MainWindow", "Find next"))
@ -658,8 +804,12 @@ class Ui_MainWindow(object):
self.actionFind_previous.setText(_translate("MainWindow", "Find previous")) self.actionFind_previous.setText(_translate("MainWindow", "Find previous"))
self.actionFind_previous.setShortcut(_translate("MainWindow", "P")) self.actionFind_previous.setShortcut(_translate("MainWindow", "P"))
self.action_About.setText(_translate("MainWindow", "&About")) self.action_About.setText(_translate("MainWindow", "&About"))
self.actionSave_as_template.setText(_translate("MainWindow", "Save as template...")) self.actionSave_as_template.setText(
self.actionNew_from_template.setText(_translate("MainWindow", "New from template...")) _translate("MainWindow", "Save as template...")
)
self.actionNew_from_template.setText(
_translate("MainWindow", "New from template...")
)
self.actionDebug.setText(_translate("MainWindow", "Debug")) self.actionDebug.setText(_translate("MainWindow", "Debug"))
self.actionAdd_cart.setText(_translate("MainWindow", "Edit cart &1...")) self.actionAdd_cart.setText(_translate("MainWindow", "Edit cart &1..."))
self.actionMark_for_moving.setText(_translate("MainWindow", "Mark for moving")) self.actionMark_for_moving.setText(_translate("MainWindow", "Mark for moving"))
@ -668,11 +818,23 @@ class Ui_MainWindow(object):
self.actionPaste.setShortcut(_translate("MainWindow", "Ctrl+V")) self.actionPaste.setShortcut(_translate("MainWindow", "Ctrl+V"))
self.actionResume.setText(_translate("MainWindow", "Resume")) self.actionResume.setText(_translate("MainWindow", "Resume"))
self.actionResume.setShortcut(_translate("MainWindow", "Ctrl+R")) self.actionResume.setShortcut(_translate("MainWindow", "Ctrl+R"))
self.actionSearch_title_in_Wikipedia.setText(_translate("MainWindow", "Search title in Wikipedia")) self.actionSearch_title_in_Wikipedia.setText(
self.actionSearch_title_in_Wikipedia.setShortcut(_translate("MainWindow", "Ctrl+W")) _translate("MainWindow", "Search title in Wikipedia")
self.actionSearch_title_in_Songfacts.setText(_translate("MainWindow", "Search title in Songfacts")) )
self.actionSearch_title_in_Songfacts.setShortcut(_translate("MainWindow", "Ctrl+S")) self.actionSearch_title_in_Wikipedia.setShortcut(
self.actionSelect_duplicate_rows.setText(_translate("MainWindow", "Select duplicate rows...")) _translate("MainWindow", "Ctrl+W")
)
self.actionSearch_title_in_Songfacts.setText(
_translate("MainWindow", "Search title in Songfacts")
)
self.actionSearch_title_in_Songfacts.setShortcut(
_translate("MainWindow", "Ctrl+S")
)
self.actionSelect_duplicate_rows.setText(
_translate("MainWindow", "Select duplicate rows...")
)
self.actionReplace_files.setText(_translate("MainWindow", "Import files...")) self.actionReplace_files.setText(_translate("MainWindow", "Import files..."))
from infotabs import InfoTabs
from pyqtgraph import PlotWidget
from infotabs import InfoTabs # type: ignore
from pyqtgraph import PlotWidget # type: ignore