예제 #1
0
파일: mdi.py 프로젝트: giumas/gsdview
class MdiMainWindow(QtWidgets.QMainWindow):
    '''Base class for MDI applications.

    :SIGNALS:

        * :attr:`subWindowClosed`

    '''

    # @TODO: should the subWindowClosed signal be emitted by mdiarea?
    #: SIGNAL: it is emitted when an MDI subwindow is closed
    #:
    #: :C++ signature: `void subWindowClosed()`
    subWindowClosed = QtCore.Signal()

    def __init__(self, parent=None, flags=QtCore.Qt.WindowFlags(0), **kwargs):
        super(MdiMainWindow, self).__init__(parent, flags, **kwargs)

        #: MDI area instance (QMdiArea)
        self.mdiarea = QtWidgets.QMdiArea()
        self.setCentralWidget(self.mdiarea)
        self.mdiarea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.mdiarea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)

        #: sub-windows menu
        self.windowmenu = QtWindowListMenu(self.menuBar())
        self.windowmenu.attachToMdiArea(self.mdiarea)
예제 #2
0
class RubberBandMode(MouseMode):
    '''Mouse mode for rubber band selection.

    :SIGNALS:

        * :attr:`rubberBandSeclection`

    '''

    dragmode = QtWidgets.QGraphicsView.RubberBandDrag
    cursor = QtCore.Qt.CrossCursor
    icon = qtsupport.geticon('area.svg', __name__)
    label = 'Rubber band'
    name = 'rubberband'

    #: SIGNAL: it is emitted when a rectangular area is selected
    #:
    #: :C++ signature: `void rubberBandSeclection(const QRectF&)`
    rubberBandSeclection = QtCore.Signal(QtCore.QRectF)

    def sceneEventFilter(self, obj, event):
        if event.type() == QtCore.QEvent.GraphicsSceneMouseRelease:
            p0 = event.buttonDownScenePos(QtCore.Qt.LeftButton)
            p1 = event.scenePos()
            rect = QtCore.QRectF(p0, p1).normalized()
            self.rubberBandSeclection.emit(rect)
            return True

        #return obj.eventFilter(obj, event)   # @TODO: check
        #return QtWidgets.QGraphicsScene.eventFilter(self, obj, event)
        return False

    def scrollbarEventFilter(self, obj, event):
        # ignore wheel events if some button is pressed
        if ((event.type() == QtCore.QEvent.Wheel) and
                (event.buttons() != QtCore.Qt.NoButton)):
            return True
        else:
            return False
예제 #3
0
class MouseManager(QtCore.QObject):

    #: SIGNAL: it is emitted when the mouse mode is changed
    #:
    #: :C++ signature: `void modeChanged(const QString&)`
    modeChanged = QtCore.Signal(str)

    def __init__(self, parent=None, stdmodes=True, **kwargs):
        QtCore.QObject.__init__(self, parent, **kwargs)

        self._moderegistry = []
        self.actions = QtWidgets.QActionGroup(self)
        self.actions.setExclusive(True)

        if stdmodes:
            self.registerStandardModes()

    def registerStandardModes(self):
        for mode in (PointerMode, ScrollHandMode):  # , RubberBandMode):
            self.addMode(mode)
        if len(self._moderegistry) and not self.actions.checkedAction():
            self.actions.actions()[0].setChecked(True)

    def _newModeAction(self, mode, parent):
        if isinstance(mode.icon, string_types):
            icon = QtGui.QIcon(mode.icon)
        elif isinstance(mode.icon, QtWidgets.QStyle.StandardPixmap):
            style = QtWidgets.QApplication.style()
            icon = style.standardIcon(mode.icon)
        else:
            icon = mode.icon

        action = QtWidgets.QAction(
            icon, self.tr(mode.label), parent,
            statusTip=self.tr(mode.label),
            checkable=True)
        action.triggered.connect(lambda: self.modeChanged.emit(self.mode))
        return action

    def _getMode(self):
        action = self.actions.checkedAction()
        index = self.actions.actions().index(action)
        return self._moderegistry[index].name

    def _setMode(self, name):
        names = self.modes()
        index = names.index(name)
        action = self.actions.actions()[index]
        action.setChecked(True)

    def _delMode(self, name):
        names = [m.name for m in self._moderegistry]
        index = names.index(name)
        action = self.actions.actions()[index]
        self.actions.removeAction(action)
        del self._moderegistry[index]
        #~ if actin.checked() and self._moderegistry:
            #~ self.actions.actions()[0].setChecked(True)

    mode = property(_getMode, _setMode, _delMode, 'mouse mode name')

    def modes(self):
        return tuple(m.name for m in self._moderegistry)

    def addMode(self, mode):
        if isinstance(mode, type):
            mode = mode(self)

        action = self._newModeAction(mode, self.actions)
        self.actions.addAction(action)
        self._moderegistry.append(mode)

    def getModeDescriptor(self, name=None):
        '''Return the mouse mode object'''

        try:
            if name is None:
                action = self.actions.checkedAction()
                index = self.actions.actions().index(action)
            else:
                names = self.modes()
                index = names.index(name)
            return self._moderegistry[index]
        except IndexError:
            # @TODO: check
            #raise ValueError('invalid mde nema: "%s"' % mode)
            return None

    def eventFilter(self, obj, event):
        '''Events dispatcher'''

        return self.getModeDescriptor().eventFilter(obj, event)

    def register(self, obj):
        '''Register a Qt graphics object to be monitored by the mouse manager.

        QGraphicsScene and QGrapgicsViews (and descending classes) objects
        can be registered to be monitored by the mouse manager.

        Scene objects associated to views (passes as argument) are
        automatically registered.

        '''

        obj.installEventFilter(self)

        try:
            obj.verticalScrollBar().installEventFilter(self)
        except AttributeError:
            # it is a QGraphicsScene
            scene = obj
        else:
            scene = obj.scene()

        # Avoid event filter duplication
        scene.removeEventFilter(self)
        scene.installEventFilter(self)

    def unregister(self, obj):
        '''Unregister monitored objects.

        If the object passed as argument is not a registered object
        nothing happens.

        .. note:: this method never tries to unregister scene objects
                  associated to the view passed as argument.

        '''

        obj.removeEventFilter(self)
예제 #4
0
파일: qt.py 프로젝트: giumas/gsdview
class QtOutputPane(QtWidgets.QTextEdit):

    #: SIGNAL: emits a hide request.
    #:
    #: :C++ signature: `void paneHideRequest()`
    paneHideRequest = QtCore.Signal()

    def __init__(self, parent=None, **kwargs):
        super(QtOutputPane, self).__init__(parent, **kwargs)
        self._setupActions()
        self.banner = None

    def _setupActions(self):
        qstype = QtWidgets.QApplication.style()

        # Setup actions
        self.actions = QtWidgets.QActionGroup(self)

        # Save As
        icon = qstype.standardIcon(QtWidgets.QStyle.SP_DialogSaveButton)
        self.actionSaveAs = QtWidgets.QAction(
            icon,
            self.tr('&Save As'),
            self,
            shortcut=self.tr('Ctrl+S'),
            statusTip=self.tr('Save text to file'),
            triggered=self.save)
        self.actions.addAction(self.actionSaveAs)

        # Clear
        icon = QtGui.QIcon(':/trolltech/styles/commonstyle/images/'
                           'standardbutton-clear-32.png')
        self.actionClear = QtWidgets.QAction(
            icon,
            self.tr('&Clear'),
            self,
            shortcut=self.tr('Shift+F5'),
            statusTip=self.tr('Clear the text'),
            triggered=self.clear)
        self.actions.addAction(self.actionClear)

        # Close
        icon = qstype.standardIcon(QtWidgets.QStyle.SP_DialogCloseButton)
        self.actionHide = QtWidgets.QAction(
            icon,
            self.tr('&Hide'),
            self,
            shortcut=self.tr('Ctrl+W'),
            statusTip=self.tr('Hide the text pane'),
            triggered=self.paneHideRequest)
        self.actions.addAction(self.actionHide)

    def contextMenuEvent(self, event):
        menu = QtWidgets.QTextEdit.createStandardContextMenu(self)
        menu.addSeparator()
        menu.addActions(self.actions.actions())
        menu.exec_(event.globalPos())

    def _report(self):
        if callable(self.banner):
            header = self.banner()
        elif self.banner is not None:
            header = self.banner
        else:
            header = '# Output log generated on %s' % time.asctime()
        text = self.toPlainText()
        return '%s\n\n%s' % (header, text)

    # def clear(self): # it is a standard QtWidgets.QTextEdit method

    def save(self):
        '''Save a file.'''

        filter_ = self.tr('Text files (*.txt)')
        filename, _ = QtWidgets.QFileDialog.getSaveFileName(
            self, '', '', filter_)
        if filename:
            text = self._report()
            logfile = open(filename, 'w')
            logfile.write(text)
            logfile.close()
예제 #5
0
파일: qt.py 프로젝트: giumas/gsdview
class QtToolController(QtCore.QObject, BaseToolController):
    '''Qt tool controller.

    :SIGNALS:

        * :attr:`finished`

    :SLOTS:

        * :meth:`stop_tool`
        * :meth:`finalize_run`
        * :meth:`handle_stdout`
        * :meth:`handle_stderr`
        * :meth:`handle_error`

    '''

    _delay_after_stop = 200  # ms

    #: SIGNAL: it is emitted when the processing is finished.
    #:
    #: :param int exitcode:
    #:     the external proces exit code
    #:
    #: :C++ signature: `void finished(int exitCode)`
    finished = QtCore.Signal(int)

    def __init__(self, logger=None, parent=None, **kwargs):
        QtCore.QObject.__init__(self, parent, **kwargs)
        BaseToolController.__init__(self, logger)
        self.subprocess = QtCore.QProcess(parent)
        self.subprocess.setProcessChannelMode(QtCore.QProcess.MergedChannels)

        # connect process handlers and I/O handlers
        self.subprocess.readyReadStandardOutput.connect(self.handle_stdout)
        self.subprocess.readyReadStandardError.connect(self.handle_stderr)
        self.subprocess.error.connect(self.handle_error)
        self.subprocess.finished.connect(self.finalize_run)

    @property
    def isbusy(self):
        '''If True then the controller is already running a subprocess.'''

        return self.subprocess.state() != self.subprocess.NotRunning

    @QtCore.Slot(int, QtCore.QProcess.ExitStatus)
    def finalize_run(self, exitCode=None, exitStatus=None):
        '''Perform finalization actions.

        This method is called when the controlled process terminates
        to perform finalization actions like:

        * read and handle residual data in buffers,
        * flush and close output handlers,
        * close subprocess file descriptors
        * run the "finalize_run_hook" method
        * reset the controller instance

        If one just needs to perfor some additional finalization action
        it should be better to use a custom "finalize_run_hook" instead
        of overriging "finalize_run".

        :C++ signature: `finalize_run(int, QProcess::ExitStatus)`

        '''

        if not self._tool:
            return

        out_encoding = self._tool.output_encoding

        try:
            # retrieve residual data
            # @TODO: check if it is actually needed
            if self._tool.stdout_handler:
                byteArray = self.subprocess.readAllStandardOutput()
                data = byteArray.data().decode(out_encoding)
                self._tool.stdout_handler.feed(data)
            if self._tool.stderr_handler:
                byteArray = self.subprocess.readAllStandardError()
                data = byteArray.data().decode(out_encoding)
                self._tool.stderr_handler.feed(data)

            # close the pipe and wait for the subprocess termination
            self.subprocess.close()
            if self._tool.stdout_handler:
                self._tool.stdout_handler.close()
            if self._tool.stderr_handler:
                self._tool.stderr_handler.close()

            if self._userstop:
                self.logger.info('Execution stopped by the user.')
            elif exitCode != EX_OK:
                msg = ('Process (PID=%d) exited with return code %d.' %
                       (self.subprocess.pid(), self.subprocess.exitCode()))
                self.logger.warning(msg)

            # Call finalize hook if available
            self.finalize_run_hook()
        finally:
            # @TODO: check
            # Protect for unexpected errors in the feed and close methods of
            # the stdout_handler
            self._reset()
            self.finished.emit(exitCode)

    def _reset(self):
        '''Internal reset.'''

        if self.subprocess.state() != self.subprocess.NotRunning:
            self._stop(force=True)
            self.subprocess.waitForFinished()
            stopped = self.subprocess.state() == self.subprocess.NotRunning
            if not stopped:
                self.logger.warning('reset running process (PID=%d)' %
                                    self.subprocess.pid())

        assert self.subprocess.state() == self.subprocess.NotRunning, \
            'the process is still running'
        self.subprocess.setProcessState(self.subprocess.NotRunning)
        # @TODO: check
        self.subprocess.closeReadChannel(QtCore.QProcess.StandardOutput)
        self.subprocess.closeReadChannel(QtCore.QProcess.StandardError)
        self.subprocess.closeWriteChannel()

        super(QtToolController, self)._reset()
        self.subprocess.close()

    @QtCore.Slot()
    def handle_stdout(self):
        '''Handle standard output.

        :C++ signature: `void handle_stdout()`

        '''

        byteArray = self.subprocess.readAllStandardOutput()
        if not byteArray.isEmpty():
            data = byteArray.data().decode(self._tool.output_encoding)
            self._tool.stdout_handler.feed(data)

    @QtCore.Slot()
    def handle_stderr(self):
        '''Handle standard error.

        :C++ signature: `void handle_stderr()`

        '''

        byteArray = self.subprocess.readAllStandardError()
        if not byteArray.isEmpty():
            data = byteArray.data().decode(self._tool.output_encoding)
            self._tool.stderr_handler.feed(data)

    @QtCore.Slot(QtCore.QProcess.ProcessError)
    def handle_error(self, error):
        '''Handle a error in process execution.

        Can be handle different types of errors:

        * starting failed
        * crashing after starts successfully
        * timeout elapsed
        * write error
        * read error
        * unknow error

        :C++ signature: `void handle_error(QProcess::ProcessError)`

        '''

        msg = ''
        level = logging.DEBUG
        if self.subprocess.state() == self.subprocess.NotRunning:
            logging.debug('NotRunning')
            exit_code = self.subprocess.exitCode()
        else:
            exit_code = 0

        if error == QtCore.QProcess.FailedToStart:
            msg = ('The process failed to start. Either the invoked program '
                   'is missing, or you may have insufficient permissions to '
                   'invoke the program.')
            level = logging.ERROR
            # @TODO: check
            #self._reset()
        elif error == QtCore.QProcess.Crashed:
            if not self._userstop and self.subprocess.exitCode() == EX_OK:
                msg = ('The process crashed some time after starting '
                       'successfully.')
                level = logging.ERROR
        elif error == QtCore.QProcess.Timedout:
            msg = ('The last waitFor...() function timed out. The state of '
                   'QProcess is unchanged, and you can try calling '
                   'waitFor...() again.')
            level = logging.DEBUG
        elif error == QtCore.QProcess.WriteError:
            msg = ('An error occurred when attempting to write to the process.'
                   ' For example, the process may not be running, or it may '
                   'have closed its input channel.')
            #level = logging.ERROR # @TODO: check
        elif error == QtCore.QProcess.ReadError:
            msg = ('An error occurred when attempting to read from the '
                   'process. For example, the process may not be running.')
            #level = logging.ERROR # @TODO: check
        elif error == QtCore.QProcess.UnknownError:
            msg = ('An unknown error occurred. This is the default return '
                   'value of error().')
            #level = logging.ERROR # @TODO: check

        if msg:
            self.logger.log(level, msg)

        self.finished.emit(exit_code)

    #QtCore.Slot() # @TODO: check how to handle varargs
    def run_tool(self, tool, *args, **kwargs):
        '''Run an external tool in controlled way.

        The output of the child process is handled by the controller
        and, optionally, notifications can be achieved at sub-process
        termination.

        '''

        assert self.subprocess.state() == self.subprocess.NotRunning
        self.reset()
        self._tool = tool

        if self._tool.stdout_handler:
            self._tool.stdout_handler.reset()
        if self._tool.stderr_handler:
            self._tool.stderr_handler.reset()

        cmd = self._tool.cmdline(*args, **kwargs)
        self.prerun_hook(cmd)
        cmd = ' '.join(cmd)

        if self._tool.env:
            qenv = QtCore.QProcessEnvironment()
            for key, val in self._tool.env.items():
                qenv.insert(key, str(val))
            self.subprocess.setProcessEnvironment(qenv)

        if self._tool.cwd:
            self.subprocess.setWorkingDirectory(self._tool.cwd)

        self.logger.debug('"shell" flag set to %s.' % self._tool.shell)
        self.logger.debug('Starting: %s' % cmd)
        self.subprocess.start(cmd)
        self.subprocess.closeWriteChannel()

    def _stop(self, force=True):
        if self.subprocess.state() == self.subprocess.NotRunning:
            return
        self.subprocess.terminate()
        self.subprocess.waitForFinished(self._delay_after_stop)
        stopped = self.subprocess.state() == self.subprocess.NotRunning
        if not stopped and force:
            self.logger.info('Force process termination (PID=%d).' %
                             self.subprocess.pid())
            self.subprocess.kill()

    @QtCore.Slot()
    @QtCore.Slot(bool)
    def stop_tool(self, force=True):
        '''Stop the execution of controlled subprocess.

        When this method is invoked the controller instance is always
        reset even if the controller is unable to stop the subprocess.

        When possible the controller try to kill the subprocess in a
        polite way.  If this fails it also tryes brute killing by
        default (force=True).  This behaviour can be controlled using
        the `force` parameter.

        :C++ signature: `void stop_tool(bool)`

        '''

        if self._userstop:
            return

        if self.subprocess.state() != self.subprocess.NotRunning:
            self.logger.debug('Execution stopped by the user.')
            self._userstop = True
            self._stop(force)
            self.subprocess.waitForFinished()
            stopped = self.subprocess.state() == self.subprocess.NotRunning
            if not stopped:
                msg = ('Unable to stop the sub-process (PID=%d).' %
                       self.subprocess.pid())
                self.logger.warning(msg)
예제 #6
0
파일: qt.py 프로젝트: giumas/gsdview
class QtOutputHandler(QtCore.QObject, BaseOutputHandler):
    '''Qt Output Handler.

    :SIGNALS:

        * :attr:`pulse`
        * :attr:`percentageChanged`

    '''

    _statusbar_timeout = 2000  # ms

    #: SIGNAL: it is emitted to signal some kind of activity of the external
    #: process
    #:
    #: :param str text:
    #:     an optional text describing the kind activity of the external
    #:     process
    #:
    #: :C++ signature: `void pulse(QString)`
    pulse = QtCore.Signal([], [str])

    #: SIGNAL: it is emitted when the progress percentage changes
    #:
    #: :param float percentage:
    #:     the new completion percentage [0, 100]
    #:
    #: :C++ signature: `void percentageChanged(float)`
    percentageChanged = QtCore.Signal([int], [])

    def __init__(self,
                 logger=None,
                 statusbar=None,
                 progressbar=None,
                 blinker=None,
                 parent=None,
                 **kwargs):
        QtCore.QObject.__init__(self, parent, **kwargs)
        BaseOutputHandler.__init__(self, logger)

        self.statusbar = statusbar
        if self.statusbar:
            if blinker is None:
                blinker = QtBlinker()
                statusbar.addPermanentWidget(blinker)
                blinker.hide()
            self.pulse.connect(blinker.show)
            self.pulse.connect(blinker.pulse)
            self.pulse[str].connect(lambda text: statusbar.showMessage(
                text, self._statusbar_timeout))

            if progressbar is None:
                progressbar = QtWidgets.QProgressBar(self.statusbar)
                progressbar.setTextVisible(True)
                statusbar.addPermanentWidget(progressbar)  # , 1) # stretch=1
                progressbar.hide()
            self.progressbar = progressbar
            #self.percentageChanged[()].connect(progressbar.show)
            self.percentageChanged.connect(progressbar.show)
            self.percentageChanged.connect(progressbar.setValue)

        self.progressbar = progressbar
        self.blinker = blinker

    def feed(self, data):
        '''Feed some data to the parser.

        It is processed insofar as it consists of complete elements;
        incomplete data is buffered until more data is fed or close()
        is called.

        '''

        if self.blinker:
            self.blinker.show()
        super(QtOutputHandler, self).feed(data)

    def close(self):
        '''Reset the instance.'''

        if self.statusbar:
            self.statusbar.clearMessage()
        super(QtOutputHandler, self).close()

    def reset(self):
        '''Reset the handler instance.

        Loses all unprocessed data. This is called implicitly at
        instantiation time.

        '''

        super(QtOutputHandler, self).reset()
        if self.progressbar:
            self.progressbar.setRange(0, 100)
            self.progressbar.reset()
            self.progressbar.hide()
        if self.blinker:
            self.blinker.reset()
            self.blinker.hide()

    def handle_progress(self, data):
        '''Handle progress data.

        :param data:
            a list containing an item for each named group in the
            "progress" regular expression: (pulse, percentage, text)
            for the default implementation.
            Each item can be None.

        '''

        #pulse = data.get('pulse')
        percentage = data.get('percentage')
        text = data.get('text')

        self.pulse.emit()
        if text:
            self.pulse[str].emit(text)
        if percentage is not None:
            self.percentageChanged.emit(int(percentage))
예제 #7
0
파일: widgets.py 프로젝트: giumas/gsdview
class StretchDialog(QtWidgets.QDialog, StretchDialogBase):
    '''Stretch dialog.

    :SIGNALS:

        * :attr:`valueChanged`

    '''

    #: SIGNAL: it is emitted when the stretch value changes
    #:
    #: :C++ signature: `void valueChanged()`
    valueChanged = QtCore.Signal()

    def __init__(self, parent=None, flags=QtCore.Qt.WindowFlags(0),
                 floatmode=True, **kwargs):
        super(StretchDialog, self).__init__(parent, flags, **kwargs)
        self.setupUi(self)

        self.stretchwidget = StretchWidget(self, floatmode=floatmode)
        self.mainLayout.insertWidget(0, self.stretchwidget)

        if not self.checkBox.isChecked():
            self.setAdvanced(False)

        self.state = None
        self.saveState()

        self.checkBox.toggled.connect(self.setAdvanced)
        self.buttonBox.button(
            QtWidgets.QDialogButtonBox.Reset).clicked.connect(self.reset)

        self.stretchwidget.valueChanged.connect(self.valueChanged)

        #~ self.stretchwidget.lowSpinBox.valueChanged.connect(
        #~     self.valueChanged)
        #~ self.stretchwidget.highSpinBox.valueChanged.connect(
        #~     self.valueChanged)

    def advanced(self):
        return self.stretchwidget.lowSpinBox.isVisible()

    @QtCore.Slot()
    @QtCore.Slot(bool)
    def setAdvanced(self, advmode=True):
        self.stretchwidget.lowSpinBox.setVisible(advmode)
        self.stretchwidget.lowSlider.setVisible(advmode)

    @QtCore.Property(bool)
    def floatmode(self):
        return self.stretchwidget.floatmode

    @floatmode.setter
    def floatmode(self, mode):
        self.stretchwidget.floatmode = mode

    def saveState(self):
        self.state = self.stretchwidget.state()

    @QtCore.Slot()
    def reset(self, d=None):
        if d is None:
            d = self.state
        if d is None:
            return
        try:
            self.stretchwidget.setState(d)
        except KeyError as e:
            _log.info('unable to set state: %s', e)

    def values(self):
        return self.stretchwidget.values()
예제 #8
0
파일: widgets.py 프로젝트: giumas/gsdview
class StretchWidget(QtWidgets.QWidget, StretchWidgetBase):
    '''Stretch widget.

    :SIGNALS:

        * :attr:`valueChanged`

    '''

    #: SIGNAL: it is emitted when the stretch value changes
    #:
    #: :C++ signature: `void valueChanged()`
    valueChanged = QtCore.Signal()

    #: SIGNAL: it is emitted when the stretch range changes
    #:
    #: :C++ signature: `void rangeChanged(int, int)`
    #rangeChanged = QtCore.Signal(int, int)

    def __init__(self, parent=None, flags=QtCore.Qt.WindowFlags(0),
                 floatmode=True, **kwargs):
        super(StretchWidget, self).__init__(parent, flags, **kwargs)
        self.setupUi(self)
        self._floatmode = False
        self._kslider = self._computeKSlider()

        self._connectSignals()

        self.floatmode = floatmode

    def _connectSignals(self):
        self.lowSlider.valueChanged.connect(self._onLowSliderChanged)
        self.highSlider.valueChanged.connect(self._onHighSliderChanged)

        #self.rangeChanged.connect(self._onRangeChanged)

        self.minSpinBox.valueChanged[float].connect(self._onMinimumChanged)
        self.maxSpinBox.valueChanged[float].connect(self._onMaximumChanged)
        self.lowSpinBox.valueChanged[float].connect(self._onLowSpinBoxChanged)
        self.highSpinBox.valueChanged[float].connect(self._onHighSpinBoxChanged)

        self.lowSpinBox.valueChanged.connect(self.valueChanged)
        self.highSpinBox.valueChanged.connect(self.valueChanged)

    def _disconnectSignals(self):
        self.lowSlider.valueChanged.disconnect(self._onLowSliderChanged)
        self.highSlider.valueChanged.disconnect(self._onHighSliderChanged)

        #self.rangeChanged.disconnect(self._onRangeChanged)

        self.minSpinBox.valueChanged[float].disconnect(self._onMinimumChanged)
        self.maxSpinBox.valueChanged[float].disconnect(self._onMaximumChanged)
        self.lowSpinBox.valueChanged[float].disconnect(self._onLowSpinBoxChanged)
        self.highSpinBox.valueChanged[float].disconnect(self._onHighSpinBoxChanged)

        self.lowSpinBox.valueChanged.disconnect(self.valueChanged)
        self.highSpinBox.valueChanged.disconnect(self.valueChanged)

    @contextlib.contextmanager
    def _disconnectedSignals(self):
        self._disconnectSignals()
        yield
        self._connectSignals()

    @QtCore.Property(bool)
    def floatmode(self):
        return self._floatmode

    @floatmode.setter
    def floatmode(self, floatmode=True):
        '''Set the stretch widget in floating point mode.'''

        floatmode = bool(floatmode)
        if floatmode == self._floatmode:
            return

        vmin = self.minSpinBox.value()
        vmax = self.maxSpinBox.value()

        self._floatmode = floatmode
        if self._floatmode:
            self.lowSlider.setRange(0, 1000)
            self.highSlider.setRange(0, 1000)
        else:
            self.lowSlider.setRange(vmin, vmax)
            self.highSlider.setRange(vmin, vmax)

        self.setRange(vmin, vmax)

    def setRange(self, vmin, vmax):
        if vmin >= vmax:
            raise ValueError('vmin (%f) >= vmax (%f)' % (vmin, vmax))

        low = self.lowSpinBox.value()
        if low < vmin:
            low = vmin
        elif low > vmax:
            low = vmax
        high = self.highSpinBox.value()
        if high < vmin:
            high = vmin
        elif high > vmax:
            high = vmax

        with self._disconnectedSignals():
            self.minSpinBox.setValue(vmin)
            self.maxSpinBox.setValue(vmax)

            self._kslider = self._computeKSlider(vmin, vmax)

            if not self._floatmode:
                self.lowSlider.setRange(vmin, vmax)
                self.highSlider.setRange(vmin, vmax)

                self.lowSlider.setValue(low)
                self.highSlider.setValue(high)
            else:
                self.lowSlider.setValue(self._pos(low))
                self.highSlider.setValue(self._pos(high))

        self._updateSteps(self.maximum(), self.minimum())

    def _computeSteps(self, vmin, vmax):
        vmax = max(abs(vmax), abs(vmin))
        if vmax == 0:
            # @TODO: check
            return 1, 10

        if not self._floatmode and vmax < 32:
            return 1, min(5, vmax)

        kmax = np.log10(vmax)
        singleStep = 10**(np.round(kmax) - 2)
        pageStep = 10 * singleStep

        return singleStep, pageStep

    def _updateSteps(self, vmin, vmax):
        singleStep, pageStep = self._computeSteps(vmin, vmax)

        self.lowSpinBox.setSingleStep(singleStep)
        self.highSpinBox.setSingleStep(singleStep)

        if self._floatmode:
            decimals = 7
            self.lowSpinBox.setDecimals(decimals)
            self.highSpinBox.setDecimals(decimals)

            step = np.round(singleStep * self._kslider)
            self.lowSlider.setSingleStep(step)
            self.highSlider.setSingleStep(step)

            step = np.round(pageStep * self._kslider)
            self.lowSlider.setSingleStep(step)
            self.highSlider.setSingleStep(step)

        else:
            self.lowSpinBox.setDecimals(0)
            self.highSpinBox.setDecimals(0)

            self.lowSlider.setSingleStep(singleStep)
            self.highSlider.setSingleStep(singleStep)

            self.lowSlider.setSingleStep(pageStep)
            self.highSlider.setSingleStep(pageStep)

        # @TODO: update all steps
        self.maxSpinBox.setSingleStep(pageStep)
        if self.minimum() != 0:
            step = 10**(np.round(np.log10(abs(self.minimum()))))
        else:
            step = singleStep
        self.minSpinBox.setSingleStep(step)

    def _computeKSlider(self, vmin=None, vmax=None):
        if not self._floatmode:
            return 1

        if vmin is None:
            vmin = self.lowSlider.minimum()

        if vmax is None:
            vmax = self.highSlider.maximum()

        vrange = float(self.maxSpinBox.value() - self.minSpinBox.value())
        srange = float(vmax - vmin)

        if srange == 0:
            return 0
        else:
            return np.round(vrange / srange)

    def _pos(self, value):
        if self._kslider == 0:
            return self.minSpinBox.value()

        return (value - self.minSpinBox.value()) / self._kslider

    def _value(self, pos):
        return self.minSpinBox.value() + self._kslider * pos

    def low(self):
        return self.lowSpinBox.value()

    @QtCore.Slot(float)
    def setLow(self, value):
        self.lowSpinBox.setValue(value)
        if self.lowSpinBox.value() > self.highSpinBox.value():
            self.highSpinBox.setValue(self.lowSpinBox.value())

    def high(self):
        return self.highSpinBox.value()

    @QtCore.Slot(float)
    def setHigh(self, value):
        self.highSpinBox.setValue(value)
        if self.lowSpinBox.value() > self.highSpinBox.value():
            self.lowSpinBox.setValue(self.highSpinBox.value())

    @QtCore.Slot(float)
    def _onLowSpinBoxChanged(self, value):
        if self.floatmode:
            pos = self._pos(value)
        else:
            pos = value
        self.lowSlider.setValue(pos)

    @QtCore.Slot(float)
    def _onHighSpinBoxChanged(self, value):
        if self.floatmode:
            pos = self._pos(value)
        else:
            pos = value
        self.highSlider.setValue(pos)

    @QtCore.Slot(int)
    def _onLowSliderChanged(self, value):
        if value > self.highSlider.value():
            self.highSlider.setValue(value)

        if self.floatmode:
            value = self._value(value)
        self.lowSpinBox.setValue(value)

    @QtCore.Slot(int)
    def _onHighSliderChanged(self, value):
        if value < self.lowSlider.value():
            self.lowSlider.setValue(value)

        if self.floatmode:
            value = self._value(value)
        self.highSpinBox.setValue(value)

    def values(self):
        return self.low(), self.high()

    def singleStep(self):
        #assert self.lowSlider.singleStep() == self.highSlider.singleStep()
        #assert self.lowSlider.singleStep() == self.lowSpinBox.singleStep()
        #assert self.lowSlider.singleStep() == self.highSpinBox.singleStep()
        return self.highSpinBox.singleStep()

    def setSingleStep(self, step):
        k = self._kslider if self._kslider else 1
        self.lowSlider.setSingleStep(step / k)
        self.highSlider.setSingleStep(step / k)
        self.lowSpinBox.setSingleStep(step)
        self.highSpinBox.setSingleStep(step)

    def pageStep(self):
        #assert self.lowSlider.pageStep() == self.highSlider.pageStep()
        return self.highSlider.pageStep() * self._kslider

    def setPageStep(self, step):
        k = self._kslider if self._kslider else 1
        self.lowSlider.setPageStep(step / k)
        self.highSlider.setPageStep(step / k)

    def minimum(self):
        return self.minSpinBox.value()

    def setMinimum(self, value):
        if value >= self.maximum():
            raise ValueError("can't set a minimum value greater that maximum")
        self.minSpinBox.setValue(value)

    @QtCore.Slot(float)
    def _onMinimumChanged(self, value):
        if value >= self.maxSpinBox.value():
            value = self.maxSpinBox.value() - self.singleStep()
            self.minSpinBox.setValue(value)
            return

        stretch_changed = False

        with self._disconnectedSignals():
            self.maxSpinBox.setMinimum(value)
            self.lowSpinBox.setMinimum(value)
            self.highSpinBox.setMinimum(value)

            if self.floatmode:
                self._kslider = self._computeKSlider()

                vmin = self._pos(self.lowSpinBox.value())
                vmax = self._pos(self.highSpinBox.value())

                self.lowSlider.setValue(vmin)
                self.highSlider.setValue(vmax)
            else:
                self.lowSlider.setMinimum(value)
                self.highSlider.setMinimum(value)

            if self.lowSpinBox.value() < self.minSpinBox.value():
                self.lowSpinBox.setValue(self.minSpinBox.value())
                stretch_changed = True

            if self.highSpinBox.value() < self.minSpinBox.value():
                high = self.minSpinBox.value() + self.lowSpinBox.singleStep()
                high = min(high, self.maxSpinBox.value())
                self.highSpinBox.setValue(high)
                stretch_changed = True

        self._updateSteps(self.minimum(), self.maximum())

        if stretch_changed:
            self.valueChanged.emit()

    def maximum(self):
        return self.maxSpinBox.value()

    def setMaximum(self, value):
        if value <= self.minimum():
            raise ValueError("can't set a maximum value smaller that minimum")
        self.maxSpinBox.setValue(value)

    @QtCore.Slot(float)
    def _onMaximumChanged(self, value):
        if value <= self.minSpinBox.value():
            value = self.minSpinBox.value() + self.singleStep()
            self.maxSpinBox.setValue(value)
            return

        stretch_changed = False

        with self._disconnectedSignals():
            self.minSpinBox.setMaximum(value)
            self.lowSpinBox.setMaximum(value)
            self.highSpinBox.setMaximum(value)

            if self.floatmode:
                self._kslider = self._computeKSlider()

                vmin = self._pos(self.lowSpinBox.value())
                vmax = self._pos(self.highSpinBox.value())

                self.lowSlider.setValue(vmin)
                self.highSlider.setValue(vmax)
            else:
                self.lowSlider.setMaximum(value)
                self.highSlider.setMaximum(value)

            if self.lowSpinBox.value() > self.maxSpinBox.value():
                low = self.maxSpinBox.value() + self.highSpinBox.singleStep()
                low = max(low, self.minSpinBox.value())
                self.lowSpinBox.setValue(low)
                stretch_changed = True

            if self.highSpinBox.value() > self.maxSpinBox.value():
                self.highSpinBox.setValue(self.maxSpinBox.value())
                stretch_changed = True

        self._updateSteps(self.minimum(), self.maximum())

        if stretch_changed:
            self.valueChanged.emit()

    def setState(self, d):
        self.minSpinBox.setMinimum(d['minSpinBox.minimum'])
        self.minSpinBox.setMaximum(d['minSpinBox.maximum'])
        self.minSpinBox.setSingleStep(d['minSpinBox.singleStep'])
        self.maxSpinBox.setMinimum(d['maxSpinBox.minimum'])
        self.maxSpinBox.setMaximum(d['maxSpinBox.maximum'])
        self.maxSpinBox.setSingleStep(d['maxSpinBox.singleStep'])

        self.floatmode = d['floatmode']
        self.setMinimum(d['minimum'])
        self.setMaximum(d['maximum'])
        self.setLow(d['low'])
        self.setHigh(d['high'])
        self.setSingleStep(d['singleStep'])
        self.setPageStep(d['pageStep'])

    def state(self, d=None):
        if d is None:
            d = dict()

        d['floatmode'] = self.floatmode
        d['minimum'] = self.minimum()
        d['maximum'] = self.maximum()
        d['low'] = self.low()
        d['high'] = self.high()
        d['singleStep'] = self.singleStep()
        d['pageStep'] = self.pageStep()

        d['minSpinBox.minimum'] = self.minSpinBox.minimum()
        d['minSpinBox.maximum'] = self.minSpinBox.maximum()
        d['minSpinBox.singleStep'] = self.minSpinBox.singleStep()
        d['maxSpinBox.minimum'] = self.maxSpinBox.minimum()
        d['maxSpinBox.maximum'] = self.maxSpinBox.maximum()
        d['maxSpinBox.singleStep'] = self.maxSpinBox.singleStep()

        return d
예제 #9
0
class NavigationGraphicsView(QtWidgets.QGraphicsView):
    '''Graphics view for dataset navigation.

    The view usually displays an auto-scalled low resolution overview
    of the scene with a red box indicating the area currently displayed
    in the high resolution view.

    :SIGNALS:

        * :attr:`mousePressed`
        * :attr:`mouseMoved`

    '''

    BOXCOLOR = QtGui.QColor(QtCore.Qt.red)
    BOXWIDTH = 50

    #: SIGNAL: it is emitted when a mouse button is presses on the view
    #:
    #: :param point:
    #:     the scene position
    #: :param mousebutton:
    #:     the ID of the pressed button
    #: :param dragmode:
    #:     current darg mode
    #:
    #: :C++ signature: `void mousePressed(QPointF, Qt::MouseButtons,
    #:                                    QGraphicsView::DragMode)`
    mousePressed = QtCore.Signal(QtCore.QPointF, QtCore.Qt.MouseButtons,
                                 QtWidgets.QGraphicsView.DragMode)

    #: SIGNAL: it is emitted when the mouse is moved on the view
    #:
    #: :param point:
    #:     the scene position
    #: :param mousebutton:
    #:     the ID of the pressed button
    #: :param dragmode:
    #:     current darg mode
    #:
    #: :C++ signature: `void mouseMoved(QPointF, Qt::MouseButtons,
    #:                                    QGraphicsView::DragMode)`
    mouseMoved = QtCore.Signal(QtCore.QPointF, QtCore.Qt.MouseButtons,
                               QtWidgets.QGraphicsView.DragMode)

    def __init__(self, parent=None, **kwargs):
        super(NavigationGraphicsView, self).__init__(parent, **kwargs)
        self._viewbox = None
        self._autoscale = True
        self.setMouseTracking(True)

        # default pen
        self._pen = QtGui.QPen()
        self._pen.setColor(self.BOXCOLOR)
        self._pen.setWidth(self.BOXWIDTH)

    @property
    def viewbox(self):
        '''Viewport box in scene coordinates'''
        return self._viewbox

    @viewbox.setter
    def viewbox(self, box):
        '''Set the viewport box in scene coordinates'''
        assert isinstance(box, (QtCore.QRect, QtCore.QRectF))
        self._viewbox = box
        if self.isVisible():
            # @WARNING: calling "update" on the scene causes a repaint of
            #           *all* attached views and for each view the entire
            #           exposedRect is updated.
            #           Using QGraphicsView.invalidateScene with the
            #           QtWidgets.QGraphicsScene.ForegroundLayer parameter
            #           should be faster and repaint only one layer of the
            #           current view.

            # @TODO: check
            #self.invalidateScene(self.sceneRect(),
            #                     QtWidgets.QGraphicsScene.ForegroundLayer)
            self.scene().update()

    def drawForeground(self, painter, rect):
        if not self.viewbox:
            return

        pen = painter.pen()
        try:
            box = self.viewbox.intersected(self.sceneRect())
            painter.setPen(self._pen)
            painter.drawRect(box)
            #painter.drawConvexPolygon(self.viewbox) #@TODO: check
        finally:
            painter.setPen(pen)

    def fitInView(self, rect=None, aspectRatioMode=QtCore.Qt.KeepAspectRatio):
        if not rect:
            scene = self.scene()
            if scene:
                rect = scene.sceneRect()
            else:
                return
        QtWidgets.QGraphicsView.fitInView(self, rect, aspectRatioMode)

    @property
    def autoscale(self):
        return self._autoscale

    @autoscale.setter
    def autoscale(self, flag):
        self._autoscale = bool(flag)
        if self._autoscale:
            self.fitInView()
        else:
            self.setTransform(QtGui.QTransform())
            self.update()

    def resizeEvent(self, event):
        if self.autoscale:
            self.fitInView()
        return QtWidgets.QGraphicsView.resizeEvent(self, event)

    # @TODO: use event filters
    def mousePressEvent(self, event):
        pos = self.mapToScene(event.pos())
        self.mousePressed.emit(pos, event.buttons(), self.dragMode())
        return QtWidgets.QGraphicsView.mousePressEvent(self, event)

    def mouseMoveEvent(self, event):
        pos = self.mapToScene(event.pos())
        self.mouseMoved.emit(pos, event.buttons(), self.dragMode())
        return QtWidgets.QGraphicsView.mouseMoveEvent(self, event)
예제 #10
0
class PreferencesDialog(QtWidgets.QDialog, PreferencesDialogBase):
    '''Extendible preferences dialogg for GSDView.

    :SIGNALS:

    * :attr:`apply`

    '''

    #: SIGNAL: it is emitted when modifications are applied
    #:
    #: :C++ signature: `void apply()`
    apply = QtCore.Signal()

    # @TODO: also look at
    # .../python-qt4-doc/examples/tools/settingseditor/settingseditor.py

    def __init__(self, parent=None, flags=QtCore.Qt.WindowFlags(0), **kwargs):
        super(PreferencesDialog, self).__init__(parent, flags, **kwargs)
        self.setupUi(self)

        self.setWindowIcon(qtsupport.geticon('preferences.svg', __name__))

        # remove empty page
        page = self.stackedWidget.widget(0)
        self.stackedWidget.removeWidget(page)

        # app pages
        icon = qtsupport.geticon('preferences.svg', __name__)
        self.addPage(GeneralPreferencesPage(), icon, self.tr('General'))

        #~ icon = qt4support.geticon('harddisk.svg', __name__)
        #~ self.addPage(CachePreferencesPage(), icon, self.tr('Cache'))

        assert self.listWidget.count() == self.stackedWidget.count()

        self.listWidget.currentItemChanged.connect(self.changePage)

        applybutton = self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply)
        applybutton.clicked.connect(self.apply)

    # @TODO: check
    #@QtCore.Slot(QtWidgets.QListWidgetItem, QtWidgets.QListWidgetItem)
    def changePage(self, current, previous):
        if not current:
            current = previous

        self.stackedWidget.setCurrentIndex(self.listWidget.row(current))

    def addPage(self, page, icon, label=None):
        if not (hasattr(page, 'load') and hasattr(page, 'save')):
            raise TypeError('preference pages must have both "load" and '
                            '"save" methods')
        index = self.stackedWidget.addWidget(page)
        item = QtWidgets.QListWidgetItem(icon, label)
        self.listWidget.addItem(item)
        assert self.listWidget.row(item) == index

    def removePageIndex(self, index):
        if 0 <= index < self.stackedWidget.count():
            page = self.stackedWidget.widget(index)
            self.stackedWidget.removeWidget(page)
            self.listWidget.model().removeRow(index)

    def removePage(self, page):
        index = self.stackedWidget.indexOf(page)
        if 0 <= index < self.stackedWidget.count():
            self.stackedWidget.removeWidget(page)
            self.listWidget.model().removeRow(index)

    def load(self, settings):
        for index in range(self.stackedWidget.count()):
            page = self.stackedWidget.widget(index)
            page.load(settings)

    def save(self, settings):
        for index in range(self.stackedWidget.count()):
            page = self.stackedWidget.widget(index)
            page.save(settings)
예제 #11
0
class GraphicsViewMonitor(QtCore.QObject):
    '''Emit signals when a registered graphics view changes status.

    :SIGNALS:

        * :attr:`leave`
        * :attr:`scrolled`
        * :attr:`resized`
        * :attr:`viewportResized`
        * :attr:`mouseMoved`

    '''

    ##: SIGNAL: it is emitted when the mouse pointer enterss the scene
    ##:
    ##: :C++ signature: `void enter(QGraphicsView*)`
    ###enter = QtCore.Signal(QtWidgets.QGraphicsScene)
    ##enter = QtCore.Signal(QtCore.QObject) # @TODO: check

    #: SIGNAL: it is emitted when the mouse pointer leaves the scene
    #:
    #: :C++ signature: `void leave(QGraphicsView*)`
    leave = QtCore.Signal(QtWidgets.QGraphicsScene)

    #: SIGNAL: it is emitted when a graphics view is scrolled
    #:
    #: :C++ signature: `void scrolled(QGraphicsView*)`
    scrolled = QtCore.Signal(QtWidgets.QGraphicsView)

    #: SIGNAL: it is emitted when the graphicsview window is resized
    #:
    #: :C++ signature: `void resized(QGraphicsView*, QSize)`
    resized = QtCore.Signal(QtWidgets.QGraphicsView, QtCore.QSize)

    # @TODO: explain difference with previous
    #: SIGNAL:
    #:
    #: :C++ signature: `void viewportResized(QGraphicsView*)`
    viewportResized = QtCore.Signal(QtWidgets.QGraphicsView)

    #: SIGNAL: it is emitted when the mouse pointer is moved on the scene
    #:
    #: :C++ signature: `void mouseMoved(QtWidgets.QGraphicsScene,
    #:                                  QtCore.QPointF,
    #:                                  QtCore.Qt.MuseButtons)`
    mouseMoved = QtCore.Signal(QtWidgets.QGraphicsScene, QtCore.QPointF,
                               QtCore.Qt.MouseButtons)

    ##: SIGNAL:
    ##:
    ##: :C++ signature: `newPos(QtCore.QObject, QPoint)`
    ###newPos = QtCore.Signal(QtWidgets.QGraphicsView, QtCore.QPoint)
    ##newPos = QtCore.Signal(QtCore.QObject, QtCore.QPoint) # @TODO: check

    # @TODO: use signal mappers
    #~ def __init__(self, parent=None, **kwargs):
    #~ super(GraphicsViewMonitor, self).__init__(parent, **kwargs)

    #~ self.mappers = {}
    #~ self.mappers['scroll'] = QtCore.QSignalMapper(self,
    #~ mapped=self.scrolled)
    #~ #self.mappers['scroll'].mapped.connect(self.scrolled)

    #~ self.mappers['scale'] = QtCore.QSignalMapper(
    #~     self, mapped=self.scaled)
    #~ #self.mappers['scale'].mapped.connect(self.scaled)

    #~ def register(self, graphicsview):
    #~ graphicsview.horizontalScrollBar().valueChanged.connect(
    #~ self.mappers['scroll'].map)
    #~ graphicsview.verticalScrollBar().valueChanged.connect(
    #~ self.mappers['scroll'].map)
    #~ self.mappers['scroll'].setMapping(graphicsview, graphicsview)
    #~ graphicsview.viewportResized.connect(self.mappers['scale'].map)
    #~ self.mappers['scale'].setMapping(graphicsview, graphicsview)

    def register(self, graphicsview):
        graphicsview.horizontalScrollBar().valueChanged.connect(
            lambda: self.scrolled.emit(graphicsview))
        graphicsview.verticalScrollBar().valueChanged.connect(
            lambda: self.scrolled.emit(graphicsview))
        graphicsview.horizontalScrollBar().rangeChanged.connect(
            lambda: self.viewportResized.emit(graphicsview))
        graphicsview.verticalScrollBar().rangeChanged.connect(
            lambda: self.viewportResized.emit(graphicsview))
        graphicsview.installEventFilter(self)

        # Many views can refer to the same scene so before installing a new
        # event filter old ones are removed
        scene = graphicsview.scene()
        if scene:
            scene.removeEventFilter(self)
            scene.installEventFilter(self)

    def eventFilter(self, obj, event):
        # @TODO: use an event map (??)
        if event.type() == QtCore.QEvent.Resize:
            assert isinstance(obj, QtWidgets.QGraphicsView)
            self.resized.emit(obj, event.size())
        elif event.type() == QtCore.QEvent.GraphicsSceneMouseMove:
            assert isinstance(obj, QtWidgets.QGraphicsScene)
            self.mouseMoved.emit(obj, event.scenePos(), event.buttons())
        elif event.type() == QtCore.QEvent.Leave:
            # Discard events from graphicsviews
            if isinstance(obj, QtWidgets.QGraphicsScene):
                self.leave.emit(obj)

        return obj.eventFilter(obj, event)