Compare commits

...

4 Commits

Author SHA1 Message Date
Keith Edmunds
bdf7b0979d Cell editing rewrite
Simplied, commented, no longer using custom signals, all functions
have type information.
2022-08-13 22:12:22 +01:00
Keith Edmunds
cee84563fb WIP re editing 2022-08-13 21:13:03 +01:00
Keith Edmunds
4d9bf9a36b Hide/show played tracks button working 2022-08-13 16:32:37 +01:00
Keith Edmunds
ce0c3de40d 3dB drop button working 2022-08-13 16:11:55 +01:00
3 changed files with 217 additions and 175 deletions

View File

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

View File

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

View File

@ -6,7 +6,7 @@ from collections import namedtuple
from datetime import datetime, timedelta
from typing import List, Optional
from PyQt5.QtCore import QEvent, Qt, pyqtSignal
from PyQt5.QtCore import QEvent, QModelIndex, Qt, pyqtSignal
from PyQt5.QtGui import (
QBrush,
QColor,
@ -14,16 +14,18 @@ from PyQt5.QtGui import (
QDropEvent
)
from PyQt5.QtWidgets import (
QAbstractItemDelegate,
QAbstractItemView,
QApplication,
# QInputDialog,
# QLineEdit,
QLineEdit,
QMainWindow,
QMenu,
# QStyledItemDelegate,
QStyledItemDelegate,
QMessageBox,
QTableWidget,
QTableWidgetItem,
QWidget
)
from config import Config
@ -70,25 +72,25 @@ columns["lastplayed"] = Column(idx=7, heading=Config.COLUMN_NAME_LAST_PLAYED)
columns["row_notes"] = Column(idx=8, heading=Config.COLUMN_NAME_NOTES)
# class NoSelectDelegate(QStyledItemDelegate):
# """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 deselect():
# # Important! First disconnect, otherwise editor.deselect()
# # will call again this function
# editor.selectionChanged.disconnect(deselect)
# editor.deselect()
# editor.selectionChanged.connect(deselect)
# return editor
class NoSelectDelegate(QStyledItemDelegate):
"""
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 deselect():
# Important! First disconnect, otherwise editor.deselect()
# will call again this function
editor.selectionChanged.disconnect(deselect)
editor.deselect()
editor.selectionChanged.connect(deselect)
return editor
class PlaylistTab(QTableWidget):
# cellEditingStarted = QtCore.pyqtSignal(int, int)
# cellEditingEnded = QtCore.pyqtSignal()
# Qt.UserRoles
ROW_FLAGS = Qt.UserRole
ROW_TRACK_ID = Qt.UserRole + 1
@ -103,12 +105,12 @@ class PlaylistTab(QTableWidget):
self.menu: Optional[QMenu] = None
self.current_track_start_time: Optional[datetime] = None
#
# # Don't select text on edit
# self.setItemDelegate(NoSelectDelegate(self))
#
# Don't select text on edit
self.setItemDelegate(NoSelectDelegate(self))
# Set up widget
# self.setEditTriggers(QAbstractItemView.AllEditTriggers)
self.setEditTriggers(QAbstractItemView.DoubleClicked)
self.setAlternatingRowColors(True)
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
@ -146,14 +148,9 @@ class PlaylistTab(QTableWidget):
self.itemSelectionChanged.connect(self._select_event)
self.row_filter: Optional[str] = None
# self.editing_cell: bool = False
self.edit_cell_type = None
self.selecting_in_progress = False
# 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)
# Now load our tracks and notes
@ -161,12 +158,8 @@ class PlaylistTab(QTableWidget):
def __repr__(self) -> str:
return f"<PlaylistTab(id={self.playlist_id}"
#
# # ########## Events ##########
#
# def closeEditor(self, editor, hint): # review
# super(PlaylistTab, self).closeEditor(editor, hint)
# self.cellEditingEnded.emit()
# ########## Events other than cell editing ##########
def closeEvent(self, event) -> None:
"""Handle closing playist tab"""
@ -230,12 +223,6 @@ class PlaylistTab(QTableWidget):
self.save_playlist(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):
"""Used to process context (right-click) menu, which is defined here"""
@ -327,6 +314,158 @@ class PlaylistTab(QTableWidget):
self.setDragEnabled(False)
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 ##########
def clear_next(self, session) -> None:
@ -1150,86 +1289,6 @@ class PlaylistTab(QTableWidget):
cb = QApplication.clipboard()
cb.clear(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:
# """Clear played status on row"""
@ -1268,23 +1327,6 @@ class PlaylistTab(QTableWidget):
return (index.row() + 1 if self._is_below(event.pos(), index)
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]:
# """Return rows marked as notes, or None"""
#