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
|
# 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
|
||||||
|
|||||||
122
app/playlists.py
122
app/playlists.py
@ -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
46
poetry.lock
generated
@ -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"
|
||||||
|
|||||||
@ -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"]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user