WIP V3: Add track to header row implemented
This commit is contained in:
parent
9554336860
commit
fedcfc3eea
@ -14,7 +14,8 @@ class MusicMusterSignals(QObject):
|
|||||||
https://refactoring.guru/design-patterns/singleton/python/example#example-0
|
https://refactoring.guru/design-patterns/singleton/python/example#example-0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
add_track_to_header_signal = pyqtSignal(int, int, int)
|
||||||
|
add_track_to_playlist_signal = pyqtSignal(int, int, int, str)
|
||||||
enable_escape_signal = pyqtSignal(bool)
|
enable_escape_signal = pyqtSignal(bool)
|
||||||
set_next_track_signal = pyqtSignal(int, int)
|
set_next_track_signal = pyqtSignal(int, int)
|
||||||
span_cells_signal = pyqtSignal(int, int, int, int)
|
span_cells_signal = pyqtSignal(int, int, int, int)
|
||||||
add_track_to_playlist_signal = pyqtSignal(int, int, int, str)
|
|
||||||
|
|||||||
175
app/dialogs.py
Normal file
175
app/dialogs.py
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
from PyQt6.QtCore import QEvent, Qt
|
||||||
|
from PyQt6.QtWidgets import QDialog, QListWidgetItem
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import helpers
|
||||||
|
|
||||||
|
from datastructures import MusicMusterSignals
|
||||||
|
from dbconfig import scoped_session
|
||||||
|
from models import Settings, Tracks
|
||||||
|
from ui.dlg_TrackSelect_ui import Ui_Dialog # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
class TrackSelectDialog(QDialog):
|
||||||
|
"""Select track from database"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
session: scoped_session,
|
||||||
|
new_row_number: int,
|
||||||
|
playlist_id: int,
|
||||||
|
add_to_header: Optional[bool] = False,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Subclassed QDialog to manage track selection
|
||||||
|
"""
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.session = session
|
||||||
|
self.new_row_number = new_row_number
|
||||||
|
self.playlist_id = playlist_id
|
||||||
|
self.add_to_header = add_to_header
|
||||||
|
self.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_int_settings(self.session, "dbdialog_width")
|
||||||
|
width = record.f_int or 800
|
||||||
|
record = Settings.get_int_settings(self.session, "dbdialog_height")
|
||||||
|
height = record.f_int or 600
|
||||||
|
self.resize(width, height)
|
||||||
|
|
||||||
|
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 note and not track:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.ui.txtNote.clear()
|
||||||
|
self.select_searchtext()
|
||||||
|
|
||||||
|
track_id = None
|
||||||
|
if track:
|
||||||
|
track_id = track.id
|
||||||
|
if self.add_to_header:
|
||||||
|
self.signals.add_track_to_header_signal.emit(
|
||||||
|
self.playlist_id, self.new_row_number, track_id
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.signals.add_track_to_playlist_signal.emit(
|
||||||
|
self.playlist_id, self.new_row_number, track_id, note
|
||||||
|
)
|
||||||
|
|
||||||
|
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 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"[{helpers.ms_to_mmss(track.duration)}] "
|
||||||
|
f"({helpers.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_int_settings(self.session, "dbdialog_height")
|
||||||
|
if record.f_int != self.height():
|
||||||
|
record.update(self.session, {"f_int": self.height()})
|
||||||
|
|
||||||
|
record = Settings.get_int_settings(self.session, "dbdialog_width")
|
||||||
|
if record.f_int != self.width():
|
||||||
|
record.update(self.session, {"f_int": self.width()})
|
||||||
|
|
||||||
|
event.accept()
|
||||||
|
|
||||||
|
def keyPressEvent(self, event):
|
||||||
|
"""
|
||||||
|
Clear selection on ESC if there is one
|
||||||
|
"""
|
||||||
|
|
||||||
|
if 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} ({helpers.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())
|
||||||
@ -66,12 +66,12 @@ from dbconfig import (
|
|||||||
import helpers
|
import helpers
|
||||||
import icons_rc # noqa F401
|
import icons_rc # noqa F401
|
||||||
import music
|
import music
|
||||||
|
from dialogs import TrackSelectDialog
|
||||||
from models import Base, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
|
from models import Base, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
|
||||||
from config import Config
|
from config import Config
|
||||||
from datastructures import MusicMusterSignals
|
from datastructures import MusicMusterSignals
|
||||||
from playlists import PlaylistTab
|
from playlists import PlaylistTab
|
||||||
from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore
|
from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore
|
||||||
from ui.dlg_TrackSelect_ui import Ui_Dialog # type: ignore
|
|
||||||
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
|
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
|
||||||
from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
|
from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
|
||||||
from ui.main_window_ui import Ui_MainWindow # type: ignore
|
from ui.main_window_ui import Ui_MainWindow # type: ignore
|
||||||
@ -1901,164 +1901,6 @@ class CartDialog(QDialog):
|
|||||||
self.ui.lblPath.setText(self.path)
|
self.ui.lblPath.setText(self.path)
|
||||||
|
|
||||||
|
|
||||||
class TrackSelectDialog(QDialog):
|
|
||||||
"""Select track from database"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
session: scoped_session,
|
|
||||||
new_row_number: int,
|
|
||||||
playlist_id: int,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Subclassed QDialog to manage track selection
|
|
||||||
"""
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.session = session
|
|
||||||
self.new_row_number = new_row_number
|
|
||||||
self.playlist_id = playlist_id
|
|
||||||
self.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_int_settings(self.session, "dbdialog_width")
|
|
||||||
width = record.f_int or 800
|
|
||||||
record = Settings.get_int_settings(self.session, "dbdialog_height")
|
|
||||||
height = record.f_int or 600
|
|
||||||
self.resize(width, height)
|
|
||||||
|
|
||||||
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 note and not track:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.ui.txtNote.clear()
|
|
||||||
self.select_searchtext()
|
|
||||||
|
|
||||||
track_id = None
|
|
||||||
if track:
|
|
||||||
track_id = track.id
|
|
||||||
self.signals.add_track_to_playlist_signal.emit(
|
|
||||||
self.playlist_id, self.new_row_number, track_id, note
|
|
||||||
)
|
|
||||||
|
|
||||||
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 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"[{helpers.ms_to_mmss(track.duration)}] "
|
|
||||||
f"({helpers.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_int_settings(self.session, "dbdialog_height")
|
|
||||||
if record.f_int != self.height():
|
|
||||||
record.update(self.session, {"f_int": self.height()})
|
|
||||||
|
|
||||||
record = Settings.get_int_settings(self.session, "dbdialog_width")
|
|
||||||
if record.f_int != self.width():
|
|
||||||
record.update(self.session, {"f_int": self.width()})
|
|
||||||
|
|
||||||
event.accept()
|
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
"""
|
|
||||||
Clear selection on ESC if there is one
|
|
||||||
"""
|
|
||||||
|
|
||||||
if 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} ({helpers.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())
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadCSV(QDialog):
|
class DownloadCSV(QDialog):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@ -21,6 +21,7 @@ from dbconfig import scoped_session, Session
|
|||||||
from helpers import (
|
from helpers import (
|
||||||
file_is_unreadable,
|
file_is_unreadable,
|
||||||
)
|
)
|
||||||
|
from log import log
|
||||||
from models import PlaylistRows, Tracks
|
from models import PlaylistRows, Tracks
|
||||||
|
|
||||||
|
|
||||||
@ -104,6 +105,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
self.signals = MusicMusterSignals()
|
self.signals = MusicMusterSignals()
|
||||||
|
|
||||||
self.signals.add_track_to_playlist_signal.connect(self.add_track)
|
self.signals.add_track_to_playlist_signal.connect(self.add_track)
|
||||||
|
self.signals.add_track_to_header_signal.connect(self.add_track_to_header)
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
self.refresh_data(session)
|
self.refresh_data(session)
|
||||||
@ -138,6 +140,49 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
# No track, no note, no point
|
# No track, no note, no point
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def add_track_to_header(
|
||||||
|
self,
|
||||||
|
playlist_id: int,
|
||||||
|
row_number: int,
|
||||||
|
track_id: int,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Add track to existing header row if it's for our playlist
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Ignore if it's not for us
|
||||||
|
if playlist_id != self.playlist_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get existing row
|
||||||
|
try:
|
||||||
|
prd = self.playlist_rows[row_number]
|
||||||
|
except KeyError:
|
||||||
|
log.error(
|
||||||
|
f"KeyError in PlaylistModel:add_track_to_header ({playlist_id=}, "
|
||||||
|
f"{row_number=}, {track_id=}, {len(self.playlist_rows)=}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if prd.path:
|
||||||
|
log.error(
|
||||||
|
f"Error in PlaylistModel:add_track_to_header ({prd=}, "
|
||||||
|
"Header row already has track associated"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
with Session() as session:
|
||||||
|
plr = session.get(PlaylistRows, prd.plrid)
|
||||||
|
if plr:
|
||||||
|
# Add track to PlaylistRows
|
||||||
|
plr.track_id = track_id
|
||||||
|
# Reset header row spanning
|
||||||
|
self.signals.span_cells_signal.emit(
|
||||||
|
row_number, HEADER_NOTES_COLUMN, 1, 1
|
||||||
|
)
|
||||||
|
# Update local copy
|
||||||
|
self.refresh_row(session, row_number)
|
||||||
|
# Repaint row
|
||||||
|
self.invalidate_row(row_number)
|
||||||
|
|
||||||
def background_role(self, row: int, column: int, prd: PlaylistRowData) -> QBrush:
|
def background_role(self, row: int, column: int, prd: PlaylistRowData) -> QBrush:
|
||||||
"""Return background setting"""
|
"""Return background setting"""
|
||||||
|
|
||||||
@ -321,6 +366,13 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
return QVariant()
|
return QVariant()
|
||||||
|
|
||||||
|
def is_header_row(self, row_number: int) -> bool:
|
||||||
|
"""
|
||||||
|
Return True if row is a header row, else False
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.playlist_rows[row_number].path == ""
|
||||||
|
|
||||||
def insert_header_row(self, row_number: Optional[int], text: str) -> None:
|
def insert_header_row(self, row_number: Optional[int], text: str) -> None:
|
||||||
"""
|
"""
|
||||||
Insert a header row.
|
Insert a header row.
|
||||||
|
|||||||
223
app/playlists.py
223
app/playlists.py
@ -7,7 +7,7 @@ import threading
|
|||||||
import obsws_python as obs # type: ignore
|
import obsws_python as obs # type: ignore
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, cast, List, Optional, Tuple, TYPE_CHECKING
|
from typing import Any, Callable, cast, List, Optional, Tuple, TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt6.QtCore import (
|
from PyQt6.QtCore import (
|
||||||
QEvent,
|
QEvent,
|
||||||
@ -22,7 +22,7 @@ from PyQt6.QtWidgets import (
|
|||||||
QAbstractItemView,
|
QAbstractItemView,
|
||||||
QApplication,
|
QApplication,
|
||||||
QHeaderView,
|
QHeaderView,
|
||||||
# QMenu,
|
QMenu,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
QPlainTextEdit,
|
QPlainTextEdit,
|
||||||
QStyledItemDelegate,
|
QStyledItemDelegate,
|
||||||
@ -37,6 +37,7 @@ from PyQt6.QtWidgets import (
|
|||||||
|
|
||||||
from datastructures import MusicMusterSignals
|
from datastructures import MusicMusterSignals
|
||||||
from dbconfig import Session, scoped_session
|
from dbconfig import Session, scoped_session
|
||||||
|
from dialogs import TrackSelectDialog
|
||||||
from config import Config
|
from config import Config
|
||||||
from helpers import (
|
from helpers import (
|
||||||
ask_yes_no,
|
ask_yes_no,
|
||||||
@ -48,11 +49,11 @@ from helpers import (
|
|||||||
set_track_metadata,
|
set_track_metadata,
|
||||||
)
|
)
|
||||||
from log import log
|
from log import log
|
||||||
from models import Playlists, PlaylistRows, Settings, Tracks, NoteColours
|
from models import PlaylistRows, Settings, Tracks, NoteColours
|
||||||
from playlistmodel import PlaylistModel
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from musicmuster import Window, MusicMusterSignals
|
from musicmuster import Window
|
||||||
|
from playlistmodel import PlaylistModel
|
||||||
|
|
||||||
HEADER_NOTES_COLUMN = 2
|
HEADER_NOTES_COLUMN = 2
|
||||||
|
|
||||||
@ -78,7 +79,8 @@ class EscapeDelegate(QStyledItemDelegate):
|
|||||||
Intercept createEditor call and make row just a little bit taller
|
Intercept createEditor call and make row just a little bit taller
|
||||||
"""
|
"""
|
||||||
|
|
||||||
signals.enable_escape_signal.emit(False)
|
self.signals = MusicMusterSignals()
|
||||||
|
self.signals.enable_escape_signal.emit(False)
|
||||||
if isinstance(self.parent(), PlaylistTab):
|
if isinstance(self.parent(), PlaylistTab):
|
||||||
p = cast(PlaylistTab, self.parent())
|
p = cast(PlaylistTab, self.parent())
|
||||||
if isinstance(index.data(), str):
|
if isinstance(index.data(), str):
|
||||||
@ -111,7 +113,7 @@ class EscapeDelegate(QStyledItemDelegate):
|
|||||||
return True
|
return True
|
||||||
elif key_event.key() == Qt.Key.Key_Escape:
|
elif key_event.key() == Qt.Key.Key_Escape:
|
||||||
discard_edits = QMessageBox.question(
|
discard_edits = QMessageBox.question(
|
||||||
self.parent(), "Abandon edit", "Discard changes?"
|
cast(QWidget, self), "Abandon edit", "Discard changes?"
|
||||||
)
|
)
|
||||||
if discard_edits == QMessageBox.StandardButton.Yes:
|
if discard_edits == QMessageBox.StandardButton.Yes:
|
||||||
self.closeEditor.emit(editor)
|
self.closeEditor.emit(editor)
|
||||||
@ -134,8 +136,7 @@ class PlaylistStyle(QProxyStyle):
|
|||||||
def drawPrimitive(self, element, option, painter, widget=None):
|
def drawPrimitive(self, element, option, painter, widget=None):
|
||||||
"""
|
"""
|
||||||
Draw a line across the entire row rather than just the column
|
Draw a line across the entire row rather than just the column
|
||||||
we're hovering over. This may not always work depending on global
|
we're hovering over.
|
||||||
style - for instance I think it won't work on OSX.
|
|
||||||
"""
|
"""
|
||||||
if (
|
if (
|
||||||
element == QStyle.PrimitiveElement.PE_IndicatorItemViewItemDrop
|
element == QStyle.PrimitiveElement.PE_IndicatorItemViewItemDrop
|
||||||
@ -178,9 +179,9 @@ class PlaylistTab(QTableView):
|
|||||||
# rows selected
|
# rows selected
|
||||||
self.setDragEnabled(True)
|
self.setDragEnabled(True)
|
||||||
# Prepare for context menu
|
# Prepare for context menu
|
||||||
# self.menu = QMenu()
|
self.menu = QMenu()
|
||||||
# self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
# self.customContextMenuRequested.connect(self._context_menu)
|
self.customContextMenuRequested.connect(self._context_menu)
|
||||||
|
|
||||||
# Connect signals
|
# Connect signals
|
||||||
# This dancing is to satisfy mypy
|
# This dancing is to satisfy mypy
|
||||||
@ -291,27 +292,27 @@ class PlaylistTab(QTableView):
|
|||||||
|
|
||||||
# self.hide_or_show_played_tracks()
|
# self.hide_or_show_played_tracks()
|
||||||
|
|
||||||
# def _add_context_menu(
|
def _add_context_menu(
|
||||||
# self,
|
self,
|
||||||
# text: str,
|
text: str,
|
||||||
# action: Callable,
|
action: Callable,
|
||||||
# disabled: bool = False,
|
disabled: bool = False,
|
||||||
# parent_menu: Optional[QMenu] = None,
|
parent_menu: Optional[QMenu] = None,
|
||||||
# ) -> Optional[QAction]:
|
) -> Optional[QAction]:
|
||||||
# """
|
"""
|
||||||
# Add item to self.menu
|
Add item to self.menu
|
||||||
# """
|
"""
|
||||||
|
|
||||||
# if parent_menu is None:
|
if parent_menu is None:
|
||||||
# parent_menu = self.menu
|
parent_menu = self.menu
|
||||||
|
|
||||||
# menu_item = parent_menu.addAction(text)
|
menu_item = parent_menu.addAction(text)
|
||||||
# if not menu_item:
|
if not menu_item:
|
||||||
# return None
|
return None
|
||||||
# menu_item.setDisabled(disabled)
|
menu_item.setDisabled(disabled)
|
||||||
# menu_item.triggered.connect(action)
|
menu_item.triggered.connect(action)
|
||||||
|
|
||||||
# return menu_item
|
return menu_item
|
||||||
|
|
||||||
# def mouseReleaseEvent(self, event):
|
# def mouseReleaseEvent(self, event):
|
||||||
# """
|
# """
|
||||||
@ -1033,106 +1034,90 @@ class PlaylistTab(QTableView):
|
|||||||
"""Add a track to a section header making it a normal track row"""
|
"""Add a track to a section header making it a normal track row"""
|
||||||
|
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
# Add track to playlist row
|
dlg = TrackSelectDialog(
|
||||||
plr = self._get_row_plr(session, row_number)
|
session=session,
|
||||||
if not plr:
|
new_row_number=row_number,
|
||||||
return
|
playlist_id=self.playlist_id,
|
||||||
|
add_to_header=True,
|
||||||
|
)
|
||||||
|
dlg.exec()
|
||||||
|
|
||||||
# Don't add track if there's already a track there
|
def _build_context_menu(self, item: QTableWidgetItem) -> None:
|
||||||
if plr.track_id is not None:
|
"""Used to process context (right-click) menu, which is defined here"""
|
||||||
return
|
|
||||||
|
|
||||||
# Get track
|
self.menu.clear()
|
||||||
track = self.musicmuster.get_one_track(session)
|
row_number = item.row()
|
||||||
if not track:
|
# track_id = self._get_row_track_id(row_number)
|
||||||
return
|
# track_row = bool(track_id)
|
||||||
plr.track_id = track.id
|
header_row = False
|
||||||
|
model = cast(PlaylistModel, self.model())
|
||||||
|
if model:
|
||||||
|
|
||||||
# Reset row span
|
header_row = model.is_header_row(row_number)
|
||||||
self.setSpan(row_number, HEADER_NOTES_COLUMN, 1, 1)
|
# current = row_number == self._get_current_track_row_number()
|
||||||
|
# next_row = row_number == self._get_next_track_row_number()
|
||||||
|
|
||||||
# Update attributes of row
|
# # Play with mplayer
|
||||||
self._update_row_track_info(session, row_number, track)
|
# if track_row and not current:
|
||||||
self._set_row_bold(row_number)
|
# self._add_context_menu(
|
||||||
self._set_row_colour_default(row_number)
|
# "Play with mplayer", lambda: self._mplayer_play(row_number)
|
||||||
self._set_row_note_text(session, row_number, plr.note)
|
# )
|
||||||
self.clear_selection()
|
|
||||||
self.save_playlist(session)
|
|
||||||
# Update times once display updated
|
|
||||||
self._update_start_end_times(session)
|
|
||||||
|
|
||||||
# def _build_context_menu(self, item: QTableWidgetItem) -> None:
|
# # Paste
|
||||||
# """Used to process context (right-click) menu, which is defined here"""
|
# self._add_context_menu(
|
||||||
|
# "Paste",
|
||||||
|
# lambda: self.musicmuster.paste_rows(),
|
||||||
|
# self.musicmuster.selected_plrs is None,
|
||||||
|
# )
|
||||||
|
|
||||||
# self.menu.clear()
|
# # Open in Audacity
|
||||||
# row_number = item.row()
|
# if track_row and not current:
|
||||||
# track_id = self._get_row_track_id(row_number)
|
# self._add_context_menu(
|
||||||
# track_row = bool(track_id)
|
# "Open in Audacity", lambda: self._open_in_audacity(row_number)
|
||||||
# header_row = not track_row
|
# )
|
||||||
# current = row_number == self._get_current_track_row_number()
|
|
||||||
# next_row = row_number == self._get_next_track_row_number()
|
|
||||||
|
|
||||||
# # Play with mplayer
|
# # Rescan
|
||||||
# if track_row and not current:
|
# if track_row and not current:
|
||||||
# self._add_context_menu(
|
# self._add_context_menu(
|
||||||
# "Play with mplayer", lambda: self._mplayer_play(row_number)
|
# "Rescan track", lambda: self._rescan(row_number, track_id)
|
||||||
# )
|
# )
|
||||||
|
|
||||||
# # Paste
|
# # ----------------------
|
||||||
# self._add_context_menu(
|
self.menu.addSeparator()
|
||||||
# "Paste",
|
|
||||||
# lambda: self.musicmuster.paste_rows(),
|
|
||||||
# self.musicmuster.selected_plrs is None,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Open in Audacity
|
# # Remove row
|
||||||
# if track_row and not current:
|
# if not current and not next_row:
|
||||||
# self._add_context_menu(
|
# self._add_context_menu("Delete row", self._delete_rows)
|
||||||
# "Open in Audacity", lambda: self._open_in_audacity(row_number)
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Rescan
|
# # Move to playlist
|
||||||
# if track_row and not current:
|
# if not current and not next_row:
|
||||||
# self._add_context_menu(
|
# self._add_context_menu(
|
||||||
# "Rescan track", lambda: self._rescan(row_number, track_id)
|
# "Move to playlist...", self.musicmuster.move_selected
|
||||||
# )
|
# )
|
||||||
|
|
||||||
# # ----------------------
|
# # ----------------------
|
||||||
# self.menu.addSeparator()
|
# self.menu.addSeparator()
|
||||||
|
|
||||||
# # Remove row
|
# # Remove track from row
|
||||||
# if not current and not next_row:
|
# if track_row and not current and not next_row:
|
||||||
# self._add_context_menu("Delete row", self._delete_rows)
|
# self._add_context_menu(
|
||||||
|
# "Remove track from row", lambda: self._remove_track(row_number)
|
||||||
|
# )
|
||||||
|
|
||||||
# # Move to playlist
|
# Add track to section header (ie, make this a track row)
|
||||||
# if not current and not next_row:
|
if header_row:
|
||||||
# self._add_context_menu(
|
self._add_context_menu("Add a track", lambda: self._add_track(row_number))
|
||||||
# "Move to playlist...", self.musicmuster.move_selected
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # ----------------------
|
# # Mark unplayed
|
||||||
# self.menu.addSeparator()
|
# if self._get_row_userdata(row_number, self.PLAYED):
|
||||||
|
# self._add_context_menu("Mark unplayed", self._mark_unplayed)
|
||||||
|
|
||||||
# # Remove track from row
|
# # Unmark as next
|
||||||
# if track_row and not current and not next_row:
|
# if next_row:
|
||||||
# self._add_context_menu(
|
# self._add_context_menu("Unmark as next track", self.clear_next)
|
||||||
# "Remove track from row", lambda: self._remove_track(row_number)
|
|
||||||
# )
|
|
||||||
|
|
||||||
# # Add track to section header (ie, make this a track row)
|
# # ----------------------
|
||||||
# if header_row:
|
self.menu.addSeparator()
|
||||||
# self._add_context_menu("Add a track", lambda: self._add_track(row_number))
|
|
||||||
|
|
||||||
# # Mark unplayed
|
|
||||||
# if self._get_row_userdata(row_number, self.PLAYED):
|
|
||||||
# self._add_context_menu("Mark unplayed", self._mark_unplayed)
|
|
||||||
|
|
||||||
# # Unmark as next
|
|
||||||
# if next_row:
|
|
||||||
# self._add_context_menu("Unmark as next track", self.clear_next)
|
|
||||||
|
|
||||||
# # ----------------------
|
|
||||||
# self.menu.addSeparator()
|
|
||||||
|
|
||||||
# # Sort
|
# # Sort
|
||||||
# sort_menu = self.menu.addMenu("Sort")
|
# sort_menu = self.menu.addMenu("Sort")
|
||||||
@ -1198,12 +1183,12 @@ class PlaylistTab(QTableView):
|
|||||||
record = Settings.get_int_settings(session, attr_name)
|
record = Settings.get_int_settings(session, attr_name)
|
||||||
record.f_int = self.columnWidth(column_number)
|
record.f_int = self.columnWidth(column_number)
|
||||||
|
|
||||||
# def _context_menu(self, pos):
|
def _context_menu(self, pos):
|
||||||
# """Display right-click menu"""
|
"""Display right-click menu"""
|
||||||
|
|
||||||
# item = self.itemAt(pos)
|
item = self.indexAt(pos)
|
||||||
# self._build_context_menu(item)
|
self._build_context_menu(item)
|
||||||
# self.menu.exec(self.mapToGlobal(pos))
|
self.menu.exec(self.mapToGlobal(pos))
|
||||||
|
|
||||||
def _copy_path(self, row_number: int) -> None:
|
def _copy_path(self, row_number: int) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user