Compare commits
7 Commits
c626d91f26
...
dfb45dd0ff
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfb45dd0ff | ||
|
|
02391f04b1 | ||
|
|
31f7122a7f | ||
|
|
480c832852 | ||
|
|
6f5c371510 | ||
|
|
23a9eff43b | ||
|
|
25e3be6fae |
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,3 +10,4 @@ StudioPlaylist.png
|
||||
*.otl
|
||||
*.howto
|
||||
.direnv
|
||||
tmp/
|
||||
|
||||
@ -6,7 +6,6 @@ from typing import (
|
||||
cast,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
)
|
||||
from os.path import basename
|
||||
|
||||
@ -49,7 +48,6 @@ from PyQt6.QtWidgets import (
|
||||
QProgressBar,
|
||||
QPushButton,
|
||||
)
|
||||
from sqlalchemy import text
|
||||
import stackprinter # type: ignore
|
||||
|
||||
from classes import (
|
||||
@ -511,12 +509,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.actionEnable_controls.triggered.connect(self.enable_play_next_controls)
|
||||
self.actionExport_playlist.triggered.connect(self.export_playlist_tab)
|
||||
self.actionFade.triggered.connect(self.fade)
|
||||
self.actionFind_next.triggered.connect(
|
||||
lambda: self.tabPlaylist.currentWidget().search_next()
|
||||
)
|
||||
self.actionFind_previous.triggered.connect(
|
||||
lambda: self.tabPlaylist.currentWidget().search_previous()
|
||||
)
|
||||
self.actionImport.triggered.connect(self.import_track)
|
||||
self.actionInsertSectionHeader.triggered.connect(self.insert_header)
|
||||
self.actionInsertTrack.triggered.connect(self.insert_track)
|
||||
@ -555,7 +547,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.hdrNextTrack.clicked.connect(self.show_next)
|
||||
self.tabPlaylist.tabCloseRequested.connect(self.close_tab)
|
||||
self.tabBar = self.tabPlaylist.tabBar()
|
||||
self.txtSearch.returnPressed.connect(self.search_playlist_return)
|
||||
self.txtSearch.textChanged.connect(self.search_playlist_text_changed)
|
||||
|
||||
self.signals.enable_escape_signal.connect(self.enable_escape)
|
||||
self.signals.next_track_changed_signal.connect(self.update_headers)
|
||||
@ -770,14 +762,13 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
|
||||
if self.hide_played_tracks:
|
||||
self.hide_played_tracks = False
|
||||
self.active_model().hide_played_tracks(False)
|
||||
self.btnHidePlayed.setText("Hide played")
|
||||
else:
|
||||
self.hide_played_tracks = True
|
||||
self.active_model().hide_played_tracks(True)
|
||||
self.btnHidePlayed.setText("Show played")
|
||||
|
||||
# Update displayed playlist
|
||||
self.active_tab().hide_or_show_played_tracks()
|
||||
|
||||
def import_track(self) -> None:
|
||||
"""Import track file"""
|
||||
|
||||
@ -870,7 +861,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
ok = dlg.exec()
|
||||
if ok:
|
||||
model.insert_row(
|
||||
proposed_row_number=self.active_tab().get_selected_row_number(),
|
||||
proposed_row_number=self.active_tab().selected_model_row_number(),
|
||||
note=dlg.textValue(),
|
||||
)
|
||||
|
||||
@ -1249,6 +1240,13 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.active_tab().set_search(self.txtSearch.text())
|
||||
self.enable_play_next_controls()
|
||||
|
||||
def search_playlist_text_changed(self) -> None:
|
||||
"""
|
||||
Incremental search of playlist
|
||||
"""
|
||||
|
||||
self.active_model().set_incremental_search(self.txtSearch.text())
|
||||
|
||||
def select_next_row(self) -> None:
|
||||
"""Select next or first row in playlist"""
|
||||
|
||||
|
||||
@ -3,11 +3,13 @@ from datetime import datetime, timedelta
|
||||
from enum import auto, Enum
|
||||
from operator import attrgetter
|
||||
from pprint import pprint
|
||||
from typing import List, Optional
|
||||
from typing import cast, List, Optional
|
||||
|
||||
from PyQt6.QtCore import (
|
||||
QAbstractTableModel,
|
||||
QModelIndex,
|
||||
QRegularExpression,
|
||||
QSortFilterProxyModel,
|
||||
Qt,
|
||||
QVariant,
|
||||
)
|
||||
@ -23,10 +25,8 @@ from dbconfig import scoped_session, Session
|
||||
from helpers import (
|
||||
file_is_unreadable,
|
||||
get_embedded_time,
|
||||
get_file_metadata,
|
||||
get_relative_date,
|
||||
open_in_audacity,
|
||||
normalise_track,
|
||||
ms_to_mmss,
|
||||
set_track_metadata,
|
||||
)
|
||||
@ -122,6 +122,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
self.playlist_rows: dict[int, PlaylistRowData] = {}
|
||||
self.start_end_times: dict[int, StartEndTimes] = {}
|
||||
self.signals = MusicMusterSignals()
|
||||
self.played_tracks_hidden = False
|
||||
|
||||
self.signals.add_track_to_header_signal.connect(self.add_track_to_header)
|
||||
self.signals.add_track_to_playlist_signal.connect(self.add_track)
|
||||
@ -666,6 +667,16 @@ class PlaylistModel(QAbstractTableModel):
|
||||
|
||||
return prd.note
|
||||
|
||||
def hide_played_tracks(self, hide: bool) -> None:
|
||||
"""
|
||||
Set played tracks hidden according to 'hide'
|
||||
"""
|
||||
|
||||
self.played_tracks_hidden = hide
|
||||
for row_number in range(len(self.playlist_rows)):
|
||||
if self.is_played_row(row_number):
|
||||
self.invalidate_row(row_number)
|
||||
|
||||
def is_header_row(self, row_number: int) -> bool:
|
||||
"""
|
||||
Return True if row is a header row, else False
|
||||
@ -673,7 +684,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
|
||||
return self.playlist_rows[row_number].path == ""
|
||||
|
||||
def is_unplayed_row(self, row_number: int) -> bool:
|
||||
def is_played_row(self, row_number: int) -> bool:
|
||||
"""
|
||||
Return True if row is an unplayed track row, else False
|
||||
"""
|
||||
@ -685,9 +696,9 @@ class PlaylistModel(QAbstractTableModel):
|
||||
proposed_row_number: Optional[int],
|
||||
track_id: Optional[int] = None,
|
||||
note: Optional[str] = None,
|
||||
) -> PlaylistRows:
|
||||
) -> None:
|
||||
"""
|
||||
Insert a track row.
|
||||
Insert a row.
|
||||
"""
|
||||
|
||||
new_row_number = self._get_new_row_number(proposed_row_number)
|
||||
@ -704,9 +715,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
super().endInsertRows()
|
||||
|
||||
self.row_order_changed(self.playlist_id)
|
||||
self.invalidate_rows(list(range(plr.plr_rownum, len(self.playlist_rows))))
|
||||
|
||||
return plr
|
||||
self.invalidate_rows(list(range(new_row_number, len(self.playlist_rows))))
|
||||
|
||||
def invalidate_row(self, modified_row: int) -> None:
|
||||
"""
|
||||
@ -714,7 +723,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
"""
|
||||
|
||||
self.dataChanged.emit(
|
||||
self.index(modified_row, 0), self.index(modified_row, self.columnCount())
|
||||
self.index(modified_row, 0), self.index(modified_row, self.columnCount() - 1)
|
||||
)
|
||||
|
||||
def invalidate_rows(self, modified_rows: List[int]) -> None:
|
||||
@ -986,7 +995,9 @@ class PlaylistModel(QAbstractTableModel):
|
||||
if now_plr:
|
||||
track_sequence.now.plr_rownum = now_plr.plr_rownum
|
||||
if track_sequence.previous.plr_rownum:
|
||||
previous_plr = session.get(PlaylistRows, track_sequence.previous.plr_rownum)
|
||||
previous_plr = session.get(
|
||||
PlaylistRows, track_sequence.previous.plr_rownum
|
||||
)
|
||||
if previous_plr:
|
||||
track_sequence.previous.plr_rownum = previous_plr.plr_rownum
|
||||
|
||||
@ -1020,12 +1031,14 @@ class PlaylistModel(QAbstractTableModel):
|
||||
Set row_number as next track. If row_number is None, clear next track.
|
||||
"""
|
||||
|
||||
if row_number is None:
|
||||
next_row_was = track_sequence.next.plr_rownum
|
||||
if next_row_was is not None:
|
||||
self.invalidate_row(next_row_was)
|
||||
|
||||
if row_number is None:
|
||||
if next_row_was is None:
|
||||
return
|
||||
track_sequence.next = PlaylistTrack()
|
||||
self.invalidate_row(next_row_was)
|
||||
self.signals.next_track_changed_signal.emit()
|
||||
return
|
||||
|
||||
@ -1227,3 +1240,141 @@ class PlaylistModel(QAbstractTableModel):
|
||||
self.index(updated_row, Col.START_TIME.value),
|
||||
self.index(updated_row, Col.END_TIME.value),
|
||||
)
|
||||
|
||||
|
||||
class PlaylistProxyModel(QSortFilterProxyModel):
|
||||
"""
|
||||
For searching and filtering
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
playlist_model: PlaylistModel,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
self.playlist_model = playlist_model
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.setSourceModel(playlist_model)
|
||||
# Search all columns
|
||||
self.setFilterKeyColumn(-1)
|
||||
|
||||
def filterAcceptsRow(self, source_row: int, source_parent: QModelIndex) -> bool:
|
||||
"""
|
||||
Subclass to filter by played status
|
||||
"""
|
||||
|
||||
if self.playlist_model.played_tracks_hidden:
|
||||
if self.playlist_model.is_played_row(source_row):
|
||||
# Don't hide current or next track
|
||||
with Session() as session:
|
||||
next_plr = session.get(PlaylistRows, track_sequence.next.plr_id)
|
||||
if (
|
||||
next_plr
|
||||
and next_plr.plr_rownum == source_row
|
||||
and next_plr.playlist_id == self.playlist_model.playlist_id
|
||||
):
|
||||
return True
|
||||
now_plr = session.get(PlaylistRows, track_sequence.now.plr_id)
|
||||
if (
|
||||
now_plr
|
||||
and now_plr.plr_rownum == source_row
|
||||
and now_plr.playlist_id == self.playlist_model.playlist_id
|
||||
):
|
||||
return True
|
||||
return False
|
||||
return super().filterAcceptsRow(source_row, source_parent)
|
||||
|
||||
def set_incremental_search(self, search_string: str) -> None:
|
||||
"""
|
||||
Update search pattern
|
||||
"""
|
||||
|
||||
self.setFilterRegularExpression(
|
||||
QRegularExpression(
|
||||
search_string, QRegularExpression.PatternOption.CaseInsensitiveOption
|
||||
)
|
||||
)
|
||||
|
||||
# ######################################
|
||||
# Forward functions not handled in proxy
|
||||
# ######################################
|
||||
|
||||
def current_track_started(self):
|
||||
return self.playlist_model.current_track_started()
|
||||
|
||||
def delete_rows(self, row_numbers: List[int]) -> None:
|
||||
return self.playlist_model.delete_rows(row_numbers)
|
||||
|
||||
def get_duplicate_rows(self) -> List[int]:
|
||||
return self.playlist_model.get_duplicate_rows()
|
||||
|
||||
def get_rows_duration(self, row_numbers: List[int]) -> int:
|
||||
return self.playlist_model.get_rows_duration(row_numbers)
|
||||
|
||||
def get_row_info(self, row_number: int) -> PlaylistRowData:
|
||||
return self.playlist_model.get_row_info(row_number)
|
||||
|
||||
def get_row_track_path(self, row_number: int) -> str:
|
||||
return self.playlist_model.get_row_track_path(row_number)
|
||||
|
||||
def hide_played_tracks(self, hide: bool) -> None:
|
||||
return self.playlist_model.hide_played_tracks(hide)
|
||||
|
||||
def insert_row(
|
||||
self,
|
||||
proposed_row_number: Optional[int],
|
||||
track_id: Optional[int] = None,
|
||||
note: Optional[str] = None,
|
||||
) -> None:
|
||||
return self.playlist_model.insert_row(proposed_row_number, track_id, note)
|
||||
|
||||
def is_header_row(self, row_number: int) -> bool:
|
||||
return self.playlist_model.is_header_row(row_number)
|
||||
|
||||
def is_played_row(self, row_number: int) -> bool:
|
||||
return self.playlist_model.is_played_row(row_number)
|
||||
|
||||
def mark_unplayed(self, row_numbers: List[int]) -> None:
|
||||
return self.playlist_model.mark_unplayed(row_numbers)
|
||||
|
||||
def move_rows(self, from_rows: List[int], to_row_number: int) -> None:
|
||||
return self.playlist_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.playlist_model.move_rows_between_playlists(
|
||||
from_rows, to_row_number, to_playlist_id
|
||||
)
|
||||
|
||||
def open_in_audacity(self, row_number: int) -> None:
|
||||
return self.playlist_model.open_in_audacity(row_number)
|
||||
|
||||
def previous_track_ended(self) -> None:
|
||||
return self.playlist_model.previous_track_ended()
|
||||
|
||||
def remove_track(self, row_number: int) -> None:
|
||||
return self.playlist_model.remove_track(row_number)
|
||||
|
||||
def rescan_track(self, row_number: int) -> None:
|
||||
return self.playlist_model.rescan_track(row_number)
|
||||
|
||||
def set_next_row(self, row_number: Optional[int]) -> None:
|
||||
return self.playlist_model.set_next_row(row_number)
|
||||
|
||||
def sort_by_artist(self, row_numbers: List[int]) -> None:
|
||||
return self.playlist_model.sort_by_artist(row_numbers)
|
||||
|
||||
def sort_by_duration(self, row_numbers: List[int]) -> None:
|
||||
return self.playlist_model.sort_by_duration(row_numbers)
|
||||
|
||||
def sort_by_lastplayed(self, row_numbers: List[int]) -> None:
|
||||
return self.playlist_model.sort_by_lastplayed(row_numbers)
|
||||
|
||||
def sort_by_title(self, row_numbers: List[int]) -> None:
|
||||
return self.playlist_model.sort_by_title(row_numbers)
|
||||
|
||||
def update_track_times(self) -> None:
|
||||
return self.playlist_model.update_track_times()
|
||||
|
||||
215
app/playlists.py
215
app/playlists.py
@ -1,24 +1,21 @@
|
||||
import os
|
||||
import re
|
||||
import stackprinter # type: ignore
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
import obsws_python as obs # type: ignore
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from pprint import pprint
|
||||
from typing import Any, Callable, cast, List, Optional, Tuple, TYPE_CHECKING
|
||||
from typing import Callable, cast, List, Optional, TYPE_CHECKING
|
||||
|
||||
from PyQt6.QtCore import (
|
||||
QEvent,
|
||||
QModelIndex,
|
||||
QObject,
|
||||
QItemSelection,
|
||||
QItemSelectionModel,
|
||||
Qt,
|
||||
# QTimer,
|
||||
)
|
||||
from PyQt6.QtGui import QAction, QBrush, QColor, QFont, QDropEvent, QKeyEvent
|
||||
from PyQt6.QtGui import QAction, QDropEvent, QKeyEvent
|
||||
from PyQt6.QtWidgets import (
|
||||
QAbstractItemDelegate,
|
||||
QAbstractItemView,
|
||||
@ -49,13 +46,14 @@ from helpers import (
|
||||
open_in_audacity,
|
||||
send_mail,
|
||||
set_track_metadata,
|
||||
show_warning,
|
||||
)
|
||||
from log import log
|
||||
from models import PlaylistRows, Settings, Tracks, NoteColours
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from musicmuster import Window
|
||||
from playlistmodel import PlaylistModel
|
||||
from playlistmodel import PlaylistModel, PlaylistProxyModel
|
||||
|
||||
# HEADER_NOTES_COLUMN = 2
|
||||
|
||||
@ -67,8 +65,9 @@ class EscapeDelegate(QStyledItemDelegate):
|
||||
- checks with user before abandoning edit on Escape
|
||||
"""
|
||||
|
||||
def __init__(self, parent) -> None:
|
||||
def __init__(self, parent, playlist_model: PlaylistModel) -> None:
|
||||
super().__init__(parent)
|
||||
self.playlist_model = playlist_model
|
||||
self.signals = MusicMusterSignals()
|
||||
|
||||
def createEditor(
|
||||
@ -123,12 +122,24 @@ class EscapeDelegate(QStyledItemDelegate):
|
||||
return False
|
||||
|
||||
def setEditorData(self, editor, index):
|
||||
value = index.model().data(index, Qt.ItemDataRole.EditRole)
|
||||
model = index.model()
|
||||
if hasattr(model, "mapToSource"):
|
||||
edit_index = model.mapToSource(index)
|
||||
else:
|
||||
edit_index = index
|
||||
|
||||
value = self.playlist_model.data(edit_index, Qt.ItemDataRole.EditRole)
|
||||
editor.setPlainText(value.value())
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
model = index.model()
|
||||
if hasattr(model, "mapToSource"):
|
||||
edit_index = model.mapToSource(index)
|
||||
else:
|
||||
edit_index = index
|
||||
|
||||
value = editor.toPlainText()
|
||||
model.setData(index, value, Qt.ItemDataRole.EditRole)
|
||||
self.playlist_model.setData(edit_index, value, Qt.ItemDataRole.EditRole)
|
||||
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
editor.setGeometry(option.rect)
|
||||
@ -165,10 +176,10 @@ class PlaylistTab(QTableView):
|
||||
self.playlist_id = playlist_id
|
||||
|
||||
# Set up widget
|
||||
self.setItemDelegate(EscapeDelegate(self))
|
||||
self.playlist_model = PlaylistModel(playlist_id)
|
||||
self.proxy_model = PlaylistProxyModel(self.playlist_model)
|
||||
self.setItemDelegate(EscapeDelegate(self, self.playlist_model))
|
||||
self.setAlternatingRowColors(True)
|
||||
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
# self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked)
|
||||
self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
|
||||
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
|
||||
@ -202,8 +213,12 @@ class PlaylistTab(QTableView):
|
||||
self.sort_undo: List[int] = []
|
||||
# self.edit_cell_type: Optional[int]
|
||||
|
||||
# Selection model
|
||||
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
|
||||
# Load playlist rows
|
||||
self.setModel(PlaylistModel(playlist_id))
|
||||
self.setModel(self.proxy_model)
|
||||
self._set_column_widths()
|
||||
|
||||
def closeEditor(
|
||||
@ -224,8 +239,7 @@ class PlaylistTab(QTableView):
|
||||
|
||||
# Update start times in case a start time in a note has been
|
||||
# edited
|
||||
model = cast(PlaylistModel, self.model())
|
||||
model.update_track_times()
|
||||
self.playlist_model.update_track_times()
|
||||
|
||||
def dropEvent(self, event):
|
||||
if event.source() is not self or (
|
||||
@ -234,7 +248,7 @@ class PlaylistTab(QTableView):
|
||||
):
|
||||
super().dropEvent(event)
|
||||
|
||||
from_rows = list(set([a.row() for a in self.selectedIndexes()]))
|
||||
from_rows = self.selected_model_row_numbers()
|
||||
to_row = self.indexAt(event.position().toPoint()).row()
|
||||
if (
|
||||
0 <= min(from_rows) <= self.model().rowCount()
|
||||
@ -311,17 +325,76 @@ class PlaylistTab(QTableView):
|
||||
self.clearSelection()
|
||||
self.setDragEnabled(False)
|
||||
|
||||
def get_selected_row_number(self) -> Optional[int]:
|
||||
def selected_display_row_number(self):
|
||||
"""
|
||||
Return the selected row number or None if none selected.
|
||||
"""
|
||||
|
||||
row_index = self._selected_row_index()
|
||||
if row_index:
|
||||
return row_index.row()
|
||||
else:
|
||||
return None
|
||||
return row_index.row()
|
||||
|
||||
def selected_display_row_numbers(self):
|
||||
"""
|
||||
Return a list of the selected row numbers
|
||||
"""
|
||||
|
||||
indexes = self._selected_row_indexes()
|
||||
|
||||
return [a.row() for a in indexes]
|
||||
|
||||
def selected_model_row_number(self) -> Optional[int]:
|
||||
"""
|
||||
Return the model row number corresponding to the selected row or None
|
||||
"""
|
||||
|
||||
selected_index = self._selected_row_index()
|
||||
if selected_index is None:
|
||||
return None
|
||||
if hasattr(self.proxy_model, "mapToSource"):
|
||||
return self.proxy_model.mapToSource(selected_index).row()
|
||||
return selected_index.row()
|
||||
|
||||
def selected_model_row_numbers(self) -> Optional[List[int]]:
|
||||
"""
|
||||
Return a list of model row numbers corresponding to the selected rows or
|
||||
an empty list.
|
||||
"""
|
||||
|
||||
selected_indexes = self._selected_row_indexes()
|
||||
if selected_indexes is None:
|
||||
return None
|
||||
if hasattr(self.proxy_model, "mapToSource"):
|
||||
return [self.proxy_model.mapToSource(a).row() for a in selected_indexes]
|
||||
return [a.row() for a in selected_indexes]
|
||||
|
||||
def _selected_row_index(self) -> Optional[QModelIndex]:
|
||||
"""
|
||||
Return the selected row index or None if none selected.
|
||||
"""
|
||||
|
||||
row_indexes = self._selected_row_indexes()
|
||||
|
||||
if len(row_indexes) != 1:
|
||||
show_warning(
|
||||
self.musicmuster, "No or multiple rows selected", "Select only one row"
|
||||
)
|
||||
return None
|
||||
|
||||
return row_indexes[0]
|
||||
|
||||
def _selected_row_indexes(self) -> List[QModelIndex]:
|
||||
"""
|
||||
Return a list of indexes of column 1 of selected rows
|
||||
"""
|
||||
|
||||
sm = self.selectionModel()
|
||||
if sm and sm.hasSelection():
|
||||
index = sm.currentIndex()
|
||||
if index.isValid():
|
||||
return index.row()
|
||||
return None
|
||||
return sm.selectedRows()
|
||||
return []
|
||||
|
||||
def get_selected_row_track_path(self) -> str:
|
||||
"""
|
||||
@ -329,13 +402,10 @@ class PlaylistTab(QTableView):
|
||||
row does not have a track, return empty string.
|
||||
"""
|
||||
|
||||
sm = self.selectionModel()
|
||||
if sm and sm.hasSelection():
|
||||
index = sm.currentIndex()
|
||||
if index.isValid():
|
||||
model = cast(PlaylistModel, self.model())
|
||||
return model.get_row_track_path(index.row())
|
||||
model_row_number = self.selected_model_row_number()
|
||||
if model_row_number is None:
|
||||
return ""
|
||||
return self.playlist_model.get_row_track_path(model_row_number)
|
||||
|
||||
# def lookup_row_in_songfacts(self) -> None:
|
||||
# """
|
||||
@ -473,22 +543,25 @@ class PlaylistTab(QTableView):
|
||||
Set selected row as next track
|
||||
"""
|
||||
|
||||
selected_row = self.get_selected_row_number()
|
||||
if selected_row is None:
|
||||
model_row_number = self.selected_model_row_number()
|
||||
if model_row_number is None:
|
||||
return
|
||||
model = cast(PlaylistModel, self.model())
|
||||
model.set_next_row(selected_row)
|
||||
self.playlist_model.set_next_row(model_row_number)
|
||||
self.clearSelection()
|
||||
|
||||
# # # ########## Internally called functions ##########
|
||||
|
||||
def _add_track(self, row_number: int) -> None:
|
||||
def _add_track(self) -> None:
|
||||
"""Add a track to a section header making it a normal track row"""
|
||||
|
||||
model_row_number = self.selected_model_row_number()
|
||||
if model_row_number is None:
|
||||
return
|
||||
|
||||
with Session() as session:
|
||||
dlg = TrackSelectDialog(
|
||||
session=session,
|
||||
new_row_number=row_number,
|
||||
new_row_number=model_row_number,
|
||||
playlist_id=self.playlist_id,
|
||||
add_to_header=True,
|
||||
)
|
||||
@ -498,25 +571,31 @@ class PlaylistTab(QTableView):
|
||||
"""Used to process context (right-click) menu, which is defined here"""
|
||||
|
||||
self.menu.clear()
|
||||
model = cast(PlaylistModel, self.model())
|
||||
if not model:
|
||||
return
|
||||
model = self.proxy_model
|
||||
|
||||
row_number = item.row()
|
||||
header_row = model.is_header_row(row_number)
|
||||
display_row_number = item.row()
|
||||
if hasattr(model, "mapToSource"):
|
||||
index = model.index(item.row(), item.column())
|
||||
model_row_number = model.mapToSource(index).row()
|
||||
else:
|
||||
model_row_number = display_row_number
|
||||
|
||||
header_row = model.is_header_row(model_row_number)
|
||||
track_row = not header_row
|
||||
current_row = row_number == track_sequence.now.plr_rownum
|
||||
next_row = row_number == track_sequence.next.plr_rownum
|
||||
current_row = model_row_number == track_sequence.now.plr_rownum
|
||||
next_row = model_row_number == track_sequence.next.plr_rownum
|
||||
|
||||
# Open in Audacity
|
||||
if track_row and not current_row:
|
||||
self._add_context_menu(
|
||||
"Open in Audacity", lambda: model.open_in_audacity(row_number)
|
||||
"Open in Audacity", lambda: model.open_in_audacity(model_row_number)
|
||||
)
|
||||
|
||||
# Rescan
|
||||
if track_row and not current_row:
|
||||
self._add_context_menu("Rescan track", lambda: self._rescan(row_number))
|
||||
self._add_context_menu(
|
||||
"Rescan track", lambda: self._rescan(model_row_number)
|
||||
)
|
||||
|
||||
# ----------------------
|
||||
self.menu.addSeparator()
|
||||
@ -528,21 +607,21 @@ class PlaylistTab(QTableView):
|
||||
# Remove track from row
|
||||
if track_row and not current_row and not next_row:
|
||||
self._add_context_menu(
|
||||
"Remove track from row", lambda: model.remove_track(row_number)
|
||||
"Remove track from row", lambda: model.remove_track(model_row_number)
|
||||
)
|
||||
|
||||
# Add track to section header (ie, make this a track row)
|
||||
# TODO
|
||||
if header_row:
|
||||
self._add_context_menu("Add a track", lambda: print("Add a track"))
|
||||
self._add_context_menu("Add a track", lambda: self._add_track())
|
||||
|
||||
# # ----------------------
|
||||
self.menu.addSeparator()
|
||||
|
||||
# Mark unplayed
|
||||
if track_row and model.is_unplayed_row(row_number):
|
||||
if track_row and model.is_unplayed_row(model_row_number):
|
||||
self._add_context_menu(
|
||||
"Mark unplayed", lambda: self._mark_as_unplayed(self.get_selected_rows())
|
||||
"Mark unplayed",
|
||||
lambda: self._mark_as_unplayed(self.get_selected_rows()),
|
||||
)
|
||||
|
||||
# Unmark as next
|
||||
@ -579,11 +658,13 @@ class PlaylistTab(QTableView):
|
||||
|
||||
# Info
|
||||
if track_row:
|
||||
self._add_context_menu("Info", lambda: self._info_row(row_number))
|
||||
self._add_context_menu("Info", lambda: self._info_row(model_row_number))
|
||||
|
||||
# Track path TODO
|
||||
if track_row:
|
||||
self._add_context_menu("Copy track path", lambda: self._copy_path(row_number))
|
||||
self._add_context_menu(
|
||||
"Copy track path", lambda: self._copy_path(model_row_number)
|
||||
)
|
||||
|
||||
def _calculate_end_time(
|
||||
self, start: Optional[datetime], duration: int
|
||||
@ -625,8 +706,7 @@ class PlaylistTab(QTableView):
|
||||
to the clipboard. Otherwise, return None.
|
||||
"""
|
||||
|
||||
model = cast(PlaylistModel, self.model())
|
||||
track_path = model.get_row_info(row_number).path
|
||||
track_path = self.playlist_model.get_row_info(row_number).path
|
||||
if not track_path:
|
||||
return
|
||||
|
||||
@ -663,8 +743,7 @@ class PlaylistTab(QTableView):
|
||||
if not ask_yes_no("Delete rows", f"Really delete {row_count} row{plural}?"):
|
||||
return
|
||||
|
||||
model = cast(PlaylistModel, self.model())
|
||||
model.delete_rows(self.get_selected_rows())
|
||||
self.playlist_model.delete_rows(self.selected_model_row_numbers())
|
||||
|
||||
def get_selected_rows(self) -> List[int]:
|
||||
"""Return a list of selected row numbers sorted by row"""
|
||||
@ -676,8 +755,7 @@ class PlaylistTab(QTableView):
|
||||
def _info_row(self, row_number: int) -> None:
|
||||
"""Display popup with info re row"""
|
||||
|
||||
model = cast(PlaylistModel, self.model())
|
||||
prd = model.get_row_info(row_number)
|
||||
prd = self.playlist_model.get_row_info(row_number)
|
||||
if prd:
|
||||
txt = (
|
||||
f"Title: {prd.title}\n"
|
||||
@ -733,8 +811,7 @@ class PlaylistTab(QTableView):
|
||||
def _mark_as_unplayed(self, row_numbers: List[int]) -> None:
|
||||
"""Rescan track"""
|
||||
|
||||
model = cast(PlaylistModel, self.model())
|
||||
model.mark_unplayed(row_numbers)
|
||||
self.playlist_model.mark_unplayed(row_numbers)
|
||||
self.clear_selection()
|
||||
|
||||
def _obs_change_scene(self, current_row: int) -> None:
|
||||
@ -780,8 +857,7 @@ class PlaylistTab(QTableView):
|
||||
def _rescan(self, row_number: int) -> None:
|
||||
"""Rescan track"""
|
||||
|
||||
model = cast(PlaylistModel, self.model())
|
||||
model.rescan_track(row_number)
|
||||
self.playlist_model.rescan_track(row_number)
|
||||
self.clear_selection()
|
||||
|
||||
# def _reset_next(self, old_plrid: int, new_plrid: int) -> None:
|
||||
@ -925,8 +1001,7 @@ class PlaylistTab(QTableView):
|
||||
# We need to be in MultiSelection mode
|
||||
self.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
|
||||
# Get the duplicate rows
|
||||
model = cast(PlaylistModel, self.model())
|
||||
duplicate_rows = model.get_duplicate_rows()
|
||||
duplicate_rows = self.playlist_model.get_duplicate_rows()
|
||||
# Select the rows
|
||||
for duplicate_row in duplicate_rows:
|
||||
self.selectRow(duplicate_row)
|
||||
@ -945,8 +1020,9 @@ class PlaylistTab(QTableView):
|
||||
if len(selected_rows) == 0:
|
||||
self.musicmuster.lblSumPlaytime.setText("")
|
||||
else:
|
||||
model = cast(PlaylistModel, self.model())
|
||||
selected_duration = model.get_rows_duration(self.get_selected_rows())
|
||||
selected_duration = self.playlist_model.get_rows_duration(
|
||||
self.get_selected_rows()
|
||||
)
|
||||
if selected_duration > 0:
|
||||
self.musicmuster.lblSumPlaytime.setText(
|
||||
f"Selected duration: {ms_to_mmss(selected_duration)}"
|
||||
@ -1005,6 +1081,14 @@ class PlaylistTab(QTableView):
|
||||
Implement spanning of cells, initiated by signal
|
||||
"""
|
||||
|
||||
model = self.proxy_model
|
||||
if hasattr(model, "mapToSource"):
|
||||
edit_index = model.mapFromSource(
|
||||
self.playlist_model.createIndex(row, column)
|
||||
)
|
||||
row = edit_index.row()
|
||||
column = edit_index.column()
|
||||
|
||||
# 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
|
||||
# again calls us here.
|
||||
@ -1019,6 +1103,5 @@ class PlaylistTab(QTableView):
|
||||
def _unmark_as_next(self) -> None:
|
||||
"""Rescan track"""
|
||||
|
||||
model = cast(PlaylistModel, self.model())
|
||||
model.set_next_row(None)
|
||||
self.playlist_model.set_next_row(None)
|
||||
self.clear_selection()
|
||||
|
||||
@ -785,8 +785,6 @@ padding-left: 8px;</string>
|
||||
<string>&Search</string>
|
||||
</property>
|
||||
<addaction name="actionSearch"/>
|
||||
<addaction name="actionFind_next"/>
|
||||
<addaction name="actionFind_previous"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionSelect_next_track"/>
|
||||
<addaction name="actionSelect_previous_track"/>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Form implementation generated from reading ui file 'app/ui/main_window.ui'
|
||||
#
|
||||
# Created by: PyQt6 UI code generator 6.5.3
|
||||
# Created by: PyQt6 UI code generator 6.6.0
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
@ -492,8 +492,6 @@ class Ui_MainWindow(object):
|
||||
self.menuPlaylist.addAction(self.actionMark_for_moving)
|
||||
self.menuPlaylist.addAction(self.actionPaste)
|
||||
self.menuSearc_h.addAction(self.actionSearch)
|
||||
self.menuSearc_h.addAction(self.actionFind_next)
|
||||
self.menuSearc_h.addAction(self.actionFind_previous)
|
||||
self.menuSearc_h.addSeparator()
|
||||
self.menuSearc_h.addAction(self.actionSelect_next_track)
|
||||
self.menuSearc_h.addAction(self.actionSelect_previous_track)
|
||||
|
||||
@ -43,8 +43,7 @@ def create_model_with_playlist_rows(
|
||||
# Create a model
|
||||
model = playlistmodel.PlaylistModel(playlist.id)
|
||||
for row in range(rows):
|
||||
plr = model.insert_row(proposed_row_number=row, note=str(row))
|
||||
model.playlist_rows[plr.plr_rownum] = playlistmodel.PlaylistRowData(plr)
|
||||
model.insert_row(proposed_row_number=row, note=str(row))
|
||||
|
||||
session.commit()
|
||||
return model
|
||||
|
||||
Loading…
Reference in New Issue
Block a user