Enable drag-select, then drag selection
This commit is contained in:
parent
32e81fb074
commit
96255e83ea
@ -430,11 +430,6 @@ class PlaylistRows(Base):
|
||||
track_id = Column(Integer, ForeignKey('tracks.id'), nullable=True)
|
||||
track = relationship("Tracks", back_populates="playlistrows")
|
||||
|
||||
# Ensure row numbers are unique within each playlist
|
||||
__table_args__ = (UniqueConstraint
|
||||
('row_number', 'playlist_id', name="uniquerow"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<PlaylistRow(id={self.id}, playlist_id={self.playlist_id}, "
|
||||
@ -453,27 +448,27 @@ class PlaylistRows(Base):
|
||||
# session.add(self)
|
||||
# session.flush()
|
||||
#
|
||||
@staticmethod
|
||||
def delete_higher_rows(session: Session, playlist_id: int, row: int) \
|
||||
-> None:
|
||||
"""
|
||||
Delete rows in given playlist that have a higher row number
|
||||
than 'row'
|
||||
"""
|
||||
@staticmethod
|
||||
def delete_higher_rows(session: Session, playlist_id: int, row: int) \
|
||||
-> None:
|
||||
"""
|
||||
Delete rows in given playlist that have a higher row number
|
||||
than 'row'
|
||||
"""
|
||||
|
||||
# Log the rows to be deleted
|
||||
rows_to_go = session.execute(
|
||||
select(PlaylistRows)
|
||||
.where(PlaylistRows.playlist_id == playlist_id,
|
||||
PlaylistRows.row_number > row)
|
||||
).scalars().all()
|
||||
if not rows_to_go:
|
||||
return
|
||||
# Log the rows to be deleted
|
||||
rows_to_go = session.execute(
|
||||
select(PlaylistRows)
|
||||
.where(PlaylistRows.playlist_id == playlist_id,
|
||||
PlaylistRows.row_number > row)
|
||||
).scalars().all()
|
||||
if not rows_to_go:
|
||||
return
|
||||
|
||||
for row in rows_to_go:
|
||||
log.debu(f"Should delete: {row}")
|
||||
# If needed later:
|
||||
# session.delete(row)
|
||||
for row in rows_to_go:
|
||||
log.debu(f"Should delete: {row}")
|
||||
# If needed later:
|
||||
# session.delete(row)
|
||||
|
||||
|
||||
# @classmethod
|
||||
|
||||
239
app/playlists.py
239
app/playlists.py
@ -9,9 +9,9 @@ from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import (
|
||||
QColor,
|
||||
QFont,
|
||||
# QDropEvent
|
||||
QDropEvent
|
||||
)
|
||||
# from PyQt5 import QtWidgets
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView,
|
||||
# QApplication,
|
||||
@ -37,9 +37,8 @@ from helpers import (
|
||||
get_relative_date,
|
||||
# open_in_audacity
|
||||
)
|
||||
# from log import log.debug, log.error
|
||||
from log import log
|
||||
from models import (
|
||||
# Notes,
|
||||
Playdates,
|
||||
Playlists,
|
||||
PlaylistRows,
|
||||
@ -115,9 +114,9 @@ class PlaylistTab(QTableWidget):
|
||||
# Set up widget
|
||||
# self.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers)
|
||||
self.setAlternatingRowColors(True)
|
||||
# self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
# self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
# self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
||||
self.setRowCount(0)
|
||||
self.setColumnCount(len(columns))
|
||||
|
||||
@ -133,14 +132,12 @@ class PlaylistTab(QTableWidget):
|
||||
key=lambda item: item.idx))]
|
||||
)
|
||||
|
||||
# self.setDragEnabled(True)
|
||||
# self.setAcceptDrops(True)
|
||||
# self.viewport().setAcceptDrops(True)
|
||||
# self.setDragDropOverwriteMode(False)
|
||||
# self.setDropIndicatorShown(True)
|
||||
# self.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||||
# self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
# self.setDragDropMode(QAbstractItemView.InternalMove)
|
||||
self.setAcceptDrops(True)
|
||||
self.viewport().setAcceptDrops(True)
|
||||
self.setDragDropOverwriteMode(False)
|
||||
self.setDropIndicatorShown(True)
|
||||
self.setDragDropMode(QAbstractItemView.InternalMove)
|
||||
self.setDragEnabled(False)
|
||||
#
|
||||
# # This property defines how the widget shows a context menu
|
||||
# self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
@ -182,55 +179,66 @@ class PlaylistTab(QTableWidget):
|
||||
if record.f_int != self.columnWidth(idx):
|
||||
record.update(session, {'f_int': width})
|
||||
|
||||
# def __repr__(self) -> str:
|
||||
# return f"<PlaylistTab(id={self.playlist_id}"
|
||||
def __repr__(self) -> str:
|
||||
return f"<PlaylistTab(id={self.playlist_id}"
|
||||
#
|
||||
# # ########## Events ##########
|
||||
#
|
||||
# def dropEvent(self, event: QDropEvent) -> None:
|
||||
# # if not event.isAccepted() and event.source() == self:
|
||||
# if not event.source() == self:
|
||||
# return # We don't accept external drops
|
||||
#
|
||||
# drop_row: int = self._drop_on(event)
|
||||
#
|
||||
# rows: List = sorted(set(item.row() for item in self.selectedItems()))
|
||||
# rows_to_move = [
|
||||
# [QTableWidgetItem(self.item(row_index, column_index)) for
|
||||
# column_index in range(self.columnCount())]
|
||||
# for row_index in rows
|
||||
# ]
|
||||
# for row_index in reversed(rows):
|
||||
# self.removeRow(row_index)
|
||||
# if row_index < drop_row:
|
||||
# drop_row -= 1
|
||||
#
|
||||
# for row_index, data in enumerate(rows_to_move):
|
||||
# row_index += drop_row
|
||||
# self.insertRow(row_index)
|
||||
# for column_index, column_data in enumerate(data):
|
||||
# self.setItem(row_index, column_index, column_data)
|
||||
# event.accept()
|
||||
# # The above doesn't handle column spans, which we use in note
|
||||
# # rows. Check and fix:
|
||||
# row = 0 # So row is defined even if there are no rows in range
|
||||
# for row in range(drop_row, drop_row + len(rows_to_move)):
|
||||
# if row in self._get_notes_rows():
|
||||
# self.setSpan(
|
||||
# row, self.COL_NOTE, self.NOTE_ROW_SPAN, self.NOTE_COL_SPAN)
|
||||
#
|
||||
# # Scroll to drop zone
|
||||
# self.scrollToItem(self.item(row, 1))
|
||||
# super().dropEvent(event)
|
||||
#
|
||||
# log.debug(
|
||||
# "playlist.dropEvent(): "
|
||||
# f"Moved row(s) {rows} to become row {drop_row}"
|
||||
# )
|
||||
#
|
||||
# with Session() as session: # checked
|
||||
# self.save_playlist(session)
|
||||
# self.update_display(session)
|
||||
# def closeEditor(self, editor, hint): # review
|
||||
# super(PlaylistTab, self).closeEditor(editor, hint)
|
||||
# self.cellEditingEnded.emit()
|
||||
|
||||
def dropEvent(self, event: QDropEvent) -> None:
|
||||
"""
|
||||
Handle drag/drop of rows
|
||||
|
||||
https://stackoverflow.com/questions/26227885/drag-and-drop-rows-within-qtablewidget
|
||||
"""
|
||||
|
||||
if not event.source() == self:
|
||||
return # We don't accept external drops
|
||||
|
||||
drop_row: int = self._drop_on(event)
|
||||
|
||||
rows: List = sorted(set(item.row() for item in self.selectedItems()))
|
||||
rows_to_move = [
|
||||
[QTableWidgetItem(self.item(row_index, column_index)) for
|
||||
column_index in range(self.columnCount())]
|
||||
for row_index in rows
|
||||
]
|
||||
for row_index in reversed(rows):
|
||||
self.removeRow(row_index)
|
||||
if row_index < drop_row:
|
||||
drop_row -= 1
|
||||
|
||||
for row_index, data in enumerate(rows_to_move):
|
||||
row_index += drop_row
|
||||
self.insertRow(row_index)
|
||||
for column_index, column_data in enumerate(data):
|
||||
self.setItem(row_index, column_index, column_data)
|
||||
event.accept()
|
||||
# The above doesn't handle column spans, which we use in note
|
||||
# rows. Check and fix:
|
||||
for row in range(drop_row, drop_row + len(rows_to_move)):
|
||||
if not self._get_row_track_id(row):
|
||||
self.setSpan(row, 1, 1, len(columns))
|
||||
|
||||
# Scroll to drop zone
|
||||
self.scrollToItem(self.item(row, 1))
|
||||
|
||||
# Reset drag mode to allow row selection by dragging
|
||||
self.setDragEnabled(False)
|
||||
|
||||
super().dropEvent(event)
|
||||
|
||||
log.debug(
|
||||
"playlist.dropEvent(): "
|
||||
f"Moved row(s) {rows} to become row {drop_row}"
|
||||
)
|
||||
|
||||
with Session() as session: # checked
|
||||
self.save_playlist(session)
|
||||
self.update_display(session)
|
||||
#
|
||||
# def edit(self, index, trigger, event): # review
|
||||
# result = super(PlaylistTab, self).edit(index, trigger, event)
|
||||
@ -238,10 +246,6 @@ class PlaylistTab(QTableWidget):
|
||||
# self.cellEditingStarted.emit(index.row(), index.column())
|
||||
# return result
|
||||
#
|
||||
# def closeEditor(self, editor, hint): # review
|
||||
# super(PlaylistTab, self).closeEditor(editor, hint)
|
||||
# self.cellEditingEnded.emit()
|
||||
#
|
||||
# def eventFilter(self, source, event): # review
|
||||
# """Used to process context (right-click) menu, which is defined here"""
|
||||
#
|
||||
@ -287,7 +291,18 @@ class PlaylistTab(QTableWidget):
|
||||
# act_delete.triggered.connect(self._delete_rows)
|
||||
#
|
||||
# return super(PlaylistTab, self).eventFilter(source, event)
|
||||
#
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
"""
|
||||
Enable dragging if rows are selected
|
||||
"""
|
||||
|
||||
if self.selectedItems():
|
||||
self.setDragEnabled(True)
|
||||
else:
|
||||
self.setDragEnabled(False)
|
||||
super().mouseReleaseEvent(event)
|
||||
|
||||
# # ########## Externally called functions ##########
|
||||
#
|
||||
# def closeEvent(self, event) -> None:
|
||||
@ -648,27 +663,27 @@ class PlaylistTab(QTableWidget):
|
||||
# KAE self.save_playlist(session)
|
||||
self.update_display(session)
|
||||
|
||||
def save_playlist(self, session: Session) -> None:
|
||||
"""
|
||||
Save playlist to database
|
||||
"""
|
||||
def save_playlist(self, session: Session) -> None:
|
||||
"""
|
||||
Save playlist to database
|
||||
"""
|
||||
|
||||
# Iteratate through playlist and check that the row in each
|
||||
# playlist_row object is correct
|
||||
for row in range(self.rowCount()):
|
||||
plr = session.get(PlaylistRows, self._get_playlistrow_id(row))
|
||||
# Set the row number (even if it's already correct)
|
||||
if plr.row_number != row:
|
||||
log.debug(
|
||||
f"Updating PlaylistRow: {plr.row_number=}, {row=}"
|
||||
)
|
||||
plr.row_number = row
|
||||
# Iteratate through playlist and check that the row in each
|
||||
# playlist_row object is correct
|
||||
for row in range(self.rowCount()):
|
||||
plr = session.get(PlaylistRows, self._get_playlistrow_id(row))
|
||||
# Set the row number (even if it's already correct)
|
||||
if plr.row_number != row:
|
||||
log.debug(
|
||||
f"Updating PlaylistRow: {plr.row_number=}, {row=}"
|
||||
)
|
||||
plr.row_number = row
|
||||
|
||||
# Any rows in the database with a row_number higher that the
|
||||
# current value of 'row' should not be there. Commit session
|
||||
# first to ensure any changes made above are committed.
|
||||
session.commit()
|
||||
PlaylistRows.delete_higher_rows(session, self.playlist_id, row)
|
||||
# Any rows in the database with a row_number higher that the
|
||||
# current value of 'row' should not be there. Commit session
|
||||
# first to ensure any changes made above are committed.
|
||||
session.commit()
|
||||
PlaylistRows.delete_higher_rows(session, self.playlist_id, row)
|
||||
|
||||
# def save_playlist(self, session) -> None:
|
||||
# """
|
||||
@ -1231,15 +1246,19 @@ class PlaylistTab(QTableWidget):
|
||||
#
|
||||
# self.save_playlist(session)
|
||||
# self.update_display(session)
|
||||
#
|
||||
# def _drop_on(self, event): # review
|
||||
# index = self.indexAt(event.pos())
|
||||
# if not index.isValid():
|
||||
# return self.rowCount()
|
||||
#
|
||||
# return (index.row() + 1 if self._is_below(event.pos(), index)
|
||||
# else index.row())
|
||||
#
|
||||
|
||||
def _drop_on(self, event):
|
||||
"""
|
||||
https://stackoverflow.com/questions/26227885/drag-and-drop-rows-within-qtablewidget
|
||||
"""
|
||||
|
||||
index = self.indexAt(event.pos())
|
||||
if not index.isValid():
|
||||
return self.rowCount()
|
||||
|
||||
return (index.row() + 1 if self._is_below(event.pos(), index)
|
||||
else index.row())
|
||||
|
||||
# def _edit_note_cell(self, row, column): # review
|
||||
# """Called when table is single-clicked"""
|
||||
#
|
||||
@ -1481,20 +1500,24 @@ class PlaylistTab(QTableWidget):
|
||||
# if repaint:
|
||||
# self.save_playlist(session)
|
||||
# self.update_display(session, clear_selection=False)
|
||||
#
|
||||
# def _is_below(self, pos, index): # review
|
||||
# rect = self.visualRect(index)
|
||||
# margin = 2
|
||||
# if pos.y() - rect.top() < margin:
|
||||
# return False
|
||||
# elif rect.bottom() - pos.y() < margin:
|
||||
# return True
|
||||
# return (
|
||||
# rect.contains(pos, True) and not
|
||||
# (int(self.model().flags(index)) & Qt.ItemIsDropEnabled)
|
||||
# and pos.y() >= rect.center().y() # noqa W503
|
||||
# )
|
||||
#
|
||||
|
||||
def _is_below(self, pos, index): # review
|
||||
"""
|
||||
https://stackoverflow.com/questions/26227885/drag-and-drop-rows-within-qtablewidget
|
||||
"""
|
||||
|
||||
rect = self.visualRect(index)
|
||||
margin = 2
|
||||
if pos.y() - rect.top() < margin:
|
||||
return False
|
||||
elif rect.bottom() - pos.y() < margin:
|
||||
return True
|
||||
return (
|
||||
rect.contains(pos, True) and not
|
||||
(int(self.model().flags(index)) & Qt.ItemIsDropEnabled)
|
||||
and pos.y() >= rect.center().y() # noqa W503
|
||||
)
|
||||
|
||||
# def _is_note_row(self, row: int) -> bool:
|
||||
# """
|
||||
# Return True if passed row is a note row, else False
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
"""Drop uniquerow index on playlist_rows
|
||||
|
||||
Revision ID: 29c0d7ffc741
|
||||
Revises: 3b063011ed67
|
||||
Create Date: 2022-08-06 22:21:46.881378
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '29c0d7ffc741'
|
||||
down_revision = '3b063011ed67'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.drop_index('uniquerow', table_name='playlist_rows')
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.create_index('uniquerow', 'playlist_rows', ['row_number', 'playlist_id'], unique=True)
|
||||
Loading…
Reference in New Issue
Block a user