#!/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())