diff options
Diffstat (limited to 'src/sugar/tutorius')
41 files changed, 0 insertions, 7038 deletions
diff --git a/src/sugar/tutorius/Makefile.am b/src/sugar/tutorius/Makefile.am deleted file mode 100644 index 072a119..0000000 --- a/src/sugar/tutorius/Makefile.am +++ /dev/null @@ -1,19 +0,0 @@ -SUBDIRS = uam addons - -sugardir = $(pythondir)/sugar/tutorius -sugar_PYTHON = \ - __init__.py \ - core.py \ - dialog.py \ - actions.py \ - gtkutils.py \ - filters.py \ - services.py \ - overlayer.py \ - editor.py \ - constraints.py \ - properties.py \ - creator.py \ - bundler.py \ - linear_creator.py \ - addon.py diff --git a/src/sugar/tutorius/__init__.py b/src/sugar/tutorius/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/src/sugar/tutorius/__init__.py +++ /dev/null diff --git a/src/sugar/tutorius/actions.py b/src/sugar/tutorius/actions.py deleted file mode 100644 index 4269cd7..0000000 --- a/src/sugar/tutorius/actions.py +++ /dev/null @@ -1,324 +0,0 @@ -# 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 -""" -This module defines Actions that can be done and undone on a state -""" -from gettext import gettext as _ - -from sugar.tutorius import gtkutils, addon -from dialog import TutoriusDialog -import overlayer -from sugar.tutorius.editor import WidgetIdentifier -from sugar.tutorius.services import ObjectStore -from sugar.tutorius.properties import * -from sugar.graphics import icon -import gtk.gdk - -class DragWrapper(object): - """Wrapper to allow gtk widgets to be dragged around""" - def __init__(self, widget, position, draggable=False): - """ - Creates a wrapper to allow gtk widgets to be mouse dragged, if the - parent container supports the move() method, like a gtk.Layout. - @param widget the widget to enhance with drag capability - @param position the widget's position. Will translate the widget if needed - @param draggable wether to enable the drag functionality now - """ - self._widget = widget - self._eventbox = None - self._drag_on = False # whether dragging is enabled - self._rel_pos = (0,0) # mouse pos relative to widget - self._handles = [] # event handlers - self._dragging = False # whether a drag is in progress - self.position = position # position of the widget - - self.draggable = draggable - - def _pressed_cb(self, widget, evt): - """Callback for start of drag event""" - self._eventbox.grab_add() - self._dragging = True - self._rel_pos = evt.get_coords() - - def _moved_cb(self, widget, evt): - """Callback for mouse drag events""" - if not self._dragging: - return - - # Focus on a widget before dragging another would - # create addititonal move event, making the widget jump unexpectedly. - # Solution found was to process those focus events before dragging. - if gtk.events_pending(): - return - - xrel, yrel = self._rel_pos - xparent, yparent = evt.get_coords() - xparent, yparent = widget.translate_coordinates(widget.parent, - xparent, yparent) - self.position = (xparent-xrel, yparent-yrel) - self._widget.parent.move(self._eventbox, *self.position) - self._widget.parent.move(self._widget, *self.position) - self._widget.parent.queue_draw() - - def _released_cb(self, *args): - """Callback for end of drag (mouse release).""" - self._eventbox.grab_remove() - self._dragging = False - - def _drag_end(self, *args): - """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): - if value: - self._eventbox = gtk.EventBox() - self._eventbox.show() - self._eventbox.set_visible_window(False) - size = self._widget.size_request() - self._eventbox.set_size_request(*size) - self._widget.parent.put(self._eventbox, *self.position) - self._handles.append(self._eventbox.connect( - "button-press-event", self._pressed_cb)) - self._handles.append(self._eventbox.connect( - "button-release-event", self._released_cb)) - self._handles.append(self._eventbox.connect( - "motion-notify-event", self._moved_cb)) - self._handles.append(self._eventbox.connect( - "grab-broken-event", self._drag_end)) - else: - while len(self._handles): - handle = self._handles.pop() - self._eventbox.disconnect(handle) - self._eventbox.parent.remove(self._eventbox) - self._eventbox.destroy() - self._eventbox = None - self._drag_on = value - - def get_draggable(self): - """Getter for the draggable property""" - return self._drag_on - - draggable = property(fset=set_draggable, fget=get_draggable, \ - doc="Property to enable the draggable behaviour of the widget") - - def set_widget(self, widget): - """Setter for the widget property""" - if self._dragging or self._drag_on: - raise Exception("Can't change widget while dragging is enabled.") - - assert hasattr(widget, "parent"), "wrapped widget should have a parent" - parent = widget.parent - assert hasattr(parent, "move"), "container of widget need move method" - self._widget = widget - - def get_widget(self): - """Getter for the widget property""" - return self._widget - - widget = property(fset=set_widget, fget=get_widget) - -class Action(TPropContainer): - """Base class for Actions""" - def __init__(self): - TPropContainer.__init__(self) - self.position = (0,0) - self._drag = None - - def do(self, **kwargs): - """ - Perform the action - """ - raise NotImplementedError("Not implemented") - - def undo(self): - """ - Revert anything the action has changed - """ - pass #Should raise NotImplemented? - - def enter_editmode(self, **kwargs): - """ - Enters edit mode. The action should display itself in some way, - without affecting the currently running application. The default is - a small box with the action icon. - """ - meta = addon.get_addon_meta(type(self).__name__) - - actionicon = icon.Icon(icon_name=meta['icon'], - icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR) - # Eventbox create a visible window for the icon, so it clips correctly - self.__edit_img = gtk.EventBox() - self.__edit_img.set_visible_window(True) - self.__edit_img.add(actionicon) - - x, y = self.position - - ObjectStore().activity._overlayer.put(self.__edit_img, x, y) - self.__edit_img.show_all() - self._drag = DragWrapper(self.__edit_img, self.position, True) - - def exit_editmode(self, **kwargs): - x, y = self._drag.position - self.position = [int(x), int(y)] - self.__edit_img.destroy() - -class OnceWrapper(Action): - """ - Wraps a class to perform an action once only - - This ConcreteActions's do() method will only be called on the first do() - and the undo() will be callable after do() has been called - """ - - _action = TAddonProperty() - - def __init__(self, action): - Action.__init__(self) - self._called = False - self._need_undo = False - self._action = action - - def do(self): - """ - Do the action only on the first time - """ - if not self._called: - self._called = True - self._action.do() - self._need_undo = True - - def undo(self): - """ - Undo the action if it's been done - """ - if self._need_undo: - self._action.undo() - self._need_undo = False - -class WidgetIdentifyAction(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 = WidgetIdentifier(self.activity) - self._dialog.show() - - - def undo(self): - if self._dialog: - self._dialog.destroy() - -class ChainAction(Action): - """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 - - def do(self,**kwargs): - """do() each action in the chain""" - for act in self._actions: - act.do(**kwargs) - - def undo(self): - """undo() each action in the chain, starting with the last""" - for act in reversed(self._actions): - act.undo() - -class DisableWidgetAction(Action): - def __init__(self, target): - """Constructor - @param target target treeish - """ - Action.__init__(self) - self._target = target - self._widget = None - - def do(self): - """Action do""" - os = ObjectStore() - if os.activity: - self._widget = gtkutils.find_widget(os.activity, self._target) - if self._widget: - self._widget.set_sensitive(False) - - def undo(self): - """Action undo""" - if self._widget: - self._widget.set_sensitive(True) - - -class TypeTextAction(Action): - """ - Simulate a user typing text in a widget - Work on any widget that implements a insert_text method - - @param widget The treehish representation of the widget - @param text the text that is typed - """ - def __init__(self, widget, text): - Action.__init__(self) - - self._widget = widget - self._text = text - - def do(self, **kwargs): - """ - Type the text - """ - widget = gtkutils.find_widget(ObjectStore().activity, self._widget) - if hasattr(widget, "insert_text"): - widget.insert_text(self._text, -1) - - def undo(self): - """ - no undo - """ - pass - -class ClickAction(Action): - """ - Action that simulate a click on a widget - Work on any widget that implements a clicked() method - - @param widget The threehish representation of the widget - """ - def __init__(self, widget): - Action.__init__(self) - self._widget = widget - - def do(self): - """ - click the widget - """ - widget = gtkutils.find_widget(ObjectStore().activity, self._widget) - if hasattr(widget, "clicked"): - widget.clicked() - - def undo(self): - """ - No undo - """ - pass - diff --git a/src/sugar/tutorius/addon.py b/src/sugar/tutorius/addon.py deleted file mode 100644 index 51791d1..0000000 --- a/src/sugar/tutorius/addon.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Simon Poirier <simpoir@gmail.com> -# -# -# 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 -""" -This module manages the loading and listing of tutorius addons. -Addons are modular actions and events that are package in such a way that they -can be autodetected and can integrate with Tutorius components (the editor) -without any configuration or explicit dependencies (python import). - -An action addon is expected to have a metadata dict such as this one: -__action__ = { - "name" : "HelloWorld", - "display_name" : "Hello World!", - "icon" : "hello", - "class" : HelloAction, - "mandatory_props" : ["text"], -} -""" - -import os -import re -import logging - -PREFIX = __name__+"s" -PATH = re.sub("addon\\.py[c]$", "", __file__)+"addons" - -_cache = None - -def _reload_addons(): - global _cache - _cache = {} - for addon in filter(lambda x: x.endswith("py"), os.listdir(PATH)): - mod = __import__(PREFIX+'.'+re.sub("\\.py$", "", addon), {}, {}, [""]) - if hasattr(mod, "__action__"): - _cache[mod.__action__['name']] = mod.__action__ - continue - if hasattr(mod, "__event__"): - _cache[mod.__event__['name']] = mod.__event__ - -def create(name, *args, **kwargs): - global _cache - if not _cache: - _reload_addons() - try: - return _cache[name]['class'](*args, **kwargs) - except KeyError: - logging.error("Addon not found for class '%s'", name) - return None - -def list_addons(): - global _cache - if not _cache: - _reload_addons() - return _cache.keys() - -def get_addon_meta(name): - global _cache - if not _cache: - _reload_addons() - return _cache[name] - -# vim:set ts=4 sts=4 sw=4 et: diff --git a/src/sugar/tutorius/addons/Makefile.am b/src/sugar/tutorius/addons/Makefile.am deleted file mode 100644 index 3d1d18a..0000000 --- a/src/sugar/tutorius/addons/Makefile.am +++ /dev/null @@ -1,6 +0,0 @@ -sugardir = $(pythondir)/sugar/tutorius/addons -sugar_PYTHON = \ - __init__.py \ - bubblemessage.py \ - gtkwidgeteventfilter.py \ - dialogmessage.py diff --git a/src/sugar/tutorius/addons/__init__.py b/src/sugar/tutorius/addons/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/src/sugar/tutorius/addons/__init__.py +++ /dev/null diff --git a/src/sugar/tutorius/addons/bubblemessage.py b/src/sugar/tutorius/addons/bubblemessage.py deleted file mode 100644 index a859ef8..0000000 --- a/src/sugar/tutorius/addons/bubblemessage.py +++ /dev/null @@ -1,113 +0,0 @@ -# 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 * - -class BubbleMessage(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) - - def __init__(self, message=None, pos=None, speaker=None, tailpos=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 pos A list of the form [x, y] - @param speaker treeish representation of the speaking widget - @param tailpos The position of the tail of the bubble; useful to point to - specific elements of the interface - """ - Action.__init__(self) - - if pos: - self.position = pos - if tailpos: - self.tail_pos = tailpos - if message: - self.message = message - - self.overlay = None - self._bubble = None - self._speaker = None - - def do(self): - """ - 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.TextBubble(text=self.message, - tailpos=self.tail_pos) - 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.TextBubble(text=self.message, - tailpos=self.tail_pos) - 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" : "BubbleMessage", - "display_name" : "Message Bubble", - "icon" : "message-bubble", - "class" : BubbleMessage, - "mandatory_props" : ["message"] -} - diff --git a/src/sugar/tutorius/addons/dialogmessage.py b/src/sugar/tutorius/addons/dialogmessage.py deleted file mode 100644 index 22a223b..0000000 --- a/src/sugar/tutorius/addons/dialogmessage.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Simon Poirier <simpoir@gmail.com> -# -# -# 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 * - -class DialogMessage(Action): - message = TStringProperty("Message") - position = TArrayProperty([0, 0], 2, 2) - - def __init__(self, message=None, pos=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 pos A list of the form [x, y] - """ - super(DialogMessage, self).__init__() - self._dialog = None - - if message: - self.message = message - if pos: self.position = pos - - def do(self): - """ - Show the dialog - """ - self._dialog = TutoriusDialog(self.message) - self._dialog.set_button_clicked_cb(self._dialog.close_self) - self._dialog.set_modal(False) - self._dialog.move(self.position[0], self.position[1]) - self._dialog.show() - - def undo(self): - """ - Destroy the dialog - """ - if self._dialog: - self._dialog.destroy() - self._dialog = None - -__action__ = { - "name" : "DialogMessage", - "display_name" : "Message Dialog", - "icon" : "window_fullscreen", - "class" : DialogMessage, - "mandatory_props" : ["message"] -} - -# vim:set ts=4 sts=4 sw=4 et: - diff --git a/src/sugar/tutorius/addons/gtkwidgeteventfilter.py b/src/sugar/tutorius/addons/gtkwidgeteventfilter.py deleted file mode 100644 index cbfb00c..0000000 --- a/src/sugar/tutorius/addons/gtkwidgeteventfilter.py +++ /dev/null @@ -1,69 +0,0 @@ -# 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.filters import * -from sugar.tutorius.properties import * - -class GtkWidgetEventFilter(EventFilter): - """ - Basic Event filter for Gtk widget events - """ - object_id = TUAMProperty() - event_name = TStringProperty("clicked") - - def __init__(self, next_state=None, object_id=None, event_name=None): - """Constructor - @param next_state default EventFilter param, passed on to EventFilter - @param object_id object fqdn-style identifier - @param event_name event to attach to - """ - super(GtkWidgetEventFilter,self).__init__(next_state) - self._callback = None - self.object_id = object_id - self.event_name = event_name - self._widget = None - self._handler_id = None - - def install_handlers(self, callback, **kwargs): - """install handlers - @param callback default EventFilter callback arg - @param activity keyword argument activity must be present to install - the event handler into the activity's widget hierarchy - """ - super(GtkWidgetEventFilter, self).install_handlers(callback, **kwargs) - if not "activity" in kwargs: - raise TypeError("activity argument is Mandatory") - - #find the widget and connect to its event - self._widget = find_widget(kwargs["activity"], self.object_id) - self._handler_id = self._widget.connect( \ - self.event_name, self.do_callback ) - - def remove_handlers(self): - """remove handlers""" - super(GtkWidgetEventFilter, self).remove_handlers() - #if an event was connected, disconnect it - if self._handler_id: - self._widget.handler_disconnect(self._handler_id) - self._handler_id=None - -__event__ = { - "name" : "GtkWidgetEventFilter", - "display_name" : "GTK Event catcher", - "icon" : "player_play", - "class" : GtkWidgetEventFilter, - "mandatory_props" : ["object_id"] -} - diff --git a/src/sugar/tutorius/bundler.py b/src/sugar/tutorius/bundler.py deleted file mode 100644 index 8e7fc3d..0000000 --- a/src/sugar/tutorius/bundler.py +++ /dev/null @@ -1,556 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Jean-Christophe Savard <savard.jean.christophe@gmail.com> -# -# 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 - - -""" -This module contains all the data handling class of Tutorius -""" - -import logging -import os -import uuid -import xml.dom.minidom - -from sugar.tutorius import addon -from sugar.tutorius.core import Tutorial, State, FiniteStateMachine -from sugar.tutorius.filters import * -from sugar.tutorius.actions import * -from ConfigParser import SafeConfigParser - -# this is where user installed/generated tutorials will go -def _get_store_root(): - return os.path.join(os.getenv("HOME"),".sugar",os.getenv("SUGAR_PROFILE"),"tutorius","data") -# this is where activity bundled tutorials should be, under the activity bundle -def _get_bundle_root(): - return os.path.join(os.getenv("SUGAR_BUNDLE_PATH"),"data","tutorius","data") - -INI_ACTIVITY_SECTION = "RELATED_ACTIVITIES" -INI_METADATA_SECTION = "GENERAL_METADATA" -INI_GUID_PROPERTY = "GUID" -INI_NAME_PROPERTY = "NAME" -INI_XML_FSM_PROPERTY = "FSM_FILENAME" -INI_FILENAME = "meta.ini" -TUTORIAL_FILENAME = "tutorial.xml" -NODE_COMPONENT = "Component" - -class TutorialStore(object): - - def list_available_tutorials(self, activity_name, activity_vers): - """ - Generate the list of all tutorials present on disk for a - given activity. - - @returns a map of tutorial {names : GUID}. - """ - # check both under the activity data and user installed folders - paths = [_get_store_root(), _get_bundle_root()] - - tutoGuidName = {} - - for repository in paths: - # (our) convention dictates that tutorial folders are named - # with their GUID (for unicity) but this is not enforced. - try: - for tuto in os.listdir(repository): - parser = SafeConfigParser() - parser.read(os.path.join(repository, tuto, INI_FILENAME)) - guid = parser.get(INI_METADATA_SECTION, INI_GUID_PROPERTY) - name = parser.get(INI_METADATA_SECTION, INI_NAME_PROPERTY) - activities = parser.options(INI_ACTIVITY_SECTION) - # enforce matching activity name AND version, as UI changes - # break tutorials. We may lower this requirement when the - # UAM gets less dependent on the widget order. - # Also note property names are always stored lowercase. - if activity_name.lower() in activities: - version = parser.get(INI_ACTIVITY_SECTION, activity_name) - if activity_vers == version: - tutoGuidName[guid] = name - except OSError: - # the repository may not exist. Continue scanning - pass - - return tutoGuidName - - def load_tutorial(self, Guid): - """ - Rebuilds a tutorial object from it's serialized state. - Common storing paths will be scanned. - - @param Guid the generic identifier of the tutorial - @returns a Tutorial object containing an FSM - """ - bundle = TutorialBundler(Guid) - bundle_path = bundle.get_tutorial_path() - config = SafeConfigParser() - config.read(os.path.join(bundle_path, INI_FILENAME)) - - serializer = XMLSerializer() - - name = config.get(INI_METADATA_SECTION, INI_NAME_PROPERTY) - fsm = serializer.load_fsm(Guid) - - tuto = Tutorial(name, fsm) - return tuto - - -class Serializer(object): - """ - Interface that provide serializing and deserializing of the FSM - used in the tutorials to/from disk. Must be inherited. - """ - - def save_fsm(self,fsm): - """ - Save fsm to disk. If a GUID parameter is provided, the existing GUID is - located in the .ini files in the store root and bundle root and - the corresponding FSM is/are overwritten. If the GUId is not found, an - exception occur. If no GUID is provided, FSM is written in a new file - in the store root. - """ - NotImplementedError - - def load_fsm(self): - """ - Load fsm from disk. - """ - NotImplementedError - -class XMLSerializer(Serializer): - """ - Class that provide serializing and deserializing of the FSM - used in the tutorials to/from a .xml file. Inherit from Serializer - """ - - def _create_state_dict_node(self, state_dict, doc): - """ - Create and return a xml Node from a State dictionnary. - """ - statesList = doc.createElement("States") - for state_name, state in state_dict.items(): - stateNode = doc.createElement("State") - statesList.appendChild(stateNode) - stateNode.setAttribute("Name", state_name) - actionsList = stateNode.appendChild(self._create_action_list_node(state.get_action_list(), doc)) - eventfiltersList = stateNode.appendChild(self._create_event_filters_node(state.get_event_filter_list(), doc)) - return statesList - - def _create_component_node(self, comp, doc): - """ - Takes a single component (action or eventfilter) and transforms it - into a xml node. - - @param comp A single component - @param doc The XML document root (used to create nodes only - @return A XML Node object with the component tag name - """ - compNode = doc.createElement(NODE_COMPONENT) - - # Write down just the name of the Action class as the Class - # property -- - compNode.setAttribute("Class",type(comp).__name__) - - # serialize all tutorius properties - for propname in comp.get_properties(): - propval = getattr(comp, propname) - if getattr(type(comp), propname).type == "addonlist": - for subval in propval: - compNode.appendChild(self._create_component_node(subval, doc)) - elif getattr(type(comp), propname).type == "addonlist": - compNode.appendChild(self._create_component_node(subval, doc)) - else: - # repr instead of str, as we want to be able to eval() it into a - # valid object. - compNode.setAttribute(propname, repr(propval)) - - return compNode - - def _create_action_list_node(self, action_list, doc): - """ - Create and return a xml Node from a Action list. - - @param action_list A list of actions - @param doc The XML document root (used to create new nodes only) - @return A XML Node object with the Actions tag name and a serie of - Action children - """ - actionsList = doc.createElement("Actions") - for action in action_list: - # Create the action node - actionNode = self._create_component_node(action, doc) - # Append it to the list - actionsList.appendChild(actionNode) - - return actionsList - - def _create_event_filters_node(self, event_filters, doc): - """ - Create and return a xml Node from a event filters. - """ - eventFiltersList = doc.createElement("EventFiltersList") - for event_f in event_filters: - eventFilterNode = self._create_component_node(event_f, doc) - eventFiltersList.appendChild(eventFilterNode) - - return eventFiltersList - - def save_fsm(self, fsm, xml_filename, path): - """ - Save fsm to disk, in the xml file specified by "xml_filename", in the - "path" folder. If the specified file doesn't exist, it will be created. - """ - self.doc = doc = xml.dom.minidom.Document() - fsm_element = doc.createElement("FSM") - doc.appendChild(fsm_element) - fsm_element.setAttribute("Name", fsm.name) - fsm_element.setAttribute("StartStateName", fsm.start_state_name) - statesDict = fsm_element.appendChild(self._create_state_dict_node(fsm._states, doc)) - - fsm_actions_node = self._create_action_list_node(fsm.actions, doc) - fsm_actions_node.tagName = "FSMActions" - actionsList = fsm_element.appendChild(fsm_actions_node) - - file_object = open(os.path.join(path, xml_filename), "w") - file_object.write(doc.toprettyxml()) - file_object.close() - - - def _find_tutorial_dir_with_guid(self, guid): - """ - Finds the tutorial with the associated GUID. If it is found, return - the path to the tutorial's directory. If it doesn't exist, raise an - IOError. - - A note : if there are two tutorials with this GUID in the folders, - they will both be inspected and the one with the highest version - number will be returned. If they have the same version number, the one - from the global store will be returned. - - @param guid The GUID of the tutorial that is to be loaded. - """ - # Attempt to find the tutorial's directory in the global directory - global_dir = os.path.join(_get_store_root(), guid) - # Then in the activty's bundle path - activity_dir = os.path.join(_get_bundle_root(), guid) - - # If they both exist - if os.path.isdir(global_dir) and os.path.isdir(activity_dir): - # Inspect both metadata files - global_meta = os.path.join(global_dir, "meta.ini") - activity_meta = os.path.join(activity_dir, "meta.ini") - - # Open both config files - global_parser = SafeConfigParser() - global_parser.read(global_meta) - - activity_parser = SafeConfigParser() - activity_parser.read(activity_meta) - - # Get the version number for each tutorial - global_version = global_parser.get(INI_METADATA_SECTION, "version") - activity_version = activity_parser.get(INI_METADATA_SECTION, "version") - - # If the global version is higher or equal, we'll take it - if global_version >= activity_version: - return global_dir - else: - return activity_dir - - # Do we just have the global directory? - if os.path.isdir(global_dir): - return global_dir - - # Or just the activity's bundle directory? - if os.path.isdir(activity_dir): - return activity_dir - - # Error : none of these directories contain the tutorial - raise IOError(2, "Neither the global nor the bundle directory contained the tutorial with GUID %s"%guid) - - def _load_xml_properties(self, properties_elem): - """ - Changes a list of properties into fully instanciated properties. - - @param properties_elem An XML element reprensenting a list of - properties - """ - return [] - - def _load_xml_event_filters(self, filters_elem): - """ - Loads up a list of Event Filters. - - @param filters_elem An XML Element representing a list of event filters - """ - reformed_event_filters_list = [] - event_filter_element_list = filters_elem.getElementsByTagName(NODE_COMPONENT) - new_event_filter = None - - for event_filter in event_filter_element_list: - new_event_filter = self._load_xml_component(event_filter) - - if new_event_filter is not None: - reformed_event_filters_list.append(new_event_filter) - - return reformed_event_filters_list - - def _load_xml_component(self, node): - """ - Loads a single addon component instance from an Xml node. - - @param node The component XML Node to transform - object - @return The addon component object of the correct type according to the XML - description - """ - new_action = addon.create(node.getAttribute("Class")) - if not new_action: - return None - - for attrib in node.attributes.keys(): - if attrib == "Class": continue - # security note: keep sandboxed - setattr(new_action, attrib, eval(node.getAttribute(attrib), {}, {})) - - # recreate complex attributes - for sub in node.childNodes: - name = getattr(new_action, sub.nodeName) - if name == "addon": - setattr(new_action, sub.getAttribute("Name"), self._load_xml_action(sub)) - - return new_action - - def _load_xml_actions(self, actions_elem): - """ - Transforms an Actions element into a list of instanciated Action. - - @param actions_elem An XML Element representing a list of Actions - """ - reformed_actions_list = [] - actions_element_list = actions_elem.getElementsByTagName(NODE_COMPONENT) - - for action in actions_element_list: - new_action = self._load_xml_component(action) - - reformed_actions_list.append(new_action) - - return reformed_actions_list - - def _load_xml_states(self, states_elem): - """ - Takes in a States element and fleshes out a complete list of State - objects. - - @param states_elem An XML Element that represents a list of States - """ - reformed_state_list = [] - # item(0) because there is always only one <States> tag in the xml file - # so states_elem should always contain only one element - states_element_list = states_elem.item(0).getElementsByTagName("State") - - for state in states_element_list: - stateName = state.getAttribute("Name") - # Using item 0 in the list because there is always only one - # Actions and EventFilterList element per State node. - actions_list = self._load_xml_actions(state.getElementsByTagName("Actions")[0]) - event_filters_list = self._load_xml_event_filters(state.getElementsByTagName("EventFiltersList")[0]) - reformed_state_list.append(State(stateName, actions_list, event_filters_list)) - - return reformed_state_list - - def _load_xml_fsm(self, fsm_elem): - """ - Takes in an XML element representing an FSM and returns the fully - crafted FSM. - - @param fsm_elem The XML element that describes a FSM - """ - # Load the FSM's name and start state's name - fsm_name = fsm_elem.getAttribute("Name") - - fsm_start_state_name = None - try: - fsm_start_state_name = fsm_elem.getAttribute("StartStateName") - except: - pass - - fsm = FiniteStateMachine(fsm_name, start_state_name=fsm_start_state_name) - - # Load the states - states = self._load_xml_states(fsm_elem.getElementsByTagName("States")) - for state in states: - fsm.add_state(state) - - # Load the actions on this FSM - actions = self._load_xml_actions(fsm_elem.getElementsByTagName("FSMActions")[0]) - for action in actions: - fsm.add_action(action) - - # Load the event filters - events = self._load_xml_event_filters(fsm_elem.getElementsByTagName("EventFiltersList")[0]) - for event in events: - fsm.add_event_filter(event) - - return fsm - - - def load_fsm(self, guid): - """ - Load fsm from xml file whose .ini file guid match argument guid. - """ - # Fetch the directory (if any) - tutorial_dir = self._find_tutorial_dir_with_guid(guid) - - # Open the XML file - tutorial_file = os.path.join(tutorial_dir, TUTORIAL_FILENAME) - - xml_dom = xml.dom.minidom.parse(tutorial_file) - - fsm_elem = xml_dom.getElementsByTagName("FSM")[0] - - return self._load_xml_fsm(fsm_elem) - - -class TutorialBundler(object): - """ - This class provide the various data handling methods useable by the tutorial - editor. - """ - - def __init__(self,generated_guid = None): - """ - Tutorial_bundler constructor. If a GUID is given in the parameter, the - Tutorial_bundler object will be associated with it. If no GUID is given, - a new GUID will be generated, - """ - - self.Guid = generated_guid or str(uuid.uuid1()) - - #Look for the file in the path if a uid is supplied - if generated_guid: - #General store - store_path = os.path.join(_get_store_root(), generated_guid, INI_FILENAME) - if os.path.isfile(store_path): - self.Path = os.path.dirname(store_path) - else: - #Bundle store - bundle_path = os.path.join(_get_bundle_root(), generated_guid, INI_FILENAME) - if os.path.isfile(bundle_path): - self.Path = os.path.dirname(bundle_path) - else: - raise IOError(2,"Unable to locate metadata file for guid '%s'" % generated_guid) - - else: - #Create the folder, any failure will go through to the caller for now - store_path = os.path.join(_get_store_root(), self.Guid) - os.makedirs(store_path) - self.Path = store_path - - def write_metadata_file(self, tutorial): - """ - Write metadata to the property file. - @param tutorial Tutorial for which to write metadata - """ - #Create the Config Object and populate it - cfg = SafeConfigParser() - cfg.add_section(INI_METADATA_SECTION) - cfg.set(INI_METADATA_SECTION, INI_GUID_PROPERTY, self.Guid) - cfg.set(INI_METADATA_SECTION, INI_NAME_PROPERTY, tutorial.name) - cfg.set(INI_METADATA_SECTION, INI_XML_FSM_PROPERTY, TUTORIAL_FILENAME) - cfg.add_section(INI_ACTIVITY_SECTION) - cfg.set(INI_ACTIVITY_SECTION, os.environ['SUGAR_BUNDLE_NAME'], - os.environ['SUGAR_BUNDLE_VERSION']) - - #Write the ini file - cfg.write( file( os.path.join(self.Path, INI_FILENAME), 'w' ) ) - - def get_tutorial_path(self): - """ - Return the path of the .ini file associated with the guiven guid set in - the Guid property of the Tutorial_Bundler. If the guid is present in - more than one path, the store_root is given priority. - """ - - store_root = _get_store_root() - bundle_root = _get_bundle_root() - - config = SafeConfigParser() - path = None - - logging.debug("************ Path of store_root folder of activity : " \ - + store_root) - - # iterate in each GUID subfolder - for dir in os.listdir(store_root): - - # iterate for each .ini file in the store_root folder - - for file_name in os.listdir(os.path.join(store_root, dir)): - if file_name.endswith(".ini"): - logging.debug("******************* Found .ini file : " \ - + file_name) - config.read(os.path.join(store_root, dir, file_name)) - if config.get(INI_METADATA_SECTION, INI_GUID_PROPERTY) == self.Guid: - xml_filename = config.get(INI_METADATA_SECTION, - INI_XML_FSM_PROPERTY) - - path = os.path.join(store_root, dir) - return path - - logging.debug("************ Path of bundle_root folder of activity : " \ - + bundle_root) - - - # iterate in each GUID subfolder - for dir in os.listdir(bundle_root): - - # iterate for each .ini file in the bundle_root folder - for file_name in os.listdir(os.path.join(bundle_root, dir)): - if file_name.endswith(".ini"): - logging.debug("******************* Found .ini file : " \ - + file_name) - config.read(os.path.join(bundle_root, dir, file_name)) - if config.get(INI_METADATA_SECTION, INI_GUID_PROPERTY) == self.Guid: - path = os.path.join(bundle_root, self.Guid) - return path - - if path is None: - logging.debug("**************** Error : GUID not found") - raise KeyError - - def write_fsm(self, fsm): - - """ - Save fsm to disk. If a GUID parameter is provided, the existing GUID is - located in the .ini files in the store root and bundle root and - the corresponding FSM is/are created or overwritten. If the GUID is not - found, an exception occur. - """ - - config = SafeConfigParser() - - serializer = XMLSerializer() - path = os.path.join(self.Path, "meta.ini") - config.read(path) - xml_filename = config.get(INI_METADATA_SECTION, INI_XML_FSM_PROPERTY) - serializer.save_fsm(fsm, xml_filename, self.Path) - - - def add_resources(self, typename, file): - """ - Add ressources to metadata. - """ - raise NotImplementedError("add_resources not implemented") diff --git a/src/sugar/tutorius/constraints.py b/src/sugar/tutorius/constraints.py deleted file mode 100644 index 36abdfb..0000000 --- a/src/sugar/tutorius/constraints.py +++ /dev/null @@ -1,207 +0,0 @@ -# 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 -""" -Constraints - -Defines a set of constraints with their related errors. These constraints are -made to be used inside TutoriusProperties in order to limit the values that -they might take. They can also be used to enforce a particular format or type -for some properties. -""" - -# For the File Constraint -import os - -class Constraint(): - """ - Basic block for defining constraints on a TutoriusProperty. Every class - inheriting from Constraint will have a validate function that will be - executed when the property's value is to be changed. - """ - def validate(self, value): - """ - This function receives the value that is proposed as a new value for - the property. It needs to raise an Error in the case where the value - does not respect this constraint. - """ - raise NotImplementedError("Unable to validate a base Constraint") - -class ValueConstraint(Constraint): - """ - A value constraint contains a _limit member that can be used in a children - class as a basic value. See UpperLimitConstraint for an exemple. - """ - def __init__(self, limit): - self.limit = limit - -class UpperLimitConstraintError(Exception): - pass - -class UpperLimitConstraint(ValueConstraint): - def validate(self, value): - """ - Evaluates whether the given value is smaller than the limit. - - @raise UpperLimitConstraintError When the value is strictly higher than - the limit. - """ - if self.limit is not None: - if self.limit >= value: - return - raise UpperLimitConstraintError() - return - -class LowerLimitConstraintError(Exception): - pass - -class LowerLimitConstraint(ValueConstraint): - def validate(self, value): - """ - If the value is lower than the limit, this function raises an error. - - @raise LowerLimitConstraintError When the value is strictly smaller - than the limit. - """ - if self.limit is not None: - if value >= self.limit: - return - raise LowerLimitConstraintError() - return - -class MaxSizeConstraintError(Exception): - pass - -class MaxSizeConstraint(ValueConstraint): - def validate(self, value): - """ - Evaluate whether a given object is smaller than the given size when - run through len(). Great for string, lists and the like. ;) - - @raise SizeConstraintError If the length of the value is strictly - bigger than the limit. - """ - if self.limit is not None: - if self.limit >= len(value): - return - raise MaxSizeConstraintError("Setter : trying to set value of length %d while limit is %d"%(len(value), self.limit)) - return - -class MinSizeConstraintError(Exception): - pass - -class MinSizeConstraint(ValueConstraint): - def validate(self, value): - """ - Evaluate whether a given object is smaller than the given size when - run through len(). Great for string, lists and the like. ;) - - @raise SizeConstraintError If the length of the value is strictly - bigger than the limit. - """ - if self.limit is not None: - if self.limit <= len(value): - return - raise MinSizeConstraintError("Setter : trying to set value of length %d while limit is %d"%(len(value), self.limit)) - return - -class ColorConstraintError(Exception): - pass - -class ColorArraySizeError(ColorConstraintError): - pass - -class ColorTypeError(ColorConstraintError): - pass - -class ColorValueError(ColorConstraintError): - pass - -class ColorConstraint(Constraint): - """ - Validates that the value is an array of size 3 with three numbers between - 0 and 255 (inclusively) in it. - - """ - def validate(self, value): - if len(value) != 3: - raise ColorArraySizeError("The value is not an array of size 3") - - if not (type(value[0]) == type(22) and type(value[1]) == type(22) and type(value[2]) == type(22)): - raise ColorTypeError("Not all the elements of the array are integers") - - if value[0] > 255 or value[0] <0: - raise ColorValueError("Red value is not between 0 and 255") - - if value[1] > 255 or value[1] <0: - raise ColorValueError("Green value is not between 0 and 255") - - if value[2] > 255 or value[2] <0: - raise ColorValueError("Blue value is not between 0 and 255") - - return - -class BooleanConstraintError(Exception): - pass - -class BooleanConstraint(Constraint): - """ - Validates that the value is either True or False. - """ - def validate(self, value): - if value == True or value == False: - return - raise BooleanConstraintError("Value is not True or False") - -class EnumConstraintError(Exception): - pass - -class EnumConstraint(Constraint): - """ - Validates that the value is part of a set of well-defined values. - """ - def __init__(self, accepted_values): - """ - Creates the constraint and stores the list of accepted values. - - @param correct_values A list that contains all the values that will - be declared as satisfying the constraint - """ - self._accepted_values = accepted_values - - def validate(self, value): - """ - Ensures that the value that is passed is part of the list of accepted - values. - """ - if not value in self._accepted_values: - raise EnumConstraintError("Value is not part of the enumeration") - return - -class FileConstraintError(Exception): - pass - -class FileConstraint(Constraint): - """ - Ensures that the string given corresponds to an existing file on disk. - """ - def validate(self, value): - # TODO : Decide on the architecture for file retrieval on disk - # Relative paths? From where? Support macros? - # - if not os.path.isfile(value): - raise FileConstraintError("Non-existing file : %s"%value) - return - diff --git a/src/sugar/tutorius/core.py b/src/sugar/tutorius/core.py deleted file mode 100644 index dd2435e..0000000 --- a/src/sugar/tutorius/core.py +++ /dev/null @@ -1,528 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com> -# -# 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 -""" -Core - -This module contains the core classes for tutorius - -""" - -import gtk -import logging -import copy -import os - -from sugar.tutorius.dialog import TutoriusDialog -from sugar.tutorius.gtkutils import find_widget -from sugar.tutorius.services import ObjectStore - -logger = logging.getLogger("tutorius") - -class Tutorial (object): - """ - Tutorial Class, used to run through the FSM. - """ - - def __init__(self, name, fsm,filename= None): - """ - Creates an unattached tutorial. - """ - object.__init__(self) - self.name = name - self.activity_init_state_filename = filename - - self.state_machine = fsm - self.state_machine.set_tutorial(self) - - self.state = None - - self.handlers = [] - self.activity = None - #Rest of initialisation happens when attached - - def attach(self, activity): - """ - Attach to a running activity - - @param activity the activity to attach to - """ - #For now, absolutely detach if a previous one! - if self.activity: - self.detach() - self.activity = activity - ObjectStore().activity = activity - ObjectStore().tutorial = self - self._prepare_activity() - self.state_machine.set_state("INIT") - - def detach(self): - """ - Detach from the current activity - """ - - # Uninstall the whole FSM - self.state_machine.teardown() - - #FIXME There should be some amount of resetting done here... - self.activity = None - - - def set_state(self, name): - """ - Switch to a new state - """ - logger.debug("==== NEW STATE: %s ====" % name) - - self.state_machine.set_state(name) - - - # Currently unused -- equivalent function is in each state - def _eventfilter_state_done(self, eventfilter): - """ - Callback handler for eventfilter to notify - when we must go to the next state. - """ - #XXX Tests should be run here normally - - #Swith to the next state pointed by the eventfilter - self.set_state(eventfilter.get_next_state()) - - def _prepare_activity(self): - """ - Prepare the activity for the tutorial by loading the saved state and - emitting gtk signals - """ - #Load the saved activity if any - if self.activity_init_state_filename is not None: - #For now the file will be saved in the data folder - #of the activity root directory - filename = os.getenv("SUGAR_ACTIVITY_ROOT") + "/data/" +\ - self.activity_init_state_filename - if os.path.exists(filename): - self.activity.read_file(filename) - - -class State(object): - """ - This is a step in a tutorial. The state represents a collection of actions - to undertake when entering the state, and a series of event filters - with associated actions that point to a possible next state. - """ - - def __init__(self, name="", action_list=None, event_filter_list=None, tutorial=None): - """ - Initializes the content of the state, like loading the actions - that are required and building the correct tests. - - @param action_list The list of actions to execute when entering this - state - @param event_filter_list A list of tuples of the form - (event_filter, next_state_name), that explains the outgoing links for - this state - @param tutorial The higher level container of the state - """ - object.__init__(self) - - self.name = name - - self._actions = action_list or [] - - # Unused for now - #self.tests = [] - - self._event_filters = event_filter_list or [] - - self.tutorial = tutorial - - def set_tutorial(self, tutorial): - """ - Associates this state with a tutorial. A tutorial must be set prior - to executing anything in the state. The reason for this is that the - states need to have access to the activity (via the tutorial) in order - to properly register their callbacks on the activities' widgets. - - @param tutorial The tutorial that this state runs under. - """ - if self.tutorial == None : - self.tutorial = tutorial - else: - raise RuntimeWarning(\ - "The state %s was already associated with a tutorial." % self.name) - - def setup(self): - """ - Install the state itself, by first registering the event filters - and then triggering the actions. - """ - for eventfilter in self._event_filters: - eventfilter.install_handlers(self._event_filter_state_done_cb, - activity=self.tutorial.activity) - - for action in self._actions: - action.do() - - def teardown(self): - """ - Uninstall all the event filters that were active in this state. - Also undo every action that was installed for this state. This means - removing dialogs that were displayed, removing highlights, etc... - """ - # Remove the handlers for the all of the state's event filters - for event_filter in self._event_filters: - event_filter.remove_handlers() - - # Undo all the actions related to this state - for action in self._actions: - action.undo() - - def _event_filter_state_done_cb(self, event_filter): - """ - Callback for event filters. This function needs to inform the - tutorial that the state is over and tell it what is the next state. - - @param event_filter The event filter that was called - """ - # Run the tests here, if need be - - # Warn the higher level that we wish to change state - self.tutorial.set_state(event_filter.get_next_state()) - - # Model manipulation - # These functions are used to simplify the creation of states - def add_action(self, new_action): - """ - Adds an action to the state (only if it wasn't added before) - - @param new_action The new action to execute when in this state - @return True if added, False otherwise - """ - if new_action not in self._actions: - self._actions.append(new_action) - return True - return False - - # remove_action - We did not define names for the action, hence they're - # pretty hard to remove on a precise basis - - def get_action_list(self): - """ - @return A list of actions that the state will execute - """ - return self._actions - - def clear_actions(self): - """ - Removes all the action associated with this state. A cleared state will - not do anything when entered or exited. - """ - self._actions = [] - - def add_event_filter(self, event_filter): - """ - Adds an event filter that will cause a transition from this state. - - The same event filter may not be added twice. - - @param event_filter The new event filter that will trigger a transition - @return True if added, False otherwise - """ - if event_filter not in self._event_filters: - self._event_filters.append(event_filter) - return True - return False - - def get_event_filter_list(self): - """ - @return The list of event filters associated with this state. - """ - return self._event_filters - - def clear_event_filters(self): - """ - Removes all the event filters associated with this state. A state that - was just cleared will become a sink and will be the end of the - tutorial. - """ - self._event_filters = [] - -class FiniteStateMachine(State): - """ - This is a collection of states, with a start state and an end callback. - It is used to simplify the development of the various tutorials by - encapsulating a collection of states that represent a given learning - process. - - For now, we will consider that there can only be states - inserted in the FSM, and that there are no nested FSM inside. - """ - - def __init__(self, name, tutorial=None, state_dict=None, start_state_name="INIT", action_list=None): - """ - The constructor for a FSM. Pass in the start state and the setup - actions that need to be taken when the FSM itself start (which may be - different from what is done in the first state of the machine). - - @param name A short descriptive name for this FSM - @param tutorial The tutorial that will execute this FSM. If None is - attached on creation, then one must absolutely be attached before - executing the FSM with set_tutorial(). - @param state_dict A dictionary containing the state names as keys and - the state themselves as entries. - @param start_state_name The name of the starting state, if different - from "INIT" - @param action_list The actions to undertake when initializing the FSM - """ - State.__init__(self, name) - - self.name = name - self.tutorial = tutorial - - # Dictionnary of states contained in the FSM - self._states = state_dict or {} - - self.start_state_name = start_state_name - # Set the current state to None - we are not executing anything yet - self.current_state = None - - # Register the actions for the FSM - They will be processed at the - # FSM level, meaning that when the FSM will start, it will first - # execute those actions. When the FSM closes, it will tear down the - # inner actions of the state, then close its own actions - self.actions = action_list or [] - - # Flag to mention that the FSM was initialized - self._fsm_setup_done = False - # Flag that must be raised when the FSM is to be teared down - self._fsm_teardown_done = False - # Flag used to declare that the FSM has reached an end state - self._fsm_has_finished = False - - def set_tutorial(self, tutorial): - """ - This associates the FSM to the given tutorial. It MUST be associated - either in the constructor or with this function prior to executing the - FSM. - - @param tutorial The tutorial that will execute this FSM. - """ - # If there was no tutorial associated - if self.tutorial == None: - # Associate it with this FSM and all the underlying states - self.tutorial = tutorial - for state in self._states.itervalues(): - state.set_tutorial(tutorial) - else: - raise RuntimeWarning(\ - "The FSM %s is already associated with a tutorial."%self.name) - - def setup(self): - """ - This function initializes the FSM the first time it is called. - Then, every time it is called, it initializes the current state. - """ - # Are we associated with a tutorial? - if self.tutorial == None: - raise UnboundLocalError("No tutorial was associated with FSM %s" % self.name) - - # If we never initialized the FSM itself, then we need to run all the - # actions associated with the FSM. - if self._fsm_setup_done == False: - # Remember the initial state - we might want to reset - # or rewind the FSM at a later moment - self.start_state = self._states[self.start_state_name] - self.current_state = self.start_state - # Flag the FSM level setup as done - self._fsm_setup_done = True - # Execute all the FSM level actions - for action in self.actions: - action.do() - - # Then, we need to run the setup of the current state - self.current_state.setup() - - def set_state(self, new_state_name): - """ - This functions changes the current state of the finite state machine. - - @param new_state The identifier of the state we need to go to - """ - # TODO : Since we assume no nested FSMs, we don't set state on the - # inner States / FSMs -## # Pass in the name to the internal state - it might be a FSM and -## # this name will apply to it -## self.current_state.set_state(new_state_name) - - # Make sure the given state is owned by the FSM - if not self._states.has_key(new_state_name): - # If we did not recognize the name, then we do not possess any - # state by that name - we must ignore this state change request as - # it will be done elsewhere in the hierarchy (or it's just bogus). - return - - if self.current_state != None: - if new_state_name == self.current_state.name: - # If we already are in this state, we do not need to change - # anything in the current state - By design, a state may not point - # to itself - return - - new_state = self._states[new_state_name] - - # Undo the actions of the old state - self.teardown() - - # Insert the new state - self.current_state = new_state - - # Call the initial actions in the new state - self.setup() - - def get_current_state_name(self): - """ - Returns the name of the current state. - - @return A string representing the name of the current state - """ - return self.current_state.name - - def teardown(self): - """ - Revert any changes done by setup() - """ - # Teardown the current state - if self.current_state is not None: - self.current_state.teardown() - - # If we just finished the whole FSM, we need to also call the teardown - # on the FSM level actions - if self._fsm_has_finished == True: - # Flag the FSM teardown as not needed anymore - self._fsm_teardown_done = True - # Undo all the FSM level actions here - for action in self.actions: - action.undo() - - # TODO : It might be nice to have a start() and stop() method for the - # FSM. - - # Data manipulation section - # These functions are dedicated to the building and editing of a graph. - def add_state(self, new_state): - """ - Inserts a new state in the FSM. - - @param new_state The State object that will now be part of the FSM - @raise KeyError In the case where a state with this name already exists - """ - if self._states.has_key(new_state.name): - raise KeyError("There is already a state by this name in the FSM") - - self._states[new_state.name] = new_state - - # Not such a great name for the state accessor... We already have a - # set_state name, so get_state would conflict with the notion of current - # state - I would recommend having a set_current_state instead. - def get_state_by_name(self, state_name): - """ - Fetches a state from the FSM, based on its name. If there is no - such state, the method will throw a KeyError. - - @param state_name The name of the desired state - @return The State object having the given name - """ - return self._states[state_name] - - def remove_state(self, state_name): - """ - Removes a state from the FSM. Raises a KeyError when the state is - not existent. - - Warning : removing a state will also remove all the event filters that - point to this given name, to preserve the FSM's integrity. If you only - want to edit a state, you would be better off fetching this state with - get_state_by_name(). - - @param state_name A string being the name of the state to remove - @raise KeyError When the state_name does not a represent a real state - stored in the dictionary - """ - - state_to_remove = self._states[state_name] - - # Remove the state from the states' dictionnary - for st in self._states.itervalues(): - # Iterate through the list of event filters and remove those - # that point to the state that will be removed - - #TODO : Move this code inside the State itself - we're breaking - # encap :P - for event_filter in st._event_filters: - if event_filter.get_next_state() == state_name: - st._event_filters.remove(event_filter) - - # Remove the state from the dictionary - del self._states[state_name] - - # Exploration methods - used to know more about a given state - def get_following_states(self, state_name): - """ - Returns a tuple of the names of the states that point to the given - state. If there is no such state, the function raises a KeyError. - - @param state_name The name of the state to analyse - @raise KeyError When there is no state by this name in the FSM - """ - state = self._states[state_name] - - next_states = set() - - for event_filter in state._event_filters: - next_states.add(event_filter.get_next_state()) - - return tuple(next_states) - - def get_previous_states(self, state_name): - """ - Returns a tuple of the names of the state that can transition to - the given state. If there is no such state, the function raises a - KeyError. - - @param state_name The name of the state that the returned states might - transition to. - """ - # This might seem a bit funny, but we don't verify if the given - # state is present or not in the dictionary. - # This is due to the fact that when building a graph, we might have a - # prototypal state that has not been inserted yet. We could not know - # which states are pointing to it until we insert it in the graph. - - states = [] - # Walk through the list of states - for st in self._states.itervalues(): - for event_filter in st._event_filters: - if event_filter.get_next_state() == state_name: - states.append(event_filter.get_next_state()) - continue - - return tuple(states) - - # Convenience methods to see the content of a FSM - def __str__(self): - out_string = "" - for st in self._states.itervalues(): - out_string += st.name + ", " - return out_string diff --git a/src/sugar/tutorius/creator.py b/src/sugar/tutorius/creator.py deleted file mode 100644 index d6826dc..0000000 --- a/src/sugar/tutorius/creator.py +++ /dev/null @@ -1,436 +0,0 @@ -""" -This package contains UI classes related to tutorial authoring. -This includes visual display of tools to edit and create tutorials from within -the activity itself. -""" -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Simon Poirier <simpoir@gmail.com> -# -# -# 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 1 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 - -import gtk.gdk -import gobject -from gettext import gettext as T - -from sugar.graphics.toolbutton import ToolButton - -from sugar.tutorius import overlayer, gtkutils, actions, bundler, properties, addon -from sugar.tutorius import filters -from sugar.tutorius.services import ObjectStore -from sugar.tutorius.linear_creator import LinearCreator -from sugar.tutorius.tutorial import Tutorial - -class Creator(object): - """ - Class acting as a bridge between the creator, serialization and core - classes. This contains most of the UI part of the editor. - """ - def __init__(self, activity, tutorial=None): - """ - Instanciate a tutorial creator for the activity. - - @param activity to bind the creator to - @param tutorial an existing tutorial to edit, or None to create one - """ - self._activity = activity - if not tutorial: - self._tutorial = LinearCreator() - else: - self._tutorial = tutorial - - self._action_panel = None - self._current_filter = None - self._intro_mask = None - self._intro_handle = None - self._state_bubble = overlayer.TextBubble(self._tutorial.state_name) - allocation = self._activity.get_allocation() - self._width = allocation.width - self._height = allocation.height - self._selected_widget = None - self._eventmenu = None - - self._hlmask = overlayer.Rectangle(None, (1.0, 0.0, 0.0, 0.5)) - self._activity._overlayer.put(self._hlmask, 0, 0) - - self._activity._overlayer.put(self._state_bubble, - self._width/2-self._state_bubble.allocation.width/2, 0) - - dlg_width = 300 - dlg_height = 70 - sw = gtk.gdk.screen_width() - sh = gtk.gdk.screen_height() - self._tooldialog = gtk.Window() - self._tooldialog.set_title("Tutorius tools") - self._tooldialog.set_transient_for(self._activity) - self._tooldialog.set_decorated(True) - self._tooldialog.set_resizable(False) - self._tooldialog.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_UTILITY) - self._tooldialog.set_destroy_with_parent(True) - self._tooldialog.set_deletable(False) - self._tooldialog.set_size_request(dlg_width, dlg_height) - - toolbar = gtk.Toolbar() - for tool in addon.list_addons(): - meta = addon.get_addon_meta(tool) - toolitem = ToolButton(meta['icon']) - toolitem.set_tooltip(meta['display_name']) - toolitem.connect("clicked", self._add_action_cb, tool) - toolbar.insert(toolitem, -1) - toolitem = ToolButton("go-next") - toolitem.connect("clicked", self._add_step_cb) - toolitem.set_tooltip("Add Step") - toolbar.insert(toolitem, -1) - toolitem = ToolButton("stop") - toolitem.connect("clicked", self._cleanup_cb) - toolitem.set_tooltip("End Tutorial") - toolbar.insert(toolitem, -1) - self._tooldialog.add(toolbar) - self._tooldialog.show_all() - # simpoir: I suspect the realized widget is a tiny bit larger than - # it should be, thus the -10. - self._tooldialog.move(sw-10-dlg_width, sh-dlg_height) - - self._propedit = EditToolBox(self._activity) - - def _evfilt_cb(self, menuitem, event_name, *args): - """ - This will get called once the user has selected a menu item from the - event filter popup menu. This should add the correct event filter - to the FSM and increment states. - """ - self.introspecting = False - eventfilter = addon.create('GtkWidgetEventFilter', - next_state=None, - object_id=self._selected_widget, - event_name=event_name) - # undo actions so they don't persist through step editing - for action in self._tutorial.current_actions: - action.exit_editmode() - self._tutorial.event(eventfilter) - self._state_bubble.label = self._tutorial.state_name - self._hlmask.covered = None - self._propedit.action = None - self._activity.queue_draw() - - def _intro_cb(self, widget, evt): - """ - Callback for capture of widget events, when in introspect mode. - """ - if evt.type == gtk.gdk.BUTTON_PRESS: - # widget has focus, let's hilight it - win = gtk.gdk.display_get_default().get_window_at_pointer() - click_wdg = win[0].get_user_data() - if not click_wdg.is_ancestor(self._activity._overlayer): - # as popups are not (yet) supported, it would break - # badly if we were to play with a widget not in the - # hierarchy. - return - for hole in self._intro_mask.pass_thru: - self._intro_mask.mask(hole) - self._intro_mask.unmask(click_wdg) - self._selected_widget = gtkutils.raddr_lookup(click_wdg) - - if self._eventmenu: - self._eventmenu.destroy() - self._eventmenu = gtk.Menu() - menuitem = gtk.MenuItem(label=type(click_wdg).__name__) - menuitem.set_sensitive(False) - self._eventmenu.append(menuitem) - self._eventmenu.append(gtk.MenuItem()) - - for item in gobject.signal_list_names(click_wdg): - menuitem = gtk.MenuItem(label=item) - menuitem.connect("activate", self._evfilt_cb, item) - self._eventmenu.append(menuitem) - self._eventmenu.show_all() - self._eventmenu.popup(None, None, None, evt.button, evt.time) - self._activity.queue_draw() - - def set_intropecting(self, value): - """ - Set whether creator is in UI introspection mode. Setting this will - connect necessary handlers. - @param value True to setup introspection handlers. - """ - if bool(value) ^ bool(self._intro_mask): - if value: - self._intro_mask = overlayer.Mask(catch_events=True) - self._intro_handle = self._intro_mask.connect_after( - "button-press-event", self._intro_cb) - self._activity._overlayer.put(self._intro_mask, 0, 0) - else: - self._intro_mask.catch_events = False - self._intro_mask.disconnect(self._intro_handle) - self._intro_handle = None - self._activity._overlayer.remove(self._intro_mask) - self._intro_mask = None - - def get_introspecting(self): - """ - Whether creator is in UI introspection (catch all event) mode. - @return True if introspection handlers are connected, or False if not. - """ - return bool(self._intro_mask) - - introspecting = property(fset=set_intropecting, fget=get_introspecting) - - def _add_action_cb(self, widget, actiontype): - """Callback for the action creation toolbar tool""" - action = addon.create(actiontype) - if isinstance(action, actions.Action): - action.enter_editmode() - self._tutorial.action(action) - # FIXME: replace following with event catching - action._drag._eventbox.connect_after( - "button-release-event", self._action_refresh_cb, action) - else: - addonname = type(action).__name__ - meta = addon.get_addon_meta(addonname) - had_introspect = False - for propname in meta['mandatory_props']: - prop = getattr(type(action), propname) - if isinstance(prop, properties.TUAMProperty): - had_introspect = True - self.introspecting = True - elif isinstance(prop, properties.TStringProperty): - dlg = TextInputDialog(title="Mandatory property", - field=propname) - setattr(action, propname, dlg.pop()) - else: - raise NotImplementedError() - - # FIXME: hack to reuse previous introspection code - if not had_introspect: - self._tutorial.event(action) - - - def _action_refresh_cb(self, widget, evt, action): - """ - Callback for refreshing properties values and notifying the - property dialog of the new values. - """ - action.exit_editmode() - action.enter_editmode() - self._activity.queue_draw() - # TODO: replace following with event catching - action._drag._eventbox.connect_after( - "button-release-event", self._action_refresh_cb, action) - self._propedit.action = action - - def _add_step_cb(self, widget): - """Callback for the "add step" tool""" - self.introspecting = True - - def _cleanup_cb(self, *args): - """ - Quit editing and cleanup interface artifacts. - """ - self.introspecting = False - eventfilter = filters.EventFilter(None) - # undo actions so they don't persist through step editing - for action in self._tutorial.current_actions: - action.exit_editmode() - self._tutorial.event(eventfilter) - - dlg = TextInputDialog(text=T("Enter a tutorial title."), - field=T("Title")) - tutorialName = "" - while not tutorialName: tutorialName = dlg.pop() - dlg.destroy() - - # prepare tutorial for serialization - tuto = Tutorial(tutorialName, self._tutorial.fsm) - bundle = bundler.TutorialBundler() - bundle.write_metadata_file(tuto) - bundle.write_fsm(self._tutorial.fsm) - - # remove UI remains - self._hlmask.covered = None - self._activity._overlayer.remove(self._hlmask) - self._activity._overlayer.remove(self._state_bubble) - self._hlmask.destroy() - self._hlmask = None - self._tooldialog.destroy() - self._propedit.destroy() - self._activity.queue_draw() - del self._activity._creator - - def launch(*args, **kwargs): - """ - Launch and attach a creator to the currently running activity. - """ - activity = ObjectStore().activity - if not hasattr(activity, "_creator"): - activity._creator = Creator(activity) - launch = staticmethod(launch) - -class EditToolBox(gtk.Window): - """Helper toolbox class for managing action properties""" - def __init__(self, parent, action=None): - """ - Create the property edition toolbox and display it. - - @param parent the parent window of this toolbox, usually an activity - @param action the action to introspect/edit - """ - gtk.Window.__init__(self) - self._action = None - self.__parent = parent # private avoid gtk clash - - self.set_title("Action Properties") - self.set_transient_for(parent) - self.set_decorated(True) - self.set_resizable(False) - self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_UTILITY) - self.set_destroy_with_parent(True) - self.set_deletable(False) - self.set_size_request(200, 400) - - self._vbox = gtk.VBox() - self.add(self._vbox) - propwin = gtk.ScrolledWindow() - propwin.props.hscrollbar_policy = gtk.POLICY_AUTOMATIC - propwin.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC - self._vbox.pack_start(propwin) - self._propbox = gtk.VBox(spacing=10) - propwin.add(self._propbox) - - self.action = action - - sw = gtk.gdk.screen_width() - sh = gtk.gdk.screen_height() - - self.show_all() - self.move(sw-10-200, (sh-400)/2) - - def refresh(self): - """Refresh property values from the selected action.""" - if self._action is None: - return - props = self._action._props.keys() - for propnum in xrange(len(props)): - row = self._propbox.get_children()[propnum] - propname = props[propnum] - prop = getattr(type(self._action), propname) - propval = getattr(self._action, propname) - if isinstance(prop, properties.TStringProperty): - propwdg = row.get_children()[1] - propwdg.get_buffer().set_text(propval) - elif isinstance(prop, properties.TIntProperty): - propwdg = row.get_children()[1] - propwdg.set_value(propval) - elif isinstance(prop, properties.TArrayProperty): - propwdg = row.get_children()[1] - for i in xrange(len(propval)): - entry = propwdg.get_children()[i] - entry.set_text(str(propval[i])) - else: - propwdg = row.get_children()[1] - propwdg.set_text(str(propval)) - - def set_action(self, action): - """Setter for the action property.""" - if self._action is action: - self.refresh() - return - parent = self._propbox.get_parent() - parent.remove(self._propbox) - self._propbox = gtk.VBox(spacing=10) - parent.add(self._propbox) - - self._action = action - if action is None: - return - for propname in action._props.keys(): - row = gtk.HBox() - row.pack_start(gtk.Label(T(propname)), False, False, 10) - prop = getattr(type(action), propname) - propval = getattr(action, propname) - if isinstance(prop, properties.TStringProperty): - propwdg = gtk.TextView() - propwdg.get_buffer().set_text(propval) - propwdg.connect_after("focus-out-event", \ - self._str_prop_changed, action, propname) - elif isinstance(prop, properties.TIntProperty): - adjustment = gtk.Adjustment(value=propval, - lower=prop.lower_limit.limit, - upper=prop.upper_limit.limit, - step_incr=1) - propwdg = gtk.SpinButton(adjustment=adjustment) - propwdg.connect_after("focus-out-event", \ - self._int_prop_changed, action, prop) - elif isinstance(prop, properties.TArrayProperty): - propwdg = gtk.HBox() - for i in xrange(len(propval)): - entry = gtk.Entry() - propwdg.pack_start(entry) - entry.connect_after("focus-out-event", \ - self._list_prop_changed, action, propname, i) - else: - propwdg = gtk.Entry() - propwdg.set_text(str(propval)) - row.pack_end(propwdg) - self._propbox.pack_start(row, expand=False) - self._vbox.show_all() - self.refresh() - - def get_action(self): - """Getter for the action property""" - return self._action - action = property(fset=set_action, fget=get_action, doc=\ - "Action to be edited through introspection.") - - def _list_prop_changed(self, widget, evt, action, propname, idx): - try: - getattr(action, propname)[idx] = int(widget.get_text()) - except ValueError: - widget.set_text(str(getattr(action, propname)[idx])) - self.__parent._creator._action_refresh_cb(None, None, action) - def _str_prop_changed(self, widget, evt, action, propname): - buf = widget.get_buffer() - setattr(action, propname, buf.get_text(buf.get_start_iter(), buf.get_end_iter())) - self.__parent._creator._action_refresh_cb(None, None, action) - def _int_prop_changed(self, widget, evt, action, prop): - setattr(action, propname, widget.get_value_as_int()) - self.__parent._creator._action_refresh_cb(None, None, action) - -class TextInputDialog(gtk.MessageDialog): - def __init__(self, text, field): - gtk.MessageDialog.__init__(self, None, - gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, - gtk.MESSAGE_QUESTION, - gtk.BUTTONS_OK, - None) - self.set_markup(text) - self.entry = gtk.Entry() - self.entry.connect("activate", self._dialog_done_cb, gtk.RESPONSE_OK) - hbox = gtk.HBox() - lbl = gtk.Label(field) - hbox.pack_start(lbl, False) - hbox.pack_end(self.entry) - self.vbox.pack_end(hbox, True, True) - self.show_all() - - def pop(self): - self.run() - self.hide() - text = self.entry.get_text() - return text - - def _dialog_done_cb(self, entry, response): - self.response(response) - -# vim:set ts=4 sts=4 sw=4 et: diff --git a/src/sugar/tutorius/dialog.py b/src/sugar/tutorius/dialog.py deleted file mode 100644 index be51a0e..0000000 --- a/src/sugar/tutorius/dialog.py +++ /dev/null @@ -1,59 +0,0 @@ -# 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 -""" -The Dialog module provides means of interacting with the user -through the use of Dialogs. -""" -import gtk - -class TutoriusDialog(gtk.Dialog): - """ - TutoriusDialog is a simple wrapper around gtk.Dialog. - - It allows creating and showing a dialog and connecting the response and - button click events to callbacks. - """ - def __init__(self, label="Hint", button_clicked_cb=None, response_cb=None): - """ - Constructor. - - @param label text to be shown on the dialog - @param button_clicked_cb callback for the button click - @param response_cb callback for the dialog response - """ - gtk.Dialog.__init__(self) - - self._button = gtk.Button(label) - - self.add_action_widget(self._button, 1) - - if not button_clicked_cb == None: - self._button.connect("clicked", button_clicked_cb) - - self._button.show() - - if not response_cb == None: - self.connect("response", response_cb) - - self.set_decorated(False) - - def set_button_clicked_cb(self, funct): - """Setter for the button_clicked callback""" - self._button.connect("clicked", funct) - - def close_self(self, arg=None): - """Close the dialog""" - self.destroy() diff --git a/src/sugar/tutorius/editor.py b/src/sugar/tutorius/editor.py deleted file mode 100644 index 42cc718..0000000 --- a/src/sugar/tutorius/editor.py +++ /dev/null @@ -1,318 +0,0 @@ -# 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 Module -""" - -import gtk -import gobject -#import hippo -#import gconf - -from gettext import gettext as _ - -from sugar.tutorius.gtkutils import register_signals_numbered, get_children - -class WidgetIdentifier(gtk.Window): - """ - Tool that allows identifying widgets. - - """ - __gtype_name__ = 'TutoriusWidgetIdentifier' - - def __init__(self, activity): - gtk.Window.__init__(self) - - self._activity = activity - self._handlers = {} - # dict of signals to register on the widgets. - # key : signal name - # value : initial checkbox status - signals = { - "focus":True, - "button-press-event":True, - "enter-notify-event":False, - "leave-notify-event":False, - "key-press-event":True, - "text-selected":True, - "clicked":True, - } - - self.set_decorated(False) - self.set_resizable(False) - self.set_modal(False) - - self.connect('realize', self.__realize_cb) - - self._expander = gtk.Expander(_("Widget Identifier")) - self._expander.set_expanded(True) - self.add(self._expander) - self._expander.connect("notify::expanded", self.__expander_cb) - - self._expander.show() - - nbk = gtk.Notebook() - self._expander.add(nbk) - nbk.show() - - ############################### - # Event log viewer page - ############################### - self.logview = gtk.TextView() - self.logview.set_editable(False) - self.logview.set_cursor_visible(False) - self.logview.set_wrap_mode(gtk.WRAP_NONE) - self._textbuffer = self.logview.get_buffer() - - swd = gtk.ScrolledWindow() - swd.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - swd.add(self.logview) - self.logview.show() - - nbk.append_page(swd, gtk.Label(_("Log"))) - swd.show() - - ############################### - # Filters page - ############################### - filters = gtk.Table( (len(signals)+1)/2, 2) - - xpos, ypos = 0, 0 - for key, active in signals.items(): - cbtn = gtk.CheckButton(label=key) - filters.attach(cbtn, xpos, xpos+1, ypos, ypos+1) - cbtn.show() - cbtn.set_active(active) - if active: - self._handlers[key] = register_signals_numbered( \ - self._activity, self._handle_events, events=(key,)) - else: - self._handlers[key] = [] - - cbtn.connect("toggled", self.__filter_toggle_cb, key) - - #Follow lines then columns - xpos, ypos = (xpos+1)%2, ypos+(xpos%2) - - nbk.append_page(filters, gtk.Label(_("Events"))) - filters.show() - - ############################### - # Explorer Page - ############################### - tree = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING) - explorer = gtk.TreeView(tree) - - pathrendr = gtk.CellRendererText() - pathrendr.set_properties(background="#ffffff", foreground="#000000") - pathcol = gtk.TreeViewColumn(_("Path"), pathrendr, text=0, background=0, foreground=0) - explorer.append_column(pathcol) - - typerendr = gtk.CellRendererText() - typerendr.set_properties(background="#ffffff", foreground="#000000") - typecol = gtk.TreeViewColumn(_("Widget"), typerendr, text=1, background=1, foreground=1) - explorer.append_column(typecol) - - self.__populate_treestore( - tree, #tree - tree.append(None, ["0",self._activity.get_name()]), #parent - self._activity, #widget - "0" #path - ) - - explorer.set_expander_column(typecol) - - swd2 = gtk.ScrolledWindow() - swd2.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - swd2.add(explorer) - explorer.show() - nbk.append_page(swd2, gtk.Label(_("Explorer"))) - swd2.show() - - ############################### - # GObject Explorer Page - ############################### - tree2 = gtk.TreeStore(gobject.TYPE_STRING, gobject.TYPE_STRING) - explorer2 = gtk.TreeView(tree2) - - pathrendr2 = gtk.CellRendererText() - pathrendr2.set_properties(background="#ffffff", foreground="#000000") - pathcol2 = gtk.TreeViewColumn(_("Path"), pathrendr2, text=0, background=0, foreground=0) - explorer2.append_column(pathcol2) - - typerendr2 = gtk.CellRendererText() - typerendr2.set_properties(background="#ffffff", foreground="#000000") - typecol2 = gtk.TreeViewColumn(_("Widget"), typerendr2, text=1, background=1, foreground=1) - explorer2.append_column(typecol2) - - self.__populate_gobject_treestore( - tree2, #tree - tree2.append(None, ["activity",self._activity.get_name()]), #parent - self._activity, #widget - "activity" #path - ) - - explorer2.set_expander_column(typecol2) - - swd3 = gtk.ScrolledWindow() - swd3.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - swd3.add(explorer2) - explorer2.show() - nbk.append_page(swd3, gtk.Label(_("GObject Explorer"))) - swd3.show() - - def __populate_treestore(self, tree, parent, widget, path): - """Populates the treestore with the widget's children recursively - @param tree gtk.TreeStore to populate - @param parent gtk.TreeIter to append to - @param widget gtk.Widget to check for children - @param path treeish of the widget - """ - #DEBUG: show parameters in log window gehehe - #self._handle_events((path,str(type(widget)))) - children = get_children(widget) - for i in xrange(len(children)): - childpath = ".".join([path, str(i)]) - child = children[i] - self.__populate_treestore( - tree, #tree - tree.append(parent, [childpath, child.get_name()]), #parent - child, #widget - childpath #path - ) - - - def __populate_gobject_treestore(self, tree, parent, widget, path, listed=None): - """Populates the treestore with the widget's children recursively - @param tree gtk.TreeStore to populate - @param parent gtk.TreeIter to append to - @param widget gtk.Widget to check for children - @param path treeish of the widget - """ - listed = listed or [] - if widget in listed: - return - listed.append(widget) - #DEBUG: show parameters in log window gehehe - #self._handle_events((path,str(type(widget)))) - #Add a child node - children = tree.append(parent, ["","children"]) - for i in dir(widget): - #Add if a gobject - try: - child = getattr(widget, i) - except: - continue - if isinstance(child,gobject.GObject): - childpath = ".".join([path, i]) - child = getattr(widget, i) - self.__populate_gobject_treestore( - tree, #tree - tree.append(children, [childpath, i]), #parent - child, #widget - path + "." + i, #path, - listed - ) - widgets = tree.append(parent, ["","widgets"]) - wchildren = get_children(widget) - for i in xrange(len(wchildren)): - childpath = ".".join([path, str(i)]) - child = wchildren[i] - self.__populate_gobject_treestore( - tree, #tree - tree.append(widgets, [childpath, (hasattr(child,"get_name") and child.get_name()) or i]), #parent - child, #widget - childpath, #path, - listed - ) - - #Add signals and attributes nodes - signals = tree.append(parent, ["","signals"]) - for signame in gobject.signal_list_names(widget): - tree.append(signals, ["",signame]) - - attributes = tree.append(parent, ["","properties"]) - for prop in gobject.list_properties(widget): - tree.append(attributes, ["",prop]) - - def __filter_toggle_cb(self, btn, eventname): - """Callback for signal name checkbuttons' toggling""" - #Disconnect existing handlers on key - self.__disconnect_handlers(eventname) - if btn.get_active(): - #if checked, reconnect - self._handlers[eventname] = register_signals_numbered( \ - self._activity, self._handle_events, events=(eventname,)) - - - 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""" - width = 500 - height = 300 - swidth = gtk.gdk.screen_width() - sheight = gtk.gdk.screen_height() - - self.set_size_request(width, height) - self.move((swidth-width)/2, 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, 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() - - def _disconnect_handlers(self): - """ Disconnect all event handlers """ - for key in self._handlers: - self.__disconnect_handlers(key) - - def __disconnect_handlers(self, key): - """ Disconnect event handlers associated to signal name "key" """ - if self._handlers.has_key(key): - for widget, handlerid in self._handlers[key]: - widget.handler_disconnect(handlerid) - del self._handlers[key] - - def _handle_events(self, *args): - """ Event handler for subscribed widget events. - Accepts variable length argument list. Last must be - a two-tuple containing (event name, widget name) """ - sig, name = args[-1] - text = "\r\n".join( - (["%s event received from %s" % (sig, name)] + - self._textbuffer.get_text(*(self._textbuffer.get_bounds()) - ).split("\r\n"))[:80] - ) - self._textbuffer.set_text(text) - - diff --git a/src/sugar/tutorius/filters.py b/src/sugar/tutorius/filters.py deleted file mode 100644 index aa8c997..0000000 --- a/src/sugar/tutorius/filters.py +++ /dev/null @@ -1,204 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com> -# -# 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 - -import gobject -import gtk -import logging -logger = logging.getLogger("filters") - -from sugar.tutorius.gtkutils import find_widget -from sugar.tutorius.services import ObjectStore -from sugar.tutorius import properties - - -class EventFilter(properties.TPropContainer): - """ - Base class for an event filter - """ - - next_state = properties.TStringProperty("None") - - def __init__(self, next_state=None): - """ - Constructor. - @param next_state name of the next state - """ - super(EventFilter, self).__init__() - if next_state: - self.next_state = next_state - self._callback = None - - def get_next_state(self): - """ - Getter for the next state - """ - return self.next_state - - def set_next_state(self, new_next_name): - """ - Setter for the next state. Should only be used during construction of - the event_fitler, not while the tutorial is running. - """ - self.next_state = new_next_name - - def install_handlers(self, callback, **kwargs): - """ - install_handlers is called for eventfilters to setup all - necessary event handlers to be able to catch the desired - event. - - @param callback the callback function that will be called - with the event filter as an argument when the event is catched - and validated. - @param **kwargs unused by this handler for now, allows subclasses - to receive information about the context when installing - - Subclasses must call this super method to setup the callback if they - feel like cooperating - """ - self._callback = callback - - def remove_handlers(self): - """ - remove_handlers is called when a state is done so that all - event filters can cleanup any handlers they have installed - - This function will also clear the callback function so that any - leftover handler that is triggered will not be able to change the - application state. - - subclasses must call this super method to cleanup the callback if they - collaborate and use this classe's do_callback() - """ - self._callback = None - - def do_callback(self, *args, **kwargs): - """ - Default callback function that calls the event filter callback - with the event filter as only argument. - """ - if self._callback: - self._callback(self) - -class TimerEvent(EventFilter): - """ - TimerEvent is a special EventFilter that uses gobject - timeouts to trigger a state change after a specified amount - of time. It must be used inside a gobject main loop to work. - """ - def __init__(self,next_state,timeout_s): - """Constructor. - - @param next_state default EventFilter param, passed on to EventFilter - @param timeout_s timeout in seconds - """ - super(TimerEvent,self).__init__(next_state) - self._timeout = timeout_s - self._handler_id = None - - def install_handlers(self, callback, **kwargs): - """install_handlers creates the timer and starts it""" - super(TimerEvent,self).install_handlers(callback, **kwargs) - #Create the timer - self._handler_id = gobject.timeout_add_seconds(self._timeout, self._timeout_cb) - - def remove_handlers(self): - """remove handler removes the timer""" - super(TimerEvent,self).remove_handlers() - if self._handler_id: - try: - #XXX What happens if this was already triggered? - #remove the timer - gobject.source_remove(self._handler_id) - except: - pass - - def _timeout_cb(self): - """ - _timeout_cb triggers the eventfilter callback. - - It is necessary because gobject timers only stop if the callback they - trigger returns False - """ - self.do_callback() - return False #Stops timeout - -class GtkWidgetTypeFilter(EventFilter): - """ - Event Filter that listens for keystrokes on a widget - """ - def __init__(self, next_state, object_id, text=None, strokes=None): - """Constructor - @param next_state default EventFilter param, passed on to EventFilter - @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__(next_state) - self._object_id = object_id - self._text = text - self._captext = "" - self._strokes = strokes - self._capstrokes = [] - self._widget = None - self._handler_id = None - - def install_handlers(self, callback, **kwargs): - """install handlers - @param callback default EventFilter callback arg - """ - super(GtkWidgetTypeFilter, self).install_handlers(callback, **kwargs) - logger.debug("~~~GtkWidgetTypeFilter install") - activity = ObjectStore().activity - if activity is None: - logger.error("No activity") - raise RuntimeWarning("no activity in the objectstore") - - self._widget = find_widget(activity, self._object_id) - if self._widget: - self._handler_id= self._widget.connect("key-press-event",self.__keypress_cb) - logger.debug("~~~Connected handler %d on %s" % (self._handler_id,self._object_id) ) - - def remove_handlers(self): - """remove handlers""" - super(GtkWidgetTypeFilter, self).remove_handlers() - #if an event was connected, disconnect it - if self._handler_id: - self._widget.handler_disconnect(self._handler_id) - self._handler_id=None - - def __keypress_cb(self, widget, event, *args): - """keypress callback""" - logger.debug("~~~keypressed!") - key = event.keyval - keystr = event.string - logger.debug("~~~Got key: " + str(key) + ":"+ keystr) - self._capstrokes += [key] - #TODO Treat other stuff, such as arrows - if key == gtk.keysyms.BackSpace: - self._captext = self._captext[:-1] - else: - self._captext = self._captext + keystr - - logger.debug("~~~Current state: " + str(self._capstrokes) + ":" + str(self._captext)) - if not self._strokes is None and self._strokes in self._capstrokes: - self.do_callback() - if not self._text is None and self._text in self._captext: - self.do_callback() - diff --git a/src/sugar/tutorius/gtkutils.py b/src/sugar/tutorius/gtkutils.py deleted file mode 100644 index 1a9cb0f..0000000 --- a/src/sugar/tutorius/gtkutils.py +++ /dev/null @@ -1,203 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com> -# -# 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 -""" -Utility classes and functions that are gtk related -""" -import gtk - -def raddr_lookup(widget): - name = [] - child = widget - parent = widget.parent - while parent: - name.append(str(parent.get_children().index(child))) - child = parent - parent = child.parent - - name.append("0") # root object itself - name.reverse() - return ".".join(name) - - -def find_widget(base, target_fqdn): - """Find a widget by digging into a parent widget's children tree - @param base the parent widget - @param target_fqdn fqdn-style target object name - - @return widget found - - The object should normally be the activity widget, as it is the root - widget for activities. The target_fqdn is a dot separated list of - indexes used in widget.get_children and should start with a 0 which is - the base widget itself, - - Example Usage: - find_widget(activity,"0.0.0.1.0.0.2") - """ - path = target_fqdn.split(".") - #We select the first object and pop the first zero - obj = base - path.pop(0) - - while len(path) > 0: - try: - obj = get_children(obj)[int(path.pop(0))] - except: - break - - return obj - -EVENTS = ( - "focus", - "button-press-event", - "enter-notify-event", - "leave-notify-event", - "key-press-event", - "text-selected", - "clicked", -) - -IGNORED_WIDGETS = ( - "GtkVBox", - "GtkHBox", - "GtkAlignment", - "GtkNotebook", - "GtkButton", - "GtkToolItem", - "GtkToolbar", -) - -def register_signals_numbered(target, handler, prefix="0", max_depth=None, events=None): - """ - Recursive function to register event handlers on an target - and it's children. The event handler is called with an extra - argument which is a two-tuple containing the signal name and - the FQDN-style name of the target that triggered the event. - - This function registers all of the events listed in - EVENTS - - Example arg tuple added: - ("focus", "1.1.2") - Side effects: - -Handlers connected on the various targets - - @param target the target to recurse on - @param handler the handler function to connect - @param prefix name prepended to the target name to form a chain - @param max_depth maximum recursion depth, None for infinity - - @returns list of (object, handler_id) - """ - ret = [] - evts = events or EVENTS - #Gtk Containers have a get_children() function - children = get_children(target) - for i in range(len(children)): - child = children[i] - if max_depth is None or max_depth > 0: - #Recurse with a prefix on all children - pre = ".".join( \ - [p for p in (prefix, str(i)) if not p is None] - ) - if max_depth is None: - dep = None - else: - dep = max_depth - 1 - ret+=register_signals_numbered(child, handler, pre, dep, evts) - #register events on the target if a widget XXX necessary to check this? - if isinstance(target, gtk.Widget): - for sig in evts: - try: - ret.append( \ - (target, target.connect(sig, handler, (sig, prefix) ))\ - ) - except TypeError: - pass - - return ret - -def register_signals(target, handler, prefix=None, max_depth=None, events=None): - """ - Recursive function to register event handlers on an target - and it's children. The event handler is called with an extra - argument which is a two-tuple containing the signal name and - the FQDN-style name of the target that triggered the event. - - This function registers all of the events listed in - EVENTS and omits widgets with a name matching - IGNORED_WIDGETS from the name hierarchy. - - Example arg tuple added: - ("focus", "Activity.Toolbox.Bold") - Side effects: - -Handlers connected on the various targets - - @param target the target to recurse on - @param handler the handler function to connect - @param prefix name prepended to the target name to form a chain - @param max_depth maximum recursion depth, None for infinity - - @returns list of (object, handler_id) - """ - ret = [] - evts = events or EVENTS - #Gtk Containers have a get_children() function - for child in get_children(target): - if max_depth is None or max_depth > 0: - #Recurse with a prefix on all children - pre = ".".join( \ - [p for p in (prefix, target.get_name()) \ - if not (p is None or p in IGNORED_WIDGETS)] \ - ) - if max_depth is None: - dep = None - else: - dep = max_depth - 1 - ret += register_signals(child, handler, pre, dep, evts) - name = ".".join( \ - [p for p in (prefix, target.get_name()) \ - if not (p is None or p in IGNORED_WIDGETS)] \ - ) - #register events on the target if a widget XXX necessary to check this? - if isinstance(target, gtk.Widget): - for sig in evts: - try: - ret.append( \ - (target, target.connect(sig, handler, (sig, name) )) \ - ) - except TypeError: - pass - - return ret - -def get_children(widget): - """Lists widget's children""" - #widgets with multiple children - try: - return widget.get_children() - except (AttributeError,TypeError): - pass - - #widgets with a single child - try: - return [widget.get_child(),] - except (AttributeError,TypeError): - pass - - #otherwise return empty list - return [] diff --git a/src/sugar/tutorius/linear_creator.py b/src/sugar/tutorius/linear_creator.py deleted file mode 100644 index 91b11f4..0000000 --- a/src/sugar/tutorius/linear_creator.py +++ /dev/null @@ -1,95 +0,0 @@ -# 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 - -from sugar.tutorius.core import * -from sugar.tutorius.actions import * -from sugar.tutorius.filters import * - -from copy import deepcopy - -class LinearCreator(object): - """ - This class is used to create a FSM from a linear sequence of orders. The - orders themselves are meant to be either an action or a transition. - """ - - def __init__(self): - self.fsm = FiniteStateMachine("Sample Tutorial") - self.current_actions = [] - self.nb_state = 0 - self.state_name = "INIT" - - def set_name(self, name): - """ - Sets the name of the generated FSM. - """ - self.fsm.name = name - - def action(self, action): - """ - Adds an action to execute in the current state. - """ - self.current_actions.append(action) - - def event(self, event_filter): - """ - Adds a transition to another state. When executing this, all the actions - previously called will be bundled in a single state, with the exit - condition of this state being the transition just added. - - Whatever the name of the next state you inserted in the event, it will - be replaced to point to the next event in the line. - """ - if len(self.current_actions) != 0: - # Set the next state name - there is no way the caller should have - # to deal with that. - next_state_name = "State %d" % (self.nb_state+1) - event_filter.set_next_state(next_state_name) - state = State(self.state_name, action_list=self.current_actions, - event_filter_list=[event_filter]) - self.state_name = next_state_name - - self.nb_state += 1 - self.fsm.add_state(state) - - # Clear the actions from the list - self.current_actions = [] - - def generate_fsm(self): - """ - Returns a finite state machine corresponding to the sequence of calls - that were made from this point on. - """ - # Copy the whole FSM that was generated yet - new_fsm = deepcopy(self.fsm) - - # Generate the final state - state = None - if len(self.current_actions) != 0: - state = State("State" + str(self.nb_state), action_list=self.current_actions) - # Don't increment the nb_state here - we would break the linearity - # because we might generate more stuff with this creator later. - # Since we rely on linearity for continuity when generating the - # next state's name on an event filter, we cannot increment here. - else: - state = State("State" + str(self.nb_state)) - - # Insert the state in the copy of the FSM - new_fsm.add_state(state) - - return new_fsm - diff --git a/src/sugar/tutorius/overlayer.py b/src/sugar/tutorius/overlayer.py deleted file mode 100644 index 931949d..0000000 --- a/src/sugar/tutorius/overlayer.py +++ /dev/null @@ -1,503 +0,0 @@ -""" -This module manages drawing of overlayed widgets. The class responsible for -drawing management (Overlayer) and basic overlayable widgets are defined here. -""" -# 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 - -import gobject -import gtk -import cairo -import pangocairo -from math import pi - -from sugar import profile - -# for easy profile access from cairo -color = profile.get_color().get_stroke_color() -xo_line_color = (int(color[1:3], 16)/255.0, - int(color[3:5], 16)/255.0, - int(color[5:7], 16)/255.0) -color = profile.get_color().get_fill_color() -xo_fill_color = (int(color[1:3], 16)/255.0, - int(color[3:5], 16)/255.0, - int(color[5:7], 16)/255.0) -del color - -# This is the CanvasDrawable protocol. Any widget wishing to be drawn on the -# overlay must implement it. See TextBubble for a sample implementation. -#class CanvasDrawable(object): -# """Defines the CanvasDrawable protocol""" -# no_expose = None -# def draw_with_context(self, context): -# """ -# Draws the cairo widget with the passed cairo context. -# This will be called if the widget is child of an overlayer. -# """ -# pass - -class Overlayer(gtk.Layout): - """ - This guy manages drawing of overlayed widgets. Those can be standard GTK - widgets or special "cairoDrawable" widgets which support the defined - interface (see the put method). - - @param overlayed widget to be overlayed. Will be resized to full size. - """ - def __init__(self, overlayed=None): - gtk.Layout.__init__(self) - - self._overlayed = overlayed - if overlayed: - self.put(overlayed, 0, 0) - - self.__realizer = self.connect("expose-event", self.__init_realized) - self.connect("size-allocate", self.__size_allocate) - self.show() - - self.__render_handle = None - - def put(self, child, x, y): - """ - Adds a child widget to be overlayed. This can be, overlay widgets or - normal GTK widgets (though normal widgets will alwas appear under - cairo widgets due to the rendering chain). - - @param child the child to add - @param x the horizontal coordinate for positionning - @param y the vertical coordinate for positionning - """ - if hasattr(child, "draw_with_context"): - # if the widget has the CanvasDrawable protocol, use it. - child.no_expose = True - gtk.Layout.put(self, child, x, y) - - # be sure to redraw or the overlay may not show - self.queue_draw() - - - def __init_realized(self, widget, event): - """ - Initializer to set once widget is realized. - Since an expose event is signaled only to realized widgets, we set this - callback for the first expose run. It should also be called after - beign reparented to ensure the window used for drawing is set up. - """ - assert hasattr(self.window, "set_composited"), \ - "compositing not supported or widget not realized." - self.disconnect(self.__realizer) - del self.__realizer - - self.parent.set_app_paintable(True) - - # the parent is composited, so we can access gtk's rendered buffer - # and overlay over. If we don't composite, we won't be able to read - # pixels and background will be black. - self.window.set_composited(True) - self.__render_handle = self.parent.connect_after("expose-event", \ - self.__expose_overlay) - - def __expose_overlay(self, widget, event): - """expose event handler to draw the thing.""" - #get our child (in this case, the event box) - child = widget.get_child() - - #create a cairo context to draw to the window - ctx = widget.window.cairo_create() - - #the source data is the (composited) event box - ctx.set_source_pixmap(child.window, - child.allocation.x, - child.allocation.y) - - #draw no more than our expose event intersects our child - region = gtk.gdk.region_rectangle(child.allocation) - rect = gtk.gdk.region_rectangle(event.area) - region.intersect(rect) - ctx.region (region) - ctx.clip() - - ctx.set_operator(cairo.OPERATOR_OVER) - # has to be blended and a 1.0 alpha would not make it blend - ctx.paint_with_alpha(0.99) - - #draw overlay - for drawn_child in self.get_children()[1:]: - if hasattr(drawn_child, "draw_with_context"): - drawn_child.draw_with_context(ctx) - - def __size_allocate(self, widget, allocation): - """ - Set size allocation (actual gtk widget size) and propagate it to - overlayed child - """ - self.allocation = allocation - # One may wonder why using size_request instead of size_allocate; - # Since widget is laid out in a Layout box, the Layout will honor the - # requested size. Using size_allocate could make a nasty nested loop in - # some cases. - self._overlayed.set_size_request(allocation.width, allocation.height) - - -class TextBubble(gtk.Widget): - """ - A CanvasDrawableWidget drawing a round textbox and a tail pointing - to a specified widget. - """ - def __init__(self, text, speaker=None, tailpos=[0,0]): - """ - Creates a new cairo rendered text bubble. - - @param text the text to render in the bubble - @param speaker the widget to compute the tail position from - @param tailpos (optional) position relative to the bubble to use as - the tail position, if no speaker - """ - gtk.Widget.__init__(self) - - # FIXME: ensure previous call does not interfere with widget stacking, - # as using a gtk.Layout and stacking widgets may reveal a screwed up - # order with the cairo widget on top. - self.__label = None - - self.label = text - self.speaker = speaker - self.tailpos = tailpos - self.line_width = 5 - self.padding = 20 - - self._no_expose = False - self.__exposer = None - - def draw_with_context(self, context): - """ - Draw using the passed cairo context instead of creating a new cairo - context. This eases blending between multiple cairo-rendered - widgets. - """ - context.translate(self.allocation.x, self.allocation.y) - width = self.allocation.width - height = self.allocation.height - xradius = width/2 - yradius = height/2 - width -= self.line_width - height -= self.line_width - # - # TODO fetch speaker coordinates - - # draw bubble tail if present - if self.tailpos != [0,0]: - context.move_to(xradius-width/4, yradius) - context.line_to(self.tailpos[0], self.tailpos[1]) - context.line_to(xradius+width/4, yradius) - context.set_line_width(self.line_width) - context.set_source_rgb(*xo_line_color) - context.stroke_preserve() - - # bubble border - context.move_to(width-self.padding, 0.0) - context.line_to(self.padding, 0.0) - context.arc_negative(self.padding, self.padding, self.padding, - 3*pi/2, pi) - context.line_to(0.0, height-self.padding) - context.arc_negative(self.padding, height-self.padding, self.padding, - pi, pi/2) - context.line_to(width-self.padding, height) - context.arc_negative(width-self.padding, height-self.padding, - self.padding, pi/2, 0) - context.line_to(width, self.padding) - context.arc_negative(width-self.padding, self.padding, self.padding, - 0.0, -pi/2) - context.set_line_width(self.line_width) - context.set_source_rgb(*xo_line_color) - context.stroke_preserve() - context.set_source_rgb(*xo_fill_color) - context.fill() - - # bubble painting. Redrawing the inside after the tail will combine - if self.tailpos != [0,0]: - context.move_to(xradius-width/4, yradius) - context.line_to(self.tailpos[0], self.tailpos[1]) - context.line_to(xradius+width/4, yradius) - context.set_line_width(self.line_width) - context.set_source_rgb(*xo_fill_color) - context.fill() - - context.set_source_rgb(1.0, 1.0, 1.0) - pangoctx = pangocairo.CairoContext(context) - self._text_layout.set_markup(self.__label) - text_size = self._text_layout.get_pixel_size() - pangoctx.move_to( - int((self.allocation.width-text_size[0])/2), - int((self.allocation.height-text_size[1])/2)) - pangoctx.show_layout(self._text_layout) - - # work done. Be kind to next cairo widgets and reset matrix. - context.identity_matrix() - - def do_realize(self): - """ Setup gdk window creation. """ - self.set_flags(gtk.REALIZED | gtk.NO_WINDOW) - self.window = self.get_parent_window() - if not self._no_expose: - self.__exposer = self.connect_after("expose-event", \ - self.__on_expose) - - def __on_expose(self, widget, event): - """Redraw event callback.""" - ctx = self.window.cairo_create() - - self.draw_with_context(ctx) - - return True - - def _set_label(self, value): - """Sets the label and flags the widget to be redrawn.""" - self.__label = "<b>%s</b>"%value - if not self.parent: - return - ctx = self.parent.window.cairo_create() - pangoctx = pangocairo.CairoContext(ctx) - self._text_layout = pangoctx.create_layout() - self._text_layout.set_markup(value) - del pangoctx, ctx#, surf - - def do_size_request(self, requisition): - """Fill requisition with size occupied by the widget.""" - ctx = self.parent.window.cairo_create() - pangoctx = pangocairo.CairoContext(ctx) - self._text_layout = pangoctx.create_layout() - self._text_layout.set_markup(self.__label) - - width, height = self._text_layout.get_pixel_size() - requisition.width = int(width+2*self.padding) - requisition.height = int(height+2*self.padding) - - def do_size_allocate(self, allocation): - """Save zone allocated to the widget.""" - self.allocation = allocation - - def _get_label(self): - """Getter method for the label property""" - return self.__label[3:-4] - - def _set_no_expose(self, value): - """setter for no_expose property""" - self._no_expose = value - if not (self.flags() and gtk.REALIZED): - return - - if self.__exposer and value: - self.parent.disconnect(self.__exposer) - self.__exposer = None - elif (not self.__exposer) and (not value): - self.__exposer = self.parent.connect_after("expose-event", - self.__on_expose) - - def _get_no_expose(self): - """getter for no_expose property""" - return self._no_expose - - no_expose = property(fset=_set_no_expose, fget=_get_no_expose, - doc="Whether the widget should handle exposition events or not.") - - label = property(fget=_get_label, fset=_set_label, - doc="Text label which is to be painted on the top of the widget") - -gobject.type_register(TextBubble) - -class Rectangle(gtk.Widget): - """ - A CanvasDrawableWidget drawing a rectangle over a specified widget. - """ - def __init__(self, widget, color): - """ - Creates a new Rectangle - - @param widget the widget to cover - @param color the color of the rectangle, as a 4-tuple RGBA - """ - gtk.Widget.__init__(self) - - self.covered = widget - self.color = color - - self.__exposer = self.connect("expose-event", self.__on_expose) - - def draw_with_context(self, context): - """ - Draw using the passed cairo context instead of creating a new cairo - context. This eases blending between multiple cairo-rendered - widgets. - """ - if self.covered is None: - # nothing to hide, no coordinates, no drawing - return - mask_alloc = self.covered.get_allocation() - x, y = self.covered.translate_coordinates(self.parent, 0, 0) - - context.rectangle(x, y, mask_alloc.width, mask_alloc.height) - context.set_source_rgba(*self.color) - context.fill() - - def do_realize(self): - """ Setup gdk window creation. """ - self.set_flags(gtk.REALIZED | gtk.NO_WINDOW) - - self.window = self.get_parent_window() - if not isinstance(self.parent, Overlayer): - assert False, "%s should not realize" % type(self).__name__ - print "Danger, Will Robinson! Rectangle parent is not Overlayer" - - def __on_expose(self, widget, event): - """Redraw event callback.""" - assert False, "%s wasn't meant to be exposed by gtk" % \ - type(self).__name__ - ctx = self.window.cairo_create() - - self.draw_with_context(ctx) - - return True - - def do_size_request(self, requisition): - """Fill requisition with size occupied by the masked widget.""" - # This is a bit pointless, as this will always ignore allocation and - # be rendered directly on overlay, but for sanity, let's put some values - # in there. - if not self.covered: - requisition.width = 0 - requisition.height = 0 - return - - masked_alloc = self.covered.get_allocation() - requisition.width = masked_alloc.width - requisition.height = masked_alloc.height - - def do_size_allocate(self, allocation): - """Save zone allocated to the widget.""" - self.allocation = allocation - - def _set_no_expose(self, value): - """setter for no_expose property""" - if self.__exposer and value: - self.disconnect(self.__exposer) - self.__exposer = None - elif (not self.__exposer) and (not value): - self.__exposer = self.connect("expose-event", self.__on_expose) - - def _get_no_expose(self): - """getter for no_expose property""" - return not self.__exposer - - no_expose = property(fset=_set_no_expose, fget=_get_no_expose, - doc="Whether the widget should handle exposition events or not.") -gobject.type_register(Rectangle) - -class Mask(gtk.EventBox): - """ - A CanvasDrawableWidget drawing a rectangle over a specified widget. - """ - def __init__(self, catch_events=False, pass_thru=()): - """ - Creates a new Rectangle - - @param catch_events whether the Mask should catch events - @param pass_thru the widgets that "punch holes" through this Mask. - Events will pass through to those widgets. - """ - gtk.EventBox.__init__(self) - self.no_expose = True # ignored - self._catch_events = False - self.catch_events = catch_events - self.pass_thru = list(pass_thru) - - def __del__(self): - for widget in self.pass_thru: - widget.drag_unhighlight() - - def mask(self, widget): - """ - Remove the widget from the unmasked list. - @param widget a widget to remask - """ - assert widget in self.pass_thru, \ - "trying to mask already masked widget" - self.pass_thru.remove(widget) - widget.drag_unhighlight() - - def unmask(self, widget): - """ - Add to the unmasked list the widget passed. - A hole will be punched through the mask at that widget's position. - @param widget a widget to unmask - """ - if widget not in self.pass_thru: - widget.drag_highlight() - self.pass_thru.append(widget) - - - def set_catch_events(self, do_catch): - """Sets whether the mask catches events of widgets under it""" - if bool(self._catch_events) ^ bool(do_catch): - if do_catch: - self._catch_events = True - self.grab_add() - else: - self.grab_remove() - self._catch_events = False - - def get_catch_events(self): - """Gets whether the mask catches events of widgets under it""" - return bool(self._catch_handle) - - catch_events = property(fset=set_catch_events, fget=get_catch_events) - - def draw_with_context(self, context): - """ - Draw using the passed cairo context instead of creating a new cairo - context. This eases blending between multiple cairo-rendered - widgets. - """ - # Fill parent container - mask_alloc = self.parent.get_allocation() - oldrule = context.get_fill_rule() - context.set_fill_rule(cairo.FILL_RULE_EVEN_ODD) - x, y = self.translate_coordinates(self.parent, 0, 0) - - context.rectangle(x, y, mask_alloc.width, mask_alloc.height) - for hole in self.pass_thru: - alloc = hole.get_allocation() - x, y = hole.translate_coordinates(self.parent, 0, 0) - context.rectangle(x, y, alloc.width, alloc.height) - context.set_source_rgba(0, 0, 0, 0.7) - context.fill() - context.set_fill_rule(oldrule) - - def do_size_request(self, requisition): - """Fill requisition with size occupied by the masked widget.""" - # This is required for the event box to span across all the parent. - alloc = self.parent.get_allocation() - requisition.width = alloc.width - requisition.height = alloc.height - - def do_size_allocate(self, allocation): - """Save zone allocated to the widget.""" - self.allocation = allocation - -gobject.type_register(Mask) - - -# vim:set ts=4 sts=4 sw=4 et: diff --git a/src/sugar/tutorius/properties.py b/src/sugar/tutorius/properties.py deleted file mode 100644 index 34b508a..0000000 --- a/src/sugar/tutorius/properties.py +++ /dev/null @@ -1,323 +0,0 @@ -# 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 -""" -This module contains properties class that can be included in other types. -TutoriusProperties have the same behaviour as python properties (assuming you -also use the TPropContainer), with the added benefit of having builtin dialog -prompts and constraint validation. -""" - -from sugar.tutorius.constraints import Constraint, \ - UpperLimitConstraint, LowerLimitConstraint, \ - MaxSizeConstraint, MinSizeConstraint, \ - ColorConstraint, FileConstraint, BooleanConstraint, EnumConstraint - -class TPropContainer(object): - """ - A class containing properties. This does the attribute wrapping between - the container instance and the property value. As properties are on the - containing classes, they allow static introspection of those types - at the cost of needing a mapping between container instances, and - property values. This is what TPropContainer does. - """ - def __init__(self): - """ - 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. - """ - # create property value storage - object.__setattr__(self, "_props", {}) - for attr_name in dir(type(self)): - propinstance = object.__getattribute__(self, attr_name) - if isinstance(propinstance, TutoriusProperty): - # only care about TutoriusProperty instances - propinstance.tname = attr_name - self._props[attr_name] = propinstance.validate( - propinstance.default) - - def __getattribute__(self, name): - """ - Process the 'fake' read of properties in the appropriate instance - container. Pass 'real' attributes as usual. - """ - try: - props = object.__getattribute__(self, "_props") - except AttributeError: - # necessary for deepcopy as order of init can't be guaranteed - object.__setattr__(self, "_props", {}) - props = object.__getattribute__(self, "_props") - - try: - # try gettin value from property storage - # if it's not in the map, it's not a property or its default wasn't - # set at initialization. - return props[name] - except KeyError: - return object.__getattribute__(self, name) - - def __setattr__(self, name, value): - """ - Process the 'fake' write of properties in the appropriate instance - container. Pass 'real' attributes as usual. - - @param name the name of the property - @param value the value to assign to name - @return the setted value - """ - props = object.__getattribute__(self, "_props") - try: - # We attempt to get the property object with __getattribute__ - # to work through inheritance and benefit of the MRO. - return props.__setitem__(name, - object.__getattribute__(self, name).validate(value)) - except AttributeError: - return object.__setattr__(self, name, value) - - def get_properties(self): - """ - Return the list of property names. - """ - return object.__getattribute__(self, "_props").keys() - -class TutoriusProperty(object): - """ - The base class for all actions' properties. The interface is the following : - - value : the value of the property - - type : the type of the property - - get_contraints() : the constraints inserted on this property. They define - what is acceptable or not as values. - """ - def __init__(self): - super(TutoriusProperty, self).__init__() - self.type = None - self._constraints = None - self.default = None - - def get_constraints(self): - """ - Returns the list of constraints associated to this property. - """ - if self._constraints is None: - self._constraints = [] - for i in dir(self): - typ = getattr(self, i) - if isinstance(typ, Constraint): - self._constraints.append(i) - return self._constraints - - def validate(self, value): - """ - Validates the value of the property. If the value does not respect - the constraints on the property, this method will raise an exception. - - The exception should be of the type related to the constraint that - failed. E.g. When a int is to be set with a value that - """ - for constraint_name in self.get_constraints(): - constraint = getattr(self, constraint_name) - constraint.validate(value) - return value - -class TAddonListProperty(TutoriusProperty): - """ - Stores an addon component list as a property. - The purpose of this class is to allow correct mapping of properties - through encapsulated hierarchies. - """ - pass - - - def get_constraints(self): - """ - Returns the list of constraints associated to this property. - """ - if self._constraints is None: - self._constraints = [] - for i in dir(self): - typ = getattr(self, i) - if isinstance(typ, Constraint): - self._constraints.append(i) - return self._constraints - -class TIntProperty(TutoriusProperty): - """ - Represents an integer. Can have an upper value limit and/or a lower value - limit. - """ - - def __init__(self, value, lower_limit=None, upper_limit=None): - TutoriusProperty.__init__(self) - self.type = "int" - self.upper_limit = UpperLimitConstraint(upper_limit) - self.lower_limit = LowerLimitConstraint(lower_limit) - - self.default = self.validate(value) - -class TFloatProperty(TutoriusProperty): - """ - Represents a floating point number. Can have an upper value limit and/or - a lower value limit. - """ - def __init__(self, value, lower_limit=None, upper_limit=None): - TutoriusProperty.__init__(self) - self.type = "float" - - self.upper_limit = UpperLimitConstraint(upper_limit) - self.lower_limit = LowerLimitConstraint(lower_limit) - - self.default = self.validate(value) - -class TStringProperty(TutoriusProperty): - """ - Represents a string. Can have a maximum size limit. - """ - def __init__(self, value, size_limit=None): - TutoriusProperty.__init__(self) - self.type = "string" - self.size_limit = MaxSizeConstraint(size_limit) - - self.default = self.validate(value) - -class TArrayProperty(TutoriusProperty): - """ - Represents an array of properties. Can have a maximum number of element - limit, but there are no constraints on the content of the array. - """ - def __init__(self, value, min_size_limit=None, max_size_limit=None): - TutoriusProperty.__init__(self) - self.type = "array" - self.max_size_limit = MaxSizeConstraint(max_size_limit) - self.min_size_limit = MinSizeConstraint(min_size_limit) - self.default = self.validate(value) - -class TColorProperty(TutoriusProperty): - """ - Represents a RGB color with 3 8-bit integer values. - - The value of the property is the array [R, G, B] - """ - def __init__(self, red=None, green=None, blue=None): - TutoriusProperty.__init__(self) - self.type = "color" - - self.color_constraint = ColorConstraint() - - self._red = red or 0 - self._green = green or 0 - self._blue = blue or 0 - - self.default = self.validate([self._red, self._green, self._blue]) - -class TFileProperty(TutoriusProperty): - """ - Represents a path to a file on the disk. - """ - def __init__(self, path): - """ - Defines the path to an existing file on disk file. - - For now, the path may be relative or absolute, as long as it exists on - the local machine. - TODO : Make sure that we have a file scheme that supports distribution - on other computers (LP 355197) - """ - TutoriusProperty.__init__(self) - - self.type = "file" - - self.file_constraint = FileConstraint() - - self.default = self.validate(path) - -class TEnumProperty(TutoriusProperty): - """ - Represents a value in a given enumeration. This means that the value will - always be one in the enumeration and nothing else. - - """ - def __init__(self, value, accepted_values): - """ - Creates the enumeration property. - - @param value The initial value of the enum. Must be part of - accepted_values - @param accepted_values A list of values that the property can take - """ - TutoriusProperty.__init__(self) - - self.type = "enum" - - self.enum_constraint = EnumConstraint(accepted_values) - - self.default = self.validate(value) - -class TBooleanProperty(TutoriusProperty): - """ - Represents a True or False value. - """ - def __init__(self, value=False): - TutoriusProperty.__init__(self) - - self.type = "boolean" - - self.boolean_constraint = BooleanConstraint() - - self.default = self.validate(value) - -class TUAMProperty(TutoriusProperty): - """ - Represents a widget of the interface by storing its UAM. - """ - # TODO : Pending UAM check-in (LP 355199) - pass - -class TAddonProperty(TutoriusProperty): - """ - Reprensents an embedded tutorius Addon Component (action, trigger, etc.) - The purpose of this class is to flag the container for proper - serialization, as the contained object can't be directly dumped to text - for its attributes to be saved. - """ - class NullAction(TPropContainer): - def do(self): pass - def undo(self): pass - - def __init__(self): - super(TAddonProperty, self).__init__() - self.type = "addon" - self.default = self.NullAction() - - def validate(self, value): - if isinstance(value, TPropContainer): - return super(TAddonProperty, self).validate(value) - raise ValueError("Expected TPropContainer instance as TaddonProperty value") - -class TAddonListProperty(TutoriusProperty): - """ - Reprensents an embedded tutorius Addon List Component. - See TAddonProperty - """ - def __init__(self): - super(TAddonProperty, self).__init__() - self.type = "addonlist" - self.default = [] - - diff --git a/src/sugar/tutorius/services.py b/src/sugar/tutorius/services.py deleted file mode 100644 index 9ed2e50..0000000 --- a/src/sugar/tutorius/services.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com> -# -# 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 -""" -Services - -This module supplies services to be used by States, FSMs, Actions and Filters. - -Services provided are: --Access to the running activity --Access to the running tutorial -""" - - -class ObjectStore(object): - #Begin Singleton code - instance=None - def __new__(cls): - if not ObjectStore.instance: - ObjectStore.instance = ObjectStore.__ObjectStore() - - return ObjectStore.instance - - #End Singleton code - class __ObjectStore(object): - """ - The Object Store is a singleton class that allows access to - the current runnign activity and tutorial. - """ - def __init__(self): - self._activity = None - self._tutorial = None - #self._fsm_path = [] - - def set_activity(self, activity): - """Setter for activity""" - self._activity = activity - - def get_activity(self): - """Getter for activity""" - return self._activity - - activity = property(fset=set_activity,fget=get_activity,doc="activity") - - def set_tutorial(self, tutorial): - """Setter for tutorial""" - self._tutorial = tutorial - - def get_tutorial(self): - """Getter for tutorial""" - return self._tutorial - - tutorial = property(fset=set_tutorial,fget=get_tutorial,doc="tutorial") - - __doc__ = __ObjectStore.__doc__ - diff --git a/src/sugar/tutorius/temp.py b/src/sugar/tutorius/temp.py deleted file mode 100644 index 6c7fb85..0000000 --- a/src/sugar/tutorius/temp.py +++ /dev/null @@ -1 +0,0 @@ -Ex GUID : 79521158-22b4-11de-8cfa-000c293a027a
\ No newline at end of file diff --git a/src/sugar/tutorius/tests/actiontests.py b/src/sugar/tutorius/tests/actiontests.py deleted file mode 100644 index 4e126b3..0000000 --- a/src/sugar/tutorius/tests/actiontests.py +++ /dev/null @@ -1,206 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Michael Janelle-Montcalm <michael.jmontcalm@gmail.com> -# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com> -# -# 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 -""" -Action tests - -The behavior of the actions must be tested here. -""" - -import unittest -import gtk - -from sugar.tutorius import addon -from sugar.tutorius.actions import * -from sugar.tutorius.services import ObjectStore - -test_props = {"prop_a":8, "prop_b":3, "prop_c":"Hi"} - -class PropertyAction(Action): - prop_a = TIntProperty(test_props["prop_a"]) - prop_b = TIntProperty(test_props["prop_b"]) - prop_c = TStringProperty(test_props["prop_c"]) - def __init__(self, na): - Action.__init__(self) - -def has_function(obj, function_name): - """ - Checks whether the object has a function by that name. - """ - if hasattr(obj, function_name) and hasattr(obj.__getattribute__(function_name), "__call__"): - return True - return False - -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'" - - for prop_name in act.get_properties(): - 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]) - - def test_properties(self): - assert self.dial.message == "Message text", "Wrong start value for the message" - - assert self.dial.position == [200, 300], "Wrong start value for the position" - -class BubbleMessageTest(unittest.TestCase): - def setUp(self): - self.bubble = addon.create('BubbleMessage', message="Message text", pos=[200, 300], tailpos=[-15, -25]) - - def test_properties(self): - props = self.bubble.get_properties() - - assert "message" in props, 'No message property of BubbleMessage' - - assert "position" in props, 'No position property in BubbleMessage' - - assert "tail_pos" in props, 'No tail position property in BubbleMessage' - - -class CountAction(Action): - """ - This action counts how many times it's do and undo methods get called - """ - def __init__(self): - Action.__init__(self) - self.do_count = 0 - self.undo_count = 0 - - def do(self): - self.do_count += 1 - - def undo(self): - self.undo_count += 1 - - -class BaseActionTests(unittest.TestCase): - def test_do_unimplemented(self): - act = Action() - try: - act.do() - assert False, "do() should trigger a NotImplemented" - except NotImplementedError: - assert True, "do() should trigger a NotImplemented" - - def test_undo(self): - act = Action() - act.undo() - assert True, "undo() should never fail on the base action" - - -class OnceWrapperTests(unittest.TestCase): - def test_onceaction_toggle(self): - """ - Validate that the OnceWrapper wrapper works properly using the - CountAction - """ - act = CountAction() - wrap = OnceWrapper(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__()" - - wrap.undo() - - assert act.undo_count == 0, "undo() should not be called if do() has not been called" - - wrap.do() - assert act.do_count == 1, "do() should have been called once" - - wrap.do() - assert act.do_count == 1, "do() should have been called only once" - - wrap.undo() - assert act.undo_count == 1, "undo() should have been called once" - - wrap.undo() - assert act.undo_count == 1, "undo() should have been called only once" - -class ChainTester(Action): - def __init__(self, witness): - Action.__init__(self) - self._witness = witness - - def do(self, **kwargs): - self._witness.append([self,"do"]) - - def undo(self): - self._witness.append([self,"undo"]) - -class ChainActionTest(unittest.TestCase): - """Tester for ChainAction""" - def test_empty(self): - """If the expected empty behavior (do nothing) changes - and starts throwing exceptions, this will flag it""" - a = ChainAction() - a.do() - a.undo() - - def test_order(self): - witness = [] - first = ChainTester(witness) - second = ChainTester(witness) - - c = ChainAction(first, second) - assert witness == [], "Actions should not be triggered on init""" - c.do() - - assert witness[0][0] is first, "First triggered action must be 'first'" - assert witness[0][1] is "do", "Action do() should be triggered" - - assert witness[1][0] is second, "second triggered action must be 'second'" - assert witness[1][1] is "do", "Action do() should be triggered" - - assert len(witness) is 2, "Two actions should give 2 do's" - - #empty the witness list - while len(witness): - rm = witness.pop() - - c.undo() - assert witness[1][0] is first, "second triggered action must be 'first'" - assert witness[1][1] is "undo", "Action undo() should be triggered" - - assert witness[0][0] is second, "first triggered action must be 'second'" - assert witness[0][1] is "undo", "Action undo() should be triggered" - - assert len(witness) is 2, "Two actions should give 2 undo's" - -class DisableWidgetActionTests(unittest.TestCase): - def test_disable(self): - btn = gtk.Button() - ObjectStore().activity = btn - btn.set_sensitive(True) - - assert btn.props.sensitive is True, "Callback should have been called" - - act = DisableWidgetAction("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" - act.undo() - assert btn.props.sensitive is True, "Callback should have been called again" - -if __name__ == "__main__": - unittest.main() - diff --git a/src/sugar/tutorius/tests/bundlertests.py b/src/sugar/tutorius/tests/bundlertests.py deleted file mode 100644 index 8da2310..0000000 --- a/src/sugar/tutorius/tests/bundlertests.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Charles-Etienne Carriere <iso.swiffer@gmail.com> -# -# 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 -""" -Bundler tests - -This module contains all the tests for the storage mecanisms for tutorials -This mean testing savins and loading tutorial, .ini file management and -adding ressources to tutorial -""" - -import unittest -import os -import uuid - -from sugar.tutorius import bundler - -class TutorialBundlerTests(unittest.TestCase): - - def setUp(self): - - #generate a test GUID - self.test_guid = uuid.uuid1() - self.guid_path = os.path.join(bundler._get_store_root(),str(self.test_guid)) - os.mkdir(self.guid_path) - - self.ini_file = os.path.join(self.guid_path, "meta.ini") - - f = open(self.ini_file,'w') - f.write("[GENERAL_METADATA]") - f.write(os.linesep) - f.write("GUID:") - f.write(str(self.test_guid)) - f.close() - - def tearDown(self): - os.remove(self.ini_file) - os.rmdir(self.guid_path) - - def test_add_ressource(self): - bund = bundler.TutorialBundler(self.test_guid) - - temp_file = open("test.txt",'w') - temp_file.write('test') - temp_file.close() - - bund.add_resource("test.txt") - - assert os.path.exists(os.path.join(self.guid_path,"test.txt")), "add_ressource did not create the file" - -if __name__ == "__main__": - unittest.main()
\ No newline at end of file diff --git a/src/sugar/tutorius/tests/constraintstests.py b/src/sugar/tutorius/tests/constraintstests.py deleted file mode 100644 index b7b0a47..0000000 --- a/src/sugar/tutorius/tests/constraintstests.py +++ /dev/null @@ -1,233 +0,0 @@ -# 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 - -import unittest - -from sugar.tutorius.constraints import * - -class ConstraintTest(unittest.TestCase): - def test_base_class(self): - cons = Constraint() - try: - cons.validate(1) - assert False, "Base class should throw an assertion" - except NotImplementedError: - pass - -class ValueConstraintTest(unittest.TestCase): - def test_limit_set(self): - cons = ValueConstraint(12) - - assert cons.limit == 12 - -class UpperLimitConstraintTest(unittest.TestCase): - def test_empty_constraint(self): - cons = UpperLimitConstraint(None) - try: - cons.validate(20) - except UpperLimitConstraintError: - assert False, "Empty contraint should not raise an exception" - - def test_validate(self): - cons = UpperLimitConstraint(10) - - try: - cons.validate(20) - assert False, "Validation of UpperLimit(10) on 20 should raise an exception" - except UpperLimitConstraintError: - pass - - try: - cons.validate(5) - except UpperLimitConstraintError: - assert True, "Validation of UpperLimit(10) on 5 should not raise an exception" - -class LowerLimitConstraintTest(unittest.TestCase): - def test_empty_constraint(self): - cons = LowerLimitConstraint(None) - try: - cons.validate(20) - except LowerLimitConstraintError: - assert False, "Empty contraint should not raise an exception" - - def test_validate(self): - cons = LowerLimitConstraint(10) - - try: - cons.validate(5) - assert False, "Validation of LowerLimit(10) on 5 should raise an exception" - except LowerLimitConstraintError: - pass - - try: - cons.validate(20) - except LowerLimitConstraintError: - assert True, "Validation of LowerLimit(10) on 20 should not raise an exception" - -class MaxSizeConstraintTest(unittest.TestCase): - def test_empty_constraint(self): - cons = MaxSizeConstraint(None) - try: - cons.validate(20) - except MaxSizeConstraintError: - assert False, "Empty contraint should not raise an exception" - - def test_validate(self): - cons = MaxSizeConstraint(10) - - try: - cons.validate(range(0, 20)) - assert False, "Validation of MaxSizeConstraint(10) on list of length 20 should raise an exception" - except MaxSizeConstraintError: - pass - - try: - cons.validate(range(0,5)) - except MaxSizeConstraintError: - assert True, "Validation of MaxSizeConstraint(10) on list of length 5 should not raise an exception" - -class MinSizeConstraintTest(unittest.TestCase): - def test_empty_constraint(self): - cons = MinSizeConstraint(None) - try: - cons.validate(20) - except MinSizeConstraintError: - assert False, "Empty contraint should not raise an exception" - - def test_validate(self): - cons = MinSizeConstraint(10) - - try: - cons.validate(range(0, 5)) - assert False, "Validation of MinSizeConstraint(10) on list of length 20 should raise an exception" - except MinSizeConstraintError: - pass - - try: - cons.validate(range(0,20)) - except MinSizeConstraintError: - assert True, "Validation of MinSizeConstraint(10) on list of length 5 should not raise an exception" - -class ColorConstraintTest(unittest.TestCase): - def test_validate(self): - cons = ColorConstraint() - - try: - cons.validate([0, 0]) - assert False, "ColorConstraint on list of length 2 should raise an exception" - except ColorArraySizeError: - pass - except ColorConstraintError: - assert False, "ColorConstraint threw the wrong type of error" - - try: - cons.validate([0, 0, "str"]) - assert False, "ColorConstraint on with non-integers values should raise an exception" - except ColorTypeError: - pass - except ColorConstraintError: - assert False, "ColorConstraint threw the wrong type of error" - - try: - cons.validate([0, "str", 0]) - assert False, "ColorConstraint on with non-integers values should raise an exception" - except ColorTypeError: - pass - except ColorConstraintError: - assert False, "ColorConstraint threw the wrong type of error" - - try: - cons.validate(["str", 0, 0]) - assert False, "ColorConstraint on with non-integers values should raise an exception" - except ColorTypeError: - pass - except ColorConstraintError: - assert False, "ColorConstraint threw the wrong type of error" - - try: - cons.validate([1, 2, 300]) - assert False, "ColorConstraint on with non-integers values should raise an exception" - except ColorValueError: - pass - except ColorConstraintError: - assert False, "ColorConstraint threw the wrong type of error" - - try: - cons.validate([1, -100, 30]) - assert False, "ColorConstraint on with non-integers values should raise an exception" - except ColorValueError: - pass - except ColorConstraintError: - assert False, "ColorConstraint threw the wrong type of error" - - try: - cons.validate([999999, 2, 300]) - assert False, "ColorConstraint on with non-integers values should raise an exception" - except ColorValueError: - pass - except ColorConstraintError: - assert False, "ColorConstraint threw the wrong type of error" - - try: - cons.validate([12, 23, 34]) - except LowerLimitConstraintError: - assert True, "ColorConstraint on expected input should not raise an exception" - -class BooleanConstraintTest(unittest.TestCase): - def test_validate(self): - cons = BooleanConstraint() - - cons.validate(True) - cons.validate(False) - - try: - cons.validate(18) - assert False, "Setting integer on constraint should raise an error" - except BooleanConstraintError: - pass - except: - assert False, "Wrong exception type raised when setting wrong type" - -class EnumConstraintTest(unittest.TestCase): - def test_validate(self): - cons = EnumConstraint([1,2,3,7,8,9, "ex"]) - - cons.validate(8) - - cons.validate("ex") - - try: - cons.validate(4) - assert False, "There should be an exception on setting a value out of the enum" - except EnumConstraintError: - pass - except: - assert False, "Wrong exception type thrown" - -class FileConstraintTest(unittest.TestCase): - def test_validate(self): - cons = FileConstraint() - - cons.validate("run-tests.py") - - try: - cons.validate("unknown/file.py") - assert False, "Non-existing file check should throw an exception" - except FileConstraintError: - pass - -if __name__ == "__main__": - unittest.main()
\ No newline at end of file diff --git a/src/sugar/tutorius/tests/coretests.py b/src/sugar/tutorius/tests/coretests.py deleted file mode 100644 index eadea01..0000000 --- a/src/sugar/tutorius/tests/coretests.py +++ /dev/null @@ -1,597 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Michael Janelle-Montcalm <michael.jmontcalm@gmail.com> -# -# 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 -""" -Core Tests - -This module contains all the tests that pertain to the usage of the Tutorius -Core. This means that the Event Filters, the Finite State Machine and all the -related elements and interfaces are tested here. - -Usage of actions and event filters is tested, but not the concrete actions -and event filters. Those are in their separate test module - -""" - -import unittest - -import logging -from sugar.tutorius.actions import Action, OnceWrapper, ClickAction, TypeTextAction -from sugar.tutorius.core import * -from sugar.tutorius.filters import * - - -from actiontests import CountAction - -# Helper classes to help testing -class SimpleTutorial(Tutorial): - """ - Fake tutorial - """ - def __init__(self, start_name="INIT"): - #Tutorial.__init__(self, "Simple Tutorial", None) - self.current_state_name = start_name - self.activity = "TODO : This should be an activity" - - def set_state(self, name): - self.current_state_name = name - -class TutorialWithFSM(Tutorial): - """ - Fake tutorial, but associated with a FSM. - """ - def __init__(self, start_name="INIT", fsm=None): - Tutorial.__init__(self, start_name, fsm) - self.activity = activity.Activity() - -class TrueWhileActiveAction(Action): - """ - This action's active member is set to True after a do and to False after - an undo. - - Used to verify that a State correctly triggers the do and undo actions. - """ - def __init__(self): - Action.__init__(self) - self.active = False - - def do(self): - self.active = True - - def undo(self): - self.active = False - -class ClickableWidget(): - """ - This class fakes a widget with a clicked() method - """ - def __init__(self): - self.click_count = 0 - - def clicked(self): - self.click_count += 1 - -class FakeTextEntry(): - """ - This class fakes a widget with an insert_text() method - """ - def __init__(self): - self.text_lines = [] - self.last_entered_line = "" - self.displayed_text = "" - - def insert_text(self, text, index): - self.last_entered_line = text - self.text_lines.append(text) - self.displayed_text = self.displayed_text[0:index] + text + self.displayed_text[index+1:] - -class FakeParentWidget(): - """ - This class fakes a widet container, it implements the get_children() method - """ - def __init__(self): - self._children = [] - - def add_child(self, child): - self._children.append(child) - - def get_children(self): - return self._children - - - - -class TriggerEventFilter(EventFilter): - """ - This event filter can be triggered by simply calling its do_callback function. - - Used to fake events and see the effect on the FSM. - """ - def __init__(self, next_state): - EventFilter.__init__(self, next_state) - self.toggle_on_callback = False - - def install_handlers(self, callback, **kwargs): - """ - Forsakes the incoming callback function and just set the inner one. - """ - self._callback = self._inner_cb - - def _inner_cb(self, event_filter): - self.toggle_on_callback = not self.toggle_on_callback - -class FakeEventFilter(TriggerEventFilter): - """ - This is a fake event that is connected to the tutorial. - - The difference between this one and the TriggerEventFilter is that the - tutorial's set_state will be called on the callback. - - Do not forget to add the do_callback() after creating the object. - """ - def set_tutorial(self, tutorial): - self.tutorial = tutorial - - def _inner_cb(self, event_filter): - self.toggle_on_callback = not self.toggle_on_callback - self.tutorial.set_state(event_filter.get_next_state()) - - -class ClickActionTests(unittest.TestCase): - """ - Test class for click action - """ - def test_do_action(self): - activity = FakeParentWidget() - widget = ClickableWidget() - activity.add_child(widget) - ObjectStore().activity = activity - - action = ClickAction("0.0") - - assert widget == ObjectStore().activity.get_children()[0],\ - "The clickable widget isn't reachable from the object store \ - the test cannot pass" - - action.do() - - assert widget.click_count == 1, "clicked() should have been called by do()" - - action.do() - - assert widget.click_count == 2, "clicked() should have been called by do()" - - def test_undo(self): - activity = FakeParentWidget() - widget = ClickableWidget() - activity.add_child(widget) - ObjectStore().activity = activity - - action = ClickAction("0.0") - - assert widget == ObjectStore().activity.get_children()[0],\ - "The clickable widget isn't reachable from the object store \ - the test cannot pass" - - action.undo() - - #There is no undo for this action so the test should not fail - assert True - - - -class TypeTextActionTests(unittest.TestCase): - """ - Test class for type text action - """ - def test_do_action(self): - activity = FakeParentWidget() - widget = FakeTextEntry() - activity.add_child(widget) - ObjectStore().activity = activity - - test_text = "This is text" - - - action = TypeTextAction("0.0", test_text) - - assert widget == ObjectStore().activity.get_children()[0],\ - "The clickable widget isn't reachable from the object store \ - the test cannot pass" - - action.do() - - assert widget.last_entered_line == test_text, "insert_text() should have been called by do()" - - action.do() - - assert widget.last_entered_line == test_text, "insert_text() should have been called by do()" - assert len(widget.text_lines) == 2, "insert_text() should have been called twice" - - def test_undo(self): - activity = FakeParentWidget() - widget = FakeTextEntry() - activity.add_child(widget) - ObjectStore().activity = activity - - test_text = "This is text" - - - action = TypeTextAction("0.0", test_text) - - assert widget == ObjectStore().activity.get_children()[0],\ - "The clickable widget isn't reachable from the object store \ - the test cannot pass" - - action.undo() - - #There is no undo for this action so the test should not fail - assert True - -# State testing class -class StateTest(unittest.TestCase): - """ - This class has to test the State interface as well as the expected - functionality. - """ - - def test_action_toggle(self): - """ - Validate that the actions are properly done on setup and undone on - teardown. - - Pretty awesome. - """ - act = TrueWhileActiveAction() - - state = State("action_test", action_list=[act]) - - assert act.active == False, "Action is not initialized properly" - - state.setup() - - assert act.active == True, "Action was not triggered properly" - - state.teardown() - - assert act.active == False, "Action was not undone properly" - - def test_event_filter(self): - """ - Tests the fact that the event filters are correctly installed on setup - and uninstalled on teardown. - """ - event_filter = TriggerEventFilter("second_state") - - state = State("event_test", event_filter_list=[event_filter]) - state.set_tutorial(SimpleTutorial()) - - assert event_filter.toggle_on_callback == False, "Wrong init of event_filter" - assert event_filter._callback == None, "Event filter has a registered callback before installing handlers" - - state.setup() - - assert event_filter._callback != None, "Event filter did not register callback!" - - # 'Trigger' the event - This is more like a EventFilter test. - event_filter.do_callback() - - assert event_filter.toggle_on_callback == True, "Event filter did not execute callback" - - state.teardown() - - assert event_filter._callback == None, "Event filter did not remove callback properly" - - def test_warning_set_tutorial_twice(self): - """ - Calls set_tutorial twice and expects a warning on the second. - """ - state = State("start_state") - tut = SimpleTutorial("First") - tut2 = SimpleTutorial("Second") - - state.set_tutorial(tut) - - try: - state.set_tutorial(tut2) - assert False, "No RuntimeWarning was raised on second set_tutorial" - except : - pass - - def test_add_action(self): - """ - Tests on manipulating the actions inside a state. - """ - state = State("INIT") - - act1 = CountAction() - act2 = CountAction() - act3 = CountAction() - - # Try to add the actions - assert state.add_action(act1), "Could not add the first action" - assert state.add_action(act2), "Could not add the second action" - assert state.add_action(act3), "Could not add the third action" - - # Try to add a second time an action that was already inserted - assert state.add_action(act1) == False, "Not supposed to insert an action twice" - - # Fetch the associated actions - actions = state.get_action_list() - - # Make sure all the actions are present in the state - assert act1 in actions and act2 in actions and act3 in actions,\ - "The actions were not properly inserted in the state" - - # Clear the list - state.clear_actions() - - # Make sure the list of actions is empty now - assert len(state.get_action_list()) == 0, "Clearing of actions failed" - - def test_add_event_filter(self): - state = State("INIT") - - event1 = TriggerEventFilter("s") - event2 = TriggerEventFilter("t") - event3 = TriggerEventFilter("r") - - # Insert the event filters - assert state.add_event_filter(event1), "Could not add event filter 1" - assert state.add_event_filter(event2), "Could not add event filter 2" - assert state.add_event_filter(event3), "Could not add event filter 3" - - # Make sure we cannot insert an event twice - assert state.add_event_filter(event1) == False, "Could add twice the event filter" - - # Get the list of event filters - event_filters = state.get_event_filter_list() - - assert event1 in event_filters and event2 in event_filters and event3 in event_filters, \ - "The event filters were not all added inside the state" - - # Clear the list - state.clear_event_filters() - - assert len(state.get_event_filter_list()) == 0, \ - "Could not clear the event filter list properly" - -class FSMTest(unittest.TestCase): - """ - This class needs to text the interface and functionality of the Finite - State Machine. - """ - - def test_sample_usage(self): - act_init = TrueWhileActiveAction() - act_second = TrueWhileActiveAction() - - event_init = FakeEventFilter("SECOND") - - content = { - "INIT": State("INIT", action_list=[act_init],event_filter_list=[event_init]), - "SECOND": State("SECOND", action_list=[act_second]) - } - - fsm = FiniteStateMachine("SampleUsage", state_dict=content) - - assert fsm is not None, "Unable to create FSM" - - tut = Tutorial("SampleUsageTutorial", fsm) - - tut.attach(None) - event_init.set_tutorial(tut) - - assert fsm.current_state.name == "INIT", "Unable to set state to initial state" - - assert act_init.active, "FSM did not call the state's action DO properly" - - # Trigger the event of the INIT state - event_init.do_callback() - - assert act_init.active == False, "FSM did not teardown INIT properly" - - assert fsm.current_state.name == "SECOND", "FSM did not switch to SECOND state" - - assert act_second.active == True, "FSM did not setup SECOND properly" - - tut.detach() - - assert act_second.active == False, "FSM did not teardown SECOND properly" - - - def test_state_insert(self): - """ - This is a simple test to insert, then find a state. - """ - st1 = State("FakeState") - - fsm = FiniteStateMachine("StateInsertTest") - - fsm.add_state(st1) - - inserted_state = fsm.get_state_by_name(st1.name) - - assert inserted_state is st1, "Inserting, then fetching a state did not work" - - # Make sure we cannot insert it twice - try : - fsm.add_state(st1) - assert False, "No error raised on addition of an already present state" - except KeyError: - pass - - def test_state_find_by_name(self): - """ - Tests the interface for fetching a state by name. - - Basic functionnality - - Non-existent state - """ - - st1 = State("INIT") - - st2 = State("second") - - fsm = FiniteStateMachine("StateFindTest") - - fsm.add_state(st1) - fsm.add_state(st2) - - # Test the fetch by name - fetched_st1 = fsm.get_state_by_name(st1.name) - - assert fetched_st1 is st1, "Fetched state is not the same as the inserted one" - - fetched_st2 = fsm.get_state_by_name(st2.name) - - assert fetched_st2 is st2, "Fetched state is not the same as the inserted one" - - try: - fsm.get_state_by_name("no such state") - assert False, "Did not get a KeyError on non-existing key search" - except KeyError: - pass - except Exception: - assert False, "Did not get the right error on non-existing key search" - - def test_state_removal(self): - """ - This test removes a state from the FSM. It also verifies that the links - from other states going into the removed state are gone. - """ - st1 = State("INIT", event_filter_list=[TriggerEventFilter("second")]) - st2 = State("second", event_filter_list=[TriggerEventFilter("third")]) - st3 = State("third", event_filter_list=[TriggerEventFilter("second")]) - - fsm = FiniteStateMachine("StateRemovalTest") - - fsm.add_state(st1) - fsm.add_state(st2) - fsm.add_state(st3) - - # First tests - Removing a non-existing state and make sure we get a - # KeyError - try: - fsm.remove_state("Non-existing") - assert False, "Removing a non-existing state did not throw a KeyError" - except KeyError: - pass - except Exception: - assert False, "Removing a non-existing state dit not throw the right kind of exception" - - # Now try removing the second state - fsm.remove_state("second") - - # Make sure it cannot be fetched - try : - fetched_state = fsm.get_state_by_name("second") - assert False, "The supposedly removed state is still present in the FSM" - except KeyError: - pass - - # Make sure that there is no link to the removed state in the rest - # of the FSM - assert "second" not in fsm.get_following_states("INIT"),\ - "The link to second from INIT still exists after removal" - - assert "second" not in fsm.get_following_states("third"),\ - "The link to second from third still exists after removal" - - def test_set_same_state(self): - fsm = FiniteStateMachine("Set same state") - - st1 = State("INIT") - st1.add_action(CountAction()) - - fsm.add_state(st1) - - tut = SimpleTutorial() - - fsm.set_tutorial(tut) - - fsm.set_state("INIT") - - assert fsm.get_state_by_name("INIT").get_action_list()[0].do_count == 1, \ - "The action was not triggered on 'INIT'" - - fsm.set_state("INIT") - - do_count = fsm.get_state_by_name("INIT").get_action_list()[0].do_count - assert fsm.get_state_by_name("INIT").get_action_list()[0].do_count == 1, \ - "The action was triggered a second time, do_count = %d"%do_count - - undo_count = fsm.get_state_by_name("INIT").get_action_list()[0].undo_count - assert fsm.get_state_by_name("INIT").get_action_list()[0].undo_count == 0,\ - "The action has been undone unappropriately, undo_count = %d"%undo_count - -class FSMExplorationTests(unittest.TestCase): - def setUp(self): - self.buildFSM() - - def buildFSM(self): - """ - Create a sample FSM to play with in the rest of the tests. - """ - st1 = State("INIT") - st1.add_action(CountAction()) - st1.add_event_filter(TriggerEventFilter("Second")) - st1.add_event_filter(TriggerEventFilter("Third")) - - st2 = State("Second") - st2.add_action(TrueWhileActiveAction()) - st2.add_event_filter(TriggerEventFilter("Third")) - st2.add_event_filter(TriggerEventFilter("Fourth")) - - st3 = State("Third") - st3.add_action(CountAction()) - st3.add_action(TrueWhileActiveAction()) - - self.fsm = FiniteStateMachine("ExplorationTestingMachine") - self.fsm.add_state(st1) - self.fsm.add_state(st2) - self.fsm.add_state(st3) - - def validate_following_states(self, in_name, out_name_list): - nextStates = self.fsm.get_following_states(in_name) - assert list(nextStates).sort() == list(out_name_list).sort(), \ - "The following states for %s are wrong : got %s"%\ - (in_name, str(nextStates)) - - def validate_previous_states(self, in_name, out_name_list): - prevStates = self.fsm.get_previous_states(in_name) - assert list(prevStates).sort() == list(out_name_list).sort(), \ - "The following states for %s are wrong : got %s"%\ - (in_name, str(prevStates)) - - def test_get_following_states(self): - self.validate_following_states("INIT", ('Second', 'Third')) - - self.validate_following_states("Second", ("Third", "Fourth")) - - self.validate_following_states("Third", ()) - - def test_get_previous_states(self): - self.validate_previous_states("INIT", ()) - - self.validate_previous_states("Second", ("INIT")) - - self.validate_previous_states("Third", ("INIT", "Second")) - - self.validate_previous_states("Fourth", ("Second")) - - -if __name__ == "__main__": - unittest.main() diff --git a/src/sugar/tutorius/tests/filterstests.py b/src/sugar/tutorius/tests/filterstests.py deleted file mode 100644 index 3e79bcc..0000000 --- a/src/sugar/tutorius/tests/filterstests.py +++ /dev/null @@ -1,201 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com> -# -# 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 -""" -Filters Tests - -This module contains all the tests that pertain to the usage of the Tutorius -Event Filters -""" - -import unittest -import time -import gobject -import gtk - -from sugar.tutorius.filters import EventFilter, TimerEvent, GtkWidgetTypeFilter -from sugar.tutorius import addon -from gtkutilstests import SignalCatcher - -class BaseEventFilterTests(unittest.TestCase): - """Test the behavior of the Base EventFilter class""" - def test_properties(self): - """Test EventFilter properties""" - e = EventFilter("NEXTSTATE") - - assert e.next_state == "NEXTSTATE", "next_state should have value used in constructor" - - e.next_state = "NEWSTATE" - - assert e.next_state == "NEWSTATE", "next_state should have been changed by setter" - - - def test_callback(self): - """Test the callback mechanism""" - e = EventFilter("Next") - s = SignalCatcher() - - #Trigger the do_callback, shouldn't do anything - e.do_callback() - - #Install the handler - e.install_handlers(s.callback) - - #Trigger the do_callback, s should receive e - e.do_callback() - assert s.data[0] is e - - s.data = None - - e.remove_handlers() - - #Trigger callback, nothing should happen again - e.do_callback() - - assert s.data is None - - - - - -class TestTimerEvent(unittest.TestCase): - """Tests for timer""" - def test_timer(self): - """Make sure timer gets called once, and only once""" - gobject.threads_init() - ctx = gobject.MainContext() - main = gobject.MainLoop(ctx) - - e = TimerEvent("Next",1) #1 second should be enough :s - s = SignalCatcher() - - e.install_handlers(s.callback) - - assert s.data is None, "Callback should not have been called yet" - - #process events - while gtk.events_pending(): - gtk.main_iteration(block=False) - while ctx.pending(): - ctx.iteration(may_block=False) - - #Wait 1.4 sec - time.sleep(1.4) - - #process events - while gtk.events_pending(): - gtk.main_iteration(block=False) - while ctx.pending(): - ctx.iteration(may_block=False) - - assert not s.data is None, "Callback should have been called" - - s.data = None - - #Wait 1.4 sec - time.sleep(1.4) - - #process events - while gtk.events_pending(): - gtk.main_iteration(block=False) - while ctx.pending(): - ctx.iteration(may_block=False) - - assert s.data is None, "Callback should not have been called again" - - def test_timer_stop(self): - """Make sure timer can be stopped""" - gobject.threads_init() - ctx = gobject.MainContext() - main = gobject.MainLoop(ctx) - - e = TimerEvent("Next",1) #1 second should be enough :s - s = SignalCatcher() - - e.install_handlers(s.callback) - - assert s.data is None, "Callback should not have been called yet" - - #process events - while gtk.events_pending(): - gtk.main_iteration(block=False) - while ctx.pending(): - ctx.iteration(may_block=False) - - assert s.data is None, "Callback should not have been called yet" - - #Wait 0.5 sec - time.sleep(0.5) - - e.remove_handlers() - - #Wait 0.5 sec - time.sleep(0.7) - - #process events - while gtk.events_pending(): - gtk.main_iteration(block=False) - while ctx.pending(): - ctx.iteration(may_block=False) - - assert s.data is None, "Callback should not have been called" - - s.data = None - - -class TestGtkWidgetEventFilter(unittest.TestCase): - """Tests for GtkWidgetEventFilter""" - def __init__(self,*args): - unittest.TestCase.__init__(self,*args) - self.top=None - self.btn1=None - - def setUp(self): - self.top = gtk.Window() - self.btn1 = gtk.Button() - self.top.add(self.btn1) - - def test_install(self): - h = addon.create('GtkWidgetEventFilter', "Next","0","whatever") - try: - h.install_handlers(None) - - assert False, "Install handlers should have failed" - except TypeError: - assert True, "Install should have failed" - - def test_button_clicks(self): - h = addon.create('GtkWidgetEventFilter', "Next","0.0","clicked") - s = SignalCatcher() - - h.install_handlers(s.callback, activity=self.top) - - assert s.data is None, "no callback to call yet" - - self.btn1.clicked() - assert not s.data is None, "callback should have been called" - s.data = None - - h.remove_handlers() - - assert s.data is None, "callback must not be called again" - - self.btn1.clicked() - - assert s.data is None, "callback must not be called again" - - - diff --git a/src/sugar/tutorius/tests/gtkutilstests.py b/src/sugar/tutorius/tests/gtkutilstests.py deleted file mode 100644 index 41634ae..0000000 --- a/src/sugar/tutorius/tests/gtkutilstests.py +++ /dev/null @@ -1,211 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Simon Poirier <simpoir@gmail.com> -# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com> -# -# 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 -""" -Gtk Utils Tests - -This module contains all the tests that pertain to the usage of the Tutorius -gtkutils -""" - -import unittest - -import logging -import gtk, gobject -from sugar.tutorius.gtkutils import find_widget, register_signals_numbered, register_signals, get_children - -class SignalCatcher(object): - """Test class that store arguments received on it's callback method. - Useful for testing callbacks""" - def __init__(self): - """Constructor""" - self.data = None - - def callback(self, *args): - """Callback function, stores argument list in self.data""" - self.data = args - -def disconnect_handlers(hlist): - """Disconnect handles in handler list. hlist must be a list of - two-tuples (widget, handler_id)""" - for widget, handler in hlist: - try: - widget.handler_disconnect(handler) - except: - pass - -class GtkUtilsTests(unittest.TestCase): - def __init__(self,*args): - unittest.TestCase.__init__(self,*args) - self.widgets = {} - self.top = None - - def setUp(self): - #create hierarchy - self.top = gtk.Window(type=gtk.WINDOW_TOPLEVEL) - self.top.set_name("Top") - self.widgets["top"] = {"named":"Top","numbered":"0","widget":self.top} - - hbox = gtk.HBox() - self.top.add(hbox) - hbox.show() - self.widgets["hbox0"] = {"name":"Top.GtkHBox","numbered":"0.0","widget":hbox} - - btn1 = gtk.Button() - btn1.set_name("Button1") - hbox.pack_start(btn1) - btn1.show() - self.widgets["btn1"] = {"name":"Top.GtkHBox.Button1","numbered":"0.0.0","widget":btn1} - - btn2 = gtk.Button() - btn2.set_name("Button2") - hbox.pack_start(btn2) - btn2.show() - self.widgets["btn2"] = {"name":"Top.GtkHBox.Button2","numbered":"0.0.1","widget":btn2} - - vbox = gtk.VBox() - vbox.set_name("VBox1") - hbox.pack_start(vbox) - hbox.show() - self.widgets["vbox0"] = {"name":"Top.GtkHBox.VBox1","numbered":"0.0.2","widget":vbox} - - btn3 = gtk.Button() - btn3.set_name("Button3") - vbox.pack_start(btn3) - btn3.show() - self.widgets["btn3"] = {"name":"Top.GtkHBox.VBox1.Button3","numbered":"0.0.2.0","widget":btn3} - - btn4 = gtk.Button() - vbox.pack_start(btn4) - btn4.show() - self.widgets["btn4"] = {"name":"Top.GtkHBox.VBox1.GtkButton","numbered":"0.0.2.1","widget":btn4} - - def tearDown(self): - #destroy hierarchy - self.top.destroy() - self.top = None - self.widgets = {} - - def test_named(self): - #def register_signals(target, handler, prefix=None, max_depth=None): - s=SignalCatcher() - - #Test 0 depth - handler_list = register_signals(self.top, s.callback, max_depth=0) - - #remove duplicates in widget list - widget_list = dict.fromkeys([w for w, h in handler_list]).keys() - - assert len(widget_list) == 1, "register_signals should not have recursed (%d objects registered)" % len(widget_list) - - assert widget_list[0] == self.top, "register_signals should have gotten only the top" - - disconnect_handlers(handler_list) - - #Test 2 depth - handler_list = register_signals(self.top, s.callback, max_depth=2) - - #remove duplicates in widget list - widget_list = dict.fromkeys([w for w, h in handler_list]).keys() - - assert len(widget_list) == 5, "expected %d objects (got %d)" % (len(widget_list), 5) - - disconnect_handlers(handler_list) - - #Test Infinite depth - handler_list = register_signals(self.top, s.callback, max_depth=None) - - #remove duplicates in widget list - widget_list = dict.fromkeys([w for w, h in handler_list]).keys() - - assert len(widget_list) == 7, "expected %d objects (got %d)" % (len(widget_list), 7) - - disconnect_handlers(handler_list) - - - def test_numbered(self): - s=SignalCatcher() - #def register_signals_numbered(target, handler, prefix="0", max_depth=None): - - #Test 0 depth - handler_list = register_signals_numbered(self.top, s.callback, max_depth=0) - - #remove duplicates in widget list - widget_list = dict.fromkeys([w for w, h in handler_list]).keys() - - assert len(widget_list) == 1, "register_signals should not have recursed (%d objects registered)" % len(widget_list) - - assert widget_list[0] == self.top, "register_signals should have gotten only the top" - - disconnect_handlers(handler_list) - - #Test 1 depth - handler_list = register_signals_numbered(self.top, s.callback, max_depth=1) - - #remove duplicates in widget list - widget_list = dict.fromkeys([w for w, h in handler_list]).keys() - - assert len(widget_list) == 2, "expected %d objects (got %d)" % (len(widget_list), 2) - - disconnect_handlers(handler_list) - - #Test Infinite depth - handler_list = register_signals_numbered(self.top, s.callback, max_depth=None) - - #remove duplicates in widget list - widget_list = dict.fromkeys([w for w, h in handler_list]).keys() - - assert len(widget_list) == 7, "expected %d objects (got %d)" % (len(widget_list), 7) - - disconnect_handlers(handler_list) - - - def test_find_widget(self): - #Test individual values in the defined widgets - for widget in self.widgets.values(): - f = find_widget(self.top, widget["numbered"]) - assert f is widget["widget"], "Widget %s found with path %s, expected %s" % (f, widget["numbered"], widget["widget"]) - - #Test out of index - f = find_widget(self.top, "0.99.1.2") - assert f is self.top, "Should have returned top widget" - - def test_register_args_numbered(self): - #Need to check the signal catcher and stuff... grreat - while gtk.events_pending(): - gtk.main_iteration(block=False) - - - def test_register_args_normal(self): - #Need to check the signal catcher and stuff... grreat - while gtk.events_pending(): - gtk.main_iteration(block=False) - - def test_notwidget(self): - """Test the get_children function""" - o = object() - res = get_children(o) - - assert len(res) == 0, "object has no children" - - top_children = get_children(self.top) - expected = [self.widgets["hbox0"]["widget"],] - assert top_children == expected, "expected %s for top's children, got %s" % (str(expected),str(top_children)) - -if __name__ == "__main__": - unittest.main() - diff --git a/src/sugar/tutorius/tests/linear_creatortests.py b/src/sugar/tutorius/tests/linear_creatortests.py deleted file mode 100644 index dcded57..0000000 --- a/src/sugar/tutorius/tests/linear_creatortests.py +++ /dev/null @@ -1,79 +0,0 @@ -# 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 - -from sugar.tutorius.core import * -from sugar.tutorius.actions import * -from sugar.tutorius.filters import * -from sugar.tutorius.linear_creator import * -from coretests import TriggerEventFilter -from actiontests import CountAction -import unittest - -class CreatorTests(unittest.TestCase): - - def test_simple_usage(self): - creator = LinearCreator() - fsm_name = "SimpleUsageTest" - - creator.set_name(fsm_name) - - # Generate an FSM using the steps - creator.action(CountAction()) - creator.action(CountAction()) - - creator.event(TriggerEventFilter("Not important")) - - creator.action(CountAction()) - - creator.event(TriggerEventFilter("Not good either...")) - - fsm = creator.generate_fsm() - - # Make sure everything worked! - assert fsm.name == fsm_name, "Name was not set properly" - - init_state = fsm.get_state_by_name("INIT") - - assert len(init_state.get_action_list()) == 2, "Creator did not insert all the actions" - - assert init_state.get_event_filter_list()[0].get_next_state() == "State 1" , "expected next state to be 'State 1' but got %s" % init_state.get_event_filter_list()[0].get_next_state() - - state1 = fsm.get_state_by_name("State 1") - - assert len(state1.get_action_list()) == 1, "Creator did not insert all the actions" - - assert state1.get_event_filter_list()[0].get_next_state() == "State 2" - - # Make sure we have the final state and that it's empty - state2 = fsm.get_state_by_name("State2") - - assert len(state2.get_action_list()) == 0, "Creator inserted extra actions on wrong state" - - assert len(state2.get_event_filter_list()) == 0, "Creator assigner events to the final state" - - creator.action(CountAction()) - - fsm = creator.generate_fsm() - - state2 = fsm.get_state_by_name("State2") - - assert len(state2.get_action_list()) == 1, "Creator did not add the action" - - assert len(state2.get_event_filter_list()) == 0, "Creator assigner events to the final state" - -if __name__ == '__main__': - unittest.main() diff --git a/src/sugar/tutorius/tests/overlaytests.py b/src/sugar/tutorius/tests/overlaytests.py deleted file mode 100644 index 783377c..0000000 --- a/src/sugar/tutorius/tests/overlaytests.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Simon Poirier <simpoir@gmail.com> -# -# 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 -""" -GUI Tests - -This module contains all the tests that pertain to the usage of the Tutorius -overlay mechanism used to display objects on top of the application. -""" - -import unittest - -import logging -import gtk, gobject -from sugar.tutorius.actions import Action -import sugar.tutorius.overlayer as overlayer - -class CanvasDrawable(object): - def __init__(self): - self._no_expose = False - self.exposition_count = 0 - def _set_no_expose(self, value): - self._no_expose = value - def draw_with_context(self, context): - self.exposition_count += 1 - no_expose = property(fset=_set_no_expose) - - -class OverlayerTest(unittest.TestCase): - def test_cairodrawable_iface(self): - """ - Quickly validates that all our cairo widgets have a minimal interface - implemented. - """ - drawables = [overlayer.TextBubble] - for widget in drawables: - for attr in filter(lambda s:s[0]!='_', dir(CanvasDrawable)): - assert hasattr(widget, attr), \ - "%s not implementing CanvasDrawable iface"%widget.__name__ - - - def test_drawn(self): - """ - Ensures a cairo widget draw method is called at least once in - a real gui app. - """ - win = gtk.Window(type=gtk.WINDOW_TOPLEVEL) - - btn = gtk.Button() - btn.show() - overlay = overlayer.Overlayer(btn) - win.add(overlay) - # let's also try to draw substitute button label - lbl = overlayer.TextBubble("test!") - assert lbl.label == 'test!', \ - "label property mismatch" - btn.show() - lbl.show() - btn.add(lbl) - - lbl.no_expose = True - assert lbl.no_expose, "wrong no_expose evaluation" - lbl.no_expose = False - assert not lbl.no_expose, "wrong no_expose evaluation" - - - widg = gtk.Button('bo') - widg.show() - overlay.put(widg, 50,50) - #widget = overlayer.TextBubble("testing msg!", tailpos=(10,-20)) - #widget.exposition_count = 0 - ## override draw method - #def counter(ctx, self=widget): - # self.exposition_count += 1 - # self.real_exposer(ctx) - #widget.real_exposer = widget.draw_with_context - #widget.draw_with_context = counter - ## centering allows to test the blending with the label - #overlay.put(widget, 50, 50) - #widget.show() - #assert widget.no_expose, \ - # "Overlay should overide exposition handling of widget" - #assert not lbl.no_expose, \ - # "Non-overlayed cairo should expose as usual" - - # force widget realization - # the child is flagged to be redrawn, the overlay should redraw too. - win.set_default_size(100, 100) - win.show() - - while gtk.events_pending(): - gtk.main_iteration(block=False) - # visual validation: there should be 2 visible bubbles, one as label, - # one as overlay - import time - time.sleep(1) - # as x11 events are asynchronous, wait a bit before assuming it went - # wrong. - while gtk.events_pending(): - gtk.main_iteration(block=False) - time.sleep(10) - assert widget.exposition_count>0, "overlay widget should expose" - - -if __name__ == "__main__": - unittest.main() diff --git a/src/sugar/tutorius/tests/propertiestests.py b/src/sugar/tutorius/tests/propertiestests.py deleted file mode 100644 index 46346c4..0000000 --- a/src/sugar/tutorius/tests/propertiestests.py +++ /dev/null @@ -1,402 +0,0 @@ -# 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 - -import unittest - -from sugar.tutorius.constraints import * -from sugar.tutorius.properties import * - -# Helper function to test the wrong types on a property, given its type -def try_wrong_values(obj): - typ = type(obj).prop.type - if typ != "int": - try: - obj.prop = 3 - assert False, "Able to insert int value in property of type %s"%typ - except: - pass - - if typ != "float": - try: - obj.prop = 1.1 - assert False, "Able to insert float value in property of type %s"%typ - except: - pass - - if typ != "string": - try: - obj.prop = "Fake string" - assert False, "Able to insert string value in property of type %s"%typ - except: - pass - - if typ != "array": - try: - obj.prop = [1, 2000, 3, 400] - assert False, "Able to insert array value in property of type %s"%typ - except: - pass - - if typ != "color": - try: - obj.prop = [1,2,3] - if typ != "array": - assert False, "Able to insert color value in property of type %s"%typ - except: - pass - - if typ != "boolean": - try: - obj.prop = True - if typ != "boolean": - assert False, "Able to set boolean value in property of type %s"%typ - except: - pass - -class BasePropertyTest(unittest.TestCase): - def test_base_class(self): - class klass(TPropContainer): - prop = TutoriusProperty() - obj = klass() - - assert klass.prop.default == None, "There should not be an initial value in the base property" - - assert klass.prop.type == None, "There should be no type associated with the base property" - - assert klass.prop.get_constraints() == [], "There should be no constraints on the base property" - - obj.prop = 2 - - assert obj.prop == 2, "Unable to set a value on base class" - -class TIntPropertyTest(unittest.TestCase): - def test_int_property(self): - class klass(TPropContainer): - prop = TIntProperty(22) - obj = klass() - - assert obj.prop == 22, "Could not set value on property via constructor" - - assert klass.prop.type == "int", "Wrong type on int property : %s" % prop.type - cons = klass.prop.get_constraints() - assert len(cons) == 2, "Not enough constraints on the int property" - - obj.prop = 12 - - assert obj.prop == 12, "Could not set value" - - def test_wrong_values(self): - class klass(TPropContainer): - prop = TIntProperty(33) - obj = klass() - - # Try setting values of other types - try_wrong_values(obj) - - def test_limit_constructor(self): - class klass(TPropContainer): - prop = TIntProperty(22, 0, 30) - obj = klass() - - try: - obj.prop = -22 - assert False, "Assigning an out-of-range value should trigger LowerLimitConstraint" - except LowerLimitConstraintError: - pass - except Exception: - assert False, "Wrong exception triggered by assignation" - - try: - obj.prop = 222 - assert False, "Assigning an out-of-range value should trigger UpperLimitConstraint" - except UpperLimitConstraintError: - pass - except Exception: - assert False, "Wrong exception triggered by assignation" - - def test_failing_constructor(self): - try: - prop = TIntProperty(100, 0, 20) - assert False, "Creation of the property should fail." - except UpperLimitConstraintError: - pass - except: - assert False, "Wrong exception type on failed constructor" - - try: - prop = TIntProperty(-100, 0, 20) - assert False, "Creation of the property should fail." - except LowerLimitConstraintError: - pass - except: - assert False, "Wrong exception type on failed constructor" - -class TFloatPropertyTest(unittest.TestCase): - def test_float_property(self): - class klass(TPropContainer): - prop = TFloatProperty(22) - obj = klass() - - assert obj.prop == 22, "Could not set value on property via constructor" - - assert klass.prop.type == "float", "Wrong type on float property : %s" % klass.prop.type - cons = klass.prop.get_constraints() - assert len(cons) == 2, "Not enough constraints on the float property" - - obj.prop = 12 - - assert obj.prop == 12, "Could not set value" - - def test_wrong_values(self): - class klass(TPropContainer): - prop = TFloatProperty(33) - obj = klass() - - # Try setting values of other types - try_wrong_values(obj) - - def test_limit_constructor(self): - class klass(TPropContainer): - prop = TFloatProperty(22.4, 0.1, 30.5223) - obj = klass() - - try: - obj.prop = -22.8 - assert False, "Assigning an out-of-range value should trigger LowerLimitConstraint" - except LowerLimitConstraintError: - pass - except Exception: - assert False, "Wrong exception triggered by assignation" - - try: - obj.prop = 222.2 - assert False, "Assigning an out-of-range value should trigger UpperLimitConstraint" - except UpperLimitConstraintError: - pass - except Exception: - assert False, "Wrong exception triggered by assignation" - - def test_failing_constructor(self): - try: - prop = TFloatProperty(100, 0, 20) - assert False, "Creation of the property should fail." - except UpperLimitConstraintError: - pass - except: - assert False, "Wrong exception type on failed constructor" - - try: - prop = TFloatProperty(-100, 0, 20) - assert False, "Creation of the property should fail." - except LowerLimitConstraintError: - pass - except: - assert False, "Wrong exception type on failed constructor" - -class TStringPropertyTest(unittest.TestCase): - def test_basic_string(self): - class klass(TPropContainer): - prop = TStringProperty("Starter string") - obj = klass() - - assert obj.prop == "Starter string", "Could not set string value via constructor" - - assert klass.prop.type == "string", "Wrong type for string property : %s" % klass.prop.type - - def test_size_limit(self): - class klass(TPropContainer): - prop = TStringProperty("Small", 10) - obj = klass() - - try: - obj.prop = "My string is too big!" - assert False, "String should not set to longer than max size" - except MaxSizeConstraintError: - pass - except: - assert False, "Wrong exception type thrown" - - def test_wrong_values(self): - class klass(TPropContainer): - prop = TStringProperty("Beginning") - obj = klass() - - try_wrong_values(obj) - - def test_failing_constructor(self): - try: - prop = TStringProperty("This is normal", 5) - assert False, "Creation of the property should fail." - except MaxSizeConstraintError: - pass - except: - assert False, "Wrong exception type on failed constructor" - -class TArrayPropertyTest(unittest.TestCase): - def test_basic_array(self): - class klass(TPropContainer): - prop = TArrayProperty([1, 2, 3, 4]) - obj = klass() - - assert obj.prop == [1,2,3,4], "Unable to set initial value via constructor" - - assert klass.prop.type == "array", "Wrong type for array : %s"%klass.prop.type - - def test_wrong_values(self): - class klass(TPropContainer): - prop = TArrayProperty([1,2,3,4,5]) - obj = klass() - - try_wrong_values(obj) - - def test_size_limits(self): - class klass(TPropContainer): - prop = TArrayProperty([1,2], None, 4) - obj = klass() - - try: - obj.prop = [1,2,4,5,6,7] - assert False, "Maximum size limit constraint was not properly applied" - except MaxSizeConstraintError: - pass - - class klass(TPropContainer): - prop = TArrayProperty([1,2,3,4], 2) - obj = klass() - - try: - obj.prop = [1] - assert False, "Minimum size limit constraint was not properly applied" - except MinSizeConstraintError: - pass - - def test_failing_constructor(self): - try: - prop = TArrayProperty([100, 0, 20], None, 2) - assert False, "Creation of the property should fail." - except MaxSizeConstraintError: - pass - try: - prop = TArrayProperty([100, 0, 20], 4, None) - assert False, "Creation of the property should fail." - except MinSizeConstraintError: - pass - -class TColorPropertyTest(unittest.TestCase): - def test_basic_color(self): - class klass(TPropContainer): - prop = TColorProperty(20, 40, 60) - obj = klass() - - assert obj.prop == [20, 40, 60], "Could not set initial value with constructor" - - assert klass.prop.type == "color", "Wrong type on color : %s"%klass.prop.type - - def test_wrong_values(self): - class klass(TPropContainer): - prop = TColorProperty(250, 250, 250) - obj = klass() - - try_wrong_values(obj) - - def test_failing_constructor(self): - try: - prop = TColorProperty(0, "str", 0) - assert False, "Creation of the property should fail." - except ColorTypeError: - pass - except: - assert False, "Wrong exception type on failed constructor" - -class TBooleanPropertyTest(unittest.TestCase): - def setUp(self): - class klass(TPropContainer): - prop = TBooleanProperty(False) - self.obj = klass() - - def test_basic_boolean(self): - assert self.obj.prop == False, "Could not set initial value via constructor" - - assert self.obj.__class__.prop.type == "boolean", "Wrong type for TBooleanProperty : %s"%self.obj.__class__.prop.type - - self.obj.prop = True - - assert self.obj.prop == True, "Could not change the value via set" - - self.obj.prop = False - - assert self.obj.prop == False, "Could not change the value via set" - - def test_wrong_types(self): - try_wrong_values(self.obj) - - def test_failing_constructor(self): - try: - prop = TBooleanProperty(64) - assert False, "Creation of the property should fail with non-boolean value" - except BooleanConstraintError: - pass - except: - assert False, "Wrong exception type on failed constructor" - -class TEnumPropertyTest(unittest.TestCase): - def setUp(self): - class klass(TPropContainer): - prop = TEnumProperty("hello", [1, 2, "hello", "world", True, None]) - self.obj = klass() - - def test_basic_enum(self): - assert self.obj.prop == "hello", "Could not set initial value on property" - - assert type(self.obj).prop.type == "enum", "Wrong type for TEnumProperty : %s"%type(self.obj).prop.type - - self.obj.prop = True - - assert self.obj.prop, "Could not change the value via set" - - try: - self.obj.prop = 4 - assert False, "Switched to a value outside the enum" - except EnumConstraintError: - pass - - def test_wrong_type(self): - try_wrong_values(self.obj) - -class TFilePropertyTest(unittest.TestCase): - def setUp(self): - class klass(TPropContainer): - prop = TFileProperty("propertiestests.py") - self.obj = klass() - - def test_basic_file(self): - assert self.obj.prop == "propertiestests.py", "Could not set initial value" - - assert type(self.obj).prop.type == "file", "Wrong type for TFileProperty : %s"%type(self.obj).prop.type - - self.obj.prop = "run-tests.py" - - assert self.obj.prop == "run-tests.py", "Could not change value" - - try: - self.obj.prop = "unknown/file/on/disk.gif" - assert False, "An exception should be thrown on unknown file" - except FileConstraintError: - pass - -if __name__ == "__main__": - unittest.main() - diff --git a/src/sugar/tutorius/tests/run-tests.py b/src/sugar/tutorius/tests/run-tests.py deleted file mode 100755 index d41aa0a..0000000 --- a/src/sugar/tutorius/tests/run-tests.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/python -# 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 -sys.path.insert(0, - os.path.abspath(INSTALL_PATH) -) - -FULL_PATH = os.path.join(INSTALL_PATH,"sugar/tutorius") -SUBDIRS = ["uam"] -GLOB_PATH = os.path.join(FULL_PATH,"*.py") -import unittest -from glob import glob -def report_files(): - ret = glob(GLOB_PATH) - for dir in SUBDIRS: - ret += glob(os.path.join(FULL_PATH,dir,"*.py")) - return ret - -import sys -if __name__=='__main__': - if "--coverage" in sys.argv: - sys.argv=[arg for arg in sys.argv if arg != "--coverage"] - import coverage - coverage.erase() - #coverage.exclude('raise NotImplementedError') - coverage.start() - - import coretests - import servicestests - import gtkutilstests - #import overlaytests # broken - import linear_creatortests - import actiontests - import uamtests - import filterstests - import constraintstests - import propertiestests - import serializertests - suite = unittest.TestSuite() - suite.addTests(unittest.findTestCases(coretests)) - suite.addTests(unittest.findTestCases(servicestests)) - suite.addTests(unittest.findTestCases(gtkutilstests)) - #suite.addTests(unittest.findTestCases(overlaytests)) # broken - suite.addTests(unittest.findTestCases(linear_creatortests)) - suite.addTests(unittest.findTestCases(actiontests)) - suite.addTests(unittest.findTestCases(uamtests)) - suite.addTests(unittest.findTestCases(filterstests)) - suite.addTests(unittest.findTestCases(constraintstests)) - suite.addTests(unittest.findTestCases(propertiestests)) - suite.addTests(unittest.findTestCases(serializertests)) - runner = unittest.TextTestRunner() - runner.run(suite) - coverage.stop() - coverage.report(report_files()) - coverage.erase() - else: - from coretests import * - from servicestests import * - from gtkutilstests import * - #from overlaytests import * # broken - from actiontests import * - from linear_creatortests import * - from uamtests import * - from filterstests import * - from constraintstests import * - from propertiestests import * - from actiontests import * - from serializertests import * - - unittest.main() diff --git a/src/sugar/tutorius/tests/serializertests.py b/src/sugar/tutorius/tests/serializertests.py deleted file mode 100644 index 6c25bae..0000000 --- a/src/sugar/tutorius/tests/serializertests.py +++ /dev/null @@ -1,197 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Jean-Christophe Savard <savard.jean.christophe@gmail.com> -# -# 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 -""" -Serialization Tests - -This module contains all the tests that pertain to the usage of the Tutorius -Serializer object. This means testing saving a tutorial dictionary to a .tml -file, loading the list of tutorials for this activity and building chosen -tutorial. -""" - -import unittest - -import os -import shutil - -from sugar.tutorius import bundler, addon -from sugar.tutorius.core import State, FiniteStateMachine -from sugar.tutorius.actions import * -from sugar.tutorius.filters import * -from sugar.tutorius.bundler import XMLSerializer, Serializer -import sugar -from uuid import uuid1 - -class SerializerInterfaceTest(unittest.TestCase): - """ - For completeness' sake. - """ - def test_save(self): - ser = Serializer() - - try: - ser.save_fsm(None) - assert False, "save_fsm() should throw an unimplemented error" - except: - pass - - def test_load(self): - ser = Serializer() - - try: - ser.load_fsm(str(uuid.uuid1())) - assert False, "load_fsm() should throw an unimplemented error" - except: - pass - -class XMLSerializerTest(unittest.TestCase): - """ - Tests the transformation of XML to FSM, then back. - """ - def setUp(self): - # Make the serializer believe the test is in a activity path - self.testpath = "/tmp/testdata/" - os.environ["SUGAR_BUNDLE_PATH"] = self.testpath - os.environ["SUGAR_PREFIX"] = self.testpath - os.environ["SUGAR_PROFILE"] = 'test' -## os.mkdir(sugar.tutorius.bundler._get_store_root()) - - # Create the sample FSM - self.fsm = FiniteStateMachine("testingMachine") - - # Add a few states - act1 = addon.create('BubbleMessage', message="Hi", pos=[300, 450]) - ev1 = addon.create('GtkWidgetEventFilter', "0.12.31.2.2", "clicked", "Second") - act2 = addon.create('BubbleMessage', message="Second message", pos=[250, 150], tailpos=[1,2]) - - st1 = State("INIT") - st1.add_action(act1) - st1.add_event_filter(ev1) - - st2 = State("Second") - - st2.add_action(act2) - - self.fsm.add_state(st1) - self.fsm.add_state(st2) - - self.uuid = uuid1() - - # Flag to set to True if the output can be deleted after execution of - # the test - self.remove = True - - def tearDown(self): - """ - Removes the created files, if need be. - """ - if self.remove == True: - shutil.rmtree(os.path.join(os.getenv("HOME"),".sugar",os.getenv("SUGAR_PROFILE"))) - if os.path.isdir(self.testpath): - shutil.rmtree(self.testpath) - - def test_save(self): - """ - Writes an FSM to disk, then compares the file to the expected results. - "Remove" boolean argument specify if the test data must be removed or not - """ - xml_ser = XMLSerializer() - os.makedirs(os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid))) - #rpdb2.start_embedded_debugger('flakyPass') - xml_ser.save_fsm(self.fsm, bundler.TUTORIAL_FILENAME, os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid))) - - def test_save_and_load(self): - """ - Load up the written FSM and compare it with the object representation. - """ - self.test_save() - testpath = "/tmp/testdata/" - #rpdb2.start_embedded_debugger('flakyPass') - xml_ser = XMLSerializer() - - # This interface needs to be redone... It's not clean because there is - # a responsibility mixup between the XML reader and the bundler. - loaded_fsm = xml_ser.load_fsm(str(self.uuid)) - - # Compare the two FSMs - assert loaded_fsm._states.get("INIT").name == self.fsm._states.get("INIT").name, \ - 'FSM underlying dictionary differ from original to pickled/reformed one' - assert loaded_fsm._states.get("Second").name == self.fsm._states.get("Second").name, \ - 'FSM underlying dictionary differ from original to pickled/reformed one' - assert loaded_fsm._states.get("INIT").get_action_list()[0].message == \ - self.fsm._states.get("INIT").get_action_list()[0].message, \ - 'FSM underlying State underlying Action differ from original to reformed one' - assert len(loaded_fsm.get_action_list()) == 0, "FSM should not have any actions on itself" - - def test_all_actions(self): - """ - Inserts all the known action types in a FSM, then attempt to load it. - """ - st = State("INIT") - - act1 = addon.create('BubbleMessage', "Hi!", pos=[10,120], tailpos=[-12,30]) - act2 = addon.create('DialogMessage', "Hello again.", pos=[120,10]) - act3 = WidgetIdentifyAction() - act4 = DisableWidgetAction("0.0.0.1.0.0.0") - act5 = TypeTextAction("0.0.0.1.1.1.0.0", "New text") - act6 = ClickAction("0.0.1.0.1.1") - act7 = OnceWrapper(act1) - act8 = ChainAction([act1, act2, act3, act4]) - actions = [act1, act2, act3, act4, act5, act6, act7, act8] - - for action in actions: - st.add_action(action) - - self.fsm.remove_state("Second") - self.fsm.remove_state("INIT") - self.fsm.add_state(st) - - xml_ser = XMLSerializer() - - self.test_save() - - reloaded_fsm = xml_ser.load_fsm(str(self.uuid)) - assert self.fsm == reloaded_fsm, "Expected equivalence before saving vs after loading." - - def test_all_filters(self): - """ - Inserts all the known action types in a FSM, then attempt to load it. - """ - st = State("INIT") - - ev1 = TimerEvent("Second", 1000) - ev2 = addon.create('GtkWidgetEventFilter', "Second", "0.0.1.1.0.0.1", "clicked") - ev3 = GtkWidgetTypeFilter("Second", "0.0.1.1.1.2.3", text="Typed stuff") - ev4 = GtkWidgetTypeFilter("Second", "0.0.1.1.1.2.3", strokes="acbd") - filters = [ev1, ev2, ev3, ev4] - - for filter in filters: - st.add_event_filter(filter) - - self.fsm.remove_state("INIT") - self.fsm.add_state(st) - - xml_ser = XMLSerializer() - - self.test_save() - - reloaded_fsm = xml_ser.load_fsm(str(self.uuid)) - - assert self.fsm == reloaded_fsm, "Expected equivalence before saving vs after loading." - -if __name__ == "__main__": - unittest.main() diff --git a/src/sugar/tutorius/tests/servicestests.py b/src/sugar/tutorius/tests/servicestests.py deleted file mode 100644 index d669012..0000000 --- a/src/sugar/tutorius/tests/servicestests.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Michael Janelle-Montcalm <michael.jmontcalm@gmail.com> -# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com> -# -# 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 -"""Services tests""" - -import unittest - -from sugar.tutorius.services import * - - -class ObjectStoreTests(unittest.TestCase): - def setUp(self): - self.os1 = ObjectStore() - self.os2 = ObjectStore() - - def tearDown(self): - del self.os1 - del self.os2 - ObjectStore.instance = None - - def test_singleton(self): - """ - Validate that the object store is a singleton - """ - assert self.os1 is self.os2, "Both objectstore objects should be the same" - - def test_activity(self): - """Validate the activity property""" - act = object() - self.os1.activity = act - assert self.os1.activity is self.os2.activity - - def test_tutorial(self): - """Validate the tutorial property""" - tut = object() - self.os1.tutorial = tut - assert self.os1.tutorial is self.os2.tutorial - - diff --git a/src/sugar/tutorius/tests/uamtests.py b/src/sugar/tutorius/tests/uamtests.py deleted file mode 100644 index b2a5901..0000000 --- a/src/sugar/tutorius/tests/uamtests.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Michael Janelle-Montcalm <michael.jmontcalm@gmail.com> -# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com> -# -# 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 - -import unittest - -from sugar.tutorius.uam import parse_uri, SchemeError - -PARSE_SUITE={ -#URI SCHEME HOST PARAMS PATH QUERY FRAGMENT -"tap://act.tut.org/": ["tap", "act.tut.org","", "/", "", ""], -"tap.gtk://a.t.o/0/1": ["tap.gtk","a.t.o","","/0/1","","",""], -"tap.gobject://a.t.o/Timer?timeout=5":["tap.gobject","a.t.o","","/Timer","timeout=5",""], -} - -class ParseUriTests(unittest.TestCase): - """Tests the UAM parsers""" - def test_parse_uri(self): - """Test parsing results""" - for uri, test in PARSE_SUITE.items(): - res = parse_uri(uri) - - assert res.scheme == test[0], "%s : Expected scheme %s, got %s" % (uri, test[0], res.scheme) - assert res.netloc == test[1], "%s : Expected netloc %s, got %s" % (uri, test[1], res.netloc) - assert res.params == test[2], "%s : Expected params %s, got %s" % (uri, test[2], res.params) - assert res.path == test[3], "%s : Expected path %s, got %s" % (uri, test[3], res.path) - assert res.query == test[4], "%s : Expected query %s, got %s" % (uri, test[4], res.query) - assert res.fragment == test[5], "%s : Expected fragment %s, got %s" % (uri, test[5], res.fragment) - - def test_errors(self): - """Test exceptions""" - try: - parse_uri("http://something.org/path") - assert False, "Parsing http should fail" - except SchemeError: - pass - - try: - parse_uri("tap.notarealsubscheme://something.org/path") - assert False, "Invalid Subscheme should fail" - except SchemeError: - pass - - -if __name__ == "__main__": - unittest.main() - diff --git a/src/sugar/tutorius/testwin.py b/src/sugar/tutorius/testwin.py deleted file mode 100644 index ef92b7f..0000000 --- a/src/sugar/tutorius/testwin.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python -# 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 - -import gtk -import dragbox -import textbubble - -box = None - -def _destroy(widget, data=None): - gtk.main_quit() - -def _delete_event(widget, event, data=None): - print "quitting" - return False - -def blublu(widget, data=""): - print data - -def _drag_toggle(widget, data=None): - global box - box.dragMode = not box.dragMode - - -def addBtn(widget, data, bubble=0, btns=[0]): - if bubble == 1: - bt = textbubble.TextBubble("Bubble(%d)"%btns[0]) - else: - bt = gtk.Button("Bubble(%d)"%btns[0]) - ##bt.set_size_request(60,40) - bt.show() - data.attach(bt) - btns[0] += 1 - -def main(): - global box - win = gtk.Window(type=gtk.WINDOW_TOPLEVEL) - win.connect("delete_event", _delete_event) - win.connect("destroy", _destroy) - - win.set_default_size(800,600) - - vbox = gtk.VBox() - vbox.show() - win.add(vbox) - - check = gtk.CheckButton(label="dragMode") - check.connect("toggled", _drag_toggle) - check.show() - vbox.pack_start(check, expand=False) - - btnadd = gtk.Button("Add Bubble") - btnadd.show() - vbox.pack_start(btnadd, expand=False) - btnadd2 = gtk.Button("Add Button") - btnadd2.show() - vbox.pack_start(btnadd2, expand=False) - -## bubble = textbubble.TextBubble("Bubbles!") -## bubble.show() -## bubble.set_size_request(40,40) -## vbox.pack_start(bubble, expand=False) - - box = dragbox.DragBox() - box.set_border_width(10) - box.show() - vbox.pack_start(box, expand=True, fill=True) - - btnadd.connect("clicked", addBtn, box, 1) - btnadd2.connect("clicked", addBtn, box) - - win.show() - gtk.main() - - -if __name__ == "__main__": - main() - diff --git a/src/sugar/tutorius/textbubble.py b/src/sugar/tutorius/textbubble.py deleted file mode 100644 index e09b298..0000000 --- a/src/sugar/tutorius/textbubble.py +++ /dev/null @@ -1,109 +0,0 @@ -# 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 -""" -This module represents TextBubble widget. Also, it aims to be a short example -of drawing with Cairo. -""" - -import gtk -from math import pi as M_PI -import cairo - -# FIXME set as subclass of gtk.Widget, not EventBox -class TextBubble(gtk.EventBox): - def __init__(self, label): - gtk.EventBox.__init__(self) - - ##self.set_app_paintable(True) # else may be blank - # FIXME ensure previous call does not interfere with widget stacking - self.label = label - self.lineWidth = 5 - - self.connect("expose-event", self._on_expose) - - def __draw_with_cairo__(self, context): - """ - - """ - pass - - def _on_expose(self, widget, event): - """Redraw event callback.""" - # TODO - ctx = self.window.cairo_create() - - # set drawing region. Useless since this widget has its own window. - ##region = gtk.gdk.region_rectangle(self.allocation) - ##region.intersect(gtk.gdk.region_rectangle(event.area)) - ##ctx.region(region) - ##ctx.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) - ##ctx.clip() - - ##import pdb; pdb.set_trace() - ##ctx.set_operator(cairo.OPERATOR_CLEAR) - ##ctx.paint() - ##ctx.set_operator(cairo.OPERATOR_OVER) - - width = self.allocation.width - height = self.allocation.height - xradius = width/2 - yradius = height/2 - width -= self.lineWidth - height -= self.lineWidth - ctx.move_to(self.lineWidth, yradius) - ctx.curve_to(self.lineWidth, self.lineWidth, - self.lineWidth, self.lineWidth, xradius, self.lineWidth) - ctx.curve_to(width, self.lineWidth, - width, self.lineWidth, width, yradius) - ctx.curve_to(width, height, width, height, xradius, height) - ctx.curve_to(self.lineWidth, height, - self.lineWidth, height, self.lineWidth, yradius) - ctx.set_source_rgb(1.0, 1.0, 1.0) - ctx.fill_preserve() - ctx.set_line_width(self.lineWidth) - ctx.set_source_rgb(0.0, 0.0, 0.0) - ctx.stroke() - - _, _, textWidth, textHeight, _, _ = ctx.text_extents(self._label) - ctx.move_to(int((self.allocation.width-textWidth)/2), - int((self.allocation.height+textHeight)/2)) - ctx.text_path(self._label) - ctx.fill() - - return True - - - def _set_label(self, value): - """Sets the label and flags the widget to be redrawn.""" - self._label = value - # FIXME hack to calculate size. necessary because may not have been - # realized - surf = cairo.SVGSurface("/dev/null", 0, 0) - ctx = cairo.Context(surf) - _, _, width, height, _, _ = ctx.text_extents(self._label) - del ctx, surf - - # FIXME bogus values follows - self.set_size_request(int(width+20), int(height+40)) - # TODO test changing a realized label - - def _get_label(self): - """Getter method for the label property""" - return self._label - - label = property(fget=_get_label, fset=_set_label,\ - doc="Text label which is to be painted on the top of the widget") - diff --git a/src/sugar/tutorius/uam/Makefile.am b/src/sugar/tutorius/uam/Makefile.am deleted file mode 100644 index 219291e..0000000 --- a/src/sugar/tutorius/uam/Makefile.am +++ /dev/null @@ -1,5 +0,0 @@ -sugardir = $(pythondir)/sugar/tutorius/uam -sugar_PYTHON = \ - gobjectparser.py \ - gtkparser.py \ - __init__.py diff --git a/src/sugar/tutorius/uam/__init__.py b/src/sugar/tutorius/uam/__init__.py deleted file mode 100644 index 7cf5671..0000000 --- a/src/sugar/tutorius/uam/__init__.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com> -# -# 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 -""" -Universal Addressing Mechanism module - -Allows addressing Events, signals, widgets, etc for supported platforms -""" - -from urllib2 import urlparse - -import gtkparser -import gobjectparser - - -SCHEME="tap" #Tutorius Adressing Protocol - -__parsers = { - gtkparser.SCHEME:gtkparser.parse_gtk, - gobjectparser.SCHEME:gobjectparser.parse_gobject, -} - -def __add_to_urlparse(name): - #Add to uses_netloc - if not name in urlparse.uses_netloc: - urlparse.uses_netloc.append(name) - - #Add to uses_relative - if not name in urlparse.uses_relative: - urlparse.uses_relative.append(name) - -# #Add to uses_params -# if not name in urlparse.uses_params: -# urlparse.uses_params.append(name) - - #Add to uses_query - if not name in urlparse.uses_query: - urlparse.uses_query.append(name) - - #Add to uses_frament - if not name in urlparse.uses_fragment: - urlparse.uses_fragment.append(name) - - -#Add schemes to urlparse -__add_to_urlparse(SCHEME) - -for subscheme in [".".join([SCHEME,s]) for s in __parsers]: - __add_to_urlparse(subscheme) - - -class SchemeError(Exception): - def __init__(self, message): - Exception.__init__(self, message) - self.message = message - - -def parse_uri(uri): - res = urlparse.urlparse(uri) - - scheme = res.scheme.split(".")[0] - subscheme = ".".join(res.scheme.split(".")[1:]) - if not scheme == SCHEME: - raise SchemeError("Scheme %s not supported" % scheme) - - if subscheme != "" and not subscheme in __parsers: - raise SchemeError("SubScheme %s not supported" % subscheme) - - if subscheme: - return __parsers[subscheme](res) - - return res - - - diff --git a/src/sugar/tutorius/uam/gobjectparser.py b/src/sugar/tutorius/uam/gobjectparser.py deleted file mode 100644 index c1fba3d..0000000 --- a/src/sugar/tutorius/uam/gobjectparser.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com> -# -# 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 -""" -UAM Parser for gobject subscheme - -To be completed -""" - -SCHEME="gobject" - -def parse_gobject(parsed_uri): - """Do nothing for now""" - return parsed_uri diff --git a/src/sugar/tutorius/uam/gtkparser.py b/src/sugar/tutorius/uam/gtkparser.py deleted file mode 100644 index ede2f03..0000000 --- a/src/sugar/tutorius/uam/gtkparser.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Vincent Vinet <vince.vinet@gmail.com> -# -# 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 -""" -UAM Parser for gtk subscheme - -Allows addressing Gtk Events, signals, widgets - -The gtk subscheme for tutorius is - -<scheme>://<activity>/<path>[?<params>#<ptype>] - -where: - -<scheme> is the uam.SCHEME + "." + SCHEME - -<activity> is the activity's dns identifier, such as battleship.tutorius.org - -<path> is the Hierarchical path to the widget, where 0 is the activity, such as /0/0/1/0/1/0 - -<params> can be used to specify additionnal parameters required for an event handler or action, such as event=clicked - -<ptype> must be used with params to specify which action or eventfilter to use, such as "DialogMessage" - -""" - -SCHEME="gtk" - -def parse_gtk(parsed_uri): - """Do nothing for now""" - return parsed_uri |