# -*- coding: utf-8 -*- # statemachines.py # Copyright (C) 2013 LEAP # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ State machines for the Bitmask app. """ import logging from PySide.QtCore import QStateMachine, QState from PySide.QtCore import QObject from leap.bitmask.services import connections from leap.common.check import leap_assert_type logger = logging.getLogger(__name__) _tr = QObject().tr # Indexes for the state dict _ON = "on" _OFF = "off" _CON = "connecting" _DIS = "disconnecting" class IntermediateState(QState): """ Intermediate state that emits a custom signal on entry """ def __init__(self, signal): """ Initializer. :param signal: the signal to be emitted on entry on this state. :type signal: QtCore.QSignal """ super(IntermediateState, self).__init__() self._signal = signal def onEntry(self, *args): """ Emits the signal on entry. """ logger.debug('IntermediateState entered. Emitting signal ...') if self._signal is not None: self._signal.emit() class ConnectionMachineBuilder(object): """ Builder class for state machines made from LEAPConnections. """ def __init__(self, connection): """ :param connection: an instance of a concrete LEAPConnection we will be building a state machine for. :type connection: AbstractLEAPConnection """ self._conn = connection leap_assert_type(self._conn, connections.AbstractLEAPConnection) def make_machine(self, button=None, action=None, label=None): """ Creates a statemachine associated with the passed controls. :param button: the switch button. :type button: QPushButton :param action: the actionh that controls connection switch in a menu. :type action: QAction :param label: the label that displays the connection state :type label: QLabel :returns: a state machine :rtype: QStateMachine """ machine = QStateMachine() conn = self._conn states = self._make_states(button, action, label) # transitions: states[_OFF].addTransition( conn.qtsigs.do_connect_signal, states[_CON]) # * Clicking the buttons or actions transitions to the # intermediate stage. if button: states[_OFF].addTransition( button.clicked, states[_CON]) states[_ON].addTransition( button.clicked, states[_DIS]) if action: states[_OFF].addTransition( action.triggered, states[_CON]) states[_ON].addTransition( action.triggered, states[_DIS]) # * We transition to the completed stages when # we receive the matching signal from the underlying # conductor. states[_CON].addTransition( conn.qtsigs.connected_signal, states[_ON]) states[_DIS].addTransition( conn.qtsigs.disconnected_signal, states[_OFF]) # * If we receive the connection_died, we transition # to the off state states[_ON].addTransition( conn.qtsigs.connection_died_signal, states[_OFF]) # adding states to the machine for state in states.itervalues(): machine.addState(state) machine.setInitialState(states[_OFF]) return machine def _make_states(self, button, action, label): """ Creates the four states for the state machine :param button: the switch button. :type button: QPushButton :param action: the actionh that controls connection switch in a menu. :type action: QAction :param label: the label that displays the connection state :type label: QLabel :returns: a dict of states :rtype: dict """ conn = self._conn states = {} # TODO add tooltip # OFF State ---------------------- off = QState() off_label = _tr("Turn {0}").format( conn.Connected.short_label) if button: off.assignProperty( button, 'text', off_label) off.assignProperty( button, 'enabled', True) if action: off.assignProperty( action, 'text', off_label) off.setObjectName(_OFF) states[_OFF] = off # CONNECTING State ---------------- connecting = IntermediateState( conn.qtsigs.connecting_signal) on_label = _tr("Turn {0}").format( conn.Disconnected.short_label) if button: connecting.assignProperty( button, 'text', on_label) connecting.assignProperty( button, 'enabled', False) if action: connecting.assignProperty( action, 'text', on_label) connecting.assignProperty( action, 'enabled', False) connecting.setObjectName(_CON) states[_CON] = connecting # ON State ------------------------ on = QState() if button: on.assignProperty( button, 'text', on_label) on.assignProperty( button, 'enabled', True) if action: on.assignProperty( action, 'text', on_label) on.assignProperty( action, 'enabled', True) # TODO set label for ON state on.setObjectName(_ON) states[_ON] = on # DISCONNECTING State ------------- disconnecting = IntermediateState( conn.qtsigs.disconnecting_signal) if button: disconnecting.assignProperty( button, 'enabled', False) # XXX complete disconnecting # TODO disable button disconnecting.setObjectName(_DIS) states[_DIS] = disconnecting return states