示例#1
0
    def _wait_reply(self, call_id, call_name, timeout):
        """Wait for the other side reply."""
        if call_id in self._reply_inbox:
            return

        # Create event loop to wait with
        wait_loop = QEventLoop()
        self._sig_got_reply.connect(wait_loop.quit)
        wait_timeout = QTimer()
        wait_timeout.setSingleShot(True)
        wait_timeout.timeout.connect(wait_loop.quit)

        # Wait until the kernel returns the value
        wait_timeout.start(timeout * 1000)
        while len(self._reply_waitlist) > 0:
            if not wait_timeout.isActive():
                self._sig_got_reply.disconnect(wait_loop.quit)
                if call_id in self._reply_waitlist:
                    raise TimeoutError("Timeout while waiting for {}".format(
                        self._reply_waitlist))
                return
            wait_loop.exec_()

        wait_timeout.stop()
        self._sig_got_reply.disconnect(wait_loop.quit)
示例#2
0
class Clock_Node(NodeBase):
    title = 'clock'
    init_inputs = [
        NodeInputBP(dtype=dtypes.Float(default=0.1), label='delay'),
        NodeInputBP(dtype=dtypes.Integer(default=-1, bounds=(-1, 1000)),
                    label='iterations'),
    ]
    init_outputs = [NodeOutputBP('exec')]
    color = '#5d95de'
    main_widget_class = widgets.ClockNode_MainWidget
    main_widget_pos = 'below ports'

    def __init__(self, params):
        super().__init__(params)

        self.actions['start'] = {'method': self.start}
        self.actions['stop'] = {'method': self.stop}

        if self.session.gui:

            from qtpy.QtCore import QTimer
            self.timer = QTimer(self)
            self.timer.timeout.connect(self.timeouted)
            self.iteration = 0

    def timeouted(self):
        self.exec_output(0)
        self.iteration += 1
        if -1 < self.input(1) <= self.iteration:
            self.stop()

    def start(self):
        if self.session.gui:
            self.timer.setInterval(self.input(0) * 1000)
            self.timer.start()
        else:
            import time
            for i in range(self.input(1)):
                self.exec_output(0)
                time.sleep(self.input(0))

    def stop(self):
        self.iteration = 0
        if self.session.gui:
            self.timer.stop()

    def toggle(self):
        # triggered from main widget
        if self.session.gui:
            if self.timer.isActive():
                self.stop()
            else:
                self.start()

    def update_event(self, inp=-1):
        if self.session.gui:
            self.timer.setInterval(self.input(0) * 1000)

    def remove_event(self):
        self.stop()
示例#3
0
    def _wait(self, condition, signal, timeout_msg, timeout):
        """
        Wait until condition() is True by running an event loop.

        signal: qt signal that should interrupt the event loop.
        timeout_msg: Message to display in case of a timeout.
        timeout: time in seconds before a timeout
        """
        if condition():
            return

        # Create event loop to wait with
        wait_loop = QEventLoop()
        signal.connect(wait_loop.quit)
        wait_timeout = QTimer()
        wait_timeout.setSingleShot(True)
        wait_timeout.timeout.connect(wait_loop.quit)

        # Wait until the kernel returns the value
        wait_timeout.start(timeout * 1000)
        while not condition():
            if not wait_timeout.isActive():
                signal.disconnect(wait_loop.quit)
                if not condition():
                    raise TimeoutError(timeout_msg)
                return
            wait_loop.exec_()

        wait_timeout.stop()
        signal.disconnect(wait_loop.quit)
示例#4
0
class SignalThrottler(QObject):
    def __init__(self, interval):
        QObject.__init__(self)
        self.timer = QTimer()
        self.timer.setInterval(interval)
        self.hasPendingEmission = False
        self.timer.timeout.connect(self.maybeEmitTriggered)

    def maybeEmitTriggered(self):
        if self.hasPendingEmission:
            self.emit_triggered()

    @Slot()
    def throttle(self):
        self.hasPendingEmission = True

        if not self.timer.isActive():
            self.timer.start()

    def emit_triggered(self):
        self.hasPendingEmission = False
        self.triggered.emit()

    triggered = Signal()
    timeoutChanged = Signal(int)
    timerTypeChanged = Signal(Qt.TimerType)
示例#5
0
class MatplotlibDataViewer(MatplotlibViewerMixin, DataViewer):

    _state_cls = MatplotlibDataViewerState

    tools = ['mpl:home', 'mpl:pan', 'mpl:zoom']
    subtools = {'save': ['mpl:save']}

    def __init__(self, session, parent=None, wcs=None, state=None):

        super(MatplotlibDataViewer, self).__init__(session, parent=parent, state=state)

        # Use MplWidget to set up a Matplotlib canvas inside the Qt window
        self.mpl_widget = MplWidget()
        self.setCentralWidget(self.mpl_widget)

        # TODO: shouldn't have to do this
        self.central_widget = self.mpl_widget

        self.figure, self.axes = init_mpl(self.mpl_widget.canvas.fig, wcs=wcs)

        MatplotlibViewerMixin.setup_callbacks(self)

        self.central_widget.resize(600, 400)
        self.resize(self.central_widget.size())

        self._monitor_computation = QTimer()
        self._monitor_computation.setInterval(500)
        self._monitor_computation.timeout.connect(self._update_computation)

    def _update_computation(self, message=None):

        # If we get a ComputationStartedMessage and the timer isn't currently
        # active, then we start the timer but we then return straight away.
        # This is to avoid showing the 'Computing' message straight away in the
        # case of reasonably fast operations.
        if isinstance(message, ComputationStartedMessage):
            if not self._monitor_computation.isActive():
                self._monitor_computation.start()
            return

        for layer_artist in self.layers:
            if layer_artist.is_computing:
                self.loading_rectangle.set_visible(True)
                text = self.loading_text.get_text()
                if text.count('.') > 2:
                    text = 'Computing'
                else:
                    text += '.'
                self.loading_text.set_text(text)
                self.loading_text.set_visible(True)
                self.redraw()
                return

        self.loading_rectangle.set_visible(False)
        self.loading_text.set_visible(False)
        self.redraw()

        # If we get here, the computation has stopped so we can stop the timer
        self._monitor_computation.stop()
示例#6
0
class BufferedItemModel(QStandardItemModel):
    __row_appended = Signal()

    def __init__(self, parent=None, limit=None, refresh=20):
        super().__init__(parent)

        self.limit = limit
        self.buffer = list()
        self.timer = QTimer()

        self.timer.setSingleShot(True)
        self.timer.setInterval(refresh)

        @Slot()
        @helpers.connect_slot(self.__row_appended)
        def __on_row_appended():
            if not self.timer.isActive():
                self.timer.start()

        @Slot()
        @helpers.connect_slot(self.timer.timeout)
        def __on_timer_timeout():
            self.__dump_buffer()

    def __dump_buffer(self):
        self.insertRows(self.rowCount(), len(
            self.buffer))  # Append rows for each item in the buffer

        # Set the items for each new row
        for offset, item in enumerate(self.buffer):
            self.setItem(self.rowCount() - len(self.buffer) + offset, 0, item)

        self.buffer.clear()  # Reset the buffer

    def __apply_limit(self):
        if self.rowCount() > self.limit:
            # Remove rows from the beginning, count being the number of rows over the limit
            self.removeRows(0, self.rowCount() - self.limit)

    def insertRows(self, row, count, _=None):
        super().insertRows(row, count)

        if self.limit:
            self.__apply_limit()

    def appendRow(self, item):
        # Append the QStandardItem to the internal list to be popped into the model on the next timeout
        self.buffer.append(item)
        self.__row_appended.emit()
示例#7
0
class ClickableSvgItem(SvgItem):
    def __init__(self,
                 id,
                 renderer,
                 signal: NodeSignal,
                 node_name: str,
                 parent=None):
        super().__init__(id, renderer, parent)
        self.__signal = signal
        self.__node_name = node_name
        self.__timer = QTimer(self)
        self.__timer.setSingleShot(True)
        self.__timer.timeout.connect(self.__on_single_click)
        self.__double_click_interval = QApplication.doubleClickInterval()
        self.setAcceptHoverEvents(True)

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        event.accept()

    def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent):
        event.accept()
        if not self.__timer.isActive():
            self.__timer.start(self.__double_click_interval)
        else:
            self.__timer.stop()
            self.__on_double_click()

    def mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        event.accept()
        self.__timer.stop()
        self.__on_double_click()

    def __on_single_click(self):
        self.__signal.on_click.emit(self.__node_name)

    def __on_double_click(self):
        self.__signal.on_double_click.emit(self.__node_name)

    def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent):
        self.__signal.on_context.emit(self.__node_name, event.screenPos())

    def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
        self.setToolTip(self.__node_name)

    def hoverLeaveEvent(self, event: QGraphicsSceneHoverEvent):
        self.setToolTip('')
示例#8
0
    def _wait(self, condition, signal, timeout_msg, timeout):
        """
        Wait until condition() is True by running an event loop.

        signal: qt signal that should interrupt the event loop.
        timeout_msg: Message to display in case of a timeout.
        timeout: time in seconds before a timeout
        """
        # Exit if condition is fulfilled or the kernel is dead.
        if condition():
            return
        if not self.kernel_client.is_alive():
            raise RuntimeError("Kernel is dead")

        # Create event loop to wait with
        wait_loop = QEventLoop()
        wait_timeout = QTimer()
        wait_timeout.setSingleShot(True)

        # Connect signals to stop kernel loop
        wait_timeout.timeout.connect(wait_loop.quit)
        self.kernel_client.hb_channel.kernel_died.connect(wait_loop.quit)
        signal.connect(wait_loop.quit)

        # Wait until the kernel returns the value
        wait_timeout.start(timeout * 1000)
        while not condition():
            if not wait_timeout.isActive():
                signal.disconnect(wait_loop.quit)
                self.kernel_client.hb_channel.kernel_died.disconnect(
                    wait_loop.quit)
                if condition():
                    return
                if not self.kernel_client.is_alive():
                    raise RuntimeError("Kernel is dead")
                raise TimeoutError(timeout_msg)
            wait_loop.exec_()

        wait_timeout.stop()
        signal.disconnect(wait_loop.quit)
        self.kernel_client.hb_channel.kernel_died.disconnect(
            wait_loop.quit)
示例#9
0
class BasePlot(PlotWidget, PyDMPrimitiveWidget):
    crosshair_position_updated = Signal(float, float)

    def __init__(self, parent=None, background='default', axisItems=None):
        PlotWidget.__init__(self, parent=parent, background=background,
                            axisItems=axisItems)
        PyDMPrimitiveWidget.__init__(self)

        self.plotItem = self.getPlotItem()
        self.plotItem.hideButtons()
        self._auto_range_x = None
        self.setAutoRangeX(True)
        self._auto_range_y = None
        self.setAutoRangeY(True)
        self._min_x = 0.0
        self._max_x = 1.0
        self._min_y = 0.0
        self._max_y = 1.0
        self._show_x_grid = None
        self.setShowXGrid(False)
        self._show_y_grid = None
        self.setShowYGrid(False)

        self._show_right_axis = False

        self.redraw_timer = QTimer(self)
        self.redraw_timer.timeout.connect(self.redrawPlot)

        self._redraw_rate = 30 # Redraw at 30 Hz by default.
        self.maxRedrawRate = self._redraw_rate
        self._curves = []
        self._title = None
        self._show_legend = False
        self._legend = self.addLegend()
        self._legend.hide()

        # Drawing crosshair on the ViewBox
        self.vertical_crosshair_line = None
        self.horizontal_crosshair_line = None
        self.crosshair_movement_proxy = None

    def addCurve(self, plot_item, curve_color=None):
        if curve_color is None:
            curve_color = utilities.colors.default_colors[
                    len(self._curves) % len(utilities.colors.default_colors)]
            plot_item.color_string = curve_color
        self._curves.append(plot_item)
        self.addItem(plot_item)
        self.redraw_timer.start()
        # Connect channels
        for chan in plot_item.channels():
            if chan:
                chan.connect()
        # self._legend.addItem(plot_item, plot_item.curve_name)

    def removeCurve(self, plot_item):
        self.removeItem(plot_item)
        self._curves.remove(plot_item)
        if len(self._curves) < 1:
            self.redraw_timer.stop()
        # Disconnect channels
        for chan in plot_item.channels():
            if chan:
                chan.disconnect()

    def removeCurveWithName(self, name):
        for curve in self._curves:
            if curve.name() == name:
                self.removeCurve(curve)

    def removeCurveAtIndex(self, index):
        curve_to_remove = self._curves[index]
        self.removeCurve(curve_to_remove)

    def setCurveAtIndex(self, index, new_curve):
        old_curve = self._curves[index]
        self._curves[index] = new_curve
        # self._legend.addItem(new_curve, new_curve.name())
        self.removeCurve(old_curve)

    def curveAtIndex(self, index):
        return self._curves[index]

    def curves(self):
        return self._curves

    def clear(self):
        legend_items = [label.text for (sample, label) in self._legend.items]
        for item in legend_items:
            self._legend.removeItem(item)
        self.plotItem.clear()
        self._curves = []

    @Slot()
    def redrawPlot(self):
        pass

    def getShowXGrid(self):
        return self._show_x_grid

    def setShowXGrid(self, value, alpha=None):
        self._show_x_grid = value
        self.showGrid(x=self._show_x_grid, alpha=alpha)

    def resetShowXGrid(self):
        self.setShowXGrid(False)

    showXGrid = Property("bool", getShowXGrid, setShowXGrid, resetShowXGrid)

    def getShowYGrid(self):
        return self._show_y_grid

    def setShowYGrid(self, value, alpha=None):
        self._show_y_grid = value
        self.showGrid(y=self._show_y_grid, alpha=alpha)

    def resetShowYGrid(self):
        self.setShowYGrid(False)

    showYGrid = Property("bool", getShowYGrid, setShowYGrid, resetShowYGrid)

    def getBackgroundColor(self):
        return self.backgroundBrush().color()

    def setBackgroundColor(self, color):
        if self.backgroundBrush().color() != color:
            self.setBackgroundBrush(QBrush(color))

    backgroundColor = Property(QColor, getBackgroundColor, setBackgroundColor)

    def getAxisColor(self):
        return self.getAxis('bottom')._pen.color()

    def setAxisColor(self, color):
        if self.getAxis('bottom')._pen.color() != color:
            self.getAxis('bottom').setPen(color)
            self.getAxis('left').setPen(color)
            self.getAxis('top').setPen(color)
            self.getAxis('right').setPen(color)

    axisColor = Property(QColor, getAxisColor, setAxisColor)

    def getBottomAxisLabel(self):
        return self.getAxis('bottom').labelText

    def getShowRightAxis(self):
        """
        Provide whether the right y-axis is being shown.

        Returns : bool
        -------
        True if the graph shows the right y-axis. False if not.

        """
        return self._show_right_axis

    def setShowRightAxis(self, show):
        """
        Set whether the graph should show the right y-axis.

        Parameters
        ----------
        show : bool
            True for showing the right axis; False is for not showing.
        """
        if show:
            self.showAxis("right")
        else:
            self.hideAxis("right")
        self._show_right_axis = show

    showRightAxis = Property("bool", getShowRightAxis, setShowRightAxis)

    def getPlotTitle(self):
        if self._title is None:
            return ""
        return str(self._title)

    def setPlotTitle(self, value):
        self._title = str(value)
        if len(self._title) < 1:
            self._title = None
        self.setTitle(self._title)

    def resetPlotTitle(self):
        self._title = None
        self.setTitle(self._title)

    title = Property(str, getPlotTitle, setPlotTitle, resetPlotTitle)

    def getShowLegend(self):
        """
        Check if the legend is being shown.

        Returns : bool
        -------
            True if the legend is displayed on the graph; False if not.
        """
        return self._show_legend

    def setShowLegend(self, value):
        """
        Set to display the legend on the graph.

        Parameters
        ----------
        value : bool
            True to display the legend; False is not.
        """
        self._show_legend = value
        if self._show_legend:
            if self._legend is None:
                self._legend = self.addLegend()
            else:
                self._legend.show()
        else:
            if self._legend is not None:
                self._legend.hide()

    def resetShowLegend(self):
        """
        Reset the legend display status to hidden.
        """
        self.setShowLegend(False)

    showLegend = Property(bool, getShowLegend, setShowLegend, resetShowLegend)

    def getAutoRangeX(self):
        return self._auto_range_x

    def setAutoRangeX(self, value):
        self._auto_range_x = value
        if self._auto_range_x:
            self.plotItem.enableAutoRange(ViewBox.XAxis, enable=self._auto_range_x)

    def resetAutoRangeX(self):
        self.setAutoRangeX(True)

    def getAutoRangeY(self):
        return self._auto_range_y

    def setAutoRangeY(self, value):
        self._auto_range_y = value
        if self._auto_range_y:
            self.plotItem.enableAutoRange(ViewBox.YAxis, enable=self._auto_range_y)

    def resetAutoRangeY(self):
        self.setAutoRangeY(True)

    def getMinXRange(self):
        """
        Minimum X-axis value visible on the plot.

        Returns
        -------
        float
        """
        return self.plotItem.viewRange()[0][0]

    def setMinXRange(self, new_min_x_range):
        """
        Set the minimum X-axis value visible on the plot.

        Parameters
        -------
        new_min_x_range : float
        """
        if self._auto_range_x:
            return
        self._min_x = new_min_x_range
        self.plotItem.setXRange(self._min_x, self._max_x, padding=0)

    def getMaxXRange(self):
        """
        Maximum X-axis value visible on the plot.

        Returns
        -------
        float
        """
        return self.plotItem.viewRange()[0][1]

    def setMaxXRange(self, new_max_x_range):
        """
        Set the Maximum X-axis value visible on the plot.

        Parameters
        -------
        new_max_x_range : float
        """
        if self._auto_range_x:
            return

        self._max_x = new_max_x_range
        self.plotItem.setXRange(self._min_x, self._max_x, padding=0)

    def getMinYRange(self):
        """
        Minimum Y-axis value visible on the plot.

        Returns
        -------
        float
        """
        return self.plotItem.viewRange()[1][0]

    def setMinYRange(self, new_min_y_range):
        """
        Set the minimum Y-axis value visible on the plot.

        Parameters
        -------
        new_min_y_range : float
        """
        if self._auto_range_y:
            return

        self._min_y = new_min_y_range
        self.plotItem.setYRange(self._min_y, self._max_y, padding=0)


    def getMaxYRange(self):
        """
        Maximum Y-axis value visible on the plot.

        Returns
        -------
        float
        """
        return self.plotItem.viewRange()[1][1]

    def setMaxYRange(self, new_max_y_range):
        """
        Set the maximum Y-axis value visible on the plot.

        Parameters
        -------
        new_max_y_range : float
        """
        if self._auto_range_y:
            return

        self._max_y = new_max_y_range
        self.plotItem.setYRange(self._min_y, self._max_y, padding=0)

    @Property(bool)
    def mouseEnabledX(self):
        """
        Whether or not mouse interactions are enabled for the X-axis.

        Returns
        -------
        bool
        """
        return self.plotItem.getViewBox().state['mouseEnabled'][0]

    @mouseEnabledX.setter
    def mouseEnabledX(self, x_enabled):
        """
        Whether or not mouse interactions are enabled for the X-axis.

        Parameters
        -------
        x_enabled : bool
        """
        self.plotItem.setMouseEnabled(x=x_enabled)

    @Property(bool)
    def mouseEnabledY(self):
        """
        Whether or not mouse interactions are enabled for the Y-axis.

        Returns
        -------
        bool
        """
        return self.plotItem.getViewBox().state['mouseEnabled'][1]

    @mouseEnabledY.setter
    def mouseEnabledY(self, y_enabled):
        """
        Whether or not mouse interactions are enabled for the Y-axis.

        Parameters
        -------
        y_enabled : bool
        """
        self.plotItem.setMouseEnabled(y=y_enabled)

    @Property(int)
    def maxRedrawRate(self):
        """
        The maximum rate (in Hz) at which the plot will be redrawn.
        The plot will not be redrawn if there is not new data to draw.

        Returns
        -------
        int
        """
        return self._redraw_rate

    @maxRedrawRate.setter
    def maxRedrawRate(self, redraw_rate):
        """
        The maximum rate (in Hz) at which the plot will be redrawn.
        The plot will not be redrawn if there is not new data to draw.

        Parameters
        -------
        redraw_rate : int
        """
        self._redraw_rate = redraw_rate
        self.redraw_timer.setInterval(int((1.0/self._redraw_rate)*1000))

    def pausePlotting(self):
        self.redraw_timer.stop() if self.redraw_timer.isActive() else self.redraw_timer.start()
        return self.redraw_timer.isActive()

    def mouseMoved(self, evt):
        """
        A handler for the crosshair feature. Every time the mouse move, the mouse coordinates are updated, and the
        horizontal and vertical hairlines will be redrawn at the new coordinate. If a PyDMDisplay object is available,
        that display will also have the x- and y- values to update on the UI.

        Parameters
        -------
        evt: MouseEvent
            The mouse event type, from which the mouse coordinates are obtained.
        """
        pos = evt[0]
        if self.sceneBoundingRect().contains(pos):
            mouse_point = self.getViewBox().mapSceneToView(pos)
            self.vertical_crosshair_line.setPos(mouse_point.x())
            self.horizontal_crosshair_line.setPos(mouse_point.y())

            self.crosshair_position_updated.emit(mouse_point.x(), mouse_point.y())

    def enableCrosshair(self, is_enabled, starting_x_pos, starting_y_pos,  vertical_angle=90, horizontal_angle=0,
                        vertical_movable=False, horizontal_movable=False):
        """
        Enable the crosshair to be drawn on the ViewBox.

        Parameters
        ----------
        is_enabled : bool
            True is to draw the crosshair, False is to not draw.
        starting_x_pos : float
            The x coordinate where to start the vertical crosshair line.
        starting_y_pos : float
            The y coordinate where to start the horizontal crosshair line.
        vertical_angle : float
            The angle to tilt the vertical crosshair line. Default at 90 degrees.
        horizontal_angle
            The angle to tilt the horizontal crosshair line. Default at 0 degrees.
        vertical_movable : bool
            True if the vertical line can be moved by the user; False is not.
        horizontal_movable
            False if the horizontal line can be moved by the user; False is not.
        """
        if is_enabled:
            self.vertical_crosshair_line = InfiniteLine(pos=starting_x_pos, angle=vertical_angle,
                                                        movable=vertical_movable)
            self.horizontal_crosshair_line = InfiniteLine(pos=starting_y_pos, angle=horizontal_angle,
                                                          movable=horizontal_movable)

            self.plotItem.addItem(self.vertical_crosshair_line)
            self.plotItem.addItem(self.horizontal_crosshair_line)
            self.crosshair_movement_proxy = SignalProxy(self.plotItem.scene().sigMouseMoved, rateLimit=60,
                                                        slot=self.mouseMoved)
        else:
            if self.vertical_crosshair_line:
                self.plotItem.removeItem(self.vertical_crosshair_line)
            if self.horizontal_crosshair_line:
                self.plotItem.removeItem(self.horizontal_crosshair_line)
            if self.crosshair_movement_proxy:
                self.crosshair_movement_proxy.disconnect()
示例#10
0
class QWaitingSpinner(QWidget):
    def __init__(self,
                 parent,
                 centerOnParent=True,
                 disableParentWhenSpinning=False,
                 modality=Qt.NonModal):
        # super().__init__(parent)
        QWidget.__init__(self, parent)

        self._centerOnParent = centerOnParent
        self._disableParentWhenSpinning = disableParentWhenSpinning

        # WAS IN initialize()
        self._color = QColor(Qt.black)
        self._roundness = 100.0
        self._minimumTrailOpacity = 3.14159265358979323846
        self._trailFadePercentage = 80.0
        self._trailSizeDecreasing = False
        self._revolutionsPerSecond = 1.57079632679489661923
        self._numberOfLines = 20
        self._lineLength = 10
        self._lineWidth = 2
        self._innerRadius = 10
        self._currentCounter = 0
        self._isSpinning = False

        self._timer = QTimer(self)
        self._timer.timeout.connect(self.rotate)
        self.updateSize()
        self.updateTimer()
        self.hide()
        # END initialize()

        self.setWindowModality(modality)
        self.setAttribute(Qt.WA_TranslucentBackground)

    def paintEvent(self, QPaintEvent):
        self.updatePosition()
        painter = QPainter(self)
        painter.fillRect(self.rect(), Qt.transparent)
        painter.setRenderHint(QPainter.Antialiasing, True)

        if self._currentCounter >= self._numberOfLines:
            self._currentCounter = 0

        painter.setPen(Qt.NoPen)
        for i in range(0, self._numberOfLines):
            painter.save()
            painter.translate(self._innerRadius + self._lineLength,
                              self._innerRadius + self._lineLength)
            rotateAngle = float(360 * i) / float(self._numberOfLines)
            painter.rotate(rotateAngle)
            painter.translate(self._innerRadius, 0)
            distance = self.lineCountDistanceFromPrimary(
                i, self._currentCounter, self._numberOfLines)
            color = self.currentLineColor(distance, self._numberOfLines,
                                          self._trailFadePercentage,
                                          self._minimumTrailOpacity,
                                          self._color)

            # Compute the scaling factor to apply to the size and thickness
            # of the lines in the trail.
            if self._trailSizeDecreasing:
                sf = (self._numberOfLines - distance) / self._numberOfLines
            else:
                sf = 1

            painter.setBrush(color)
            rect = QRect(0, round(-self._lineWidth / 2),
                         round(sf * self._lineLength),
                         round(sf * self._lineWidth))
            painter.drawRoundedRect(rect, self._roundness, self._roundness,
                                    Qt.RelativeSize)
            painter.restore()

    def start(self):
        self.updatePosition()
        self._isSpinning = True
        self.show()

        if self.parentWidget and self._disableParentWhenSpinning:
            self.parentWidget().setEnabled(False)

        if not self._timer.isActive():
            self._timer.start()
            self._currentCounter = 0

    def stop(self):
        self._isSpinning = False
        self.hide()

        if self.parentWidget() and self._disableParentWhenSpinning:
            self.parentWidget().setEnabled(True)

        if self._timer.isActive():
            self._timer.stop()
            self._currentCounter = 0

    def setNumberOfLines(self, lines):
        self._numberOfLines = lines
        self._currentCounter = 0
        self.updateTimer()

    def setLineLength(self, length):
        self._lineLength = length
        self.updateSize()

    def setLineWidth(self, width):
        self._lineWidth = width
        self.updateSize()

    def setInnerRadius(self, radius):
        self._innerRadius = radius
        self.updateSize()

    def color(self):
        return self._color

    def roundness(self):
        return self._roundness

    def minimumTrailOpacity(self):
        return self._minimumTrailOpacity

    def trailFadePercentage(self):
        return self._trailFadePercentage

    def revolutionsPersSecond(self):
        return self._revolutionsPerSecond

    def numberOfLines(self):
        return self._numberOfLines

    def lineLength(self):
        return self._lineLength

    def isTrailSizeDecreasing(self):
        """
        Return whether the length and thickness of the trailing lines
        are decreasing.
        """
        return self._trailSizeDecreasing

    def lineWidth(self):
        return self._lineWidth

    def innerRadius(self):
        return self._innerRadius

    def isSpinning(self):
        return self._isSpinning

    def setRoundness(self, roundness):
        self._roundness = max(0.0, min(100.0, roundness))

    def setColor(self, color=Qt.black):
        self._color = QColor(color)

    def setRevolutionsPerSecond(self, revolutionsPerSecond):
        self._revolutionsPerSecond = revolutionsPerSecond
        self.updateTimer()

    def setTrailFadePercentage(self, trail):
        self._trailFadePercentage = trail

    def setTrailSizeDecreasing(self, value):
        """
        Set whether the length and thickness of the trailing lines
        are decreasing.
        """
        self._trailSizeDecreasing = value

    def setMinimumTrailOpacity(self, minimumTrailOpacity):
        self._minimumTrailOpacity = minimumTrailOpacity

    def rotate(self):
        self._currentCounter += 1
        if self._currentCounter >= self._numberOfLines:
            self._currentCounter = 0
        self.update()

    def updateSize(self):
        size = int((self._innerRadius + self._lineLength) * 2)
        self.setFixedSize(size, size)

    def updateTimer(self):
        self._timer.setInterval(
            int(1000 / (self._numberOfLines * self._revolutionsPerSecond)))

    def updatePosition(self):
        if self.parentWidget() and self._centerOnParent:
            self.move(
                int(self.parentWidget().width() / 2 - self.width() / 2),
                int(self.parentWidget().height() / 2 - self.height() / 2))

    def lineCountDistanceFromPrimary(self, current, primary, totalNrOfLines):
        distance = primary - current
        if distance < 0:
            distance += totalNrOfLines
        return distance

    def currentLineColor(self, countDistance, totalNrOfLines, trailFadePerc,
                         minOpacity, colorinput):
        color = QColor(colorinput)
        if countDistance == 0:
            return color
        minAlphaF = minOpacity / 100.0
        distanceThreshold = int(
            math.ceil((totalNrOfLines - 1) * trailFadePerc / 100.0))
        if countDistance > distanceThreshold:
            color.setAlphaF(minAlphaF)
        else:
            alphaDiff = color.alphaF() - minAlphaF
            gradient = alphaDiff / float(distanceThreshold + 1)
            resultAlpha = color.alphaF() - gradient * countDistance
            # If alpha is out of bounds, clip it.
            resultAlpha = min(1.0, max(0.0, resultAlpha))
            color.setAlphaF(resultAlpha)
        return color
示例#11
0
class ScrollFlagArea(Panel):
    """Source code editor's scroll flag area"""
    WIDTH = 24 if sys.platform == 'darwin' and is_dark_interface() else 12
    FLAGS_DX = 4
    FLAGS_DY = 2

    def __init__(self, editor):
        Panel.__init__(self, editor)
        self.setAttribute(Qt.WA_OpaquePaintEvent)
        self.scrollable = True
        self.setMouseTracking(True)

        # Define some attributes to be used for unit testing.
        self._unit_testing = False
        self._range_indicator_is_visible = False
        self._alt_key_is_down = False

        # Define permanent Qt colors that are needed for painting the flags
        # and the slider range.
        self._facecolors = {
            'warning': QColor(editor.warning_color),
            'error': QColor(editor.error_color),
            'todo': QColor(editor.todo_color),
            'breakpoint': QColor(editor.breakpoint_color),
            'occurrence': QColor(editor.occurrence_color),
            'found_results': QColor(editor.found_results_color)
        }
        self._edgecolors = {
            key: color.darker(120)
            for key, color in self._facecolors.items()
        }
        self._slider_range_color = QColor(Qt.gray)
        self._slider_range_color.setAlphaF(.85)
        self._slider_range_brush = QColor(Qt.gray)
        self._slider_range_brush.setAlphaF(.5)

        editor.sig_focus_changed.connect(self.update)
        editor.sig_key_pressed.connect(self.keyPressEvent)
        editor.sig_key_released.connect(self.keyReleaseEvent)
        editor.sig_alt_left_mouse_pressed.connect(self.mousePressEvent)
        editor.sig_alt_mouse_moved.connect(self.mouseMoveEvent)
        editor.sig_leave_out.connect(self.update)
        editor.sig_flags_changed.connect(self.delayed_update_flags)
        editor.sig_theme_colors_changed.connect(self.update_flag_colors)

        self._update_list_timer = QTimer(self)
        self._update_list_timer.setSingleShot(True)
        self._update_list_timer.timeout.connect(self.update_flags)
        # Dictionary with flag lists
        self._dict_flag_list = {}

    @property
    def slider(self):
        """This property holds whether the vertical scrollbar is visible."""
        return self.editor.verticalScrollBar().isVisible()

    def sizeHint(self):
        """Override Qt method"""
        return QSize(self.WIDTH, 0)

    def update_flag_colors(self, color_dict):
        """
        Update the permanent Qt colors that are used for painting the flags
        and the slider range with the new colors defined in the given dict.
        """
        for name, color in color_dict.items():
            self._facecolors[name] = QColor(color)
            self._edgecolors[name] = self._facecolors[name].darker(120)

    def delayed_update_flags(self):
        """
        This function is called every time a flag is changed.
        There is no need of updating the flags thousands of time by second,
        as it is quite resources-heavy. This limits the calls to REFRESH_RATE.
        """
        if self._update_list_timer.isActive():
            return

        self._update_list_timer.start(REFRESH_RATE)

    def update_flags(self):
        """
        Update flags list.

        This parses the entire file, which can take a lot of time for
        large files. Save all the flags in lists for painting during
        paint events.
        """
        self._dict_flag_list = {
            'error': [],
            'warning': [],
            'todo': [],
            'breakpoint': [],
        }

        editor = self.editor
        block = editor.document().firstBlock()
        while block.isValid():
            # Parse all lines in the file looking for something to flag.
            data = block.userData()
            if data:
                if data.code_analysis:
                    # Paint the errors and warnings
                    for _, _, severity, _ in data.code_analysis:
                        if severity == DiagnosticSeverity.ERROR:
                            flag_type = 'error'
                            break
                    else:
                        flag_type = 'warning'
                elif data.todo:
                    flag_type = 'todo'
                elif data.breakpoint:
                    flag_type = 'breakpoint'
                else:
                    flag_type = None

                if flag_type is not None:
                    self._dict_flag_list[flag_type].append(block.blockNumber())

            block = block.next()

        self.update()

    def paintEvent(self, event):
        """
        Override Qt method.
        Painting the scroll flag area

        There is two cases:
            - The scroll bar is moving, in which case paint all flags.
            - The scroll bar is not moving, only paint flags corresponding
              to visible lines.
        """
        # The area in which the slider handle of the scrollbar may move.
        groove_rect = self.get_scrollbar_groove_rect()
        # The scrollbar's scale factor ratio between pixel span height and
        # value span height
        scale_factor = groove_rect.height() / self.get_scrollbar_value_height()
        # The vertical offset of the scroll flag area relative to the
        # top of the text editor.
        offset = groove_rect.y()

        # Note that we calculate the pixel metrics required to draw the flags
        # here instead of using the convenience methods of the ScrollFlagArea
        # for performance reason.
        rect_x = ceil(self.FLAGS_DX / 2)
        rect_w = self.WIDTH - self.FLAGS_DX
        rect_h = self.FLAGS_DY

        # Fill the whole painting area
        painter = QPainter(self)
        painter.fillRect(event.rect(), self.editor.sideareas_color)

        editor = self.editor

        # Define compute_flag_ypos to position the flags:
        # Paint flags for the entire document
        last_line = editor.document().lastBlock().firstLineNumber()
        # The 0.5 offset is used to align the flags with the center of
        # their corresponding text edit block before scaling.
        first_y_pos = self.value_to_position(0.5, scale_factor,
                                             offset) - self.FLAGS_DY / 2
        last_y_pos = self.value_to_position(last_line + 0.5, scale_factor,
                                            offset) - self.FLAGS_DY / 2

        def compute_flag_ypos(block):
            line_number = block.firstLineNumber()
            if editor.verticalScrollBar().maximum() == 0:
                geometry = editor.blockBoundingGeometry(block)
                pos = geometry.y() + geometry.height() / 2 + self.FLAGS_DY / 2
            elif last_line != 0:
                frac = line_number / last_line
                pos = first_y_pos + frac * (last_y_pos - first_y_pos)
            else:
                pos = first_y_pos
            return ceil(pos)

        # All the lists of block numbers for flags
        dict_flag_lists = {
            "occurrence": editor.occurrences,
            "found_results": editor.found_results
        }
        dict_flag_lists.update(self._dict_flag_list)

        for flag_type in dict_flag_lists:
            painter.setBrush(self._facecolors[flag_type])
            painter.setPen(self._edgecolors[flag_type])
            for block_number in dict_flag_lists[flag_type]:
                # Find the block
                block = editor.document().findBlockByNumber(block_number)
                if not block.isValid():
                    continue
                # paint if everything else is fine
                rect_y = compute_flag_ypos(block)
                painter.drawRect(rect_x, rect_y, rect_w, rect_h)

        # Paint the slider range
        if not self._unit_testing:
            alt = QApplication.queryKeyboardModifiers() & Qt.AltModifier
        else:
            alt = self._alt_key_is_down

        if self.slider:
            cursor_pos = self.mapFromGlobal(QCursor().pos())
            is_over_self = self.rect().contains(cursor_pos)
            is_over_editor = editor.rect().contains(
                editor.mapFromGlobal(QCursor().pos()))
            # We use QRect.contains instead of QWidget.underMouse method to
            # determined if the cursor is over the editor or the flag scrollbar
            # because the later gives a wrong result when a mouse button
            # is pressed.
            if is_over_self or (alt and is_over_editor):
                painter.setPen(self._slider_range_color)
                painter.setBrush(self._slider_range_brush)
                x, y, width, height = self.make_slider_range(
                    cursor_pos, scale_factor, offset, groove_rect)
                painter.drawRect(x, y, width, height)
                self._range_indicator_is_visible = True
            else:
                self._range_indicator_is_visible = False

    def enterEvent(self, event):
        """Override Qt method"""
        self.update()

    def leaveEvent(self, event):
        """Override Qt method"""
        self.update()

    def mouseMoveEvent(self, event):
        """Override Qt method"""
        self.update()

    def mousePressEvent(self, event):
        """Override Qt method"""
        if self.slider and event.button() == Qt.LeftButton:
            vsb = self.editor.verticalScrollBar()
            value = self.position_to_value(event.pos().y())
            vsb.setValue(int(value - vsb.pageStep() / 2))

    def keyReleaseEvent(self, event):
        """Override Qt method."""
        if event.key() == Qt.Key_Alt:
            self._alt_key_is_down = False
            self.update()

    def keyPressEvent(self, event):
        """Override Qt method"""
        if event.key() == Qt.Key_Alt:
            self._alt_key_is_down = True
            self.update()

    def get_vertical_offset(self):
        """
        Return the vertical offset of the scroll flag area relative to the
        top of the text editor.
        """
        groove_rect = self.get_scrollbar_groove_rect()
        return groove_rect.y()

    def get_slider_min_height(self):
        """
        Return the minimum height of the slider range based on that set for
        the scroll bar's slider.
        """
        return QApplication.instance().style().pixelMetric(
            QStyle.PM_ScrollBarSliderMin)

    def get_scrollbar_groove_rect(self):
        """Return the area in which the slider handle may move."""
        vsb = self.editor.verticalScrollBar()
        style = QApplication.instance().style()
        opt = QStyleOptionSlider()
        vsb.initStyleOption(opt)

        # Get the area in which the slider handle may move.
        groove_rect = style.subControlRect(QStyle.CC_ScrollBar, opt,
                                           QStyle.SC_ScrollBarGroove, self)

        return groove_rect

    def get_scrollbar_position_height(self):
        """Return the pixel span height of the scrollbar area in which
        the slider handle may move"""
        groove_rect = self.get_scrollbar_groove_rect()
        return float(groove_rect.height())

    def get_scrollbar_value_height(self):
        """Return the value span height of the scrollbar"""
        vsb = self.editor.verticalScrollBar()
        return vsb.maximum() - vsb.minimum() + vsb.pageStep()

    def get_scale_factor(self):
        """Return scrollbar's scale factor:
        ratio between pixel span height and value span height"""
        return (self.get_scrollbar_position_height() /
                self.get_scrollbar_value_height())

    def value_to_position(self, y, scale_factor, offset):
        """Convert value to position in pixels"""
        vsb = self.editor.verticalScrollBar()
        return int((y - vsb.minimum()) * scale_factor + offset)

    def position_to_value(self, y):
        """Convert position in pixels to value"""
        vsb = self.editor.verticalScrollBar()
        offset = self.get_vertical_offset()
        return vsb.minimum() + max([0, (y - offset) / self.get_scale_factor()])

    def make_slider_range(self, cursor_pos, scale_factor, offset, groove_rect):
        """
        Return the slider x and y positions and the slider width and height.
        """
        # The slider range indicator position follows the mouse vertical
        # position while its height corresponds to the part of the file that
        # is currently visible on screen.

        vsb = self.editor.verticalScrollBar()
        slider_height = self.value_to_position(vsb.pageStep(), scale_factor,
                                               offset) - offset
        slider_height = max(slider_height, self.get_slider_min_height())

        # Calcul the minimum and maximum y-value to constraint the slider
        # range indicator position to the height span of the scrollbar area
        # where the slider may move.
        min_ypos = offset
        max_ypos = groove_rect.height() + offset - slider_height

        # Determine the bounded y-position of the slider rect.
        slider_y = max(min_ypos,
                       min(max_ypos, ceil(cursor_pos.y() - slider_height / 2)))

        return 1, slider_y, self.WIDTH - 2, slider_height

    def wheelEvent(self, event):
        """Override Qt method"""
        self.editor.wheelEvent(event)

    def set_enabled(self, state):
        """Toggle scroll flag area visibility"""
        self.enabled = state
        self.setVisible(state)
示例#12
0
class MatplotlibDataViewer(MatplotlibViewerMixin, DataViewer):

    _state_cls = MatplotlibDataViewerState

    tools = ['mpl:home', 'mpl:pan', 'mpl:zoom']
    subtools = {'save': ['mpl:save']}

    def __init__(self,
                 session,
                 parent=None,
                 wcs=None,
                 state=None,
                 projection=None):

        super(MatplotlibDataViewer, self).__init__(session,
                                                   parent=parent,
                                                   state=state)

        # Use MplWidget to set up a Matplotlib canvas inside the Qt window
        self.mpl_widget = MplWidget()
        self.setCentralWidget(self.mpl_widget)

        # TODO: shouldn't have to do this
        self.central_widget = self.mpl_widget

        self.figure, self.axes = init_mpl(self.mpl_widget.canvas.fig,
                                          wcs=wcs,
                                          projection=projection)

        MatplotlibViewerMixin.setup_callbacks(self)

        self.central_widget.resize(600, 400)
        self.resize(self.central_widget.size())

        self._monitor_computation = QTimer()
        self._monitor_computation.setInterval(500)
        self._monitor_computation.timeout.connect(self._update_computation)

    def _update_computation(self, message=None):

        # If we get a ComputationStartedMessage and the timer isn't currently
        # active, then we start the timer but we then return straight away.
        # This is to avoid showing the 'Computing' message straight away in the
        # case of reasonably fast operations.
        if isinstance(message, ComputationStartedMessage):
            if not self._monitor_computation.isActive():
                self._monitor_computation.start()
            return

        for layer_artist in self.layers:
            if layer_artist.is_computing:
                self.loading_rectangle.set_visible(True)
                text = self.loading_text.get_text()
                if text.count('.') > 2:
                    text = 'Computing'
                else:
                    text += '.'
                self.loading_text.set_text(text)
                self.loading_text.set_visible(True)
                self.redraw()
                return

        self.loading_rectangle.set_visible(False)
        self.loading_text.set_visible(False)
        self.redraw()

        # If we get here, the computation has stopped so we can stop the timer
        self._monitor_computation.stop()
示例#13
0
class MatplotlibDataViewer(DataViewer):

    _state_cls = MatplotlibDataViewerState

    tools = ['mpl:home', 'mpl:pan', 'mpl:zoom']
    subtools = {'save': ['mpl:save']}

    def __init__(self, session, parent=None, wcs=None, state=None):

        super(MatplotlibDataViewer, self).__init__(session,
                                                   parent=parent,
                                                   state=state)

        # Use MplWidget to set up a Matplotlib canvas inside the Qt window
        self.mpl_widget = MplWidget()
        self.setCentralWidget(self.mpl_widget)

        # TODO: shouldn't have to do this
        self.central_widget = self.mpl_widget

        self.figure, self._axes = init_mpl(self.mpl_widget.canvas.fig, wcs=wcs)

        for spine in self._axes.spines.values():
            spine.set_zorder(ZORDER_MAX)

        self.loading_rectangle = Rectangle((0, 0),
                                           1,
                                           1,
                                           color='0.9',
                                           alpha=0.9,
                                           zorder=ZORDER_MAX - 1,
                                           transform=self.axes.transAxes)
        self.loading_rectangle.set_visible(False)
        self.axes.add_patch(self.loading_rectangle)

        self.loading_text = self.axes.text(
            0.4,
            0.5,
            'Computing',
            color='k',
            zorder=self.loading_rectangle.get_zorder() + 1,
            ha='left',
            va='center',
            transform=self.axes.transAxes)
        self.loading_text.set_visible(False)

        self.state.add_callback('aspect', self.update_aspect)

        self.update_aspect()

        self.state.add_callback('x_min', self.limits_to_mpl)
        self.state.add_callback('x_max', self.limits_to_mpl)
        self.state.add_callback('y_min', self.limits_to_mpl)
        self.state.add_callback('y_max', self.limits_to_mpl)

        self.limits_to_mpl()

        self.state.add_callback('x_log', self.update_x_log, priority=1000)
        self.state.add_callback('y_log', self.update_y_log, priority=1000)

        self.update_x_log()

        self.axes.callbacks.connect('xlim_changed', self.limits_from_mpl)
        self.axes.callbacks.connect('ylim_changed', self.limits_from_mpl)

        self.axes.set_autoscale_on(False)

        self.state.add_callback('x_axislabel', self.update_x_axislabel)
        self.state.add_callback('x_axislabel_weight', self.update_x_axislabel)
        self.state.add_callback('x_axislabel_size', self.update_x_axislabel)

        self.state.add_callback('y_axislabel', self.update_y_axislabel)
        self.state.add_callback('y_axislabel_weight', self.update_y_axislabel)
        self.state.add_callback('y_axislabel_size', self.update_y_axislabel)

        self.state.add_callback('x_ticklabel_size', self.update_x_ticklabel)
        self.state.add_callback('y_ticklabel_size', self.update_y_ticklabel)

        self.update_x_axislabel()
        self.update_y_axislabel()
        self.update_x_ticklabel()
        self.update_y_ticklabel()

        self.central_widget.resize(600, 400)
        self.resize(self.central_widget.size())

        self._monitor_computation = QTimer()
        self._monitor_computation.setInterval(500)
        self._monitor_computation.timeout.connect(self._update_computation)

    def _update_computation(self, message=None):

        # If we get a ComputationStartedMessage and the timer isn't currently
        # active, then we start the timer but we then return straight away.
        # This is to avoid showing the 'Computing' message straight away in the
        # case of reasonably fast operations.
        if isinstance(message, ComputationStartedMessage):
            if not self._monitor_computation.isActive():
                self._monitor_computation.start()
            return

        for layer_artist in self.layers:
            if layer_artist.is_computing:
                self.loading_rectangle.set_visible(True)
                text = self.loading_text.get_text()
                if text.count('.') > 2:
                    text = 'Computing'
                else:
                    text += '.'
                self.loading_text.set_text(text)
                self.loading_text.set_visible(True)
                self.redraw()
                return

        self.loading_rectangle.set_visible(False)
        self.loading_text.set_visible(False)
        self.redraw()

        # If we get here, the computation has stopped so we can stop the timer
        self._monitor_computation.stop()

    def add_data(self, *args, **kwargs):
        return super(MatplotlibDataViewer, self).add_data(*args, **kwargs)

    def add_subset(self, *args, **kwargs):
        return super(MatplotlibDataViewer, self).add_subset(*args, **kwargs)

    def update_x_axislabel(self, *event):
        self.axes.set_xlabel(self.state.x_axislabel,
                             weight=self.state.x_axislabel_weight,
                             size=self.state.x_axislabel_size)
        self.redraw()

    def update_y_axislabel(self, *event):
        self.axes.set_ylabel(self.state.y_axislabel,
                             weight=self.state.y_axislabel_weight,
                             size=self.state.y_axislabel_size)
        self.redraw()

    def update_x_ticklabel(self, *event):
        self.axes.tick_params(axis='x', labelsize=self.state.x_ticklabel_size)
        self.axes.xaxis.get_offset_text().set_fontsize(
            self.state.x_ticklabel_size)
        self.redraw()

    def update_y_ticklabel(self, *event):
        self.axes.tick_params(axis='y', labelsize=self.state.y_ticklabel_size)
        self.axes.yaxis.get_offset_text().set_fontsize(
            self.state.y_ticklabel_size)
        self.redraw()

    def redraw(self):
        self.figure.canvas.draw()

    def update_x_log(self, *args):
        self.axes.set_xscale('log' if self.state.x_log else 'linear')
        self.redraw()

    def update_y_log(self, *args):
        self.axes.set_yscale('log' if self.state.y_log else 'linear')
        self.redraw()

    def update_aspect(self, aspect=None):
        self.axes.set_aspect(self.state.aspect, adjustable='datalim')

    @avoid_circular
    def limits_from_mpl(self, *args):

        with delay_callback(self.state, 'x_min', 'x_max', 'y_min', 'y_max'):

            if isinstance(self.state.x_min, np.datetime64):
                x_min, x_max = [
                    mpl_to_datetime64(x) for x in self.axes.get_xlim()
                ]
            else:
                x_min, x_max = self.axes.get_xlim()

            self.state.x_min, self.state.x_max = x_min, x_max

            if isinstance(self.state.y_min, np.datetime64):
                y_min, y_max = [
                    mpl_to_datetime64(y) for y in self.axes.get_ylim()
                ]
            else:
                y_min, y_max = self.axes.get_ylim()

            self.state.y_min, self.state.y_max = y_min, y_max

    @avoid_circular
    def limits_to_mpl(self, *args):

        if self.state.x_min is not None and self.state.x_max is not None:
            x_min, x_max = self.state.x_min, self.state.x_max
            if self.state.x_log:
                if self.state.x_max <= 0:
                    x_min, x_max = 0.1, 1
                elif self.state.x_min <= 0:
                    x_min = x_max / 10
            self.axes.set_xlim(x_min, x_max)

        if self.state.y_min is not None and self.state.y_max is not None:
            y_min, y_max = self.state.y_min, self.state.y_max
            if self.state.y_log:
                if self.state.y_max <= 0:
                    y_min, y_max = 0.1, 1
                elif self.state.y_min <= 0:
                    y_min = y_max / 10
            self.axes.set_ylim(y_min, y_max)

        if self.state.aspect == 'equal':

            # FIXME: for a reason I don't quite understand, dataLim doesn't
            # get updated immediately here, which means that there are then
            # issues in the first draw of the image (the limits are such that
            # only part of the image is shown). We just set dataLim manually
            # to avoid this issue.
            self.axes.dataLim.intervalx = self.axes.get_xlim()
            self.axes.dataLim.intervaly = self.axes.get_ylim()

            # We then force the aspect to be computed straight away
            self.axes.apply_aspect()

            # And propagate any changes back to the state since we have the
            # @avoid_circular decorator
            with delay_callback(self.state, 'x_min', 'x_max', 'y_min',
                                'y_max'):
                # TODO: fix case with datetime64 here
                self.state.x_min, self.state.x_max = self.axes.get_xlim()
                self.state.y_min, self.state.y_max = self.axes.get_ylim()

        self.axes.figure.canvas.draw()

    # TODO: shouldn't need this!
    @property
    def axes(self):
        return self._axes

    def _update_appearance_from_settings(self, message=None):
        update_appearance_from_settings(self.axes)
        self.redraw()

    def get_layer_artist(self, cls, layer=None, layer_state=None):
        return cls(self.axes, self.state, layer=layer, layer_state=layer_state)

    def apply_roi(self, roi, use_current=False):
        """ This method must be implemented by subclasses """
        raise NotImplementedError

    def _script_header(self):
        state_dict = self.state.as_dict()
        return ['import matplotlib.pyplot as plt'
                ], SCRIPT_HEADER.format(**state_dict)

    def _script_footer(self):
        state_dict = self.state.as_dict()
        state_dict['x_log_str'] = 'log' if self.state.x_log else 'linear'
        state_dict['y_log_str'] = 'log' if self.state.y_log else 'linear'
        return [], SCRIPT_FOOTER.format(**state_dict)
示例#14
0
class  livestream(QWidget):
    i = 0
    def __init__(self,qnd,images = None,annotations_on = True,annotate_coords = None,threshold_switch = False):
        QWidget.__init__(self)
        
        
        self.threshold_switch = threshold_switch
        self.video = images #frames buffer
        self.videobox = Label()
        if annotations_on and annotate_coords is not None:
            self.coords = annotate_coords

            self.videobox.switch = annotations_on
            self.videobox.activecoord = self.coords[0]

        
        
        if self.video is not None:
            self.videobox.activeframe = self.video[0]
            
            self.videobox.maxintens = self.video.shape[0]
            
        else:
            self.videobox.activeframe = np.loadtxt(os.getcwd() + '/defaultimage.txt')
            print(self.videobox.activeframe.shape)
            self.videobox.maxintens = np.max(self.videobox.activeframe)


        self.videobox.setGeometry(QtCore.QRect(70, 80, 310, 310))
        self.videobox.h = 310
        self.videobox.w = 310
        
        self.lyt = QVBoxLayout()
        self.lyt.addWidget(self.videobox,5)
        
        
        self.setLayout(self.lyt)
        
        
        
        self.sl = QSlider(Qt.Horizontal)
        
        self.sl.setMinimum(0.0)
        if self.video is not None:
            self.sl.setMaximum(self.video.shape[0])
            self.sl.valueChanged.connect(self.whenslidechanges)
        self.sl.setTickPosition(QSlider.TicksAbove)
        self.sl.setTracking(True)
        self.sl.setTickInterval(100)
        

        
        self.frame_counter = QDoubleSpinBox()
        if images is not None:
            self.frame = images[0]
            self.frame_counter.valueChanged.connect(self.video_time_update)
        self.frame_counter.setSingleStep(1)
        self.frame_counter.setRange(self.sl.minimum(),self.sl.maximum())
        self.frame_counter.valueChanged.connect(self.sl.setValue)

        
        self.video_time = QDoubleSpinBox()
        self.video_time.setSingleStep(30)
        self.video_time.setRange(self.sl.minimum(),30*self.sl.maximum())
        self.frameratetimer = QTimer()
        self.frameratetimer.setInterval(30)
        if self.video is not None:
            self.frameratetimer.timeout.connect(self.update_display)
        
        
        self.play_button = QPushButton('Play Video')
        self.play_button.clicked.connect(self.frameratetimer.start)
        
        self.stop_button = QPushButton('Stop Video')
        self.stop_button.clicked.connect(self.frameratetimer.stop)

        if self.video is not None:
            self.sl.valueChanged.connect(self.whenslidechanges)
       
        self.lyt.addWidget(self.play_button,0)
        self.lyt.addWidget(self.stop_button,1)
        self.lyt.addWidget(self.sl,2)
        self.lyt.addWidget(self.frame_counter,3)
        self.lyt.addWidget(self.video_time,4)
        
        self.show()
    
    def assign_images(self,images,centres = None):
    
        '''#first disconnect signals from eachother so nothing should change whilst video data is being updated
        self.sl.valueChanged.disconnect(self.video_time_update)
        self.frameratetimer.timeout.disconnect(self.update_display)
        self.frame_counter.valueChanged.disconnect(self.whenslidechanges)
        '''
        
        self.video = images
        self.coords = centres
        self.videobox.activeframe = self.video[0]
        if self.coords is not None:
            self.videobox.activecoord = self.coords[0]

        #readjust slider and ticker values to dimensions of video
        
        self.sl.setMaximum(len(self.video)-1)
        self.frame_counter.setRange(self.sl.minimum(),self.sl.maximum())
        self.video_time.setRange(self.sl.minimum(),30*self.sl.maximum())
        
        
        
        
        #connect slider and timer etc.
    
        self.sl.valueChanged.connect(self.whenslidechanges)
        self.frameratetimer.timeout.connect(self.update_display)
        self.frame_counter.valueChanged.connect(self.video_time_update)
        
        self.videobox.maxintens = np.max(self.video)
        self.videobox.update()
        
        
    def update_display(self):
        
        if self.threshold_switch:
            frame = self.video[livestream.i]
            threshold = threshold_otsu(frame)
            
            mask = np.zeros_like(frame)
            mask[frame > threshold] = 1
            self.videobox.maxintens = 1
            self.videobox.activeframe = mask
        else:
            #if threshold switch is off display usual video, so change active frame source and reset maximum intensity for passing to qimage2ndarray
            self.videobox.activeframe = self.video[livestream.i]
            self.videobox.maxintens = np.max(self.video)
            
        try:
            self.videobox.activecoord = self.coords[livestream.i]

            if not self.videobox.switch:
                
            
                self.videobox.switch = True
                
        except:
            self.videobox.activecoord = None
            self.videobox.switch = False
            
            
        self.videobox.update()
        self.frame_counter.setValue(float(livestream.i))
        
        livestream.i+=1
       
    def whenslidechanges(self):
        
        if self.frameratetimer.isActive():
            self.frameratetimer.stop()
            
            livestream.i = self.sl.value()
        
            self.update_display()
            livestream.i -=1
            
            self.frameratetimer.start()
        else:
            
            livestream.i = self.sl.value()
        
            self.update_display()
            livestream.i -=1
    
    def video_time_update(self):
        self.video_time.setValue(30*self.frame_counter.value())
        
        
    def turn_on_threshold(self,threshold_switch):
        self.threshold_switch = threshold_switch
        self.update_display()
示例#15
0
class QtPoll(QObject):
    """Polls anything once per frame via an event.

    QtPoll was first created for VispyTiledImageLayer. It polls the visual
    when the camera moves. However, we also want visuals to keep loading
    chunks even when the camera stops. We want the visual to finish up
    anything that was in progress. Before it goes fully idle.

    QtPoll will poll those visuals using a timer. If the visual says the
    event was "handled" it means the visual has more work to do. If that
    happens, QtPoll will continue to poll and draw the visual it until the
    visual is done with the in-progress work.

    An analogy is a snow globe. The user moving the camera shakes up the
    snow globe. We need to keep polling/drawing things until all the snow
    settles down. Then everything will stay completely still until the
    camera is moved again, shaking up the globe once more.

    Parameters
    ----------
    parent : QObject
        Parent Qt object.
    camera : Camera
        The viewer's main camera.
    """
    def __init__(self, parent: QObject, camera: Camera):
        super().__init__(parent)

        self.events = EmitterGroup(source=self, auto_connect=True, poll=None)
        camera.events.connect(self._on_camera)

        self.timer = QTimer()
        self.timer.setInterval(POLL_INTERVAL_MS)
        self.timer.timeout.connect(self._on_timer)

    def _on_camera(self, _event) -> None:
        """Called when camera view changes at all."""
        # Poll right away. If the timer is running, it's generally starved
        # out by the mouse button being down. Why? If we end up "double
        # polling" it *should* be harmless. But if we don't poll then
        # everything is frozen. So better to poll.
        self._poll()

        # Start the timer so that we will keep polling even if the camera
        # doesn't move again. Although the mouse movement is starving out
        # the timer right now, we need the timer going so we keep polling
        # even if the mouse stops.
        self.timer.start()

    def _on_timer(self) -> None:
        """Called when the timer is running."""
        # The timer is running which means someone we are polling still has
        # work to do.
        self._poll()

    def _poll(self) -> None:
        """Called on camera move or with the timer."""
        # Poll everyone listening to our even.
        event = self.events.poll()

        # Listeners will "handle" the event if they need more polling. If
        # no one needs polling, then we can stop the timer.
        if not event.handled:
            self.timer.stop()
            return

        # Someone handled the event, so they want to be polled even if
        # the mouse doesn't move. So start the timer if needed.
        if not self.timer.isActive():
            self.timer.start()

    def closeEvent(self, _event: QEvent) -> None:
        """Cleanup and close.

        Parameters
        ----------
        event : QEvent
            The close event.
        """
        self.timer.stop()
        self.deleteLater()
示例#16
0
class QtLayerList(QScrollArea):
    """Widget storing a list of all the layers present in the current window.

    Parameters
    ----------
    layers : napari.components.LayerList
        The layer list to track and display.

    Attributes
    ----------
    centers : list
        List of layer widgets center coordinates.
    layers : napari.components.LayerList
        The layer list to track and display.
    vbox_layout : QVBoxLayout
        The layout instance in which the layouts appear.
    """

    def __init__(self, layers: 'LayerList'):
        super().__init__()

        self.layers = layers
        self.setAttribute(Qt.WA_DeleteOnClose)

        self.setWidgetResizable(True)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scrollWidget = QWidget()
        self.setWidget(scrollWidget)
        self.vbox_layout = QVBoxLayout(scrollWidget)
        self.vbox_layout.addWidget(QtDivider())
        self.vbox_layout.addStretch(1)
        self.vbox_layout.setContentsMargins(0, 0, 0, 0)
        self.vbox_layout.setSpacing(2)
        self.centers = []

        # Create a timer to be used for autoscrolling the layers list up and
        # down when dragging a layer near the end of the displayed area
        self._drag_timer = QTimer()
        self._drag_timer.setSingleShot(False)
        self._drag_timer.setInterval(20)
        self._drag_timer.timeout.connect(self._force_scroll)
        self._scroll_up = True
        self._min_scroll_region = 24
        self.setAcceptDrops(True)
        self.setToolTip(trans._('Layer list'))
        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)

        self.layers.events.inserted.connect(self._add)
        self.layers.events.removed.connect(self._remove)
        self.layers.events.reordered.connect(self._reorder)
        self.layers.selection.events.changed.connect(self._on_selection_change)

        self._drag_start_position = np.zeros(2)
        self._drag_name = None

        self.chunk_receiver = _create_chunk_receiver(self)

    def _on_selection_change(self, event):
        for layer in event.added:
            w = self._find_widget(layer)
            if w:
                w.setSelected(True)
        for layer in event.removed:
            w = self._find_widget(layer)
            if w:
                w.setSelected(False)
        if event.added:
            self._ensure_visible(list(event.added)[-1])

    def _find_widget(self, layer):
        for i in range(self.vbox_layout.count()):
            w = self.vbox_layout.itemAt(i).widget()
            if getattr(w, 'layer', None) == layer:
                return w

    def _add(self, event):
        """Insert widget for layer `event.value` at index `event.index`.

        Parameters
        ----------
        event : napari.utils.event.Event
            The napari event that triggered this method.
        """
        layer = event.value
        total = len(self.layers)
        index = 2 * (total - event.index) - 1
        widget = QtLayerWidget(layer, selected=layer in self.layers.selection)
        self.vbox_layout.insertWidget(index, widget)
        self.vbox_layout.insertWidget(index + 1, QtDivider())

    def _remove(self, event):
        """Remove widget for layer at index `event.index`.

        Parameters
        ----------
        event : napari.utils.event.Event
            The napari event that triggered this method.
        """
        layer_index = event.index
        total = len(self.layers)
        # Find property widget and divider for layer to be removed
        index = 2 * (total - layer_index) + 1
        widget = self.vbox_layout.itemAt(index).widget()
        divider = self.vbox_layout.itemAt(index + 1).widget()
        self.vbox_layout.removeWidget(widget)
        disconnect_events(widget.layer.events, self)
        widget.close()
        self.vbox_layout.removeWidget(divider)
        divider.deleteLater()

    def _reorder(self, event=None):
        """Reorder list of layer widgets.

        Loops through all widgets in list, sequentially removing them
        and inserting them into the correct place in the final list.

        Parameters
        ----------
        event : napari.utils.event.Event, optional
            The napari event that triggered this method.
        """
        total = len(self.layers)

        # Create list of the current property and divider widgets
        widgets = [
            self.vbox_layout.itemAt(i + 1).widget() for i in range(2 * total)
        ]
        # Take every other widget to ignore the dividers and get just the
        # property widgets
        indices = [
            self.layers.index(w.layer)
            for i, w in enumerate(widgets)
            if i % 2 == 0
        ]

        # Move through the layers in order
        for i in range(total):
            # Find index of property widget in list of the current layer
            index = 2 * indices.index(i)
            widget = widgets[index]
            divider = widgets[index + 1]
            # Check if current index does not match new index
            index_current = self.vbox_layout.indexOf(widget)
            index_new = 2 * (total - i) - 1
            if index_current != index_new:
                # Remove that property widget and divider
                self.vbox_layout.removeWidget(widget)
                self.vbox_layout.removeWidget(divider)
                # Insert the property widget and divider into new location
                self.vbox_layout.insertWidget(index_new, widget)
                self.vbox_layout.insertWidget(index_new + 1, divider)

    def _force_scroll(self):
        """Force the scroll bar to automattically scroll either up or down."""
        cur_value = self.verticalScrollBar().value()
        if self._scroll_up:
            new_value = cur_value - self.verticalScrollBar().singleStep() / 4
            if new_value < 0:
                new_value = 0
            self.verticalScrollBar().setValue(new_value)
        else:
            new_value = cur_value + self.verticalScrollBar().singleStep() / 4
            if new_value > self.verticalScrollBar().maximum():
                new_value = self.verticalScrollBar().maximum()
            self.verticalScrollBar().setValue(new_value)

    def _ensure_visible(self, layer):
        """Ensure layer widget for at particular layer is visible.

        Parameters
        ----------
        layer : napari.layers.Layer
            An instance of a napari layer.

        """
        total = len(self.layers)
        layer_index = self.layers.index(layer)
        # Find property widget and divider for layer to be removed
        index = 2 * (total - layer_index) - 1
        widget = self.vbox_layout.itemAt(index).widget()
        self.ensureWidgetVisible(widget)

    def keyPressEvent(self, event):
        """Ignore a key press event.

        Allows the event to pass through a parent widget to its child widget
        without doing anything. If we did not use event.ignore() then the
        parent widget would catch the event and not pass it on to the child.

        Parameters
        ----------
        event : qtpy.QtCore.QEvent
            Event from the Qt context.
        """
        event.ignore()

    def keyReleaseEvent(self, event):
        """Ignore key release event.

        Allows the event to pass through a parent widget to its child widget
        without doing anything. If we did not use event.ignore() then the
        parent widget would catch the event and not pass it on to the child.

        Parameters
        ----------
        event : qtpy.QtCore.QEvent
            Event from the Qt context.
        """
        event.ignore()

    def mousePressEvent(self, event):
        """Register mouse click if it happens on a layer widget.

        Checks if mouse press happens on a layer properties widget or
        a child of such a widget. If not, the press has happened on the
        Layers Widget itself and should be ignored.

        Parameters
        ----------
        event : qtpy.QtCore.QEvent
            Event from the Qt context.
        """
        widget = self.childAt(event.pos())
        layer = (
            getattr(widget, 'layer', None)
            or getattr(widget.parentWidget(), 'layer', None)
            or getattr(widget.parentWidget().parentWidget(), 'layer', None)
        )

        if layer is not None:
            self._drag_start_position = np.array(
                [event.pos().x(), event.pos().y()]
            )
            self._drag_name = layer.name
        else:
            self._drag_name = None

    def mouseReleaseEvent(self, event):
        """Select layer using mouse click.

        Key modifiers:
        Shift - If the Shift button is pressed, select all layers in between
            currently selected one and the clicked one.
        Control - If the Control button is pressed, mouse click will
            toggle selected state of the layer.

        Parameters
        ----------
        event : qtpy.QtCore.QEvent
            Event from the Qt context.
        """
        if self._drag_name is None:
            # Unselect all the layers if not dragging a layer
            self.layers.selection.active = None
            return

        modifiers = event.modifiers()
        clicked_layer = self.layers[self._drag_name]
        if modifiers == Qt.ShiftModifier and self.layers.selection._current:
            # shift-click: select all layers between current and clicked
            clicked = self.layers.index(clicked_layer)
            current = self.layers.index(self.layers.selection._current)
            from_, to_ = sorted([clicked, current])
            _to_select = self.layers[slice(from_, to_ + 1)]  # inclusive range
            self.layers.selection.update(_to_select)
            self.layers.selection._current = clicked_layer
        elif modifiers == Qt.ControlModifier:
            # If control click toggle selected state of clicked layer
            self.layers.selection.toggle(clicked_layer)
        else:
            # If otherwise unselect all and leave clicked one selected
            self.layers.selection.active = clicked_layer

    def mouseMoveEvent(self, event):
        """Drag and drop layer with mouse movement.

        Parameters
        ----------
        event : qtpy.QtCore.QEvent
            Event from the Qt context.
        """
        position = np.array([event.pos().x(), event.pos().y()])
        distance = np.linalg.norm(position - self._drag_start_position)
        if (
            distance < QApplication.startDragDistance()
            or self._drag_name is None
        ):
            return
        mimeData = QMimeData()
        mimeData.setText(self._drag_name)
        drag = QDrag(self)
        drag.setMimeData(mimeData)
        drag.setHotSpot(event.pos() - self.rect().topLeft())
        drag.exec_()
        # Check if dragged layer still exists or was deleted during drag
        names = [layer.name for layer in self.layers]
        dragged_layer_exists = self._drag_name in names
        if self._drag_name is not None and dragged_layer_exists:
            index = self.layers.index(self._drag_name)
            layer = self.layers[index]
            self._ensure_visible(layer)

    def dragLeaveEvent(self, event):
        """Unselects layer dividers.

        Allows the event to pass through a parent widget to its child widget
        without doing anything. If we did not use event.ignore() then the
        parent widget would catch the event and not pass it on to the child.

        Parameters
        ----------
        event : qtpy.QtCore.QEvent
            Event from the Qt context.
        """
        event.ignore()
        self._drag_timer.stop()
        for i in range(0, self.vbox_layout.count(), 2):
            self.vbox_layout.itemAt(i).widget().setSelected(False)

    def dragEnterEvent(self, event):
        """Update divider position before dragging layer widget to new position

        Allows the event to pass through a parent widget to its child widget
        without doing anything. If we did not use event.ignore() then the
        parent widget would catch the event and not pass it on to the child.

        Parameters
        ----------
        event : qtpy.QtCore.QEvent
            Event from the Qt context.
        """
        if event.source() == self:
            event.accept()
            divs = []
            for i in range(0, self.vbox_layout.count(), 2):
                widget = self.vbox_layout.itemAt(i).widget()
                divs.append(widget.y() + widget.frameGeometry().height() / 2)
            self.centers = [
                (divs[i + 1] + divs[i]) / 2 for i in range(len(divs) - 1)
            ]
        else:
            event.ignore()

    def dragMoveEvent(self, event):
        """Highlight appriate divider when dragging layer to new position.

        Sets the appropriate layers list divider to be highlighted when
        dragging a layer to a new position in the layers list.

        Parameters
        ----------
        event : qtpy.QtCore.QEvent
            Event from the Qt context.
        """
        max_height = self.frameGeometry().height()
        if (
            event.pos().y() < self._min_scroll_region
            and not self._drag_timer.isActive()
        ):
            self._scroll_up = True
            self._drag_timer.start()
        elif (
            event.pos().y() > max_height - self._min_scroll_region
            and not self._drag_timer.isActive()
        ):
            self._scroll_up = False
            self._drag_timer.start()
        elif (
            self._drag_timer.isActive()
            and event.pos().y() >= self._min_scroll_region
            and event.pos().y() <= max_height - self._min_scroll_region
        ):
            self._drag_timer.stop()

        # Determine which widget center is the mouse currently closed to
        cord = event.pos().y() + self.verticalScrollBar().value()
        center_list = (i for i, x in enumerate(self.centers) if x > cord)
        divider_index = next(center_list, len(self.centers))
        # Determine the current location of the widget being dragged
        total = self.vbox_layout.count() // 2 - 1
        insert = total - divider_index
        index = self.layers.index(self._drag_name)
        # If the widget being dragged hasn't moved above or below any other
        # widgets then don't highlight any dividers
        selected = not (insert == index) and not (insert - 1 == index)
        # Set the selected state of all the dividers
        for i in range(0, self.vbox_layout.count(), 2):
            if i == 2 * divider_index:
                self.vbox_layout.itemAt(i).widget().setSelected(selected)
            else:
                self.vbox_layout.itemAt(i).widget().setSelected(False)

    def dropEvent(self, event):
        """Drop dragged layer widget into new position in the list of layers.

        Parameters
        ----------
        event : qtpy.QtCore.QEvent
            Event from the Qt context.
        """
        if self._drag_timer.isActive():
            self._drag_timer.stop()

        for i in range(0, self.vbox_layout.count(), 2):
            self.vbox_layout.itemAt(i).widget().setSelected(False)
        cord = event.pos().y() + self.verticalScrollBar().value()
        center_list = (i for i, x in enumerate(self.centers) if x > cord)
        divider_index = next(center_list, len(self.centers))
        total = self.vbox_layout.count() // 2 - 1
        insert = total - divider_index
        index = self.layers.index(self._drag_name)
        if index != insert and index + 1 != insert:
            if insert >= index:
                insert -= 1
            self.layers.move_selected(index, insert)
        event.accept()
示例#17
0
class ProgressView(QWidget):
    """
    :type batch_manager: CalculationManager
    """
    def __init__(self, parent, batch_manager):
        super().__init__(parent)
        self.task_count = 0
        self.calculation_manager = batch_manager
        self.whole_progress = QProgressBar(self)
        self.whole_progress.setMinimum(0)
        self.whole_progress.setMaximum(1)
        self.whole_progress.setFormat("%v of %m")
        self.whole_progress.setTextVisible(True)
        self.part_progress = QProgressBar(self)
        self.part_progress.setMinimum(0)
        self.part_progress.setMaximum(1)
        self.part_progress.setFormat("%v of %m")
        self.whole_label = QLabel("All batch progress:", self)
        self.part_label = QLabel("Single batch progress:", self)
        self.cancel_remove_btn = QPushButton("Remove task")
        self.cancel_remove_btn.setDisabled(True)
        self.logs = ExceptionList(self)
        self.logs.setToolTip("Logs")
        self.task_view = QListView()
        self.task_que = QStandardItemModel(self)
        self.task_view.setModel(self.task_que)
        self.process_num_timer = QTimer()
        self.process_num_timer.setInterval(1000)
        self.process_num_timer.setSingleShot(True)
        self.process_num_timer.timeout.connect(self.change_number_of_workers)
        self.number_of_process = QSpinBox(self)
        self.number_of_process.setRange(1, multiprocessing.cpu_count())
        self.number_of_process.setValue(1)
        self.number_of_process.setToolTip(
            "Number of process used in batch calculation")
        self.number_of_process.valueChanged.connect(
            self.process_num_timer_start)
        self.progress_item_dict = {}
        layout = QGridLayout()
        layout.addWidget(self.whole_label, 0, 0, Qt.AlignRight)
        layout.addWidget(self.whole_progress, 0, 1, 1, 2)
        layout.addWidget(self.part_label, 1, 0, Qt.AlignRight)
        layout.addWidget(self.part_progress, 1, 1, 1, 2)
        lab = QLabel("Number of process:")
        lab.setToolTip("Number of process used in batch calculation")
        layout.addWidget(lab, 2, 0)
        layout.addWidget(self.number_of_process, 2, 1)
        layout.addWidget(self.logs, 3, 0, 2, 3)
        layout.addWidget(self.task_view, 0, 4, 4, 1)
        layout.addWidget(self.cancel_remove_btn, 4, 4, 1, 1)
        layout.setColumnMinimumWidth(2, 10)
        layout.setColumnStretch(2, 1)
        self.setLayout(layout)
        self.preview_timer = QTimer()
        self.preview_timer.setInterval(1000)
        self.preview_timer.timeout.connect(self.update_info)
        self.task_view.selectionModel().currentChanged.connect(
            self.task_selection_change)
        self.cancel_remove_btn.clicked.connect(self.task_cancel_remove)

    def task_selection_change(self, new, old):
        task: CalculationProcessItem = self.task_que.item(
            new.row(), new.column())
        if task is None:
            self.cancel_remove_btn.setDisabled(True)
            return
        self.cancel_remove_btn.setEnabled(True)
        if task.is_finished():
            self.cancel_remove_btn.setText(f"Remove task {task.num}")
        else:
            self.cancel_remove_btn.setText(f"Cancel task {task.num}")

    def task_cancel_remove(self):
        index = self.task_view.selectionModel().currentIndex()
        task: CalculationProcessItem = self.task_que.item(
            index.row(), index.column())
        if task.is_finished():
            self.calculation_manager.remove_calculation(task.calculation)
            self.task_que.takeRow(index.row())
        else:
            self.calculation_manager.cancel_calculation(task.calculation)
        print(task)

    def new_task(self):
        self.whole_progress.setMaximum(
            self.calculation_manager.calculation_size)
        if not self.preview_timer.isActive():
            self.update_info()
            self.preview_timer.start()

    def update_info(self):
        res = self.calculation_manager.get_results()
        for el in res.errors:
            if el[0]:
                QListWidgetItem(el[0], self.logs)
            ExceptionListItem(el[1], self.logs)
            if (state_store.report_errors and parsed_version.is_devrelease
                    and not isinstance(el[1][0], SegmentationLimitException)
                    and isinstance(el[1][1], tuple)):
                with sentry_sdk.push_scope() as scope:
                    scope.set_tag("auto_report", "true")
                    sentry_sdk.capture_event(el[1][1][0])
        self.whole_progress.setValue(res.global_counter)
        working_search = True
        for uuid, progress in res.jobs_status.items():
            calculation = self.calculation_manager.calculation_dict[uuid]
            total = len(calculation.file_list)
            if uuid in self.progress_item_dict:
                item = self.progress_item_dict[uuid]
                item.update_count(progress)
            else:
                item = CalculationProcessItem(calculation, self.task_count,
                                              progress)
                self.task_count += 1
                self.task_que.appendRow(item)
                self.progress_item_dict[uuid] = item

            if working_search and progress != total:
                self.part_progress.setMaximum(total)
                self.part_progress.setValue(progress)
                working_search = False
        if not self.calculation_manager.has_work:
            self.part_progress.setValue(self.part_progress.maximum())
            self.preview_timer.stop()
            logging.info("Progress stop")

    def process_num_timer_start(self):
        self.process_num_timer.start()

    def update_progress(self, total_progress, part_progress):
        self.whole_progress.setValue(total_progress)
        self.part_progress.setValue(part_progress)

    def set_total_size(self, size):
        self.whole_progress.setMaximum(size)

    def set_part_size(self, size):
        self.part_progress.setMaximum(size)

    def change_number_of_workers(self):
        self.calculation_manager.set_number_of_workers(
            self.number_of_process.value())
示例#18
0
class MatplotlibDataViewer(DataViewer):

    _state_cls = MatplotlibDataViewerState

    tools = ['mpl:home', 'mpl:pan', 'mpl:zoom']
    subtools = {'save': ['mpl:save']}

    def __init__(self, session, parent=None, wcs=None, state=None):

        super(MatplotlibDataViewer, self).__init__(session, parent=parent, state=state)

        # Use MplWidget to set up a Matplotlib canvas inside the Qt window
        self.mpl_widget = MplWidget()
        self.setCentralWidget(self.mpl_widget)

        # TODO: shouldn't have to do this
        self.central_widget = self.mpl_widget

        self.figure, self._axes = init_mpl(self.mpl_widget.canvas.fig, wcs=wcs)

        for spine in self._axes.spines.values():
            spine.set_zorder(ZORDER_MAX)

        self.loading_rectangle = Rectangle((0, 0), 1, 1, color='0.9', alpha=0.9,
                                           zorder=ZORDER_MAX - 1, transform=self.axes.transAxes)
        self.loading_rectangle.set_visible(False)
        self.axes.add_patch(self.loading_rectangle)

        self.loading_text = self.axes.text(0.4, 0.5, 'Computing', color='k',
                                           zorder=self.loading_rectangle.get_zorder() + 1,
                                           ha='left', va='center',
                                           transform=self.axes.transAxes)
        self.loading_text.set_visible(False)

        self.state.add_callback('aspect', self.update_aspect)

        self.update_aspect()

        self.state.add_callback('x_min', self.limits_to_mpl)
        self.state.add_callback('x_max', self.limits_to_mpl)
        self.state.add_callback('y_min', self.limits_to_mpl)
        self.state.add_callback('y_max', self.limits_to_mpl)

        self.limits_to_mpl()

        self.state.add_callback('x_log', self.update_x_log, priority=1000)
        self.state.add_callback('y_log', self.update_y_log, priority=1000)

        self.update_x_log()

        self.axes.callbacks.connect('xlim_changed', self.limits_from_mpl)
        self.axes.callbacks.connect('ylim_changed', self.limits_from_mpl)

        self.axes.set_autoscale_on(False)

        self.state.add_callback('x_axislabel', self.update_x_axislabel)
        self.state.add_callback('x_axislabel_weight', self.update_x_axislabel)
        self.state.add_callback('x_axislabel_size', self.update_x_axislabel)

        self.state.add_callback('y_axislabel', self.update_y_axislabel)
        self.state.add_callback('y_axislabel_weight', self.update_y_axislabel)
        self.state.add_callback('y_axislabel_size', self.update_y_axislabel)

        self.state.add_callback('x_ticklabel_size', self.update_x_ticklabel)
        self.state.add_callback('y_ticklabel_size', self.update_y_ticklabel)

        self.update_x_axislabel()
        self.update_y_axislabel()
        self.update_x_ticklabel()
        self.update_y_ticklabel()

        self.central_widget.resize(600, 400)
        self.resize(self.central_widget.size())

        self._monitor_computation = QTimer()
        self._monitor_computation.setInterval(500)
        self._monitor_computation.timeout.connect(self._update_computation)

    def _update_computation(self, message=None):

        # If we get a ComputationStartedMessage and the timer isn't currently
        # active, then we start the timer but we then return straight away.
        # This is to avoid showing the 'Computing' message straight away in the
        # case of reasonably fast operations.
        if isinstance(message, ComputationStartedMessage):
            if not self._monitor_computation.isActive():
                self._monitor_computation.start()
            return

        for layer_artist in self.layers:
            if layer_artist.is_computing:
                self.loading_rectangle.set_visible(True)
                text = self.loading_text.get_text()
                if text.count('.') > 2:
                    text = 'Computing'
                else:
                    text += '.'
                self.loading_text.set_text(text)
                self.loading_text.set_visible(True)
                self.redraw()
                return

        self.loading_rectangle.set_visible(False)
        self.loading_text.set_visible(False)
        self.redraw()

        # If we get here, the computation has stopped so we can stop the timer
        self._monitor_computation.stop()

    def add_data(self, *args, **kwargs):
        return super(MatplotlibDataViewer, self).add_data(*args, **kwargs)

    def add_subset(self, *args, **kwargs):
        return super(MatplotlibDataViewer, self).add_subset(*args, **kwargs)

    def update_x_axislabel(self, *event):
        self.axes.set_xlabel(self.state.x_axislabel,
                             weight=self.state.x_axislabel_weight,
                             size=self.state.x_axislabel_size)
        self.redraw()

    def update_y_axislabel(self, *event):
        self.axes.set_ylabel(self.state.y_axislabel,
                             weight=self.state.y_axislabel_weight,
                             size=self.state.y_axislabel_size)
        self.redraw()

    def update_x_ticklabel(self, *event):
        self.axes.tick_params(axis='x', labelsize=self.state.x_ticklabel_size)
        self.axes.xaxis.get_offset_text().set_fontsize(self.state.x_ticklabel_size)
        self.redraw()

    def update_y_ticklabel(self, *event):
        self.axes.tick_params(axis='y', labelsize=self.state.y_ticklabel_size)
        self.axes.yaxis.get_offset_text().set_fontsize(self.state.y_ticklabel_size)
        self.redraw()

    def redraw(self):
        self.figure.canvas.draw()

    def update_x_log(self, *args):
        self.axes.set_xscale('log' if self.state.x_log else 'linear')
        self.redraw()

    def update_y_log(self, *args):
        self.axes.set_yscale('log' if self.state.y_log else 'linear')
        self.redraw()

    def update_aspect(self, aspect=None):
        self.axes.set_aspect(self.state.aspect, adjustable='datalim')

    @avoid_circular
    def limits_from_mpl(self, *args):

        with delay_callback(self.state, 'x_min', 'x_max', 'y_min', 'y_max'):

            if isinstance(self.state.x_min, np.datetime64):
                x_min, x_max = [mpl_to_datetime64(x) for x in self.axes.get_xlim()]
            else:
                x_min, x_max = self.axes.get_xlim()

            self.state.x_min, self.state.x_max = x_min, x_max

            if isinstance(self.state.y_min, np.datetime64):
                y_min, y_max = [mpl_to_datetime64(y) for y in self.axes.get_ylim()]
            else:
                y_min, y_max = self.axes.get_ylim()

            self.state.y_min, self.state.y_max = y_min, y_max

    @avoid_circular
    def limits_to_mpl(self, *args):

        if self.state.x_min is not None and self.state.x_max is not None:
            x_min, x_max = self.state.x_min, self.state.x_max
            if self.state.x_log:
                if self.state.x_max <= 0:
                    x_min, x_max = 0.1, 1
                elif self.state.x_min <= 0:
                    x_min = x_max / 10
            self.axes.set_xlim(x_min, x_max)

        if self.state.y_min is not None and self.state.y_max is not None:
            y_min, y_max = self.state.y_min, self.state.y_max
            if self.state.y_log:
                if self.state.y_max <= 0:
                    y_min, y_max = 0.1, 1
                elif self.state.y_min <= 0:
                    y_min = y_max / 10
            self.axes.set_ylim(y_min, y_max)

        if self.state.aspect == 'equal':

            # FIXME: for a reason I don't quite understand, dataLim doesn't
            # get updated immediately here, which means that there are then
            # issues in the first draw of the image (the limits are such that
            # only part of the image is shown). We just set dataLim manually
            # to avoid this issue.
            self.axes.dataLim.intervalx = self.axes.get_xlim()
            self.axes.dataLim.intervaly = self.axes.get_ylim()

            # We then force the aspect to be computed straight away
            self.axes.apply_aspect()

            # And propagate any changes back to the state since we have the
            # @avoid_circular decorator
            with delay_callback(self.state, 'x_min', 'x_max', 'y_min', 'y_max'):
                # TODO: fix case with datetime64 here
                self.state.x_min, self.state.x_max = self.axes.get_xlim()
                self.state.y_min, self.state.y_max = self.axes.get_ylim()

        self.axes.figure.canvas.draw()

    # TODO: shouldn't need this!
    @property
    def axes(self):
        return self._axes

    def _update_appearance_from_settings(self, message=None):
        update_appearance_from_settings(self.axes)
        self.redraw()

    def get_layer_artist(self, cls, layer=None, layer_state=None):
        return cls(self.axes, self.state, layer=layer, layer_state=layer_state)

    def apply_roi(self, roi, use_current=False):
        """ This method must be implemented by subclasses """
        raise NotImplementedError

    def _script_header(self):
        state_dict = self.state.as_dict()
        return ['import matplotlib.pyplot as plt'], SCRIPT_HEADER.format(**state_dict)

    def _script_footer(self):
        state_dict = self.state.as_dict()
        state_dict['x_log_str'] = 'log' if self.state.x_log else 'linear'
        state_dict['y_log_str'] = 'log' if self.state.y_log else 'linear'
        return [], SCRIPT_FOOTER.format(**state_dict)
示例#19
0
class InputsWidget(QWidget, Ui_Form):
    """There has following functions:

    + Function of mechanism variables settings.
    + Path recording.
    """
    about_to_resolve = Signal()

    def __init__(self, parent: MainWindowBase) -> None:
        super(InputsWidget, self).__init__(parent)
        self.setupUi(self)

        # parent's function pointer
        self.free_move_button = parent.free_move_button
        self.entities_point = parent.entities_point
        self.entities_link = parent.entities_link
        self.vpoints = parent.vpoint_list
        self.vlinks = parent.vlink_list
        self.main_canvas = parent.main_canvas
        self.solve = parent.solve
        self.reload_canvas = parent.reload_canvas
        self.output_to = parent.output_to
        self.conflict = parent.conflict
        self.dof = parent.dof
        self.right_input = parent.right_input
        self.command_stack = parent.command_stack
        self.set_coords_as_current = parent.set_coords_as_current
        self.get_back_position = parent.get_back_position

        # Angle panel
        self.dial = QRotatableView(self)
        self.dial.setStatusTip("Input widget of rotatable joint.")
        self.dial.setEnabled(False)
        self.dial.value_changed.connect(self.__update_var)
        self.dial_spinbox.valueChanged.connect(self.__set_var)
        self.inputs_dial_layout.addWidget(self.dial)

        # Play button
        self.variable_stop.clicked.connect(self.variable_value_reset)

        # Timer for play button
        self.inputs_play_shaft = QTimer()
        self.inputs_play_shaft.setInterval(10)
        self.inputs_play_shaft.timeout.connect(self.__change_index)

        # Change the point coordinates with current position
        self.update_pos.clicked.connect(self.set_coords_as_current)

        # Inputs record context menu
        self.pop_menu_record_list = QMenu(self)
        self.record_list.customContextMenuRequested.connect(
            self.__record_list_context_menu)
        self.__path_data: Dict[str, _Paths] = {}

    def clear(self) -> None:
        """Clear function to reset widget status."""
        self.__path_data.clear()
        for _ in range(self.record_list.count() - 1):
            self.record_list.takeItem(1)
        self.variable_list.clear()

    def __set_angle_mode(self) -> None:
        """Change to angle input."""
        self.dial.set_minimum(0)
        self.dial.set_maximum(360)
        self.dial_spinbox.setMinimum(0)
        self.dial_spinbox.setMaximum(360)

    def __set_unit_mode(self) -> None:
        """Change to unit input."""
        self.dial.set_minimum(-500)
        self.dial.set_maximum(500)
        self.dial_spinbox.setMinimum(-500)
        self.dial_spinbox.setMaximum(500)

    def path_data(self) -> Dict[str, _Paths]:
        """Return current path data."""
        return self.__path_data

    @Slot(tuple)
    def set_selection(self, selections: Sequence[int]) -> None:
        """Set one selection from canvas."""
        self.joint_list.setCurrentRow(selections[0])

    @Slot()
    def clear_selection(self) -> None:
        """Clear the points selection."""
        self.driver_list.clear()
        self.joint_list.setCurrentRow(-1)

    @Slot(int, name='on_joint_list_currentRowChanged')
    def __update_relate_points(self, _=None) -> None:
        """Change the point row from input widget."""
        self.driver_list.clear()

        item: Optional[QListWidgetItem] = self.joint_list.currentItem()
        if item is None:
            return
        p0 = _variable_int(item.text())
        base_point = self.vpoints[p0]
        type_int = base_point.type
        if type_int == VJoint.R:
            for i, vpoint in enumerate(self.vpoints):
                if i == p0:
                    continue
                if base_point.same_link(vpoint):
                    if base_point.grounded() and vpoint.grounded():
                        continue
                    self.driver_list.addItem(f"[{vpoint.type_str}] Point{i}")
        elif type_int in {VJoint.P, VJoint.RP}:
            self.driver_list.addItem(f"[{base_point.type_str}] Point{p0}")

    @Slot(int, name='on_driver_list_currentRowChanged')
    def __set_add_var_enabled(self, _=None) -> None:
        """Set enable of 'add variable' button."""
        driver = self.driver_list.currentIndex()
        self.variable_add.setEnabled(driver != -1)

    @Slot(name='on_variable_add_clicked')
    def __add_inputs_variable(self,
                              p0: Optional[int] = None,
                              p1: Optional[int] = None) -> None:
        """Add variable with '->' sign."""
        if p0 is None:
            item: Optional[QListWidgetItem] = self.joint_list.currentItem()
            if item is None:
                return
            p0 = _variable_int(item.text())
        if p1 is None:
            item = self.driver_list.currentItem()
            if item is None:
                return
            p1 = _variable_int(item.text())

        # Check DOF
        if self.dof() <= self.input_count():
            QMessageBox.warning(
                self, "Wrong DOF",
                "The number of variable must no more than degrees of freedom.")
            return

        # Check same link
        if not self.vpoints[p0].same_link(self.vpoints[p1]):
            QMessageBox.warning(
                self, "Wrong pair",
                "The base point and driver point should at the same link.")
            return

        # Check repeated pairs
        for p0_, p1_, a in self.input_pairs():
            if {p0, p1} == {p0_, p1_} and self.vpoints[p0].type == VJoint.R:
                QMessageBox.warning(self, "Wrong pair",
                                    "There already have a same pair.")
                return

        if p0 == p1:
            # One joint by offset
            value = self.vpoints[p0].true_offset()
        else:
            # Two joints by angle
            value = self.vpoints[p0].slope_angle(self.vpoints[p1])
        self.command_stack.push(
            AddInput(
                '->'.join((
                    f'Point{p0}',
                    f"Point{p1}",
                    f"{value:.02f}",
                )), self.variable_list))

    def add_inputs_variables(self, variables: Sequence[Tuple[int,
                                                             int]]) -> None:
        """Add from database."""
        for p0, p1 in variables:
            self.__add_inputs_variable(p0, p1)

    @Slot(QListWidgetItem, name='on_variable_list_itemClicked')
    def __dial_ok(self, _=None) -> None:
        """Set the angle of base link and drive link."""
        if self.inputs_play_shaft.isActive():
            return
        row = self.variable_list.currentRow()
        enabled = row > -1
        rotatable = (enabled and not self.free_move_button.isChecked()
                     and self.right_input())
        self.dial.setEnabled(rotatable)
        self.dial_spinbox.setEnabled(rotatable)
        self.oldVar = self.dial.value()
        self.variable_play.setEnabled(rotatable)
        self.variable_speed.setEnabled(rotatable)
        item: Optional[QListWidgetItem] = self.variable_list.currentItem()
        if item is None:
            return
        expr = item.text().split('->')
        p0 = int(expr[0].replace('Point', ''))
        p1 = int(expr[1].replace('Point', ''))
        value = float(expr[2])
        if p0 == p1:
            self.__set_unit_mode()
        else:
            self.__set_angle_mode()
        self.dial.set_value(value if enabled else 0)

    def variable_excluding(self, row: Optional[int] = None) -> None:
        """Remove variable if the point was been deleted. Default: all."""
        one_row: bool = row is not None
        for i, (b, d, a) in enumerate(self.input_pairs()):
            # If this is not origin point any more
            if one_row and row != b:
                continue
            self.command_stack.push(DeleteInput(i, self.variable_list))

    @Slot(name='on_variable_remove_clicked')
    def remove_var(self, row: int = -1) -> None:
        """Remove and reset angle."""
        if row == -1:
            row = self.variable_list.currentRow()
        if not row > -1:
            return
        self.variable_stop.click()
        self.command_stack.push(DeleteInput(row, self.variable_list))
        self.get_back_position()
        self.solve()

    def interval(self) -> float:
        """Return interval value."""
        return self.record_interval.value()

    def input_count(self) -> int:
        """Use to show input variable count."""
        return self.variable_list.count()

    def input_pairs(self) -> Iterator[Tuple[int, int, float]]:
        """Back as point number code."""
        for row in range(self.variable_list.count()):
            var = self.variable_list.item(row).text().split('->')
            p0 = int(var[0].replace('Point', ''))
            p1 = int(var[1].replace('Point', ''))
            angle = float(var[2])
            yield p0, p1, angle

    def variable_reload(self) -> None:
        """Auto check the points and type."""
        self.joint_list.clear()
        for i in range(self.entities_point.rowCount()):
            type_text = self.entities_point.item(i, 2).text()
            self.joint_list.addItem(f"[{type_text}] Point{i}")
        self.variable_value_reset()

    @Slot(float)
    def __set_var(self, value: float) -> None:
        self.dial.set_value(value)

    @Slot(float)
    def __update_var(self, value: float) -> None:
        """Update the value when rotating QDial."""
        item = self.variable_list.currentItem()
        self.dial_spinbox.blockSignals(True)
        self.dial_spinbox.setValue(value)
        self.dial_spinbox.blockSignals(False)
        if item:
            item_text = item.text().split('->')
            item_text[-1] = f"{value:.02f}"
            item.setText('->'.join(item_text))
            self.about_to_resolve.emit()
        if (self.record_start.isChecked()
                and abs(self.oldVar - value) > self.record_interval.value()):
            self.main_canvas.record_path()
            self.oldVar = value

    def variable_value_reset(self) -> None:
        """Reset the value of QDial."""
        if self.inputs_play_shaft.isActive():
            self.variable_play.setChecked(False)
            self.inputs_play_shaft.stop()
        self.get_back_position()
        for i, (p0, p1, a) in enumerate(self.input_pairs()):
            self.variable_list.item(i).setText('->'.join([
                f'Point{p0}',
                f'Point{p1}',
                f"{self.vpoints[p0].slope_angle(self.vpoints[p1]):.02f}",
            ]))
        self.__dial_ok()
        self.solve()

    @Slot(bool, name='on_variable_play_toggled')
    def __play(self, toggled: bool) -> None:
        """Triggered when play button was changed."""
        self.dial.setEnabled(not toggled)
        self.dial_spinbox.setEnabled(not toggled)
        if toggled:
            self.inputs_play_shaft.start()
        else:
            self.inputs_play_shaft.stop()
            if self.update_pos_option.isChecked():
                self.set_coords_as_current()

    @Slot()
    def __change_index(self) -> None:
        """QTimer change index."""
        index = self.dial.value()
        speed = self.variable_speed.value()
        extreme_rebound = (self.conflict.isVisible()
                           and self.extremeRebound.isChecked())
        if extreme_rebound:
            speed = -speed
            self.variable_speed.setValue(speed)
        index += speed * 0.06 * (3 if extreme_rebound else 1)
        self.dial.set_value(index)

    @Slot(bool, name='on_record_start_toggled')
    def __start_record(self, toggled: bool) -> None:
        """Save to file path data."""
        if toggled:
            self.main_canvas.record_start(
                int(self.dial_spinbox.maximum() /
                    self.record_interval.value()))
            return
        path = self.main_canvas.get_record_path()
        name, ok = QInputDialog.getText(self, "Recording completed!",
                                        "Please input name tag:")
        i = 0
        name = name or f"Record_{i}"
        while name in self.__path_data:
            name = f"Record_{i}"
            i += 1
        QMessageBox.information(self, "Record",
                                "The name tag is being used or empty.")
        self.add_path(name, path)

    def add_path(self, name: str, path: _Paths) -> None:
        """Add path function."""
        self.command_stack.push(
            AddPath(self.record_list, name, self.__path_data, path))
        self.record_list.setCurrentRow(self.record_list.count() - 1)

    def load_paths(self, paths: Dict[str, _Paths]) -> None:
        """Add multiple path."""
        for name, path in paths.items():
            self.add_path(name, path)

    @Slot(name='on_record_remove_clicked')
    def __remove_path(self) -> None:
        """Remove path data."""
        row = self.record_list.currentRow()
        if not row > 0:
            return
        self.command_stack.push(
            DeletePath(row, self.record_list, self.__path_data))
        self.record_list.setCurrentRow(self.record_list.count() - 1)
        self.reload_canvas()

    @Slot(QListWidgetItem, name='on_record_list_itemDoubleClicked')
    def __path_dlg(self, item: QListWidgetItem) -> None:
        """View path data."""
        name = item.text().split(":")[0]
        try:
            data = self.__path_data[name]
        except KeyError:
            return

        points_text = ", ".join(f"Point{i}" for i in range(len(data)))
        if QMessageBox.question(self, "Path data",
                                f"This path data including {points_text}.",
                                (QMessageBox.Save | QMessageBox.Close),
                                QMessageBox.Close) != QMessageBox.Save:
            return
        file_name = self.output_to(
            "path data",
            ["Comma-Separated Values (*.csv)", "Text file (*.txt)"])
        if not file_name:
            return
        with open(file_name, 'w', encoding='utf-8', newline='') as stream:
            writer = csv.writer(stream)
            for point in data:
                for coordinate in point:
                    writer.writerow(coordinate)
                writer.writerow(())
        logger.info(f"Output path data: {file_name}")

    @Slot(QPoint)
    def __record_list_context_menu(self, p: QPoint) -> None:
        """Show the context menu.

        Show path [0], [1], ...
        Or copy path coordinates.
        """
        row = self.record_list.currentRow()
        if not row > -1:
            return
        showall_action = self.pop_menu_record_list.addAction("Show all")
        showall_action.index = -1
        copy_action = self.pop_menu_record_list.addAction("Copy as new")
        name = self.record_list.item(row).text().split(':')[0]
        if name in self.__path_data:
            data = self.__path_data[name]
        else:
            # Auto preview path
            data = self.main_canvas.path_preview
        targets = 0
        for text in ("Show", "Copy data from"):
            self.pop_menu_record_list.addSeparator()
            for i, path in enumerate(data):
                if len(set(path)) > 1:
                    action = self.pop_menu_record_list.addAction(
                        f"{text} Point{i}")
                    action.index = i
                    targets += 1
        copy_action.setEnabled(targets > 0)
        action = self.pop_menu_record_list.exec_(
            self.record_list.mapToGlobal(p))
        if action is None:
            self.pop_menu_record_list.clear()
            return
        text = action.text()
        if action == copy_action:
            # Copy path data
            num = 0
            name_copy = f"{name}_{num}"
            while name_copy in self.__path_data:
                name_copy = f"{name}_{num}"
                num += 1
            self.add_path(name_copy, copy(data))
        elif text.startswith("Copy data from"):
            # Copy data to clipboard (csv)
            QApplication.clipboard().setText('\n'.join(
                f"[{x}, {y}]," for x, y in data[action.index]))
        elif text.startswith("Show"):
            # Switch points enabled status
            if action.index == -1:
                self.record_show.setChecked(True)
            self.main_canvas.set_path_show(action.index)
        self.pop_menu_record_list.clear()

    @Slot(bool, name='on_record_show_toggled')
    def __set_path_show(self, toggled: bool) -> None:
        """Show all paths or hide."""
        self.main_canvas.set_path_show(-1 if toggled else -2)

    @Slot(int, name='on_record_list_currentRowChanged')
    def __set_path(self, _=None) -> None:
        """Reload the canvas when switch the path."""
        if not self.record_show.isChecked():
            self.record_show.setChecked(True)
        self.reload_canvas()

    def current_path(self) -> _Paths:
        """Return current path data to main canvas.

        + No path.
        + Show path data.
        + Auto preview.
        """
        row = self.record_list.currentRow()
        if row in {0, -1}:
            return ()
        path_name = self.record_list.item(row).text().split(':')[0]
        return self.__path_data.get(path_name, ())

    @Slot(name='on_variable_up_clicked')
    @Slot(name='on_variable_down_clicked')
    def __set_variable_priority(self) -> None:
        row = self.variable_list.currentRow()
        if not row > -1:
            return
        item = self.variable_list.currentItem()
        self.variable_list.insertItem(
            row + (-1 if self.sender() == self.variable_up else 1),
            self.variable_list.takeItem(row))
        self.variable_list.setCurrentItem(item)
示例#20
0
class TrapViewer(QWidget):
    i = 0

    def __init__(self, qnd, images, trap_positions=None, labels=None):
        QWidget.__init__(self)

        self.video = images  # This is a file object buffer containing the images

        self.trap_positions = trap_positions
        self.labels = labels
        self.videobox = Label(trap_positions, labels)
        self.videobox.activeframe = images.asarray(key=TrapViewer.i)
        try:
            self.videobox.maxintens = int(images.imagej_metadata['max'])
            self.videobox.maxintens = 15265
            print(images.imagej_metadata)
        except KeyError:
            self.videobox.maxintens = int(np.max(self.videobox.activeframe))

        self.videobox.setGeometry(QtCore.QRect(70, 80, 200, 200))

        self.lyt = QVBoxLayout()
        self.lyt.addWidget(self.videobox, 5)

        self.setLayout(self.lyt)

        self.sl = QSlider(Qt.Horizontal)

        self.sl.setMinimum(0.0)
        self.sl.setMaximum(self.video.imagej_metadata['frames'] - 1)

        self.sl.setTickPosition(QSlider.TicksAbove)
        self.sl.setTracking(True)
        self.sl.setTickInterval(100)

        self.sl.valueChanged.connect(self.whenslidechanges)

        self.frame_counter = QDoubleSpinBox()
        self.frame = self.videobox.activeframe
        self.frame_counter.setSingleStep(1)
        self.frame_counter.setRange(self.sl.minimum(), self.sl.maximum() - 1)
        self.frame_counter.valueChanged.connect(self.sl.setValue)
        self.frame_counter.valueChanged.connect(self.video_time_update)

        self.video_time = QDoubleSpinBox()
        self.video_time.setSingleStep(30)
        self.video_time.setRange(self.sl.minimum(), 30 * self.sl.maximum() - 1)
        self.frameratetimer = QTimer()
        self.frameratetimer.setInterval(50)
        self.frameratetimer.timeout.connect(self.update_display)

        self.play_button = QPushButton('Play Video')
        self.play_button.clicked.connect(self.frameratetimer.start)

        self.stop_button = QPushButton('Stop Video')
        self.stop_button.clicked.connect(self.frameratetimer.stop)
        self.sl.valueChanged.connect(self.whenslidechanges)

        self.lyt.addWidget(self.play_button, 0)
        self.lyt.addWidget(self.stop_button, 1)
        self.lyt.addWidget(self.sl, 2)
        self.lyt.addWidget(self.frame_counter, 3)
        self.lyt.addWidget(self.video_time, 4)

        self.show()

    def update_display(self):

        self.frame = self.video.asarray(key=TrapViewer.i)
        self.videobox.activeframe = self.frame

        self.videobox.update()

        self.frame_counter.setValue(float(TrapViewer.i))

        if TrapViewer.i < self.video.imagej_metadata['frames']:

            TrapViewer.i += 1

    def whenslidechanges(self):

        if self.frameratetimer.isActive():
            self.frameratetimer.stop()

            TrapViewer.i = self.sl.value()

            self.update_display()
            TrapViewer.i -= 1

            self.frameratetimer.start()
        else:

            TrapViewer.i = self.sl.value()

            self.update_display()
            TrapViewer.i -= 1

    def video_time_update(self):
        self.video_time.setValue(30 * self.frame_counter.value())
示例#21
0
class ScriptRunner(object):
    """
    Runs a script that interacts with a widget (tests it).
    If the script is a python generator then after each iteration controls returns
    to the QApplication's event loop.
    Generator scripts can yield a positive number. It is treated as the number of seconds
    before the next iteration is called. During the wait time the event loop is running.
    """
    def __init__(self, script, widget=None, close_on_finish=True, pause=0, is_cli=False):
        """
        Initialise a runner.
        :param script: The script to run.
        :param widget: The widget to test.
        :param close_on_finish: If true close the widget after the script has finished.
        :param is_cli: If true the script is to be run from a command line tool. Exceptions are
            treated slightly differently in this case.
        """
        app = get_application()
        self.script = script
        self.widget = widget
        self.close_on_finish = close_on_finish
        self.pause = pause
        self.is_cli = is_cli
        self.error = None
        self.script_iter = [None]
        self.pause_timer = QTimer(app)
        self.pause_timer.setSingleShot(True)
        self.script_timer = QTimer(app)

    def run(self):
        ret = run_script(self.script, self.widget)
        if isinstance(ret, Exception):
            raise ret
        self.script_iter = [iter(ret) if inspect.isgenerator(ret) else None]
        if self.pause != 0:
            self.script_timer.setInterval(self.pause * 1000)
        # Zero-timeout timer runs script_runner() between Qt events
        self.script_timer.timeout.connect(self, Qt.QueuedConnection)
        QMetaObject.invokeMethod(self.script_timer, 'start', Qt.QueuedConnection)

    def __call__(self):
        app = get_application()
        if not self.pause_timer.isActive():
            try:
                script_iter = self.script_iter[-1]
                if script_iter is None:
                    if self.close_on_finish:
                        app.closeAllWindows()
                        app.exit()
                    return
                # Run test script until the next 'yield'
                try:
                    ret = next(script_iter)
                except ValueError:
                    return
                while ret is not None:
                    if inspect.isgenerator(ret):
                        self.script_iter.append(ret)
                        ret = None
                    elif isinstance(ret, six.integer_types) or isinstance(ret, float):
                        # Start non-blocking pause in seconds
                        self.pause_timer.start(int(ret * 1000))
                        ret = None
                    else:
                        ret = ret()
            except StopIteration:
                if len(self.script_iter) > 1:
                    self.script_iter.pop()
                else:
                    self.script_iter = [None]
                    self.script_timer.stop()
                    if self.close_on_finish:
                        app.closeAllWindows()
                        app.exit(0)
            except Exception as e:
                self.script_iter = [None]
                traceback.print_exc()
                if self.close_on_finish:
                    app.exit(1)
                self.error = e
示例#22
0
class QtLayerList(QScrollArea):
    def __init__(self, layers):
        super().__init__()

        self.layers = layers
        self.setWidgetResizable(True)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        scrollWidget = QWidget()
        self.setWidget(scrollWidget)
        self.vbox_layout = QVBoxLayout(scrollWidget)
        self.vbox_layout.addWidget(QtDivider())
        self.vbox_layout.addStretch(1)
        self.vbox_layout.setContentsMargins(0, 0, 0, 0)
        self.vbox_layout.setSpacing(2)
        self.centers = []

        # Create a timer to be used for autoscrolling the layers list up and
        # down when dragging a layer near the end of the displayed area
        self.dragTimer = QTimer()
        self.dragTimer.setSingleShot(False)
        self.dragTimer.setInterval(20)
        self.dragTimer.timeout.connect(self._force_scroll)
        self._scroll_up = True
        self._min_scroll_region = 24
        self.setAcceptDrops(True)
        self.setToolTip('Layer list')
        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)

        self.layers.events.added.connect(self._add)
        self.layers.events.removed.connect(self._remove)
        self.layers.events.reordered.connect(self._reorder)

        self.drag_start_position = np.zeros(2)
        self.drag_name = None

    def _add(self, event):
        """Insert widget for layer `event.item` at index `event.index`."""
        layer = event.item
        total = len(self.layers)
        index = 2 * (total - event.index) - 1
        widget = QtLayerWidget(layer)
        self.vbox_layout.insertWidget(index, widget)
        self.vbox_layout.insertWidget(index + 1, QtDivider())
        layer.events.select.connect(self._scroll_on_select)

    def _remove(self, event):
        """Remove widget for layer at index `event.index`."""
        layer_index = event.index
        total = len(self.layers)
        # Find property widget and divider for layer to be removed
        index = 2 * (total - layer_index) + 1
        widget = self.vbox_layout.itemAt(index).widget()
        divider = self.vbox_layout.itemAt(index + 1).widget()
        self.vbox_layout.removeWidget(widget)
        widget.deleteLater()
        self.vbox_layout.removeWidget(divider)
        divider.deleteLater()

    def _reorder(self, event=None):
        """Reorders list of layer widgets by looping through all
        widgets in list sequentially removing them and inserting
        them into the correct place in final list.
        """
        total = len(self.layers)

        # Create list of the current property and divider widgets
        widgets = [
            self.vbox_layout.itemAt(i + 1).widget() for i in range(2 * total)
        ]
        # Take every other widget to ignore the dividers and get just the
        # property widgets
        indices = [
            self.layers.index(w.layer)
            for i, w in enumerate(widgets)
            if i % 2 == 0
        ]

        # Move through the layers in order
        for i in range(total):
            # Find index of property widget in list of the current layer
            index = 2 * indices.index(i)
            widget = widgets[index]
            divider = widgets[index + 1]
            # Check if current index does not match new index
            index_current = self.vbox_layout.indexOf(widget)
            index_new = 2 * (total - i) - 1
            if index_current != index_new:
                # Remove that property widget and divider
                self.vbox_layout.removeWidget(widget)
                self.vbox_layout.removeWidget(divider)
                # Insert the property widget and divider into new location
                self.vbox_layout.insertWidget(index_new, widget)
                self.vbox_layout.insertWidget(index_new + 1, divider)

    def _force_scroll(self):
        """Force the scroll bar to automattically scroll either up or down."""
        cur_value = self.verticalScrollBar().value()
        if self._scroll_up:
            new_value = cur_value - self.verticalScrollBar().singleStep() / 4
            if new_value < 0:
                new_value = 0
            self.verticalScrollBar().setValue(new_value)
        else:
            new_value = cur_value + self.verticalScrollBar().singleStep() / 4
            if new_value > self.verticalScrollBar().maximum():
                new_value = self.verticalScrollBar().maximum()
            self.verticalScrollBar().setValue(new_value)

    def _scroll_on_select(self, event):
        """Scroll to ensure that the currently selected layer is visible."""
        layer = event.source
        self._ensure_visible(layer)

    def _ensure_visible(self, layer):
        """Ensure layer widget for at particular layer is visible."""
        total = len(self.layers)
        layer_index = self.layers.index(layer)
        # Find property widget and divider for layer to be removed
        index = 2 * (total - layer_index) - 1
        widget = self.vbox_layout.itemAt(index).widget()
        self.ensureWidgetVisible(widget)

    def keyPressEvent(self, event):
        event.ignore()

    def keyReleaseEvent(self, event):
        event.ignore()

    def mousePressEvent(self, event):
        # Check if mouse press happens on a layer properties widget or
        # a child of such a widget. If not, the press has happended on the
        # Layers Widget itself and should be ignored.
        widget = self.childAt(event.pos())
        layer = (
            getattr(widget, 'layer', None)
            or getattr(widget.parentWidget(), 'layer', None)
            or getattr(widget.parentWidget().parentWidget(), 'layer', None)
        )

        if layer is not None:
            self.drag_start_position = np.array(
                [event.pos().x(), event.pos().y()]
            )
            self.drag_name = layer.name
        else:
            self.drag_name = None

    def mouseReleaseEvent(self, event):
        if self.drag_name is None:
            # Unselect all the layers if not dragging a layer
            self.layers.unselect_all()
            return

        modifiers = event.modifiers()
        layer = self.layers[self.drag_name]
        if modifiers == Qt.ShiftModifier:
            # If shift select all layers in between currently selected one and
            # clicked one
            index = self.layers.index(layer)
            lastSelected = None
            for i in range(len(self.layers)):
                if self.layers[i].selected:
                    lastSelected = i
            r = [index, lastSelected]
            r.sort()
            for i in range(r[0], r[1] + 1):
                self.layers[i].selected = True
        elif modifiers == Qt.ControlModifier:
            # If control click toggle selected state
            layer.selected = not layer.selected
        else:
            # If otherwise unselect all and leave clicked one selected
            self.layers.unselect_all(ignore=layer)
            layer.selected = True

    def mouseMoveEvent(self, event):
        position = np.array([event.pos().x(), event.pos().y()])
        distance = np.linalg.norm(position - self.drag_start_position)
        if (
            distance < QApplication.startDragDistance()
            or self.drag_name is None
        ):
            return
        mimeData = QMimeData()
        mimeData.setText(self.drag_name)
        drag = QDrag(self)
        drag.setMimeData(mimeData)
        drag.setHotSpot(event.pos() - self.rect().topLeft())
        drag.exec_()
        if self.drag_name is not None:
            index = self.layers.index(self.drag_name)
            layer = self.layers[index]
            self._ensure_visible(layer)

    def dragLeaveEvent(self, event):
        """Unselects layer dividers."""
        event.ignore()
        self.dragTimer.stop()
        for i in range(0, self.vbox_layout.count(), 2):
            self.vbox_layout.itemAt(i).widget().setSelected(False)

    def dragEnterEvent(self, event):
        if event.source() == self:
            event.accept()
            divs = []
            for i in range(0, self.vbox_layout.count(), 2):
                widget = self.vbox_layout.itemAt(i).widget()
                divs.append(widget.y() + widget.frameGeometry().height() / 2)
            self.centers = [
                (divs[i + 1] + divs[i]) / 2 for i in range(len(divs) - 1)
            ]
        else:
            event.ignore()

    def dragMoveEvent(self, event):
        """Set the appropriate layers list divider to be highlighted when
        dragging a layer to a new position in the layers list.
        """
        max_height = self.frameGeometry().height()
        if (
            event.pos().y() < self._min_scroll_region
            and not self.dragTimer.isActive()
        ):
            self._scroll_up = True
            self.dragTimer.start()
        elif (
            event.pos().y() > max_height - self._min_scroll_region
            and not self.dragTimer.isActive()
        ):
            self._scroll_up = False
            self.dragTimer.start()
        elif (
            self.dragTimer.isActive()
            and event.pos().y() >= self._min_scroll_region
            and event.pos().y() <= max_height - self._min_scroll_region
        ):
            self.dragTimer.stop()

        # Determine which widget center is the mouse currently closed to
        cord = event.pos().y() + self.verticalScrollBar().value()
        center_list = (i for i, x in enumerate(self.centers) if x > cord)
        divider_index = next(center_list, len(self.centers))
        # Determine the current location of the widget being dragged
        total = self.vbox_layout.count() // 2 - 1
        insert = total - divider_index
        index = self.layers.index(self.drag_name)
        # If the widget being dragged hasn't moved above or below any other
        # widgets then don't highlight any dividers
        selected = not (insert == index) and not (insert - 1 == index)
        # Set the selected state of all the dividers
        for i in range(0, self.vbox_layout.count(), 2):
            if i == 2 * divider_index:
                self.vbox_layout.itemAt(i).widget().setSelected(selected)
            else:
                self.vbox_layout.itemAt(i).widget().setSelected(False)

    def dropEvent(self, event):
        if self.dragTimer.isActive():
            self.dragTimer.stop()

        for i in range(0, self.vbox_layout.count(), 2):
            self.vbox_layout.itemAt(i).widget().setSelected(False)
        cord = event.pos().y() + self.verticalScrollBar().value()
        center_list = (i for i, x in enumerate(self.centers) if x > cord)
        divider_index = next(center_list, len(self.centers))
        total = self.vbox_layout.count() // 2 - 1
        insert = total - divider_index
        index = self.layers.index(self.drag_name)
        if index != insert and index + 1 != insert:
            if insert >= index:
                insert -= 1
            self.layers.move_selected(index, insert)
        event.accept()
示例#23
0
class QtFrameRate(QLabel):
    """A frame rate label with green/yellow/red LEDs.

    The LED values are logarithmic, so a lot of detail between 60Hz and
    10Hz but then the highest LED is many seconds long.
    """
    def __init__(self):
        super().__init__()

        self.leds = LedState()  # The per-LED config and state.

        # The last time we were updated, either from mouse move or our
        # timer. We update _last_time in both cases.
        self._last_time: Optional[float] = None

        # The bitmap image we draw into.
        self._image = np.zeros(BITMAP_SHAPE, dtype=np.uint8)

        # We animate on camera movements, but then we use a timer to
        # animate the display. When all the LEDs go off, we stop the timer
        # so that we use zero CPU until another camera movement.
        self._timer = QTimer()
        self._timer.setSingleShot(False)
        self._timer.setInterval(33)
        self._timer.timeout.connect(self._on_timer)

        # _print_calibration()  # Debugging.

    def _on_timer(self) -> None:
        """Animate the LEDs."""
        now = time.time()
        self._draw(now)  # Just animate and draw, no new peak.

        # Stop timer if nothing more to animation, save CPU.
        if self.leds.all_off():
            self._timer.stop()

    def _draw(self, now: float) -> None:
        """Animate the LEDs.

        Parameters
        ----------
        now : float
            The current time in seconds.
        """
        self.leds.update(now)  # Animates the LEDs.
        self._update_image(now)  # Draws our internal self._image
        self._update_bitmap()  # Writes self._image into the QLabel bitmap.

        # We always update _last_time whether this was from a camera move
        # or the timer. This is the right thing to do since in either case
        # it means a frame was drawn.
        self._last_time = now

    def on_camera_move(self) -> None:
        """Update our display to show the new framerate."""

        # Only count this frame if the timer is active. This avoids
        # displaying a potentially super long frame since it might have
        # been many seconds or minutes since the last camera movement.
        #
        # Ideally we should display the draw time of even that first frame,
        # but there's no easy/obvious way to do that today. And this will
        # show everthing except one frame.
        first_time = self._last_time is None
        use_delta = self._timer.isActive() and not first_time
        now = time.time()

        if use_delta:
            delta_seconds = now - self._last_time
            self.leds.set_peak(now, delta_seconds)

        self._draw(now)  # Draw the whole meter.

        # Since there was activity, we need to start the timer so we can
        # animate the decay of the LEDs. The timer will be shut off when
        # all the LEDs go idle.
        self._timer.start()

    def _update_image(self, now: float) -> None:
        """Update our self._image with the latest meter display.

        Parameters
        ----------
        now : float
            The current time in seconds.
        """
        self._image.fill(0)  # Start fresh each time.

        # Get colors with latest alpha values accord to decay.
        colors = self.leds.get_colors(now)

        # Draw each segment with the right color and alpha (due to decay).
        for index in range(NUM_SEGMENTS):
            x0 = int(index * SEGMENT_SPACING)
            x1 = int(x0 + SEGMENT_WIDTH)
            y0, y1 = 0, BITMAP_SHAPE[0]  # The whole height of the bitmap.
            self._image[y0:y1, x0:x1] = colors[index]

    def _update_bitmap(self) -> None:
        """Update the bitmap with latest image data."""
        height, width = BITMAP_SHAPE[:2]
        image = QImage(self._image, width, height, QImage.Format_RGBA8888)
        self.setPixmap(QPixmap.fromImage(image))
示例#24
0
class InputsWidget(QWidget, Ui_Form):
    """There has following functions:

    + Function of mechanism variables settings.
    + Path recording.
    """
    __paths: Dict[str, _Paths]
    __slider_paths: Dict[str, _SliderPaths]

    about_to_resolve = Signal()

    def __init__(self, parent: MainWindowBase):
        super(InputsWidget, self).__init__(parent)
        self.setupUi(self)
        # parent's function pointer
        self.free_move_button = parent.free_move_button
        self.entities_point = parent.entities_point
        self.entities_link = parent.entities_link
        self.vpoints = parent.vpoint_list
        self.vlinks = parent.vlink_list
        self.main_canvas = parent.main_canvas
        self.solve = parent.solve
        self.reload_canvas = parent.reload_canvas
        self.output_to = parent.output_to
        self.conflict = parent.conflict
        self.dof = parent.dof
        self.right_input = parent.right_input
        self.command_stack = parent.cmd_stack
        self.set_coords_as_current = parent.set_coords_as_current
        self.get_back_position = parent.get_back_position
        # Angle panel
        self.dial = QRotatableView(self)
        self.dial.setStatusTip("Input widget of rotatable joint.")
        self.dial.setEnabled(False)
        self.dial.value_changed.connect(self.__update_var)
        self.dial_spinbox.valueChanged.connect(self.__set_var)
        self.inputs_dial_layout.insertWidget(0, self.dial)
        # Play button
        self.variable_stop.clicked.connect(self.variable_value_reset)
        # Timer for play button
        self.inputs_play_shaft = QTimer()
        self.inputs_play_shaft.setInterval(10)
        self.inputs_play_shaft.timeout.connect(self.__change_index)
        # Change the point coordinates with current position
        self.update_pos.clicked.connect(self.set_coords_as_current)
        # Record list
        self.record_list.blockSignals(True)
        self.record_list.addItem(_AUTO_PATH)
        self.record_list.setCurrentRow(0)
        self.record_list.blockSignals(False)
        self.__paths = {_AUTO_PATH: self.main_canvas.path_preview}
        self.__slider_paths = {_AUTO_PATH: self.main_canvas.slider_path_preview}

        def slot(widget: QCheckBox) -> Callable[[int], None]:
            @Slot(int)
            def func(ind: int) -> None:
                widget.setEnabled(ind >= 0
                                  and self.vpoints[ind].type != VJoint.R)

            return func

        # Slot option
        self.plot_joint.currentIndexChanged.connect(slot(self.plot_joint_slot))
        self.wrt_joint.currentIndexChanged.connect(slot(self.wrt_joint_slot))

    def clear(self) -> None:
        """Clear function to reset widget status."""
        self.__paths = {_AUTO_PATH: self.__paths[_AUTO_PATH]}
        for _ in range(self.record_list.count() - 1):
            self.record_list.takeItem(1)
        self.variable_list.clear()

    def __set_angle_mode(self) -> None:
        """Change to angle input."""
        self.dial.set_minimum(0)
        self.dial.set_maximum(360)
        self.dial_spinbox.setMinimum(0)
        self.dial_spinbox.setMaximum(360)

    def __set_unit_mode(self) -> None:
        """Change to unit input."""
        self.dial.set_minimum(-500)
        self.dial.set_maximum(500)
        self.dial_spinbox.setMinimum(-500)
        self.dial_spinbox.setMaximum(500)

    def paths(self) -> Mapping[str, _Paths]:
        """Return current path data."""
        return _no_auto_path(self.__paths)

    def slider_paths(self) -> Mapping[str, _SliderPaths]:
        """Return current path data."""
        return _no_auto_path(self.__slider_paths)

    @Slot(tuple)
    def set_selection(self, selections: Sequence[int]) -> None:
        """Set one selection from canvas."""
        self.joint_list.setCurrentRow(selections[0])

    @Slot()
    def clear_selection(self) -> None:
        """Clear the points selection."""
        self.driver_list.clear()
        self.joint_list.setCurrentRow(-1)

    @Slot(int, name='on_joint_list_currentRowChanged')
    def __update_relate_points(self, _=None) -> None:
        """Change the point row from input widget."""
        self.driver_list.clear()
        item: Optional[QListWidgetItem] = self.joint_list.currentItem()
        if item is None:
            return
        p0 = _variable_int(item.text())
        base_point = self.vpoints[p0]
        type_int = base_point.type
        if type_int == VJoint.R:
            for i, vpoint in enumerate(self.vpoints):
                if i == p0:
                    continue
                if base_point.same_link(vpoint):
                    if base_point.grounded() and vpoint.grounded():
                        continue
                    self.driver_list.addItem(f"[{vpoint.type_str}] Point{i}")
        elif type_int in {VJoint.P, VJoint.RP}:
            self.driver_list.addItem(f"[{base_point.type_str}] Point{p0}")

    @Slot(int, name='on_driver_list_currentRowChanged')
    def __set_add_var_enabled(self, _=None) -> None:
        """Set enable of 'add variable' button."""
        driver = self.driver_list.currentIndex()
        self.variable_add.setEnabled(driver != -1)

    @Slot(name='on_variable_add_clicked')
    def __add_inputs_variable(
        self,
        p0: Optional[int] = None,
        p1: Optional[int] = None
    ) -> None:
        """Add variable with '->' sign."""
        if p0 is None:
            item: Optional[QListWidgetItem] = self.joint_list.currentItem()
            if item is None:
                return
            p0 = _variable_int(item.text())
        if p1 is None:
            item = self.driver_list.currentItem()
            if item is None:
                return
            p1 = _variable_int(item.text())
        # Check DOF
        if self.dof() <= self.input_count():
            QMessageBox.warning(
                self,
                "Wrong DOF",
                "The number of variable must no more than degrees of freedom."
            )
            return
        # Check same link
        if not self.vpoints[p0].same_link(self.vpoints[p1]):
            QMessageBox.warning(
                self,
                "Wrong pair",
                "The base point and driver point should at the same link."
            )
            return
        # Check repeated pairs
        for p0_, p1_, _ in self.input_pairs():
            if {p0, p1} == {p0_, p1_} and self.vpoints[p0].type == VJoint.R:
                QMessageBox.warning(
                    self,
                    "Wrong pair",
                    "There already have a same pair."
                )
                return
        if p0 == p1:
            # One joint by offset
            value = self.vpoints[p0].true_offset()
        else:
            # Two joints by angle
            value = self.vpoints[p0].slope_angle(self.vpoints[p1])
        self.command_stack.push(AddInput('->'.join((
            f'Point{p0}',
            f"Point{p1}",
            f"{value:.02f}",
        )), self.variable_list))

    def add_inputs_variables(self, variables: _Vars) -> None:
        """Add from database."""
        for p0, p1 in variables:
            self.__add_inputs_variable(p0, p1)

    @Slot(QListWidgetItem, name='on_variable_list_itemClicked')
    def __dial_ok(self, _=None) -> None:
        """Set the angle of base link and drive link."""
        if self.inputs_play_shaft.isActive():
            return
        row = self.variable_list.currentRow()
        enabled = row > -1
        is_rotatable = (
            enabled
            and not self.free_move_button.isChecked()
            and self.right_input()
        )
        self.dial.setEnabled(is_rotatable)
        self.dial_spinbox.setEnabled(is_rotatable)
        self.oldVar = self.dial.value()
        self.variable_play.setEnabled(is_rotatable)
        self.variable_speed.setEnabled(is_rotatable)
        item: Optional[QListWidgetItem] = self.variable_list.currentItem()
        if item is None:
            return
        expr = item.text().split('->')
        p0 = int(expr[0].replace('Point', ''))
        p1 = int(expr[1].replace('Point', ''))
        value = float(expr[2])
        if p0 == p1:
            self.__set_unit_mode()
        else:
            self.__set_angle_mode()
        self.dial.set_value(value if enabled else 0)

    def variable_excluding(self, row: Optional[int] = None) -> None:
        """Remove variable if the point was been deleted. Default: all."""
        one_row: bool = row is not None
        for i, (b, d, _) in enumerate(self.input_pairs()):
            # If this is not origin point any more
            if one_row and row != b:
                continue
            self.command_stack.push(DeleteInput(i, self.variable_list))

    @Slot(name='on_variable_remove_clicked')
    def remove_var(self, row: int = -1) -> None:
        """Remove and reset angle."""
        if row == -1:
            row = self.variable_list.currentRow()
        if not row > -1:
            return
        self.variable_stop.click()
        self.command_stack.push(DeleteInput(row, self.variable_list))
        self.get_back_position()
        self.solve()

    def interval(self) -> float:
        """Return interval value."""
        return self.record_interval.value()

    def input_count(self) -> int:
        """Use to show input variable count."""
        return self.variable_list.count()

    def input_pairs(self) -> Iterator[Tuple[int, int, float]]:
        """Back as point number code."""
        for row in range(self.variable_list.count()):
            var = self.variable_list.item(row).text().split('->')
            p0 = int(var[0].replace('Point', ''))
            p1 = int(var[1].replace('Point', ''))
            angle = float(var[2])
            yield p0, p1, angle

    def variable_reload(self) -> None:
        """Auto check the points and type."""
        self.joint_list.clear()
        self.plot_joint.clear()
        self.wrt_joint.clear()
        for i in range(self.entities_point.rowCount()):
            type_text = self.entities_point.item(i, 2).text()
            for w in [self.joint_list, self.plot_joint, self.wrt_joint]:
                w.addItem(f"[{type_text}] Point{i}")
        self.variable_value_reset()

    @Slot(float)
    def __set_var(self, value: float) -> None:
        self.dial.set_value(value)

    @Slot(float)
    def __update_var(self, value: float) -> None:
        """Update the value when rotating QDial."""
        item = self.variable_list.currentItem()
        self.dial_spinbox.blockSignals(True)
        self.dial_spinbox.setValue(value)
        self.dial_spinbox.blockSignals(False)
        if item:
            item_text = item.text().split('->')
            item_text[-1] = f"{value:.02f}"
            item.setText('->'.join(item_text))
            self.about_to_resolve.emit()
        if (
            self.record_start.isChecked()
            and abs(self.oldVar - value) > self.record_interval.value()
        ):
            self.main_canvas.record_path()
            self.oldVar = value

    def variable_value_reset(self) -> None:
        """Reset the value of QDial."""
        if self.inputs_play_shaft.isActive():
            self.variable_play.setChecked(False)
            self.inputs_play_shaft.stop()
        self.get_back_position()
        for i, (p0, p1, _) in enumerate(self.input_pairs()):
            self.variable_list.item(i).setText('->'.join([
                f'Point{p0}',
                f'Point{p1}',
                f"{self.vpoints[p0].slope_angle(self.vpoints[p1]):.02f}",
            ]))
        self.__dial_ok()
        self.solve()

    @Slot(bool, name='on_variable_play_toggled')
    def __play(self, toggled: bool) -> None:
        """Triggered when play button was changed."""
        self.dial.setEnabled(not toggled)
        self.dial_spinbox.setEnabled(not toggled)
        if toggled:
            self.inputs_play_shaft.start()
        else:
            self.inputs_play_shaft.stop()

    @Slot()
    def __change_index(self) -> None:
        """QTimer change index."""
        index = self.dial.value()
        speed = self.variable_speed.value()
        extreme_rebound = (
            self.conflict.isVisible()
            and self.extremeRebound.isChecked()
        )
        if extreme_rebound:
            speed = -speed
            self.variable_speed.setValue(speed)
        index += speed * 0.06 * (3 if extreme_rebound else 1)
        self.dial.set_value(index)

    @Slot(bool, name='on_record_start_toggled')
    def __start_record(self, toggled: bool) -> None:
        """Save to file path data."""
        if toggled:
            self.main_canvas.record_start(int(
                self.dial_spinbox.maximum() / self.record_interval.value()
            ))
            return
        path, path_slider = self.main_canvas.get_record_path()
        name, ok = QInputDialog.getText(
            self,
            "Recording completed!",
            "Please input name tag:"
        )
        i = 0
        name = name or f"Record_{i}"
        while name in self.__paths:
            name = f"Record_{i}"
            i += 1
        QMessageBox.information(self, "Record",
                                "The name tag is being used or empty.")
        self.add_path(name, path, path_slider)

    def add_path(self, name: str, path: _Paths, slider: _SliderPaths) -> None:
        """Add path function."""
        self.command_stack.push(AddPath(
            self.record_list,
            name,
            self.__paths,
            self.__slider_paths,
            path,
            slider
        ))
        self.record_list.setCurrentRow(self.record_list.count() - 1)

    def load_paths(self, paths: Mapping[str, _Paths],
                   slider_paths: Mapping[str, _SliderPaths]) -> None:
        """Add multiple paths."""
        for name, path in paths.items():
            self.add_path(name, path, slider_paths.get(name, {}))

    @Slot(name='on_record_remove_clicked')
    def __remove_path(self) -> None:
        """Remove path data."""
        row = self.record_list.currentRow()
        if not row > 0:
            return
        self.command_stack.push(DeletePath(
            row,
            self.record_list,
            self.__paths,
            self.__slider_paths
        ))
        self.record_list.setCurrentRow(self.record_list.count() - 1)
        self.reload_canvas()

    @Slot(QListWidgetItem, name='on_record_list_itemDoubleClicked')
    def __path_dlg(self, item: QListWidgetItem) -> None:
        """View path data."""
        name = item.text().split(":", maxsplit=1)[0]
        try:
            paths = self.__paths[name]
        except KeyError:
            return
        points_text = ", ".join(f"Point{i}" for i in range(len(paths)))
        if QMessageBox.question(
            self,
            "Path data",
            f"This path data including {points_text}.",
            (QMessageBox.Save | QMessageBox.Close),
            QMessageBox.Close
        ) != QMessageBox.Save:
            return
        file_name = self.output_to(
            "path data",
            ["Comma-Separated Values (*.csv)", "Text file (*.txt)"]
        )
        if not file_name:
            return
        with open(file_name, 'w+', encoding='utf-8', newline='') as stream:
            w = writer(stream)
            for path in paths:
                for point in path:
                    w.writerow(point)
                w.writerow(())
        logger.info(f"Output path data: {file_name}")

    def __current_path_name(self) -> str:
        """Return the current path name."""
        return self.record_list.currentItem().text().split(':', maxsplit=1)[0]

    @Slot(name='on_copy_path_clicked')
    def __copy_path(self):
        """Copy path from record list."""
        name = self.__current_path_name()
        num = 0
        name_copy = f"{name}_{num}"
        while name_copy in self.__paths:
            name_copy = f"{name}_{num}"
            num += 1
        self.add_path(name_copy, copy(self.__paths[name]), {})

    @Slot(name='on_cp_data_button_clicked')
    def __copy_path_data(self) -> None:
        """Copy current path data to clipboard."""
        data = self.__paths[self.__current_path_name()]
        if not data:
            return
        QApplication.clipboard().setText('\n'.join(
            f"[{x}, {y}]," for x, y in data[self.plot_joint.currentIndex()]
        ))

    @Slot(name='on_show_button_clicked')
    def __show_path(self) -> None:
        """Show specified path."""
        self.main_canvas.set_path_show(self.plot_joint.currentIndex())

    @Slot(name='on_show_all_button_clicked')
    def __show_all_path(self) -> None:
        """Show all paths."""
        self.record_show.setChecked(True)
        self.main_canvas.set_path_show(-1)

    @Slot(bool, name='on_record_show_toggled')
    def __set_path_show(self, toggled: bool) -> None:
        """Show all paths or hide."""
        self.main_canvas.set_path_show(-1 if toggled else -2)

    @Slot(int, name='on_record_list_currentRowChanged')
    def __set_path(self, _=None) -> None:
        """Reload the canvas when switch the path."""
        if not self.record_show.isChecked():
            self.record_show.setChecked(True)
        self.reload_canvas()

    def current_path(self) -> Tuple[_Paths, _SliderPaths]:
        """Return current path data to main canvas.

        + No path.
        + Show path data.
        + Auto preview.
        """
        row = self.record_list.currentRow()
        if row in {0, -1}:
            return (), {}
        name = self.record_list.item(row).text().split(':')[0]
        return self.__paths.get(name, ()), self.__slider_paths.get(name, {})

    @Slot(name='on_variable_up_clicked')
    @Slot(name='on_variable_down_clicked')
    def __set_variable_priority(self) -> None:
        row = self.variable_list.currentRow()
        if not row > -1:
            return
        item = self.variable_list.currentItem()
        self.variable_list.insertItem(
            row + (-1 if self.sender() == self.variable_up else 1),
            self.variable_list.takeItem(row)
        )
        self.variable_list.setCurrentItem(item)

    @Slot(name='on_animate_button_clicked')
    def __animate(self) -> None:
        """Make a motion animation."""
        name = self.__current_path_name()
        data = self.__paths.get(name, [])
        if not data:
            return
        dlg = AnimateDialog(self.vpoints, self.vlinks, data,
                            self.__slider_paths.get(name, {}),
                            self.main_canvas.monochrome, self)
        dlg.show()
        dlg.exec_()
        dlg.deleteLater()

    @Slot(name='on_plot_button_clicked')
    def __plot(self) -> None:
        """Plot the data. Show the X and Y axes as two line."""
        joint = self.plot_joint.currentIndex()
        name = self.__current_path_name()
        data = self.__paths.get(name, [])
        slider_data = self.__slider_paths.get(name, {})
        if not data:
            return
        if self.plot_joint_slot.isChecked():
            pos = array(slider_data.get(joint, []))
        else:
            pos = array(data[joint])
        if self.wrt_label.isChecked():
            joint_wrt = self.wrt_joint.currentIndex()
            if self.wrt_joint_slot.isChecked():
                pos[:] -= array(slider_data.get(joint_wrt, []))
            else:
                pos[:] -= array(data[joint_wrt])
        plot = {}
        row = 0
        for button, value in [
            (self.plot_pos, lambda: pos),
            (self.plot_vel, vel := lambda: derivative(pos)),
            (self.plot_acc, acc := lambda: derivative(vel())),
            (self.plot_jerk, lambda: derivative(acc())),
            (self.plot_curvature, cur := lambda: curvature(data[joint])),
            (self.plot_signature, lambda: path_signature(cur())),
            (self.plot_norm, lambda: norm_path(pos)),
            (self.plot_norm_pca, lambda: norm_pca(pos)),
            (self.plot_fourier, lambda: _fourier(pos)),
        ]:  # type: QCheckBox, Callable[[], ndarray]
            if button.isChecked():
                row += 1
                plot[button.text()] = value()
        if row < 1:
            QMessageBox.warning(self, "No target", "No any plotting target.")
            return
        polar = self.p_coord_sys.isChecked()
        col = 1
        if polar:
            row, col = col, row
        dlg = DataChartDialog(self, "Analysis", row, col, polar)
        dlg.setWindowIcon(QIcon(QPixmap("icons:formula.png")))
        ax = dlg.ax()
        for p, (title, xy) in enumerate(plot.items()):
            ax_i = ax[p]
            ax_i.set_title(title)
            if title == "Path Signature":
                ax_i.plot(xy[:, 0], xy[:, 1])
                ax_i.set_ylabel(r"$\kappa$")
                ax_i.set_xlabel(r"$\int|\kappa|dt$")
            elif xy.ndim == 2:
                x = xy[:, 0]
                y = xy[:, 1]
                if self.c_coord_sys.isChecked():
                    ax_i.plot(x, label='x')
                    ax_i.plot(y, label='y')
                    ax_i.legend()
                else:
                    r = hypot(x, y)
                    theta = arctan2(y, x)
                    ax_i.plot(theta, r, linewidth=5)
            else:
                ax_i.plot(xy)
        dlg.set_margin(0.2)
        dlg.show()
        dlg.exec_()
        dlg.deleteLater()
示例#25
0
class ProgressView(QWidget):
    """
    :type batch_manager: CalculationManager
    """
    def __init__(self, parent, batch_manager):
        QWidget.__init__(self, parent)
        self.calculation_manager = batch_manager
        self.whole_progress = QProgressBar(self)
        self.whole_progress.setMinimum(0)
        self.whole_progress.setMaximum(1)
        self.whole_progress.setFormat("%v of %m")
        self.whole_progress.setTextVisible(True)
        self.part_progress = QProgressBar(self)
        self.part_progress.setMinimum(0)
        self.part_progress.setMaximum(1)
        self.part_progress.setFormat("%v of %m")
        self.whole_label = QLabel("All batch progress:", self)
        self.part_label = QLabel("Single batch progress:", self)
        self.logs = ExceptionList(self)
        self.logs.setToolTip("Logs")
        self.task_que = QListWidget()
        self.process_num_timer = QTimer()
        self.process_num_timer.setInterval(1000)
        self.process_num_timer.setSingleShot(True)
        self.process_num_timer.timeout.connect(self.change_number_of_workers)
        self.number_of_process = QSpinBox(self)
        self.number_of_process.setRange(1, multiprocessing.cpu_count())
        self.number_of_process.setValue(1)
        self.number_of_process.setToolTip(
            "Number of process used in batch calculation")
        self.number_of_process.valueChanged.connect(
            self.process_num_timer_start)
        layout = QGridLayout()
        layout.addWidget(self.whole_label, 0, 0, Qt.AlignRight)
        layout.addWidget(self.whole_progress, 0, 1, 1, 2)
        layout.addWidget(self.part_label, 1, 0, Qt.AlignRight)
        layout.addWidget(self.part_progress, 1, 1, 1, 2)
        lab = QLabel("Number of process:")
        lab.setToolTip("Number of process used in batch calculation")
        layout.addWidget(lab, 2, 0)
        layout.addWidget(self.number_of_process, 2, 1)
        layout.addWidget(self.logs, 3, 0, 1, 3)
        layout.addWidget(self.task_que, 0, 4, 0, 1)
        layout.setColumnMinimumWidth(2, 10)
        layout.setColumnStretch(2, 1)
        self.setLayout(layout)
        self.preview_timer = QTimer()
        self.preview_timer.setInterval(1000)
        self.preview_timer.timeout.connect(self.update_info)

    def new_task(self):
        self.whole_progress.setMaximum(
            self.calculation_manager.calculation_size)
        if not self.preview_timer.isActive():
            self.update_info()
            self.preview_timer.start()

    def update_info(self):
        res = self.calculation_manager.get_results()
        for el in res.errors:
            if el[0]:
                QListWidgetItem(el[0], self.logs)
            ExceptionListItem(el[1], self.logs)
            if (state_store.report_errors and parsed_version.is_devrelease
                    and not isinstance(el[1][0], SegmentationLimitException)
                    and isinstance(el[1][1], tuple)):
                with sentry_sdk.push_scope() as scope:
                    scope.set_tag("auto_report", "true")
                    sentry_sdk.capture_event(el[1][1][0])
        self.whole_progress.setValue(res.global_counter)
        working_search = True
        for i, (progress, total) in enumerate(res.jobs_status):
            if working_search and progress != total:
                self.part_progress.setMaximum(total)
                self.part_progress.setValue(progress)
                working_search = False
            if i < self.task_que.count():
                item = self.task_que.item(i)
                item.setText("Task {} ({}/{})".format(i, progress, total))
            else:
                self.task_que.addItem("Task {} ({}/{})".format(
                    i, progress, total))
        if not self.calculation_manager.has_work:
            print(
                "[ProgressView.update_info]",
                self.calculation_manager.has_work,
                self.calculation_manager.batch_manager.has_work,
                self.calculation_manager.writer.writing_finished(),
            )
            self.part_progress.setValue(self.part_progress.maximum())
            self.preview_timer.stop()
            logging.info("Progress stop")

    def process_num_timer_start(self):
        self.process_num_timer.start()

    def update_progress(self, total_progress, part_progress):
        self.whole_progress.setValue(total_progress)
        self.part_progress.setValue(part_progress)

    def set_total_size(self, size):
        self.whole_progress.setMaximum(size)

    def set_part_size(self, size):
        self.part_progress.setMaximum(size)

    def change_number_of_workers(self):
        self.calculation_manager.set_number_of_workers(
            self.number_of_process.value())
示例#26
0
class PyDMTerminator(QLabel, PyDMPrimitiveWidget):
    """
    A watchdog widget to close a window after X seconds of inactivity.
    """
    designer_icon = QIcon(get_icon_file("terminator.png"))

    def __init__(self, parent=None, timeout=60, *args, **kwargs):
        super(PyDMTerminator, self).__init__(parent=parent, *args, **kwargs)
        self.setText("")
        self._hook_setup = False
        self._timeout = 60
        self._time_rem_ms = 0

        self._timer = QTimer()
        self._timer.timeout.connect(self.handle_timeout)
        self._timer.setSingleShot(True)

        self._window = None

        if timeout and timeout > 0:
            self.timeout = timeout
        else:
            self._reset()

        self._setup_activity_hook()
        self._update_label()

    def _find_window(self):
        """
        Finds the first window available starting from this widget's parent

        Returns
        -------
        QWidget
        """
        # check buffer
        if self._window:
            return self._window
        # go fish
        w = self.parent()
        while w is not None:
            if w.isWindow():
                return w
            w = w.parent()

        # we couldn't find it
        return None

    def _setup_activity_hook(self):
        if is_qt_designer():
            return
        logger.debug('Setup Hook')
        if self._hook_setup:
            logger.debug('Setup Hook Already there')
            return
        self._window = self._find_window()
        logger.debug('Install event filter at window')

        # We must install the event filter in the application otherwise
        # it won't stop when typing or moving over other widgets or even
        # the PyDM main window if in use.
        QApplication.instance().installEventFilter(self)
        self._hook_setup = True

    def eventFilter(self, obj, ev):
        if ev.type() in (QEvent.MouseMove, QEvent.KeyPress, QEvent.KeyRelease):
            self.reset()
        return super(PyDMTerminator, self).eventFilter(obj, ev)

    def reset(self):
        if self._time_rem_ms != self._timeout * 1000:
            self._time_rem_ms = self._timeout * 1000
            self._update_label()
        self.stop()
        self.start()

    def start(self):
        if is_qt_designer():
            return
        interval = SLOW_TIMER_INTERVAL
        if self._time_rem_ms < 60 * 1000:
            interval = FAST_TIMER_INTERVAL
        self._timer.setInterval(interval)

        if not self._timer.isActive():
            self._timer.start()

    def stop(self):
        if self._timer.isActive():
            self._timer.stop()

    def _get_time_text(self, value):
        """
        Converts value in seconds into a text for days, hours, minutes and
        seconds remaining.

        Parameters
        ----------
        value : int
            The value in seconds to be converted

        Returns
        -------
        str
        """
        def time_msg(unit, val):
            return "{} {}{}".format(val, unit, "s" if val > 1 else "")

        units = ["day", "hour", "minute", "second"]
        scale = [86400, 3600, 60, 1]

        values = [0, 0, 0, 0]
        rem = value
        for idx, sc in enumerate(scale):
            val_scaled, rem = int(rem // sc), rem % sc
            if val_scaled >= 1:
                val_scaled = math.ceil(val_scaled + (rem / sc))
                values[idx] = val_scaled
                break
            values[idx] = val_scaled

        time_items = []
        for idx, un in enumerate(units):
            v = values[idx]
            if v > 0:
                time_items.append(time_msg(un, v))

        return ", ".join(time_items)

    def _update_label(self):
        """Updates the label text with the remaining time."""
        rem_time_s = self._time_rem_ms / 1000.0
        text = self._get_time_text(rem_time_s)
        self.setText("This screen will close in {}.".format(text))

    @Property(int)
    def timeout(self):
        """
        Timeout in seconds.

        Returns
        -------
        int
        """
        return self._timeout

    @timeout.setter
    def timeout(self, seconds):
        self.stop()
        if seconds and seconds > 0:
            self._timeout = seconds
            self.reset()

    def handle_timeout(self):
        """
        Handles the timeout event for the timer.
        Decreases the time remaining counter until 0 and when it is time,
        cleans up the event filter and closes the window.
        """
        if is_qt_designer():
            return
        # Decrease remaining time
        self._time_rem_ms -= self._timer.interval()
        # Update screen label with new remaining time
        self._update_label()

        if self._time_rem_ms > 0:
            self.start()
            return
        QApplication.instance().removeEventFilter(self)

        if self._window:
            logger.debug('Time to close the window')
            self._window.close()
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Warning)
            msg.setText(
                "Your window was closed due to inactivity for {}.".format(
                    self._get_time_text(self.timeout)))
            msg.setStandardButtons(QMessageBox.Ok)
            msg.setDefaultButton(QMessageBox.Ok)
            msg.exec_()
示例#27
0
class QtPoll(QObject):
    """Polls anything once per frame via an event.

    QtPoll was first created for VispyTiledImageLayer. It polls the visual
    when the camera moves. However, we also want visuals to keep loading
    chunks even when the camera stops. We want the visual to finish up
    anything that was in progress. Before it goes fully idle.

    QtPoll will poll those visuals using a timer. If the visual says the
    event was "handled" it means the visual has more work to do. If that
    happens, QtPoll will continue to poll and draw the visual it until the
    visual is done with the in-progress work.

    An analogy is a snow globe. The user moving the camera shakes up the
    snow globe. We need to keep polling/drawing things until all the snow
    settles down. Then everything will stay completely still until the
    camera is moved again, shaking up the globe once more.

    Parameters
    ----------
    parent : QObject
        Parent Qt object.
    camera : Camera
        The viewer's main camera.
    """

    def __init__(self, parent: QObject):
        super().__init__(parent)

        self.events = EmitterGroup(source=self, auto_connect=True, poll=None)

        self.timer = QTimer()
        self.timer.setInterval(POLL_INTERVAL_MS)
        self.timer.timeout.connect(self._on_timer)
        self._interval = IntervalTimer()

    def on_camera(self, _event) -> None:
        """Called when camera view changes at all."""
        self.wake_up()

    def wake_up(self, _event=None) -> None:
        """Wake up QtPoll so it starts polling."""
        # Start the timer so that we start polling. We used to poll once
        # right away here, but it led to crashes. Because we polled during
        # a paintGL event?
        if not self.timer.isActive():
            self.timer.start()

    def _on_timer(self) -> None:
        """Called when the timer is running.

        The timer is running which means someone we are polling still has
        work to do.
        """
        self._poll()

    def _poll(self) -> None:
        """Called on camera move or with the timer."""
        # Listeners might include visuals and the monitor.
        event = self.events.poll()

        # Listeners will "handle" the event if they need more polling. If
        # no one needs polling, then we can stop the timer.
        if not event.handled:
            self.timer.stop()
            return

        # Someone handled the event, so they want to be polled even if
        # the mouse doesn't move. So start the timer if needed.
        if not self.timer.isActive():
            self.timer.start()

    def closeEvent(self, _event: QEvent) -> None:
        """Cleanup and close.

        Parameters
        ----------
        _event : QEvent
            The close event.
        """
        self.timer.stop()
        self.deleteLater()
示例#28
0
class Job(QObject):
    """Class for managing and monitoring an MFiX job. This class contains
    methods for issuing requests to and handling responses from the MFiX API.

    pidfile: Name of file containing MFiX API connection information.
    """

    # Signals
    sig_job_exit = Signal()
    sig_response = Signal(str, dict)
    sig_error = Signal()
    sig_success = Signal()
    sig_handle_test = Signal(str, dict)
    sig_handle_test_error = Signal(str, QObject)
    sig_update_job_status = Signal()
    sig_change_run_state = Signal()

    def __init__(self, parent, pidfile):
        super(Job, self).__init__()
        self.parent = parent
        self.warning = parent.warning
        self.error = parent.error
        self.mfix_pid = None
        self.job_id = None
        self.mtime = time.time() # Job start time
        self.status = {}
        self.pidfile = pidfile
        self.api = None
        self.api_available = False
        self.pending = True

        self.sig_handle_test.connect(self.slot_handle_test)
        self.sig_handle_test_error.connect(self.slot_handle_test_error)

        self.api = PymfixAPI(self)

        self.mfix_pid = self.api.mfix_pid
        self.job_id = self.api.job_id
        self.run_cmd = self.api.run_cmd

        # test API before starting status loop
        # Timer will be stopped by slot_handle_test
        self.api_test_timer = QTimer()
        self.api_test_timer.setInterval(100)
        self.api_test_timer.timeout.connect(self.test_connection)
        self.api_test_timer.start()
        # don't start status timer here, it is started once API is responsive
        self.api_status_timer = QTimer()
        self.api_status_timer.setInterval(100)
        self.api_status_timer.timeout.connect(self.update_job_status)


    def update_job_status(self):
        self.api.get_status()

    def connect(self):
        self.test_connection()

    def stop_timers(self):
        if self.api_test_timer and self.api_test_timer.isActive():
            self.api_test_timer.stop()
        if self.api_status_timer and self.api_status_timer.isActive():
            self.api_status_timer.stop()

    def cleanup_and_exit(self):
        self.stop_timers()
        self.api_available = False
        self.sig_job_exit.emit()

    def test_connection(self):
        """Test API connection.
        Start API test timer if it is not already running. Check current test
        count and signal job exit if timeout has been exceeded."""
        try:
            self.api.get_status()
        except Exception as e: #
            #print("SIG_ERROR", e) #increments error count
            self.sig_error.emit()

    def slot_handle_test_error(self, req_id, reply):
        # this can be hit multiple times: until JobManager
        # error limit is reached
        self.error('Network error: request id %s' % req_id)


    def slot_handle_test(self, req_id, response):
        """Parse response data from API test call. If response is in JSON
        format and does not contain an error message, then stop API test timer
        and start API status timer.

        If API response includes a permenant failure or is not well formed,
        emit :class:`Job.sig_error` signal.
        """
        # this can be hit multiple times: until we hit JobManager error limit
        try:
            if response.get('internal_error'):
                self.error('Internal error %s' % response)
                self.sig_error.emit()
                return
        except Exception as e:
            # response was not parsable, API is not functional
            self.error('Response format error: %s, response=%s' % (e, response))
            self.sig_error.emit()
            return

        # reset error count and mark API as available
        self.api_available = True
        # stop test timer and start status timer
        self.api_test_timer.stop()
        self.api_status_timer.start()
        self.sig_update_job_status.emit()
        self.sig_change_run_state.emit()

    def reinit(self, project_file_contents, autostart=False):
        """reinitialize job. Sanity checks (paused, gui.unsaved_flag, etc)
        must be handled by caller"""

        data = json.dumps({'mfix_dat': project_file_contents,
                           'autostart': autostart})

        # TODO move unicode conversion into 'post' method
        data = data.encode('utf-8')
        headers = {'content-type': 'application/json'}
        self.api.post('reinit', data=data, headers=headers)
        # TODO:  handlers for reinit success/failure (don't unpause job if reinit failed)
        # TODO restore GUI values to values matching running job if reinit failed (how?)


    def is_paused(self):
        return self.status.get('paused')

    def pause(self):
        self.api.get('pause')

    def unpause(self):
        self.api.get('unpause')

    def stop_mfix(self):
        self.api.get('stop')

    def exit_mfix(self):
        self.api.get('exit')

    def force_stop_mfix(self):
        """Send stop request"""
        if self.job_id:
            # Queued job; TODO qkill
            return

        if not self.mfix_pid:
            # Nothing to kill
            return

        try:
            p = psutil.Process(self.mfix_pid)
        except Exception as e:
            if isinstance(e, psutil.NoSuchProcess):
                return # Nothing to do
            else:
                raise

        is_mpi = False
        kill_pid = self.mfix_pid
        if self.run_cmd and 'mpirun' in self.run_cmd:
            while p:
                is_mpi = any('mpirun' in x for x in p.cmdline()) # cmdline is list of strings
                if is_mpi:
                    kill_pid = p.pid
                    break
                p = p.parent()
            else:
                return

        if is_mpi:
            sig = signal.SIGTERM
        else:
            sig = signal.SIGTERM
        try:
            os.kill(kill_pid, sig)
        except Exception as e:
            self.error("force_kill: %s" % e)


    def handle_stop_mfix(self, req_id, response):
        """Handler for responses to stop requests"""
        self.api_available = False
        self.api_status_timer.stop()
        self.handle_response(req_id, response)
        self.sig_change_run_state.emit()
        self.cleanup_and_exit()


    def is_output_pending(self):
        return not (self.api.stdout_collected and
                    self.api.stderr_collected)
示例#29
0
class WaitingSpinner(QWidget):
    """QtWaitingSpinner is a highly configurable, custom Qt widget
    for showing "waiting" or "loading" spinner icons in Qt applications,
    e.g. the spinners below are all QtWaitingSpinner widgets
    differing only in their configuration

    Args:
        parent (QWidget): Parent widget.
        centerOnParent (bool): Center on parent widget.
        disableParentWhenSpinning (bool): Disable parent widget when spinning.
        modality (Qt.WindowModality): Spinner modality.
        roundness (float): Lines roundness.
        fade (float): Spinner fade.
        lines (int): Lines count.
        line_length (int): Lines length.
        line_width (int): Lines width.
        radius (int): Spinner radius.
        speed (float): Spinner speed.
    """
    def __init__(self,
                 parent,
                 centerOnParent=True,
                 disableParentWhenSpinning=False,
                 modality=Qt.NonModal,
                 roundness=100.,
                 fade=80.,
                 lines=20,
                 line_length=10,
                 line_width=2,
                 radius=10,
                 speed=math.pi / 2):
        super().__init__(parent)

        self._centerOnParent = centerOnParent
        self._disableParentWhenSpinning = disableParentWhenSpinning

        self._color = QColor(0, 0, 0)
        self._roundness = roundness
        self._minimumTrailOpacity = math.pi
        self._trailFadePercentage = fade
        self._oldTrailFadePercentage = fade
        self._revolutionsPerSecond = speed
        self._numberOfLines = lines
        self._lineLength = line_length
        self._lineWidth = line_width
        self._innerRadius = radius
        self._currentCounter = 0

        self._isSpinning = False

        self.fadeInTimer = QTimer()
        self.fadeOutTimer = QTimer()

        self._timer = QTimer(self)
        self._timer.timeout.connect(self.rotate)
        self.updateSize()
        self.updateTimer()
        self.hide()

        self.setWindowModality(modality)
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.setColor(qrainbowstyle.getCurrentPalette().COLOR_ACCENT_4)

    def paintEvent(self, QPaintEvent):
        self.updatePosition()
        painter = QPainter(self)
        painter.fillRect(self.rect(), Qt.transparent)
        painter.setRenderHint(QPainter.Antialiasing, True)

        if self._currentCounter >= self._numberOfLines:
            self._currentCounter = 0

        painter.setPen(Qt.NoPen)
        for i in range(self._numberOfLines):
            painter.save()
            painter.translate(self._innerRadius + self._lineLength,
                              self._innerRadius + self._lineLength)
            rotateAngle = float(360 * i) / float(self._numberOfLines)
            painter.rotate(rotateAngle)
            painter.translate(self._innerRadius, 0)
            distance = self.lineCountDistanceFromPrimary(
                i, self._currentCounter, self._numberOfLines)
            color = self.currentLineColor(distance, self._numberOfLines,
                                          self._trailFadePercentage,
                                          self._minimumTrailOpacity,
                                          self._color)
            painter.setBrush(color)
            painter.drawRoundedRect(
                QRectF(0, -self._lineWidth / 2, self._lineLength,
                       self._lineWidth), self._roundness, self._roundness,
                Qt.RelativeSize)
            painter.restore()

    def changeEvent(self, event: QEvent):
        """Change event handler.

        Args:
            event (QEvent): Event.
        """
        if event.type() == QEvent.StyleChange:
            self.setColor(qrainbowstyle.getCurrentPalette().COLOR_ACCENT_4)

    def start(self):
        self.updatePosition()
        self._isSpinning = True
        self.show()

        if self.parentWidget and self._disableParentWhenSpinning:
            self.parentWidget().setEnabled(False)

        if not self._timer.isActive():
            self._timer.start()
            self._currentCounter = 0

    def stop(self):
        self._isSpinning = False
        self.hide()

        if self.parentWidget() and self._disableParentWhenSpinning:
            self.parentWidget().setEnabled(True)

        if self._timer.isActive():
            self._timer.stop()
            self._currentCounter = 0

    def setNumberOfLines(self, lines):
        self._numberOfLines = lines
        self._currentCounter = 0
        self.updateTimer()

    def setLineLength(self, length):
        self._lineLength = length
        self.updateSize()

    def setLineWidth(self, width):
        self._lineWidth = width
        self.updateSize()

    def setInnerRadius(self, radius):
        self._innerRadius = radius
        self.updateSize()

    def fadeIn(self, time: int = 15):
        self.setTrailFadePercentage(0)
        self.stopFade()
        self.hide()

        self.fadeInTimer = QTimer()
        self.fadeInTimer.timeout.connect(self._on_fadeIn)
        self.fadeInTimer.start(time)

    def _on_fadeIn(self):
        if self.trailFadePercentage < self._oldTrailFadePercentage:
            if self.trailFadePercentage == 0:
                self.show()
            self.setTrailFadePercentage(self.trailFadePercentage + 1)
        else:
            self.fadeInTimer.stop()

    def fadeOut(self, time: int = 15):
        self.show()
        self.stopFade()

        self.fadeOutTimer = QTimer()
        self.fadeOutTimer.timeout.connect(self._on_fadeOut)
        self.fadeOutTimer.start(time)

    def _on_fadeOut(self):
        if self.trailFadePercentage > 0:
            self.setTrailFadePercentage(self.trailFadePercentage - 1)
        else:
            self.hide()
            self.fadeOutTimer.stop()
            self._isFading = False

    def stopFade(self):
        if self.fadeInTimer.isActive():
            self.fadeInTimer.stop()
        if self.fadeOutTimer.isActive():
            self.fadeOutTimer.stop()

    @property
    def color(self):
        return self._color

    @property
    def roundness(self):
        return self._roundness

    @property
    def minimumTrailOpacity(self):
        return self._minimumTrailOpacity

    @property
    def trailFadePercentage(self):
        return self._trailFadePercentage

    @property
    def revolutionsPersSecond(self):
        return self._revolutionsPerSecond

    @property
    def numberOfLines(self):
        return self._numberOfLines

    @property
    def lineLength(self):
        return self._lineLength

    @property
    def lineWidth(self):
        return self._lineWidth

    @property
    def innerRadius(self):
        return self._innerRadius

    @property
    def isSpinning(self):
        return self._isSpinning

    def setRoundness(self, roundness):
        self._roundness = max(0.0, min(100.0, roundness))

    def setColor(self, color=Qt.black):
        self._color = QColor(color)

    def setRevolutionsPerSecond(self, revolutionsPerSecond):
        self._revolutionsPerSecond = revolutionsPerSecond
        self.updateTimer()

    def setTrailFadePercentage(self, trail):
        self._trailFadePercentage = trail

    def setMinimumTrailOpacity(self, minimumTrailOpacity):
        self._minimumTrailOpacity = minimumTrailOpacity

    def rotate(self):
        self._currentCounter += 1
        if self._currentCounter >= self._numberOfLines:
            self._currentCounter = 0
        self.update()

    def updateSize(self):
        size = int((self._innerRadius + self._lineLength) * 2)
        self.setFixedSize(size, size)

    def updateTimer(self):
        self._timer.setInterval(
            int(1000 / (self._numberOfLines * self._revolutionsPerSecond)))

    def updatePosition(self):
        if self.parentWidget() and self._centerOnParent:
            self.move(
                int(self.parentWidget().width() / 2 - self.width() / 2),
                int(self.parentWidget().height() / 2 - self.height() / 2))

    def lineCountDistanceFromPrimary(self, current, primary, totalNrOfLines):
        distance = primary - current
        if distance < 0:
            distance += totalNrOfLines
        return distance

    def currentLineColor(self, countDistance, totalNrOfLines, trailFadePerc,
                         minOpacity, colorinput):
        color = QColor(colorinput)
        if countDistance == 0:
            return color
        minAlphaF = minOpacity / 100.0
        distanceThreshold = int(
            math.ceil((totalNrOfLines - 1) * trailFadePerc / 100.0))
        if countDistance > distanceThreshold:
            color.setAlphaF(minAlphaF)
        else:
            alphaDiff = color.alphaF() - minAlphaF
            gradient = alphaDiff / float(distanceThreshold + 1)
            resultAlpha = color.alphaF() - gradient * countDistance
            # If alpha is out of bounds, clip it.
            resultAlpha = min(1.0, max(0.0, resultAlpha))
            color.setAlphaF(resultAlpha)
        return color