class ListModel(QAbstractTableModel): orderChanged = pyqtSignal() elementSelected = pyqtSignal(int) class ColumnID(): ''' Define how many column the model holds and their type ''' ncols=2 Name=0 Delete=1 def __init__(self, elements=None, parent=None): ''' Common interface for the labelListModel and the boxListModel see concrete implementations for details :param elements: :param parent: ''' QAbstractTableModel.__init__(self, parent) if elements is None: elements = [] self._elements = list(elements) self._selectionModel = QItemSelectionModel(self) def onSelectionChanged(selected, deselected): if selected: ind = selected[0].indexes() if len(ind)>0: self.elementSelected.emit(ind[0].row()) self._selectionModel.selectionChanged.connect(onSelectionChanged) self._allowRemove = True self._toolTipSuffixes = {} self.unremovable_rows=[] #rows in this list cannot be removed from the gui, # to add to this list call self.makeRowPermanent(int) # to remove make the self.makeRowRemovable(int) def makeRowPermanent(self,rowindex): """ The rowindex cannot be removed from gui to remove this index use self.makeRowRemovable """ self.unremovable_rows.append(rowindex) def makeRowRemovable(self,rowindex): self.unremovable_rows.pop(rowindex) def __len__(self): return len(self._elements) def __getitem__(self, i): return self._elements[i] def selectedRow(self): selected = self._selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def rowCount(self, parent=None): return len(self._elements) def columnCount(self, parent): return self.ColumnID.ncols def _getToolTipSuffix(self, row): """ Get the middle column tooltip suffix """ suffix = "; Click to select" if row in self._toolTipSuffixes: suffix = self._toolTipSuffixes[row] return suffix def _setToolTipSuffix(self, row, text): """ Set the middle column tooltip suffix """ self._toolTipSuffixes[row] = text index = self.createIndex(row, 1) self.dataChanged.emit(index, index) class EntryToolTipAdapter(object): """This class can be used to make each row look like a separate widget with its own tooltip. In this case, the "tooltip" is the suffix appended to the tooltip of the middle column. """ def __init__(self, table, row): self._row = row self._table = table def toolTip(self): return self._table._getToolTipSuffix(self._row) def setToolTip(self, text): self._table._setToolTipSuffix(self._row, text) def insertRow(self, position, object, parent=QModelIndex()): self.beginInsertRows(parent, position, position) object.changed.connect(self.modelReset) self._elements.insert(position, object) self.endInsertRows() return True def removeRow(self, position, parent=QModelIndex()): if position in self.unremovable_rows: return False self.beginRemoveRows(parent, position, position) value = self._elements[position] logger.debug("removing row: " + str(value)) self._elements.remove(value) self.endRemoveRows() return True def allowRemove(self, check): #Allow removing of rows. Needed to be able to disallow it #in interactive mode self._allowRemove = check self.dataChanged.emit(self.createIndex(0, self.ColumnID.Delete), self.createIndex(self.rowCount(), self.ColumnID.Delete)) def data(self, index, role): ''' Reimplement, see labelListModel or boxListModel for concrete example :param index: :param role: ''' if role == Qt.EditRole and index.column() == self.ColumnID.Name: return self._elements[index.row()].name elif role == Qt.ToolTipRole and index.column() == self.ColumnID.Delete: return "Delete {}".format(self._elements[index.row()].name) elif role == Qt.ToolTipRole and index.column() == self.ColumnID.Name: suffix = self._getToolTipSuffix(index.row()) return "{}\nDouble click to rename {}".format( self._elements[index.row()].name, suffix) elif role == Qt.DisplayRole and index.column() == self.ColumnID.Name: row = index.row() value = self._elements[row] return value.name if role == Qt.DecorationRole and index.column() == self.ColumnID.Delete: if index.row() in self.unremovable_rows: return row = index.row() pixmap = QPixmap(_NPIXELS, _NPIXELS) pixmap.fill(Qt.transparent) painter = QPainter() painter.begin(pixmap) painter.setRenderHint(QPainter.Antialiasing) painter.setBrush(QColor("red")) painter.drawEllipse(1, 1, _NPIXELS - 2, _NPIXELS - 2) pen = QPen(QColor("black")) pen.setWidth(2) painter.setPen(pen) x = _XSTART y = _NPIXELS - x painter.drawLine(x, x, y, y) painter.drawLine(y, x, x, y) painter.end() icon = QIcon(pixmap) return icon def flags(self, index): ''' Reimplement, see labelListModel or boxListModel for concrete example :param index: ''' if index.column() == self.ColumnID.Delete: if self._allowRemove: return Qt.ItemIsEnabled | Qt.ItemIsSelectable else: return Qt.NoItemFlags elif index.column() == self.ColumnID.Name: return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable def setData(self, index, value, role=Qt.EditRole): ''' Reimplement, see labelListModel or boxListModel for concrete example :param index: ''' if role == Qt.EditRole and index.column() == self.ColumnID.Name: row = index.row() name = value self._elements[row].name = str(name.toString()) self.dataChanged.emit(index, index) return True return False def select(self, row): ''' Reimplement, see labelListModel or boxListModel for concrete example :param row ''' self._selectionModel.clear() self._selectionModel.select(self.index(row, self.ColumnID.Name), QItemSelectionModel.Select) def clearSelectionModel(self): self._selectionModel.clear()
class LayerStackModel(QAbstractListModel): canMoveSelectedUp = pyqtSignal("bool") canMoveSelectedDown = pyqtSignal("bool") canDeleteSelected = pyqtSignal("bool") orderChanged = pyqtSignal() layerAdded = pyqtSignal(Layer, int) # is now in row layerRemoved = pyqtSignal(Layer, int) # was in row stackCleared = pyqtSignal() def __init__(self, parent=None): QAbstractListModel.__init__(self, parent) self._layerStack = [] self.selectionModel = QItemSelectionModel(self) self.selectionModel.selectionChanged.connect(self.updateGUI) self.selectionModel.selectionChanged.connect(self._onSelectionChanged) self._movingRows = False QTimer.singleShot(0, self.updateGUI) def _handleRemovedLayer(layer): # Layerstacks *own* the layers they hold, and thus are # responsible for cleaning them up when they are removed: layer.clean_up() self.layerRemoved.connect(_handleRemovedLayer) #### ## High level API to manipulate the layerstack ### def __len__(self): return self.rowCount() def __repr__(self): return "<LayerStackModel: layerStack='%r'>" % (self._layerStack, ) def __getitem__(self, i): return self._layerStack[i] def __iter__(self): return self._layerStack.__iter__() def layerIndex(self, layer): #note that the 'index' function already has a different implementation #from Qt side return self._layerStack.index(layer) def findMatchingIndex(self, func): """Call the given function with each layer and return the index of the first layer for which f is True.""" for index, layer in enumerate(self._layerStack): if func(layer): return index raise ValueError("No matching layer in stack.") def append(self, data): self.insert(0, data) def clear(self): if len(self) > 0: self.removeRows(0, len(self)) self.stackCleared.emit() def insert(self, index, data): """ Insert a layer into this layer stack, which *takes ownership* of the layer. """ assert isinstance( data, Layer), "Only Layers can be added to a LayerStackModel" self.insertRow(index) self.setData(self.index(index), data) if self.selectedRow() >= 0: self.selectionModel.select(self.index(self.selectedRow()), QItemSelectionModel.Deselect) self.selectionModel.select(self.index(index), QItemSelectionModel.Select) data.changed.connect( functools.partial(self._onLayerChanged, self.index(index))) index = self._layerStack.index(data) self.layerAdded.emit(data, index) self.updateGUI() def selectRow(self, row): already_selected_rows = self.selectionModel.selectedRows() if len(already_selected_rows) == 1 and row == already_selected_rows[0]: # Nothing to do if this row is already selected return self.selectionModel.clear() self.selectionModel.setCurrentIndex(self.index(row), QItemSelectionModel.SelectCurrent) @pyqtSignature("deleteSelected()") def deleteSelected(self): num_rows = len(self.selectionModel.selectedRows()) assert num_rows == 1, "Can't delete selected row: {} layers are currently selected.".format( num_rows) row = self.selectionModel.selectedRows()[0] layer = self._layerStack[row.row()] assert not layer._cleaned_up, "This layer ({}) has already been cleaned up. Shouldn't it already be removed from the layerstack?".format( layer.name) self.removeRow(row.row()) if self.rowCount() > 0: self.selectionModel.select(self.index(0), QItemSelectionModel.Select) self.layerRemoved.emit(layer, row.row()) self.updateGUI() @pyqtSignature("moveSelectedUp()") def moveSelectedUp(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != 0: oldRow = row.row() newRow = oldRow - 1 self._moveToRow(oldRow, newRow) @pyqtSignature("moveSelectedDown()") def moveSelectedDown(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != self.rowCount() - 1: oldRow = row.row() newRow = oldRow + 1 self._moveToRow(oldRow, newRow) @pyqtSignature("moveSelectedToTop()") def moveSelectedToTop(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != 0: oldRow = row.row() newRow = 0 self._moveToRow(oldRow, newRow) @pyqtSignature("moveSelectedToBottom()") def moveSelectedToBottom(self): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != self.rowCount() - 1: oldRow = row.row() newRow = self.rowCount() - 1 self._moveToRow(oldRow, newRow) def moveSelectedToRow(self, newRow): assert len(self.selectionModel.selectedRows()) == 1 row = self.selectionModel.selectedRows()[0] if row.row() != newRow: oldRow = row.row() self._moveToRow(oldRow, newRow) def _moveToRow(self, oldRow, newRow): d = self._layerStack[oldRow] self.removeRow(oldRow) self.insertRow(newRow) self.setData(self.index(newRow), d) self.selectionModel.select(self.index(newRow), QItemSelectionModel.Select) self.orderChanged.emit() self.updateGUI() #### ## Low level API. To add, remove etc. layers use the high level API from above. #### def updateGUI(self): self.canMoveSelectedUp.emit(self.selectedRow() > 0) self.canMoveSelectedDown.emit(self.selectedRow() < self.rowCount() - 1) self.canDeleteSelected.emit(self.rowCount() > 0) self.wantsUpdate() def selectedRow(self): selected = self.selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def rowCount(self, parent=QModelIndex()): if not parent.isValid(): return len(self._layerStack) return 0 def insertRows(self, row, count, parent=QModelIndex()): '''Insert empty rows in the stack. DO NOT USE THIS METHOD TO INSERT NEW LAYERS! Always use the insert() or append() method. ''' if parent.isValid(): return False oldRowCount = self.rowCount() #for some reason, row can be negative! beginRow = max(0, row) endRow = min(beginRow + count - 1, len(self._layerStack)) self.beginInsertRows(parent, beginRow, endRow) while (beginRow <= endRow): self._layerStack.insert(row, Layer(datasources=[])) beginRow += 1 self.endInsertRows() assert self.rowCount( ) == oldRowCount + 1, "oldRowCount = %d, self.rowCount() = %d" % ( oldRowCount, self.rowCount()) return True def removeRows(self, row, count, parent=QModelIndex()): '''Remove rows from the stack. DO NOT USE THIS METHOD TO REMOVE LAYERS! Use the deleteSelected() method instead. ''' if parent.isValid(): return False if row + count <= 0 or row >= len(self._layerStack): return False oldRowCount = self.rowCount() beginRow = max(0, row) endRow = min(row + count - 1, len(self._layerStack) - 1) self.beginRemoveRows(parent, beginRow, endRow) while (beginRow <= endRow): del self._layerStack[row] beginRow += 1 self.endRemoveRows() return True def flags(self, index): defaultFlags = Qt.ItemIsSelectable | Qt.ItemIsEditable | Qt.ItemIsEnabled if index.isValid(): return Qt.ItemIsDragEnabled | defaultFlags else: return Qt.ItemIsDropEnabled | defaultFlags def supportedDropActions(self): return Qt.MoveAction def data(self, index, role=Qt.DisplayRole): if not index.isValid(): return None if index.row() > len(self._layerStack): return None if role == Qt.DisplayRole or role == Qt.EditRole: return self._layerStack[index.row()] elif role == Qt.ToolTipRole: return self._layerStack[index.row()].toolTip() else: return None def setData(self, index, value, role=Qt.EditRole): '''Replace one layer with another. DO NOT USE THIS METHOD TO INSERT NEW LAYERS! Use deleteSelected() followed by insert() or append(). ''' if role == Qt.EditRole: layer = value if not isinstance(value, Layer): layer = value.toPyObject() self._layerStack[index.row()] = layer self.dataChanged.emit(index, index) return True elif role == Qt.ToolTipRole: self._layerStack[index.row()].setToolTip() return True return False def headerData(self, section, orientation, role=Qt.DisplayRole): if role != Qt.DisplayRole: return None if orientation == Qt.Horizontal: return "Column %r" % section else: return "Row %r" % section def wantsUpdate(self): self.layoutChanged.emit() def _onLayerChanged(self, idx): self.dataChanged.emit(idx, idx) self.updateGUI() def _onSelectionChanged(self, selected, deselected): for idx in deselected.indexes(): self[idx.row()].setActive(False) for idx in selected.indexes(): self[idx.row()].setActive(True)
class LabelListModel(QAbstractTableModel): orderChanged = pyqtSignal() labelSelected = pyqtSignal(int) def __init__(self, labels = [], parent = None): QAbstractTableModel.__init__(self, parent) self._labels = labels self._selectionModel = QItemSelectionModel(self) def onSelectionChanged(selected, deselected): if selected: self.labelSelected.emit(selected[0].indexes()[0].row()) self._selectionModel.selectionChanged.connect(onSelectionChanged) self._allowRemove = True def __len__(self): return len(self._labels) def __getitem__(self, i): return self._labels[i] def selectedRow(self): selected = self._selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def rowCount(self, parent=None): return len(self._labels) def columnCount(self, parent): return 3 def data(self, index, role): if role == Qt.EditRole and index.column() == 0: return self._labels[index.row()].color if role == Qt.EditRole and index.column() == 1: return self._labels[index.row()].name if role == Qt.ToolTipRole and index.column() == 0: return "Hex code : " + self._labels[index.row()].color.name() + "\n DoubleClick to change" if role == Qt.ToolTipRole and index.column() == 1: return self._labels[index.row()].name + "\n DoubleClick to rename" if role == Qt.ToolTipRole and index.column() == 2: return "Delete " + self._labels[index.row()].name if role == Qt.DecorationRole and index.column() == 0: row = index.row() value = self._labels[row] pixmap = QPixmap(26, 26) pixmap.fill(value.color) icon = QIcon(pixmap) return icon if role == Qt.DecorationRole and index.column() == 2: row = index.row() pixmap = QPixmap(26, 26) pixmap.fill(Qt.transparent) painter = QPainter() painter.begin(pixmap) painter.setRenderHint(QPainter.Antialiasing) painter.setBrush(QColor("red")) painter.drawEllipse(1, 1, 24, 24) pen = QPen(QColor("black")) pen.setWidth(2) painter.setPen(pen) painter.drawLine(8,8, 18,18) painter.drawLine(18,8, 8,18) painter.end() icon = QIcon(pixmap) return icon if role == Qt.DisplayRole and index.column() == 1: row = index.row() value = self._labels[row] return value.name def flags(self, index): if index.column() == 0: return Qt.ItemIsEnabled | Qt.ItemIsSelectable elif index.column() == 1: return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable elif index.column() == 2: if self._allowRemove: return Qt.ItemIsEnabled | Qt.ItemIsSelectable else: return Qt.NoItemFlags def setData(self, index, value, role = Qt.EditRole): if role == Qt.EditRole and index.column() == 0: row = index.row() color = QColor(value) if color.isValid(): self._labels[row].color = color self.dataChanged.emit(index, index) return True if role == Qt.EditRole and index.column() == 1: row = index.row() name = value self._labels[row].name = str(name.toString()) self.dataChanged.emit(index, index) return True return False def insertRow(self, position, object, parent = QModelIndex()): self.beginInsertRows(parent, position, position) self._labels.insert(position, object) self.endInsertRows() return True def removeRow(self, position, parent = QModelIndex()): self.beginRemoveRows(parent, position, position) value = self._labels[position] print "removing row: ", value self._labels.remove(value) self.endRemoveRows() return True def allowRemove(self, check): #Allow removing of rows. Needed to be able to disallow it #in interactive mode self._allowRemove = check self.dataChanged.emit(self.createIndex(0, 2), self.createIndex(self.rowCount(), 2)) def select(self, row): self._selectionModel.clear() self._selectionModel.select(self.index(row, 0), QItemSelectionModel.Select) self._selectionModel.select(self.index(row, 1), QItemSelectionModel.Select)
class LabelListModel(QAbstractTableModel): orderChanged = pyqtSignal() labelSelected = pyqtSignal(int) def __init__(self, labels=[], parent=None): QAbstractTableModel.__init__(self, parent) self._labels = labels self._selectionModel = QItemSelectionModel(self) def onSelectionChanged(selected, deselected): if selected: self.labelSelected.emit(selected[0].indexes()[0].row()) self._selectionModel.selectionChanged.connect(onSelectionChanged) self._allowRemove = True self._toolTipSuffixes = {} def __len__(self): return len(self._labels) def __getitem__(self, i): return self._labels[i] def selectedRow(self): selected = self._selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def rowCount(self, parent=None): return len(self._labels) def columnCount(self, parent): return 3 def _getToolTipSuffix(self, row): """ Get the middle column tooltip suffix """ suffix = "; Click to select" if row in self._toolTipSuffixes: suffix = self._toolTipSuffixes[row] return suffix def _setToolTipSuffix(self, row, text): """ Set the middle column tooltip suffix """ self._toolTipSuffixes[row] = text index = self.createIndex(row, 1) self.dataChanged.emit(index, index) class EntryToolTipAdapter(object): """ This class can be used to make each row look like a separate widget with its own tooltip. In this case, the "tooltip" is the suffix appended to the tooltip of the middle column. """ def __init__(self, table, row): self._row = row self._table = table def toolTip(self): return self._table._getToolTipSuffix(self._row) def setToolTip(self, text): self._table._setToolTipSuffix(self._row, text) def data(self, index, role): if role == Qt.EditRole and index.column() == 0: return self._labels[index.row()].color if role == Qt.EditRole and index.column() == 1: return self._labels[index.row()].name if role == Qt.ToolTipRole and index.column() == 0: return "Hex code : " + self._labels[ index.row()].color.name() + "\n DoubleClick to change" if role == Qt.ToolTipRole and index.column() == 1: suffix = self._getToolTipSuffix(index.row()) return self._labels[ index.row()].name + "\n DoubleClick to rename" + suffix if role == Qt.ToolTipRole and index.column() == 2: return "Delete " + self._labels[index.row()].name if role == Qt.DecorationRole and index.column() == 0: row = index.row() value = self._labels[row] pixmap = QPixmap(26, 26) pixmap.fill(value.color) icon = QIcon(pixmap) return icon if role == Qt.DecorationRole and index.column() == 2: row = index.row() pixmap = QPixmap(26, 26) pixmap.fill(Qt.transparent) painter = QPainter() painter.begin(pixmap) painter.setRenderHint(QPainter.Antialiasing) painter.setBrush(QColor("red")) painter.drawEllipse(1, 1, 24, 24) pen = QPen(QColor("black")) pen.setWidth(2) painter.setPen(pen) painter.drawLine(8, 8, 18, 18) painter.drawLine(18, 8, 8, 18) painter.end() icon = QIcon(pixmap) return icon if role == Qt.DisplayRole and index.column() == 1: row = index.row() value = self._labels[row] return value.name def flags(self, index): if index.column() == 0: return Qt.ItemIsEnabled | Qt.ItemIsSelectable elif index.column() == 1: return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable elif index.column() == 2: if self._allowRemove: return Qt.ItemIsEnabled | Qt.ItemIsSelectable else: return Qt.NoItemFlags def setData(self, index, value, role=Qt.EditRole): if role == Qt.EditRole and index.column() == 0: row = index.row() color = QColor(value) if color.isValid(): self._labels[row].color = color self.dataChanged.emit(index, index) return True if role == Qt.EditRole and index.column() == 1: row = index.row() name = value self._labels[row].name = str(name.toString()) self.dataChanged.emit(index, index) return True return False def insertRow(self, position, object, parent=QModelIndex()): self.beginInsertRows(parent, position, position) self._labels.insert(position, object) self.endInsertRows() return True def removeRow(self, position, parent=QModelIndex()): self.beginRemoveRows(parent, position, position) value = self._labels[position] logger.debug("removing row: " + str(value)) self._labels.remove(value) self.endRemoveRows() return True def allowRemove(self, check): #Allow removing of rows. Needed to be able to disallow it #in interactive mode self._allowRemove = check self.dataChanged.emit(self.createIndex(0, 2), self.createIndex(self.rowCount(), 2)) def select(self, row): self._selectionModel.clear() self._selectionModel.select(self.index(row, 0), QItemSelectionModel.Select) self._selectionModel.select(self.index(row, 1), QItemSelectionModel.Select)
class ListModel(QAbstractTableModel): orderChanged = pyqtSignal() elementSelected = pyqtSignal(int) class ColumnID(): ''' Define how many column the model holds and their type ''' ncols=2 Name=0 Delete=1 def __init__(self, elements=None, parent=None): ''' Common interface for the labelListModel and the boxListModel see concrete implementations for details :param elements: :param parent: ''' QAbstractTableModel.__init__(self, parent) if elements is None: elements = [] self._elements = list(elements) self._selectionModel = QItemSelectionModel(self) def onSelectionChanged(selected, deselected): if selected: ind = selected[0].indexes() if len(ind)>0: self.elementSelected.emit(ind[0].row()) self._selectionModel.selectionChanged.connect(onSelectionChanged) self._allowRemove = True self._toolTipSuffixes = {} self.unremovable_rows=[] #rows in this list cannot be removed from the gui, # to add to this list call self.makeRowPermanent(int) # to remove make the self.makeRowRemovable(int) def makeRowPermanent(self,rowindex): """ The rowindex cannot be removed from gui to remove this index use self.makeRowRemovable """ self.unremovable_rows.append(rowindex) def makeRowRemovable(self,rowindex): self.unremovable_rows.pop(rowindex) def __len__(self): return len(self._elements) def __getitem__(self, i): return self._elements[i] def selectedRow(self): selected = self._selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def rowCount(self, parent=None): return len(self._elements) def columnCount(self, parent): return self.ColumnID.ncols def _getToolTipSuffix(self, row): """ Get the middle column tooltip suffix """ suffix = "; Click to select" if row in self._toolTipSuffixes: suffix = self._toolTipSuffixes[row] return suffix def _setToolTipSuffix(self, row, text): """ Set the middle column tooltip suffix """ self._toolTipSuffixes[row] = text index = self.createIndex(row, 1) self.dataChanged.emit(index, index) class EntryToolTipAdapter(object): """This class can be used to make each row look like a separate widget with its own tooltip. In this case, the "tooltip" is the suffix appended to the tooltip of the middle column. """ def __init__(self, table, row): self._row = row self._table = table def toolTip(self): return self._table._getToolTipSuffix(self._row) def setToolTip(self, text): self._table._setToolTipSuffix(self._row, text) def insertRow(self, position, object, parent=QModelIndex()): self.beginInsertRows(parent, position, position) object.changed.connect(self.modelReset) self._elements.insert(position, object) self.endInsertRows() return True def removeRow(self, position, parent=QModelIndex()): if position in self.unremovable_rows: return False self.beginRemoveRows(parent, position, position) value = self._elements[position] logger.debug("removing row: " + str(value)) self._elements.remove(value) self.endRemoveRows() return True def allowRemove(self, check): #Allow removing of rows. Needed to be able to disallow it #in interactive mode self._allowRemove = check self.dataChanged.emit(self.createIndex(0, self.ColumnID.Delete), self.createIndex(self.rowCount(), self.ColumnID.Delete)) def data(self, index, role): ''' Reimplement, see labelListModel or boxListModel for concrete example :param index: :param role: ''' if role == Qt.EditRole and index.column() == self.ColumnID.Name: name = self._elements[index.row()].name return decode_to_qstring(name, 'utf-8') elif role == Qt.ToolTipRole and index.column() == self.ColumnID.Delete: s = "Delete {}".format(self._elements[index.row()].name) return decode_to_qstring(s, 'utf-8') elif role == Qt.ToolTipRole and index.column() == self.ColumnID.Name: suffix = self._getToolTipSuffix(index.row()) s = "{}\nDouble click to rename {}".format( self._elements[index.row()].name, suffix) return decode_to_qstring(s, 'utf-8') elif role == Qt.DisplayRole and index.column() == self.ColumnID.Name: name = self._elements[index.row()].name return decode_to_qstring(name, 'utf-8') if role == Qt.DecorationRole and index.column() == self.ColumnID.Delete: if index.row() in self.unremovable_rows: return row = index.row() pixmap = QPixmap(_NPIXELS, _NPIXELS) pixmap.fill(Qt.transparent) painter = QPainter() painter.begin(pixmap) painter.setRenderHint(QPainter.Antialiasing) painter.setBrush(QColor("red")) painter.drawEllipse(1, 1, _NPIXELS - 2, _NPIXELS - 2) pen = QPen(QColor("black")) pen.setWidth(2) painter.setPen(pen) x = _XSTART y = _NPIXELS - x painter.drawLine(x, x, y, y) painter.drawLine(y, x, x, y) painter.end() icon = QIcon(pixmap) return icon def flags(self, index): ''' Reimplement, see labelListModel or boxListModel for concrete example :param index: ''' if index.column() == self.ColumnID.Delete: if self._allowRemove: return Qt.ItemIsEnabled | Qt.ItemIsSelectable else: return Qt.NoItemFlags elif index.column() == self.ColumnID.Name: return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable def setData(self, index, value, role=Qt.EditRole): ''' Reimplement, see labelListModel or boxListModel for concrete example :param index: ''' if role == Qt.EditRole and index.column() == self.ColumnID.Name: row = index.row() # value is a user provided QVariant, possibly with unicode # characters in it. internally, we keep a str self._elements[row].name = encode_from_qstring(value.toString(), 'utf-8') self.dataChanged.emit(index, index) return True return False def select(self, row): ''' Reimplement, see labelListModel or boxListModel for concrete example :param row ''' self._selectionModel.clear() self._selectionModel.select(self.index(row, self.ColumnID.Name), QItemSelectionModel.Select) def clearSelectionModel(self): self._selectionModel.clear()
class LabelListModel(QAbstractTableModel): orderChanged = pyqtSignal() labelSelected = pyqtSignal(int) def __init__(self, labels=None, parent=None): QAbstractTableModel.__init__(self, parent) if labels is None: labels = [] self._labels = list(labels) self._selectionModel = QItemSelectionModel(self) def onSelectionChanged(selected, deselected): if selected: self.labelSelected.emit(selected[0].indexes()[0].row()) self._selectionModel.selectionChanged.connect(onSelectionChanged) self._allowRemove = True self._toolTipSuffixes = {} def __len__(self): return len(self._labels) def __getitem__(self, i): return self._labels[i] def selectedRow(self): selected = self._selectionModel.selectedRows() if len(selected) == 1: return selected[0].row() return -1 def selectedIndex(self): row = self.selectedRow() if row >= 0: return self.index(self.selectedRow()) else: return QModelIndex() def rowCount(self, parent=None): return len(self._labels) def columnCount(self, parent): return 3 def _getToolTipSuffix(self, row): """ Get the middle column tooltip suffix """ suffix = "; Click to select" if row in self._toolTipSuffixes: suffix = self._toolTipSuffixes[row] return suffix def _setToolTipSuffix(self, row, text): """ Set the middle column tooltip suffix """ self._toolTipSuffixes[row] = text index = self.createIndex(row, 1) self.dataChanged.emit(index, index) class EntryToolTipAdapter(object): """This class can be used to make each row look like a separate widget with its own tooltip. In this case, the "tooltip" is the suffix appended to the tooltip of the middle column. """ def __init__(self, table, row): self._row = row self._table = table def toolTip(self): return self._table._getToolTipSuffix(self._row) def setToolTip(self, text): self._table._setToolTipSuffix(self._row, text) def data(self, index, role): if role == Qt.EditRole and index.column() == ColumnID.Color: return (self._labels[index.row()].brushColor(), self._labels[index.row()].pmapColor()) if role == Qt.EditRole and index.column() == ColumnID.Name: return self._labels[index.row()].name if role == Qt.ToolTipRole and index.column() == ColumnID.Color: return ("Hex code : {}\nDouble click to change".format( self._labels[index.row()].brushColor().name())) if role == Qt.ToolTipRole and index.column() == ColumnID.Name: suffix = self._getToolTipSuffix(index.row()) return "{}\nDouble click to rename {}".format( self._labels[index.row()].name, suffix) if role == Qt.ToolTipRole and index.column() == ColumnID.Delete: return "Delete {}".format(self._labels[index.row()].name) if role == Qt.DecorationRole and index.column() == ColumnID.Color: row = index.row() value = self._labels[row] if value.brushColor == value.pmapColor(): pixmap = QPixmap(_NPIXELS, _NPIXELS) pixmap.fill(value.brushColor) else: a = value.brushColor().rgba() b = value.pmapColor().rgba() img = QImage(_NPIXELS,_NPIXELS, QImage.Format_RGB32) for i in range(_NPIXELS): for j in range(0, _NPIXELS - i): img.setPixel(i, j, a) for i in range(_NPIXELS): for j in range(_NPIXELS - i, _NPIXELS): img.setPixel(i, j, b) pixmap = QPixmap.fromImage(img) icon = QIcon(pixmap) return icon if role == Qt.DecorationRole and index.column() == ColumnID.Delete: row = index.row() pixmap = QPixmap(_NPIXELS, _NPIXELS) pixmap.fill(Qt.transparent) painter = QPainter() painter.begin(pixmap) painter.setRenderHint(QPainter.Antialiasing) painter.setBrush(QColor("red")) painter.drawEllipse(1, 1, _NPIXELS - 2, _NPIXELS - 2) pen = QPen(QColor("black")) pen.setWidth(2) painter.setPen(pen) x = _XSTART y = _NPIXELS - x painter.drawLine(x, x, y, y) painter.drawLine(y, x, x, y) painter.end() icon = QIcon(pixmap) return icon if role == Qt.DisplayRole and index.column() == ColumnID.Name: row = index.row() value = self._labels[row] return value.name def flags(self, index): if index.column() == ColumnID.Color: return Qt.ItemIsEnabled | Qt.ItemIsSelectable elif index.column() == ColumnID.Name: return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable elif index.column() == ColumnID.Delete: if self._allowRemove: return Qt.ItemIsEnabled | Qt.ItemIsSelectable else: return Qt.NoItemFlags def setData(self, index, value, role=Qt.EditRole): if role == Qt.EditRole and index.column() == ColumnID.Color: row = index.row() brushColor = QColor(value[0]) pmapColor = QColor(value[1]) if brushColor.isValid() and pmapColor.isValid(): print "setData: brushColor = {}, pmapColor = {}".format( brushColor.name(), pmapColor.name()) print " self._labels[row] has type {}".format( type(self._labels[row])) self._labels[row].setBrushColor(brushColor) self._labels[row].setPmapColor(pmapColor) print " self._labels[row].brushColor = {}".format( self._labels[row].brushColor().name()) print " self._labels[row].pmapColor = {}".format( self._labels[row].pmapColor().name()) self.dataChanged.emit(index, index) return True if role == Qt.EditRole and index.column() == ColumnID.Name: row = index.row() name = value self._labels[row].name = str(name.toString()) self.dataChanged.emit(index, index) return True return False def insertRow(self, position, object, parent=QModelIndex()): self.beginInsertRows(parent, position, position) self._labels.insert(position, object) self.endInsertRows() return True def removeRow(self, position, parent=QModelIndex()): self.beginRemoveRows(parent, position, position) value = self._labels[position] logger.debug("removing row: " + str(value)) self._labels.remove(value) self.endRemoveRows() return True def allowRemove(self, check): #Allow removing of rows. Needed to be able to disallow it #in interactive mode self._allowRemove = check self.dataChanged.emit(self.createIndex(0, ColumnID.Delete), self.createIndex(self.rowCount(), ColumnID.Delete)) def select(self, row): self._selectionModel.clear() self._selectionModel.select(self.index(row, ColumnID.Color), QItemSelectionModel.Select) self._selectionModel.select(self.index(row, ColumnID.Name), QItemSelectionModel.Select)