V3 WIP Drag and drop partly implemented
UI works but outputs model changes needed to stdout
This commit is contained in:
parent
4903330e44
commit
9d3e4b8d0c
@ -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
|
||||
|
||||
122
app/playlists.py
122
app/playlists.py
@ -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
46
poetry.lock
generated
@ -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"
|
||||
|
||||
@ -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"]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user