V3 tweaks and polishes
This commit is contained in:
parent
63a38b5bf9
commit
3179c6f5de
@ -104,10 +104,6 @@ class PlaylistTrack:
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
Only initialises data structure. Call set_plr to populate.
|
||||
|
||||
Do NOT store row_number here - that changes if tracks are reordered
|
||||
in playlist (add, remove, drag/drop) and we shouldn't care about row
|
||||
number: that's the playlist's problem.
|
||||
"""
|
||||
|
||||
self.artist: Optional[str] = None
|
||||
|
||||
@ -32,16 +32,6 @@ class Config(object):
|
||||
COLOUR_ODD_PLAYLIST = "#f2f2f2"
|
||||
COLOUR_UNREADABLE = "#dc3545"
|
||||
COLOUR_WARNING_TIMER = "#ffc107"
|
||||
COLUMN_NAME_ARTIST = "Artist"
|
||||
COLUMN_NAME_AUTOPLAY = "A"
|
||||
COLUMN_NAME_BITRATE = "bps"
|
||||
COLUMN_NAME_END_TIME = "End"
|
||||
COLUMN_NAME_LAST_PLAYED = "Last played"
|
||||
COLUMN_NAME_LEADING_SILENCE = "Gap"
|
||||
COLUMN_NAME_LENGTH = "Length"
|
||||
COLUMN_NAME_NOTES = "Notes"
|
||||
COLUMN_NAME_START_TIME = "Start"
|
||||
COLUMN_NAME_TITLE = "Title"
|
||||
DBFS_SILENCE = -50
|
||||
DEBUG_FUNCTIONS: List[Optional[str]] = []
|
||||
DEBUG_MODULES: List[Optional[str]] = ["dbconfig"]
|
||||
|
||||
@ -31,9 +31,7 @@ def Session() -> Generator[scoped_session, None, None]:
|
||||
function = frame.function
|
||||
lineno = frame.lineno
|
||||
Session = scoped_session(sessionmaker(bind=engine))
|
||||
log.debug(
|
||||
f"Session acquired: {file}:{function}:{lineno} " f"[{hex(id(Session))}]"
|
||||
)
|
||||
log.debug(f"Session acquired: {file}:{function}:{lineno} " f"[{hex(id(Session))}]")
|
||||
yield Session
|
||||
log.debug(f" Session released [{hex(id(Session))}]")
|
||||
Session.commit()
|
||||
|
||||
@ -87,7 +87,7 @@ class TrackSelectDialog(QDialog):
|
||||
default_yes=True,
|
||||
):
|
||||
move_existing = True
|
||||
if self.add_to_header and existing_prd: # and existing_prd for mypy's benefit
|
||||
if self.add_to_header and existing_prd: # "and existing_prd" for mypy's benefit
|
||||
if move_existing:
|
||||
self.model.move_track_to_header(self.new_row_number, existing_prd, note)
|
||||
else:
|
||||
@ -95,7 +95,7 @@ class TrackSelectDialog(QDialog):
|
||||
# Close dialog - we can only add one track to a header
|
||||
self.accept()
|
||||
else:
|
||||
if move_existing and existing_prd: # and existing_prd for mypy's benefit
|
||||
if move_existing and existing_prd: # "and existing_prd" for mypy's benefit
|
||||
self.model.move_track_add_note(self.new_row_number, existing_prd, note)
|
||||
else:
|
||||
self.model.insert_row(self.new_row_number, track_id, note)
|
||||
|
||||
@ -14,7 +14,7 @@ from mutagen.flac import FLAC # type: ignore
|
||||
from mutagen.mp3 import MP3 # type: ignore
|
||||
from pydub import AudioSegment, effects
|
||||
from pydub.utils import mediainfo
|
||||
from PyQt6.QtWidgets import QMainWindow, QMessageBox # type: ignore
|
||||
from PyQt6.QtWidgets import QMainWindow, QMessageBox
|
||||
from tinytag import TinyTag # type: ignore
|
||||
|
||||
from config import Config
|
||||
@ -99,7 +99,7 @@ def get_audio_segment(path: str) -> Optional[AudioSegment]:
|
||||
|
||||
|
||||
def get_embedded_time(text: str) -> Optional[datetime]:
|
||||
"""Return datetime specified as @hh:mm:ss in text"""
|
||||
"""Return datetime specified as @hh:mm in text"""
|
||||
|
||||
try:
|
||||
match = start_time_re.search(text)
|
||||
@ -171,7 +171,7 @@ def get_relative_date(
|
||||
weeks, days = divmod((reference_date.date() - past_date.date()).days, 7)
|
||||
if weeks == days == 0:
|
||||
# Same day so return time instead
|
||||
return past_date.strftime("%H:%M")
|
||||
return Config.LAST_PLAYED_TODAY_STRING + " " + past_date.strftime("%H:%M")
|
||||
if weeks == 1:
|
||||
weeks_str = "week"
|
||||
else:
|
||||
@ -226,32 +226,6 @@ def leading_silence(
|
||||
return min(trim_ms, len(audio_segment))
|
||||
|
||||
|
||||
def send_mail(to_addr, from_addr, subj, body):
|
||||
# From https://docs.python.org/3/library/email.examples.html
|
||||
|
||||
# Create a text/plain message
|
||||
msg = EmailMessage()
|
||||
msg.set_content(body)
|
||||
|
||||
msg["Subject"] = subj
|
||||
msg["From"] = from_addr
|
||||
msg["To"] = to_addr
|
||||
|
||||
# Send the message via SMTP server.
|
||||
context = ssl.create_default_context()
|
||||
try:
|
||||
s = smtplib.SMTP(host=Config.MAIL_SERVER, port=Config.MAIL_PORT)
|
||||
if Config.MAIL_USE_TLS:
|
||||
s.starttls(context=context)
|
||||
if Config.MAIL_USERNAME and Config.MAIL_PASSWORD:
|
||||
s.login(Config.MAIL_USERNAME, Config.MAIL_PASSWORD)
|
||||
s.send_message(msg)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
finally:
|
||||
s.quit()
|
||||
|
||||
|
||||
def ms_to_mmss(
|
||||
ms: Optional[int],
|
||||
decimals: int = 0,
|
||||
@ -390,6 +364,32 @@ def open_in_audacity(path: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def send_mail(to_addr, from_addr, subj, body):
|
||||
# From https://docs.python.org/3/library/email.examples.html
|
||||
|
||||
# Create a text/plain message
|
||||
msg = EmailMessage()
|
||||
msg.set_content(body)
|
||||
|
||||
msg["Subject"] = subj
|
||||
msg["From"] = from_addr
|
||||
msg["To"] = to_addr
|
||||
|
||||
# Send the message via SMTP server.
|
||||
context = ssl.create_default_context()
|
||||
try:
|
||||
s = smtplib.SMTP(host=Config.MAIL_SERVER, port=Config.MAIL_PORT)
|
||||
if Config.MAIL_USE_TLS:
|
||||
s.starttls(context=context)
|
||||
if Config.MAIL_USERNAME and Config.MAIL_PASSWORD:
|
||||
s.login(Config.MAIL_USERNAME, Config.MAIL_PASSWORD)
|
||||
s.send_message(msg)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
finally:
|
||||
s.quit()
|
||||
|
||||
|
||||
def set_track_metadata(track):
|
||||
"""Set/update track metadata in database"""
|
||||
|
||||
|
||||
@ -307,8 +307,7 @@ class Playlists(Base):
|
||||
"""
|
||||
|
||||
return session.scalars(
|
||||
select(cls).where(cls.open.is_(True))
|
||||
.order_by(cls.tab)
|
||||
select(cls).where(cls.open.is_(True)).order_by(cls.tab)
|
||||
).all()
|
||||
|
||||
def mark_open(self) -> None:
|
||||
@ -323,10 +322,10 @@ class Playlists(Base):
|
||||
Return True if no playlist of this name exists else false.
|
||||
"""
|
||||
|
||||
return session.execute(
|
||||
select(Playlists)
|
||||
.where(Playlists.name == name)
|
||||
).first() is None
|
||||
return (
|
||||
session.execute(select(Playlists).where(Playlists.name == name)).first()
|
||||
is None
|
||||
)
|
||||
|
||||
def rename(self, session: scoped_session, new_name: str) -> None:
|
||||
"""
|
||||
@ -479,9 +478,7 @@ class PlaylistRows(Base):
|
||||
session.flush()
|
||||
|
||||
@staticmethod
|
||||
def delete_row(
|
||||
session: scoped_session, playlist_id: int, row_number: int
|
||||
) -> None:
|
||||
def delete_row(session: scoped_session, playlist_id: int, row_number: int) -> None:
|
||||
"""
|
||||
Delete passed row in given playlist.
|
||||
"""
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
# import os
|
||||
import threading
|
||||
import vlc # type: ignore
|
||||
|
||||
#
|
||||
from config import Config
|
||||
from helpers import file_is_unreadable
|
||||
from typing import Optional
|
||||
@ -10,7 +8,7 @@ from time import sleep
|
||||
|
||||
from log import log
|
||||
|
||||
from PyQt6.QtCore import ( # type: ignore
|
||||
from PyQt6.QtCore import (
|
||||
QRunnable,
|
||||
QThreadPool,
|
||||
)
|
||||
|
||||
@ -170,7 +170,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
if plr:
|
||||
# Add track to PlaylistRows
|
||||
plr.track_id = track_id
|
||||
# Add any further note
|
||||
# Add any further note (header will already have a note)
|
||||
if note:
|
||||
plr.note += "\n" + note
|
||||
# Reset header row spanning
|
||||
@ -262,9 +262,11 @@ class PlaylistModel(QAbstractTableModel):
|
||||
# Check for OBS scene change
|
||||
self.obs_scene_change(row_number)
|
||||
|
||||
# Update Playdates in database
|
||||
with Session() as session:
|
||||
# Update Playdates in database
|
||||
Playdates(session, track_sequence.now.track_id)
|
||||
|
||||
# Mark track as played in playlist
|
||||
plr = session.get(PlaylistRows, track_sequence.now.plr_id)
|
||||
if plr:
|
||||
plr.played = True
|
||||
@ -305,32 +307,29 @@ class PlaylistModel(QAbstractTableModel):
|
||||
prd = self.playlist_rows[row]
|
||||
|
||||
# Dispatch to role-specific functions
|
||||
if role == Qt.ItemDataRole.DisplayRole:
|
||||
return self.display_role(row, column, prd)
|
||||
elif role == Qt.ItemDataRole.DecorationRole:
|
||||
pass
|
||||
elif role == Qt.ItemDataRole.EditRole:
|
||||
return self.edit_role(row, column, prd)
|
||||
elif role == Qt.ItemDataRole.ToolTipRole:
|
||||
pass
|
||||
elif role == Qt.ItemDataRole.StatusTipRole:
|
||||
pass
|
||||
elif role == Qt.ItemDataRole.WhatsThisRole:
|
||||
pass
|
||||
elif role == Qt.ItemDataRole.SizeHintRole:
|
||||
pass
|
||||
elif role == Qt.ItemDataRole.FontRole:
|
||||
return self.font_role(row, column, prd)
|
||||
elif role == Qt.ItemDataRole.TextAlignmentRole:
|
||||
pass
|
||||
elif role == Qt.ItemDataRole.BackgroundRole:
|
||||
return self.background_role(row, column, prd)
|
||||
elif role == Qt.ItemDataRole.ForegroundRole:
|
||||
pass
|
||||
elif role == Qt.ItemDataRole.CheckStateRole:
|
||||
pass
|
||||
elif role == Qt.ItemDataRole.InitialSortOrderRole:
|
||||
pass
|
||||
dispatch_table = {
|
||||
int(Qt.ItemDataRole.DisplayRole): self.display_role,
|
||||
int(Qt.ItemDataRole.EditRole): self.edit_role,
|
||||
int(Qt.ItemDataRole.FontRole): self.font_role,
|
||||
int(Qt.ItemDataRole.BackgroundRole): self.background_role,
|
||||
}
|
||||
|
||||
if role in dispatch_table:
|
||||
return dispatch_table[role](row, column, prd)
|
||||
|
||||
# Document other roles but don't use them
|
||||
if role in [
|
||||
Qt.ItemDataRole.DecorationRole,
|
||||
Qt.ItemDataRole.ToolTipRole,
|
||||
Qt.ItemDataRole.StatusTipRole,
|
||||
Qt.ItemDataRole.WhatsThisRole,
|
||||
Qt.ItemDataRole.SizeHintRole,
|
||||
Qt.ItemDataRole.TextAlignmentRole,
|
||||
Qt.ItemDataRole.ForegroundRole,
|
||||
Qt.ItemDataRole.CheckStateRole,
|
||||
Qt.ItemDataRole.InitialSortOrderRole,
|
||||
]:
|
||||
return QVariant()
|
||||
|
||||
# Fall through to no-op
|
||||
return QVariant()
|
||||
@ -363,36 +362,39 @@ class PlaylistModel(QAbstractTableModel):
|
||||
self.signals.span_cells_signal.emit(
|
||||
row, HEADER_NOTES_COLUMN, 1, self.columnCount() - 1
|
||||
)
|
||||
return QVariant(self.header_text(prd))
|
||||
header_text = self.header_text(prd)
|
||||
if not header_text:
|
||||
return QVariant(Config.TEXT_NO_TRACK_NO_NOTE)
|
||||
else:
|
||||
return QVariant(self.header_text(prd))
|
||||
else:
|
||||
return QVariant()
|
||||
|
||||
if column == Col.START_GAP.value:
|
||||
return QVariant(prd.start_gap)
|
||||
if column == Col.TITLE.value:
|
||||
return QVariant(prd.title)
|
||||
if column == Col.ARTIST.value:
|
||||
return QVariant(prd.artist)
|
||||
if column == Col.DURATION.value:
|
||||
return QVariant(ms_to_mmss(prd.duration))
|
||||
if column == Col.START_TIME.value:
|
||||
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 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:
|
||||
return QVariant(prd.bitrate)
|
||||
if column == Col.NOTE.value:
|
||||
return QVariant(prd.note)
|
||||
|
||||
dispatch_table = {
|
||||
Col.START_GAP.value: QVariant(prd.start_gap),
|
||||
Col.TITLE.value: QVariant(prd.title),
|
||||
Col.ARTIST.value: QVariant(prd.artist),
|
||||
Col.DURATION.value: QVariant(ms_to_mmss(prd.duration)),
|
||||
Col.LAST_PLAYED.value: QVariant(get_relative_date(prd.lastplayed)),
|
||||
Col.BITRATE.value: QVariant(prd.bitrate),
|
||||
Col.NOTE.value: QVariant(prd.note),
|
||||
}
|
||||
if column in dispatch_table:
|
||||
return dispatch_table[column]
|
||||
|
||||
return QVariant()
|
||||
|
||||
@ -408,26 +410,6 @@ class PlaylistModel(QAbstractTableModel):
|
||||
super().endResetModel()
|
||||
self.row_order_changed(self.playlist_id)
|
||||
|
||||
def get_duplicate_rows(self) -> List[int]:
|
||||
"""
|
||||
Return a list of duplicate rows. If track appears in rows 2, 3 and 4, return [3, 4]
|
||||
(ie, ignore the first, not-yet-duplicate, track).
|
||||
"""
|
||||
|
||||
found = []
|
||||
result = []
|
||||
|
||||
for i in range(len(self.playlist_rows)):
|
||||
track_id = self.playlist_rows[i].track_id
|
||||
if track_id is None:
|
||||
continue
|
||||
if track_id in found:
|
||||
result.append(i)
|
||||
else:
|
||||
found.append(track_id)
|
||||
|
||||
return result
|
||||
|
||||
def edit_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant:
|
||||
"""
|
||||
Return text for editing
|
||||
@ -479,6 +461,26 @@ class PlaylistModel(QAbstractTableModel):
|
||||
|
||||
return QVariant(boldfont)
|
||||
|
||||
def get_duplicate_rows(self) -> List[int]:
|
||||
"""
|
||||
Return a list of duplicate rows. If track appears in rows 2, 3 and 4, return [3, 4]
|
||||
(ie, ignore the first, not-yet-duplicate, track).
|
||||
"""
|
||||
|
||||
found = []
|
||||
result = []
|
||||
|
||||
for i in range(len(self.playlist_rows)):
|
||||
track_id = self.playlist_rows[i].track_id
|
||||
if track_id is None:
|
||||
continue
|
||||
if track_id in found:
|
||||
result.append(i)
|
||||
else:
|
||||
found.append(track_id)
|
||||
|
||||
return result
|
||||
|
||||
def _get_new_row_number(self, proposed_row_number: Optional[int]) -> int:
|
||||
"""
|
||||
Sanitises proposed new row number.
|
||||
@ -498,6 +500,13 @@ class PlaylistModel(QAbstractTableModel):
|
||||
|
||||
return new_row_number
|
||||
|
||||
def get_row_info(self, row_number: int) -> PlaylistRowData:
|
||||
"""
|
||||
Return info about passed row
|
||||
"""
|
||||
|
||||
return self.playlist_rows[row_number]
|
||||
|
||||
def get_row_track_path(self, row_number: int) -> str:
|
||||
"""
|
||||
Return path of track associated with row or empty string if no track associated
|
||||
@ -516,13 +525,6 @@ class PlaylistModel(QAbstractTableModel):
|
||||
|
||||
return duration
|
||||
|
||||
def get_row_info(self, row_number: int) -> PlaylistRowData:
|
||||
"""
|
||||
Return info about passed row
|
||||
"""
|
||||
|
||||
return self.playlist_rows[row_number]
|
||||
|
||||
def get_unplayed_rows(self) -> List[int]:
|
||||
"""
|
||||
Return a list of unplayed row numbers
|
||||
@ -677,22 +679,6 @@ class PlaylistModel(QAbstractTableModel):
|
||||
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
|
||||
"""
|
||||
|
||||
if row_number in self.playlist_rows:
|
||||
return self.playlist_rows[row_number].path == ""
|
||||
return False
|
||||
|
||||
def is_played_row(self, row_number: int) -> bool:
|
||||
"""
|
||||
Return True if row is an unplayed track row, else False
|
||||
"""
|
||||
|
||||
return self.playlist_rows[row_number].played
|
||||
|
||||
def insert_row(
|
||||
self,
|
||||
proposed_row_number: Optional[int],
|
||||
@ -737,6 +723,22 @@ class PlaylistModel(QAbstractTableModel):
|
||||
for modified_row in modified_rows:
|
||||
self.invalidate_row(modified_row)
|
||||
|
||||
def is_header_row(self, row_number: int) -> bool:
|
||||
"""
|
||||
Return True if row is a header row, else False
|
||||
"""
|
||||
|
||||
if row_number in self.playlist_rows:
|
||||
return self.playlist_rows[row_number].path == ""
|
||||
return False
|
||||
|
||||
def is_played_row(self, row_number: int) -> bool:
|
||||
"""
|
||||
Return True if row is an unplayed track row, else False
|
||||
"""
|
||||
|
||||
return self.playlist_rows[row_number].played
|
||||
|
||||
def is_track_in_playlist(self, track_id: int) -> Optional[PlaylistRowData]:
|
||||
"""
|
||||
If this track_id is in the playlist, return the PlaylistRowData object
|
||||
@ -749,21 +751,6 @@ class PlaylistModel(QAbstractTableModel):
|
||||
|
||||
return None
|
||||
|
||||
def mark_unplayed(self, row_numbers: List[int]) -> None:
|
||||
"""
|
||||
Mark row as unplayed
|
||||
"""
|
||||
|
||||
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:
|
||||
"""
|
||||
Move the playlist rows given to to_row and below.
|
||||
@ -829,6 +816,21 @@ class PlaylistModel(QAbstractTableModel):
|
||||
self.signals.row_order_changed_signal.emit(self.playlist_id)
|
||||
self.invalidate_rows(list(row_map.keys()))
|
||||
|
||||
def mark_unplayed(self, row_numbers: List[int]) -> None:
|
||||
"""
|
||||
Mark row as unplayed
|
||||
"""
|
||||
|
||||
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_between_playlists(
|
||||
self, from_rows: List[int], to_row_number: int, to_playlist_id: int
|
||||
) -> None:
|
||||
@ -969,7 +971,6 @@ class PlaylistModel(QAbstractTableModel):
|
||||
Actions required:
|
||||
- sanity check
|
||||
- update display
|
||||
- update track times
|
||||
"""
|
||||
|
||||
# Sanity check
|
||||
@ -1124,7 +1125,7 @@ class PlaylistModel(QAbstractTableModel):
|
||||
self.signals.next_track_changed_signal.emit()
|
||||
return
|
||||
|
||||
# Update playing_trtack
|
||||
# Update playing_track
|
||||
with Session() as session:
|
||||
track_sequence.next = PlaylistTrack()
|
||||
try:
|
||||
@ -1331,14 +1332,14 @@ class PlaylistProxyModel(QSortFilterProxyModel):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
playlist_model: PlaylistModel,
|
||||
data_model: PlaylistModel,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
self.playlist_model = playlist_model
|
||||
self.data_model = data_model
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.setSourceModel(playlist_model)
|
||||
self.setSourceModel(data_model)
|
||||
# Search all columns
|
||||
self.setFilterKeyColumn(-1)
|
||||
|
||||
@ -1347,8 +1348,8 @@ class PlaylistProxyModel(QSortFilterProxyModel):
|
||||
Subclass to filter by played status
|
||||
"""
|
||||
|
||||
if self.playlist_model.played_tracks_hidden:
|
||||
if self.playlist_model.is_played_row(source_row):
|
||||
if self.data_model.played_tracks_hidden:
|
||||
if self.data_model.is_played_row(source_row):
|
||||
# Don't hide current or next track
|
||||
with Session() as session:
|
||||
if track_sequence.next.plr_id:
|
||||
@ -1356,7 +1357,7 @@ class PlaylistProxyModel(QSortFilterProxyModel):
|
||||
if (
|
||||
next_plr
|
||||
and next_plr.plr_rownum == source_row
|
||||
and next_plr.playlist_id == self.playlist_model.playlist_id
|
||||
and next_plr.playlist_id == self.data_model.playlist_id
|
||||
):
|
||||
return True
|
||||
if track_sequence.now.plr_id:
|
||||
@ -1386,25 +1387,25 @@ class PlaylistProxyModel(QSortFilterProxyModel):
|
||||
# ######################################
|
||||
|
||||
def current_track_started(self):
|
||||
return self.playlist_model.current_track_started()
|
||||
return self.data_model.current_track_started()
|
||||
|
||||
def delete_rows(self, row_numbers: List[int]) -> None:
|
||||
return self.playlist_model.delete_rows(row_numbers)
|
||||
return self.data_model.delete_rows(row_numbers)
|
||||
|
||||
def get_duplicate_rows(self) -> List[int]:
|
||||
return self.playlist_model.get_duplicate_rows()
|
||||
return self.data_model.get_duplicate_rows()
|
||||
|
||||
def get_rows_duration(self, row_numbers: List[int]) -> int:
|
||||
return self.playlist_model.get_rows_duration(row_numbers)
|
||||
return self.data_model.get_rows_duration(row_numbers)
|
||||
|
||||
def get_row_info(self, row_number: int) -> PlaylistRowData:
|
||||
return self.playlist_model.get_row_info(row_number)
|
||||
return self.data_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)
|
||||
return self.data_model.get_row_track_path(row_number)
|
||||
|
||||
def hide_played_tracks(self, hide: bool) -> None:
|
||||
return self.playlist_model.hide_played_tracks(hide)
|
||||
return self.data_model.hide_played_tracks(hide)
|
||||
|
||||
def insert_row(
|
||||
self,
|
||||
@ -1412,70 +1413,68 @@ class PlaylistProxyModel(QSortFilterProxyModel):
|
||||
track_id: Optional[int] = None,
|
||||
note: Optional[str] = None,
|
||||
) -> None:
|
||||
return self.playlist_model.insert_row(proposed_row_number, track_id, note)
|
||||
return self.data_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)
|
||||
return self.data_model.is_header_row(row_number)
|
||||
|
||||
def is_played_row(self, row_number: int) -> bool:
|
||||
return self.playlist_model.is_played_row(row_number)
|
||||
return self.data_model.is_played_row(row_number)
|
||||
|
||||
def is_track_in_playlist(self, track_id: int) -> Optional[PlaylistRowData]:
|
||||
return self.playlist_model.is_track_in_playlist(track_id)
|
||||
return self.data_model.is_track_in_playlist(track_id)
|
||||
|
||||
def mark_unplayed(self, row_numbers: List[int]) -> None:
|
||||
return self.playlist_model.mark_unplayed(row_numbers)
|
||||
return self.data_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)
|
||||
return self.data_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(
|
||||
return self.data_model.move_rows_between_playlists(
|
||||
from_rows, to_row_number, to_playlist_id
|
||||
)
|
||||
|
||||
def move_track_add_note(
|
||||
self, new_row_number: int, existing_prd: PlaylistRowData, note: str
|
||||
) -> None:
|
||||
return self.playlist_model.move_track_add_note(
|
||||
new_row_number, existing_prd, note
|
||||
)
|
||||
return self.data_model.move_track_add_note(new_row_number, existing_prd, note)
|
||||
|
||||
def move_track_to_header(
|
||||
self, header_row_number: int, existing_prd: PlaylistRowData, note: Optional[str]
|
||||
) -> None:
|
||||
return self.playlist_model.move_track_to_header(
|
||||
return self.data_model.move_track_to_header(
|
||||
header_row_number, existing_prd, note
|
||||
)
|
||||
|
||||
def open_in_audacity(self, row_number: int) -> None:
|
||||
return self.playlist_model.open_in_audacity(row_number)
|
||||
return self.data_model.open_in_audacity(row_number)
|
||||
|
||||
def previous_track_ended(self) -> None:
|
||||
return self.playlist_model.previous_track_ended()
|
||||
return self.data_model.previous_track_ended()
|
||||
|
||||
def remove_track(self, row_number: int) -> None:
|
||||
return self.playlist_model.remove_track(row_number)
|
||||
return self.data_model.remove_track(row_number)
|
||||
|
||||
def rescan_track(self, row_number: int) -> None:
|
||||
return self.playlist_model.rescan_track(row_number)
|
||||
return self.data_model.rescan_track(row_number)
|
||||
|
||||
def set_next_row(self, row_number: Optional[int]) -> None:
|
||||
return self.playlist_model.set_next_row(row_number)
|
||||
return self.data_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)
|
||||
return self.data_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)
|
||||
return self.data_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)
|
||||
return self.data_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)
|
||||
return self.data_model.sort_by_title(row_numbers)
|
||||
|
||||
def update_track_times(self) -> None:
|
||||
return self.playlist_model.update_track_times()
|
||||
return self.data_model.update_track_times()
|
||||
|
||||
299
app/playlists.py
299
app/playlists.py
@ -34,6 +34,7 @@ from config import Config
|
||||
from helpers import (
|
||||
ask_yes_no,
|
||||
ms_to_mmss,
|
||||
show_OK,
|
||||
show_warning,
|
||||
)
|
||||
from models import Settings
|
||||
@ -50,9 +51,9 @@ class EscapeDelegate(QStyledItemDelegate):
|
||||
- checks with user before abandoning edit on Escape
|
||||
"""
|
||||
|
||||
def __init__(self, parent, playlist_model: PlaylistModel) -> None:
|
||||
def __init__(self, parent, data_model: PlaylistModel) -> None:
|
||||
super().__init__(parent)
|
||||
self.playlist_model = playlist_model
|
||||
self.data_model = data_model
|
||||
self.signals = MusicMusterSignals()
|
||||
|
||||
def createEditor(
|
||||
@ -113,7 +114,7 @@ class EscapeDelegate(QStyledItemDelegate):
|
||||
else:
|
||||
edit_index = index
|
||||
|
||||
value = self.playlist_model.data(edit_index, Qt.ItemDataRole.EditRole)
|
||||
value = self.data_model.data(edit_index, Qt.ItemDataRole.EditRole)
|
||||
editor.setPlainText(value.value())
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
@ -124,7 +125,7 @@ class EscapeDelegate(QStyledItemDelegate):
|
||||
edit_index = index
|
||||
|
||||
value = editor.toPlainText().strip()
|
||||
self.playlist_model.setData(edit_index, value, Qt.ItemDataRole.EditRole)
|
||||
self.data_model.setData(edit_index, value, Qt.ItemDataRole.EditRole)
|
||||
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
editor.setGeometry(option.rect)
|
||||
@ -149,6 +150,10 @@ class PlaylistStyle(QProxyStyle):
|
||||
|
||||
|
||||
class PlaylistTab(QTableView):
|
||||
"""
|
||||
The playlist view
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
musicmuster: "Window",
|
||||
@ -161,9 +166,9 @@ class PlaylistTab(QTableView):
|
||||
self.playlist_id = playlist_id
|
||||
|
||||
# Set up widget
|
||||
self.playlist_model = PlaylistModel(playlist_id)
|
||||
self.proxy_model = PlaylistProxyModel(self.playlist_model)
|
||||
self.setItemDelegate(EscapeDelegate(self, self.playlist_model))
|
||||
self.data_model = PlaylistModel(playlist_id)
|
||||
self.proxy_model = PlaylistProxyModel(self.data_model)
|
||||
self.setItemDelegate(EscapeDelegate(self, self.data_model))
|
||||
self.setAlternatingRowColors(True)
|
||||
self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
|
||||
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
|
||||
@ -190,10 +195,6 @@ class PlaylistTab(QTableView):
|
||||
self.signals.resize_rows_signal.connect(self.resizeRowsToContents)
|
||||
self.signals.span_cells_signal.connect(self._span_cells)
|
||||
|
||||
# Initialise miscellaneous instance variables
|
||||
self.search_text: str = ""
|
||||
self.sort_undo: List[int] = []
|
||||
|
||||
# Selection model
|
||||
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
@ -202,6 +203,8 @@ class PlaylistTab(QTableView):
|
||||
self.setModel(self.proxy_model)
|
||||
self._set_column_widths()
|
||||
|
||||
# ########## Overrident class functions ##########
|
||||
|
||||
def closeEditor(
|
||||
self, editor: QWidget | None, hint: QAbstractItemDelegate.EndEditHint
|
||||
) -> None:
|
||||
@ -220,7 +223,7 @@ class PlaylistTab(QTableView):
|
||||
|
||||
# Update start times in case a start time in a note has been
|
||||
# edited
|
||||
self.playlist_model.update_track_times()
|
||||
self.data_model.update_track_times()
|
||||
|
||||
def dropEvent(self, event):
|
||||
if event.source() is not self or (
|
||||
@ -240,6 +243,7 @@ class PlaylistTab(QTableView):
|
||||
|
||||
# Reset drag mode to allow row selection by dragging
|
||||
self.setDragEnabled(False)
|
||||
|
||||
# Deselect rows
|
||||
self.clear_selection()
|
||||
|
||||
@ -252,7 +256,7 @@ class PlaylistTab(QTableView):
|
||||
event: Optional[QEvent],
|
||||
) -> bool:
|
||||
"""
|
||||
Override PySide2.QAbstractItemView.edit to catch when editing starts
|
||||
Override QAbstractItemView.edit to catch when editing starts
|
||||
|
||||
Editing only ever starts with a double click on a cell
|
||||
"""
|
||||
@ -264,6 +268,43 @@ class PlaylistTab(QTableView):
|
||||
|
||||
return result
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
"""
|
||||
Enable dragging if rows are selected
|
||||
"""
|
||||
|
||||
if self.selectedIndexes():
|
||||
self.setDragEnabled(True)
|
||||
else:
|
||||
self.setDragEnabled(False)
|
||||
self.reset()
|
||||
super().mouseReleaseEvent(event)
|
||||
|
||||
def selectionChanged(
|
||||
self, selected: QItemSelection, deselected: QItemSelection
|
||||
) -> None:
|
||||
"""
|
||||
Toggle drag behaviour according to whether rows are selected
|
||||
"""
|
||||
|
||||
selected_rows = self.get_selected_rows()
|
||||
# If no rows are selected, we have nothing to do
|
||||
if len(selected_rows) == 0:
|
||||
self.musicmuster.lblSumPlaytime.setText("")
|
||||
else:
|
||||
selected_duration = self.data_model.get_rows_duration(
|
||||
self.get_selected_rows()
|
||||
)
|
||||
if selected_duration > 0:
|
||||
self.musicmuster.lblSumPlaytime.setText(
|
||||
f"Selected duration: {ms_to_mmss(selected_duration)}"
|
||||
)
|
||||
else:
|
||||
self.musicmuster.lblSumPlaytime.setText("")
|
||||
|
||||
super().selectionChanged(selected, deselected)
|
||||
|
||||
# ########## Custom functions ##########
|
||||
def _add_context_menu(
|
||||
self,
|
||||
text: str,
|
||||
@ -286,121 +327,6 @@ class PlaylistTab(QTableView):
|
||||
|
||||
return menu_item
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
"""
|
||||
Enable dragging if rows are selected
|
||||
"""
|
||||
|
||||
if self.selectedIndexes():
|
||||
self.setDragEnabled(True)
|
||||
else:
|
||||
self.setDragEnabled(False)
|
||||
self.reset()
|
||||
super().mouseReleaseEvent(event)
|
||||
|
||||
# # ########## Externally called functions ##########
|
||||
|
||||
def clear_selection(self) -> None:
|
||||
"""Unselect all tracks and reset drag mode"""
|
||||
|
||||
self.clearSelection()
|
||||
self.setDragEnabled(False)
|
||||
|
||||
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) -> 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 []
|
||||
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():
|
||||
return sm.selectedRows()
|
||||
return []
|
||||
|
||||
def get_selected_row_track_path(self) -> str:
|
||||
"""
|
||||
Return the path of the selected row. If no row selected or selected
|
||||
row does not have a track, return empty string.
|
||||
"""
|
||||
|
||||
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 set_row_as_next_track(self) -> None:
|
||||
"""
|
||||
Set selected row as next track
|
||||
"""
|
||||
|
||||
model_row_number = self.selected_model_row_number()
|
||||
if model_row_number is None:
|
||||
return
|
||||
self.playlist_model.set_next_row(model_row_number)
|
||||
self.clearSelection()
|
||||
|
||||
# # # ########## Internally called functions ##########
|
||||
|
||||
def _add_track(self) -> None:
|
||||
"""Add a track to a section header making it a normal track row"""
|
||||
|
||||
@ -412,7 +338,7 @@ class PlaylistTab(QTableView):
|
||||
dlg = TrackSelectDialog(
|
||||
session=session,
|
||||
new_row_number=model_row_number,
|
||||
model=self.playlist_model,
|
||||
model=self.data_model,
|
||||
add_to_header=True,
|
||||
)
|
||||
dlg.exec()
|
||||
@ -516,6 +442,12 @@ class PlaylistTab(QTableView):
|
||||
"Copy track path", lambda: self._copy_path(model_row_number)
|
||||
)
|
||||
|
||||
def clear_selection(self) -> None:
|
||||
"""Unselect all tracks and reset drag mode"""
|
||||
|
||||
self.clearSelection()
|
||||
self.setDragEnabled(False)
|
||||
|
||||
def _column_resize(self, column_number: int, _old: int, _new: int) -> None:
|
||||
"""
|
||||
Called when column width changes. Save new width to database.
|
||||
@ -546,7 +478,7 @@ class PlaylistTab(QTableView):
|
||||
to the clipboard. Otherwise, return None.
|
||||
"""
|
||||
|
||||
track_path = self.playlist_model.get_row_info(row_number).path
|
||||
track_path = self.data_model.get_row_info(row_number).path
|
||||
if not track_path:
|
||||
return
|
||||
|
||||
@ -583,9 +515,20 @@ class PlaylistTab(QTableView):
|
||||
if not ask_yes_no("Delete rows", f"Really delete {row_count} row{plural}?"):
|
||||
return
|
||||
|
||||
self.playlist_model.delete_rows(self.selected_model_row_numbers())
|
||||
self.data_model.delete_rows(self.selected_model_row_numbers())
|
||||
self.clear_selection()
|
||||
|
||||
def get_selected_row_track_path(self) -> str:
|
||||
"""
|
||||
Return the path of the selected row. If no row selected or selected
|
||||
row does not have a track, return empty string.
|
||||
"""
|
||||
|
||||
model_row_number = self.selected_model_row_number()
|
||||
if model_row_number is None:
|
||||
return ""
|
||||
return self.data_model.get_row_track_path(model_row_number)
|
||||
|
||||
def get_selected_rows(self) -> List[int]:
|
||||
"""Return a list of selected row numbers sorted by row"""
|
||||
|
||||
@ -596,7 +539,7 @@ class PlaylistTab(QTableView):
|
||||
def _info_row(self, row_number: int) -> None:
|
||||
"""Display popup with info re row"""
|
||||
|
||||
prd = self.playlist_model.get_row_info(row_number)
|
||||
prd = self.data_model.get_row_info(row_number)
|
||||
if prd:
|
||||
txt = (
|
||||
f"Title: {prd.title}\n"
|
||||
@ -610,23 +553,18 @@ class PlaylistTab(QTableView):
|
||||
else:
|
||||
txt = f"Can't find info about row{row_number}"
|
||||
|
||||
info: QMessageBox = QMessageBox(self)
|
||||
info.setIcon(QMessageBox.Icon.Information)
|
||||
info.setText(txt)
|
||||
info.setStandardButtons(QMessageBox.StandardButton.Ok)
|
||||
info.setDefaultButton(QMessageBox.StandardButton.Cancel)
|
||||
info.exec()
|
||||
show_OK(self.musicmuster, "Track info", txt)
|
||||
|
||||
def _mark_as_unplayed(self, row_numbers: List[int]) -> None:
|
||||
"""Rescan track"""
|
||||
|
||||
self.playlist_model.mark_unplayed(row_numbers)
|
||||
self.data_model.mark_unplayed(row_numbers)
|
||||
self.clear_selection()
|
||||
|
||||
def _rescan(self, row_number: int) -> None:
|
||||
"""Rescan track"""
|
||||
|
||||
self.playlist_model.rescan_track(row_number)
|
||||
self.data_model.rescan_track(row_number)
|
||||
self.clear_selection()
|
||||
|
||||
def scroll_to_top(self, row_number: int) -> None:
|
||||
@ -653,36 +591,62 @@ class PlaylistTab(QTableView):
|
||||
# We need to be in MultiSelection mode
|
||||
self.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
|
||||
# Get the duplicate rows
|
||||
duplicate_rows = self.playlist_model.get_duplicate_rows()
|
||||
duplicate_rows = self.data_model.get_duplicate_rows()
|
||||
# Select the rows
|
||||
for duplicate_row in duplicate_rows:
|
||||
self.selectRow(duplicate_row)
|
||||
# Reset selection mode
|
||||
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||
|
||||
def selectionChanged(
|
||||
self, selected: QItemSelection, deselected: QItemSelection
|
||||
) -> None:
|
||||
def selected_model_row_number(self) -> Optional[int]:
|
||||
"""
|
||||
Toggle drag behaviour according to whether rows are selected
|
||||
Return the model row number corresponding to the selected row or None
|
||||
"""
|
||||
|
||||
selected_rows = self.get_selected_rows()
|
||||
# If no rows are selected, we have nothing to do
|
||||
if len(selected_rows) == 0:
|
||||
self.musicmuster.lblSumPlaytime.setText("")
|
||||
else:
|
||||
selected_duration = self.playlist_model.get_rows_duration(
|
||||
self.get_selected_rows()
|
||||
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) -> 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 []
|
||||
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"
|
||||
)
|
||||
if selected_duration > 0:
|
||||
self.musicmuster.lblSumPlaytime.setText(
|
||||
f"Selected duration: {ms_to_mmss(selected_duration)}"
|
||||
)
|
||||
else:
|
||||
self.musicmuster.lblSumPlaytime.setText("")
|
||||
return None
|
||||
|
||||
super().selectionChanged(selected, deselected)
|
||||
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():
|
||||
return sm.selectedRows()
|
||||
return []
|
||||
|
||||
def _set_column_widths(self) -> None:
|
||||
"""Column widths from settings"""
|
||||
@ -704,6 +668,17 @@ class PlaylistTab(QTableView):
|
||||
else:
|
||||
self.setColumnWidth(column_number, Config.DEFAULT_COLUMN_WIDTH)
|
||||
|
||||
def set_row_as_next_track(self) -> None:
|
||||
"""
|
||||
Set selected row as next track
|
||||
"""
|
||||
|
||||
model_row_number = self.selected_model_row_number()
|
||||
if model_row_number is None:
|
||||
return
|
||||
self.data_model.set_next_row(model_row_number)
|
||||
self.clearSelection()
|
||||
|
||||
def _span_cells(self, row: int, column: int, rowSpan: int, columnSpan: int) -> None:
|
||||
"""
|
||||
Implement spanning of cells, initiated by signal
|
||||
@ -711,9 +686,7 @@ class PlaylistTab(QTableView):
|
||||
|
||||
model = self.proxy_model
|
||||
if hasattr(model, "mapToSource"):
|
||||
edit_index = model.mapFromSource(
|
||||
self.playlist_model.createIndex(row, column)
|
||||
)
|
||||
edit_index = model.mapFromSource(self.data_model.createIndex(row, column))
|
||||
row = edit_index.row()
|
||||
column = edit_index.column()
|
||||
|
||||
@ -731,5 +704,5 @@ class PlaylistTab(QTableView):
|
||||
def _unmark_as_next(self) -> None:
|
||||
"""Rescan track"""
|
||||
|
||||
self.playlist_model.set_next_row(None)
|
||||
self.data_model.set_next_row(None)
|
||||
self.clear_selection()
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# #!/usr/bin/env python
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
import os
|
||||
|
||||
|
||||
@ -45,7 +45,7 @@ def test_get_relative_date():
|
||||
assert get_relative_date(None) == "Never"
|
||||
today_at_10 = datetime.now().replace(hour=10, minute=0)
|
||||
today_at_11 = datetime.now().replace(hour=11, minute=0)
|
||||
assert get_relative_date(today_at_10, today_at_11) == "10:00"
|
||||
assert get_relative_date(today_at_10, today_at_11) == "Today 10:00"
|
||||
eight_days_ago = today_at_10 - timedelta(days=8)
|
||||
assert get_relative_date(eight_days_ago, today_at_11) == "1 week, 1 day ago"
|
||||
sixteen_days_ago = today_at_10 - timedelta(days=16)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user