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_id = Column(Integer, ForeignKey('tracks.id'), nullable=True)
track = relationship("Tracks", back_populates="playlistrows") 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: def __repr__(self) -> str:
return ( return (
f"<PlaylistRow(id={self.id}, playlist_id={self.playlist_id}, " f"<PlaylistRow(id={self.id}, playlist_id={self.playlist_id}, "

View File

@ -9,9 +9,9 @@ from PyQt5.QtCore import Qt
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QColor, QColor,
QFont, QFont,
# QDropEvent QDropEvent
) )
# from PyQt5 import QtWidgets from PyQt5 import QtWidgets
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QAbstractItemView,
# QApplication, # QApplication,
@ -37,9 +37,8 @@ from helpers import (
get_relative_date, get_relative_date,
# open_in_audacity # open_in_audacity
) )
# from log import log.debug, log.error from log import log
from models import ( from models import (
# Notes,
Playdates, Playdates,
Playlists, Playlists,
PlaylistRows, PlaylistRows,
@ -115,9 +114,9 @@ class PlaylistTab(QTableWidget):
# Set up widget # Set up widget
# self.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers) # self.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers)
self.setAlternatingRowColors(True) self.setAlternatingRowColors(True)
# self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
# self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
# self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
self.setRowCount(0) self.setRowCount(0)
self.setColumnCount(len(columns)) self.setColumnCount(len(columns))
@ -133,14 +132,12 @@ class PlaylistTab(QTableWidget):
key=lambda item: item.idx))] key=lambda item: item.idx))]
) )
# self.setDragEnabled(True) self.setAcceptDrops(True)
# self.setAcceptDrops(True) self.viewport().setAcceptDrops(True)
# self.viewport().setAcceptDrops(True) self.setDragDropOverwriteMode(False)
# self.setDragDropOverwriteMode(False) self.setDropIndicatorShown(True)
# self.setDropIndicatorShown(True) self.setDragDropMode(QAbstractItemView.InternalMove)
# self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setDragEnabled(False)
# self.setSelectionBehavior(QAbstractItemView.SelectRows)
# self.setDragDropMode(QAbstractItemView.InternalMove)
# #
# # This property defines how the widget shows a context menu # # This property defines how the widget shows a context menu
# self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) # self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
@ -182,55 +179,66 @@ class PlaylistTab(QTableWidget):
if record.f_int != self.columnWidth(idx): if record.f_int != self.columnWidth(idx):
record.update(session, {'f_int': width}) record.update(session, {'f_int': width})
# def __repr__(self) -> str: def __repr__(self) -> str:
# return f"<PlaylistTab(id={self.playlist_id}" return f"<PlaylistTab(id={self.playlist_id}"
# #
# # ########## Events ########## # # ########## Events ##########
# #
# def dropEvent(self, event: QDropEvent) -> None: # def closeEditor(self, editor, hint): # review
# # if not event.isAccepted() and event.source() == self: # super(PlaylistTab, self).closeEditor(editor, hint)
# if not event.source() == self: # self.cellEditingEnded.emit()
# return # We don't accept external drops
# def dropEvent(self, event: QDropEvent) -> None:
# drop_row: int = self._drop_on(event) """
# Handle drag/drop of rows
# rows: List = sorted(set(item.row() for item in self.selectedItems()))
# rows_to_move = [ https://stackoverflow.com/questions/26227885/drag-and-drop-rows-within-qtablewidget
# [QTableWidgetItem(self.item(row_index, column_index)) for """
# column_index in range(self.columnCount())]
# for row_index in rows if not event.source() == self:
# ] return # We don't accept external drops
# for row_index in reversed(rows):
# self.removeRow(row_index) drop_row: int = self._drop_on(event)
# if row_index < drop_row:
# drop_row -= 1 rows: List = sorted(set(item.row() for item in self.selectedItems()))
# rows_to_move = [
# for row_index, data in enumerate(rows_to_move): [QTableWidgetItem(self.item(row_index, column_index)) for
# row_index += drop_row column_index in range(self.columnCount())]
# self.insertRow(row_index) for row_index in rows
# for column_index, column_data in enumerate(data): ]
# self.setItem(row_index, column_index, column_data) for row_index in reversed(rows):
# event.accept() self.removeRow(row_index)
# # The above doesn't handle column spans, which we use in note if row_index < drop_row:
# # rows. Check and fix: drop_row -= 1
# 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)): for row_index, data in enumerate(rows_to_move):
# if row in self._get_notes_rows(): row_index += drop_row
# self.setSpan( self.insertRow(row_index)
# row, self.COL_NOTE, self.NOTE_ROW_SPAN, self.NOTE_COL_SPAN) for column_index, column_data in enumerate(data):
# self.setItem(row_index, column_index, column_data)
# # Scroll to drop zone event.accept()
# self.scrollToItem(self.item(row, 1)) # The above doesn't handle column spans, which we use in note
# super().dropEvent(event) # rows. Check and fix:
# for row in range(drop_row, drop_row + len(rows_to_move)):
# log.debug( if not self._get_row_track_id(row):
# "playlist.dropEvent(): " self.setSpan(row, 1, 1, len(columns))
# f"Moved row(s) {rows} to become row {drop_row}"
# ) # Scroll to drop zone
# self.scrollToItem(self.item(row, 1))
# with Session() as session: # checked
# self.save_playlist(session) # Reset drag mode to allow row selection by dragging
# self.update_display(session) 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 # def edit(self, index, trigger, event): # review
# result = super(PlaylistTab, self).edit(index, trigger, event) # result = super(PlaylistTab, self).edit(index, trigger, event)
@ -238,10 +246,6 @@ class PlaylistTab(QTableWidget):
# self.cellEditingStarted.emit(index.row(), index.column()) # self.cellEditingStarted.emit(index.row(), index.column())
# return result # return result
# #
# def closeEditor(self, editor, hint): # review
# super(PlaylistTab, self).closeEditor(editor, hint)
# self.cellEditingEnded.emit()
#
# def eventFilter(self, source, event): # review # def eventFilter(self, source, event): # review
# """Used to process context (right-click) menu, which is defined here""" # """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) # act_delete.triggered.connect(self._delete_rows)
# #
# return super(PlaylistTab, self).eventFilter(source, event) # 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 ########## # # ########## Externally called functions ##########
# #
# def closeEvent(self, event) -> None: # def closeEvent(self, event) -> None:
@ -1231,15 +1246,19 @@ class PlaylistTab(QTableWidget):
# #
# self.save_playlist(session) # self.save_playlist(session)
# self.update_display(session) # self.update_display(session)
#
# def _drop_on(self, event): # review def _drop_on(self, event):
# index = self.indexAt(event.pos()) """
# if not index.isValid(): https://stackoverflow.com/questions/26227885/drag-and-drop-rows-within-qtablewidget
# return self.rowCount() """
#
# return (index.row() + 1 if self._is_below(event.pos(), index) index = self.indexAt(event.pos())
# else index.row()) 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 # def _edit_note_cell(self, row, column): # review
# """Called when table is single-clicked""" # """Called when table is single-clicked"""
# #
@ -1481,20 +1500,24 @@ class PlaylistTab(QTableWidget):
# if repaint: # if repaint:
# self.save_playlist(session) # self.save_playlist(session)
# self.update_display(session, clear_selection=False) # self.update_display(session, clear_selection=False)
#
# def _is_below(self, pos, index): # review def _is_below(self, pos, index): # review
# rect = self.visualRect(index) """
# margin = 2 https://stackoverflow.com/questions/26227885/drag-and-drop-rows-within-qtablewidget
# if pos.y() - rect.top() < margin: """
# return False
# elif rect.bottom() - pos.y() < margin: rect = self.visualRect(index)
# return True margin = 2
# return ( if pos.y() - rect.top() < margin:
# rect.contains(pos, True) and not return False
# (int(self.model().flags(index)) & Qt.ItemIsDropEnabled) elif rect.bottom() - pos.y() < margin:
# and pos.y() >= rect.center().y() # noqa W503 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: # def _is_note_row(self, row: int) -> bool:
# """ # """
# Return True if passed row is a note row, else False # 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)