Compare commits

..

No commits in common. "d25beeda8985cd04780a35aae73829a82ea51974" and "d81b4c84b8b3f4ab2002a7c7d5423c532ab7b6c9" have entirely different histories.

6 changed files with 74 additions and 316 deletions

View File

@ -87,7 +87,6 @@ class Config(object):
OBS_PORT = 4455 OBS_PORT = 4455
PLAY_SETTLE = 500000 PLAY_SETTLE = 500000
ROOT = os.environ.get('ROOT') or "/home/kae/music" ROOT = os.environ.get('ROOT') or "/home/kae/music"
ROWS_FROM_ZERO = True
IMPORT_DESTINATION = os.path.join(ROOT, "Singles") IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
SCROLL_TOP_MARGIN = 3 SCROLL_TOP_MARGIN = 3
START_GAP_WARNING_THRESHOLD = 500 START_GAP_WARNING_THRESHOLD = 500

View File

@ -166,6 +166,20 @@ 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
@ -227,13 +241,9 @@ class PlaylistModel(QAbstractTableModel):
""" """
if not index.isValid(): if not index.isValid():
return Qt.ItemFlag.ItemIsDropEnabled return Qt.ItemFlag.ItemIsEnabled
default = ( default = Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable
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
@ -269,9 +279,6 @@ class PlaylistModel(QAbstractTableModel):
return QVariant(Config.HEADER_BITRATE) return QVariant(Config.HEADER_BITRATE)
elif section == Col.NOTE.value: elif section == Col.NOTE.value:
return QVariant(Config.HEADER_NOTE) return QVariant(Config.HEADER_NOTE)
else:
if Config.ROWS_FROM_ZERO:
return QVariant(str(section))
else: else:
return QVariant(str(section + 1)) return QVariant(str(section + 1))
@ -343,6 +350,3 @@ class PlaylistModel(QAbstractTableModel):
return True return True
return False return False
def supportedDropActions(self):
return Qt.DropAction.MoveAction | Qt.DropAction.CopyAction

View File

@ -31,9 +31,6 @@ from PyQt6.QtWidgets import (
QTableView, QTableView,
QTableWidgetItem, QTableWidgetItem,
QWidget, QWidget,
QProxyStyle,
QStyle,
QStyleOption,
) )
from config import Config from config import Config
@ -48,7 +45,6 @@ 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
@ -56,8 +52,37 @@ 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):
""" """
@ -132,25 +157,6 @@ 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,
@ -172,15 +178,17 @@ 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)
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove) # This dancing is to satisfy mypy
self.setDragDropOverwriteMode(False) # Drag and drop setup
self.setAcceptDrops(True) # self.setAcceptDrops(True)
# Set our custom style - this draws the drop indicator across the whole row # viewport = self.viewport()
self.setStyle(PlaylistStyle()) # if viewport:
# 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)
@ -213,24 +221,6 @@ 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
@ -520,7 +510,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:
# """ # """
@ -1256,7 +1246,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)
@ -1314,21 +1304,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]:
""" """
@ -2371,7 +2361,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:
@ -2394,7 +2384,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:

View File

@ -1,190 +0,0 @@
#!/usr/bin/python3
# vim: set expandtab tabstop=4 shiftwidth=4:
# PyQt Functionality Snippet by Apocalyptech
# "Licensed" in the Public Domain under CC0 1.0 Universal (CC0 1.0)
# Public Domain Dedication. Use it however you like!
#
# https://creativecommons.org/publicdomain/zero/1.0/
# https://creativecommons.org/publicdomain/zero/1.0/legalcode
from PyQt6 import QtWidgets, QtCore
# class MyModel(QtGui.QStandardItemModel):
class MyModel(QtCore.QAbstractTableModel):
def __init__(self, parent=None):
super().__init__(parent)
def columnCount(self, parent=None):
return 5
def rowCount(self, parent=None):
return 20
# def headerData(self, column: int, orientation, role: QtCore.Qt.ItemDataRole):
# return (('Regex', 'Category')[column]
# if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal
# else None)
def headerData(self, column, orientation, role):
if role == QtCore.Qt.ItemDataRole.DisplayRole and orientation == QtCore.Qt.Orientation.Horizontal:
return f"{column=}"
return None
def data(self, index: QtCore.QModelIndex, role: QtCore.Qt.ItemDataRole):
if not index.isValid() or role not in {
QtCore.Qt.ItemDataRole.DisplayRole,
QtCore.Qt.ItemDataRole.EditRole,
}:
return None
# return (self._data[index.row()][index.column()] if index.row() < len(self._data) else
# "edit me" if role == QtCore.Qt.DisplayRole else "")
# def data(self, index, role):
# if not index.isValid() or role not in [QtCore.Qt.DisplayRole,
# QtCore.Qt.EditRole]:
# return None
# return (self._data[index.row()][index.column()] if index.row() < len(self._data) else
# "edit me" if role == QtCore.Qt.DisplayRole else "")
row = index.row()
column = index.column()
return f"Row {row}, Col {column}"
def flags(self, index: QtCore.QModelIndex) -> QtCore.Qt.ItemFlag:
# https://doc.qt.io/qt-5/qt.html#ItemFlag-enum
if not index.isValid():
return QtCore.Qt.ItemFlag.ItemIsEnabled
if index.row() < 20:
return (
QtCore.Qt.ItemFlag.ItemIsEnabled
| QtCore.Qt.ItemFlag.ItemIsEditable
| QtCore.Qt.ItemFlag.ItemIsSelectable
| QtCore.Qt.ItemFlag.ItemIsDragEnabled
)
return QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsEditable
# def flags(self, index):
# if not index.isValid():
# return QtCore.Qt.ItemIsDropEnabled
# if index.row() < 5:
# return (
# QtCore.Qt.ItemIsEnabled
# | QtCore.Qt.ItemIsEditable
# | QtCore.Qt.ItemIsSelectable
# | QtCore.Qt.ItemIsDragEnabled
# )
# return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable
# def supportedDragOptions(self):
# return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction
# def supportedDropActions(self) -> bool:
# return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction
def relocateRow(self, row_source, row_target) -> None:
return
row_a, row_b = max(row_source, row_target), min(row_source, row_target)
self.beginMoveRows(
QtCore.QModelIndex(), row_a, row_a, QtCore.QModelIndex(), row_b
)
self._data.insert(row_target, self._data.pop(row_source))
self.endMoveRows()
def supportedDropActions(self):
return QtCore.Qt.DropAction.MoveAction | QtCore.Qt.DropAction.CopyAction
# def relocateRow(self, src, dst):
# print("relocateRow")
# def dropMimeData(self, data, action, row, col, parent):
# """
# Always move the entire row, and don't allow column "shifting"
# """
# # return super().dropMimeData(data, action, row, 0, parent)
# print("dropMimeData")
# super().dropMimeData(data, action, row, col, parent)
class MyStyle(QtWidgets.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 == QtWidgets.QStyle.PrimitiveElement.PE_IndicatorItemViewItemDrop and not option.rect.isNull():
option_new = QtWidgets.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 MyTableView(QtWidgets.QTableView):
def __init__(self, parent):
super().__init__(parent)
self.verticalHeader().hide()
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
self.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
self.setDragDropMode(QtWidgets.QAbstractItemView.DragDropMode.InternalMove)
self.setDragDropOverwriteMode(False)
self.setAcceptDrops(True)
# self.horizontalHeader().hide()
# self.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
# self.setShowGrid(False)
# Set our custom style - this draws the drop indicator across the whole row
self.setStyle(MyStyle())
# Set our custom model - this prevents row "shifting"
# self.model = MyModel()
# self.setModel(self.model)
self.setModel(MyModel())
# for (idx, data) in enumerate(['foo', 'bar', 'baz']):
# item_1 = QtGui.QStandardItem('Item {}'.format(idx))
# item_1.setEditable(False)
# item_1.setDropEnabled(False)
# item_2 = QtGui.QStandardItem(data)
# item_2.setEditable(False)
# item_2.setDropEnabled(False)
# self.model.appendRow([item_1, item_2])
def dropEvent(self, event):
if event.source() is not self or (
event.dropAction() != QtCore.Qt.DropAction.MoveAction
and self.dragDropMode() != QtWidgets.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)
class Testing(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
view = MyTableView(self)
view.setModel(MyModel())
self.setCentralWidget(view)
self.show()
if __name__ == "__main__":
app = QtWidgets.QApplication([])
test = Testing()
raise SystemExit(app.exec())

46
poetry.lock generated
View File

@ -1030,23 +1030,6 @@ 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"
@ -1469,18 +1452,6 @@ 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"
@ -1944,21 +1915,6 @@ 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"
@ -2192,4 +2148,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 = "514b699dbd1e579adcad3ee6112c632bbd01bc801377b27a5cbe7cebd35d5995" content-hash = "97f122b0c15850e806e764ab7d3df23ce115e8aa9cc7a775c64834b18beef664"

View File

@ -42,7 +42,6 @@ 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"]