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
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:
"""
Return text for display
@ -241,9 +227,13 @@ class PlaylistModel(QAbstractTableModel):
"""
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]:
return default | Qt.ItemFlag.ItemIsEditable
@ -353,3 +343,6 @@ class PlaylistModel(QAbstractTableModel):
return True
return False
def supportedDropActions(self):
return Qt.DropAction.MoveAction | Qt.DropAction.CopyAction

View File

@ -31,6 +31,9 @@ from PyQt6.QtWidgets import (
QTableView,
QTableWidgetItem,
QWidget,
QProxyStyle,
QStyle,
QStyleOption,
)
from config import Config
@ -45,6 +48,7 @@ from helpers import (
set_track_metadata,
)
from log import log
from models import Playlists, PlaylistRows, Settings, Tracks, NoteColours
from playlistmodel import PlaylistModel
@ -52,37 +56,8 @@ from playlistmodel import PlaylistModel
if TYPE_CHECKING:
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
# # 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):
"""
@ -157,6 +132,25 @@ class EscapeDelegate(QStyledItemDelegate):
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):
def __init__(
self,
@ -178,17 +172,15 @@ class PlaylistTab(QTableView):
self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
# self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked)
self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
# This dancing is to satisfy mypy
# Drag and drop setup
# self.setAcceptDrops(True)
# viewport = self.viewport()
# if viewport:
# viewport.setAcceptDrops(True)
# self.setDragDropOverwriteMode(False)
# self.setDropIndicatorShown(True)
# self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
# self.setDragEnabled(False)
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
self.setDragDropOverwriteMode(False)
self.setAcceptDrops(True)
# Set our custom style - this draws the drop indicator across the whole row
self.setStyle(PlaylistStyle())
# TODO: change this later to only enable drags when multiple
# rows selected
self.setDragEnabled(True)
# Prepare for context menu
# self.menu = QMenu()
# self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
@ -221,6 +213,24 @@ class PlaylistTab(QTableView):
# ########## 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:
# """
# Handle drag/drop of rows
@ -510,7 +520,7 @@ class PlaylistTab(QTableView):
"""Unselect all tracks and reset drag mode"""
self.clearSelection()
self.setDragEnabled(False)
# self.setDragEnabled(False)
# def get_new_row_number(self) -> int:
# """
@ -1246,7 +1256,7 @@ class PlaylistTab(QTableView):
self.save_playlist(session)
# Reset drag mode
self.setDragEnabled(False)
# self.setDragEnabled(False)
self._update_start_end_times(session)
@ -1304,21 +1314,21 @@ class PlaylistTab(QTableView):
return self._plrid_to_row_number(next_track.plr_id)
@staticmethod
def _get_note_text_time(text: str) -> Optional[datetime]:
"""Return datetime specified as @hh:mm:ss in text"""
# @staticmethod
# def _get_note_text_time(text: str) -> Optional[datetime]:
# """Return datetime specified as @hh:mm:ss in text"""
try:
match = start_time_re.search(text)
except TypeError:
return None
if not match:
return None
# try:
# match = start_time_re.search(text)
# except TypeError:
# return None
# if not match:
# return None
try:
return datetime.strptime(match.group(0)[1:], Config.NOTE_TIME_FORMAT)
except ValueError:
return None
# try:
# return datetime.strptime(match.group(0)[1:], Config.NOTE_TIME_FORMAT)
# except ValueError:
# return None
def _get_played_rows(self, session: scoped_session) -> List[int]:
"""
@ -2361,7 +2371,7 @@ class PlaylistTab(QTableView):
self._reorder_rows(new_order)
# Reset drag mode to allow row selection by dragging
self.setDragEnabled(False)
# self.setDragEnabled(False)
# Save playlist
with Session() as session:
@ -2384,7 +2394,7 @@ class PlaylistTab(QTableView):
]
# Reset drag mode to allow row selection by dragging
self.setDragEnabled(False)
# self.setDragEnabled(False)
# Save playlist
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"},
]
[[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]]
name = "pexpect"
version = "4.8.0"
@ -1452,6 +1469,18 @@ files = [
[package.dependencies]
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]]
name = "pytest"
version = "7.4.2"
@ -1915,6 +1944,21 @@ files = [
{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]]
name = "text-unidecode"
version = "1.3"
@ -2148,4 +2192,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = "^3.9"
content-hash = "97f122b0c15850e806e764ab7d3df23ce115e8aa9cc7a775c64834b18beef664"
content-hash = "514b699dbd1e579adcad3ee6112c632bbd01bc801377b27a5cbe7cebd35d5995"

View File

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