diff --git a/app/playlistmodel.py b/app/playlistmodel.py index c522e0f..13f23f0 100644 --- a/app/playlistmodel.py +++ b/app/playlistmodel.py @@ -1,3 +1,4 @@ +from dataclasses import dataclass from datetime import datetime, timedelta from enum import auto, Enum from operator import attrgetter @@ -54,12 +55,10 @@ class PlaylistRowData: self.artist: str = "" self.bitrate = 0 self.duration: int = 0 - self.end_time: Optional[datetime] = None self.lastplayed: datetime = Config.EPOCH self.path = "" self.played = False self.start_gap: Optional[int] = None - self.start_time: Optional[datetime] = None self.title: str = "" self.plrid: int = plr.id @@ -86,6 +85,12 @@ class PlaylistRowData: ) +@dataclass +class StartEndTimes: + start_time: Optional[datetime] = None + end_time: Optional[datetime] = None + + class PlaylistModel(QAbstractTableModel): """ The Playlist Model @@ -112,6 +117,7 @@ class PlaylistModel(QAbstractTableModel): super().__init__(*args, **kwargs) self.playlist_rows: dict[int, PlaylistRowData] = {} + self.start_end_times: dict[int, StartEndTimes] = {} self.signals = MusicMusterSignals() self.signals.add_track_to_header_signal.connect(self.add_track_to_header) @@ -268,10 +274,9 @@ class PlaylistModel(QAbstractTableModel): self.refresh_row(session, plr.plr_rownum) # Update track times - self.playlist_rows[row_number].start_time = datetime.now() - self.playlist_rows[row_number].end_time = datetime.now() + timedelta( - milliseconds=self.playlist_rows[row_number].duration - ) + self.start_end_times[row_number].start_time = track_sequence.now.start_time + self.start_end_times[row_number].end_time = track_sequence.now.end_time + # Update colour and times for current row self.invalidate_row(row_number) # Update all other track times @@ -369,15 +374,17 @@ class PlaylistModel(QAbstractTableModel): if column == Col.DURATION.value: return QVariant(ms_to_mmss(prd.duration)) if column == Col.START_TIME.value: - if prd.start_time: - return QVariant(prd.start_time.strftime(Config.TRACK_TIME_FORMAT)) - else: - return QVariant() + if row in self.start_end_times: + start_time = self.start_end_times[row].start_time + if start_time: + return QVariant(start_time.strftime(Config.TRACK_TIME_FORMAT)) + return QVariant() if column == Col.END_TIME.value: - if prd.end_time: - return QVariant(prd.end_time.strftime(Config.TRACK_TIME_FORMAT)) - else: - return QVariant() + if row in self.start_end_times: + end_time = self.start_end_times[row].end_time + if end_time: + return QVariant(end_time.strftime(Config.TRACK_TIME_FORMAT)) + return QVariant() if column == Col.LAST_PLAYED.value: return QVariant(get_relative_date(prd.lastplayed)) if column == Col.BITRATE.value: @@ -649,13 +656,20 @@ class PlaylistModel(QAbstractTableModel): for modified_row in modified_rows: self.invalidate_row(modified_row) - def mark_unplayed(self, row_number: int) -> None: + def mark_unplayed(self, row_numbers: List[int]) -> None: """ Mark row as unplayed """ - self.playlist_rows[row_number].played = False - self.invalidate_row(row_number) + with Session() as session: + for row_number in row_numbers: + plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid) + if not plr: + return + plr.played = False + self.refresh_row(session, row_number) + + self.invalidate_rows(row_numbers) def move_rows(self, from_rows: List[int], to_row_number: int) -> None: """ @@ -678,6 +692,7 @@ class PlaylistModel(QAbstractTableModel): from_rows, range(next_to_row, next_to_row + len(from_rows)) ): row_map[from_row] = to_row + # Move the remaining rows to the row_map. We want to fill it # before (if there are gaps) and after (likewise) the rows that # are moving. @@ -695,6 +710,14 @@ class PlaylistModel(QAbstractTableModel): old_row, HEADER_NOTES_COLUMN, 1, 1 ) + # Check to see whether any rows in track_sequence have moved + if track_sequence.previous.plr_rownum in row_map: + track_sequence.previous.plr_rownum = row_map[track_sequence.previous.plr_rownum] + if track_sequence.now.plr_rownum in row_map: + track_sequence.now.plr_rownum = row_map[track_sequence.now.plr_rownum] + if track_sequence.next.plr_rownum in row_map: + track_sequence.next.plr_rownum = row_map[track_sequence.next.plr_rownum] + # For SQLAlchemy, build a list of dictionaries that map plrid to # new row number: sqla_map: List[dict[str, int]] = [] @@ -756,7 +779,7 @@ class PlaylistModel(QAbstractTableModel): self.playlist_rows[p.plr_rownum] = PlaylistRowData(p) def refresh_row(self, session, row_number): - """Populate dict for one row for data calls""" + """Populate dict for one row from database""" p = PlaylistRows.deep_row(session, self.playlist_id, row_number) self.playlist_rows[row_number] = PlaylistRowData(p) @@ -950,21 +973,22 @@ class PlaylistModel(QAbstractTableModel): update_rows: List[int] = [] for row_number in range(len(self.playlist_rows)): + stend = self.start_end_times[row_number] = StartEndTimes() prd = self.playlist_rows[row_number] # Reset start_time if this is the current row if row_number == track_sequence.now.plr_rownum: - # Start/end times for current track are set in current_track_started + stend.start_time = track_sequence.now.start_time + stend.end_time = track_sequence.now.end_time if not next_start_time: - next_start_time = prd.end_time + next_start_time = stend.end_time continue # Set start time for next row if we have a current track - current_end_time = track_sequence.now.end_time - if row_number == track_sequence.next.plr_rownum and current_end_time: - prd.start_time = current_end_time - prd.end_time = current_end_time + timedelta(milliseconds=prd.duration) - next_start_time = prd.end_time + if row_number == track_sequence.next.plr_rownum and track_sequence.now.end_time: + stend.start_time = track_sequence.now.end_time + stend.end_time = stend.start_time + timedelta(milliseconds=prd.duration) + next_start_time = stend.end_time update_rows.append(row_number) continue @@ -985,14 +1009,11 @@ class PlaylistModel(QAbstractTableModel): < row_number < track_sequence.next.plr_rownum ): - prd.start_time = None - prd.end_time = None update_rows.append(row_number) continue # Reset start time if timing in header or at current track - if not prd.path: - # This is a header row + if self.is_header_row(row_number): header_time = get_embedded_time(prd.note) if header_time: next_start_time = header_time @@ -1001,14 +1022,14 @@ class PlaylistModel(QAbstractTableModel): # start time if next_start_time is None: continue - if prd.start_time != next_start_time: - prd.start_time = next_start_time + if stend.start_time != next_start_time: + stend.start_time = next_start_time update_rows.append(row_number) next_start_time += timedelta( milliseconds=self.playlist_rows[row_number].duration ) - if prd.end_time != next_start_time: - prd.end_time = next_start_time + if stend.end_time != next_start_time: + stend.end_time = next_start_time update_rows.append(row_number) # Update start/stop times of rows that have changed diff --git a/app/playlists.py b/app/playlists.py index 327e96e..98be176 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -1103,11 +1103,15 @@ class PlaylistTab(QTableView): # Mark unplayed if track_row and model.is_unplayed_row(row_number): - self._add_context_menu("Mark unplayed", lambda: model.mark_unplayed(row_number)) + self._add_context_menu( + "Mark unplayed", lambda: model.mark_unplayed(self._get_selected_rows()) + ) # Unmark as next if next_row: - self._add_context_menu("Unmark as next track", lambda: model.set_next_row(None)) + self._add_context_menu( + "Unmark as next track", lambda: model.set_next_row(None) + ) # ---------------------- self.menu.addSeparator() @@ -1134,9 +1138,6 @@ class PlaylistTab(QTableView): lambda: model.sort_by_lastplayed(self._get_selected_rows()), parent_menu=sort_menu, ) - if sort_menu: - sort_menu.setEnabled(model.selection_is_sortable(self._get_selected_rows())) - self._add_context_menu("Undo sort", self._sort_undo, not bool(self.sort_undo)) # Info TODO if track_row: @@ -1144,8 +1145,7 @@ class PlaylistTab(QTableView): # Track path TODO if track_row: - self._add_context_menu( - "Copy track path", lambda: print("Track path")) + self._add_context_menu("Copy track path", lambda: print("Track path")) def _calculate_end_time( self, start: Optional[datetime], duration: int