From fb422aef7ee6832c85c8fa8a703e491838e74d62 Mon Sep 17 00:00:00 2001 From: Vincent Vinet Date: Fri, 04 Dec 2009 05:06:49 +0000 Subject: Add Event Sources: - Add source property in Action and EventFilter - Change TPropContainer contructor to accept keyword arguments and set properties that were given - Change every single TPropContainer subclass constructor to accept kwargs and pass them on to super init - Add a "null" option for TStringProperty Use Event Sources: - Make the probe require a source property to install or subscribe - Have ProbeProxy install and subscribe return a prefixed address - Make update, uninstall and unsubsribe extract the prefix from the address - Have the TutorialRunner set a source on actions/events before installing/subscribing instead of setting current activity on ProbeManager Test Event Sources: - Change the tests according to the new constructors and behaviors --- diff --git a/addons/bubblemessage.py b/addons/bubblemessage.py index 53387bf..4e37274 100644 --- a/addons/bubblemessage.py +++ b/addons/bubblemessage.py @@ -25,42 +25,33 @@ class BubbleMessage(Action): # Do the same for the tail position tail_pos = TArrayProperty((0,0), 2, 2) - def __init__(self, message=None, position=None, speaker=None, tail_pos=None): + def __init__(self, **kwargs): """ Shows a dialog with a given text, at the given position on the screen. + Accepted keyword args: @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 + super(BubbleMessage, self).__init__(**kwargs) self.overlay = None self._bubble = None self._speaker = None - def do(self, **kwargs): + def do(self, activity=None, **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 activity is None: + raise TypeError("Missing argument activity") + + # get overlayer + self.overlay = activity._overlayer - 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 diff --git a/addons/bubblemessagewimg.py b/addons/bubblemessagewimg.py index 514a311..9fe9512 100644 --- a/addons/bubblemessagewimg.py +++ b/addons/bubblemessagewimg.py @@ -26,26 +26,18 @@ class BubbleMessageWImg(Action): tail_pos = TArrayProperty((0,0), 2, 2) imgpath = TResourceProperty("") - def __init__(self, message=None, position=None, speaker=None, tail_pos=None, imgpath=None): + def __init__(self, **kwargs): """ Shows a dialog with a given text, at the given position on the screen. + Accepted keyword args: @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 + super(BubbleMessageWImg, self).__init__(**kwargs) self.overlay = None self._bubble = None diff --git a/addons/chainaction.py b/addons/chainaction.py index 9e8b6c8..cc610d6 100644 --- a/addons/chainaction.py +++ b/addons/chainaction.py @@ -17,13 +17,8 @@ from ..actions import * class ChainAction(Action): - actions = TAddonListProperty() - """Utility class to allow executing actions in a specific order""" - def __init__(self, actions=[]): - """ChainAction(action1, ... ) builds a chain of actions""" - Action.__init__(self) - self.actions = actions + actions = TAddonListProperty() def do(self,**kwargs): """do() each action in the chain""" diff --git a/addons/changecolor.py b/addons/changecolor.py index eac891a..53e6c53 100644 --- a/addons/changecolor.py +++ b/addons/changecolor.py @@ -14,8 +14,6 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -import time - import gobject import gtk, gtk.gdk @@ -40,13 +38,12 @@ class ChangeColor(Action): # set timeout timeout = 500 - def __init__(self, widaddr=None): + def __init__(self, **kwargs): """Constructor - Change a widget color + Accepted keyword arguments: @param widaddr: the widget for which you want to change the color (UAM) """ - Action.__init__(self) - - if widaddr: self.widaddr = widaddr + super(ChangeColor, self).__init__(**kwargs) self.init_style = None self._new_color = None diff --git a/addons/clickaction.py b/addons/clickaction.py index 2821541..eae713e 100644 --- a/addons/clickaction.py +++ b/addons/clickaction.py @@ -25,9 +25,6 @@ class ClickAction(Action): @param widget The threehish representation of the widget """ widget = TStringProperty("") - def __init__(self, widget): - Action.__init__(self) - self.widget = widget def do(self, **kwargs): """ diff --git a/addons/dialogmessage.py b/addons/dialogmessage.py index fad6d2c..24646f8 100644 --- a/addons/dialogmessage.py +++ b/addons/dialogmessage.py @@ -22,20 +22,17 @@ class DialogMessage(Action): message = TStringProperty("Message") position = TArrayProperty((0, 0), 2, 2) - def __init__(self, message=None, position=None): + def __init__(self, **kwargs): """ Shows a dialog with a given text, at the given position on the screen. + Accepted keyword args: @param message A string to display to the user @param position A list of the form [x, y] """ - super(DialogMessage, self).__init__() + super(DialogMessage, self).__init__(**kwargs) self._dialog = None - if message: - self.message = message - if position: self.position = position - def do(self, **kwargs): """ Show the dialog diff --git a/addons/disablewidget.py b/addons/disablewidget.py index 8b34254..15e07f2 100644 --- a/addons/disablewidget.py +++ b/addons/disablewidget.py @@ -21,13 +21,12 @@ from ..services import ObjectStore class DisableWidgetAction(Action): target = TStringProperty("0") - def __init__(self, target): + def __init__(self, **kwargs): """Constructor + Accepted keyword args: @param target target treeish """ - Action.__init__(self) - if target is not None: - self.target = target + super(DisableWidgetAction, self).__init__(**kwargs) self._widget = None def do(self, **kwargs): diff --git a/addons/gtkwidgeteventfilter.py b/addons/gtkwidgeteventfilter.py index f6ecf86..61a9d16 100644 --- a/addons/gtkwidgeteventfilter.py +++ b/addons/gtkwidgeteventfilter.py @@ -24,15 +24,14 @@ class GtkWidgetEventFilter(EventFilter): object_id = TUAMProperty() event_name = TEventType('clicked') - def __init__(self, object_id=None, event_name=None): + def __init__(self, **kwargs): """Constructor + Accepted keyword args: @param object_id object fqdn-style identifier @param event_name event to attach to """ - super(GtkWidgetEventFilter,self).__init__() + super(GtkWidgetEventFilter,self).__init__(**kwargs) self._callback = None - self.object_id = object_id - self.event_name = event_name self._widget = None self._handler_id = None diff --git a/addons/gtkwidgettypefilter.py b/addons/gtkwidgettypefilter.py index 0339e74..7b2e15e 100644 --- a/addons/gtkwidgettypefilter.py +++ b/addons/gtkwidgettypefilter.py @@ -30,20 +30,17 @@ class GtkWidgetTypeFilter(EventFilter): text = TStringProperty("") strokes = TSequenceProperty("") - def __init__(self, object_id, text=None, strokes=None): + def __init__(self, **kwargs): """Constructor - @param next_state default EventFilter param, passed on to EventFilter + Accepted keyword args: @param object_id object tree-ish identifier @param text resulting text expected @param strokes list of strokes expected At least one of text or strokes must be supplied """ - super(GtkWidgetTypeFilter, self).__init__() - self.object_id = object_id - self.text = text or "" + super(GtkWidgetTypeFilter, self).__init__(**kwargs) self._captext = "" - self.strokes = strokes or [] self._capstrokes = [] self._widget = None self._handler_id = None diff --git a/addons/messagebuttonnext.py b/addons/messagebuttonnext.py index 74ce1bb..058ef41 100644 --- a/addons/messagebuttonnext.py +++ b/addons/messagebuttonnext.py @@ -38,23 +38,18 @@ class MessageButtonNext(EventFilter): # set padding padding = 40 - def __init__(self, message=None, position=None, center_pos=False): + def __init__(self, **kwargs): """Constructor. - + Accepted keyword arguments: @param message message to display @param position message position """ - super(MessageButtonNext,self).__init__() + super(MessageButtonNext,self).__init__(**kwargs) - if position: - self.position = position - else: + if not self.position: # TODO: to be removed when creator supports editing properties on events self.position = (300, 200) - if message: - self.message = message - self.overlay = None self.msgnext = None diff --git a/addons/oncewrapper.py b/addons/oncewrapper.py index 3f6b2d0..f263dd7 100644 --- a/addons/oncewrapper.py +++ b/addons/oncewrapper.py @@ -26,11 +26,10 @@ class OnceWrapper(Action): action = TAddonProperty() - def __init__(self, action): - Action.__init__(self) + def __init__(self, **kwargs): + super(OnceWrapper, self).__init__(**kwargs) self._called = False self._need_undo = False - self.action = action def do(self, **kwargs): """ diff --git a/addons/readfile.py b/addons/readfile.py index 4a6c54d..81385ba 100644 --- a/addons/readfile.py +++ b/addons/readfile.py @@ -21,19 +21,13 @@ from ..properties import TFileProperty from ..services import ObjectStore class ReadFile(Action): + """ + Calls activity.read_file to restore a specified state to an activity + like when restored from the journal. + @param filename Path to the file to read + """ filename = TFileProperty(None) - def __init__(self, filename=None): - """ - Calls activity.read_file to restore a specified state to an activity - like when restored from the journal. - @param filename Path to the file to read - """ - Action.__init__(self) - - if filename: - self.filename=filename - def do(self, **kwargs): """ Perform the action, call read_file on the activity diff --git a/addons/timerevent.py b/addons/timerevent.py index a986fa0..9896075 100644 --- a/addons/timerevent.py +++ b/addons/timerevent.py @@ -27,14 +27,12 @@ class TimerEvent(EventFilter): """ timeout = TIntProperty(15, 0) - def __init__(self, timeout=None): + def __init__(self, **kwargs): """Constructor. - + Accepted keyword args: @param timeout timeout in seconds """ - super(TimerEvent,self).__init__() - if timeout: - self.timeout = timeout + super(TimerEvent,self).__init__(**kwargs) self._handler_id = None def install_handlers(self, callback, **kwargs): diff --git a/addons/triggereventfilter.py b/addons/triggereventfilter.py index 19544b0..3fc48b7 100644 --- a/addons/triggereventfilter.py +++ b/addons/triggereventfilter.py @@ -23,8 +23,8 @@ class TriggerEventFilter(EventFilter): Used to fake events and see the effect on the FSM. """ - def __init__(self): - EventFilter.__init__(self) + def __init__(self, **kwargs): + super(EventFilter, self).__init__(**kwargs) self.toggle_on_callback = False def install_handlers(self, callback, **kwargs): diff --git a/addons/typetextaction.py b/addons/typetextaction.py index 8c794d9..8a09ff9 100644 --- a/addons/typetextaction.py +++ b/addons/typetextaction.py @@ -27,12 +27,6 @@ class TypeTextAction(Action): """ widget = TStringProperty("") text = TStringProperty("") - - def __init__(self, widget, text): - Action.__init__(self) - - self.widget = widget - self.text = text def do(self, **kwargs): """ diff --git a/addons/widgetidentifyaction.py b/addons/widgetidentifyaction.py index c44964b..b59c94a 100644 --- a/addons/widgetidentifyaction.py +++ b/addons/widgetidentifyaction.py @@ -19,8 +19,8 @@ from ..actions import * from ..editor import WidgetIdentifier class WidgetIdentifyAction(Action): - def __init__(self): - Action.__init__(self) + def __init__(self, **kwargs): + super(WidgetIdentifyAction, self).__init__(**kwargs) self.activity = None self._dialog = None diff --git a/tests/actiontests.py b/tests/actiontests.py index 7b8d1cb..80a10f8 100644 --- a/tests/actiontests.py +++ b/tests/actiontests.py @@ -50,14 +50,14 @@ class PropsTest(unittest.TestCase): def test_get_properties(self): act = PropertyAction(8) - assert act.get_properties() == test_props.keys(), "Action does not contain property 'a'" + assert set(act.get_properties()).issuperset(test_props.keys()), "Action properties should contain at least those we specified" - for prop_name in act.get_properties(): + for prop_name in test_props.keys(): assert getattr(act, prop_name) == test_props[prop_name], "Wrong initial value for property %s : %s"%(prop_name,str(getattr(act, prop_name))) class DialogMessageTest(unittest.TestCase): def setUp(self): - self.dial = addon.create('DialogMessage', "Message text", [200, 300]) + self.dial = addon.create('DialogMessage', message="Message text", position=[200, 300]) def test_properties(self): assert self.dial.message == "Message text", "Wrong start value for the message" @@ -116,7 +116,7 @@ class OnceWrapperTests(unittest.TestCase): CountAction """ act = CountAction() - wrap = addon.create('OnceWrapper', act) + wrap = addon.create('OnceWrapper', action=act) assert act.do_count == 0, "do() should not have been called in __init__()" assert act.undo_count == 0, "undo() should not have been called in __init__()" @@ -162,7 +162,7 @@ class ChainActionTest(unittest.TestCase): first = ChainTester(witness) second = ChainTester(witness) - c = addon.create('ChainAction', [first, second]) + c = addon.create('ChainAction', actions=[first, second]) assert witness == [], "Actions should not be triggered on init""" c.do() @@ -195,7 +195,7 @@ class DisableWidgetActionTests(unittest.TestCase): assert btn.props.sensitive is True, "Callback should have been called" - act = addon.create('DisableWidgetAction', "0") + act = addon.create('DisableWidgetAction', target="0") assert btn.props.sensitive is True, "Callback should have been called again" act.do() assert btn.props.sensitive is False, "Callback should not have been called again" @@ -285,7 +285,7 @@ class TypeTextActionTests(unittest.TestCase): test_text = "This is text" - action = addon.create('TypeTextAction', "0.0", test_text) + action = addon.create('TypeTextAction', widgetUAM="0.0", text=test_text) assert widget == ObjectStore().activity.get_children()[0],\ "The clickable widget isn't reachable from the object store \ @@ -309,7 +309,7 @@ class TypeTextActionTests(unittest.TestCase): test_text = "This is text" - action = addon.create('TypeTextAction', "0.0", test_text) + action = addon.create('TypeTextAction', widgetUAM="0.0", text=test_text) assert widget == ObjectStore().activity.get_children()[0],\ "The clickable widget isn't reachable from the object store \ @@ -330,7 +330,7 @@ class ClickActionTests(unittest.TestCase): activity.add_child(widget) ObjectStore().activity = activity - action = addon.create('ClickAction', "0.0") + action = addon.create('ClickAction', widget="0.0") assert widget == ObjectStore().activity.get_children()[0],\ "The clickable widget isn't reachable from the object store \ @@ -350,7 +350,7 @@ class ClickActionTests(unittest.TestCase): activity.add_child(widget) ObjectStore().activity = activity - action = addon.create('ClickAction', "0.0") + action = addon.create('ClickAction', widget="0.0") assert widget == ObjectStore().activity.get_children()[0],\ "The clickable widget isn't reachable from the object store \ diff --git a/tests/coretests.py b/tests/coretests.py index b9e04e5..1cc5431 100644 --- a/tests/coretests.py +++ b/tests/coretests.py @@ -242,7 +242,7 @@ class StateTest(unittest.TestCase): act1 = addon.create("BubbleMessage", message="Hi", position=[132,450]) act2 = addon.create("BubbleMessage", message="Hi", position=[132,450]) - event1 = addon.create("GtkWidgetEventFilter", "0.0.0.1.1.2.3.1", "clicked") + event1 = addon.create("GtkWidgetEventFilter", object_id="0.0.0.1.1.2.3.1", event_name="clicked") act3 = addon.create("DialogMessage", message="Hello again.", position=[200, 400]) @@ -276,19 +276,19 @@ class StateTest(unittest.TestCase): st2.name = "Identical" st3 = deepcopy(st1) - st3.add_action(addon.create("BubbleMessage", "Hi!", [128,264])) + st3.add_action(addon.create("BubbleMessage", message="Hi!", position=[128,264])) assert not (st1 == st3), "States having a different number of actions should be different" st4 = deepcopy(st1) - st4.add_event_filter(addon.create("GtkWidgetEventFilter", "0.0.1.1.2.2.3", "clicked"), "next_state") + st4.add_event_filter(addon.create("GtkWidgetEventFilter", object_id="0.0.1.1.2.2.3", event_name="clicked"), "next_state") assert not (st1 == st4), "States having a different number of events should be different" st5 = deepcopy(st1) st5._event_filters = [] - st5.add_event_filter(addon.create("GtkWidgetEventFilter", "0.1.2.3.4.1.2", "pressed"), "other_state") + st5.add_event_filter(addon.create("GtkWidgetEventFilter", object_id="0.1.2.3.4.1.2", event_name="pressed"), "other_state") assert not (st1 == st5), "States having the same number of event filters" \ + " but those being different should be different" @@ -502,7 +502,7 @@ class FSMTest(unittest.TestCase): st2 = State("Other State") st3 = State("Final State") - st1.add_action(addon.create("BubbleMessage", "Hi!", [132,312])) + st1.add_action(addon.create("BubbleMessage", message="Hi!", position=[132,312])) fsm.add_state(st1) fsm.add_state(st2) @@ -534,7 +534,7 @@ class FSMTest(unittest.TestCase): fsm3 = FiniteStateMachine("Identity test") - act3 = addon.create("BubbleMessage", "Hi!", [123,312]) + act3 = addon.create("BubbleMessage", message="Hi!", position=[123,312]) fsm3.add_action(act3) assert not(fsm3 == fsm), \ diff --git a/tests/filterstests.py b/tests/filterstests.py index ee6033b..5358605 100644 --- a/tests/filterstests.py +++ b/tests/filterstests.py @@ -69,7 +69,7 @@ class TestTimerEvent(unittest.TestCase): ctx = gobject.MainContext() main = gobject.MainLoop(ctx) - e = addon.create('TimerEvent', 2) # 2 seconds should be enough :s + e = addon.create('TimerEvent', timeout=2) # 2 seconds should be enough :s s = SignalCatcher() e.install_handlers(s.callback) @@ -112,7 +112,7 @@ class TestTimerEvent(unittest.TestCase): ctx = gobject.MainContext() main = gobject.MainLoop(ctx) - e = addon.create('TimerEvent', 2) # 2 seconds should be enough :s + e = addon.create('TimerEvent', timeout=2) # 2 seconds should be enough :s s = SignalCatcher() e.install_handlers(s.callback) @@ -159,7 +159,7 @@ class TestGtkWidgetEventFilter(unittest.TestCase): self.top.add(self.btn1) def test_install(self): - h = addon.create('GtkWidgetEventFilter', "0","whatever") + h = addon.create('GtkWidgetEventFilter', object_id="0", event_name="whatever") try: h.install_handlers(None) @@ -168,7 +168,7 @@ class TestGtkWidgetEventFilter(unittest.TestCase): assert True, "Install should have failed" def test_button_clicks(self): - h = addon.create('GtkWidgetEventFilter', "0.0","clicked") + h = addon.create('GtkWidgetEventFilter', object_id="0.0", event_name="clicked") s = SignalCatcher() h.install_handlers(s.callback, activity=self.top) diff --git a/tests/probetests.py b/tests/probetests.py index 357d223..f073135 100644 --- a/tests/probetests.py +++ b/tests/probetests.py @@ -71,7 +71,6 @@ class MockActivity(object): def get_id(self): return "unique_id_1" - class MockProbeProxy(object): _MockProxyCache = {} @@ -191,6 +190,8 @@ class ProbeTest(unittest.TestCase): self.activity.get_id(), service_proxy=MockServiceProxy()) #Override the eventOccured on the Probe... + # Stores events in a global "event box" + #WARNING: Depends on the implementation of TProbe!! self.old_eO = self.probe.eventOccured def newEo(event): global event_box @@ -343,51 +344,78 @@ class ProbeManagerTest(unittest.TestCase): assert len(self.probeManager.get_registered_probes_list("act1")) == 0 assert self.probeManager.get_registered_probes_list("act1") == [] - def test_actions(self): + def test_action_uninstall(self): self.probeManager.register_probe("act1", "unique_id_1") self.probeManager.register_probe("act2", "unique_id_2") act1 = self.probeManager.get_registered_probes_list("act1")[0][1] act2 = self.probeManager.get_registered_probes_list("act2")[0][1] - ad1 = MockAddon() - ad1_address = "Address1" - def callback(value): - pass - def error_cb(): - pass - #ErrorCase: install, update, uninstall without currentActivity - #Action functions should do a warning if there is no activity - self.assertRaises(RuntimeWarning, self.probeManager.install, ad1_address, ad1, callback) - self.assertRaises(RuntimeWarning, self.probeManager.update, ad1_address, ad1) - self.assertRaises(RuntimeWarning, self.probeManager.uninstall, ad1_address) - assert act1.MockAction is None, "Action should not be installed on inactive proxy" - assert act2.MockAction is None, "Action should not be installed on inactive proxy" - - self.probeManager.currentActivity = "act1" - self.probeManager.install(ad1, callback, error_cb) - assert act1.MockAction == ad1, "Action should have been installed" - assert act2.MockAction is None, "Action should not be installed on inactive proxy" + ad1 = MockAddon(source="act1") - self.probeManager.update(ad1_address, ad1) - assert act1.MockActionUpdate == ad1, "Action should have been updated" - assert act2.MockActionUpdate is None, "Should not update on inactive" + #ErrorCase: update should fail without a source + self.assertRaises(RuntimeWarning, self.probeManager.update, "SomeAddress", ad1) + #ErrorCase: update should fail if the source does not exist + self.assertRaises(RuntimeWarning, self.probeManager.update, "unique:SomeAddress", ad1) - self.probeManager.currentActivity = "act2" - self.probeManager.uninstall(ad1_address) - assert act1.MockActionAddress == ad1_address, "Action should still be installed" + self.probeManager.install(ad1, None, None) + + assert act1.MockAction == ad1, "Action should have been installed" + + self.probeManager.uninstall("unique_id_2:SomeAddress") + assert act1.MockAction == ad1, "Action should still be installed" - self.probeManager.currentActivity = "act1" - self.probeManager.uninstall(ad1_address) + self.probeManager.uninstall("unique_id_1:SomeAddress") assert act1.MockAction is None, "Action should be uninstalled" - def test_events(self): + def test_action_install(self): self.probeManager.register_probe("act1", "unique_id_1") self.probeManager.register_probe("act2", "unique_id_2") act1 = self.probeManager.get_registered_probes_list("act1")[0][1] act2 = self.probeManager.get_registered_probes_list("act2")[0][1] + ad1 = MockAddon(source="act1") + ad2 = MockAddon(source="act2") + ad3 = MockAddon(source="act3") + ad4 = MockAddon() + + #ErrorCase: install should fail without a source + self.assertRaises(RuntimeWarning, self.probeManager.install, ad4, None, None) + #ErrorCase: install should fail if the source does not exist + self.assertRaises(RuntimeWarning, self.probeManager.install, ad3, None, None) + + #install should put the action in the right probes + self.probeManager.install(ad1, None, None) + assert act1.MockAction is ad1, "Action should be on act1" + assert act2.MockAction is not ad1, "Action should not be on act2" + + self.probeManager.install(ad2, None, None) + assert act1.MockAction is not ad2, "Action should not be on act1" + assert act2.MockAction is ad2, "Action should be on act2" + + def test_action_update(self): + self.probeManager.register_probe("act1", "unique_id_1") + self.probeManager.register_probe("act2", "unique_id_2") + act1 = self.probeManager.get_registered_probes_list("act1")[0][1] + act2 = self.probeManager.get_registered_probes_list("act2")[0][1] ad1 = MockAddon() - ad2 = MockAddon() + + #ErrorCase: update should fail without a source + self.assertRaises(RuntimeWarning, self.probeManager.update, "SomeAddress", ad1) + #ErrorCase: update should fail if the source does not exist + self.assertRaises(RuntimeWarning, self.probeManager.update, "unique:SomeAddress", ad1) + + #update should update the right probe + self.probeManager.update("unique_id_1:Address", ad1) + assert act1.MockActionUpdate is ad1, "Action should be on act1" + assert act2.MockActionUpdate is not ad1, "Action should not be on act1" + + def test_event_unsubscribe(self): + self.probeManager.register_probe("act1", "unique_id_1") + self.probeManager.register_probe("act2", "unique_id_2") + act1 = self.probeManager.get_registered_probes_list("act1")[0][1] + + ad1 = MockAddon(source="act1") + ad2 = MockAddon(source="act2") ad2.i, ad2.s = (2, "test2") cb1 = lambda *args: None @@ -395,22 +423,43 @@ class ProbeManagerTest(unittest.TestCase): error_cb1 = lambda *args:None cb2 = lambda *args: None - #ErrorCase: unsubscribe and subscribe without current activity - #Event functions should do a warning if there is no activity - self.assertRaises(RuntimeWarning, self.probeManager.subscribe, ad1, cb1, install_cb1, error_cb1) - self.assertRaises(RuntimeWarning, self.probeManager.unsubscribe, None) - assert act1.MockEvent is None, "No event should be on act1" - assert act2.MockEvent is None, "No event should be on act2" + #ErrorCase: unsubscribe should fail without a source + self.assertRaises(RuntimeWarning, self.probeManager.unsubscribe, "SomeAddress") + #ErrorCase: unsubscribe should fail silently if the source does not exist + self.probeManager.unsubscribe("unique:SomeAddress") + assert act1.MockEventAddr is None, "No unsubcribe should have been called" - self.probeManager.currentActivity = "act1" self.probeManager.subscribe(ad1, cb1, install_cb1, error_cb1) assert act1.MockEvent == ad1, "Event should have been installed" assert act1.MockCB == cb1, "Callback should have been set" - assert act2.MockEvent is None, "No event should be on act2" - self.probeManager.unsubscribe("SomeAddress") + self.probeManager.unsubscribe("unique_id_1:SomeAddress") assert act1.MockEventAddr == "SomeAddress", "Unsubscribe should have been called" - assert act2.MockEventAddr is None, "Unsubscribe should not have been called" + + def test_event_subscribe(self): + self.probeManager.register_probe("act1", "unique_id_1") + self.probeManager.register_probe("act2", "unique_id_2") + act1 = self.probeManager.get_registered_probes_list("act1")[0][1] + act2 = self.probeManager.get_registered_probes_list("act2")[0][1] + + ad1 = MockAddon(source="act1") + ad2 = MockAddon(source="act2") + ad3 = MockAddon(source="act3") + ad4 = MockAddon() + + #ErrorCase: subscribe should fail without a source + self.assertRaises(RuntimeWarning, self.probeManager.subscribe, ad4, None, None, None) + #ErrorCase: subscribe should fail if the source does not exist + self.assertRaises(RuntimeWarning, self.probeManager.subscribe, ad3, None, None, None) + #subscribe should put the action in the right probes + self.probeManager.subscribe(ad1, None, None, None) + assert act1.MockEvent is ad1, "Action should be on act1" + assert act2.MockEvent is not ad1, "Action should not be on act2" + + self.probeManager.subscribe(ad2, None, None, None) + assert act1.MockEvent is not ad2, "Action should not be on act1" + assert act2.MockEvent is ad2, "Action should be on act2" + class ProbeProxyTest(unittest.TestCase): def setUp(self): diff --git a/tests/translatortests.py b/tests/translatortests.py index 3b5ca6f..05a7831 100644 --- a/tests/translatortests.py +++ b/tests/translatortests.py @@ -61,7 +61,7 @@ class ResourceTranslatorTests(unittest.TestCase): self.fsm = Tutorial("TestTutorial1") # Add a few states act1 = addon.create('BubbleMessage', message="Hi", position=[300, 450]) - ev1 = addon.create('GtkWidgetEventFilter', "0.12.31.2.2", "clicked") + ev1 = addon.create('GtkWidgetEventFilter', object_id="0.12.31.2.2", event_name="clicked") act2 = addon.create('BubbleMessage', message="Second message", position=[250, 150], tail_pos=[1,2]) self.fsm.add_action("INIT", act1) st2 = self.fsm.add_state((act2,)) diff --git a/tests/vaulttests.py b/tests/vaulttests.py index 729d36d..1e39d8c 100644 --- a/tests/vaulttests.py +++ b/tests/vaulttests.py @@ -106,7 +106,7 @@ class VaultInterfaceTest(unittest.TestCase): self.fsm = Tutorial("TestTutorial1") # Add a few states act1 = addon.create('BubbleMessage', message="Hi", position=[300, 450]) - ev1 = addon.create('GtkWidgetEventFilter', "0.12.31.2.2", "clicked") + ev1 = addon.create('GtkWidgetEventFilter', object_id="0.12.31.2.2", event_name="clicked") act2 = addon.create('BubbleMessage', message="Second message", position=[250, 150], tail_pos=[1,2]) self.fsm.add_action("INIT", act1) st2 = self.fsm.add_state((act2,)) @@ -487,7 +487,7 @@ class XMLSerializerTest(unittest.TestCase): # Add a few states act1 = addon.create('BubbleMessage', message="Hi", position=[300, 450]) - ev1 = addon.create('GtkWidgetEventFilter', "0.12.31.2.2", "clicked") + ev1 = addon.create('GtkWidgetEventFilter', object_id="0.12.31.2.2", event_name="clicked") act2 = addon.create('BubbleMessage', message="Second message", position=[250, 150], tail_pos=[1,2]) self.fsm.add_action("INIT",act1) @@ -535,12 +535,12 @@ class XMLSerializerTest(unittest.TestCase): """ fsm = Tutorial("TestActions") tuto_file = cStringIO.StringIO() - act1 = addon.create('BubbleMessage', "Hi!", position=[10,120], tail_pos=[-12,30]) - act2 = addon.create('DialogMessage', "Hello again.", position=[120,10]) + act1 = addon.create('BubbleMessage', message="Hi!", position=[10,120], tail_pos=[-12,30]) + act2 = addon.create('DialogMessage', message="Hello again.", position=[120,10]) act3 = addon.create('WidgetIdentifyAction') - act4 = addon.create('DisableWidgetAction', "0.0.0.1.0.0.0") - act5 = addon.create('TypeTextAction', "0.0.0.1.1.1.0.0", "New text") - act6 = addon.create('ClickAction', "0.0.1.0.1.1") + act4 = addon.create('DisableWidgetAction', target="0.0.0.1.0.0.0") + act5 = addon.create('TypeTextAction', widget="0.0.0.1.1.1.0.0", text="New text") + act6 = addon.create('ClickAction', widget="0.0.1.0.1.1") act7 = addon.create('OnceWrapper', action=act1) act8 = addon.create('ChainAction', actions=[act1, act2, act3, act4]) actions = [act1, act2, act3, act4, act5, act6, act7, act8] @@ -565,10 +565,10 @@ class XMLSerializerTest(unittest.TestCase): fsm = Tutorial("TestFilters") tuto_file = cStringIO.StringIO() - ev1 = addon.create('TimerEvent', 1000) + ev1 = addon.create('TimerEvent', timeout=1000) ev2 = addon.create('GtkWidgetEventFilter', object_id="0.0.1.1.0.0.1", event_name="clicked") - ev3 = addon.create('GtkWidgetTypeFilter', "0.0.1.1.1.2.3", text="Typed stuff") - ev4 = addon.create('GtkWidgetTypeFilter', "0.0.1.1.1.2.3", strokes="acbd") + ev3 = addon.create('GtkWidgetTypeFilter', object_id="0.0.1.1.1.2.3", text="Typed stuff") + ev4 = addon.create('GtkWidgetTypeFilter', object_id="0.0.1.1.1.2.3", strokes="acbd") filters = [ev1, ev2, ev3, ev4] for efilter in filters: diff --git a/tutorius/TProbe.py b/tutorius/TProbe.py index acba26f..2f99329 100644 --- a/tutorius/TProbe.py +++ b/tutorius/TProbe.py @@ -47,6 +47,10 @@ import copy -------------------- ---------- """ + +#Prefix separator for action/event addresses +PSEP=":" + #TODO Add stub error handling for remote calls in the classes so that it will # be clearer how errors can be handled in the future. @@ -429,6 +433,7 @@ class ProbeProxy: bus = dbus.SessionBus() self._object = bus.get_object(activityName, "/tutorius/Probe/"+str(unique_id)) self._probe = dbus.Interface(self._object, "org.tutorius.ProbeInterface") + self.prefix = str(unique_id)+PSEP self._actions = {} self._edition_callbacks = {} @@ -480,7 +485,7 @@ class ProbeProxy: if editing_cb: self._edition_callbacks[address] = editing_cb # Propagate the action installed callback upwards in the stack - callback(address) + callback(self.prefix + address) def __clear_action(self, address): # Remove the action installed at this address @@ -563,7 +568,7 @@ class ProbeProxy: # our dictionary (python pass arguments by reference) self._subscribedEvents[address] = copy.copy(event) - event_subscribed_cb(address) + event_subscribed_cb(self.prefix + address) return address def __clear_event(self, address): @@ -679,6 +684,12 @@ class ProbeManager(object): currentActivity = property(fget=getCurrentActivity, fset=setCurrentActivity) + def get_source_activity(self, propc): + if hasattr(propc, "source"): + return propc.source + else: + return None + def install(self, action, action_installed_cb, error_cb, is_editing=False, editing_cb=None): """ Install an action on the current activity @@ -691,8 +702,13 @@ class ProbeManager(object): this action (only used when is_editing is true) @return None """ - if self.currentActivity: - return self._first_proxy(self.currentActivity).install( + activity = self.get_source_activity(action) + #Allow the creator to look for the current activity + if activity is None and is_editing: + activity = self.currentActivity + + if activity: + return self._first_proxy(activity).install( action=action, is_editing=is_editing, action_installed_cb=action_installed_cb, @@ -710,8 +726,15 @@ class ProbeManager(object): @param is_editing whether this action comes from the editor @return None """ - if self.currentActivity: - return self._first_proxy(self.currentActivity).update(action_address, newaction, is_editing) + probe_id, sep, address = action_address.rpartition(PSEP) + if probe_id: + probe = self._get_proxy_by_unique_id(probe_id) + if probe is None: + #TODO What happens if the Probe is gone?? + raise RuntimeWarning("ProbeProxy containing action address is gone") + else: + return probe.update(address, newaction, is_editing) + else: raise RuntimeWarning("No activity attached") @@ -722,8 +745,16 @@ class ProbeManager(object): @param block Force a synchroneous dbus call if True @param is_editing whether this action comes from the editor """ - if self.currentActivity: - return self._first_proxy(self.currentActivity).uninstall(action_address, is_editing) + probe_id, sep, address = action_address.rpartition(PSEP) + if probe_id: + probe = self._get_proxy_by_unique_id(probe_id) + if probe is None: + logging.warning( + "ProbeProxy for address %s is gone, assuming uninstall not necessary" % \ + action_address) + else: + return probe.uninstall(address, is_editing) + else: raise RuntimeWarning("No activity attached") @@ -751,8 +782,10 @@ class ProbeManager(object): installation @return address identifier used for unsubscribing """ - if self.currentActivity: - return self._first_proxy(self.currentActivity).subscribe(event, notification_cb,\ + activity = self.get_source_activity(event) + + if activity: + return self._first_proxy(activity).subscribe(event, notification_cb,\ event_subscribed_cb, error_cb) else: raise RuntimeWarning("No activity attached") @@ -763,8 +796,15 @@ class ProbeManager(object): @param address identifier given by subscribe() @return None """ - if self.currentActivity: - return self._first_proxy(self.currentActivity).unsubscribe(address) + probe_id, sep, address = address.rpartition(PSEP) + if probe_id: + probe = self._get_proxy_by_unique_id(probe_id) + if probe is None: + logging.warning( + "ProbeProxy for address %s is gone, assuming unsubscribe not necessary" % \ + address) + else: + return probe.unsubscribe(address) else: raise RuntimeWarning("No activity attached") @@ -825,4 +865,14 @@ class ProbeManager(object): else: raise RuntimeWarning("No activity attached under '%s'", process_name) - + def _get_proxy_by_unique_id(self, unique_id): + """ + Get a probe proxy by it's unique id. + @param unique_id The unique id of the probe + @return the probe proxy or None if not found + """ + for probes in self._probes.values(): + for id, probe in probes: + if id == unique_id: + return probe + return None diff --git a/tutorius/actions.py b/tutorius/actions.py index cf586f2..6d1f58e 100644 --- a/tutorius/actions.py +++ b/tutorius/actions.py @@ -165,9 +165,9 @@ class DragWrapper(object): class Action(TPropContainer): """Base class for Actions""" - def __init__(self): - TPropContainer.__init__(self) - self.position = (0,0) + source = TStringProperty(None, null=True) + def __init__(self, **kwargs): + super(Action, self).__init__(**kwargs) self._drag = None # The callback that will be triggered when the action is requested # to notify all its changes diff --git a/tutorius/engine.py b/tutorius/engine.py index 198fa11..c0769b5 100644 --- a/tutorius/engine.py +++ b/tutorius/engine.py @@ -77,14 +77,9 @@ class TutorialRunner(object): #Temp FIX until event/actions have an activity id self._activity_id = None - #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) def stop(self): @@ -141,6 +136,8 @@ class TutorialRunner(object): # Send all the event registration for (event_name, (event, next_state)) in transitions.items(): + if hasattr(event, "source") and not event.source: + event.source = self._activity_id self._pM.subscribe(event, save_args(self._handleEvent, next_state), save_args(self.event_subscribed, event_name), @@ -153,7 +150,6 @@ class TutorialRunner(object): ########################################################################### # Helper functions def _execute_stop(self): - self.setCurrentActivity() #Temp Hack until activity in events/actions self._teardownState() self._state = None self._runner_state = RUNNER_STATE_IDLE @@ -253,6 +249,8 @@ class TutorialRunner(object): for (action_name, action) in actions.items(): LOGGER.debug("TutorialRunner :: Installed action %s"%(action_name)) + if hasattr(action, "source") and not action.source: + action.source = self._activity_id self._pM.install(action, save_args(self.action_installed, action_name), save_args(self.install_error, action_name)) @@ -269,7 +267,6 @@ class TutorialRunner(object): @param state_name The name of the state to enter in """ - self.setCurrentActivity() #Temp Hack until activity in events/actions # Set the runner state to actions setup self._runner_state = RUNNER_STATE_SETUP_ACTIONS diff --git a/tutorius/filters.py b/tutorius/filters.py index 38cf86b..6ef8867 100644 --- a/tutorius/filters.py +++ b/tutorius/filters.py @@ -25,12 +25,12 @@ class EventFilter(properties.TPropContainer): """ Base class for an event filter """ - - def __init__(self): + source = properties.TStringProperty(None, null=True) + def __init__(self, **kwargs): """ Constructor. """ - super(EventFilter, self).__init__() + super(EventFilter, self).__init__(**kwargs) self._callback = None def install_handlers(self, callback, **kwargs): diff --git a/tutorius/properties.py b/tutorius/properties.py index bfdb32c..a0d63bb 100644 --- a/tutorius/properties.py +++ b/tutorius/properties.py @@ -49,12 +49,14 @@ class TPropContainer(object): at the cost of needing a mapping between container instances, and property values. This is what TPropContainer does. """ - def __init__(self): + def __init__(self, **kwargs): """ Prepares the instance for property value storage. This is done at object initialization, thus allowing initial mapping of properties declared on the class. Properties won't work correctly without this call. + + Keyword arguments will be evaluated as properties """ # create property value storage object.__setattr__(self, "_props", {}) @@ -74,6 +76,10 @@ class TPropContainer(object): # to the creator to update its action edition dialog. self._diff_dict = {} + #Set attribute values that were supplied + for key, value in kwargs.items(): + setattr(self, key, value) + def __getattribute__(self, name): """ Process the 'fake' read of properties in the appropriate instance @@ -254,11 +260,15 @@ class TStringProperty(TutoriusProperty): Represents a string. Can have a maximum size limit. """ widget_class = StringPropWidget - def __init__(self, value, size_limit=None): + def __init__(self, value, size_limit=None, null=False): TutoriusProperty.__init__(self) self.type = "string" - self.size_limit = MaxSizeConstraint(size_limit) - self.string_type = StringTypeConstraint() + if size_limit: + self.size_limit = MaxSizeConstraint(size_limit) + if null: + self.string_type = TypeConstraint((str, type(None))) + else: + self.string_type = StringTypeConstraint() self.default = self.validate(value) -- cgit v0.9.1