Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormike <michael.jmontcalm@gmail.com>2009-11-25 18:49:32 (GMT)
committer mike <michael.jmontcalm@gmail.com>2009-11-25 18:49:32 (GMT)
commitebb94ef8194ec1e9d22b979e2f5a64a2b182102f (patch)
treea3f8439d2043a347df4022fb1a8bb58e51831403
parente7abfee767ef1f9b6efb6e6145da044151dd30e3 (diff)
LP 448319 : Cleaning up code a bit to make the FSM structure more obvious, Adding bubblemessage with image from Dave
-rw-r--r--addons/bubblemessagewimg.py119
-rw-r--r--tests/enginetests.py9
-rw-r--r--tutorius/engine.py169
3 files changed, 212 insertions, 85 deletions
diff --git a/addons/bubblemessagewimg.py b/addons/bubblemessagewimg.py
new file mode 100644
index 0000000..9c3dfc1
--- /dev/null
+++ b/addons/bubblemessagewimg.py
@@ -0,0 +1,119 @@
+# Copyright (C) 2009, Tutorius.org
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+from sugar.tutorius.actions import Action, DragWrapper
+from sugar.tutorius.properties import TStringProperty, TResourceProperty, TArrayProperty
+from sugar.tutorius import overlayer
+from sugar.tutorius.services import ObjectStore
+
+class BubbleMessageWImg(Action):
+ message = TStringProperty("Message")
+ # Create the position as an array of fixed-size 2
+ position = TArrayProperty((0,0), 2, 2)
+ # Do the same for the tail position
+ tail_pos = TArrayProperty((0,0), 2, 2)
+ imgpath = TResourceProperty("")
+
+ def __init__(self, message=None, position=None, speaker=None, tail_pos=None, imgpath=None):
+ """
+ Shows a dialog with a given text, at the given position on the screen.
+
+ @param message A string to display to the user
+ @param position A list of the form [x, y]
+ @param speaker treeish representation of the speaking widget
+ @param tail_pos The position of the tail of the bubble; useful to point to
+ specific elements of the interface
+ """
+ Action.__init__(self)
+
+ if position:
+ self.position = position
+ if tail_pos:
+ self.tail_pos = tail_pos
+ if message:
+ self.message = message
+ if imgpath:
+ self.imgpath = imgpath
+
+ self.overlay = None
+ self._bubble = None
+ self._speaker = None
+
+ def do(self, **kwargs):
+ """
+ Show the dialog
+ """
+ # get or inject overlayer
+ self.overlay = ObjectStore().activity._overlayer
+ # FIXME: subwindows, are left to overlap this. This behaviour is
+ # undesirable. subwindows (i.e. child of top level windows) should be
+ # handled either by rendering over them, or by finding different way to
+ # draw the overlay.
+
+ if not self.overlay:
+ self.overlay = ObjectStore().activity._overlayer
+ if not self._bubble:
+ x, y = self.position
+ # TODO: tails are relative to tailpos. They should be relative to
+ # the speaking widget. Same of the bubble position.
+ self._bubble = overlayer.TextBubbleWImg(text=self.message,
+ tailpos=self.tail_pos,imagepath=self.imgpath.default)
+ self._bubble.show()
+ self.overlay.put(self._bubble, x, y)
+ self.overlay.queue_draw()
+
+ def undo(self):
+ """
+ Destroy the dialog
+ """
+ if self._bubble:
+ self._bubble.destroy()
+ self._bubble = None
+
+ def enter_editmode(self, *args):
+ """
+ Enters edit mode. The action should display itself in some way,
+ without affecting the currently running application.
+ """
+ if not self.overlay:
+ self.overlay = ObjectStore().activity._overlayer
+ assert not self._drag, "bubble action set to editmode twice"
+ x, y = self.position
+ self._bubble = overlayer.TextBubbleWImg(text=self.message,
+ tailpos=self.tail_pos,imagepath=self.imgpath)
+ self.overlay.put(self._bubble, x, y)
+ self._bubble.show()
+
+ self._drag = DragWrapper(self._bubble, self.position, True)
+
+ def exit_editmode(self, *args):
+ x,y = self._drag.position
+ self.position = (int(x), int(y))
+ if self._drag:
+ self._drag.draggable = False
+ self._drag = None
+ if self._bubble:
+ self.overlay.remove(self._bubble)
+ self._bubble = None
+ self.overlay = None
+
+__action__ = {
+ "name" : "BubbleMessageWImg",
+ "display_name" : "Message Bubble with image",
+ "icon" : "message-bubble",
+ "class" : BubbleMessageWImg,
+ "mandatory_props" : ["message",'imgpath']
+}
+
diff --git a/tests/enginetests.py b/tests/enginetests.py
index ca86c9f..7fddd15 100644
--- a/tests/enginetests.py
+++ b/tests/enginetests.py
@@ -35,7 +35,12 @@ from sugar.tutorius.actions import Action
from sugar.tutorius.filters import EventFilter
from actiontests import CountAction
+
class MockProbeMgrMultiAddons(object):
+ """
+ Mock probe manager that supports installing more than one action
+ at the time.
+ """
def __init__(self):
self.action_dict = {}
self.event_dict = {}
@@ -172,7 +177,7 @@ class TestRunnerStates(unittest.TestCase):
self.pM._action_installed_cb('action1')
- assert self.runner._runner_state == engine.RUNNER_STATE_STOPPED
+ assert self.runner._runner_state == engine.RUNNER_STATE_IDLE
def test_stop_in_events(self):
self.runner.start()
@@ -185,7 +190,7 @@ class TestRunnerStates(unittest.TestCase):
assert self.runner._runner_state == engine.RUNNER_STATE_SETUP_EVENTS, "Tutorial should not be stopped until all events have been confirmed"
self.pM.event_sub_cB('event1')
- assert self.runner._runner_state == engine.RUNNER_STATE_STOPPED, "Tutorial should have been stopped right after the last event was confirmed"
+ assert self.runner._runner_state == engine.RUNNER_STATE_IDLE, "Tutorial should have been stopped right after the last event was confirmed"
class TestInstallationStates(unittest.TestCase):
def setUp(self):
diff --git a/tutorius/engine.py b/tutorius/engine.py
index be0b935..34616f6 100644
--- a/tutorius/engine.py
+++ b/tutorius/engine.py
@@ -37,7 +37,6 @@ RUNNER_STATE_SETUP_EVENTS = 2
RUNNER_STATE_AWAITING_NOTIFICATIONS = 3
RUNNER_STATE_UNINSTALLING_ACTIONS = 4
RUNNER_STATE_UNSUBSCRIBING_EVENTS = 5
-RUNNER_STATE_STOPPED = 6
LOGGER = logging.getLogger("sugar.tutorius.engine")
@@ -82,7 +81,9 @@ class TutorialRunner(object):
#Temp FIX until event, actions have an activity id
def setCurrentActivity(self):
self._pM.currentActivity = self._activity_id
-
+
+ ###########################################################################
+ # Incoming messages
def start(self):
self.setCurrentActivity() #Temp Hack until activity in events/actions
self.enterState(self._tutorial.INIT)
@@ -94,10 +95,78 @@ class TutorialRunner(object):
else:
self._execute_stop()
+ def action_installed(self, action_name, action, address):
+ LOGGER.debug("TutorialRunner :: Action %s received address %s"%(action_name, address))
+ self._installed_actions[action_name] = address
+ # Verify if we just completed the installation of the actions for this state
+ self._verify_action_install_state()
+
+ def install_error(self, action_name, action, exception):
+ # TODO : Fix this as it doesn't warn the user about the problem or anything
+ LOGGER.debug("TutorialRunner :: Action could not be installed %s, exception was : %s"%(str(action), str(exception)))
+ self._installation_errors[action_name] = exception
+ self._verify_action_install_state()
+
+ def event_subscribed(self, event_name, event_address):
+ LOGGER.debug("TutorialRunner :: Event %s was subscribed to, located at address %s"%(event_name, event_address))
+ self._subscribed_events[event_name] = event_address
+
+ # Verify if we just completed the subscription of all the events for this state
+ self._verify_event_install_state()
+
+ def subscribe_error(self, event_name, exception):
+ # TODO : Do correct error handling here
+ LOGGER.debug("TutorialRunner :: Could not subscribe to event %s, got exception : %s"%(event_name, str(exception)))
+ self._subscription_errors[event_name] = exception
+
+ # Verify if we just completed the subscription of all the events for this state
+ self._verify_event_install_state()
+
+ def all_actions_installed(self):
+ self._runner_state = RUNNER_STATE_SETUP_EVENTS
+ # Process the messages that might have been stored
+ self._process_pending_messages()
+
+ # If we processed a message that changed the runner state, we need to stop
+ # processing
+ if self._runner_state != RUNNER_STATE_SETUP_EVENTS:
+ return
+
+ # Start subscribing to events
+ transitions = self._tutorial.get_transition_dict(self._state)
+
+ # If there are no transitions, raise the All Events Subscribed message
+ if len(transitions) == 0:
+ self.all_events_subscribed()
+ return
+
+ # Send all the event registration
+ for (event_name, (event, next_state)) in transitions.items():
+ self._pM.subscribe(event_name, event,
+ save_args(self._handleEvent, next_state),
+ save_args(self.event_subscribed, event_name),
+ save_args(self.subscribe_error, event_name))
+
+ def all_events_subscribed(self):
+ self._runner_state = RUNNER_STATE_AWAITING_NOTIFICATIONS
+ self._process_pending_messages()
+
+ def _uninstall_actions(self):
+ self._runner_state = RUNNER_STATE_UNINSTALLING_ACTIONS
+ self._remove_installed_actions()
+ self._execute_stop()
+
+ def _unsubscribe_events(self):
+ self._runner_state = RUNNER_STATE_UNSUBSCRIBING_EVENTS
+ self._remove_subscribed_events()
+ self._uninstall_actions()
+
+ ###########################################################################
+ # Helper functions
def _execute_stop(self):
self.setCurrentActivity() #Temp Hack until activity in events/actions
self._state = None
- self._runner_state = RUNNER_STATE_STOPPED
+ self._runner_state = RUNNER_STATE_IDLE
def _handleEvent(self, next_state, event):
# Look if we are actually receiving notifications
@@ -136,19 +205,7 @@ class TutorialRunner(object):
self._subscribed_events.clear()
self._subscription_errors.clear()
- def __action_installed(self, action_name, action, address):
- LOGGER.debug("TutorialRunner :: Action %s received address %s"%(action_name, address))
- self._installed_actions[action_name] = address
- # Verify if we just completed the installation of the actions for this state
- self.__verify_action_install_state()
-
- def __install_error(self, action_name, action, exception):
- # TODO : Fix this as it doesn't warn the user about the problem or anything
- LOGGER.debug("TutorialRunner :: Action could not be installed %s, exception was : %s"%(str(action), str(exception)))
- self._installation_errors[action_name] = exception
- self.__verify_action_install_state()
-
- def __verify_action_install_state(self):
+ def _verify_action_install_state(self):
# Do the check to see if we have finished installing all the actions by either having
# received a address for it or an error message
install_complete = True
@@ -162,24 +219,9 @@ class TutorialRunner(object):
if install_complete:
LOGGER.debug("TutorialRunner :: All actions installed!")
# Raise the All Actions Installed event for the TutorialRunner state
- self.__all_actions_installed()
+ self.all_actions_installed()
- def __event_subscribed(self, event_name, event_address):
- LOGGER.debug("TutorialRunner :: Event %s was subscribed to, located at address %s"%(event_name, event_address))
- self._subscribed_events[event_name] = event_address
-
- # Verify if we just completed the subscription of all the events for this state
- self.__verify_event_install_state()
-
- def __subscribe_error(self, event_name, exception):
- # TODO : Do correct error handling here
- LOGGER.debug("TutorialRunner :: Could not subscribe to event %s, got exception : %s"%(event_name, str(exception)))
- self._subscription_errors[event_name] = exception
-
- # Verify if we just completed the subscription of all the events for this state
- self.__verify_event_install_state()
-
- def __verify_event_install_state(self):
+ def _verify_event_install_state(self):
transitions = self._tutorial.get_transition_dict(self._state)
# Check to see if we completed all the event subscriptions
@@ -192,48 +234,9 @@ class TutorialRunner(object):
if subscribe_complete:
LOGGER.debug("TutorialRunner : Subscribed to all events!")
- self.__all_events_subscribed()
-
- def __all_actions_installed(self):
- self._runner_state = RUNNER_STATE_SETUP_EVENTS
- # Process the messages that might have been stored
- self.__process_pending_messages()
+ self.all_events_subscribed()
- # If we processed a message that changed the runner state, we need to stop
- # processing
- if self._runner_state != RUNNER_STATE_SETUP_EVENTS:
- return
-
- # Start subscribing to events
- transitions = self._tutorial.get_transition_dict(self._state)
-
- # If there are no transitions, raise the All Events Subscribed message
- if len(transitions) == 0:
- self.__all_events_subscribed()
- return
-
- # Send all the event registration
- for (event_name, (event, next_state)) in transitions.items():
- self._pM.subscribe(event_name, event,
- save_args(self._handleEvent, next_state),
- save_args(self.__event_subscribed, event_name),
- save_args(self.__subscribe_error, event_name))
-
- def __all_events_subscribed(self):
- self._runner_state = RUNNER_STATE_AWAITING_NOTIFICATIONS
- self.__process_pending_messages()
-
- def __uninstall_actions(self):
- self._runner_state = RUNNER_STATE_UNINSTALLING_ACTIONS
- self._remove_installed_actions()
- self._execute_stop()
-
- def __unsubscribe_events(self):
- self._runner_state = RUNNER_STATE_UNSUBSCRIBING_EVENTS
- self._remove_subscribed_events()
- self.__uninstall_actions()
-
- def __process_pending_messages(self):
+ def _process_pending_messages(self):
while len(self._message_queue) != 0:
(priority, message) = heappop(self._message_queue)
@@ -245,9 +248,9 @@ class TutorialRunner(object):
# Start removing the installed addons
if self._runner_state == RUNNER_STATE_AWAITING_NOTIFICATIONS:
# Start uninstalling the events
- self.__unsubscribe_events()
+ self._unsubscribe_events()
if self._runner_state == RUNNER_STATE_SETUP_EVENTS:
- self.__uninstall_actions()
+ self._uninstall_actions()
elif priority == EVENT_NOTIFICATION_MSG_PRIORITY:
LOGGER.debug("TutorialRunner :: Handling stored event notification for next_state %s"%message[0])
self._handle_event(*message)
@@ -259,22 +262,22 @@ class TutorialRunner(object):
self._actions = self._tutorial.get_action_dict(self._state)
if len(self._actions) == 0:
- self.__all_actions_installed()
+ self.all_actions_installed()
return
for (action_name, action) in self._actions.items():
LOGGER.debug("TutorialRunner :: Installed action %s"%(action_name))
self._pM.install(action,
- save_args(self.__action_installed, action_name),
- save_args(self.__install_error, action_name))
+ save_args(self.action_installed, action_name),
+ save_args(self.install_error, action_name))
def enterState(self, state_name):
"""
- Starting from the state_name, the runner execute states until
- no automatic transition are found and will wait for an external
- event to occur.
+ Starting from the state_name, the runner execute states from the
+ tutorial until no automatic transitions are found and will wait
+ for an external event to occur.
- When entering the state, actions and events from the previous
+ When entering the sate, actions and events from the previous
state are respectively uninstalled and unsubscribed and actions
and events from the state_name will be installed and subscribed.