Enable drag-select, then drag selection

This commit is contained in:
Keith Edmunds 2022-08-06 22:41:18 +01:00
parent 32e81fb074
commit 96255e83ea
3 changed files with 174 additions and 132 deletions

View File

@ -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

View File

@ -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

View File

@ -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)