예제 #1
0
class BrushingInterpreter( QObject ):
    # states
    FINAL           = 0
    DEFAULT_MODE    = 1

    # FIXME: This state isn't really needed, now that we use a QTimer to manage the double-click case.
    #        (The state machine should be rewritten.)
    MAYBE_DRAW_MODE = 2 #received a single left-click; however, the next event
                        #might be a double-click event; therefore the state has
                        #not been decided yet
    DRAW_MODE       = 3

    @property
    def state( self ):
        return self._current_state

    def __init__( self, navigationController, brushingController ):
        QObject.__init__( self )
        self._navCtrl = navigationController
        self._navIntr = NavigationInterpreter( navigationController )
        self._brushingCtrl = brushingController
        self._current_state = self.FINAL
        self._temp_erasing = False # indicates, if user pressed shift
                                   # for temporary erasing (in
                                   # contrast to selecting the eraser brush)

        self._lineItems = [] # list of line items that have been
                            # added to the qgraphicsscene for drawing indication
                            
        self._lastEvent = None
        self._doubleClickTimer = None

        # clear the temporary line items once they
        # have been pushed to the sink
        self._brushingCtrl.wroteToSink.connect(self.clearLines)

    def start( self ):
        if self._current_state == self.FINAL:
            self._navIntr.start()
            self._current_state = self.DEFAULT_MODE
        else:
            pass # ignore

    def stop( self ):
        if self._brushingCtrl._isDrawing:
            for imageview in self._navCtrl._views:
                self._brushingCtrl.endDrawing(imageview.mousePos)
        self._current_state = self.FINAL
        self._navIntr.stop()

    def eventFilter( self, watched, event ):
        etype = event.type()

        # Before we steal this event from the scene, check that it is allowing brush strokes 
        allow_brushing = True
        for view in self._navCtrl._views:
            allow_brushing &= view.scene().allow_brushing
        if not allow_brushing:
            return self._navIntr.eventFilter( watched, event )
        
        if etype == QEvent.MouseButtonDblClick and self._doubleClickTimer is not None:
            # On doubleclick, cancel release handler that normally draws the stroke.
            self._doubleClickTimer.stop()
            self._doubleClickTimer = None
            self._current_state = self.DEFAULT_MODE
            self.onEntry_default( watched, event )
        
        if self._current_state == self.DEFAULT_MODE:
            if etype == QEvent.MouseButtonPress \
                and event.button() == Qt.LeftButton \
                and event.modifiers() == Qt.NoModifier \
                and self._navIntr.mousePositionValid(watched, event):
                
                ### default mode -> maybe draw mode
                self._current_state = self.MAYBE_DRAW_MODE

                # event will not be valid to use after this function exits,
                # so we must make a copy of it instead of just saving the pointer
                self._lastEvent = QMouseEvent( event.type(), event.pos(), event.globalPos(), event.button(), event.buttons(), event.modifiers() )
                
        elif self._current_state == self.MAYBE_DRAW_MODE:
            if etype == QEvent.MouseMove:
                # navigation interpreter also has to be in
                # default mode to avoid inconsistencies
                if self._navIntr.state == self._navIntr.DEFAULT_MODE:
                    ### maybe draw mode -> maybe draw mode
                    self._current_state = self.DRAW_MODE
                    self.onEntry_draw( watched, self._lastEvent )
                    self.onMouseMove_draw( watched, event )
                    return True
                else:
                    self._navIntr.eventFilter( watched, self._lastEvent )
                    return self._navIntr.eventFilter( watched, event )
            elif etype == QEvent.MouseButtonDblClick:
                ### maybe draw mode -> default mode
                self._current_state = self.DEFAULT_MODE
                return self._navIntr.eventFilter( watched, event )
            elif etype == QEvent.MouseButtonRelease:
                def handleRelease(releaseEvent):
                    self._current_state = self.DRAW_MODE
                    self.onEntry_draw( watched, self._lastEvent )
                    self.onExit_draw( watched, releaseEvent)
                    self._current_state = self.DEFAULT_MODE
                    self.onEntry_default( watched, releaseEvent )

                # If this event is part of a double-click, we don't really want to handle it.
                # Typical event sequence is press, release, double-click (not two presses).
                # Instead of handling this right away, set a timer to do the work.
                # We'll cancel the timer if we see a double-click event (see above).
                self._doubleClickTimer = QTimer(self)
                self._doubleClickTimer.setInterval(200)
                self._doubleClickTimer.setSingleShot(True)
                # event will not be valid to use after this function exits,
                # so we must make a copy of it instead of just saving the pointer
                eventCopy = QMouseEvent( event.type(), event.pos(), event.button(), event.buttons(), event.modifiers() )
                self._doubleClickTimer.timeout.connect( partial(handleRelease, eventCopy ) )
                self._doubleClickTimer.start()

                return True

        elif self._current_state == self.DRAW_MODE:
            if etype == QEvent.MouseButtonRelease and event.button() == Qt.LeftButton:
                self.onExit_draw( watched, event )
                ### draw mode -> default mode
                self._current_state = self.DEFAULT_MODE
                self.onEntry_default( watched, event )
                return True

            elif etype == QEvent.MouseMove and event.buttons() & Qt.LeftButton:
                if self._navIntr.mousePositionValid(watched, event):
                    self.onMouseMove_draw( watched, event )
                    return True
                else:
                    self.onExit_draw( watched, event )
                    ### draw mode -> default mode
                    self._current_state = self.DEFAULT_MODE
                    self.onEntry_default( watched, event )

        # let the navigation interpreter handle common events
        return self._navIntr.eventFilter( watched, event )

    ###
    ### Default Mode
    ###
    def onEntry_default( self, imageview, event ):
        pass

    ###
    ### Draw Mode
    ###
    def onEntry_draw( self, imageview, event ):
        if QApplication.keyboardModifiers() == Qt.ShiftModifier:
            self._brushingCtrl._brushingModel.setErasing()
            self._temp_erasing = True
        imageview.mousePos = imageview.mapScene2Data(imageview.mapToScene(event.pos()))
        self._brushingCtrl.beginDrawing(imageview, imageview.mousePos)

    def onExit_draw( self, imageview, event ):
        self._brushingCtrl.endDrawing(imageview.mousePos)
        if self._temp_erasing:
            self._brushingCtrl._brushingModel.disableErasing()
            self._temp_erasing = False

    def onMouseMove_draw( self, imageview, event ):
        self._navIntr.onMouseMove_default( imageview, event )

        o = imageview.scene().data2scene.map(QPointF(imageview.oldX,imageview.oldY))
        n = imageview.scene().data2scene.map(QPointF(imageview.x,imageview.y))

        # Draw temporary line for the brush stroke so the user gets feedback before the data is really updated.
        pen = QPen( QBrush(self._brushingCtrl._brushingModel.drawColor), self._brushingCtrl._brushingModel.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        line = QGraphicsLineItem(o.x(), o.y(), n.x(), n.y())
        line.setPen(pen)
        
        imageview.scene().addItem(line)
        line.setParentItem(imageview.scene().dataRectItem)

        self._lineItems.append(line)
        self._brushingCtrl._brushingModel.moveTo(imageview.mousePos)

    def clearLines(self):
        # This is called after the brush stroke is stored to the data.
        # Our temporary line object is no longer needed because the data provides the true pixel labels that were stored.
        lines = self._lineItems
        self._lineItems = []
        for l in lines:
            l.hide()

    def updateCursorPosition(self, *args, **kwargs):
        self._navIntr.updateCursorPosition(*args, **kwargs)
예제 #2
0
class BrushingInterpreter( QObject ):
    # states
    FINAL           = 0
    DEFAULT_MODE    = 1

    # FIXME: This state isn't really needed, now that we use a QTimer to manage the double-click case.
    #        (The state machine should be rewritten.)
    MAYBE_DRAW_MODE = 2 #received a single left-click; however, the next event
                        #might be a double-click event; therefore the state has
                        #not been decided yet
    DRAW_MODE       = 3

    @property
    def state( self ):
        return self._current_state

    def __init__( self, navigationController, brushingController ):
        QObject.__init__( self )
        self._navCtrl = navigationController
        self._navIntr = NavigationInterpreter( navigationController )
        self._brushingCtrl = brushingController
        self._current_state = self.FINAL
        self._temp_erasing = False # indicates, if user pressed shift
                                   # for temporary erasing (in
                                   # contrast to selecting the eraser brush)

        self._lineItems = [] # list of line items that have been
                            # added to the qgraphicsscene for drawing indication
                            
        self._lastEvent = None
        self._doubleClickTimer = None

        # clear the temporary line items once they
        # have been pushed to the sink
        self._brushingCtrl.wroteToSink.connect(self.clearLines)

    def start( self ):
        if self._current_state == self.FINAL:
            self._navIntr.start()
            self._current_state = self.DEFAULT_MODE
        else:
            pass # ignore

    def stop( self ):
        if self._brushingCtrl._isDrawing:
            for imageview in self._navCtrl._views:
                self._brushingCtrl.endDrawing(imageview.mousePos)
        self._current_state = self.FINAL
        self._navIntr.stop()

    def eventFilter( self, watched, event ):
        etype = event.type()

        # Before we steal this event from the scene, check that it is allowing brush strokes 
        allow_brushing = True
        for view in self._navCtrl._views:
            allow_brushing &= view.scene().allow_brushing
        if not allow_brushing:
            return self._navIntr.eventFilter( watched, event )
        
        if etype == QEvent.MouseButtonDblClick and self._doubleClickTimer is not None:
            # On doubleclick, cancel release handler that normally draws the stroke.
            self._doubleClickTimer.stop()
            self._doubleClickTimer = None
            self._current_state = self.DEFAULT_MODE
            self.onEntry_default( watched, event )
        
        if self._current_state == self.DEFAULT_MODE:
            if etype == QEvent.MouseButtonPress \
                and event.button() == Qt.LeftButton \
                and event.modifiers() == Qt.NoModifier \
                and self._navIntr.mousePositionValid(watched, event):
                
                ### default mode -> maybe draw mode
                self._current_state = self.MAYBE_DRAW_MODE

                # event will not be valid to use after this function exits,
                # so we must make a copy of it instead of just saving the pointer
                self._lastEvent = QMouseEvent( event.type(), event.pos(), event.globalPos(), event.button(), event.buttons(), event.modifiers() )
                
        elif self._current_state == self.MAYBE_DRAW_MODE:
            if etype == QEvent.MouseMove:
                # navigation interpreter also has to be in
                # default mode to avoid inconsistencies
                if self._navIntr.state == self._navIntr.DEFAULT_MODE:
                    ### maybe draw mode -> maybe draw mode
                    self._current_state = self.DRAW_MODE
                    self.onEntry_draw( watched, self._lastEvent )
                    self.onMouseMove_draw( watched, event )
                    return True
                else:
                    self._navIntr.eventFilter( watched, self._lastEvent )
                    return self._navIntr.eventFilter( watched, event )
            elif etype == QEvent.MouseButtonDblClick:
                ### maybe draw mode -> default mode
                self._current_state = self.DEFAULT_MODE
                return self._navIntr.eventFilter( watched, event )
            elif etype == QEvent.MouseButtonRelease:
                def handleRelease(releaseEvent):
                    self._current_state = self.DRAW_MODE
                    self.onEntry_draw( watched, self._lastEvent )
                    self.onExit_draw( watched, releaseEvent)
                    self._current_state = self.DEFAULT_MODE
                    self.onEntry_default( watched, releaseEvent )

                # If this event is part of a double-click, we don't really want to handle it.
                # Typical event sequence is press, release, double-click (not two presses).
                # Instead of handling this right away, set a timer to do the work.
                # We'll cancel the timer if we see a double-click event (see above).
                self._doubleClickTimer = QTimer(self)
                self._doubleClickTimer.setInterval(200)
                self._doubleClickTimer.setSingleShot(True)
                # event will not be valid to use after this function exits,
                # so we must make a copy of it instead of just saving the pointer
                eventCopy = QMouseEvent( event.type(), event.pos(), event.button(), event.buttons(), event.modifiers() )
                self._doubleClickTimer.timeout.connect( partial(handleRelease, eventCopy ) )
                self._doubleClickTimer.start()

                return True

        elif self._current_state == self.DRAW_MODE:
            if etype == QEvent.MouseButtonRelease and event.button() == Qt.LeftButton:
                self.onExit_draw( watched, event )
                ### draw mode -> default mode
                self._current_state = self.DEFAULT_MODE
                self.onEntry_default( watched, event )
                return True

            elif etype == QEvent.MouseMove and event.buttons() & Qt.LeftButton:
                if self._navIntr.mousePositionValid(watched, event):
                    self.onMouseMove_draw( watched, event )
                    return True
                else:
                    self.onExit_draw( watched, event )
                    ### draw mode -> default mode
                    self._current_state = self.DEFAULT_MODE
                    self.onEntry_default( watched, event )

        # let the navigation interpreter handle common events
        return self._navIntr.eventFilter( watched, event )

    ###
    ### Default Mode
    ###
    def onEntry_default( self, imageview, event ):
        pass

    ###
    ### Draw Mode
    ###
    def onEntry_draw( self, imageview, event ):
        if QApplication.keyboardModifiers() == Qt.ShiftModifier:
            self._brushingCtrl._brushingModel.setErasing()
            self._temp_erasing = True
        imageview.mousePos = imageview.mapScene2Data(imageview.mapToScene(event.pos()))
        self._brushingCtrl.beginDrawing(imageview, imageview.mousePos)

    def onExit_draw( self, imageview, event ):
        self._brushingCtrl.endDrawing(imageview.mousePos)
        if self._temp_erasing:
            self._brushingCtrl._brushingModel.disableErasing()
            self._temp_erasing = False

    def onMouseMove_draw( self, imageview, event ):
        self._navIntr.onMouseMove_default( imageview, event )

        o = imageview.scene().data2scene.map(QPointF(imageview.oldX,imageview.oldY))
        n = imageview.scene().data2scene.map(QPointF(imageview.x,imageview.y))

        # Draw temporary line for the brush stroke so the user gets feedback before the data is really updated.
        pen = QPen( QBrush(self._brushingCtrl._brushingModel.drawColor), self._brushingCtrl._brushingModel.brushSize, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
        line = QGraphicsLineItem(o.x(), o.y(), n.x(), n.y())
        line.setPen(pen)
        
        imageview.scene().addItem(line)
        line.setParentItem(imageview.scene().dataRectItem)

        self._lineItems.append(line)
        self._brushingCtrl._brushingModel.moveTo(imageview.mousePos)

    def clearLines(self):
        # This is called after the brush stroke is stored to the data.
        # Our temporary line object is no longer needed because the data provides the true pixel labels that were stored.
        lines = self._lineItems
        self._lineItems = []
        for l in lines:
            l.hide()

    def updateCursorPosition(self, *args, **kwargs):
        self._navIntr.updateCursorPosition(*args, **kwargs)