A Pythonic Qt List Model Implementation
The following is an attempt at a reference implementation of a PyQt list model. It supports rearranging items via internal drag and drop. By the time you have that, of course, you already have the ability to insert rows, remove rows, set data, and export drag-and-drop data to other applications. That’s everything.
Python idioms are used whenever possible.
It should also be a good foundation for implementing a table model (which has multiple columns), or a tree model (where child items are not strings, but objects with their own parent and children).
Here it is:
#!/usr/bin/env python from sip import setapi setapi("QDate", 2) setapi("QDateTime", 2) setapi("QTextStream", 2) setapi("QTime", 2) setapi("QVariant", 2) setapi("QString", 2) setapi("QUrl", 2) from PyQt4.QtCore import QAbstractListModel, QMimeData, QModelIndex, Qt from PyQt4.QtGui import QApplication, QListView, QMainWindow import sys def main(): app = QApplication(sys.argv) main = MainWindow() main.show() sys.exit(app.exec_()) class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) view = QListView() view.setDragEnabled(True) view.setAcceptDrops(True) view.setDropIndicatorShown(True) view.setSelectionMode(view.ExtendedSelection) self.model = ListModel(view) view.setModel(self.model) self.setCentralWidget(view) class ListModel(QAbstractListModel): Mimetype = 'application/vnd.row.list' def __init__(self, parent=None): super(ListModel, self).__init__(parent) self.__data = ['line 1', 'line 2', 'line 3', 'line 4'] def data(self, index, role=Qt.DisplayRole): if not index.isValid(): return None if index.row() > len(self.__data): return None if role == Qt.DisplayRole or role == Qt.EditRole: return self.__data[index.row()] return None def dropMimeData(self, data, action, row, column, parent): if action == Qt.IgnoreAction: return True if not data.hasFormat(self.Mimetype): return False if column > 0: return False strings = str(data.data(self.Mimetype)).split('\n') self.insertRows(row, len(strings)) for i, text in enumerate(strings): self.setData(self.index(row + i, 0), text) return True def flags(self, index): flags = super(ListModel, self).flags(index) if index.isValid(): flags |= Qt.ItemIsEditable flags |= Qt.ItemIsDragEnabled else: flags = Qt.ItemIsDropEnabled return flags def insertRows(self, row, count, parent=QModelIndex()): self.beginInsertRows(QModelIndex(), row, row + count - 1) self.__data[row:row] = [''] * count self.endInsertRows() return True def mimeData(self, indexes): sortedIndexes = sorted([index for index in indexes if index.isValid()], key=lambda index: index.row()) encodedData = '\n'.join(self.data(index, Qt.DisplayRole) for index in sortedIndexes) mimeData = QMimeData() mimeData.setData(self.Mimetype, encodedData) return mimeData def mimeTypes(self): return [self.Mimetype] def removeRows(self, row, count, parent=QModelIndex()): self.beginRemoveRows(QModelIndex(), row, row + count - 1) del self.__data[row:row + count] self.endRemoveRows() return True def rowCount(self, parent=QModelIndex()): return len(self.__data) def setData(self, index, value, role=Qt.EditRole): if not index.isValid() or role != Qt.EditRole: return False self.__data[index.row()] = value self.dataChanged.emit(index, index) return True def supportedDropActions(self): return Qt.MoveAction if __name__ == '__main__': main()