V3 WIP Drag and drop partly implemented

UI works but outputs model changes needed to stdout
This commit is contained in:
Keith Edmunds 2023-10-22 22:53:59 +01:00
parent 4903330e44
commit 9d3e4b8d0c
4 changed files with 121 additions and 73 deletions

View File

@ -166,20 +166,6 @@ class PlaylistModel(QAbstractTableModel):
# Fall through to no-op # Fall through to no-op
return QVariant() return QVariant()
def edit_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant:
"""
Return text for editing
"""
if column == Col.TITLE.value:
return QVariant(prd.title)
if column == Col.ARTIST.value:
return QVariant(prd.artist)
if column == Col.NOTE.value:
return QVariant(prd.note)
return QVariant()
def display_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant: def display_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant:
""" """
Return text for display Return text for display
@ -241,9 +227,13 @@ class PlaylistModel(QAbstractTableModel):
""" """
if not index.isValid(): if not index.isValid():
return Qt.ItemFlag.ItemIsEnabled return Qt.ItemFlag.ItemIsDropEnabled
default = Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable default = (
Qt.ItemFlag.ItemIsEnabled
| Qt.ItemFlag.ItemIsSelectable
| Qt.ItemFlag.ItemIsDragEnabled
)
if index.column() in [Col.TITLE.value, Col.ARTIST.value, Col.NOTE.value]: if index.column() in [Col.TITLE.value, Col.ARTIST.value, Col.NOTE.value]:
return default | Qt.ItemFlag.ItemIsEditable return default | Qt.ItemFlag.ItemIsEditable
@ -353,3 +343,6 @@ class PlaylistModel(QAbstractTableModel):
return True return True
return False return False
def supportedDropActions(self):
return Qt.DropAction.MoveAction | Qt.DropAction.CopyAction

View File

@ -31,6 +31,9 @@ from PyQt6.QtWidgets import (
QTableView, QTableView,
QTableWidgetItem, QTableWidgetItem,
QWidget, QWidget,
QProxyStyle,
QStyle,
QStyleOption,
) )
from config import Config from config import Config
@ -45,6 +48,7 @@ from helpers import (
set_track_metadata, set_track_metadata,
) )
from log import log from log import log
from models import Playlists, PlaylistRows, Settings, Tracks, NoteColours from models import Playlists, PlaylistRows, Settings, Tracks, NoteColours
from playlistmodel import PlaylistModel from playlistmodel import PlaylistModel
@ -52,37 +56,8 @@ from playlistmodel import PlaylistModel
if TYPE_CHECKING: if TYPE_CHECKING:
from musicmuster import Window, MusicMusterSignals from musicmuster import Window, MusicMusterSignals
# scene_change_re = re.compile(r"SetScene=\[([^[\]]*)\]")
# section_header_cleanup_re = re.compile(r"(@\d\d:\d\d:\d\d.*)?(\+)?")
# start_time_re = re.compile(r"@\d\d:\d\d:\d\d")
HEADER_NOTES_COLUMN = 2 HEADER_NOTES_COLUMN = 2
# # Columns
# Column = namedtuple("Column", ["idx", "heading"])
# columns = {}
# columns["userdata"] = Column(idx=0, heading=Config.COLUMN_NAME_AUTOPLAY)
# columns["start_gap"] = Column(idx=1, heading=Config.COLUMN_NAME_LEADING_SILENCE)
# columns["title"] = Column(idx=2, heading=Config.COLUMN_NAME_TITLE)
# columns["artist"] = Column(idx=3, heading=Config.COLUMN_NAME_ARTIST)
# columns["duration"] = Column(idx=4, heading=Config.COLUMN_NAME_LENGTH)
# columns["start_time"] = Column(idx=5, heading=Config.COLUMN_NAME_START_TIME)
# columns["end_time"] = Column(idx=6, heading=Config.COLUMN_NAME_END_TIME)
# columns["lastplayed"] = Column(idx=7, heading=Config.COLUMN_NAME_LAST_PLAYED)
# columns["bitrate"] = Column(idx=8, heading=Config.COLUMN_NAME_BITRATE)
# columns["row_notes"] = Column(idx=9, heading=Config.COLUMN_NAME_NOTES)
# USERDATA = columns["userdata"].idx
# START_GAP = columns["start_gap"].idx
# TITLE = columns["title"].idx
# ARTIST = columns["artist"].idx
# DURATION = columns["duration"].idx
# START_TIME = columns["start_time"].idx
# END_TIME = columns["end_time"].idx
# LASTPLAYED = columns["lastplayed"].idx
# BITRATE = columns["bitrate"].idx
# ROW_NOTES = columns["row_notes"].idx
class EscapeDelegate(QStyledItemDelegate): class EscapeDelegate(QStyledItemDelegate):
""" """
@ -157,6 +132,25 @@ class EscapeDelegate(QStyledItemDelegate):
editor.setGeometry(option.rect) editor.setGeometry(option.rect)
class PlaylistStyle(QProxyStyle):
def drawPrimitive(self, element, option, painter, widget=None):
"""
Draw a line across the entire row rather than just the column
we're hovering over. This may not always work depending on global
style - for instance I think it won't work on OSX.
"""
if (
element == QStyle.PrimitiveElement.PE_IndicatorItemViewItemDrop
and not option.rect.isNull()
):
option_new = QStyleOption(option)
option_new.rect.setLeft(0)
if widget:
option_new.rect.setRight(widget.width())
option = option_new
super().drawPrimitive(element, option, painter, widget)
class PlaylistTab(QTableView): class PlaylistTab(QTableView):
def __init__( def __init__(
self, self,
@ -178,17 +172,15 @@ class PlaylistTab(QTableView):
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) 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)
# This dancing is to satisfy mypy self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
# Drag and drop setup self.setDragDropOverwriteMode(False)
# self.setAcceptDrops(True) self.setAcceptDrops(True)
# viewport = self.viewport() # Set our custom style - this draws the drop indicator across the whole row
# if viewport: self.setStyle(PlaylistStyle())
# viewport.setAcceptDrops(True)
# self.setDragDropOverwriteMode(False)
# self.setDropIndicatorShown(True)
# self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
# self.setDragEnabled(False)
# TODO: change this later to only enable drags when multiple
# rows selected
self.setDragEnabled(True)
# Prepare for context menu # Prepare for context menu
# self.menu = QMenu() # self.menu = QMenu()
# self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) # self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
@ -221,6 +213,24 @@ class PlaylistTab(QTableView):
# ########## Events other than cell editing ########## # ########## Events other than cell editing ##########
def dropEvent(self, event):
if event.source() is not self or (
event.dropAction() != Qt.DropAction.MoveAction
and self.dragDropMode() != QAbstractItemView.InternalMove
):
super().dropEvent(event)
from_rows = list(set([a.row() for a in self.selectedIndexes()]))
to_row = self.indexAt(event.position().toPoint()).row()
if (
0 <= min(from_rows) <= self.model().rowCount()
and 0 <= max(from_rows) <= self.model().rowCount()
and 0 <= to_row <= self.model().rowCount()
):
print(f"move_rows({from_rows=}, {to_row=})")
event.accept()
super().dropEvent(event)
# def dropEvent(self, event: Optional[QDropEvent]) -> None: # def dropEvent(self, event: Optional[QDropEvent]) -> None:
# """ # """
# Handle drag/drop of rows # Handle drag/drop of rows
@ -510,7 +520,7 @@ class PlaylistTab(QTableView):
"""Unselect all tracks and reset drag mode""" """Unselect all tracks and reset drag mode"""
self.clearSelection() self.clearSelection()
self.setDragEnabled(False) # self.setDragEnabled(False)
# def get_new_row_number(self) -> int: # def get_new_row_number(self) -> int:
# """ # """
@ -1246,7 +1256,7 @@ class PlaylistTab(QTableView):
self.save_playlist(session) self.save_playlist(session)
# Reset drag mode # Reset drag mode
self.setDragEnabled(False) # self.setDragEnabled(False)
self._update_start_end_times(session) self._update_start_end_times(session)
@ -1304,21 +1314,21 @@ class PlaylistTab(QTableView):
return self._plrid_to_row_number(next_track.plr_id) return self._plrid_to_row_number(next_track.plr_id)
@staticmethod # @staticmethod
def _get_note_text_time(text: str) -> Optional[datetime]: # def _get_note_text_time(text: str) -> Optional[datetime]:
"""Return datetime specified as @hh:mm:ss in text""" # """Return datetime specified as @hh:mm:ss in text"""
try: # try:
match = start_time_re.search(text) # match = start_time_re.search(text)
except TypeError: # except TypeError:
return None # return None
if not match: # if not match:
return None # return None
try: # try:
return datetime.strptime(match.group(0)[1:], Config.NOTE_TIME_FORMAT) # return datetime.strptime(match.group(0)[1:], Config.NOTE_TIME_FORMAT)
except ValueError: # except ValueError:
return None # return None
def _get_played_rows(self, session: scoped_session) -> List[int]: def _get_played_rows(self, session: scoped_session) -> List[int]:
""" """
@ -2361,7 +2371,7 @@ class PlaylistTab(QTableView):
self._reorder_rows(new_order) self._reorder_rows(new_order)
# Reset drag mode to allow row selection by dragging # Reset drag mode to allow row selection by dragging
self.setDragEnabled(False) # self.setDragEnabled(False)
# Save playlist # Save playlist
with Session() as session: with Session() as session:
@ -2384,7 +2394,7 @@ class PlaylistTab(QTableView):
] ]
# Reset drag mode to allow row selection by dragging # Reset drag mode to allow row selection by dragging
self.setDragEnabled(False) # self.setDragEnabled(False)
# Save playlist # Save playlist
with Session() as session: with Session() as session:

46
poetry.lock generated
View File

@ -1030,6 +1030,23 @@ files = [
{file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
] ]
[[package]]
name = "pdbp"
version = "1.5.0"
description = "pdbp (Pdb+): A drop-in replacement for pdb and pdbpp."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "pdbp-1.5.0-py3-none-any.whl", hash = "sha256:7640598c336ec3e3e0b2aeec71d20a1e810ba49e3e1b3effac5b862a798dea7d"},
{file = "pdbp-1.5.0.tar.gz", hash = "sha256:23e03897fe950794a487238b64d8b0cec66760083c4697e3b7bc5ca0fae617ea"},
]
[package.dependencies]
colorama = {version = ">=0.4.6", markers = "platform_system == \"Windows\""}
pygments = ">=2.16.1"
tabcompleter = ">=1.3.0"
[[package]] [[package]]
name = "pexpect" name = "pexpect"
version = "4.8.0" version = "4.8.0"
@ -1452,6 +1469,18 @@ files = [
[package.dependencies] [package.dependencies]
numpy = ">=1.20.0" numpy = ">=1.20.0"
[[package]]
name = "pyreadline3"
version = "3.4.1"
description = "A python implementation of GNU readline."
category = "dev"
optional = false
python-versions = "*"
files = [
{file = "pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb"},
{file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"},
]
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "7.4.2" version = "7.4.2"
@ -1915,6 +1944,21 @@ files = [
{file = "stackprinter-0.2.10.tar.gz", hash = "sha256:99d1ea6b91ffad96b28241edd7bcf071752b0cf694cab58d2335df5353acd086"}, {file = "stackprinter-0.2.10.tar.gz", hash = "sha256:99d1ea6b91ffad96b28241edd7bcf071752b0cf694cab58d2335df5353acd086"},
] ]
[[package]]
name = "tabcompleter"
version = "1.3.0"
description = "tabcompleter --- Autocompletion in the Python console."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "tabcompleter-1.3.0-py3-none-any.whl", hash = "sha256:59dfe825f4d88a51d486c0a513763eca6224f2146518d185ee2ebfc4f2398b80"},
{file = "tabcompleter-1.3.0.tar.gz", hash = "sha256:47b9d4f783d14ebca5c66223c7f82cc1ef89f7313ba9ea0ce75265670178bb6e"},
]
[package.dependencies]
pyreadline3 = {version = "*", markers = "platform_system == \"Windows\""}
[[package]] [[package]]
name = "text-unidecode" name = "text-unidecode"
version = "1.3" version = "1.3"
@ -2148,4 +2192,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.9" python-versions = "^3.9"
content-hash = "97f122b0c15850e806e764ab7d3df23ce115e8aa9cc7a775c64834b18beef664" content-hash = "514b699dbd1e579adcad3ee6112c632bbd01bc801377b27a5cbe7cebd35d5995"

View File

@ -42,6 +42,7 @@ furo = "^2023.5.20"
black = "^23.3.0" black = "^23.3.0"
flakehell = "^0.9.0" flakehell = "^0.9.0"
mypy = "^1.6.0" mypy = "^1.6.0"
pdbp = "^1.5.0"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]