From 56aa5ada82c014ca0c7cdc6675b93496e0a6967a Mon Sep 17 00:00:00 2001 From: Vincent Vinet Date: Sun, 20 Sep 2009 14:02:06 +0000 Subject: merge in erick's TProbe work, add a very basic unit test --- diff --git a/addons/EmbeddedInterpreter.py b/addons/EmbeddedInterpreter.py new file mode 100644 index 0000000..8c3522e --- /dev/null +++ b/addons/EmbeddedInterpreter.py @@ -0,0 +1,30 @@ +from sugar.tutorius.actions import Action +from sugar.tutorius.editor_interpreter import EditorInterpreter +from sugar.tutorius.services import ObjectStore + +class EmbeddedInterpreter(Action): + def __init__(self): + Action.__init__(self) + self.activity = None + self._dialog = None + + def do(self): + os = ObjectStore() + if os.activity: + self.activity = os.activity + + self._dialog = EditorInterpreter(self.activity) + self._dialog.show() + + + def undo(self): + if self._dialog: + self._dialog.destroy() + +__action__ = { + "name" : "EmbeddedInterpreter", + "display_name" : "Embedded Interpreter", + "icon" : "message-bubble", + "class" : EmbeddedInterpreter, + "mandatory_props" : [] +} diff --git a/addons/WidgetIdentifier.py b/addons/WidgetIdentifier.py new file mode 100644 index 0000000..3c559b5 --- /dev/null +++ b/addons/WidgetIdentifier.py @@ -0,0 +1,35 @@ +from sugar.tutorius.actions import Action +from sugar.tutorius.editor import WidgetIdentifier as WIPrimitive +from sugar.tutorius.services import ObjectStore + +class WidgetIdentifier(Action): + def __init__(self): + Action.__init__(self) + self.activity = None + self._dialog = None + + def do(self): + os = ObjectStore() + if os.activity: + self.activity = os.activity + + self._dialog = WIPrimitive(self.activity) + self._dialog.show() + + + def undo(self): + if self._dialog: + # TODO elavoie 2009-07-19 + # We should disconnect the handlers, however there seems to be an error + # saying that the size of the dictionary changed during the iteration + # We should investigate this + #self._dialog._disconnect_handlers() + self._dialog.destroy() + +__action__ = { + "name" : "WidgetIdentifier", + "display_name" : "Widget Identifier", + "icon" : "message-bubble", + "class" : WidgetIdentifier, + "mandatory_props" : [] +} diff --git a/addons/eventgenerator.py b/addons/eventgenerator.py new file mode 100644 index 0000000..a91ccf4 --- /dev/null +++ b/addons/eventgenerator.py @@ -0,0 +1,60 @@ +# 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 * +from sugar.tutorius.gtkutils import find_widget + +class EventGenerator(Action): + source = TUAMProperty("") + type = TStringProperty("clicked") + + def __init__(self, source=None, type="clicked"): + Action.__init__(self) + + if source != None: + self.source = source + + if type != "clicked": + self.type = type + + def do(self): + self._activity = ObjectStore().activity + + # TODO elavoie 2009-07-25 We should eventually use the UAM mecanism + widget = find_widget(self._activity, self.source) + + + # TODO elavoie 2009-07-25 We assume a gtk activity, it might + # get messier with other widget systems + + # Call the signal on the widget + # We use introspection here to obtain the + # method that will send the corresponding + # signal on the gtk object + getattr(widget, self.type)() + + # That's all!!! + + def undo(self): + pass + +__action__ = { + "name" : "EventGenerator", + "display_name" : "Event Generator", + "icon" : "message-bubble", + "class" : EventGenerator, + "mandatory_props" : ["source", "type"] +} + diff --git a/tests/inject.py b/tests/inject.py new file mode 100644 index 0000000..d69d6ff --- /dev/null +++ b/tests/inject.py @@ -0,0 +1,57 @@ +#Test event injection + +import gtk +import gobject +import time +import types + +class ClickMaster(): + def __init__(self): + self.event = None + + def connect(self, button): + self.id = button.connect("pressed",self.capture_event) + self.id2 = button.connect("released",self.capture_event2) + self.id3 = button.connect("clicked",self.capture_event3) + self.button = button + + def capture_event(self, *args): + print "Capture Event" + print args + self.eventPress = args[-1] + return False + + def capture_event2(self, *args): + print "Capture Release" + print args + self.eventReleased = args[-1] + return False + + def capture_event3(self, *args): + print "Capture Clicked" + print args + self.eventClicked = args[-1] + return False + + def inject_event(self): + print "Injecting" + print self.event + #self.event.put() + self.button.emit("button_press_event", self.event) + +def print_Event(event): + for att in dir(event): + if not isinstance(att, types.MethodType): + print att, getattr(event, att) + +if __name__=='__main__': + w = gtk.Window() + b = gtk.CheckButton("Auto toggle!") + c=ClickMaster() + w.add(b) + b.show() + c.connect(b) + + w.show() + + gtk.main() diff --git a/tests/probetests.py b/tests/probetests.py new file mode 100644 index 0000000..3a55f82 --- /dev/null +++ b/tests/probetests.py @@ -0,0 +1,61 @@ +# 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 +""" +Probe Tests + +""" + +import unittest +import os, sys +import gtk +import time + +from dbus.mainloop.glib import DBusGMainLoop + +from sugar.tutorius.TProbe import TProbe, ProbeProxy + + +class FakeActivity(object): + def __init__(self): + self.top = gtk.Window(type=gtk.WINDOW_TOPLEVEL) + self.top.set_name("Top") + + hbox = gtk.HBox() + self.top.add(hbox) + hbox.show() + + btn1 = gtk.Button() + btn1.set_name("Button1") + hbox.pack_start(btn1) + btn1.show() + self.button = btn1 + +class ProbeTest(unittest.TestCase): + def test_ping(self): + pid = os.fork() + m = DBusGMainLoop(set_as_default=True) + activity = FakeActivity() + probe = TProbe("localhost.unittest.ProbeTest", activity.top) + + #Parent, ping the probe + proxy = ProbeProxy("localhost.unittest.ProbeTest") + res = probe.ping() + + assert res == "alive", "Probe should be alive" + +if __name__ == "__main__": + unittest.main() + diff --git a/tests/run-tests.py b/tests/run-tests.py index d41aa0a..23d7e24 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -2,9 +2,9 @@ # This is a dumb script to run tests on the sugar-jhbuild installed files # The path added is the default path for the jhbuild build -INSTALL_PATH="../../../../../../install/lib/python2.5/site-packages/" import os, sys +INSTALL_PATH=os.path.join(os.path.dirname(__file__),"../../sugar-jhbuild/install/lib/python2.6/site-packages/") sys.path.insert(0, os.path.abspath(INSTALL_PATH) ) @@ -40,6 +40,7 @@ if __name__=='__main__': import constraintstests import propertiestests import serializertests + import probetests suite = unittest.TestSuite() suite.addTests(unittest.findTestCases(coretests)) suite.addTests(unittest.findTestCases(servicestests)) @@ -52,6 +53,7 @@ if __name__=='__main__': suite.addTests(unittest.findTestCases(constraintstests)) suite.addTests(unittest.findTestCases(propertiestests)) suite.addTests(unittest.findTestCases(serializertests)) + suite.addTests(unittest.findTestCases(probetests)) runner = unittest.TextTestRunner() runner.run(suite) coverage.stop() @@ -70,5 +72,6 @@ if __name__=='__main__': from propertiestests import * from actiontests import * from serializertests import * + from probetests import * unittest.main() diff --git a/tutorius/TProbe.py b/tutorius/TProbe.py new file mode 100644 index 0000000..6dd3afb --- /dev/null +++ b/tutorius/TProbe.py @@ -0,0 +1,360 @@ +import gobject + +import dbus +import cPickle as pickle + +import sugar.tutorius.addon as addon + +from sugar.tutorius.services import ObjectStore + +import copy + +""" +The TProbe module defines two connected classes, TProbe and ProbeProxy. + + -------------------- ---------- + | ProbeProxy |----- DBus ---->| TProbe | + -------------------- ---------- + +""" + +class TProbe(dbus.service.Object): + """ Tutorius Probe + Defines an entry point for Tutorius into activities that allows + performing actions and registering events onto an activity via + a DBUS Interface. + + Exposes the following dbus methods: + void registered(string service) + string ping() -> status + string install(string action) -> address + void update(string address, string action_props) + void uninstall(string address) + string subscribe(string pickled_event) -> address + void unsubscribe(string address) + + Exposes the following dbus Events: + eventOccured(event): + + """ + + def __init__(self, activity_name, activity): + """ + Create and register a TProbe for an activity. + + @param activity_name unique activity_id + @param activity activity reference, must be a gtk container + """ + # Moving the ObjectStore assignment here, in the meantime + # the reference to the activity shouldn't be share as a + # global variable but passed by the Probe to the objects + # that requires it + self._activity = activity + + ObjectStore().activity = activity + + self._activity_name = activity_name + self._session_bus = dbus.SessionBus() + + # Giving a new name because _name is already used by dbus + self._name2 = dbus.service.BusName(activity_name, self._session_bus) + dbus.service.Object.__init__(self, self._session_bus, "/tutorius/Probe") + + # Add the dictionary we will use to store which actions and events + # are known + self._installedActions = {} + self._subscribedEvents = {} + + def start(self): + """ + Optional method to call if the probe is not inserted into an + existing activity. Starts a gobject mainloop + """ + mainloop = gobject.MainLoop() + print "Starting Probe for " + self._activity_name + mainloop.run() + + @dbus.service.method("org.tutorius.ProbeInterface", + in_signature='s', out_signature='') + def registered(self, service): + print ("Registered with: " + str(service)) + + @dbus.service.method("org.tutorius.ProbeInterface", + in_signature='', out_signature='s') + def ping(self): + """ + Allows testing the connection to a Probe + @return string "alive" + """ + return "alive" + + # ------------------ Action handling -------------------------------------- + @dbus.service.method("org.tutorius.ProbeInterface", + in_signature='s', out_signature='s') + def install(self, pickled_action): + """ + Install an action on the Activity + @param pickled_action string pickled action + @return string address of installed action + """ + loaded_action = pickle.loads(str(pickled_action)) + action = addon.create(loaded_action.__class__.__name__) + + address = self._generate_action_reference(action) + + self._installedActions[address] = action + + if action._props: + action._props.update(loaded_action._props) + + action.do() + + return address + + @dbus.service.method("org.tutorius.ProbeInterface", + in_signature='ss', out_signature='') + def update(self, address, action_props): + """ + Update an already registered action + @param address string address returned by install() + @param action_props pickled action properties + @return None + """ + action = self._installedActions[address] + + if action._props: + props = pickle.loads(str(action_props)) + action._props.update(props) + action.undo() + action.do() + + @dbus.service.method("org.tutorius.ProbeInterface", + in_signature='s', out_signature='') + def uninstall(self, address): + """ + Uninstall an action + @param address string address returned by install() + @return None + """ + if self._installedActions.has_key(address): + action = self._installedActions[address] + action.undo() + self._installedActions.pop(address) + + + # ------------------ Event handling --------------------------------------- + @dbus.service.method("org.tutorius.ProbeInterface", + in_signature='s', out_signature='s') + def subscribe(self, pickled_event): + """ + Subscribe to a Gtk Widget Event + @param pickled_event string pickled Event + @return string unique name of registered event + """ + event = pickle.loads(str(pickled_event)) + + # TODO elavoie 2009-07-25 Move to a reference counting implementation + # to avoid duplicating eventfilters when the event signature is the + # same + + # For now we will assume every probe is inserted in a GTK activity, + # however, in the future this should be moved in a subclass + eventfilter = addon.create("GtkWidgetEventFilter") + + # There might be a validation of the Address in source in the future + # and a partial resolution to extract the object_id from the address + eventfilter.object_id = event.source + + # TODO elavoie 2009-07-19 + # There should be a type translation from a tutorius type + # to a GTK type here + eventfilter.event_name = event.type + + # The callback uses the event defined previously and each + # successive call to subscribe will register a different + # callback that references a different event + def callback(*args): + self.notify(event) + + eventfilter.install_handlers(callback, activity=self._activity) + + name = self._generate_event_reference(event) + self._subscribedEvents[name] = eventfilter + + return name + + @dbus.service.method("org.tutorius.ProbeInterface", + in_signature='s', out_signature='') + def unsubscribe(self, address): + """ + Remove subscription to an event + @param address string adress returned by subscribe() + @return None + """ + + if self._subscribedEvents.has_key(address): + eventfilter = self._subscribedEvents[address] + eventfilter.remove_handlers() + self._subscribedEvents.pop(address) + + @dbus.service.signal("org.tutorius.ProbeInterface") + def eventOccured(self, event): + # We need no processing now, the signal will be sent + # when the method exit + pass + + # The actual method we will call on the probe to send events + def notify(self, event): + self.eventOccured(pickle.dumps(event)) + + # Return a unique name for this action + def _generate_action_reference(self, action): + # TODO elavoie 2009-07-25 Should return a universal address + name = action.__class__.__name__ + suffix = 1 + + while self._installedActions.has_key(name+str(suffix)): + suffix += 1 + + return name + str(suffix) + + + # Return a unique name for this event + def _generate_event_reference(self, event): + # TODO elavoie 2009-07-25 Should return a universal address + name = event.type + suffix = 1 + + while self._subscribedEvents.has_key(name+str(suffix)): + suffix += 1 + + return name + str(suffix) + + + +class ProbeProxy: + """ + ProbeProxy is a Proxy class for connecting to a remote TProbe. + + It provides an object interface to the TProbe, which requires pickled + strings, across a DBus communication. + + Public Methods: + ProbeProxy(string activityName) :: Constructor + string install(Action action) -> action address + void update(string address, Action action) + void uninstall(string address) + string subscribe(Event event, callable callback) -> event address + void unsubscribe(string address) + """ + def __init__(self, activityName): + """ + Constructor + @param activityName unique activity id + """ + bus = dbus.SessionBus() + self._object = bus.get_object(activityName, "/tutorius/Probe") + self._probe = dbus.Interface(self._object, "org.tutorius.ProbeInterface") + + # We keep those two data structures to be able to have multiple callbacks + # for the same event and be able to remove them independently + self._subscribedEvents = {} + self._registeredCallbacks = {} + + def _handle_signal(pickled_event): + event = pickle.loads(str(pickled_event)) + if self._registeredCallbacks.has_key(event): + for callback in self._registeredCallbacks[event].itervalues(): + callback(event) + + self._object.connect_to_signal("eventOccured", _handle_signal, dbus_interface="org.tutorius.ProbeInterface") + + def install(self, action): + """ + Install an action on the TProbe's activity + @param action Action to install + @return address identifier used for update and uninstall + """ + address = str(self._probe.install(pickle.dumps(action))) + return address + + def update(self, address, action): + """ + Update an already installed action's properties and run it again + @param address identifier returned by the action install + @param action Action to get properties from + @return None + """ + self._probe.update(address, pickle.dumps(action._props)) + + def uninstall(self, address): + """ + Uninstall an installd action + @param address identifier returned by the action install + @return None + """ + self._probe.uninstall(address) + + def subscribe(self, event, callback): + """ + Register an event listener + @param event Event to listen for + @param callback callable that will be called when the event occurs + @return address identifier used for unsubscribing + """ + # TODO elavoie 2009-07-25 When we will allow for patterns both + # for event types and sources, we will need to revise the lookup + # mecanism for which callback function to call + + + # Since multiple callbacks could be associated to the same + # event signature, we will store multiple callbacks + # in a dictionary indexed by the unique address + # given for this subscribtion and access this + # dictionary from another one indexed by event + address = str(self._probe.subscribe(pickle.dumps(event))) + + # We use the event object as a key + if not self._registeredCallbacks.has_key(event): + self._registeredCallbacks[event] = {} + + # TODO elavoie 2009-07-25 decide on a proper exception + # taxonomy + if self._registeredCallbacks[event].has_key(address): + # Oups, how come we have two similar addresses? + # send the bad news! + raise Exception("Probe subscribe exception, the following address already exists: " + str(address)) + + self._registeredCallbacks[event][address] = callback + + # We will keep another dictionary to remember the + # event that was associated to this unique address + # Let's copy to make sure that even if the event + # passed in is modified later it won't screw up + # our dictionary (python pass arguments by reference) + self._subscribedEvents[address] = copy.copy(event) + + return address + + def unsubscribe(self, address): + """ + Unregister an event listener + @param address identifier given by subscribe() + @return None + """ + self._probe.unsubscribe(address) + + # Cleanup everything + if self._subscribedEvents.has_key(address): + event = self._subscribedEvents[address] + + if self._registeredCallbacks.has_key(event)\ + and self._registeredCallbacks[event].has_key(address): + self._registeredCallbacks[event].pop(address) + + if self._registeredCallbacks[event] == {}: + self._registeredCallbacks.pop(event) + + self._subscribedEvents.pop(address) + + diff --git a/tutorius/actions.py b/tutorius/actions.py index 4269cd7..eb4a6b1 100644 --- a/tutorius/actions.py +++ b/tutorius/actions.py @@ -82,6 +82,7 @@ class DragWrapper(object): """Callback for end of drag (stolen focus).""" self._dragging = False + def set_draggable(self, value): """Setter for the draggable property""" if bool(value) ^ bool(self._drag_on): @@ -177,6 +178,15 @@ class Action(TPropContainer): self.position = [int(x), int(y)] self.__edit_img.destroy() + # Adding methods for pickling and unpickling an object with + # properties + def __getstate__(self): + return self._props.copy() + + def __setstate__(self, dict): + self._props.update(dict) + + class OnceWrapper(Action): """ Wraps a class to perform an action once only diff --git a/tutorius/editor_interpreter.py b/tutorius/editor_interpreter.py new file mode 100644 index 0000000..d559266 --- /dev/null +++ b/tutorius/editor_interpreter.py @@ -0,0 +1,105 @@ +# Copyright (C) 2009, Tutorius.org +# Greatly influenced by sugar/activity/namingalert.py +# +# 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 +""" Tutorial Editor Interpreter Module +""" + +import gtk +import pango +from sugar.tutorius.ipython_view import * + +from gettext import gettext as _ + +class EditorInterpreter(gtk.Window): + """ + Interpreter that will be shown to the user + """ + __gtype_name__ = 'TutoriusEditorInterpreter' + + def __init__(self, activity=None): + gtk.Window.__init__(self) + + self._activity = activity + + # Set basic properties of window + self.set_decorated(False) + self.set_resizable(False) + self.set_modal(False) + + # Connect to realize signal from ? + self.connect('realize', self.__realize_cb) + + # Use an expander widget to allow minimizing + self._expander = gtk.Expander(_("Editor Interpreter")) + self._expander.set_expanded(True) + self.add(self._expander) + self._expander.connect("notify::expanded", self.__expander_cb) + + + # Use the IPython widget to embed + self.interpreter = IPythonView() + self.interpreter.set_wrap_mode(gtk.WRAP_CHAR) + + # Expose the activity object in the interpreter + self.interpreter.updateNamespace({'activity':self._activity}) + + # Use a scroll window to permit scrolling of the interpreter prompt + swd = gtk.ScrolledWindow() + swd.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + swd.add(self.interpreter) + self.interpreter.show() + + # Notify GTK that expander is ready + self._expander.add(swd) + self._expander.show() + + # Notify GTK that the scrolling window is ready + swd.show() + + def __expander_cb(self, *args): + """Callback for the window expander toggling""" + if self._expander.get_expanded(): + self.__move_expanded() + else: + self.__move_collapsed() + + def __move_expanded(self): + """Move the window to it's expanded position""" + swidth = gtk.gdk.screen_width() + sheight = gtk.gdk.screen_height() + # leave room for the scrollbar at the right + width = swidth - 20 + height = 200 + + self.set_size_request(width, height) + # Put at the bottom of the screen + self.move(0, sheight-height) + + def __move_collapsed(self): + """Move the window to it's collapsed position""" + width = 150 + height = 40 + swidth = gtk.gdk.screen_width() + sheight = gtk.gdk.screen_height() + + self.set_size_request(width, height) + self.move(((swidth-width)/2)-150, sheight-height) + + def __realize_cb(self, widget): + """Callback for realize""" + self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) + self.window.set_accept_focus(True) + self.__move_expanded() diff --git a/tutorius/events.py b/tutorius/events.py new file mode 100644 index 0000000..bf0a8b9 --- /dev/null +++ b/tutorius/events.py @@ -0,0 +1,36 @@ +from sugar.tutorius.properties import * + +class Event(TPropContainer): + source = TUAMProperty() + type = TStringProperty("clicked") + + def __init__(self): + TPropContainer.__init__(self) + + + # Providing the hash methods necessary to use events as key + # in a dictionary, if new properties are added we should + # take them into account here + def __hash__(self): + return hash(str(self.source) + str(self.type)) + + def __eq__(self, e2): + return self.source == e2.source and self.type == e2.type + + + # Adding methods for pickling and unpickling an object with + # properties + def __getstate__(self): + return self._props.copy() + + def __setstate__(self, dict): + self._props.update(dict) + + +# Nothing more needs to be added, the additional +# information is in the object type +class ClickedEvent(Event): + def __init__(self): + Event.__init__(self) + self.type = "clicked" + diff --git a/tutorius/ipython_view.py b/tutorius/ipython_view.py new file mode 100644 index 0000000..c4294d0 --- /dev/null +++ b/tutorius/ipython_view.py @@ -0,0 +1,301 @@ +""" +Backend to the console plugin. + +@author: Eitan Isaacson +@organization: IBM Corporation +@copyright: Copyright (c) 2007 IBM Corporation +@license: BSD + +All rights reserved. This program and the accompanying materials are made +available under the terms of the BSD which accompanies this distribution, and +is available at U{http://www.opensource.org/licenses/bsd-license.php} +""" +# this file is a modified version of source code from the Accerciser project +# http://live.gnome.org/accerciser + +import gtk +import re +import sys +import os +import pango +from StringIO import StringIO + +try: + import IPython +except Exception,e: + raise "Error importing IPython (%s)" % str(e) + +ansi_colors = {'0;30': 'Black', + '0;31': 'Red', + '0;32': 'Green', + '0;33': 'Brown', + '0;34': 'Blue', + '0;35': 'Purple', + '0;36': 'Cyan', + '0;37': 'LightGray', + '1;30': 'DarkGray', + '1;31': 'DarkRed', + '1;32': 'SeaGreen', + '1;33': 'Yellow', + '1;34': 'LightBlue', + '1;35': 'MediumPurple', + '1;36': 'LightCyan', + '1;37': 'White'} + +class IterableIPShell: + def __init__(self,argv=None,user_ns=None,user_global_ns=None, + cin=None, cout=None,cerr=None, input_func=None): + if input_func: + IPython.iplib.raw_input_original = input_func + if cin: + IPython.Shell.Term.cin = cin + if cout: + IPython.Shell.Term.cout = cout + if cerr: + IPython.Shell.Term.cerr = cerr + + if argv is None: + argv=[] + + # This is to get rid of the blockage that occurs during + # IPython.Shell.InteractiveShell.user_setup() + IPython.iplib.raw_input = lambda x: None + + self.term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr) + os.environ['TERM'] = 'dumb' + excepthook = sys.excepthook + self.IP = IPython.Shell.make_IPython(argv,user_ns=user_ns, + user_global_ns=user_global_ns, + embedded=True, + shell_class=IPython.Shell.InteractiveShell) + self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd), + header='IPython system call: ', + verbose=self.IP.rc.system_verbose) + sys.excepthook = excepthook + self.iter_more = 0 + self.history_level = 0 + self.complete_sep = re.compile('[\s\{\}\[\]\(\)]') + + def execute(self): + self.history_level = 0 + orig_stdout = sys.stdout + sys.stdout = IPython.Shell.Term.cout + try: + line = self.IP.raw_input(None, self.iter_more) + if self.IP.autoindent: + self.IP.readline_startup_hook(None) + except KeyboardInterrupt: + self.IP.write('\nKeyboardInterrupt\n') + self.IP.resetbuffer() + # keep cache in sync with the prompt counter: + self.IP.outputcache.prompt_count -= 1 + + if self.IP.autoindent: + self.IP.indent_current_nsp = 0 + self.iter_more = 0 + except: + self.IP.showtraceback() + else: + self.iter_more = self.IP.push(line) + if (self.IP.SyntaxTB.last_syntax_error and + self.IP.rc.autoedit_syntax): + self.IP.edit_syntax_error() + if self.iter_more: + self.prompt = str(self.IP.outputcache.prompt2).strip() + if self.IP.autoindent: + self.IP.readline_startup_hook(self.IP.pre_readline) + else: + self.prompt = str(self.IP.outputcache.prompt1).strip() + sys.stdout = orig_stdout + + def historyBack(self): + self.history_level -= 1 + return self._getHistory() + + def historyForward(self): + self.history_level += 1 + return self._getHistory() + + def _getHistory(self): + try: + rv = self.IP.user_ns['In'][self.history_level].strip('\n') + except IndexError: + self.history_level = 0 + rv = '' + return rv + + def updateNamespace(self, ns_dict): + self.IP.user_ns.update(ns_dict) + + def complete(self, line): + split_line = self.complete_sep.split(line) + possibilities = self.IP.complete(split_line[-1]) + if possibilities: + common_prefix = reduce(self._commonPrefix, possibilities) + completed = line[:-len(split_line[-1])]+common_prefix + else: + completed = line + return completed, possibilities + + def _commonPrefix(self, str1, str2): + for i in range(len(str1)): + if not str2.startswith(str1[:i+1]): + return str1[:i] + return str1 + + def shell(self, cmd,verbose=0,debug=0,header=''): + stat = 0 + if verbose or debug: print header+cmd + # flush stdout so we don't mangle python's buffering + if not debug: + input, output = os.popen4(cmd) + print output.read() + output.close() + input.close() + +class ConsoleView(gtk.TextView): + def __init__(self): + gtk.TextView.__init__(self) + self.modify_font(pango.FontDescription('Mono')) + self.set_cursor_visible(True) + self.text_buffer = self.get_buffer() + self.mark = self.text_buffer.create_mark('scroll_mark', + self.text_buffer.get_end_iter(), + False) + for code in ansi_colors: + self.text_buffer.create_tag(code, + foreground=ansi_colors[code], + weight=700) + self.text_buffer.create_tag('0') + self.text_buffer.create_tag('notouch', editable=False) + self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?') + self.line_start = \ + self.text_buffer.create_mark('line_start', + self.text_buffer.get_end_iter(), True + ) + self.connect('key-press-event', self._onKeypress) + self.last_cursor_pos = 0 + + def write(self, text, editable=False): + segments = self.color_pat.split(text) + segment = segments.pop(0) + start_mark = self.text_buffer.create_mark(None, + self.text_buffer.get_end_iter(), + True) + self.text_buffer.insert(self.text_buffer.get_end_iter(), segment) + + if segments: + ansi_tags = self.color_pat.findall(text) + for tag in ansi_tags: + i = segments.index(tag) + self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(), + segments[i+1], tag) + segments.pop(i) + if not editable: + self.text_buffer.apply_tag_by_name('notouch', + self.text_buffer.get_iter_at_mark(start_mark), + self.text_buffer.get_end_iter()) + self.text_buffer.delete_mark(start_mark) + self.scroll_mark_onscreen(self.mark) + + def showPrompt(self, prompt): + self.write(prompt) + self.text_buffer.move_mark(self.line_start,self.text_buffer.get_end_iter()) + + def changeLine(self, text): + iter = self.text_buffer.get_iter_at_mark(self.line_start) + iter.forward_to_line_end() + self.text_buffer.delete(self.text_buffer.get_iter_at_mark(self.line_start), iter) + self.write(text, True) + + def getCurrentLine(self): + rv = self.text_buffer.get_slice(self.text_buffer.get_iter_at_mark(self.line_start), + self.text_buffer.get_end_iter(), False) + return rv + + def showReturned(self, text): + iter = self.text_buffer.get_iter_at_mark(self.line_start) + iter.forward_to_line_end() + self.text_buffer.apply_tag_by_name('notouch', + self.text_buffer.get_iter_at_mark(self.line_start), + iter) + self.write('\n'+text) + if text: + self.write('\n') + self.showPrompt(self.prompt) + self.text_buffer.move_mark(self.line_start,self.text_buffer.get_end_iter()) + self.text_buffer.place_cursor(self.text_buffer.get_end_iter()) + + def _onKeypress(self, obj, event): + if not event.string: + return + insert_mark = self.text_buffer.get_insert() + insert_iter = self.text_buffer.get_iter_at_mark(insert_mark) + selection_mark = self.text_buffer.get_selection_bound() + selection_iter = self.text_buffer.get_iter_at_mark(selection_mark) + start_iter = self.text_buffer.get_iter_at_mark(self.line_start) + if start_iter.compare(insert_iter) <= 0 and \ + start_iter.compare(selection_iter) <= 0: + return + elif start_iter.compare(insert_iter) > 0 and \ + start_iter.compare(selection_iter) > 0: + self.text_buffer.place_cursor(start_iter) + elif insert_iter.compare(selection_iter) < 0: + self.text_buffer.move_mark(insert_mark, start_iter) + elif insert_iter.compare(selection_iter) > 0: + self.text_buffer.move_mark(selection_mark, start_iter) + + +class IPythonView(ConsoleView, IterableIPShell): + def __init__(self): + ConsoleView.__init__(self) + self.cout = StringIO() + IterableIPShell.__init__(self, cout=self.cout,cerr=self.cout, + input_func=self.raw_input) + self.connect('key_press_event', self.keyPress) + self.execute() + self.cout.truncate(0) + self.showPrompt(self.prompt) + self.interrupt = False + + def raw_input(self, prompt=''): + if self.interrupt: + self.interrupt = False + raise KeyboardInterrupt + return self.getCurrentLine() + + def keyPress(self, widget, event): + if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99: + self.interrupt = True + self._processLine() + return True + elif event.keyval == gtk.keysyms.Return: + self._processLine() + return True + elif event.keyval == gtk.keysyms.Up: + self.changeLine(self.historyBack()) + return True + elif event.keyval == gtk.keysyms.Down: + self.changeLine(self.historyForward()) + return True + elif event.keyval == gtk.keysyms.Tab: + if not self.getCurrentLine().strip(): + return False + completed, possibilities = self.complete(self.getCurrentLine()) + if len(possibilities) > 1: + slice = self.getCurrentLine() + self.write('\n') + for symbol in possibilities: + self.write(symbol+'\n') + self.showPrompt(self.prompt) + self.changeLine(completed or slice) + return True + + def _processLine(self): + self.history_pos = 0 + self.execute() + rv = self.cout.getvalue() + if rv: rv = rv.strip('\n') + self.showReturned(rv) + self.cout.truncate(0) + diff --git a/tutorius/properties.py b/tutorius/properties.py index abf76e5..d9c68b1 100644 --- a/tutorius/properties.py +++ b/tutorius/properties.py @@ -288,7 +288,12 @@ class TUAMProperty(TutoriusProperty): Represents a widget of the interface by storing its UAM. """ # TODO : Pending UAM check-in (LP 355199) - pass + def __init__(self, value=None): + TutoriusProperty.__init__(self) + + self.type = "uam" + + class TAddonProperty(TutoriusProperty): """ -- cgit v0.9.1