232 lines
7.3 KiB
Python
232 lines
7.3 KiB
Python
# Standard library imports
|
|
from typing import Optional
|
|
|
|
# PyQt imports
|
|
from PyQt6.QtCore import QEvent, Qt
|
|
from PyQt6.QtGui import QKeyEvent
|
|
from PyQt6.QtWidgets import (
|
|
QDialog,
|
|
QListWidgetItem,
|
|
QMainWindow,
|
|
)
|
|
|
|
# Third party imports
|
|
from sqlalchemy.orm.session import Session
|
|
|
|
# App imports
|
|
from classes import MusicMusterSignals
|
|
from helpers import (
|
|
ask_yes_no,
|
|
get_relative_date,
|
|
ms_to_mmss,
|
|
)
|
|
from log import log
|
|
from models import Settings, Tracks
|
|
from playlistmodel import PlaylistModel
|
|
from ui import dlg_TrackSelect_ui
|
|
|
|
|
|
class TrackSelectDialog(QDialog):
|
|
"""Select track from database"""
|
|
|
|
def __init__(
|
|
self,
|
|
parent: QMainWindow,
|
|
session: Session,
|
|
new_row_number: int,
|
|
base_model: PlaylistModel,
|
|
add_to_header: Optional[bool] = False,
|
|
*args: Qt.WindowType,
|
|
**kwargs: Qt.WindowType,
|
|
) -> None:
|
|
"""
|
|
Subclassed QDialog to manage track selection
|
|
"""
|
|
|
|
super().__init__(parent, *args, **kwargs)
|
|
self.session = session
|
|
self.new_row_number = new_row_number
|
|
self.base_model = base_model
|
|
self.add_to_header = add_to_header
|
|
self.ui = dlg_TrackSelect_ui.Ui_Dialog()
|
|
self.ui.setupUi(self)
|
|
self.ui.btnAdd.clicked.connect(self.add_selected)
|
|
self.ui.btnAddClose.clicked.connect(self.add_selected_and_close)
|
|
self.ui.btnClose.clicked.connect(self.close)
|
|
self.ui.matchList.itemDoubleClicked.connect(self.add_selected)
|
|
self.ui.matchList.itemSelectionChanged.connect(self.selection_changed)
|
|
self.ui.radioTitle.toggled.connect(self.title_artist_toggle)
|
|
self.ui.searchString.textEdited.connect(self.chars_typed)
|
|
self.track: Optional[Tracks] = None
|
|
self.signals = MusicMusterSignals()
|
|
|
|
record = Settings.get_setting(self.session, "dbdialog_width")
|
|
width = record.f_int or 800
|
|
record = Settings.get_setting(self.session, "dbdialog_height")
|
|
height = record.f_int or 600
|
|
self.resize(width, height)
|
|
|
|
if add_to_header:
|
|
self.ui.lblNote.setVisible(False)
|
|
self.ui.txtNote.setVisible(False)
|
|
|
|
def add_selected(self) -> None:
|
|
"""Handle Add button"""
|
|
|
|
track = None
|
|
|
|
if self.ui.matchList.selectedItems():
|
|
item = self.ui.matchList.currentItem()
|
|
if item:
|
|
track = item.data(Qt.ItemDataRole.UserRole)
|
|
|
|
note = self.ui.txtNote.text()
|
|
|
|
if not (track or note):
|
|
return
|
|
|
|
track_id = None
|
|
if track:
|
|
track_id = track.id
|
|
|
|
if note and not track_id:
|
|
self.base_model.insert_row(self.new_row_number, track_id, note)
|
|
self.ui.txtNote.clear()
|
|
self.new_row_number += 1
|
|
return
|
|
|
|
self.ui.txtNote.clear()
|
|
self.select_searchtext()
|
|
|
|
if track_id is None:
|
|
log.error("track_id is None and should not be")
|
|
return
|
|
|
|
# Check whether track is already in playlist
|
|
move_existing = False
|
|
existing_prd = self.base_model.is_track_in_playlist(track_id)
|
|
if existing_prd is not None:
|
|
if ask_yes_no(
|
|
"Duplicate row",
|
|
"Track already in playlist. " "Move to new location?",
|
|
default_yes=True,
|
|
):
|
|
move_existing = True
|
|
|
|
if self.add_to_header:
|
|
if move_existing and existing_prd: # "and existing_prd" for mypy's benefit
|
|
self.base_model.move_track_to_header(
|
|
self.new_row_number, existing_prd, note
|
|
)
|
|
else:
|
|
self.base_model.add_track_to_header(self.new_row_number, track_id)
|
|
# Close dialog - we can only add one track to a header
|
|
self.accept()
|
|
else:
|
|
# Adding a new track row
|
|
if move_existing and existing_prd: # "and existing_prd" for mypy's benefit
|
|
self.base_model.move_track_add_note(
|
|
self.new_row_number, existing_prd, note
|
|
)
|
|
else:
|
|
self.base_model.insert_row(self.new_row_number, track_id, note)
|
|
|
|
self.new_row_number += 1
|
|
|
|
def add_selected_and_close(self) -> None:
|
|
"""Handle Add and Close button"""
|
|
|
|
self.add_selected()
|
|
self.accept()
|
|
|
|
def chars_typed(self, s: str) -> None:
|
|
"""Handle text typed in search box"""
|
|
|
|
self.ui.matchList.clear()
|
|
if len(s) > 0:
|
|
if s.startswith("a/") and len(s) > 2:
|
|
matches = Tracks.search_artists(self.session, "%" + s[2:])
|
|
elif self.ui.radioTitle.isChecked():
|
|
matches = Tracks.search_titles(self.session, "%" + s)
|
|
else:
|
|
matches = Tracks.search_artists(self.session, "%" + s)
|
|
if matches:
|
|
for track in matches:
|
|
last_played = None
|
|
last_playdate = max(
|
|
track.playdates, key=lambda p: p.lastplayed, default=None
|
|
)
|
|
if last_playdate:
|
|
last_played = last_playdate.lastplayed
|
|
t = QListWidgetItem()
|
|
track_text = (
|
|
f"{track.title} - {track.artist} "
|
|
f"[{ms_to_mmss(track.duration)}] "
|
|
f"({get_relative_date(last_played)})"
|
|
)
|
|
t.setText(track_text)
|
|
t.setData(Qt.ItemDataRole.UserRole, track)
|
|
self.ui.matchList.addItem(t)
|
|
|
|
def closeEvent(self, event: Optional[QEvent]) -> None:
|
|
"""
|
|
Override close and save dialog coordinates
|
|
"""
|
|
|
|
if not event:
|
|
return
|
|
|
|
record = Settings.get_setting(self.session, "dbdialog_height")
|
|
record.f_int = self.height()
|
|
|
|
record = Settings.get_setting(self.session, "dbdialog_width")
|
|
record.f_int = self.width()
|
|
|
|
self.session.commit()
|
|
|
|
event.accept()
|
|
|
|
def keyPressEvent(self, event: QKeyEvent | None) -> None:
|
|
"""
|
|
Clear selection on ESC if there is one
|
|
"""
|
|
|
|
if event and event.key() == Qt.Key.Key_Escape:
|
|
if self.ui.matchList.selectedItems():
|
|
self.ui.matchList.clearSelection()
|
|
return
|
|
|
|
super(TrackSelectDialog, self).keyPressEvent(event)
|
|
|
|
def select_searchtext(self) -> None:
|
|
"""Select the searchbox"""
|
|
|
|
self.ui.searchString.selectAll()
|
|
self.ui.searchString.setFocus()
|
|
|
|
def selection_changed(self) -> None:
|
|
"""Display selected track path in dialog box"""
|
|
|
|
if not self.ui.matchList.selectedItems():
|
|
return
|
|
|
|
item = self.ui.matchList.currentItem()
|
|
track = item.data(Qt.ItemDataRole.UserRole)
|
|
last_playdate = max(track.playdates, key=lambda p: p.lastplayed, default=None)
|
|
if last_playdate:
|
|
last_played = last_playdate.lastplayed
|
|
else:
|
|
last_played = None
|
|
path_text = f"{track.path} ({get_relative_date(last_played)})"
|
|
|
|
self.ui.dbPath.setText(path_text)
|
|
|
|
def title_artist_toggle(self) -> None:
|
|
"""
|
|
Handle switching between searching for artists and searching for
|
|
titles
|
|
"""
|
|
|
|
# Logic is handled already in chars_typed(), so just call that.
|
|
self.chars_typed(self.ui.searchString.text())
|