class ListModel(QAbstractTableModel): orderChanged = pyqtSignal() elementSelected = pyqtSignal(int) class ColumnID(object): """ 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, the boxListModel, and the cropListModel 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): """ :param rowIndex: is the index for the label of interest in the current gui setting """ self.unremovable_rows.remove(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 name elif role == Qt.ToolTipRole and index.column() == self.ColumnID.Delete: s = "Delete {}".format(self._elements[index.row()].name) return s 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 s elif role == Qt.DisplayRole and index.column() == self.ColumnID.Name: name = self._elements[index.row()].name return 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 else: return Qt.NoItemFlags 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() self._elements[row].name = value 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) 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() 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) 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) 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) 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 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 ListModel(QAbstractTableModel): orderChanged = pyqtSignal() elementSelected = pyqtSignal(int) class ColumnID(object): ''' 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, the boxListModel, and the cropListModel 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): """ :param rowIndex: is the index for the label of interest in the current gui setting """ self.unremovable_rows.remove(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 name elif role == Qt.ToolTipRole and index.column() == self.ColumnID.Delete: s = "Delete {}".format(self._elements[index.row()].name) return s 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 s elif role == Qt.DisplayRole and index.column() == self.ColumnID.Name: name = self._elements[index.row()].name return 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 else: return Qt.NoItemFlags 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() self._elements[row].name = value 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 GraphDigitGraphicsView(QGraphicsView): sigMouseMovePoint = pyqtSignal(QPoint, QPointF) sigModified = pyqtSignal(bool) sigNewCurveAdded = pyqtSignal() # 自定义信号sigMouseMovePoint,当鼠标移动时,在mouseMoveEvent事件中,将当前的鼠标位置发送出去 # QPoint--传递的是view坐标 def __init__(self, parent=None): super(GraphDigitGraphicsView, self).__init__(parent) # scene rect = QRectF(0, 0, 300, 400) self.scene = QGraphicsScene(rect) # 创建场景 参数:场景区域 self.setScene(self.scene) # 给视图窗口设置场景 # image self.graphicsPixmapItem = QGraphicsPixmapItem() # chart image self.scene.addItem(self.graphicsPixmapItem) # setAntialias self.setRenderHint(QPainter.Antialiasing) # image object stored in project data # item objects storage self.axesxObjs = {} self.axesyObjs = {} self.gridObjs = [] self.curveObjs = {} self.pointObjs = {} # axis coord stored in project data # grid setting stored in project data # grid position self.gridxpos = [] self.gridypos = [] # axes curve and point datamodel self.axesxModel = QStandardItemModel() self.axesyModel = QStandardItemModel() self.axesxSelectModel = QItemSelectionModel(self.axesxModel) self.axesySelectModel = QItemSelectionModel(self.axesyModel) self.curveModel = QStandardItemModel() self.pointsModel = QStandardItemModel() self.curveModel.setHorizontalHeaderLabels( ["current", "name", "visible"]) self.pointsModel.setHorizontalHeaderLabels(["order", "x", "y"]) self.axesxModel.setHorizontalHeaderLabels(["position", "x"]) self.axesyModel.setHorizontalHeaderLabels(["position", "y"]) ### self.xNo = 0 self.yNo = 0 ### self.mode = OpMode.default # data manage self.proj = Digi() # init self.currentCurve = None self.pointsModel.itemChanged.connect(self.changePointOrder) self.curveModel.itemChanged.connect(self.changeCurveVisible) self.scene.selectionChanged.connect(self.slotSceneSeletChanged) # state self._lastCurve = None def load(self, proj): # image # only for load self.scaleGraphImage(proj.imgScale) # axes xadded = [] yadded = [] for xpos, xcoord in proj.data["axesxObjs"].items(): if xpos in xadded: continue else: xadded.append(xpos) item = QGraphicsAxesItem( 0, self.scene.sceneRect().y(), 0, self.scene.sceneRect().y() + self.scene.sceneRect().height()) item.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable) item.setPos(xpos, 0) item.axis = "x" item.setPen(QPen(Qt.red, 1, Qt.DashLine)) self.scene.addItem(item) self.axesxObjs[item] = xcoord labelitem = QStandardItem("x:{}".format(xpos)) labelitem.setData(item) self.axesxModel.appendRow([labelitem, QStandardItem(str(xcoord))]) self.xNo += 1 self.axesxModel.setVerticalHeaderItem( self.axesxModel.rowCount() - 1, QStandardItem("x{}".format(self.xNo))) for ypos, ycoord in proj.data["axesyObjs"].items(): if ypos in yadded: continue else: yadded.append(ypos) item = QGraphicsAxesItem( self.scene.sceneRect().x(), 0, self.scene.sceneRect().x() + self.scene.sceneRect().width(), 0) item.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable) item.setPos(0, ypos) item.axis = "y" item.setPen(QPen(Qt.red, 1, Qt.DashLine)) self.scene.addItem(item) self.axesyObjs[item] = ycoord labelitem = QStandardItem("y:{}".format(ypos)) labelitem.setData(item) self.axesyModel.appendRow([labelitem, QStandardItem(str(ycoord))]) # curve for curve in proj.data["curves"]: self.pointObjs[curve] = [] self.addCurve(curve) clr = RandItem.nextColor() for x, y in proj.data["curves"][curve]: ptitem = QGraphicsPointItem() ptitem.pointColor = clr ptitem.linewidth = 1 ptitem.setPos(x, y) ptitem.parentCurve = curve ptitem.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable) i = pointInsertPosition(ptitem, self.pointObjs[self.currentCurve]) self.pointObjs[self.currentCurve].insert(i, ptitem) self.scene.addItem(ptitem) self.updateCurve(curve) # grid self.calGridCoord() self.updateGrid() self.sigModified.emit(True) self.mode = OpMode.select def resetview(self): self.setGraphImage(None) self.scaleGraphImage(1) for obj in self.axesxObjs: self.scene.removeItem(obj) self.axesxObjs = {} for obj in self.axesyObjs: self.scene.removeItem(obj) self.axesyObjs = {} for obj in self.gridObjs: self.scene.removeItem(obj) self.gridObjs = [] for curve in self.curveObjs: for obj in self.curveObjs[curve]: self.scene.removeItem(obj) self.curveObjs = {} for curve in self.pointObjs: for obj in self.pointObjs[curve]: self.scene.removeItem(obj) self.pointObjs = {} self.axesxModel.clear() self.axesyModel.clear() self.curveModel.clear() self.pointsModel.clear() self.axesxSelectModel.clear() self.axesySelectModel.clear() self.curveModel.setHorizontalHeaderLabels( ["current", "name", "visible"]) self.pointsModel.setHorizontalHeaderLabels(["order", "x", "y"]) self.axesxModel.setHorizontalHeaderLabels(["position", "x"]) self.axesyModel.setHorizontalHeaderLabels(["position", "y"]) self.xNo = 0 self.yNo = 0 ### self.mode = OpMode.default # data manage self.proj.resetData(True) # init self.currentCurve = None self._lastCurve = None def dump(self): proj = self.proj proj.data["axesxObjs"] = {} # [x1,x2,x3] proj.data["axesyObjs"] = {} # [y1,y2,y3] proj.data["curves"] = {} # {'default':(x1,y1),(x2,y2)} # axes for item, xcoord in self.axesxObjs.items(): proj.data["axesxObjs"][item.pos().x()] = xcoord for item, ycoord in self.axesyObjs.items(): proj.data["axesyObjs"][item.pos().y()] = ycoord # curve for curve in self.pointObjs: proj.data["curves"][curve] = [] for item in self.pointObjs[curve]: proj.data["curves"][curve].append((item.x(), item.y())) def mouseMoveEvent(self, evt): pt = evt.pos() # 获取鼠标坐标--view坐标 self.sigMouseMovePoint.emit(pt, self.mapToScene(pt)) # 发送鼠标位置 QGraphicsView.mouseMoveEvent(self, evt) item = self.scene.focusItem() if not item: items = self.scene.selectedItems() if len(items) != 1: return else: item = items[0] if item: if isinstance(item, QGraphicsPointItem) and item.parentCurve: # self.changeCurrentCurve(item.parentCurve) self.updateCurve(self.currentCurve, Qt.red) self.updateCurvePoints(self.currentCurve) self.sigModified.emit(True) elif isinstance(item, QGraphicsAxesItem): if item.axis == "x": if self.mode != OpMode.axesx: self.scene.clearSelection() return self.axesxSelectModel.clearSelection() self.axesySelectModel.clearSelection() for i in range(self.axesxModel.rowCount()): if self.axesxModel.item(i, 0).data() is item: parent = QModelIndex() topleftindex = self.axesxModel.index( i, 0, parent) # self.axesxModel.item(i,0).index() rightbottomindex = self.axesxModel.index( i, 1, parent) self.axesxSelectModel.select( QItemSelection(topleftindex, rightbottomindex), QItemSelectionModel.Select) self.axesxModel.item(i, 0).setText("x:{}".format( evt.pos().x())) break item.setLine( 0, self.scene.sceneRect().y(), 0, self.scene.sceneRect().y() + self.scene.sceneRect().height()) item.setPos(self.mapToScene(evt.pos()).x(), 0) self.sigModified.emit(True) self.calGridCoord() self.updateGrid() elif item.axis == "y": if self.mode != OpMode.axesy: self.scene.clearSelection() return self.axesxSelectModel.clearSelection() self.axesySelectModel.clearSelection() for i in range(self.axesyModel.rowCount()): if self.axesyModel.item(i, 0).data() is item: topleftindex = self.axesyModel.index( i, 0) # self.axesxModel.item(i,0).index() rightbottomindex = self.axesyModel.index(i, 1) self.axesySelectModel.select( QItemSelection(topleftindex, rightbottomindex), QItemSelectionModel.Select) self.axesyModel.item(i, 0).setText("y:{}".format( evt.pos().y())) break item.setLine( self.scene.sceneRect().x(), 0, self.scene.sceneRect().x() + self.scene.sceneRect().width(), 0) item.setPos(0, self.mapToScene(evt.pos()).y()) self.sigModified.emit(True) self.calGridCoord() self.updateGrid() # self.updateCurve(self.currentCurve) # self.repaint() # self.setDragMode(QGraphicsView.NoDrag) #(RubberBandDrag) #ScrollHandDrag) #NoDrag) def mousePressEvent(self, event): super().mousePressEvent(event) self.__pressPt = event.pos() ptscene = self.mapToScene(event.pos()) if self.mode is OpMode.axesx: for axisitem in self.axesxObjs: if abs(ptscene.x() - axisitem.pos().x()) < 5: self.scene.clearSelection() axisitem.setSelected(True) return elif self.mode is OpMode.axesy: for axisitem in self.axesyObjs: if abs(ptscene.y() - axisitem.pos().y()) < 5: self.scene.clearSelection() axisitem.setSelected(True) return def keyPressEvent(self, event: QKeyEvent) -> None: # super().keyPressEvent(event) item = self.scene.focusItem() if not item: items = self.scene.selectedItems() if len(items) != 1: return else: item = items[0] if item: if isinstance(item, QGraphicsPointItem) and item.parentCurve: if event.key() == Qt.Key_Up: item.setPos(item.pos().x(), item.pos().y() - 1) elif event.key() == Qt.Key_Down: item.setPos(item.pos().x(), item.pos().y() + 1) elif event.key() == Qt.Key_Left: item.setPos(item.pos().x() - 1, item.pos().y()) elif event.key() == Qt.Key_Right: item.setPos(item.pos().x() + 1, item.pos().y()) else: return self.updateCurve(self.currentCurve, Qt.red) self.updateCurvePoints(self.currentCurve) self.sigModified.emit(True) elif isinstance(item, QGraphicsAxesItem): if item.axis == "x": if event.key() == Qt.Key_Left: item.moveBy(-1, 0) elif event.key() == Qt.Key_Right: item.moveBy(1, 0) else: return self.sigModified.emit(True) self.calGridCoord() self.updateGrid() self.axesxSelectModel.clearSelection() self.axesySelectModel.clearSelection() for i in range(self.axesxModel.rowCount()): if self.axesxModel.item(i, 0).data() is item: parent = QModelIndex() topleftindex = self.axesxModel.index( i, 0, parent) # self.axesxModel.item(i,0).index() rightbottomindex = self.axesxModel.index( i, 1, parent) self.axesxSelectModel.select( QItemSelection(topleftindex, rightbottomindex), QItemSelectionModel.Select) self.axesxModel.item(i, 0).setText("x:{}".format( item.pos().x())) break elif item.axis == "y": if event.key() == Qt.Key_Up: item.setPos(0, item.pos().y() - 1) elif event.key() == Qt.Key_Down: item.setPos(0, item.pos().y() + 1) else: return self.sigModified.emit(True) self.calGridCoord() self.updateGrid() self.axesxSelectModel.clearSelection() self.axesySelectModel.clearSelection() for i in range(self.axesyModel.rowCount()): if self.axesyModel.item(i, 0).data() is item: topleftindex = self.axesyModel.index( i, 0) # self.axesxModel.item(i,0).index() rightbottomindex = self.axesyModel.index(i, 1) self.axesySelectModel.select( QItemSelection(topleftindex, rightbottomindex), QItemSelectionModel.Select) self.axesyModel.item(i, 0).setText("y:{}".format( item.pos().y())) break def mouseReleaseEvent(self, event): super().mouseReleaseEvent(event) ptscene = self.mapToScene(event.pos()) # item = self.scene.itemAt(ptscene, self.transform()) clicked = True if event.pos() == self.__pressPt else False if self.mode is OpMode.select: pass # super().mousePressEvent(event) elif self.mode is OpMode.axesx and clicked: self.axesxSelectModel.clear() self.axesySelectModel.clear() for axisitem in self.axesxObjs: if abs(ptscene.x() - axisitem.pos().x()) < 5: self.scene.clearSelection() axisitem.setSelected(True) return item = QGraphicsAxesItem( 0, self.scene.sceneRect().y(), 0, self.scene.sceneRect().y() + self.scene.sceneRect().height()) item.setPos(ptscene.x(), 0) item.axis = "x" item.setPen(QPen(Qt.red, 1, Qt.DashLine)) self.scene.addItem(item) item.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable) xs = list(self.axesxObjs.values()) if not self.axesxObjs: nextx = 0 elif len(self.axesxObjs) == 1: nextx = xs[0] + 0.1 else: nextx = 2 * xs[-1] - xs[-2] x, okPressed = QInputDialog.getDouble( self, self.tr("set x coordiniate"), self.tr("set the x coord for axis"), nextx, decimals=3) if okPressed and x not in self.axesxObjs.values(): self.axesxObjs[item] = x labelItem = QStandardItem("x:{}".format(item.pos().x())) labelItem.setData(item) self.axesxModel.appendRow([labelItem, QStandardItem(str(x))]) self.calGridCoord("x") self.updateGrid() # item.setSelected(True) for i in range(self.axesxModel.rowCount()): if self.axesxModel.item(i, 0).data() is item: topleftindex = self.axesxModel.index( i, 0) # self.axesxModel.item(i,0).index() rightbottomindex = self.axesxModel.index(i, 1) self.axesxSelectModel.select( QItemSelection(topleftindex, rightbottomindex), QItemSelectionModel.Select) break self.sigModified.emit(True) self.scene.clearSelection() else: self.scene.removeItem(item) elif self.mode is OpMode.axesy and clicked: self.axesxSelectModel.clear() self.axesySelectModel.clear() for axisitem in self.axesyObjs: if abs(ptscene.y() - axisitem.pos().y()) < 5: self.scene.clearSelection() axisitem.setSelected(True) return item = QGraphicsAxesItem( self.scene.sceneRect().x(), 0, self.scene.sceneRect().x() + self.scene.sceneRect().width(), 0) item.setPos(0, ptscene.y()) item.axis = "y" item.setPen(QPen(Qt.red, 1, Qt.DashLine)) self.scene.addItem(item) item.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable) ys = list(self.axesyObjs.values()) if not self.axesyObjs: nexty = 0 elif len(self.axesyObjs) == 1: nexty = ys[0] + 0.1 else: nexty = 2 * ys[-1] - ys[-2] y, okPressed = QInputDialog.getDouble( self, self.tr("set y coordiniate"), self.tr("set the y coord for axis"), nexty, decimals=3) if okPressed and y not in self.axesyObjs.values(): self.axesyObjs[item] = y labelItem = QStandardItem("y:{}".format(item.pos().y())) labelItem.setData(item) self.axesyModel.appendRow([labelItem, QStandardItem(str(y))]) self.calGridCoord("y") self.updateGrid() # item.setSelected(True) for i in range(self.axesyModel.rowCount()): if self.axesyModel.item(i, 0).data() is item: topleftindex = self.axesyModel.index( i, 0) # self.axesxModel.item(i,0).index() rightbottomindex = self.axesyModel.index(i, 1) self.axesySelectModel.select( QItemSelection(topleftindex, rightbottomindex), QItemSelectionModel.Select) break self.sigModified.emit(True) else: self.scene.removeItem(item) elif self.mode is OpMode.curve and clicked: self.sigMouseMovePoint.emit(event.pos(), ptscene) if len(self.curveObjs) == 0: self.addCurve('curve1') if self.currentCurve not in self.pointObjs: self.pointObjs[self.currentCurve] = [] ptitem = QGraphicsPointItem() ptitem.pointColor = Qt.blue ptitem.linewidth = 1 ptitem.setPos(ptscene) ptitem.parentCurve = self.currentCurve ptitem.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsFocusable | QGraphicsItem.ItemIsMovable) self.scene.addItem(ptitem) i = pointInsertPosition(ptitem, self.pointObjs[self.currentCurve]) self.pointObjs[self.currentCurve].insert(i, ptitem) self.updateCurve(self.currentCurve, Qt.red) self.sigModified.emit(True) ptitem.setSelected(True) # item1=QGraphicsRectItem(rect) #创建矩形---以场景为坐标 # item1.setFlags(QGraphicsItem.ItemIsSelectable|QGraphicsItem.ItemIsFocusable|QGraphicsItem.ItemIsMovable) #给图元设置标志 # QGraphicsItem.ItemIsSelectable---可选择 # QGraphicsItem.ItemIsFocusable---可设置焦点 # QGraphicsItem.ItemIsMovable---可移动 # QGraphicsItem.ItemIsPanel--- # self.scene.addItem(item1) #给场景添加图元 def slotSceneSeletChanged(self): items = self.scene.selectedItems() if len(items) != 1: # ony allow select one item self.scene.clearSelection() return # item = items[0] item = self.scene.focusItem() if not item: items = self.scene.selectedItems() if len(items) != 1: return else: item = items[0] if item: if isinstance(item, QGraphicsPointItem) and item.parentCurve: self.changeCurrentCurve(item.parentCurve) def deleteItem(self, item): """delete point on curve or axis object""" curvechange = None if isinstance(item, QGraphicsPointItem): for curvename, pointitems in self.pointObjs.items(): for ptitem in pointitems: if ptitem is item: curvechange = curvename pointitems.remove(ptitem) self.scene.removeItem(item) self.sigModified.emit(True) break if curvechange: self.updateCurve(curvechange) if isinstance(item, QGraphicsAxesItem): if item.axis == "x": for line in self.axesxObjs: if line is item: for i in range(self.axesxModel.rowCount()): if self.axesxModel.item(i, 0).data( ) is item: # float(self.axesxModel.item(i, 0).text().strip("x:")) == line.pos().x(): self.axesxModel.removeRow(i) break self.axesxObjs.pop(line) self.scene.removeItem(line) self.sigModified.emit(True) break elif item.axis == "y": for line in self.axesyObjs: if line is item: for i in range(self.axesyModel.rowCount()): if float( self.axesyModel.item(i, 0).text().strip( "y:")) == line.pos().y(): self.axesyModel.removeRow(i) break self.axesyObjs.pop(line) self.scene.removeItem(line) self.sigModified.emit(True) break self.calGridCoord() self.updateGrid() def deleteSelectedItem(self): pointitems = self.scene.selectedItems() if len(pointitems) == 1: self.deleteItem(pointitems[0]) def setGraphImage(self, imgfile): if not isinstance(imgfile, str): img = QPixmap(300, 400) img.fill(QColor("#EEE")) self.graphicsPixmapItem.setPixmap(img) self.scene.setSceneRect(0, 0, 300, 400) elif os.path.exists(imgfile): img = QPixmap(imgfile) self.graphicsPixmapItem.setPixmap(img) self.scene.setSceneRect(0, 0, img.width(), img.height()) self.scene.clearSelection() # 【清除选择】 self.sigModified.emit(True) return True else: return False def scaleGraphImage(self, scale=1): if scale and scale > 0: self.proj.imgScale = scale self.graphicsPixmapItem.setScale(scale) if self.graphicsPixmapItem.pixmap().width( ) > 0 and self.graphicsPixmapItem.pixmap().height() > 0: self.scene.setSceneRect( 0, 0, self.graphicsPixmapItem.pixmap().width() * scale, self.graphicsPixmapItem.pixmap().height() * scale) self.scene.clearSelection() # 【清除选择】 self.sigModified.emit(True) def addCurve(self, name=None): if not name: name = nextName(self.currentCurve) while (name in self.curveObjs): name = nextName(name) self.curveObjs[name] = [] self.pointObjs[name] = [] item1 = IconItem() item2 = QStandardItem(name) item3 = QStandardItem() item1.setEditable(False) item3.setCheckable(True) item3.setAutoTristate(False) item3.setEditable(False) item3.setCheckState(Qt.Checked) item1.setTextAlignment(Qt.AlignCenter) item2.setTextAlignment(Qt.AlignCenter) item3.setTextAlignment(Qt.AlignCenter) self.curveModel.appendRow([item1, item2, item3]) self.changeCurrentCurve(name) self.sigModified.emit(True) self.sigNewCurveAdded.emit() def renameCurve(self, newname=None, name=None): if name not in self.curveObjs: name = self.currentCurve if not newname: newname, okPressed = QInputDialog.getText( self, self.tr("change curve name"), self.tr("Curve to be renamed:{}".format(name)), QLineEdit.Normal, name) if okPressed and newname != '': if newname != name: self.curveObjs[newname] = self.curveObjs.pop(name) self.pointObjs[newname] = self.pointObjs.pop(name) for i in range(self.curveModel.rowCount()): item = self.curveModel.item(i, 1) if item.text() == name: item.setText(newname) self.sigModified.emit(True) self.changeCurrentCurve(newname) def delCurve(self, name=None): if name is None: name = self.currentCurve if name not in self.curveObjs: return try: self.curveObjs.pop(name) self.pointObjs.pop(name) for i in range(self.curveModel.rowCount()): item = self.curveModel.item(i, 1) if item.text() == name: self.curveModel.removeRows(i, 1) self.sigModified.emit(True) if len(self.curveObjs) > 0: self.changeCurrentCurve(list(self.curveObjs.keys())[0]) else: self.currentCurve = None except: pass def showCurve(self, curvename, visible=True): for pts in self.pointObjs[curvename]: pts.setVisible(visible) for line in self.curveObjs[curvename]: line.setVisible(visible) def updateCurve(self, name, color=Qt.black): # if name in self.curveObjs: # curveitem = self.curveObjs[name] # else: # curveitem = QGraphicsPathItem() # self.scene.addItem(curveitem) # # path=curveitem.path() # path = QPainterPath() # # pointItems = self.curvePointObjs[name] # if len(pointItems) > 0: # path.moveTo(pointItems[0].pos()) # for pointitem in pointItems[1:]: # path.lineTo(pointitem.pos()) # curveitem.setPath(path) # curveitem.update(curveitem.boundingRect()) # curveitem.prepareGeometryChange() # self.scene.update() # self.viewport().repaint() # self.viewport().update() if not isinstance(name, str): return if name not in self.pointObjs: return lastitems = [] if name in self.curveObjs: lastitems = self.curveObjs[name] if not isinstance(lastitems, list): lastitems = [] if name in self.pointObjs: pointItems = self.pointObjs[name] else: pointItems = [] points = [] for ptitem in pointItems: points.append(ptitem.pos()) self.curveObjs[name] = [] if len(points) > 1: for i in range(1, len(points)): l = QGraphicsLineItem(points[i - 1].x(), points[i - 1].y(), points[i].x(), points[i].y()) l.setPen(color) l.setZValue(10) # l.setFlag(QGraphicsItem.ItemIsSelectable) self.curveObjs[name].append(l) self.scene.addItem(l) for line in lastitems: self.scene.removeItem(line) self.updateCurvePoints(name) def showAxes(self, visible=True): if visible: for line in self.axesxObjs: line.setVisible(True) for line in self.axesyObjs: line.setVisible(True) else: for line in self.axesxObjs: line.setVisible(False) for line in self.axesyObjs: line.setVisible(False) def showGrid(self, visible=True): if visible: for line in self.gridObjs: line.setVisible(True) else: for line in self.gridObjs: line.setVisible(False) def calGridCoord(self, mode="all"): """calc the coord and pixel position of gridx list and gridy list""" if mode in ("x", "all") and len(self.axesxObjs) >= 2: axesxcoord = list(self.axesxObjs.values()) xmin = min(axesxcoord) if self.proj.gridx[0] is None else min( self.proj.gridx[0], min(axesxcoord)) xmax = max(axesxcoord) if self.proj.gridx[1] is None else max( self.proj.gridx[1], max(axesxcoord)) if self.proj.gridx[2] is None: if len(self.axesxObjs) == 2: xstep = (xmax - xmin) / 5 else: axesStep = round(abs(axesxcoord[1] - axesxcoord[0]), 5) for i in range(2, len(axesxcoord)): st = round(abs(axesxcoord[i] - axesxcoord[i - 1]), 5) if axesStep > st: axesStep = st xstep = axesStep else: xstep = self.proj.gridx[2] n = int(round((xmax - xmin) / xstep, 0)) + 1 gridxcoord = list(np.linspace(xmin, xmax, n)) else: gridxcoord = [] if mode in ("y", "all") and len(self.axesyObjs) >= 2: axesycoord = list(self.axesyObjs.values()) ymin = min(axesycoord) if self.proj.gridy[0] is None else min( self.proj.gridy[0], min(axesycoord)) ymax = max(axesycoord) if self.proj.gridy[1] is None else max( self.proj.gridy[1], max(axesycoord)) if self.proj.gridy[2] is None: if len(self.axesyObjs) == 2: ystep = (ymax - ymin) / 5 else: axesStep = round(abs(axesycoord[1] - axesycoord[0]), 5) for i in range(2, len(axesycoord)): st = round(axesycoord[i] - axesycoord[i - 1], 5) if axesStep > st: axesStep = st ystep = axesStep else: ystep = self.proj.gridy[2] n = int(round((ymax - ymin) / ystep, 0)) + 1 gridycoord = list(np.linspace(ymin, ymax, n)) # gridycoord = list(np.arange(ymin, ymax, ystep)) + [ymax] else: gridycoord = [] xpos, ypos = self.coordToPoint(gridxcoord, gridycoord) if mode in ["x", "all"]: self.gridxpos = xpos if mode in ["y", "all"]: self.gridypos = ypos return def updateGrid(self): for line in self.gridObjs: self.scene.removeItem(line) if self.gridxpos and self.gridypos: clr = QColor(self.proj.gridColor) clr.setAlphaF(self.proj.gridOpacity) for x in self.gridxpos: line = QGraphicsLineItem(x, self.gridypos[0], x, self.gridypos[-1]) line.setZValue(5) line.setPen( QPen(clr, self.proj.gridLineWidth, self.proj.gridLineType)) self.gridObjs.append(line) self.scene.addItem(line) for y in self.gridypos: line = QGraphicsLineItem(self.gridxpos[0], y, self.gridxpos[-1], y) line.setZValue(5) line.setPen( QPen(clr, self.proj.gridLineWidth, self.proj.gridLineType)) self.gridObjs.append(line) self.scene.addItem(line) def updateCurvePoints(self, name): extra = len(self.pointObjs[name]) - self.pointsModel.rowCount() if extra > 0: for i in range(extra): item1 = QStandardItem() item2 = QStandardItem() item3 = QStandardItem() item2.setEditable(False) item3.setEditable(False) self.pointsModel.appendRow([item1, item2, item3]) elif extra < 0: j = self.pointsModel.rowCount() i = j + extra self.pointsModel.removeRows(i, -extra) for i in range(self.pointsModel.rowCount()): pt = self.pointObjs[name][i] self.pointsModel.item(i, 0).setText(str(i + 1)) xlist, ylist = self.pointToCoord([pt.x()], [pt.y()]) self.pointsModel.item(i, 1).setText(str(round(xlist[0], 6))) self.pointsModel.item(i, 2).setText(str(round(ylist[0], 6))) def calcCurveCoords(self): """calculate datas for export""" data = {} for curve in self.pointObjs: data[curve] = ([], []) for item in self.pointObjs[curve]: data[curve][0].append(item.x()) data[curve][1].append(item.y()) data[curve] = self.pointToCoord(data[curve][0], data[curve][1]) return data def axisvalid(self): """if there are axes with same coord value, or if there are axes at the save position, return False""" a = len(self.axesxObjs) b = len(set(self.axesxObjs.values())) if a != b: return False a = len(self.axesyObjs) b = len(set(self.axesyObjs.values())) if a != b: return False xs = [] for item in self.axesxObjs: xs.append(item.pos().x()) ys = [] for item in self.axesyObjs: ys.append(item.pos().y()) a = len(xs) b = len(set(xs)) if a != b: return False a = len(ys) b = len(set(ys)) if a != b: return False return True def exportToCSVtext(self): """return text in csv format, like following: curve1 x,1,2,3 y,2,3,4 """ text = "" data = self.calcCurveCoords() for curve in data: text += curve text += "\nx," for x in data[curve][0]: text += str(x) + ',' text += "\ny," for y in data[curve][1]: text += str(y) + ',' text += "\n" return text def changeCurrentCurve(self, name=None): self._lastCurve = self.currentCurve if name is None: name = self.currentCurve if name is None: return for i in range(self.curveModel.rowCount()): if self.curveModel.item(i, 1).text() == name: self.curveModel.item(i, 0).switch(True) else: self.curveModel.item(i, 0).switch(False) self.currentCurve = name self.updateCurve(self._lastCurve) self.updateCurve(self.currentCurve, Qt.red) self.updateCurvePoints(name) def changeCurveVisible(self, item): if item.index().column() == 2: visible = item.checkState() curvename = self.curveModel.item(item.index().row(), 1).text() self.showCurve(curvename, visible) def changePointOrder(self, item): row = item.row() if item.column() != 0: return newindex = item.text() if not newindex.isdigit(): return newindex = int(newindex) if newindex == row + 1: return if newindex > self.pointsModel.rowCount(): newindex = self.pointsModel.rowCount() newindex -= 1 self.pointObjs[self.currentCurve].insert( newindex, self.pointObjs[self.currentCurve].pop(row)) self.updateCurve(self.currentCurve) self.updateCurvePoints(self.currentCurve) # def curvetabChanged(self, item): # i = item.row() # j = item.column() # if j == 0: # if item.checkState() is Qt.Checked: # self.curveModel.item(i,0).setCheckState(Qt.Checked) # return # else: # for k in range(self.curveModel.rowCount()): # if k == i: # #self.curveModel.item(k,0).setCheckState(Qt.Checked) # newcurrent = self.curveModel.item(k,1).text() # print(self.curveModel.item(k,0).checkState()) # else: # self.curveModel.item(k,0).setCheckState(Qt.Unchecked) # if newcurrent and newcurrent != self.currentCurve: # self.changeCurrentCurve(newcurrent) def pointToCoord(self, xlist, ylist): if len(self.axesxObjs) >= 2: gridxs = [] for item in self.axesxObjs: gridxs.append(item.pos().x()) coordx = list(self.axesxObjs.values()) xCoords = interp(gridxs, coordx, xlist) else: xCoords = xlist if len(self.axesyObjs) >= 2: gridys = [] for item in self.axesyObjs: gridys.append(item.pos().y()) coordy = list(self.axesyObjs.values()) yCoords = interp(gridys, coordy, ylist) else: yCoords = ylist return (xCoords, yCoords) def coordToPoint(self, xlist, ylist): if len(self.axesxObjs) >= 2: gridxpos = [] for item in self.axesxObjs: gridxpos.append(item.pos().x()) coordx = list(self.axesxObjs.values()) xposs = interp(coordx, gridxpos, xlist) else: xposs = xlist if len(self.axesyObjs) >= 2: gridypos = [] for item in self.axesyObjs: gridypos.append(item.pos().y()) coordy = list(self.axesyObjs.values()) yposs = interp(coordy, gridypos, ylist) else: yposs = ylist return (xposs, yposs)
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) 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() 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) 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) 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) 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 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)