class RText(QObject): def __init__(self, text, x, y, size, color): self._pos = QPointF(x - 1.8, y + 1.8) super().__init__() self.text = QGraphicsTextItem(text) transform = QTransform.fromScale(0.3, -0.3) self.text.setTransformOriginPoint(self._pos) self.text.setTransform(transform) self.text.setPos(self._pos) # self.text.setRotation(-180) font = QFont("Times", 2) self.text.setFont(font) self._visible = 1 @pyqtProperty(int) def visible(self): return self._visible @visible.setter def visible(self, value): if value > 0: self.text.show() else: self.text.hide() self._visible = value
class SpecBox(QGraphicsRectItem): """ Represents the location of a 2D spectrum in a QGraphicsScene and allows for interaction, via a context menu and keyuboard shortcuts. """ inactive_opacity = 0.21 # the opacity of rectangles that are not in focus def __init__(self, *args): rect = QRectF(*args) super().__init__(rect) self.setOpacity(SpecBox.inactive_opacity) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemIsSelectable) self.setPen(green_pen) self.pinned = False self.label = None self._spec = None self._model = None self._contam_table = None self._info_window = None self._key_bindings = {Qt.Key_Up: self.plot_column_sums, Qt.Key_Down: self.plot_column_sums, Qt.Key_Right: self.plot_row_sums, Qt.Key_Left: self.plot_row_sums, Qt.Key_S: self.show_decontaminated, Qt.Key_D: self.show_decontaminated, Qt.Key_V: self.show_variance, Qt.Key_C: self.show_contamination, Qt.Key_L: self.show_contaminant_table, Qt.Key_T: self.show_contaminant_table, Qt.Key_0: self.show_zeroth_orders, Qt.Key_Z: self.show_zeroth_orders, Qt.Key_R: self.show_residual, Qt.Key_O: self.show_original, Qt.Key_A: self.show_all_layers, Qt.Key_M: self.show_model, Qt.Key_I: self.show_info, Qt.Key_Home: self.open_analysis_tab, Qt.Key_Space: self.open_all_spectra} @property def spec(self): return self._spec @spec.setter def spec(self, spec): self._spec = spec @property def model(self): return self._model @model.setter def model(self, model): self._model = model @property def view(self): return self.scene().views()[0] def hoverEnterEvent(self, event): self.setPen(red_pen) self.setOpacity(1.0) def hoverLeaveEvent(self, event): if not self.pinned: self.setPen(green_pen) self.setOpacity(SpecBox.inactive_opacity) def mousePressEvent(self, event: 'QGraphicsSceneMouseEvent'): keys = event.modifiers() event.button() if keys & Qt.CTRL: self.grabKeyboard() elif event.button() == Qt.LeftButton: self.handle_pinning(event) self.grabKeyboard() elif event.button() == Qt.RightButton: self.handle_right_click(event.screenPos()) def handle_pinning(self, event): if self.pinned: # it's already pinned; unpin it self.unpin() else: # it's not pinned; pin it self.pin(event.scenePos()) def pin(self, label_pos=None): """ Changes the color of the bounding box and places a text label beside the object, containing the object's ID string. Refer to `self.unpin()`. """ if label_pos is not None: self.label = QGraphicsTextItem(f"{self._spec.id}", parent=self) self.label.setTransform(flip_vertical, True) self.label.setPos(label_pos) self.label.setDefaultTextColor(QColor('red')) else: self.label = QGraphicsTextItem(f"{self._spec.id}") self.label.setTransform(flip_vertical, True) self.label.setDefaultTextColor(QColor('red')) self.scene().addItem(self.label) self.label.setPos(self.scenePos()) self.setPen(red_pen) self.setOpacity(1.0) self.pinned = True def unpin(self): """ Reverses the action performed by `self.pin()`. Returns the object to an unpinned state. """ self.setPen(green_pen) if self.label is not None: self.scene().removeItem(self.label) self.label = None self.pinned = False def keyPressEvent(self, event): if event.key() in self._key_bindings: self._key_bindings[event.key()]() def handle_right_click(self, pos): """ Handles right-click (context menu) events. This implementation turned out to be more robust than implementing the virtual function for handling context menu events. """ menu = QMenu() def action(title, slot, caption=None, shortcut=None): act = QAction(title, menu) act.triggered.connect(slot) if caption is not None: act.setStatusTip(caption) if shortcut is not None: act.setShortcut(shortcut) act.setShortcutVisibleInContextMenu(True) return act menu.addSection(f'Object {self.spec.id}') menu.addAction(action('Show table of contaminants', self.show_contaminant_table, shortcut='T')) menu.addAction(action('Show Object Info', self.show_info, 'Show details about this object', 'I')) menu.addAction(action('Open Object tab', self.open_analysis_tab, shortcut=Qt.Key_Home)) menu.addAction(action('Show in all detectors', self.open_all_spectra, 'Show all spectra of object in new tabs', Qt.Key_Space)) menu.addSection('Plots') menu.addAction(action('Plot column sums', self.plot_column_sums, shortcut=Qt.Key_Up)) menu.addAction(action('Plot row sums', self.plot_row_sums, shortcut=Qt.Key_Right)) menu.addAction(action('Show all layers', self.show_all_layers, shortcut='A')) menu.addAction(action('Show decontaminated spectrum', self.show_decontaminated, shortcut='D')) menu.addAction(action('Show original spectrum', self.show_original, shortcut='O')) menu.addAction(action('Show contamination', self.show_contamination, shortcut='C')) menu.addAction(action('Show variance', self.show_variance, shortcut='V')) menu.addAction(action('Show zeroth-order positions', self.show_zeroth_orders, shortcut=Qt.Key_Z|Qt.Key_0)) menu.addAction(action('Show residual', self.show_residual, shortcut='R')) menu.addAction(action('Show model spectrum', self.show_model, shortcut='M')) menu.exec(pos) self.view.ignore_clicks() def plot_column_sums(self): self.plot_pixel_sums(0, 'Column') def plot_row_sums(self): self.plot_pixel_sums(1, 'Row') def plot_pixel_sums(self, axis, label): plot = PlotWindow(f'{self.spec.id} {label} Sum') plt.sca(plot.axis) science = self.spec.science.sum(axis=axis) contamination = self.spec.contamination.sum(axis=axis) plt.plot(contamination, alpha=0.6, label='Contamination') plt.plot(science + contamination, alpha=0.6, label='Original') plt.plot(science, label='Decontaminated') plt.title(f'Object ID: {self.spec.id}') plt.xlabel(f'Pixel {label}') plt.ylabel(f'{label} Sum') plt.legend() plt.draw() plot.show() plot.adjustSize() plt.close() def show_variance(self): title = f'Variance of {self.spec.id}' self.show_spec_layer(title, self.spec.variance) def show_decontaminated(self): title = f'Decontaminated Spectrum of {self.spec.id}' self.show_spec_layer(title, self.spec.science) def show_contamination(self): title = f'Contamination of {self.spec.id}' self.show_spec_layer(title, self.spec.contamination) def show_zeroth_orders(self): title = f'Zeroth-order contamination regions of {self.spec.id}' data = (flag['ZERO'] & self.spec.mask) == flag['ZERO'] self.show_spec_layer(title, data) def show_original(self): title = f'{self.spec.id} before decontamination' self.show_spec_layer(title, self.spec.contamination + self.spec.science) def show_residual(self): if self.model is not None: title = f"residual spectrum of {self.spec.id}" self.show_spec_layer(title, self.spec.science - self.model) def show_model(self): if self.model is not None: title = f"Model of {self.spec.id}" self.show_spec_layer(title, self.model) def show_spec_layer(self, title, data): plot = PlotWindow(title) plt.sca(plot.axis) plt.imshow(data, origin='lower') plt.subplots_adjust(top=0.975, bottom=0.025, left=0.025, right=0.975) plt.draw() plot.setWindowFlag(Qt.WindowStaysOnTopHint, False) plot.show() padding = 32 display = QApplication.desktop() current_screen = display.screenNumber(self.view) geom = display.screenGeometry(current_screen) width = geom.width() - 2 * padding height = geom.height() - 2 * padding plot.setGeometry(geom.left() + padding, geom.top() + padding, width, height) plt.close() def show_all_layers(self): title = f'All Layers of {self.spec.id}' horizontal = self.rect().width() > self.rect().height() subplot_grid_shape = (7, 1) if horizontal else (1, 7) plot = PlotWindow(title, shape=subplot_grid_shape) plt.sca(plot.axis[0]) plt.imshow(self.spec.contamination + self.spec.science, origin='lower') plt.title('Original') plt.draw() plt.sca(plot.axis[1]) plt.imshow(self.spec.contamination, origin='lower') plt.title('Contamination') plt.draw() plt.sca(plot.axis[2]) plt.imshow(self.spec.science, origin='lower') plt.title('Decontaminated') plt.draw() plt.sca(plot.axis[3]) if self.model is not None: plt.imshow(self.model, origin='lower') plt.title('Model') else: plt.title('N/A') plt.draw() plt.sca(plot.axis[4]) if self.model is not None: plt.imshow(self.spec.science - self.model, origin='lower') plt.title('Residual') else: plt.title('N/A') plt.draw() plt.sca(plot.axis[5]) plt.imshow(self.spec.variance, origin='lower') plt.title('Variance') plt.draw() plt.sca(plot.axis[6]) data = (flag['ZERO'] & self.spec.mask) == flag['ZERO'] plt.imshow(data, origin='lower') plt.title('Zeroth Orders') plt.draw() if horizontal: plt.subplots_adjust(top=0.97, bottom=0.025, left=0.025, right=0.975, hspace=0, wspace=0) else: plt.subplots_adjust(top=0.9, bottom=0.03, left=0.025, right=0.975, hspace=0, wspace=0) plt.draw() plot.setWindowFlag(Qt.WindowStaysOnTopHint, False) plot.show() padding = 50 display = QApplication.desktop() current_screen = display.screenNumber(self.view) geom = display.screenGeometry(current_screen) width = geom.width() - 2 * padding height = geom.height() - 2 * padding plot.setGeometry(geom.left() + padding, geom.top() + padding, width, height) plt.close() def show_contaminant_table(self): contents = self.spec.contaminants rows = len(contents) columns = 2 self._contam_table = SpecTable(self.view, rows, columns) self._contam_table.setWindowTitle('Contaminants') self._contam_table.setWindowFlag(Qt.WindowStaysOnTopHint, True) self._contam_table.setWindowFlag(Qt.Window, True) self._contam_table.add_spectra(contents) self._contam_table.show() def open_analysis_tab(self): view_tab = self.view.view_tab inspector = view_tab.inspector inspector.new_object_tab(view_tab.current_dither, view_tab.current_detector, self.spec.id) def open_all_spectra(self): view_tab = self.view.view_tab inspector = view_tab.inspector # make a list of all open detectors (detectors currently being viewed in tabs) open_detectors = [] for tab_index in range(inspector.tabs.count()): tab = inspector.tabs.widget(tab_index) if isinstance(tab, type(view_tab)): open_detectors.append((tab.current_dither, tab.current_detector)) # open new tabs, where necessary for dither in inspector.get_object_dithers(self.spec.id): for detector in inspector.get_object_detectors(dither, self.spec.id): if (dither, detector) not in open_detectors: inspector.new_view_tab(dither, detector) # pin the object in all tabs: for tab_index in range(inspector.tabs.count()): tab = inspector.tabs.widget(tab_index) if isinstance(tab, type(view_tab)): tab.select_spectrum_by_id(self.spec.id) def show_info(self): view_tab = self.view.view_tab inspector = view_tab.inspector if inspector.location_tables is not None: info = inspector.location_tables.get_info(self.spec.id) info_window = ObjectInfoWindow(info, inspector) info_window.show() self._info_window = info_window else: m = QMessageBox(0, 'No Object info available', "Location tables containing the requested information must be loaded before showing info.", QMessageBox.NoButton) m.exec()
class Barometer(QWidget): def __init__(self, parent): super().__init__(parent) self.uas = None svgRenderer = QSvgRenderer('res/barometer.svg') bkgnd = QGraphicsSvgItem() bkgnd.setSharedRenderer(svgRenderer) bkgnd.setCacheMode(QGraphicsItem.NoCache) bkgnd.setElementId('background') scene = QGraphicsScene() scene.addItem(bkgnd) scene.setSceneRect(bkgnd.boundingRect()) self.needle = QGraphicsSvgItem() self.needle.setSharedRenderer(svgRenderer) self.needle.setCacheMode(QGraphicsItem.NoCache) self.needle.setElementId('needle') self.needle.setParentItem(bkgnd) self.needle.setPos( bkgnd.boundingRect().width() / 2 - self.needle.boundingRect().width() / 2, bkgnd.boundingRect().height() / 2 - self.needle.boundingRect().height() / 2) self.needle.setTransformOriginPoint( self.needle.boundingRect().width() / 2, self.needle.boundingRect().height() / 2) # textElement = svgRenderer.boundsOnElement('needle-text') self.digitalBaro = QGraphicsTextItem() self.digitalBaro.setDefaultTextColor(QColor(255, 255, 255)) self.digitalBaro.document().setDefaultTextOption( QTextOption(Qt.AlignCenter)) self.digitalBaro.setFont(QFont('monospace', 13, 60)) self.digitalBaro.setParentItem(bkgnd) txm = QTransform() txm.translate( bkgnd.boundingRect().center().x(), bkgnd.boundingRect().height() - 1.5 * self.digitalBaro.document().size().height()) self.digitalBaro.setTransform(txm, False) view = QGraphicsView(scene) view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) layout = QVBoxLayout() layout.addWidget(view) self.setLayout(layout) self.setBarometer(1000) def setBarometer(self, hbar): deg = ((hbar - 950) * 3 + 210) % 360 self.needle.setRotation(deg) self.digitalBaro.setPlainText('{:.1f}'.format(hbar)) self.digitalBaro.adjustSize() self.digitalBaro.setX(0 - self.digitalBaro.textWidth() / 2) def updateAirPressure(self, sourceUAS, timestamp, absPressure, diffPressure, temperature): unused(sourceUAS, timestamp, diffPressure, temperature) self.setBarometer(absPressure) def setActiveUAS(self, uas): uas.updateAirPressureSignal.connect(self.updateAirPressure) self.uas = uas
class DetectorBox(QGraphicsRectItem): """ Represents a single detector box in the MultiDetectorSelector widget. """ length = 32 disabled_brush = QBrush(QColor(210, 210, 210, 200)) enabled_brush = QBrush(QColor(120, 123, 135, 255)) hovered_brush = QBrush(QColor(156, 186, 252, 255)) selected_brush = QBrush(QColor(80, 110, 206, 255)) invisible_pen = QPen(QColor(255, 255, 255, 0)) red_pen = QPen(QColor('red')) def __init__(self, row, column, enabled, *args): self._detector_id = box_id(row, column) self._enabled = enabled self._selected = False self._rect = QRectF(*args) self._rect.setHeight(self.length) self._rect.setWidth(self.length) super().__init__(self._rect) self.setPen(self.invisible_pen) self.setAcceptHoverEvents(True) if enabled: self.setBrush(self.enabled_brush) else: self.setBrush(self.disabled_brush) # states: enabled, disabled, hovered, selected self._label = QGraphicsTextItem(str(self.detector_id), self) self._label.setTransform(flip_vertical, True) self._label.setFont(QFont('Arial', 14)) self._label.setDefaultTextColor(QColor('white')) self._detector_selector = None @property def detector_id(self): return self._detector_id @property def size(self): return self._rect.size() @property def selected(self): return self._selected def set_parent_selector(self, parent): self._detector_selector = parent def hoverEnterEvent(self, event): if self._enabled: self.setBrush(self.hovered_brush) def hoverLeaveEvent(self, event): if self._enabled: if self._selected: self.setBrush(self.selected_brush) else: self.setBrush(self.enabled_brush) else: self.setBrush(self.disabled_brush) def mousePressEvent(self, event): if self._enabled: if event.button() == Qt.LeftButton: if not self._selected: self._selected = True self.setBrush(self.selected_brush) self.setPen(self.red_pen) else: self._selected = False self.setBrush(self.enabled_brush) self.setPen(self.invisible_pen) self._detector_selector.selection_changed() def place_label(self, rect): # FIXMe: placement should be done differently, so that flipping the text leaves it in the same place. x_center = 0.5 * (rect.left() + rect.right() ) - 0.5 * self._label.boundingRect().width() y_center = 0.5 * (rect.bottom() + rect.top( )) - 0.5 * self._label.boundingRect().height() + self.length self._label.setPos(x_center, y_center)
class LinkItem(QGraphicsObject): """ A Link item in the canvas that connects two :class:`.NodeItem`\s in the canvas. The link curve connects two `Anchor` items (see :func:`setSourceItem` and :func:`setSinkItem`). Once the anchors are set the curve automatically adjusts its end points whenever the anchors move. An optional source/sink text item can be displayed above the curve's central point (:func:`setSourceName`, :func:`setSinkName`) """ #: Z value of the item Z_VALUE = 0 def __init__(self, *args): self.__boundingRect = None QGraphicsObject.__init__(self, *args) self.setFlag(QGraphicsItem.ItemHasNoContents, True) self.setAcceptedMouseButtons(Qt.RightButton | Qt.LeftButton) self.setAcceptHoverEvents(True) self.setZValue(self.Z_VALUE) self.sourceItem = None self.sourceAnchor = None self.sinkItem = None self.sinkAnchor = None self.curveItem = LinkCurveItem(self) self.sourceIndicator = LinkAnchorIndicator(self) self.sinkIndicator = LinkAnchorIndicator(self) self.sourceIndicator.hide() self.sinkIndicator.hide() self.linkTextItem = QGraphicsTextItem(self) self.__sourceName = "" self.__sinkName = "" self.__dynamic = False self.__dynamicEnabled = False self.hover = False self.prepareGeometryChange() self.__boundingRect = None def setSourceItem(self, item, anchor=None): """ Set the source `item` (:class:`.NodeItem`). Use `anchor` (:class:`.AnchorPoint`) as the curve start point (if ``None`` a new output anchor will be created using ``item.newOutputAnchor()``). Setting item to ``None`` and a valid anchor is a valid operation (for instance while mouse dragging one end of the link). """ if item is not None and anchor is not None: if anchor not in item.outputAnchors(): raise ValueError("Anchor must be belong to the item") if self.sourceItem != item: if self.sourceAnchor: # Remove a previous source item and the corresponding anchor self.sourceAnchor.scenePositionChanged.disconnect( self._sourcePosChanged) if self.sourceItem is not None: self.sourceItem.removeOutputAnchor(self.sourceAnchor) self.sourceItem = self.sourceAnchor = None self.sourceItem = item if item is not None and anchor is None: # Create a new output anchor for the item if none is provided. anchor = item.newOutputAnchor() # Update the visibility of the start point indicator. self.sourceIndicator.setVisible(bool(item)) if anchor != self.sourceAnchor: if self.sourceAnchor is not None: self.sourceAnchor.scenePositionChanged.disconnect( self._sourcePosChanged) self.sourceAnchor = anchor if self.sourceAnchor is not None: self.sourceAnchor.scenePositionChanged.connect( self._sourcePosChanged) self.__updateCurve() def setSinkItem(self, item, anchor=None): """ Set the sink `item` (:class:`.NodeItem`). Use `anchor` (:class:`.AnchorPoint`) as the curve end point (if ``None`` a new input anchor will be created using ``item.newInputAnchor()``). Setting item to ``None`` and a valid anchor is a valid operation (for instance while mouse dragging one and of the link). """ if item is not None and anchor is not None: if anchor not in item.inputAnchors(): raise ValueError("Anchor must be belong to the item") if self.sinkItem != item: if self.sinkAnchor: # Remove a previous source item and the corresponding anchor self.sinkAnchor.scenePositionChanged.disconnect( self._sinkPosChanged) if self.sinkItem is not None: self.sinkItem.removeInputAnchor(self.sinkAnchor) self.sinkItem = self.sinkAnchor = None self.sinkItem = item if item is not None and anchor is None: # Create a new input anchor for the item if none is provided. anchor = item.newInputAnchor() # Update the visibility of the end point indicator. self.sinkIndicator.setVisible(bool(item)) if self.sinkAnchor != anchor: if self.sinkAnchor is not None: self.sinkAnchor.scenePositionChanged.disconnect( self._sinkPosChanged) self.sinkAnchor = anchor if self.sinkAnchor is not None: self.sinkAnchor.scenePositionChanged.connect( self._sinkPosChanged) self.__updateCurve() def setFont(self, font): """ Set the font for the channel names text item. """ if font != self.font(): self.linkTextItem.setFont(font) self.__updateText() def font(self): """ Return the font for the channel names text. """ return self.linkTextItem.font() def setChannelNamesVisible(self, visible): """ Set the visibility of the channel name text. """ self.linkTextItem.setVisible(visible) def setSourceName(self, name): """ Set the name of the source (used in channel name text). """ if self.__sourceName != name: self.__sourceName = name self.__updateText() def sourceName(self): """ Return the source name. """ return self.__sourceName def setSinkName(self, name): """ Set the name of the sink (used in channel name text). """ if self.__sinkName != name: self.__sinkName = name self.__updateText() def sinkName(self): """ Return the sink name. """ return self.__sinkName def _sinkPosChanged(self, *arg): self.__updateCurve() def _sourcePosChanged(self, *arg): self.__updateCurve() def __updateCurve(self): self.prepareGeometryChange() self.__boundingRect = None if self.sourceAnchor and self.sinkAnchor: source_pos = self.sourceAnchor.anchorScenePos() sink_pos = self.sinkAnchor.anchorScenePos() source_pos = self.curveItem.mapFromScene(source_pos) sink_pos = self.curveItem.mapFromScene(sink_pos) # Adaptive offset for the curve control points to avoid a # cusp when the two points have the same y coordinate # and are close together delta = source_pos - sink_pos dist = math.sqrt(delta.x()**2 + delta.y()**2) cp_offset = min(dist / 2.0, 60.0) # TODO: make the curve tangent orthogonal to the anchors path. path = QPainterPath() path.moveTo(source_pos) path.cubicTo(source_pos + QPointF(cp_offset, 0), sink_pos - QPointF(cp_offset, 0), sink_pos) self.curveItem.setPath(path) self.sourceIndicator.setPos(source_pos) self.sinkIndicator.setPos(sink_pos) self.__updateText() else: self.setHoverState(False) self.curveItem.setPath(QPainterPath()) def __updateText(self): self.prepareGeometryChange() self.__boundingRect = None if self.__sourceName or self.__sinkName: if self.__sourceName != self.__sinkName: text = u"{0} \u2192 {1}".format(self.__sourceName, self.__sinkName) else: # If the names are the same show only one. # Is this right? If the sink has two input channels of the # same type having the name on the link help elucidate # the scheme. text = self.__sourceName else: text = "" self.linkTextItem.setPlainText(text) path = self.curveItem.path() if not path.isEmpty(): center = path.pointAtPercent(0.5) angle = path.angleAtPercent(0.5) brect = self.linkTextItem.boundingRect() transform = QTransform() transform.translate(center.x(), center.y()) transform.rotate(-angle) # Center and move above the curve path. transform.translate(-brect.width() / 2, -brect.height()) self.linkTextItem.setTransform(transform) def removeLink(self): self.setSinkItem(None) self.setSourceItem(None) self.__updateCurve() def setHoverState(self, state): if self.hover != state: self.prepareGeometryChange() self.__boundingRect = None self.hover = state self.sinkIndicator.setHoverState(state) self.sourceIndicator.setHoverState(state) self.curveItem.setHoverState(state) def hoverEnterEvent(self, event): # Hover enter event happens when the mouse enters any child object # but we only want to show the 'hovered' shadow when the mouse # is over the 'curveItem', so we install self as an event filter # on the LinkCurveItem and listen to its hover events. self.curveItem.installSceneEventFilter(self) return QGraphicsObject.hoverEnterEvent(self, event) def hoverLeaveEvent(self, event): # Remove the event filter to prevent unnecessary work in # scene event filter when not needed self.curveItem.removeSceneEventFilter(self) return QGraphicsObject.hoverLeaveEvent(self, event) def sceneEventFilter(self, obj, event): if obj is self.curveItem: if event.type() == QEvent.GraphicsSceneHoverEnter: self.setHoverState(True) elif event.type() == QEvent.GraphicsSceneHoverLeave: self.setHoverState(False) return QGraphicsObject.sceneEventFilter(self, obj, event) def boundingRect(self): if self.__boundingRect is None: self.__boundingRect = self.childrenBoundingRect() return self.__boundingRect def shape(self): return self.curveItem.shape() def setEnabled(self, enabled): """ Reimplemented from :class:`QGraphicsObject` Set link enabled state. When disabled the link is rendered with a dashed line. """ # This getter/setter pair override a property from the base class. # They should be renamed to e.g. setLinkEnabled/linkEnabled self.curveItem.setLinkEnabled(enabled) def isEnabled(self): return self.curveItem.isLinkEnabled() def setDynamicEnabled(self, enabled): """ Set the link's dynamic enabled state. If the link is `dynamic` it will be rendered in red/green color respectively depending on the state of the dynamic enabled state. """ if self.__dynamicEnabled != enabled: self.__dynamicEnabled = enabled if self.__dynamic: self.__updatePen() def isDynamicEnabled(self): """ Is the link dynamic enabled. """ return self.__dynamicEnabled def setDynamic(self, dynamic): """ Mark the link as dynamic (i.e. it responds to :func:`setDynamicEnabled`). """ if self.__dynamic != dynamic: self.__dynamic = dynamic self.__updatePen() def isDynamic(self): """ Is the link dynamic. """ return self.__dynamic def __updatePen(self): self.prepareGeometryChange() self.__boundingRect = None if self.__dynamic: if self.__dynamicEnabled: color = QColor(0, 150, 0, 150) else: color = QColor(150, 0, 0, 150) normal = QPen(QBrush(color), 2.0) hover = QPen(QBrush(color.darker(120)), 2.1) else: normal = QPen(QBrush(QColor("#9CACB4")), 2.0) hover = QPen(QBrush(QColor("#7D7D7D")), 2.1) self.curveItem.setCurvePenSet(normal, hover)