#!/usr/bin/env python import sys from PyQt6.QtCore import (Qt, QAbstractTableModel, QModelIndex, QSortFilterProxyModel) from PyQt6.QtWidgets import (QApplication, QMainWindow, QTableView, QLineEdit, QVBoxLayout, QWidget) class CustomTableModel(QAbstractTableModel): def __init__(self, data): super().__init__() self._data = data def rowCount(self, parent=QModelIndex()): return len(self._data) def columnCount(self, parent=QModelIndex()): return 2 # Row number and data def data(self, index, role=Qt.ItemDataRole.DisplayRole): if role == Qt.ItemDataRole.DisplayRole: row, col = index.row(), index.column() if col == 0: return row + 1 # Row number (1-based index) elif col == 1: return self._data[row] def setData(self, index, value, role=Qt.ItemDataRole.EditRole): if role == Qt.ItemDataRole.EditRole and index.isValid(): self._data[index.row()] = value self.dataChanged.emit(index, index, [Qt.ItemDataRole.EditRole]) return True return False def flags(self, index): default_flags = Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEnabled if index.isValid(): return default_flags | Qt.ItemFlag.ItemIsDragEnabled | Qt.ItemFlag.ItemIsDropEnabled return default_flags | Qt.ItemFlag.ItemIsDropEnabled def removeRow(self, row): self.beginRemoveRows(QModelIndex(), row, row) self._data.pop(row) self.endRemoveRows() def insertRow(self, row, value): self.beginInsertRows(QModelIndex(), row, row) self._data.insert(row, value) self.endInsertRows() def moveRows(self, sourceParent, sourceRow, count, destinationParent, destinationRow): if sourceRow < destinationRow: destinationRow -= 1 self.beginMoveRows(sourceParent, sourceRow, sourceRow, destinationParent, destinationRow) row_data = self._data.pop(sourceRow) self._data.insert(destinationRow, row_data) self.endMoveRows() return True class ProxyModel(QSortFilterProxyModel): def __init__(self): super().__init__() self.filterString = "" def setFilterString(self, text): self.filterString = text self.invalidateFilter() def filterAcceptsRow(self, source_row, source_parent): if self.filterString: data = self.sourceModel().data(self.sourceModel().index(source_row, 1), Qt.ItemDataRole.DisplayRole) return self.filterString in str(data) return True class TableView(QTableView): def __init__(self, model): super().__init__() self.setModel(model) self.setDragDropMode(QTableView.DragDropMode.InternalMove) self.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows) self.setSortingEnabled(False) self.setDragDropOverwriteMode(False) def dropEvent(self, event): source_index = self.indexAt(event.pos()) if not source_index.isValid(): return destination_row = source_index.row() dragged_row = self.currentIndex().row() if dragged_row != destination_row: self.model().sourceModel().moveRows(QModelIndex(), dragged_row, 1, QModelIndex(), destination_row) super().dropEvent(event) self.model().layoutChanged.emit() # Refresh model to update row numbers class MainWindow(QMainWindow): def __init__(self): super().__init__() self.data = ["dog", "hog", "don", "cat", "bat"] self.baseModel = CustomTableModel(self.data) self.proxyModel = ProxyModel() self.proxyModel.setSourceModel(self.baseModel) self.view = TableView(self.proxyModel) self.filterLineEdit = QLineEdit() self.filterLineEdit.setPlaceholderText("Filter by substring") self.filterLineEdit.textChanged.connect(self.proxyModel.setFilterString) layout = QVBoxLayout() layout.addWidget(self.filterLineEdit) layout.addWidget(self.view) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) if __name__ == "__main__": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())