def __init__(self, label, parent=None):
        super(GestureButton, self).__init__(label, parent=parent);

        #tmpStyle = "QPushButton {background-color: red}";
        #self.setStyleSheet(tmpStyle);

        self.setFlicksEnabled(True);

        # Timer for deciding when mouse has left this
        # button long enough that even returning to this
        # button will not be counted as a flick, but as
        # a new entrance. 
        # NOTE: QTimer emits the signal "timeout()" when it
        # expires. In contrast, QBasicTimer emits a timer *event*
        # For use with state transitions, use QEventTransition
        # for QBasicTimer, but QSignalTransition for QTimer. 
        
        self.maxFlickDurationTimer = QTimer();
        self.maxFlickDurationTimer.setSingleShot(True);

        # Bit of a hack to get around a Pyside bug/missing-feature:
        # Timer used with zero delay to trigger a QSignalTransition
        # with a standard signal (no custom signals seem to work):        
        self.flickDetectDoneTrigger = QTimer();
        self.flickDetectDoneTrigger.setSingleShot(True);
        
        self.setMouseTracking(True);
        self.latestMousePos = QPoint(0,0);
        
        self.connectWidgets();
        self.initSignals();
                
        # ------------------  State Definitions ------------------
        
        # Parent state for north/south/east/west states:
        self.stateGestureActivatePending = QState();
        #self.stateGestureActivatePending.assignProperty(self, "text", "Toggle pending");
        
        # States for: mouse left button; will mouse flick back?
        self.stateNorthExit = QState(self.stateGestureActivatePending);
        self.stateSouthExit = QState(self.stateGestureActivatePending);
        self.stateEastExit  = QState(self.stateGestureActivatePending);
        self.stateWestExit  = QState(self.stateGestureActivatePending);
        
        self.stateGestureActivatePending.setInitialState(self.stateNorthExit);
        
        # State: mouse entered button area not as part of a flick:          
        self.stateEntered   = QState();
        #self.stateEntered.assignProperty(self, "text", "Entered");
        
        # State: mouse re-entered button after leaving briefly:
        self.stateReEntered = QState();
        #self.stateReEntered.assignProperty(self, "text", "Re-Entered");
        
        # State: mouse outside for longer than GestureButton.FLICK_TIME_THRESHOLD seconds: 
        self.stateIdle      = QState();
        #self.stateIdle.assignProperty(self, "text", "Idle");

        # ------------------  Transition Definitions ------------------

        # From Idle to Entered: triggered automatically by mouse entering button area:
        self.toEnteredTrans = HotEntryTransition(self, QEvent.Enter, sourceState=self.stateIdle);
        self.toEnteredTrans.setTargetState(self.stateEntered);
        
        # From Entered to GestureActivatePending: mouse cursor left button area,
        # and the user may or may not return the cursor to the button area
        # in time to trigger a flick. Transition triggered automatically by 
        # mouse leaving button area. A timer is set. If it runs to 0, 
        # a transition to Idle will be triggered. But if mouse re-enters button
        # area before the timer expires, the ReEntered state will become active: 
        self.toGAPendingTrans = TimeSettingStateTransition(self, QEvent.Leave, sourceState=self.stateEntered);
        self.toGAPendingTrans.setTargetState(self.stateGestureActivatePending);
        
        # From GestureActivatePending to ReEntered. Triggered if mouse cursor
        # re-enters button area after having left before the maxFlickDurationTimer 
        # has run down. Triggered automatically by mouse entering the button area:
        self.toReEnteredTrans = TimeReadingStateTransition(self, 
                                                           QEvent.Enter, 
                                                           self.toGAPendingTrans,
                                                           sourceState=self.stateGestureActivatePending);
        self.toReEnteredTrans.setTargetState(self.stateReEntered);

        # From GestureActivePending to Idle. Triggered by maxFlickDurationTimer running to 0
        # before mouse cursor re-entered button area after leaving. Triggered by timer that
        # is set when state GestureActivatePending is entered.
        self.toIdleTrans = HotExitTransition(self,
					     self.maxFlickDurationTimer,
					     sourceState=self.stateGestureActivatePending);
        self.toIdleTrans.setTargetState(self.stateIdle);
        
        # From ReEntered to Entered. Triggered a zero-delay timer timeout set
        # in TimeReadingStateTransition after that transition has determined 
        # whether a mouse cursor entry into the button space was a flick, or not.
        # (Note: In the PySide version
        # current at this writing, neither custom events nor custom signals
        # seem to work for triggering QEventTransaction or QSignalTransaction,
        # respectively)
        self.toEnteredFromReEnteredTrans = QSignalTransition(self.flickDetectDoneTrigger,
							     SIGNAL("timeout()"),
							     sourceState=self.stateReEntered); 
        self.toEnteredFromReEnteredTrans.setTargetState(self.stateEntered);

        # ---------------------- State Machine -------QtCore.signal--------------
                
        self.stateMachine   = QStateMachine();
        self.stateMachine.addState(self.stateGestureActivatePending);
        self.stateMachine.addState(self.stateEntered);
        self.stateMachine.addState(self.stateReEntered);
        self.stateMachine.addState(self.stateIdle);
        
        self.installEventFilter(self);
        
        self.stateMachine.setInitialState(self.stateIdle);
        self.stateMachine.start();
class GestureButton(QPushButton):
    
    FLICK_TIME_THRESHOLD_MASTER = 0.5; # seconds
    
    def __init__(self, label, parent=None):
        super(GestureButton, self).__init__(label, parent=parent);

        #tmpStyle = "QPushButton {background-color: red}";
        #self.setStyleSheet(tmpStyle);

        self.setFlicksEnabled(True);

        # Timer for deciding when mouse has left this
        # button long enough that even returning to this
        # button will not be counted as a flick, but as
        # a new entrance. 
        # NOTE: QTimer emits the signal "timeout()" when it
        # expires. In contrast, QBasicTimer emits a timer *event*
        # For use with state transitions, use QEventTransition
        # for QBasicTimer, but QSignalTransition for QTimer. 
        
        self.maxFlickDurationTimer = QTimer();
        self.maxFlickDurationTimer.setSingleShot(True);

        # Bit of a hack to get around a Pyside bug/missing-feature:
        # Timer used with zero delay to trigger a QSignalTransition
        # with a standard signal (no custom signals seem to work):        
        self.flickDetectDoneTrigger = QTimer();
        self.flickDetectDoneTrigger.setSingleShot(True);
        
        self.setMouseTracking(True);
        self.latestMousePos = QPoint(0,0);
        
        self.connectWidgets();
        self.initSignals();
                
        # ------------------  State Definitions ------------------
        
        # Parent state for north/south/east/west states:
        self.stateGestureActivatePending = QState();
        #self.stateGestureActivatePending.assignProperty(self, "text", "Toggle pending");
        
        # States for: mouse left button; will mouse flick back?
        self.stateNorthExit = QState(self.stateGestureActivatePending);
        self.stateSouthExit = QState(self.stateGestureActivatePending);
        self.stateEastExit  = QState(self.stateGestureActivatePending);
        self.stateWestExit  = QState(self.stateGestureActivatePending);
        
        self.stateGestureActivatePending.setInitialState(self.stateNorthExit);
        
        # State: mouse entered button area not as part of a flick:          
        self.stateEntered   = QState();
        #self.stateEntered.assignProperty(self, "text", "Entered");
        
        # State: mouse re-entered button after leaving briefly:
        self.stateReEntered = QState();
        #self.stateReEntered.assignProperty(self, "text", "Re-Entered");
        
        # State: mouse outside for longer than GestureButton.FLICK_TIME_THRESHOLD seconds: 
        self.stateIdle      = QState();
        #self.stateIdle.assignProperty(self, "text", "Idle");

        # ------------------  Transition Definitions ------------------

        # From Idle to Entered: triggered automatically by mouse entering button area:
        self.toEnteredTrans = HotEntryTransition(self, QEvent.Enter, sourceState=self.stateIdle);
        self.toEnteredTrans.setTargetState(self.stateEntered);
        
        # From Entered to GestureActivatePending: mouse cursor left button area,
        # and the user may or may not return the cursor to the button area
        # in time to trigger a flick. Transition triggered automatically by 
        # mouse leaving button area. A timer is set. If it runs to 0, 
        # a transition to Idle will be triggered. But if mouse re-enters button
        # area before the timer expires, the ReEntered state will become active: 
        self.toGAPendingTrans = TimeSettingStateTransition(self, QEvent.Leave, sourceState=self.stateEntered);
        self.toGAPendingTrans.setTargetState(self.stateGestureActivatePending);
        
        # From GestureActivatePending to ReEntered. Triggered if mouse cursor
        # re-enters button area after having left before the maxFlickDurationTimer 
        # has run down. Triggered automatically by mouse entering the button area:
        self.toReEnteredTrans = TimeReadingStateTransition(self, 
                                                           QEvent.Enter, 
                                                           self.toGAPendingTrans,
                                                           sourceState=self.stateGestureActivatePending);
        self.toReEnteredTrans.setTargetState(self.stateReEntered);

        # From GestureActivePending to Idle. Triggered by maxFlickDurationTimer running to 0
        # before mouse cursor re-entered button area after leaving. Triggered by timer that
        # is set when state GestureActivatePending is entered.
        self.toIdleTrans = HotExitTransition(self,
					     self.maxFlickDurationTimer,
					     sourceState=self.stateGestureActivatePending);
        self.toIdleTrans.setTargetState(self.stateIdle);
        
        # From ReEntered to Entered. Triggered a zero-delay timer timeout set
        # in TimeReadingStateTransition after that transition has determined 
        # whether a mouse cursor entry into the button space was a flick, or not.
        # (Note: In the PySide version
        # current at this writing, neither custom events nor custom signals
        # seem to work for triggering QEventTransaction or QSignalTransaction,
        # respectively)
        self.toEnteredFromReEnteredTrans = QSignalTransition(self.flickDetectDoneTrigger,
							     SIGNAL("timeout()"),
							     sourceState=self.stateReEntered); 
        self.toEnteredFromReEnteredTrans.setTargetState(self.stateEntered);

        # ---------------------- State Machine -------QtCore.signal--------------
                
        self.stateMachine   = QStateMachine();
        self.stateMachine.addState(self.stateGestureActivatePending);
        self.stateMachine.addState(self.stateEntered);
        self.stateMachine.addState(self.stateReEntered);
        self.stateMachine.addState(self.stateIdle);
        
        self.installEventFilter(self);
        
        self.stateMachine.setInitialState(self.stateIdle);
        self.stateMachine.start();
        
        #self.setGeometry(500, 500, 200,100);
        #self.show()
     
    # ------------------------ Public Methods -------------------------------
    
    @staticmethod
    def setFlicksEnabled(doEnable):
        '''
        Controls whether flicking in and out of buttons is enabled. 
        If flicks are enabled, then mouse-left-button signals are delayed
        for FLICK_TIME_THRESHOLD seconds. If flicks are disabled, then
        those signals are delivered immediately after the cursor leaves
        a button.
        @param doEnable: set to True if the flick feature is to be enabled. Else set to False
        @type doEnable: boolean
        '''
        if doEnable:
            GestureButton.flickEnabled = True;
            GestureButton.FLICK_TIME_THRESHOLD = GestureButton.FLICK_TIME_THRESHOLD_MASTER;
        else:
            GestureButton.flickEnabled = True;
            GestureButton.FLICK_TIME_THRESHOLD = 0.0;

    @staticmethod
    def flicksEnabled():
        '''
        Returns whether the flick feature is enabled.
        @return: True if flicking is enabled, else False.
        @rtype: boolean
        '''
        return GestureButton.flickEnabled;
    
    # ------------------------ Private Methods -------------------------------    

    def initSignals(self):
        #*******************
#        self.signals = GestureSignals();        
#        CommChannel.getInstance().registerSignals(self.signals);
        #CommChannel.getInstance().registerSignals(GestureSignals);
        CommChannel.registerSignals(GestureSignals);

        #*******************
        
    
    def connectWidgets(self):
        #self.maxFlickDurationTimer.timeout.connect(self.flickThresholdExceeded);
        pass;
    
    def eventFilter(self, target, event):
        if target == self and (event.__class__ == QMouseEvent):
            self.latestMousePos = event.pos();
        return False;
    
#    def mouseMoveEvent(self, mouseEvent):
#        self.latestMousePos = mouseEvent.pos();
#        super(GestureButton, self).mouseMoveEvent(mouseEvent);
    
    @Slot(QPushButton)
    def flickThresholdExceeded(self):
        print "Flick opportunity over."
        return False;

    def findClosestButtonBorder(self):
        '''
        Retrieves current mouse position, which is assumed to have been
        saved in the gesture button's lastMousePos instance variable by 
        a mouseMove signal handler or event filter. Compares this mouse position with the 
        four button borders. Returns a FlickDirection to indicate which
        border is closest to the mouse. All this uses global coordinates.
        @return: FlickDirection member.
        @raise ValueError: if minimum distance cannot be determined. 
        '''
        # Global mouse position:
        mousePos = self.mapToGlobal(self.latestMousePos)
        # Get: (upperLeftGlobal_x, upperLeftGlobal_y, width, height):
        gestureButtonRect = self.geometry()
        # Recompute upper left and lower right in global coords
        # each time, b/c window may have moved:
        topLeftPtGlobal = self.mapToGlobal(gestureButtonRect.topLeft());
        bottomRightPtGlobal = self.mapToGlobal(gestureButtonRect.bottomRight());
        # Do mouse coord absolute value compare with button edges,
        # because the last recorded mouse event is often just still
        # inside the button when this 'mouse left' signal arrives:
        distMouseFromTop = math.fabs(mousePos.y() - topLeftPtGlobal.y())
        distMouseFromBottom = math.fabs(mousePos.y() - bottomRightPtGlobal.y())
        distMouseFromLeft = math.fabs(mousePos.x() - topLeftPtGlobal.x())
        distMouseFromRight = math.fabs(mousePos.x() - bottomRightPtGlobal.x())
        minDist = min(distMouseFromTop, distMouseFromBottom, distMouseFromLeft, distMouseFromRight)
        if minDist == distMouseFromTop:
            return FlickDirection.NORTH
        elif minDist == distMouseFromBottom:
            return FlickDirection.SOUTH
        elif minDist == distMouseFromLeft:
            return FlickDirection.WEST
        elif minDist == distMouseFromRight:
            return FlickDirection.EAST
        else:
            raise ValueError("Failed to compute closest button border.")


    def __str__(self):
        if self.text() is not None:
            return self.text();
        else:
            return "<noLabel>";