WIP V3: implement searching with QSortFilterProxyModel (ooo!)

This commit is contained in:
Keith Edmunds 2023-11-26 15:22:01 +00:00
parent 6f5c371510
commit 480c832852
5 changed files with 279 additions and 100 deletions

View File

@ -6,7 +6,6 @@ from typing import (
cast, cast,
List, List,
Optional, Optional,
Sequence,
) )
from os.path import basename from os.path import basename
@ -49,7 +48,6 @@ 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 (
@ -511,12 +509,6 @@ 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)
@ -555,7 +547,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.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.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)
@ -870,7 +862,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().get_selected_row_number(), proposed_row_number=self.active_tab().selected_model_row_number(),
note=dlg.textValue(), note=dlg.textValue(),
) )
@ -1249,6 +1241,13 @@ 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,11 +3,12 @@ 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 List, Optional from typing import cast, List, Optional
from PyQt6.QtCore import ( from PyQt6.QtCore import (
QAbstractTableModel, QAbstractTableModel,
QModelIndex, QModelIndex,
QRegularExpression,
QSortFilterProxyModel, QSortFilterProxyModel,
Qt, Qt,
QVariant, QVariant,
@ -24,10 +25,8 @@ 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,
) )
@ -95,23 +94,6 @@ class StartEndTimes:
end_time: Optional[datetime] = None end_time: Optional[datetime] = None
class PlaylistProxyModel(QSortFilterProxyModel):
"""
For searching and filtering
"""
def __init__(
self,
playlist_id: int,
*args,
**kwargs,
):
self.playlist_id = playlist_id
super().__init__(*args, **kwargs)
self.setSourceModel(PlaylistModel(playlist_id))
class PlaylistModel(QAbstractTableModel): class PlaylistModel(QAbstractTableModel):
""" """
The Playlist Model The Playlist Model
@ -1002,7 +984,9 @@ 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(PlaylistRows, track_sequence.previous.plr_rownum) previous_plr = session.get(
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
@ -1243,3 +1227,126 @@ 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 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 delete_rows(self, row_numbers: List[int]) -> None:
model = cast(PlaylistModel, self.sourceModel())
return model.delete_rows(row_numbers)
def get_duplicate_rows(self) -> List[int]:
model = cast(PlaylistModel, self.sourceModel())
return model.get_duplicate_rows()
def get_rows_duration(self, row_numbers: List[int]) -> int:
model = cast(PlaylistModel, self.sourceModel())
return model.get_rows_duration(row_numbers)
def get_row_info(self, row_number: int) -> PlaylistRowData:
model = cast(PlaylistModel, self.sourceModel())
return model.get_row_info(row_number)
def get_row_track_path(self, row_number: int) -> str:
model = cast(PlaylistModel, self.sourceModel())
return model.get_row_track_path(row_number)
def insert_row(
self,
proposed_row_number: Optional[int],
track_id: Optional[int] = None,
note: Optional[str] = None,
) -> None:
model = cast(PlaylistModel, self.sourceModel())
return model.insert_row(proposed_row_number, track_id, note)
def is_header_row(self, row_number: int) -> bool:
model = cast(PlaylistModel, self.sourceModel())
return model.is_header_row(row_number)
def is_unplayed_row(self, row_number: int) -> bool:
model = cast(PlaylistModel, self.sourceModel())
return model.is_unplayed_row(row_number)
def mark_unplayed(self, row_numbers: List[int]) -> None:
model = cast(PlaylistModel, self.sourceModel())
return model.mark_unplayed(row_numbers)
def move_rows(self, from_rows: List[int], to_row_number: int) -> None:
model = cast(PlaylistModel, self.sourceModel())
return 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:
model = cast(PlaylistModel, self.sourceModel())
return model.move_rows_between_playlists(
from_rows, to_row_number, to_playlist_id
)
def open_in_audacity(self, row_number: int) -> None:
model = cast(PlaylistModel, self.sourceModel())
return model.open_in_audacity(row_number)
def remove_track(self, row_number: int) -> None:
model = cast(PlaylistModel, self.sourceModel())
return model.remove_track(row_number)
def rescan_track(self, row_number: int) -> None:
model = cast(PlaylistModel, self.sourceModel())
return model.rescan_track(row_number)
def set_next_row(self, row_number: Optional[int]) -> None:
model = cast(PlaylistModel, self.sourceModel())
return model.set_next_row(row_number)
def sort_by_artist(self, row_numbers: List[int]) -> None:
model = cast(PlaylistModel, self.sourceModel())
return model.sort_by_artist(row_numbers)
def sort_by_duration(self, row_numbers: List[int]) -> None:
model = cast(PlaylistModel, self.sourceModel())
return model.sort_by_duration(row_numbers)
def sort_by_lastplayed(self, row_numbers: List[int]) -> None:
model = cast(PlaylistModel, self.sourceModel())
return model.sort_by_lastplayed(row_numbers)
def sort_by_title(self, row_numbers: List[int]) -> None:
model = cast(PlaylistModel, self.sourceModel())
return model.sort_by_title(row_numbers)
def update_track_times(self) -> None:
model = cast(PlaylistModel, self.sourceModel())
return model.update_track_times()

View File

@ -1,24 +1,21 @@
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 Any, Callable, cast, List, Optional, Tuple, TYPE_CHECKING from typing import Callable, cast, List, Optional, 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, QBrush, QColor, QFont, QDropEvent, QKeyEvent from PyQt6.QtGui import QAction, QDropEvent, QKeyEvent
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemDelegate, QAbstractItemDelegate,
QAbstractItemView, QAbstractItemView,
@ -49,6 +46,7 @@ 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
@ -67,8 +65,9 @@ class EscapeDelegate(QStyledItemDelegate):
- checks with user before abandoning edit on Escape - checks with user before abandoning edit on Escape
""" """
def __init__(self, parent) -> None: def __init__(self, parent, playlist_model: PlaylistModel) -> None:
super().__init__(parent) super().__init__(parent)
self.playlist_model = playlist_model
self.signals = MusicMusterSignals() self.signals = MusicMusterSignals()
def createEditor( def createEditor(
@ -123,12 +122,24 @@ class EscapeDelegate(QStyledItemDelegate):
return False return False
def setEditorData(self, editor, index): 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()) 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()
model.setData(index, value, Qt.ItemDataRole.EditRole) self.playlist_model.setData(edit_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)
@ -165,10 +176,10 @@ class PlaylistTab(QTableView):
self.playlist_id = playlist_id self.playlist_id = playlist_id
# Set up widget # 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.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)
@ -202,8 +213,12 @@ 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(PlaylistProxyModel(playlist_id)) self.setModel(self.proxy_model)
self._set_column_widths() self._set_column_widths()
def closeEditor( def closeEditor(
@ -224,8 +239,7 @@ 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
model = cast(PlaylistModel, self.model()) self.playlist_model.update_track_times()
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 (
@ -234,7 +248,7 @@ class PlaylistTab(QTableView):
): ):
super().dropEvent(event) 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() to_row = self.indexAt(event.position().toPoint()).row()
if ( if (
0 <= min(from_rows) <= self.model().rowCount() 0 <= min(from_rows) <= self.model().rowCount()
@ -311,17 +325,76 @@ class PlaylistTab(QTableView):
self.clearSelection() self.clearSelection()
self.setDragEnabled(False) 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. 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():
index = sm.currentIndex() return sm.selectedRows()
if index.isValid(): return []
return index.row()
return None
def get_selected_row_track_path(self) -> str: def get_selected_row_track_path(self) -> str:
""" """
@ -329,13 +402,10 @@ class PlaylistTab(QTableView):
row does not have a track, return empty string. row does not have a track, return empty string.
""" """
sm = self.selectionModel() model_row_number = self.selected_model_row_number()
if sm and sm.hasSelection(): if model_row_number is None:
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:
# """ # """
@ -473,11 +543,10 @@ class PlaylistTab(QTableView):
Set selected row as next track Set selected row as next track
""" """
selected_row = self.get_selected_row_number() model_row_number = self.selected_model_row_number()
if selected_row is None: if model_row_number is None:
return return
model = cast(PlaylistModel, self.model()) self.playlist_model.set_next_row(model_row_number)
model.set_next_row(selected_row)
self.clearSelection() self.clearSelection()
# # # ########## Internally called functions ########## # # # ########## Internally called functions ##########
@ -485,14 +554,14 @@ class PlaylistTab(QTableView):
def _add_track(self) -> None: def _add_track(self) -> 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"""
row_number = self.get_selected_row_number() model_row_number = self.selected_model_row_number()
if not row_number: if model_row_number is None:
return return
with Session() as session: with Session() as session:
dlg = TrackSelectDialog( dlg = TrackSelectDialog(
session=session, session=session,
new_row_number=row_number, new_row_number=model_row_number,
playlist_id=self.playlist_id, playlist_id=self.playlist_id,
add_to_header=True, add_to_header=True,
) )
@ -502,25 +571,31 @@ 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 = cast(PlaylistModel, self.model()) model = self.proxy_model
if not model:
return
row_number = item.row() display_row_number = item.row()
header_row = model.is_header_row(row_number) 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 track_row = not header_row
current_row = row_number == track_sequence.now.plr_rownum current_row = model_row_number == track_sequence.now.plr_rownum
next_row = row_number == track_sequence.next.plr_rownum next_row = model_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(row_number) "Open in Audacity", lambda: model.open_in_audacity(model_row_number)
) )
# Rescan # Rescan
if track_row and not current_row: 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() self.menu.addSeparator()
@ -532,7 +607,7 @@ 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(row_number) "Remove track from row", lambda: model.remove_track(model_row_number)
) )
# Add track to section header (ie, make this a track row) # Add track to section header (ie, make this a track row)
@ -543,7 +618,7 @@ class PlaylistTab(QTableView):
self.menu.addSeparator() self.menu.addSeparator()
# Mark unplayed # 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( 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()),
@ -583,12 +658,12 @@ class PlaylistTab(QTableView):
# Info # Info
if track_row: 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 # 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(
@ -631,8 +706,7 @@ class PlaylistTab(QTableView):
to the clipboard. Otherwise, return None. to the clipboard. Otherwise, return None.
""" """
model = cast(PlaylistModel, self.model()) track_path = self.playlist_model.get_row_info(row_number).path
track_path = model.get_row_info(row_number).path
if not track_path: if not track_path:
return return
@ -669,8 +743,7 @@ 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
model = cast(PlaylistModel, self.model()) self.playlist_model.delete_rows(self.selected_model_row_numbers())
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"""
@ -682,8 +755,7 @@ 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"""
model = cast(PlaylistModel, self.model()) prd = self.playlist_model.get_row_info(row_number)
prd = model.get_row_info(row_number)
if prd: if prd:
txt = ( txt = (
f"Title: {prd.title}\n" f"Title: {prd.title}\n"
@ -739,8 +811,7 @@ 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"""
model = cast(PlaylistModel, self.model()) self.playlist_model.mark_unplayed(row_numbers)
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:
@ -786,8 +857,7 @@ class PlaylistTab(QTableView):
def _rescan(self, row_number: int) -> None: def _rescan(self, row_number: int) -> None:
"""Rescan track""" """Rescan track"""
model = cast(PlaylistModel, self.model()) self.playlist_model.rescan_track(row_number)
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:
@ -931,8 +1001,7 @@ 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
model = cast(PlaylistModel, self.model()) duplicate_rows = self.playlist_model.get_duplicate_rows()
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)
@ -951,8 +1020,9 @@ class PlaylistTab(QTableView):
if len(selected_rows) == 0: if len(selected_rows) == 0:
self.musicmuster.lblSumPlaytime.setText("") self.musicmuster.lblSumPlaytime.setText("")
else: else:
model = cast(PlaylistModel, self.model()) selected_duration = self.playlist_model.get_rows_duration(
selected_duration = model.get_rows_duration(self.get_selected_rows()) 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)}"
@ -1011,6 +1081,14 @@ 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.
@ -1025,6 +1103,5 @@ class PlaylistTab(QTableView):
def _unmark_as_next(self) -> None: def _unmark_as_next(self) -> None:
"""Rescan track""" """Rescan track"""
model = cast(PlaylistModel, self.model()) self.playlist_model.set_next_row(None)
model.set_next_row(None)
self.clear_selection() self.clear_selection()

View File

@ -785,8 +785,6 @@ 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.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 # 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,8 +492,6 @@ 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)