Compare commits

..

No commits in common. "dfb45dd0ffbfa97118498576b5009d846282e81e" and "c626d91f26ca12426b9af0c6f240f496e4a3182f" have entirely different histories.

7 changed files with 100 additions and 328 deletions

1
.gitignore vendored
View File

@ -10,4 +10,3 @@ StudioPlaylist.png
*.otl *.otl
*.howto *.howto
.direnv .direnv
tmp/

View File

@ -6,6 +6,7 @@ from typing import (
cast, cast,
List, List,
Optional, Optional,
Sequence,
) )
from os.path import basename from os.path import basename
@ -48,6 +49,7 @@ from PyQt6.QtWidgets import (
QProgressBar, QProgressBar,
QPushButton, QPushButton,
) )
from sqlalchemy import text
import stackprinter # type: ignore import stackprinter # type: ignore
from classes import ( from classes import (
@ -509,6 +511,12 @@ class Window(QMainWindow, Ui_MainWindow):
self.actionEnable_controls.triggered.connect(self.enable_play_next_controls) self.actionEnable_controls.triggered.connect(self.enable_play_next_controls)
self.actionExport_playlist.triggered.connect(self.export_playlist_tab) self.actionExport_playlist.triggered.connect(self.export_playlist_tab)
self.actionFade.triggered.connect(self.fade) 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.actionImport.triggered.connect(self.import_track)
self.actionInsertSectionHeader.triggered.connect(self.insert_header) self.actionInsertSectionHeader.triggered.connect(self.insert_header)
self.actionInsertTrack.triggered.connect(self.insert_track) self.actionInsertTrack.triggered.connect(self.insert_track)
@ -547,7 +555,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.hdrNextTrack.clicked.connect(self.show_next) self.hdrNextTrack.clicked.connect(self.show_next)
self.tabPlaylist.tabCloseRequested.connect(self.close_tab) self.tabPlaylist.tabCloseRequested.connect(self.close_tab)
self.tabBar = self.tabPlaylist.tabBar() self.tabBar = self.tabPlaylist.tabBar()
self.txtSearch.textChanged.connect(self.search_playlist_text_changed) self.txtSearch.returnPressed.connect(self.search_playlist_return)
self.signals.enable_escape_signal.connect(self.enable_escape) self.signals.enable_escape_signal.connect(self.enable_escape)
self.signals.next_track_changed_signal.connect(self.update_headers) self.signals.next_track_changed_signal.connect(self.update_headers)
@ -762,13 +770,14 @@ class Window(QMainWindow, Ui_MainWindow):
if self.hide_played_tracks: if self.hide_played_tracks:
self.hide_played_tracks = False self.hide_played_tracks = False
self.active_model().hide_played_tracks(False)
self.btnHidePlayed.setText("Hide played") self.btnHidePlayed.setText("Hide played")
else: else:
self.hide_played_tracks = True self.hide_played_tracks = True
self.active_model().hide_played_tracks(True)
self.btnHidePlayed.setText("Show played") self.btnHidePlayed.setText("Show played")
# Update displayed playlist
self.active_tab().hide_or_show_played_tracks()
def import_track(self) -> None: def import_track(self) -> None:
"""Import track file""" """Import track file"""
@ -861,7 +870,7 @@ class Window(QMainWindow, Ui_MainWindow):
ok = dlg.exec() ok = dlg.exec()
if ok: if ok:
model.insert_row( model.insert_row(
proposed_row_number=self.active_tab().selected_model_row_number(), proposed_row_number=self.active_tab().get_selected_row_number(),
note=dlg.textValue(), note=dlg.textValue(),
) )
@ -1240,13 +1249,6 @@ class Window(QMainWindow, Ui_MainWindow):
self.active_tab().set_search(self.txtSearch.text()) self.active_tab().set_search(self.txtSearch.text())
self.enable_play_next_controls() 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: def select_next_row(self) -> None:
"""Select next or first row in playlist""" """Select next or first row in playlist"""

View File

@ -3,13 +3,11 @@ from datetime import datetime, timedelta
from enum import auto, Enum from enum import auto, Enum
from operator import attrgetter from operator import attrgetter
from pprint import pprint from pprint import pprint
from typing import cast, List, Optional from typing import List, Optional
from PyQt6.QtCore import ( from PyQt6.QtCore import (
QAbstractTableModel, QAbstractTableModel,
QModelIndex, QModelIndex,
QRegularExpression,
QSortFilterProxyModel,
Qt, Qt,
QVariant, QVariant,
) )
@ -25,8 +23,10 @@ from dbconfig import scoped_session, Session
from helpers import ( from helpers import (
file_is_unreadable, file_is_unreadable,
get_embedded_time, get_embedded_time,
get_file_metadata,
get_relative_date, get_relative_date,
open_in_audacity, open_in_audacity,
normalise_track,
ms_to_mmss, ms_to_mmss,
set_track_metadata, set_track_metadata,
) )
@ -122,7 +122,6 @@ class PlaylistModel(QAbstractTableModel):
self.playlist_rows: dict[int, PlaylistRowData] = {} self.playlist_rows: dict[int, PlaylistRowData] = {}
self.start_end_times: dict[int, StartEndTimes] = {} self.start_end_times: dict[int, StartEndTimes] = {}
self.signals = MusicMusterSignals() 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_header_signal.connect(self.add_track_to_header)
self.signals.add_track_to_playlist_signal.connect(self.add_track) self.signals.add_track_to_playlist_signal.connect(self.add_track)
@ -667,16 +666,6 @@ class PlaylistModel(QAbstractTableModel):
return prd.note 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: def is_header_row(self, row_number: int) -> bool:
""" """
Return True if row is a header row, else False Return True if row is a header row, else False
@ -684,7 +673,7 @@ class PlaylistModel(QAbstractTableModel):
return self.playlist_rows[row_number].path == "" return self.playlist_rows[row_number].path == ""
def is_played_row(self, row_number: int) -> bool: def is_unplayed_row(self, row_number: int) -> bool:
""" """
Return True if row is an unplayed track row, else False Return True if row is an unplayed track row, else False
""" """
@ -696,9 +685,9 @@ class PlaylistModel(QAbstractTableModel):
proposed_row_number: Optional[int], proposed_row_number: Optional[int],
track_id: Optional[int] = None, track_id: Optional[int] = None,
note: Optional[str] = None, note: Optional[str] = None,
) -> None: ) -> PlaylistRows:
""" """
Insert a row. Insert a track row.
""" """
new_row_number = self._get_new_row_number(proposed_row_number) new_row_number = self._get_new_row_number(proposed_row_number)
@ -715,7 +704,9 @@ class PlaylistModel(QAbstractTableModel):
super().endInsertRows() super().endInsertRows()
self.row_order_changed(self.playlist_id) self.row_order_changed(self.playlist_id)
self.invalidate_rows(list(range(new_row_number, len(self.playlist_rows)))) self.invalidate_rows(list(range(plr.plr_rownum, len(self.playlist_rows))))
return plr
def invalidate_row(self, modified_row: int) -> None: def invalidate_row(self, modified_row: int) -> None:
""" """
@ -723,7 +714,7 @@ class PlaylistModel(QAbstractTableModel):
""" """
self.dataChanged.emit( self.dataChanged.emit(
self.index(modified_row, 0), self.index(modified_row, self.columnCount() - 1) self.index(modified_row, 0), self.index(modified_row, self.columnCount())
) )
def invalidate_rows(self, modified_rows: List[int]) -> None: def invalidate_rows(self, modified_rows: List[int]) -> None:
@ -995,9 +986,7 @@ class PlaylistModel(QAbstractTableModel):
if now_plr: if now_plr:
track_sequence.now.plr_rownum = now_plr.plr_rownum track_sequence.now.plr_rownum = now_plr.plr_rownum
if track_sequence.previous.plr_rownum: if track_sequence.previous.plr_rownum:
previous_plr = session.get( previous_plr = session.get(PlaylistRows, track_sequence.previous.plr_rownum)
PlaylistRows, track_sequence.previous.plr_rownum
)
if previous_plr: if previous_plr:
track_sequence.previous.plr_rownum = previous_plr.plr_rownum track_sequence.previous.plr_rownum = previous_plr.plr_rownum
@ -1031,14 +1020,12 @@ class PlaylistModel(QAbstractTableModel):
Set row_number as next track. If row_number is None, clear next track. Set row_number as next track. If row_number is None, clear next track.
""" """
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 row_number is None:
next_row_was = track_sequence.next.plr_rownum
if next_row_was is None: if next_row_was is None:
return return
track_sequence.next = PlaylistTrack() track_sequence.next = PlaylistTrack()
self.invalidate_row(next_row_was)
self.signals.next_track_changed_signal.emit() self.signals.next_track_changed_signal.emit()
return return
@ -1240,141 +1227,3 @@ class PlaylistModel(QAbstractTableModel):
self.index(updated_row, Col.START_TIME.value), self.index(updated_row, Col.START_TIME.value),
self.index(updated_row, Col.END_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()

View File

@ -1,21 +1,24 @@
import os
import re
import stackprinter # type: ignore
import subprocess import subprocess
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 pprint import pprint from pprint import pprint
from typing import Callable, cast, List, Optional, TYPE_CHECKING from typing import Any, Callable, cast, List, Optional, Tuple, TYPE_CHECKING
from PyQt6.QtCore import ( from PyQt6.QtCore import (
QEvent, QEvent,
QModelIndex, QModelIndex,
QObject, QObject,
QItemSelection, QItemSelection,
QItemSelectionModel,
Qt, Qt,
# QTimer, # QTimer,
) )
from PyQt6.QtGui import QAction, QDropEvent, QKeyEvent from PyQt6.QtGui import QAction, QBrush, QColor, QFont, QDropEvent, QKeyEvent
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemDelegate, QAbstractItemDelegate,
QAbstractItemView, QAbstractItemView,
@ -46,14 +49,13 @@ from helpers import (
open_in_audacity, open_in_audacity,
send_mail, send_mail,
set_track_metadata, set_track_metadata,
show_warning,
) )
from log import log from log import log
from models import PlaylistRows, Settings, Tracks, NoteColours from models import PlaylistRows, Settings, Tracks, NoteColours
if TYPE_CHECKING: if TYPE_CHECKING:
from musicmuster import Window from musicmuster import Window
from playlistmodel import PlaylistModel, PlaylistProxyModel from playlistmodel import PlaylistModel
# HEADER_NOTES_COLUMN = 2 # HEADER_NOTES_COLUMN = 2
@ -65,9 +67,8 @@ class EscapeDelegate(QStyledItemDelegate):
- checks with user before abandoning edit on Escape - checks with user before abandoning edit on Escape
""" """
def __init__(self, parent, playlist_model: PlaylistModel) -> None: def __init__(self, parent) -> None:
super().__init__(parent) super().__init__(parent)
self.playlist_model = playlist_model
self.signals = MusicMusterSignals() self.signals = MusicMusterSignals()
def createEditor( def createEditor(
@ -122,24 +123,12 @@ class EscapeDelegate(QStyledItemDelegate):
return False return False
def setEditorData(self, editor, index): def setEditorData(self, editor, index):
model = index.model() value = index.model().data(index, Qt.ItemDataRole.EditRole)
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()) editor.setPlainText(value.value())
def setModelData(self, editor, model, index): 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() value = editor.toPlainText()
self.playlist_model.setData(edit_index, value, Qt.ItemDataRole.EditRole) model.setData(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)
@ -176,10 +165,10 @@ class PlaylistTab(QTableView):
self.playlist_id = playlist_id self.playlist_id = playlist_id
# Set up widget # Set up widget
self.playlist_model = PlaylistModel(playlist_id) self.setItemDelegate(EscapeDelegate(self))
self.proxy_model = PlaylistProxyModel(self.playlist_model)
self.setItemDelegate(EscapeDelegate(self, self.playlist_model))
self.setAlternatingRowColors(True) self.setAlternatingRowColors(True)
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
# self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked) # self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked)
self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove) self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
@ -213,12 +202,8 @@ class PlaylistTab(QTableView):
self.sort_undo: List[int] = [] self.sort_undo: List[int] = []
# self.edit_cell_type: Optional[int] # self.edit_cell_type: Optional[int]
# Selection model
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
# Load playlist rows # Load playlist rows
self.setModel(self.proxy_model) self.setModel(PlaylistModel(playlist_id))
self._set_column_widths() self._set_column_widths()
def closeEditor( def closeEditor(
@ -239,7 +224,8 @@ 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.playlist_model.update_track_times() model = cast(PlaylistModel, self.model())
model.update_track_times()
def dropEvent(self, event): def dropEvent(self, event):
if event.source() is not self or ( if event.source() is not self or (
@ -248,7 +234,7 @@ class PlaylistTab(QTableView):
): ):
super().dropEvent(event) super().dropEvent(event)
from_rows = self.selected_model_row_numbers() from_rows = list(set([a.row() for a in self.selectedIndexes()]))
to_row = self.indexAt(event.position().toPoint()).row() to_row = self.indexAt(event.position().toPoint()).row()
if ( if (
0 <= min(from_rows) <= self.model().rowCount() 0 <= min(from_rows) <= self.model().rowCount()
@ -325,76 +311,17 @@ class PlaylistTab(QTableView):
self.clearSelection() self.clearSelection()
self.setDragEnabled(False) self.setDragEnabled(False)
def selected_display_row_number(self): def get_selected_row_number(self) -> Optional[int]:
""" """
Return the selected row number or None if none selected. 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() sm = self.selectionModel()
if sm and sm.hasSelection(): if sm and sm.hasSelection():
return sm.selectedRows() index = sm.currentIndex()
return [] if index.isValid():
return index.row()
return None
def get_selected_row_track_path(self) -> str: def get_selected_row_track_path(self) -> str:
""" """
@ -402,10 +329,13 @@ class PlaylistTab(QTableView):
row does not have a track, return empty string. row does not have a track, return empty string.
""" """
model_row_number = self.selected_model_row_number() sm = self.selectionModel()
if model_row_number is None: if sm and sm.hasSelection():
index = sm.currentIndex()
if index.isValid():
model = cast(PlaylistModel, self.model())
return model.get_row_track_path(index.row())
return "" return ""
return self.playlist_model.get_row_track_path(model_row_number)
# def lookup_row_in_songfacts(self) -> None: # def lookup_row_in_songfacts(self) -> None:
# """ # """
@ -543,25 +473,22 @@ class PlaylistTab(QTableView):
Set selected row as next track Set selected row as next track
""" """
model_row_number = self.selected_model_row_number() selected_row = self.get_selected_row_number()
if model_row_number is None: if selected_row is None:
return return
self.playlist_model.set_next_row(model_row_number) model = cast(PlaylistModel, self.model())
model.set_next_row(selected_row)
self.clearSelection() self.clearSelection()
# # # ########## Internally called functions ########## # # # ########## Internally called functions ##########
def _add_track(self) -> None: def _add_track(self, row_number: int) -> None:
"""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"""
model_row_number = self.selected_model_row_number()
if model_row_number is None:
return
with Session() as session: with Session() as session:
dlg = TrackSelectDialog( dlg = TrackSelectDialog(
session=session, session=session,
new_row_number=model_row_number, new_row_number=row_number,
playlist_id=self.playlist_id, playlist_id=self.playlist_id,
add_to_header=True, add_to_header=True,
) )
@ -571,31 +498,25 @@ 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()
model = self.proxy_model model = cast(PlaylistModel, self.model())
if not model:
return
display_row_number = item.row() row_number = item.row()
if hasattr(model, "mapToSource"): header_row = model.is_header_row(row_number)
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 track_row = not header_row
current_row = model_row_number == track_sequence.now.plr_rownum current_row = row_number == track_sequence.now.plr_rownum
next_row = model_row_number == track_sequence.next.plr_rownum next_row = row_number == track_sequence.next.plr_rownum
# Open in Audacity # Open in Audacity
if track_row and not current_row: if track_row and not current_row:
self._add_context_menu( self._add_context_menu(
"Open in Audacity", lambda: model.open_in_audacity(model_row_number) "Open in Audacity", lambda: model.open_in_audacity(row_number)
) )
# Rescan # Rescan
if track_row and not current_row: if track_row and not current_row:
self._add_context_menu( self._add_context_menu("Rescan track", lambda: self._rescan(row_number))
"Rescan track", lambda: self._rescan(model_row_number)
)
# ---------------------- # ----------------------
self.menu.addSeparator() self.menu.addSeparator()
@ -607,21 +528,21 @@ class PlaylistTab(QTableView):
# Remove track from row # Remove track from row
if track_row and not current_row and not next_row: if track_row and not current_row and not next_row:
self._add_context_menu( self._add_context_menu(
"Remove track from row", lambda: model.remove_track(model_row_number) "Remove track from row", lambda: model.remove_track(row_number)
) )
# Add track to section header (ie, make this a track row) # Add track to section header (ie, make this a track row)
# TODO
if header_row: if header_row:
self._add_context_menu("Add a track", lambda: self._add_track()) self._add_context_menu("Add a track", lambda: print("Add a track"))
# # ---------------------- # # ----------------------
self.menu.addSeparator() self.menu.addSeparator()
# Mark unplayed # Mark unplayed
if track_row and model.is_unplayed_row(model_row_number): if track_row and model.is_unplayed_row(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()),
) )
# Unmark as next # Unmark as next
@ -658,13 +579,11 @@ class PlaylistTab(QTableView):
# Info # Info
if track_row: if track_row:
self._add_context_menu("Info", lambda: self._info_row(model_row_number)) self._add_context_menu("Info", lambda: self._info_row(row_number))
# Track path TODO # Track path TODO
if track_row: if track_row:
self._add_context_menu( self._add_context_menu("Copy track path", lambda: self._copy_path(row_number))
"Copy track path", lambda: self._copy_path(model_row_number)
)
def _calculate_end_time( def _calculate_end_time(
self, start: Optional[datetime], duration: int self, start: Optional[datetime], duration: int
@ -706,7 +625,8 @@ class PlaylistTab(QTableView):
to the clipboard. Otherwise, return None. to the clipboard. Otherwise, return None.
""" """
track_path = self.playlist_model.get_row_info(row_number).path model = cast(PlaylistModel, self.model())
track_path = model.get_row_info(row_number).path
if not track_path: if not track_path:
return return
@ -743,7 +663,8 @@ 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.playlist_model.delete_rows(self.selected_model_row_numbers()) model = cast(PlaylistModel, self.model())
model.delete_rows(self.get_selected_rows())
def get_selected_rows(self) -> List[int]: def get_selected_rows(self) -> List[int]:
"""Return a list of selected row numbers sorted by row""" """Return a list of selected row numbers sorted by row"""
@ -755,7 +676,8 @@ 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.playlist_model.get_row_info(row_number) model = cast(PlaylistModel, self.model())
prd = model.get_row_info(row_number)
if prd: if prd:
txt = ( txt = (
f"Title: {prd.title}\n" f"Title: {prd.title}\n"
@ -811,7 +733,8 @@ class PlaylistTab(QTableView):
def _mark_as_unplayed(self, row_numbers: List[int]) -> None: def _mark_as_unplayed(self, row_numbers: List[int]) -> None:
"""Rescan track""" """Rescan track"""
self.playlist_model.mark_unplayed(row_numbers) model = cast(PlaylistModel, self.model())
model.mark_unplayed(row_numbers)
self.clear_selection() self.clear_selection()
def _obs_change_scene(self, current_row: int) -> None: def _obs_change_scene(self, current_row: int) -> None:
@ -857,7 +780,8 @@ class PlaylistTab(QTableView):
def _rescan(self, row_number: int) -> None: def _rescan(self, row_number: int) -> None:
"""Rescan track""" """Rescan track"""
self.playlist_model.rescan_track(row_number) model = cast(PlaylistModel, self.model())
model.rescan_track(row_number)
self.clear_selection() self.clear_selection()
# def _reset_next(self, old_plrid: int, new_plrid: int) -> None: # def _reset_next(self, old_plrid: int, new_plrid: int) -> None:
@ -1001,7 +925,8 @@ 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.playlist_model.get_duplicate_rows() model = cast(PlaylistModel, self.model())
duplicate_rows = 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)
@ -1020,9 +945,8 @@ class PlaylistTab(QTableView):
if len(selected_rows) == 0: if len(selected_rows) == 0:
self.musicmuster.lblSumPlaytime.setText("") self.musicmuster.lblSumPlaytime.setText("")
else: else:
selected_duration = self.playlist_model.get_rows_duration( model = cast(PlaylistModel, self.model())
self.get_selected_rows() selected_duration = model.get_rows_duration(self.get_selected_rows())
)
if selected_duration > 0: if selected_duration > 0:
self.musicmuster.lblSumPlaytime.setText( self.musicmuster.lblSumPlaytime.setText(
f"Selected duration: {ms_to_mmss(selected_duration)}" f"Selected duration: {ms_to_mmss(selected_duration)}"
@ -1081,14 +1005,6 @@ class PlaylistTab(QTableView):
Implement spanning of cells, initiated by signal 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 # 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
# again calls us here. # again calls us here.
@ -1103,5 +1019,6 @@ class PlaylistTab(QTableView):
def _unmark_as_next(self) -> None: def _unmark_as_next(self) -> None:
"""Rescan track""" """Rescan track"""
self.playlist_model.set_next_row(None) model = cast(PlaylistModel, self.model())
model.set_next_row(None)
self.clear_selection() self.clear_selection()

View File

@ -785,6 +785,8 @@ padding-left: 8px;</string>
<string>&amp;Search</string> <string>&amp;Search</string>
</property> </property>
<addaction name="actionSearch"/> <addaction name="actionSearch"/>
<addaction name="actionFind_next"/>
<addaction name="actionFind_previous"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionSelect_next_track"/> <addaction name="actionSelect_next_track"/>
<addaction name="actionSelect_previous_track"/> <addaction name="actionSelect_previous_track"/>

View File

@ -1,6 +1,6 @@
# Form implementation generated from reading ui file 'app/ui/main_window.ui' # Form implementation generated from reading ui file 'app/ui/main_window.ui'
# #
# Created by: PyQt6 UI code generator 6.6.0 # Created by: PyQt6 UI code generator 6.5.3
# #
# WARNING: Any manual changes made to this file will be lost when pyuic6 is # 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. # run again. Do not edit this file unless you know what you are doing.
@ -492,6 +492,8 @@ class Ui_MainWindow(object):
self.menuPlaylist.addAction(self.actionMark_for_moving) self.menuPlaylist.addAction(self.actionMark_for_moving)
self.menuPlaylist.addAction(self.actionPaste) self.menuPlaylist.addAction(self.actionPaste)
self.menuSearc_h.addAction(self.actionSearch) 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.addSeparator()
self.menuSearc_h.addAction(self.actionSelect_next_track) self.menuSearc_h.addAction(self.actionSelect_next_track)
self.menuSearc_h.addAction(self.actionSelect_previous_track) self.menuSearc_h.addAction(self.actionSelect_previous_track)

View File

@ -43,7 +43,8 @@ def create_model_with_playlist_rows(
# Create a model # Create a model
model = playlistmodel.PlaylistModel(playlist.id) model = playlistmodel.PlaylistModel(playlist.id)
for row in range(rows): for row in range(rows):
model.insert_row(proposed_row_number=row, note=str(row)) plr = model.insert_row(proposed_row_number=row, note=str(row))
model.playlist_rows[plr.plr_rownum] = playlistmodel.PlaylistRowData(plr)
session.commit() session.commit()
return model return model