Compare commits

..

No commits in common. "bdf7b0979d9111882d43cca9729b4d169aeaa4f4" and "0f8c648d1c2cf7a0572a1b121ff7fa7363965fa8" have entirely different histories.

3 changed files with 175 additions and 217 deletions

View File

@ -127,17 +127,18 @@ class Music:
# #
# with lock: # with lock:
# return self.player.set_time(ms) # return self.player.set_time(ms)
#
def set_volume(self, volume, set_default=True): # def set_volume(self, volume, set_default=True):
"""Set maximum volume used for player""" # """Set maximum volume used for player"""
#
if not self.player: # with lock:
return # if not self.player:
# return
if set_default: #
self.max_volume = volume # if set_default:
# self.max_volume = volume
self.player.audio_set_volume(volume) #
# self.player.audio_set_volume(volume)
def stop(self) -> float: def stop(self) -> float:
"""Immediately stop playing""" """Immediately stop playing"""

View File

@ -174,8 +174,9 @@ class Window(QMainWindow, Ui_MainWindow):
lambda: self.tabPlaylist.currentWidget().set_selected_as_next()) lambda: self.tabPlaylist.currentWidget().set_selected_as_next())
self.actionSkipToNext.triggered.connect(self.play_next) self.actionSkipToNext.triggered.connect(self.play_next)
self.actionStop.triggered.connect(self.stop) self.actionStop.triggered.connect(self.stop)
self.btnDrop3db.clicked.connect(self.drop3db) # ***kae
self.btnHidePlayed.clicked.connect(self.hide_played) # self.btnDrop3db.clicked.connect(self.drop3db)
# self.btnHidePlayed.clicked.connect(self.hide_played)
self.btnFade.clicked.connect(self.fade) self.btnFade.clicked.connect(self.fade)
self.btnStop.clicked.connect(self.stop) self.btnStop.clicked.connect(self.stop)
self.tabPlaylist.tabCloseRequested.connect(self.close_tab) self.tabPlaylist.tabCloseRequested.connect(self.close_tab)
@ -281,14 +282,14 @@ class Window(QMainWindow, Ui_MainWindow):
f.write( f.write(
f"{playdate.track.artist},{playdate.track.title}\n" f"{playdate.track.artist},{playdate.track.title}\n"
) )
#
def drop3db(self) -> None: # def drop3db(self) -> None:
"""Drop music level by 3db if button checked""" # """Drop music level by 3db if button checked"""
#
if self.btnDrop3db.isChecked(): # if self.btnDrop3db.isChecked():
self.music.set_volume(Config.VOLUME_VLC_DROP3db, set_default=False) # self.music.set_volume(Config.VOLUME_VLC_DROP3db, set_default=False)
else: # else:
self.music.set_volume(Config.VOLUME_VLC_DEFAULT, set_default=False) # self.music.set_volume(Config.VOLUME_VLC_DEFAULT, set_default=False)
def enable_play_next_controls(self) -> None: def enable_play_next_controls(self) -> None:
""" """
@ -388,21 +389,19 @@ class Window(QMainWindow, Ui_MainWindow):
"""Fade currently playing track""" """Fade currently playing track"""
self.stop_playing(fade=True) self.stop_playing(fade=True)
#
def hide_played(self): # def hide_played(self):
"""Toggle hide played tracks""" # """Toggle hide played tracks"""
#
if self.hide_played_tracks: # if self.hide_played_tracks:
self.hide_played_tracks = False # self.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.btnHidePlayed.setText("Show played") # self.btnHidePlayed.setText("Show played")
# if self.current_track_playlist_tab:
# Update all displayed playlists # with Session() as session:
with Session() as session: # self.current_track_playlist_tab.update_display(session)
for i in range(self.tabPlaylist.count()):
self.tabPlaylist.widget(i).update_display(session)
# #
# def import_track(self) -> None: # def import_track(self) -> None:
# """Import track file""" # """Import track file"""

View File

@ -6,7 +6,7 @@ from collections import namedtuple
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import List, Optional from typing import List, Optional
from PyQt5.QtCore import QEvent, QModelIndex, Qt, pyqtSignal from PyQt5.QtCore import QEvent, Qt, pyqtSignal
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QBrush, QBrush,
QColor, QColor,
@ -14,18 +14,16 @@ from PyQt5.QtGui import (
QDropEvent QDropEvent
) )
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemDelegate,
QAbstractItemView, QAbstractItemView,
QApplication, QApplication,
# QInputDialog, # QInputDialog,
QLineEdit, # QLineEdit,
QMainWindow, QMainWindow,
QMenu, QMenu,
QStyledItemDelegate, # QStyledItemDelegate,
QMessageBox, QMessageBox,
QTableWidget, QTableWidget,
QTableWidgetItem, QTableWidgetItem,
QWidget
) )
from config import Config from config import Config
@ -72,25 +70,25 @@ columns["lastplayed"] = Column(idx=7, heading=Config.COLUMN_NAME_LAST_PLAYED)
columns["row_notes"] = Column(idx=8, heading=Config.COLUMN_NAME_NOTES) columns["row_notes"] = Column(idx=8, heading=Config.COLUMN_NAME_NOTES)
class NoSelectDelegate(QStyledItemDelegate): # class NoSelectDelegate(QStyledItemDelegate):
""" # """https://stackoverflow.com/questions/72790705/dont-select-text-in-qtablewidget-cell-when-editing/72792962#72792962"""
https://stackoverflow.com/questions/72790705/ #
dont-select-text-in-qtablewidget-cell-when-editing/72792962#72792962 # def createEditor(self, parent, option, index):
""" # editor = super().createEditor(parent, option, index)
# if isinstance(editor, QLineEdit):
def createEditor(self, parent, option, index): # def deselect():
editor = super().createEditor(parent, option, index) # # Important! First disconnect, otherwise editor.deselect()
if isinstance(editor, QLineEdit): # # will call again this function
def deselect(): # editor.selectionChanged.disconnect(deselect)
# Important! First disconnect, otherwise editor.deselect() # editor.deselect()
# will call again this function # editor.selectionChanged.connect(deselect)
editor.selectionChanged.disconnect(deselect) # return editor
editor.deselect()
editor.selectionChanged.connect(deselect)
return editor
class PlaylistTab(QTableWidget): class PlaylistTab(QTableWidget):
# cellEditingStarted = QtCore.pyqtSignal(int, int)
# cellEditingEnded = QtCore.pyqtSignal()
# Qt.UserRoles # Qt.UserRoles
ROW_FLAGS = Qt.UserRole ROW_FLAGS = Qt.UserRole
ROW_TRACK_ID = Qt.UserRole + 1 ROW_TRACK_ID = Qt.UserRole + 1
@ -105,12 +103,12 @@ class PlaylistTab(QTableWidget):
self.menu: Optional[QMenu] = None self.menu: Optional[QMenu] = None
self.current_track_start_time: Optional[datetime] = None self.current_track_start_time: Optional[datetime] = None
#
# Don't select text on edit # # Don't select text on edit
self.setItemDelegate(NoSelectDelegate(self)) # self.setItemDelegate(NoSelectDelegate(self))
#
# Set up widget # Set up widget
self.setEditTriggers(QAbstractItemView.DoubleClicked) # self.setEditTriggers(QAbstractItemView.AllEditTriggers)
self.setAlternatingRowColors(True) self.setAlternatingRowColors(True)
self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionBehavior(QAbstractItemView.SelectRows)
@ -148,9 +146,14 @@ class PlaylistTab(QTableWidget):
self.itemSelectionChanged.connect(self._select_event) self.itemSelectionChanged.connect(self._select_event)
self.row_filter: Optional[str] = None self.row_filter: Optional[str] = None
self.edit_cell_type = None # self.editing_cell: bool = False
self.selecting_in_progress = False self.selecting_in_progress = False
# Connect signals # Connect signals
# self.cellChanged.connect(self._cell_changed)
# self.cellClicked.connect(self._edit_note_cell)
# self.cellEditingEnded.connect(self._cell_edit_ended)
# self.cellEditingStarted.connect(self._cell_edit_started)
# self.doubleClicked.connect(self._edit_cell)
self.horizontalHeader().sectionResized.connect(self._column_resize) self.horizontalHeader().sectionResized.connect(self._column_resize)
# Now load our tracks and notes # Now load our tracks and notes
@ -158,8 +161,12 @@ class PlaylistTab(QTableWidget):
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<PlaylistTab(id={self.playlist_id}" return f"<PlaylistTab(id={self.playlist_id}"
#
# ########## Events other than cell editing ########## # # ########## Events ##########
#
# def closeEditor(self, editor, hint): # review
# super(PlaylistTab, self).closeEditor(editor, hint)
# self.cellEditingEnded.emit()
def closeEvent(self, event) -> None: def closeEvent(self, event) -> None:
"""Handle closing playist tab""" """Handle closing playist tab"""
@ -223,6 +230,12 @@ class PlaylistTab(QTableWidget):
self.save_playlist(session) self.save_playlist(session)
self.update_display(session) self.update_display(session)
# def edit(self, index, trigger, event):
# result = super(PlaylistTab, self).edit(index, trigger, event)
# if result:
# self.cellEditingStarted.emit(index.row(), index.column())
# return result
def eventFilter(self, source, event): def eventFilter(self, source, event):
"""Used to process context (right-click) menu, which is defined here""" """Used to process context (right-click) menu, which is defined here"""
@ -314,158 +327,6 @@ class PlaylistTab(QTableWidget):
self.setDragEnabled(False) self.setDragEnabled(False)
super().mouseReleaseEvent(event) super().mouseReleaseEvent(event)
# ########## Cell editing ##########
#
# We only want to allow cell editing on tracks, artists and notes,
# although notes may be section headers.
#
# Once editing starts, we need to disable play controls so that a
# 'return' doesn't play the next track.
#
# Earlier in this file:
# - self.setEditTriggers(QAbstractItemView.DoubleClicked) - triggers
# editing on double-click
# - self.setItemDelegate(NoSelectDelegate(self)) and associated class
# ensure that the text is not selected when editing starts
#
# Call sequences:
# Start editing:
# edit() (called twice; not sure why)
# _cell_edit_started()
# End editing:
# _cell_changed() (only if changes made)
# closeEditor()
# _cell_edit_ended()
# def _edit_note_cell(self, row: int, column: int): # review
# """Called when table is single-clicked"""
# print(f"_edit_note_cell({row=}, {column=}")
# # if column in [FIXUP.COL_ROW_NOTES]:
# # item = self.item(row, column)
# # self.editItem(item)
def _cell_changed(self, row: int, column: int) -> None:
"""Called when cell content has changed"""
# Disable cell changed signal connection as note updates will
# change cell again (metadata)
self.cellChanged.disconnect(self._cell_changed)
new_text = self.item(row, column).text()
track_id = self._get_row_track_id(row)
# Determin cell type changed
with Session() as session:
if self.edit_cell_type == "row_notes":
# Get playlistrow object
plr_id = self._get_playlistrow_id(row)
plr_item = session.get(PlaylistRows, plr_id)
plr_item.note = new_text
# Set/clear row start time accordingly
start_time = self._get_note_text_time(new_text)
if start_time:
self._set_row_start_time(row, start_time)
else:
self._set_row_start_time(row, None)
else:
track = None
if track_id:
track = session.get(Tracks, track_id)
if track:
if self.edit_cell_type == "title":
track.title = new_text
elif self.edit_cell_type == "artist":
track.artist = new_text
# Headers will be incorrect if the edited track is
# previous / current / next TODO: this will require
# the stored data in musicmuster to be updated,
# which currently it isn't).
self.musicmuster.update_headers()
self.edit_cell_type = None
def closeEditor(self,
editor: QWidget,
hint: QAbstractItemDelegate.EndEditHint) -> None:
"""
Override QAbstractItemView.closeEditor to enable play controls
and update display.
"""
# update_display to update start times, such as when a note has
# been edited
with Session() as session:
self.update_display(session)
self.musicmuster.enable_play_next_controls()
super(PlaylistTab, self).closeEditor(editor, hint)
def edit(self, index: QModelIndex,
trigger: QAbstractItemView.EditTrigger,
event: QEvent) -> bool:
"""
Override QAbstractItemView.edit to catch when editing starts
"""
self.edit_cell_type = None
result = super(PlaylistTab, self).edit(index, trigger, event)
if result:
row = index.row()
column = index.column()
# Is this a track row?
track_row = self._get_row_track_id(row)
note_column = 0
if track_row:
# If a track row, we only allow editing of title, artist and
# note. Check that this column is one of those.
self.edit_cell_type = None
if column == columns['title'].idx:
self.edit_cell_type = "title"
elif column == columns['artist'].idx:
self.edit_cell_type = "artist"
elif column == columns['row_notes'].idx:
self.edit_cell_type = "row_notes"
else:
# Can't edit other columns
return
# Check whether we're editing a notes row for later
if self.edit_cell_type == "row_notes":
note_column = columns['row_notes'].idx
else:
# This is a section header. Text is always in row 1.
if column != 1:
return
note_column = 1
self.edit_cell_type = "row_notes"
# Connect signal so we know when cell has changed.
self.cellChanged.connect(self._cell_changed)
# Disable play controls so that keyboard input doesn't
# disturb playing
self.musicmuster.disable_play_next_controls()
# If this is a note cell, we need to remove any existing section
# timing so user can't edit that. Keep it simple: refresh text
# from database. Note column will only be non-zero if we are
# editing a note.
if note_column:
with Session() as session:
plr_id = self._get_playlistrow_id(row)
plr_item = session.get(PlaylistRows, plr_id)
item = self.item(row, note_column)
item.setText(plr_item.note)
return result
# # ########## Externally called functions ########## # # ########## Externally called functions ##########
def clear_next(self, session) -> None: def clear_next(self, session) -> None:
@ -1289,6 +1150,86 @@ class PlaylistTab(QTableWidget):
cb = QApplication.clipboard() cb = QApplication.clipboard()
cb.clear(mode=cb.Clipboard) cb.clear(mode=cb.Clipboard)
cb.setText(pathqs, mode=cb.Clipboard) cb.setText(pathqs, mode=cb.Clipboard)
#
# def _cell_changed(self, row: int, column: int) -> None:
# """Called when cell content has changed"""
#
# if not self.editing_cell:
# return
# if column not in [FIXUP.COL_TITLE, FIXUP.COL_ARTIST]:
# return
#
# new_text: str = self.item(row, column).text()
# log.debug(f"_cell_changed({row=}, {column=}, {new_text=}")
#
# with Session() as session:
# if row in self._get_notes_rows():
# # Save change to database
# note: Notes = self._get_row_notes_object(row, session)
# note.update(session, row, new_text)
# # Set/clear row start time accordingly
# start_time = self._get_note_text_time(new_text)
# if start_time:
# self._set_row_start_time(row, start_time)
# log.debug(
# f"_cell_changed:Note {new_text} contains valid "
# f"{start_time=}"
# )
# else:
# # Reset row start time in case it used to have one
# self._set_row_start_time(row, None)
# log.debug(
# f"_cell_changed:Note {new_text} does not contain "
# "start time"
# )
# else:
# track: Tracks = self._get_row_track_object(row, session)
# if column == FIXUP.COL_ARTIST:
# track.update_artist(session, artist=new_text)
# elif column == FIXUP.COL_TITLE:
# track.update_title(session, title=new_text)
# else:
# log.error("_cell_changed(): unrecognised column")
#
# def _cell_edit_ended(self) -> None:
# """Called when cell edit ends"""
#
# log.debug("_cell_edit_ended()")
#
# self.editing_cell = False
#
# # update_display to update start times, such as when a note has
# # been edited
# with Session() as session:
# self.update_display(session)
#
# self.musicmuster.enable_play_next_controls()
#
# def _cell_edit_started(self, row: int, column: int) -> None:
# """
# Called when cell editing started. Disable play controls so
# that keys work during edit.
# """
#
# log.debug(f"_cell_edit_started({row=}, {column=})")
#
# self.editing_cell = True
# # Disable play controls so that keyboard input doesn't disturb playing
# self.musicmuster.disable_play_next_controls()
#
# # If this is a note cell and it's a section start, we need to
# # remove any existing section timing so user can't edit that.
# # Section timing is only in display of item, not in note text in
# # database. Keep it simple: if this is a note, pull text from
# # database.
#
# if self._is_note_row(row):
# item = self.item(row, FIXUP.COL_TITLE)
# with Session() as session:
# note_object = self._get_row_notes_object(row, session)
# if note_object:
# item.setText(note_object.note)
# return
# def _clear_played_row_status(self, row: int) -> None: # def _clear_played_row_status(self, row: int) -> None:
# """Clear played status on row""" # """Clear played status on row"""
@ -1327,6 +1268,23 @@ class PlaylistTab(QTableWidget):
return (index.row() + 1 if self._is_below(event.pos(), index) return (index.row() + 1 if self._is_below(event.pos(), index)
else index.row()) else index.row())
# def _edit_note_cell(self, row, column): # review
# """Called when table is single-clicked"""
#
# if column in [FIXUP.COL_ROW_NOTES]:
# item = self.item(row, column)
# self.editItem(item)
#
# def _edit_cell(self, mi): # review
# """Called when table is double-clicked"""
#
# row = mi.row()
# column = mi.column()
# item = self.item(row, column)
#
# if column in [FIXUP.COL_TITLE, FIXUP.COL_ARTIST]:
# self.editItem(item)
#
# def _get_notes_rows(self) -> List[int]: # def _get_notes_rows(self) -> List[int]:
# """Return rows marked as notes, or None""" # """Return rows marked as notes, or None"""
# #