diff options
-rw-r--r-- | addons/EmbeddedInterpreter.py | 30 | ||||
-rw-r--r-- | addons/WidgetIdentifier.py | 35 | ||||
-rw-r--r-- | addons/eventgenerator.py | 60 | ||||
-rw-r--r-- | addons/readfile.py | 6 | ||||
-rw-r--r-- | addons/timerevent.py | 4 | ||||
-rw-r--r-- | data/icons/Layer 1.svg | 6 | ||||
-rw-r--r-- | data/icons/clock.sugar.svg | 1593 | ||||
-rw-r--r-- | tests/constraintstests.py | 42 | ||||
-rw-r--r-- | tests/enginetests.py | 30 | ||||
-rw-r--r-- | tests/inject.py | 57 | ||||
-rw-r--r-- | tests/propertiestests.py | 57 | ||||
-rw-r--r-- | tests/translatortests.py | 131 | ||||
-rw-r--r-- | tests/vaulttests.py | 21 | ||||
-rw-r--r-- | tutorius/actions.py | 1 | ||||
-rw-r--r-- | tutorius/constraints.py | 39 | ||||
-rw-r--r-- | tutorius/core.py | 22 | ||||
-rw-r--r-- | tutorius/editor_interpreter.py | 105 | ||||
-rw-r--r-- | tutorius/events.py | 36 | ||||
-rw-r--r-- | tutorius/ipython_view.py | 301 | ||||
-rw-r--r-- | tutorius/properties.py | 53 | ||||
-rw-r--r-- | tutorius/translator.py | 186 | ||||
-rw-r--r-- | tutorius/vault.py | 12 |
22 files changed, 2794 insertions, 33 deletions
diff --git a/addons/EmbeddedInterpreter.py b/addons/EmbeddedInterpreter.py new file mode 100644 index 0000000..8c3522e --- /dev/null +++ b/addons/EmbeddedInterpreter.py @@ -0,0 +1,30 @@ +from sugar.tutorius.actions import Action +from sugar.tutorius.editor_interpreter import EditorInterpreter +from sugar.tutorius.services import ObjectStore + +class EmbeddedInterpreter(Action): + def __init__(self): + Action.__init__(self) + self.activity = None + self._dialog = None + + def do(self): + os = ObjectStore() + if os.activity: + self.activity = os.activity + + self._dialog = EditorInterpreter(self.activity) + self._dialog.show() + + + def undo(self): + if self._dialog: + self._dialog.destroy() + +__action__ = { + "name" : "EmbeddedInterpreter", + "display_name" : "Embedded Interpreter", + "icon" : "message-bubble", + "class" : EmbeddedInterpreter, + "mandatory_props" : [] +} diff --git a/addons/WidgetIdentifier.py b/addons/WidgetIdentifier.py new file mode 100644 index 0000000..3c559b5 --- /dev/null +++ b/addons/WidgetIdentifier.py @@ -0,0 +1,35 @@ +from sugar.tutorius.actions import Action +from sugar.tutorius.editor import WidgetIdentifier as WIPrimitive +from sugar.tutorius.services import ObjectStore + +class WidgetIdentifier(Action): + def __init__(self): + Action.__init__(self) + self.activity = None + self._dialog = None + + def do(self): + os = ObjectStore() + if os.activity: + self.activity = os.activity + + self._dialog = WIPrimitive(self.activity) + self._dialog.show() + + + def undo(self): + if self._dialog: + # TODO elavoie 2009-07-19 + # We should disconnect the handlers, however there seems to be an error + # saying that the size of the dictionary changed during the iteration + # We should investigate this + #self._dialog._disconnect_handlers() + self._dialog.destroy() + +__action__ = { + "name" : "WidgetIdentifier", + "display_name" : "Widget Identifier", + "icon" : "message-bubble", + "class" : WidgetIdentifier, + "mandatory_props" : [] +} diff --git a/addons/eventgenerator.py b/addons/eventgenerator.py new file mode 100644 index 0000000..a91ccf4 --- /dev/null +++ b/addons/eventgenerator.py @@ -0,0 +1,60 @@ +# Copyright (C) 2009, Tutorius.org +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +from sugar.tutorius.actions import * +from sugar.tutorius.gtkutils import find_widget + +class EventGenerator(Action): + source = TUAMProperty("") + type = TStringProperty("clicked") + + def __init__(self, source=None, type="clicked"): + Action.__init__(self) + + if source != None: + self.source = source + + if type != "clicked": + self.type = type + + def do(self): + self._activity = ObjectStore().activity + + # TODO elavoie 2009-07-25 We should eventually use the UAM mecanism + widget = find_widget(self._activity, self.source) + + + # TODO elavoie 2009-07-25 We assume a gtk activity, it might + # get messier with other widget systems + + # Call the signal on the widget + # We use introspection here to obtain the + # method that will send the corresponding + # signal on the gtk object + getattr(widget, self.type)() + + # That's all!!! + + def undo(self): + pass + +__action__ = { + "name" : "EventGenerator", + "display_name" : "Event Generator", + "icon" : "message-bubble", + "class" : EventGenerator, + "mandatory_props" : ["source", "type"] +} + diff --git a/addons/readfile.py b/addons/readfile.py index 4a6c54d..3cd41b6 100644 --- a/addons/readfile.py +++ b/addons/readfile.py @@ -16,9 +16,9 @@ import os -from ..actions import Action -from ..properties import TFileProperty -from ..services import ObjectStore +from sugar.tutorius.actions import Action +from sugar.tutorius.properties import TFileProperty +from sugar.tutorius.services import ObjectStore class ReadFile(Action): filename = TFileProperty(None) diff --git a/addons/timerevent.py b/addons/timerevent.py index 752a865..c7374d0 100644 --- a/addons/timerevent.py +++ b/addons/timerevent.py @@ -16,8 +16,8 @@ import gobject -from ..filters import EventFilter -from ..properties import TIntProperty +from sugar.tutorius.filters import EventFilter +from sugar.tutorius.properties import TIntProperty class TimerEvent(EventFilter): """ diff --git a/data/icons/Layer 1.svg b/data/icons/Layer 1.svg new file mode 100644 index 0000000..e7d9e2b --- /dev/null +++ b/data/icons/Layer 1.svg @@ -0,0 +1,6 @@ +<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [ + <!ENTITY stroke_color "#666666"> + <!ENTITY fill_color "#ffffff"> +]><svg height="48px" id="svg2597" inkscape:output_extension="org.inkscape.output.svg.inkscape" inkscape:version="0.46" sodipodi:docname="chain.svg" sodipodi:version="0.32" width="48px" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"><g display="block" id="layer1" inkscape:groupmode="layer" inkscape:label="Layer 1"> + <path d="M 9.8339096,17.521424 L 11.225374,16.510468 L 21.895596,24.262843 L 20.957347,27.150437 L 19.395745,28.285012 L 20.561807,24.696201 L 11.225374,17.912898 L 10.202552,18.656048 L 9.8339096,17.521424 z M 21.057049,9.3673619 L 31.727272,1.6149871 L 42.397446,9.3673619 L 39.590553,18.005887 L 38.62545,17.304719 L 41.063657,9.8007699 L 31.727272,3.0174177 L 22.390838,9.8007699 L 23.597982,13.515988 L 23.001478,15.351781 L 21.057049,9.3673619 z M 24.194484,19.023319 L 24.790989,17.187573 L 25.957048,20.776336 L 37.497447,20.776336 L 37.888118,19.573873 L 38.853268,20.275089 L 38.321764,21.910912 L 25.132685,21.910912 L 24.194484,19.023319 z M 0.5551853,24.262843 L 7.9036154,18.923855 L 8.2723049,20.058432 L 1.8889674,24.696201 L 5.4551984,35.671819 L 16.995595,35.671819 L 18.202737,31.9566 L 19.764434,30.821973 L 17.819959,36.806394 L 4.6308143,36.806394 L 0.5551853,24.262843 z M 31.476438,20.209046 L 36.567095,16.510468 L 47.237269,24.262843 L 43.161633,36.806394 L 34.078493,36.806394 L 34.447138,35.671819 L 42.337264,35.671819 L 45.903479,24.696201 L 36.567095,17.912898 L 33.406728,20.209046 L 31.476438,20.209046 z M 5.3950189,9.3673619 L 16.065194,1.6149871 L 23.413707,6.9539745 L 22.44856,7.6551909 L 16.065194,3.0174177 L 6.7288079,9.8007699 L 10.29502,20.776336 L 14.201416,20.776336 L 15.763019,21.910912 L 9.4707017,21.910912 L 5.3950189,9.3673619 z M 18.061955,20.776336 L 21.835416,20.776336 L 25.401627,9.8007699 L 24.378759,9.0576215 L 25.343953,8.3564062 L 26.735416,9.3673619 L 22.659781,21.910912 L 19.623558,21.910912 L 18.061955,20.776336 z M 14.494797,37.37368 L 15.687758,37.37368 L 18.126011,44.877733 L 29.666452,44.877733 L 33.232667,33.902157 L 30.072253,31.605965 L 29.475748,29.770175 L 34.566456,33.468751 L 30.490816,46.012316 L 17.301647,46.012316 L 14.494797,37.37368 z M 25.896871,24.262843 L 28.353226,22.478199 L 30.283475,22.478199 L 27.230659,24.696201 L 30.796869,35.671819 L 32.061166,35.671819 L 31.692527,36.806394 L 29.972505,36.806394 L 25.896871,24.262843 z M 13.226011,33.468751 L 23.896234,25.716376 L 26.352544,27.501018 L 26.949047,29.336809 L 23.896234,27.118808 L 14.559799,33.902157 L 14.950472,35.104529 L 13.757512,35.104529 L 13.226011,33.468751 z" id="path2535"/> + </g></svg>
\ No newline at end of file diff --git a/data/icons/clock.sugar.svg b/data/icons/clock.sugar.svg new file mode 100644 index 0000000..0334c1a --- /dev/null +++ b/data/icons/clock.sugar.svg @@ -0,0 +1,1593 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + height="48" + id="svg2" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:version="0.46" + sodipodi:docbase="C:\Documents and Settings\Molumen\Desktop" + sodipodi:docname="clock.sugar.svg" + sodipodi:modified="true" + sodipodi:version="0.32" + version="1.0" + width="48"> + <defs + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 115.5 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="231 : 115.5 : 1" + inkscape:persp3d-origin="115.5 : 77 : 1" + id="perspective228" /> + <linearGradient + gradientUnits="userSpaceOnUse" + id="linearGradient20470" + inkscape:collect="always" + x1="302" + x2="302" + xlink:href="#linearGradient13034" + y1="365.95651" + y2="84.524567" /> + <radialGradient + cx="527" + cy="691.20294" + fx="527" + fy="691.20294" + gradientTransform="matrix(1,0,0,0.231842,-340,200.219)" + gradientUnits="userSpaceOnUse" + id="radialGradient20468" + inkscape:collect="always" + r="90.78125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="528" + cy="368.17188" + fx="528" + fy="368.17188" + gradientTransform="matrix(1,0,0,0.262455,-341,27.5432)" + gradientUnits="userSpaceOnUse" + id="radialGradient20466" + inkscape:collect="always" + r="113.53125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="504.125" + cy="468.57623" + fx="504.125" + fy="468.57623" + gradientTransform="matrix(1.05261,0,0,1.05261,-26.5224,-23.8951)" + gradientUnits="userSpaceOnUse" + id="radialGradient20464" + inkscape:collect="always" + r="2.625" + xlink:href="#linearGradient13172" /> + <radialGradient + cx="525.49945" + cy="467.18744" + fx="525.49945" + fy="467.18744" + gradientTransform="matrix(1.77314,0,0,1.77314,-744.784,-597.014)" + gradientUnits="userSpaceOnUse" + id="radialGradient20462" + inkscape:collect="always" + r="138" + spreadMethod="pad" + xlink:href="#linearGradient12953" /> + <radialGradient + cx="302" + cy="239.93021" + fx="302" + fy="239.93021" + gradientTransform="matrix(3.14096,0,0,3.14096,-646.57,-549.905)" + gradientUnits="userSpaceOnUse" + id="radialGradient20460" + inkscape:collect="always" + r="138" + xlink:href="#linearGradient20428" /> + <radialGradient + cx="302" + cy="239.93021" + fx="302" + fy="239.93021" + gradientTransform="matrix(3.14096,0,0,3.14096,-646.57,-549.905)" + gradientUnits="userSpaceOnUse" + id="radialGradient20438" + inkscape:collect="always" + r="138" + xlink:href="#linearGradient20428" /> + <radialGradient + cx="525.49945" + cy="467.18744" + fx="525.49945" + fy="467.18744" + gradientTransform="matrix(1.77314,0,0,1.77314,-744.784,-597.014)" + gradientUnits="userSpaceOnUse" + id="radialGradient19456" + inkscape:collect="always" + r="138" + spreadMethod="pad" + xlink:href="#linearGradient12953" /> + <radialGradient + cx="528" + cy="368.17188" + fx="528" + fy="368.17188" + gradientTransform="matrix(1,0,0,0.262455,-341,27.5432)" + gradientUnits="userSpaceOnUse" + id="radialGradient19449" + inkscape:collect="always" + r="113.53125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="527" + cy="691.20294" + fx="527" + fy="691.20294" + gradientTransform="matrix(1,0,0,0.231842,-340,200.219)" + gradientUnits="userSpaceOnUse" + id="radialGradient19446" + inkscape:collect="always" + r="90.78125" + xlink:href="#linearGradient12977" /> + <linearGradient + gradientUnits="userSpaceOnUse" + id="linearGradient19441" + inkscape:collect="always" + x1="302" + x2="302" + xlink:href="#linearGradient13034" + y1="365.95651" + y2="84.524567" /> + <radialGradient + cx="527" + cy="691.20294" + fx="527" + fy="691.20294" + gradientTransform="matrix(1,0,0,0.231842,-1,463.219)" + gradientUnits="userSpaceOnUse" + id="radialGradient19439" + inkscape:collect="always" + r="90.78125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="528" + cy="368.17188" + fx="528" + fy="368.17188" + gradientTransform="matrix(1,0,0,0.262455,-2,290.543)" + gradientUnits="userSpaceOnUse" + id="radialGradient19437" + inkscape:collect="always" + r="113.53125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="504.125" + cy="468.57623" + fx="504.125" + fy="468.57623" + gradientTransform="matrix(1.05261,0,0,1.05261,-26.5224,-23.8951)" + gradientUnits="userSpaceOnUse" + id="radialGradient19435" + inkscape:collect="always" + r="2.625" + xlink:href="#linearGradient13172" /> + <radialGradient + cx="525.49945" + cy="467.18744" + fx="525.49945" + fy="467.18744" + gradientTransform="matrix(1.77314,0,0,1.77314,-405.784,-334.014)" + gradientUnits="userSpaceOnUse" + id="radialGradient19433" + inkscape:collect="always" + r="138" + spreadMethod="pad" + xlink:href="#linearGradient12953" /> + <radialGradient + cx="302" + cy="239.93021" + fx="302" + fy="239.93021" + gradientTransform="matrix(3.14096,0,0,3.14096,-646.57,-549.905)" + gradientUnits="userSpaceOnUse" + id="radialGradient19431" + inkscape:collect="always" + r="138" + xlink:href="#linearGradient13012" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16295" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient16243" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-781.919,-183.605)" + gradientUnits="userSpaceOnUse" + id="radialGradient16293" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient16243" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16285" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient16243" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-781.919,-183.605)" + gradientUnits="userSpaceOnUse" + id="radialGradient16283" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient16243" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16275" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient16243" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-781.919,-183.605)" + gradientUnits="userSpaceOnUse" + id="radialGradient16273" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient16243" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16265" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient16243" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-781.919,-183.605)" + gradientUnits="userSpaceOnUse" + id="radialGradient16263" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient16243" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16249" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient16243" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-781.919,-183.605)" + gradientUnits="userSpaceOnUse" + id="radialGradient16241" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient16243" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16197" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,24.3952)" + gradientUnits="userSpaceOnUse" + id="radialGradient16195" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16189" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,24.3952)" + gradientUnits="userSpaceOnUse" + id="radialGradient16187" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16181" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,24.3952)" + gradientUnits="userSpaceOnUse" + id="radialGradient16179" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16173" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,24.3952)" + gradientUnits="userSpaceOnUse" + id="radialGradient16171" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16165" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,24.3952)" + gradientUnits="userSpaceOnUse" + id="radialGradient16163" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16095" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient15913" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,-173.605)" + gradientUnits="userSpaceOnUse" + id="radialGradient16093" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient15913" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16091" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,24.3952)" + gradientUnits="userSpaceOnUse" + id="radialGradient16089" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16087" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient15913" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,-173.605)" + gradientUnits="userSpaceOnUse" + id="radialGradient16085" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient15913" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16083" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,24.3952)" + gradientUnits="userSpaceOnUse" + id="radialGradient16081" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16079" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient15913" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,-173.605)" + gradientUnits="userSpaceOnUse" + id="radialGradient16077" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient15913" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16075" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,24.3952)" + gradientUnits="userSpaceOnUse" + id="radialGradient16073" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16071" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient15913" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,-173.605)" + gradientUnits="userSpaceOnUse" + id="radialGradient16069" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient15913" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16067" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,24.3952)" + gradientUnits="userSpaceOnUse" + id="radialGradient16065" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16063" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient15913" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,-173.605)" + gradientUnits="userSpaceOnUse" + id="radialGradient16061" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient15913" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient16059" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.25538,-1.25538,2.63349,2.63349,-1015.92,24.3952)" + gradientUnits="userSpaceOnUse" + id="radialGradient16057" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + <radialGradient + cx="202.5" + cy="578.86218" + fx="202.5" + fy="578.86218" + gradientUnits="userSpaceOnUse" + id="radialGradient13728" + inkscape:collect="always" + r="91.5" + xlink:href="#linearGradient10759" /> + <linearGradient + gradientUnits="userSpaceOnUse" + id="linearGradient13265" + inkscape:collect="always" + x1="302" + x2="302" + xlink:href="#linearGradient13034" + y1="365.95651" + y2="84.524567" /> + <radialGradient + cx="527" + cy="691.20294" + fx="527" + fy="691.20294" + gradientTransform="matrix(1,0,0,0.231842,-1,463.219)" + gradientUnits="userSpaceOnUse" + id="radialGradient13263" + inkscape:collect="always" + r="90.78125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="528" + cy="368.17188" + fx="528" + fy="368.17188" + gradientTransform="matrix(1,0,0,0.262455,-2,290.543)" + gradientUnits="userSpaceOnUse" + id="radialGradient13261" + inkscape:collect="always" + r="113.53125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="504.125" + cy="468.57623" + fx="504.125" + fy="468.57623" + gradientTransform="matrix(1.05261,0,0,1.05261,-26.5224,-23.8951)" + gradientUnits="userSpaceOnUse" + id="radialGradient13259" + inkscape:collect="always" + r="2.625" + xlink:href="#linearGradient13172" /> + <radialGradient + cx="525.49945" + cy="467.18744" + fx="525.49945" + fy="467.18744" + gradientTransform="matrix(1.77314,0,0,1.77314,-405.784,-334.014)" + gradientUnits="userSpaceOnUse" + id="radialGradient13257" + inkscape:collect="always" + r="138" + spreadMethod="pad" + xlink:href="#linearGradient12953" /> + <radialGradient + cx="302" + cy="239.93021" + fx="302" + fy="239.93021" + gradientTransform="matrix(3.14096,0,0,3.14096,-646.57,-549.905)" + gradientUnits="userSpaceOnUse" + id="radialGradient13255" + inkscape:collect="always" + r="138" + xlink:href="#linearGradient13012" /> + <linearGradient + gradientUnits="userSpaceOnUse" + id="linearGradient13231" + inkscape:collect="always" + x1="302" + x2="302" + xlink:href="#linearGradient13034" + y1="365.95651" + y2="84.524567" /> + <radialGradient + cx="527" + cy="691.20294" + fx="527" + fy="691.20294" + gradientTransform="matrix(1,0,0,0.231842,-1,463.219)" + gradientUnits="userSpaceOnUse" + id="radialGradient13229" + inkscape:collect="always" + r="90.78125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="528" + cy="368.17188" + fx="528" + fy="368.17188" + gradientTransform="matrix(1,0,0,0.262455,-2,290.543)" + gradientUnits="userSpaceOnUse" + id="radialGradient13227" + inkscape:collect="always" + r="113.53125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="504.125" + cy="468.57623" + fx="504.125" + fy="468.57623" + gradientTransform="matrix(1.05261,0,0,1.05261,-26.5224,-23.8951)" + gradientUnits="userSpaceOnUse" + id="radialGradient13225" + inkscape:collect="always" + r="2.625" + xlink:href="#linearGradient13172" /> + <radialGradient + cx="525.49945" + cy="467.18744" + fx="525.49945" + fy="467.18744" + gradientTransform="matrix(1.77314,0,0,1.77314,-405.784,-334.014)" + gradientUnits="userSpaceOnUse" + id="radialGradient13223" + inkscape:collect="always" + r="138" + spreadMethod="pad" + xlink:href="#linearGradient12953" /> + <radialGradient + cx="302" + cy="239.93021" + fx="302" + fy="239.93021" + gradientTransform="matrix(3.14096,0,0,3.14096,-646.57,-549.905)" + gradientUnits="userSpaceOnUse" + id="radialGradient13221" + inkscape:collect="always" + r="138" + xlink:href="#linearGradient13012" /> + <radialGradient + cx="525.49945" + cy="467.18744" + fx="525.49945" + fy="467.18744" + gradientTransform="matrix(1.77314,0,0,1.77314,-405.784,-334.014)" + gradientUnits="userSpaceOnUse" + id="radialGradient13206" + inkscape:collect="always" + r="138" + spreadMethod="pad" + xlink:href="#linearGradient12953" /> + <radialGradient + cx="528" + cy="368.17188" + fx="528" + fy="368.17188" + gradientTransform="matrix(1,0,0,0.262455,-2,290.543)" + gradientUnits="userSpaceOnUse" + id="radialGradient13203" + inkscape:collect="always" + r="113.53125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="527" + cy="691.20294" + fx="527" + fy="691.20294" + gradientTransform="matrix(1,0,0,0.231842,-1,463.219)" + gradientUnits="userSpaceOnUse" + id="radialGradient13200" + inkscape:collect="always" + r="90.78125" + xlink:href="#linearGradient12977" /> + <linearGradient + gradientUnits="userSpaceOnUse" + id="linearGradient13195" + inkscape:collect="always" + x1="302" + x2="302" + xlink:href="#linearGradient13034" + y1="365.95651" + y2="84.524567" /> + <radialGradient + cx="527" + cy="691.20294" + fx="527" + fy="691.20294" + gradientTransform="matrix(1,0,0,0.231842,-1,463.219)" + gradientUnits="userSpaceOnUse" + id="radialGradient13193" + inkscape:collect="always" + r="90.78125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="528" + cy="368.17188" + fx="528" + fy="368.17188" + gradientTransform="matrix(1,0,0,0.262455,-2,290.543)" + gradientUnits="userSpaceOnUse" + id="radialGradient13191" + inkscape:collect="always" + r="113.53125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="525.49945" + cy="467.18744" + fx="525.49945" + fy="467.18744" + gradientTransform="matrix(1.77314,0,0,1.77314,-405.784,-334.014)" + gradientUnits="userSpaceOnUse" + id="radialGradient13189" + inkscape:collect="always" + r="138" + spreadMethod="pad" + xlink:href="#linearGradient12953" /> + <radialGradient + cx="302" + cy="239.93021" + fx="302" + fy="239.93021" + gradientTransform="matrix(3.14096,0,0,3.14096,-646.57,-549.905)" + gradientUnits="userSpaceOnUse" + id="radialGradient13187" + inkscape:collect="always" + r="138" + xlink:href="#linearGradient13012" /> + <radialGradient + cx="504.125" + cy="468.57623" + fx="504.125" + fy="468.57623" + gradientTransform="matrix(1.05261,0,0,1.05261,-26.5224,-23.8951)" + gradientUnits="userSpaceOnUse" + id="radialGradient13170" + inkscape:collect="always" + r="2.625" + xlink:href="#linearGradient13172" /> + <linearGradient + gradientUnits="userSpaceOnUse" + id="linearGradient13146" + inkscape:collect="always" + x1="302" + x2="302" + xlink:href="#linearGradient13034" + y1="365.95651" + y2="84.524567" /> + <radialGradient + cx="525.49945" + cy="467.18744" + fx="525.49945" + fy="467.18744" + gradientTransform="matrix(1.77314,0,0,1.77314,-405.784,-334.014)" + gradientUnits="userSpaceOnUse" + id="radialGradient13143" + inkscape:collect="always" + r="138" + spreadMethod="pad" + xlink:href="#linearGradient12953" /> + <radialGradient + cx="528" + cy="368.17188" + fx="528" + fy="368.17188" + gradientTransform="matrix(1,0,0,0.262455,-2,290.543)" + gradientUnits="userSpaceOnUse" + id="radialGradient13140" + inkscape:collect="always" + r="113.53125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="527" + cy="691.20294" + fx="527" + fy="691.20294" + gradientTransform="matrix(1,0,0,0.231842,-1,463.219)" + gradientUnits="userSpaceOnUse" + id="radialGradient13137" + inkscape:collect="always" + r="90.78125" + xlink:href="#linearGradient12977" /> + <linearGradient + gradientUnits="userSpaceOnUse" + id="linearGradient13133" + inkscape:collect="always" + x1="302" + x2="302" + xlink:href="#linearGradient13034" + y1="426.36218" + y2="150.36218" /> + <radialGradient + cx="527" + cy="691.20294" + fx="527" + fy="691.20294" + gradientTransform="matrix(1,0,0,0.231842,-1,463.219)" + gradientUnits="userSpaceOnUse" + id="radialGradient13131" + inkscape:collect="always" + r="90.78125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="528" + cy="368.17188" + fx="528" + fy="368.17188" + gradientTransform="matrix(1,0,0,0.262455,-2,290.543)" + gradientUnits="userSpaceOnUse" + id="radialGradient13129" + inkscape:collect="always" + r="113.53125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="525.49945" + cy="467.18744" + fx="525.49945" + fy="467.18744" + gradientTransform="matrix(1.77314,0,0,1.77314,-405.784,-334.014)" + gradientUnits="userSpaceOnUse" + id="radialGradient13127" + inkscape:collect="always" + r="138" + spreadMethod="pad" + xlink:href="#linearGradient12953" /> + <radialGradient + cx="302" + cy="239.93021" + fx="302" + fy="239.93021" + gradientTransform="matrix(3.14096,0,0,3.14096,-646.57,-549.905)" + gradientUnits="userSpaceOnUse" + id="radialGradient13125" + inkscape:collect="always" + r="138" + xlink:href="#linearGradient13012" /> + <linearGradient + gradientUnits="userSpaceOnUse" + id="linearGradient13032" + inkscape:collect="always" + x1="302" + x2="302" + xlink:href="#linearGradient13034" + y1="426.36218" + y2="150.36218" /> + <radialGradient + cx="302" + cy="239.93021" + fx="302" + fy="239.93021" + gradientTransform="matrix(3.14096,0,0,3.14096,-646.57,-549.905)" + gradientUnits="userSpaceOnUse" + id="radialGradient13010" + inkscape:collect="always" + r="138" + xlink:href="#linearGradient13012" /> + <radialGradient + cx="527" + cy="691.20294" + fx="527" + fy="691.20294" + gradientTransform="matrix(1,0,0,0.231842,-1,463.219)" + gradientUnits="userSpaceOnUse" + id="radialGradient13000" + inkscape:collect="always" + r="90.78125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="528" + cy="368.17188" + fx="528" + fy="368.17188" + gradientTransform="matrix(-0.932879,0,0,-0.244839,1018.94,683.505)" + gradientUnits="userSpaceOnUse" + id="radialGradient12987" + inkscape:collect="always" + r="113.53125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="528" + cy="368.17188" + fx="528" + fy="368.17188" + gradientTransform="matrix(1,0,0,0.262455,-2,290.543)" + gradientUnits="userSpaceOnUse" + id="radialGradient12983" + inkscape:collect="always" + r="113.53125" + xlink:href="#linearGradient12977" /> + <radialGradient + cx="525.49945" + cy="467.18744" + fx="525.49945" + fy="467.18744" + gradientTransform="matrix(1.77314,0,0,1.77314,-405.784,-334.014)" + gradientUnits="userSpaceOnUse" + id="radialGradient12959" + inkscape:collect="always" + r="138" + spreadMethod="pad" + xlink:href="#linearGradient12953" /> + <linearGradient + id="linearGradient5553"> + <stop + id="stop5555" + offset="0" + style="stop-color: rgb(53, 155, 68); stop-opacity: 1;" /> + <stop + id="stop5557" + offset="1" + style="stop-color: rgb(50, 204, 73); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient5613"> + <stop + id="stop5615" + offset="0" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0.888889;" /> + <stop + id="stop5617" + offset="0.5" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + <stop + id="stop5619" + offset="0.51142859" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + <stop + id="stop5621" + offset="1" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + </linearGradient> + <linearGradient + id="linearGradient5675"> + <stop + id="stop5677" + offset="0" + style="stop-color: rgb(0, 138, 0); stop-opacity: 0.890196;" /> + <stop + id="stop5679" + offset="0.5" + style="stop-color: rgb(72, 143, 51); stop-opacity: 0;" /> + <stop + id="stop5681" + offset="0.51142859" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + <stop + id="stop5683" + offset="1" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + </linearGradient> + <linearGradient + id="linearGradient5563"> + <stop + id="stop5565" + offset="0" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0.703704;" /> + <stop + id="stop5571" + offset="0.50850612" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0.189815;" /> + <stop + id="stop5573" + offset="0.51142859" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + <stop + id="stop5567" + offset="1" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + </linearGradient> + <linearGradient + id="linearGradient5537"> + <stop + id="stop5539" + offset="0" + style="stop-color: rgb(74, 206, 96); stop-opacity: 1;" /> + <stop + id="stop5541" + offset="1" + style="stop-color: rgb(180, 234, 135); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient10743"> + <stop + id="stop10745" + offset="0" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0.703704;" /> + <stop + id="stop10747" + offset="0.50850612" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0.189815;" /> + <stop + id="stop10749" + offset="0.51142859" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + <stop + id="stop10751" + offset="1" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + </linearGradient> + <linearGradient + id="linearGradient10753"> + <stop + id="stop10755" + offset="0" + style="stop-color: rgb(53, 155, 68); stop-opacity: 1;" /> + <stop + id="stop10757" + offset="1" + style="stop-color: rgb(50, 204, 73); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient10759"> + <stop + id="stop10761" + offset="0" + style="stop-color: rgb(74, 206, 96); stop-opacity: 1;" /> + <stop + id="stop10763" + offset="1" + style="stop-color: rgb(180, 234, 135); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient10767"> + <stop + id="stop10769" + offset="0" + style="stop-color: rgb(53, 155, 68); stop-opacity: 1;" /> + <stop + id="stop10771" + offset="1" + style="stop-color: rgb(50, 204, 73); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient10773"> + <stop + id="stop10775" + offset="0" + style="stop-color: rgb(0, 138, 0); stop-opacity: 0.890196;" /> + <stop + id="stop10777" + offset="0.5" + style="stop-color: rgb(72, 143, 51); stop-opacity: 0;" /> + <stop + id="stop10779" + offset="0.51142859" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + <stop + id="stop10781" + offset="1" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + </linearGradient> + <linearGradient + id="linearGradient10783"> + <stop + id="stop10785" + offset="0" + style="stop-color: rgb(0, 138, 0); stop-opacity: 0.890196;" /> + <stop + id="stop10787" + offset="0.5" + style="stop-color: rgb(72, 143, 51); stop-opacity: 0;" /> + <stop + id="stop10789" + offset="0.51142859" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + <stop + id="stop10791" + offset="1" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + </linearGradient> + <linearGradient + id="linearGradient10793"> + <stop + id="stop10795" + offset="0" + style="stop-color: rgb(0, 138, 0); stop-opacity: 0.890196;" /> + <stop + id="stop10797" + offset="0.5" + style="stop-color: rgb(72, 143, 51); stop-opacity: 0;" /> + <stop + id="stop10799" + offset="0.51142859" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + <stop + id="stop10801" + offset="1" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + </linearGradient> + <linearGradient + id="linearGradient10803"> + <stop + id="stop10805" + offset="0" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0.888889;" /> + <stop + id="stop10807" + offset="0.5" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + <stop + id="stop10809" + offset="0.51142859" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + <stop + id="stop10811" + offset="1" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + </linearGradient> + <linearGradient + id="linearGradient10813"> + <stop + id="stop10815" + offset="0" + style="stop-color: rgb(53, 155, 68); stop-opacity: 1;" /> + <stop + id="stop10817" + offset="1" + style="stop-color: rgb(50, 204, 73); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient10819"> + <stop + id="stop10821" + offset="0" + style="stop-color: rgb(74, 206, 96); stop-opacity: 1;" /> + <stop + id="stop10823" + offset="1" + style="stop-color: rgb(180, 234, 135); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient12953"> + <stop + id="stop12955" + offset="0" + style="stop-color: rgb(0, 0, 0); stop-opacity: 1;" /> + <stop + id="stop12965" + offset="0.47816542" + style="stop-color: rgb(0, 0, 0); stop-opacity: 1;" /> + <stop + id="stop12961" + offset="0.49808899" + style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" /> + <stop + id="stop12967" + offset="0.50756544" + style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" /> + <stop + id="stop12963" + offset="0.53007674" + style="stop-color: rgb(0, 0, 0); stop-opacity: 1;" /> + <stop + id="stop12957" + offset="1" + style="stop-color: rgb(0, 0, 0); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient12977"> + <stop + id="stop12979" + offset="0" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0.319444;" /> + <stop + id="stop12981" + offset="1" + style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient13012"> + <stop + id="stop13014" + offset="0" + style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" /> + <stop + id="stop13018" + offset="0.20165709" + style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" /> + <stop + id="stop13020" + offset="0.32675916" + style="stop-color: rgb(239, 245, 251); stop-opacity: 1;" /> + <stop + id="stop13016" + offset="1" + style="stop-color: rgb(4, 72, 127); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient13034"> + <stop + id="stop13036" + offset="0" + style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" /> + <stop + id="stop13038" + offset="1" + style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient13172"> + <stop + id="stop13174" + offset="0" + style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" /> + <stop + id="stop13176" + offset="1" + style="stop-color: rgb(0, 0, 0); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient3346"> + <stop + id="stop3348" + offset="0" + style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" /> + <stop + id="stop3350" + offset="1" + style="stop-color: rgb(233, 233, 233); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient3916"> + <stop + id="stop3918" + offset="0" + style="stop-color: rgb(139, 139, 139); stop-opacity: 0.639175;" /> + <stop + id="stop3924" + offset="0.44642857" + style="stop-color: rgb(141, 141, 141); stop-opacity: 0.206186;" /> + <stop + id="stop3922" + offset="1" + style="stop-color: rgb(143, 143, 143); stop-opacity: 0;" /> + </linearGradient> + <linearGradient + id="linearGradient3440"> + <stop + id="stop3442" + offset="0" + style="stop-color: black; stop-opacity: 1;" /> + <stop + id="stop3452" + offset="0.3125" + style="stop-color: black; stop-opacity: 1;" /> + <stop + id="stop3446" + offset="0.53727454" + style="stop-color: white; stop-opacity: 1;" /> + <stop + id="stop3542" + offset="0.60522962" + style="stop-color: white; stop-opacity: 1;" /> + <stop + id="stop3448" + offset="0.6964286" + style="stop-color: black; stop-opacity: 1;" /> + <stop + id="stop3444" + offset="1" + style="stop-color: black; stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient3757"> + <stop + id="stop3759" + offset="0" + style="stop-color: black; stop-opacity: 1;" /> + <stop + id="stop3761" + offset="1" + style="stop-color: black; stop-opacity: 0;" /> + </linearGradient> + <linearGradient + id="linearGradient3584"> + <stop + id="stop3586" + offset="0" + style="stop-color: white; stop-opacity: 1;" /> + <stop + id="stop3592" + offset="1" + style="stop-color: white; stop-opacity: 0.498039;" /> + <stop + id="stop3588" + offset="1" + style="stop-color: white; stop-opacity: 0;" /> + </linearGradient> + <linearGradient + id="linearGradient15913"> + <stop + id="stop15915" + offset="0" + style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" /> + <stop + id="stop15917" + offset="1" + style="stop-color: rgb(240, 216, 35); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient16243"> + <stop + id="stop16245" + offset="0" + style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" /> + <stop + id="stop16247" + offset="1" + style="stop-color: rgb(35, 178, 240); stop-opacity: 1;" /> + </linearGradient> + <linearGradient + id="linearGradient20428"> + <stop + id="stop20430" + offset="0" + style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" /> + <stop + id="stop20432" + offset="0.20165709" + style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" /> + <stop + id="stop20434" + offset="0.32675916" + style="stop-color: rgb(251, 248, 239); stop-opacity: 1;" /> + <stop + id="stop20436" + offset="1" + style="stop-color: rgb(127, 98, 4); stop-opacity: 1;" /> + </linearGradient> + <radialGradient + cx="296.26508" + cy="361.61154" + fx="296.26508" + fy="361.61154" + gradientTransform="matrix(1.03888,-1.98608,4.32211,2.26874,-1497.58,2.13654)" + gradientUnits="userSpaceOnUse" + id="radialGradient2855" + inkscape:collect="always" + r="131" + xlink:href="#linearGradient3346" /> + </defs> + <sodipodi:namedview + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + id="base" + inkscape:current-layer="layer1" + inkscape:cx="21.55618" + inkscape:cy="22.885983" + inkscape:document-units="px" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:window-height="726" + inkscape:window-width="1208" + inkscape:window-x="62" + inkscape:window-y="25" + inkscape:zoom="11.240376" + objecttolerance="10" + pagecolor="#ffffff" + style="" + showgrid="false" /> + <metadata + id="metadata7"> + <rdf:RDF + style=""> + <cc:Work + rdf:about="" + style=""> + <dc:format + style="">image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" + style="" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:groupmode="layer" + inkscape:label="Layer 1" + transform="translate(-436.343,-114.661)"> + <g + id="g19409" + transform="matrix(0.1657254,0,0,0.1657254,373.12967,56.687971)"> + <path + d="M 440,288.36218 A 138,138 0 1 1 164,288.36218 A 138,138 0 1 1 440,288.36218 z" + id="path19411" + sodipodi:cx="302" + sodipodi:cy="288.36218" + sodipodi:rx="138" + sodipodi:ry="138" + sodipodi:type="arc" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="matrix(0.862319,0,0,0.862319,265.58,245.715)" /> + <path + d="M 526,356.375 C 449.824,356.375 388,418.199 388,494.375 C 388,570.551 449.824,632.375 526,632.375 C 602.176,632.375 664,570.551 664,494.375 C 664,418.199 602.176,356.375 526,356.375 z M 526,375.375 C 591.688,375.375 645,428.687 645,494.375 C 644.99999,560.063 591.688,613.375 526,613.375 C 460.312,613.37499 407,560.063 407,494.375 C 407,428.687 460.312,375.375 526,375.375 z" + id="path19413" + style="fill:url(#radialGradient19433);fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g19415"> + <path + d="M 572.45442,416.66652 L 574.56216,417.96362 L 518.94889,510.35634 L 515.03453,507.94743 L 572.45442,416.66652 z" + id="path19417" + sodipodi:nodetypes="ccccc" + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" /> + <path + d="M 470.10131,522.52645 L 468.3205,518.67564 L 539.27256,484.69517 L 541.94377,490.47138 L 470.10131,522.52645 z" + id="path19419" + sodipodi:nodetypes="ccccc" + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" /> + <path + d="M 506.75,471.48718 A 2.625,2.625 0 1 1 501.5,471.48718 A 2.625,2.625 0 1 1 506.75,471.48718 z" + id="path19421" + sodipodi:cx="504.125" + sodipodi:cy="471.48718" + sodipodi:rx="2.625" + sodipodi:ry="2.625" + sodipodi:type="arc" + style="opacity:1;fill:url(#radialGradient19435);fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="matrix(1.85714,0,0,1.85714,-410.232,-381.244)" /> + </g> + <path + d="M 526,357.375 C 478.94483,357.375 437.38188,380.97729 412.46875,416.96875 C 441.06326,387.02851 481.36166,368.375 526,368.375 C 570.63834,368.375 610.93674,387.02852 639.53125,416.96875 C 614.61812,380.97729 573.05517,357.375 526,357.375 z" + id="path19423" + style="fill:url(#radialGradient19437);fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <path + d="M 526,623.46875 C 562.37673,623.46875 594.94742,607.12155 616.78125,581.375 C 592.50974,602.60542 560.7553,615.46875 526,615.46875 C 491.2447,615.46875 459.49026,602.60542 435.21875,581.375 C 457.05258,607.12155 489.62327,623.46875 526,623.46875 z" + id="path19425" + style="fill:url(#radialGradient19439);fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <path + d="M 523.53125,378.40625 L 523.53125,402.125 C 525.1769,402.07398 526.82304,402.09578 528.46875,402.125 L 528.46875,378.40625 L 523.53125,378.40625 z M 468.9375,393.40625 L 467.09375,394.46875 L 477.0625,411.75 C 477.67174,411.38819 478.28844,411.03612 478.90625,410.6875 L 468.9375,393.40625 z M 583.0625,393.40625 L 573.09375,410.6875 C 573.71156,411.03612 574.32826,411.38819 574.9375,411.75 L 584.90625,394.46875 L 583.0625,393.40625 z M 426.09375,435.46875 L 425.03125,437.3125 L 442.3125,447.28125 C 442.66112,446.66344 443.01319,446.04674 443.375,445.4375 L 426.09375,435.46875 z M 625.90625,435.46875 L 608.625,445.4375 C 608.98681,446.04674 609.33888,446.66344 609.6875,447.28125 L 626.96875,437.3125 L 625.90625,435.46875 z M 410.03125,491.90625 L 410.03125,496.84375 L 433.75,496.84375 C 433.69898,495.1981 433.72078,493.55196 433.75,491.90625 L 410.03125,491.90625 z M 618.25,491.90625 C 618.30102,493.5519 618.27922,495.19804 618.25,496.84375 L 641.96875,496.84375 L 641.96875,491.90625 L 618.25,491.90625 z M 442.3125,541.46875 L 425.03125,551.4375 L 426.09375,553.28125 L 443.375,543.3125 C 443.01319,542.70326 442.66112,542.08656 442.3125,541.46875 z M 609.6875,541.46875 C 609.33888,542.08656 608.98681,542.70326 608.625,543.3125 L 625.90625,553.28125 L 626.96875,551.4375 L 609.6875,541.46875 z M 477.0625,577 L 467.09375,594.28125 L 468.9375,595.34375 L 478.90625,578.0625 C 478.28844,577.71388 477.67174,577.36181 477.0625,577 z M 574.9375,577 C 574.32826,577.36181 573.71156,577.71388 573.09375,578.0625 L 583.0625,595.34375 L 584.90625,594.28125 L 574.9375,577 z M 523.53125,586.625 L 523.53125,610.34375 L 528.46875,610.34375 L 528.46875,586.625 C 526.8231,586.67602 525.17696,586.65422 523.53125,586.625 z" + id="path19427" + sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <path + d="M 440,288.36218 A 138,138 0 1 1 164,288.36218 A 138,138 0 1 1 440,288.36218 z" + id="path19429" + sodipodi:cx="302" + sodipodi:cy="288.36218" + sodipodi:rx="138" + sodipodi:ry="138" + sodipodi:type="arc" + style="fill:url(#linearGradient19441);fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="matrix(0.728261,0,0,0.601449,306.065,286.927)" /> + </g> + </g> +</svg> diff --git a/tests/constraintstests.py b/tests/constraintstests.py index 4e19a92..a5ccf26 100644 --- a/tests/constraintstests.py +++ b/tests/constraintstests.py @@ -240,5 +240,47 @@ class FileConstraintTest(unittest.TestCase): except FileConstraintError: pass +class ResourceConstraintTest(unittest.TestCase): + def test_valid_names(self): + name1 = "file_" + unicode(uuid.uuid1()) + ".png" + name2 = unicode(uuid.uuid1()) + "_" + unicode(uuid.uuid1()) + ".extension" + name3 = "/home/user/.sugar/_random/new_image1231_" + unicode(uuid.uuid1()).upper() + ".mp3" + name4 = "a_" + unicode(uuid.uuid1()) + name5 = "" + + cons = ResourceConstraint() + + # All of those names should pass without exceptions + cons.validate(name1) + cons.validate(name2) + cons.validate(name3) + cons.validate(name4) + cons.validate(name5) + + def test_invalid_names(self): + bad_name1 = ".jpg" + bad_name2 = "_.jpg" + bad_name3 = "_" + unicode(uuid.uuid1()) + + cons = ResourceConstraint() + + try: + cons.validate(bad_name1) + assert False, "%s should not be a valid resource name" % bad_name1 + except ResourceConstraintError: + pass + + try: + cons.validate(bad_name2) + assert False, "%s should not be a valid resource name" % bad_name2 + except ResourceConstraintError: + pass + + try: + cons.validate(bad_name3) + assert False, "%s should not be a valid resource name" % bad_name3 + except ResourceConstraintError: + pass + if __name__ == "__main__": unittest.main() diff --git a/tests/enginetests.py b/tests/enginetests.py new file mode 100644 index 0000000..60a68f4 --- /dev/null +++ b/tests/enginetests.py @@ -0,0 +1,30 @@ +# 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 + +import dbus + +session_bus = dbus.SessionBus() + +ENGINE_BUS_NAME = "org.tutorius.engine" + +LAUNCH_PATH = "/launch" +STOP_PATH = "/stop" +PAUSE_PATH = "/pause" + +class EngineInterfaceTests(unittest.TestCase): + pass diff --git a/tests/inject.py b/tests/inject.py new file mode 100644 index 0000000..d69d6ff --- /dev/null +++ b/tests/inject.py @@ -0,0 +1,57 @@ +#Test event injection + +import gtk +import gobject +import time +import types + +class ClickMaster(): + def __init__(self): + self.event = None + + def connect(self, button): + self.id = button.connect("pressed",self.capture_event) + self.id2 = button.connect("released",self.capture_event2) + self.id3 = button.connect("clicked",self.capture_event3) + self.button = button + + def capture_event(self, *args): + print "Capture Event" + print args + self.eventPress = args[-1] + return False + + def capture_event2(self, *args): + print "Capture Release" + print args + self.eventReleased = args[-1] + return False + + def capture_event3(self, *args): + print "Capture Clicked" + print args + self.eventClicked = args[-1] + return False + + def inject_event(self): + print "Injecting" + print self.event + #self.event.put() + self.button.emit("button_press_event", self.event) + +def print_Event(event): + for att in dir(event): + if not isinstance(att, types.MethodType): + print att, getattr(event, att) + +if __name__=='__main__': + w = gtk.Window() + b = gtk.CheckButton("Auto toggle!") + c=ClickMaster() + w.add(b) + b.show() + c.connect(b) + + w.show() + + gtk.main() diff --git a/tests/propertiestests.py b/tests/propertiestests.py index 2494ea6..f07bd43 100644 --- a/tests/propertiestests.py +++ b/tests/propertiestests.py @@ -579,6 +579,63 @@ class TAddonPropertyList(unittest.TestCase): obj1.addonlist = [klass1(), klass1(), wrongAddon(), klass1()] except ValueError: pass + +class TResourcePropertyTest(unittest.TestCase): + def test_valid_names(self): + class klass1(TPropContainer): + res = TResourceProperty() + + name1 = "file_" + unicode(uuid.uuid1()) + ".png" + name2 = unicode(uuid.uuid1()) + "_" + unicode(uuid.uuid1()) + ".extension" + name3 = "/home/user/.sugar/_random/new_image1231_" + unicode(uuid.uuid1()).upper() + ".mp3" + name4 = "a_" + unicode(uuid.uuid1()) + name5 = "" + + obj1 = klass1() + + obj1.res = name1 + assert obj1.res == name1, "Could not assign the valid name correctly : %s" % name1 + + obj1.res = name2 + assert obj1.res == name2, "Could not assign the valid name correctly : %s" % name2 + + obj1.res = name3 + assert obj1.res == name3, "Could not assign the valid name correctly : %s" % name3 + + obj1.res = name4 + assert obj1.res == name4, "Could not assign the valid name correctly : %s" % name4 + + obj1.res = name5 + assert obj1.res == name5, "Could not assign the valid name correctly : %s" % name5 + + def test_invalid_names(self): + class klass1(TPropContainer): + res = TResourceProperty() + + bad_name1 = ".jpg" + bad_name2 = "_.jpg" + bad_name3 = "_" + unicode(uuid.uuid1()) + + obj1 = klass1() + + try: + obj1.res = bad_name1 + assert False, "A invalid name was accepted : %s" % bad_name1 + except ResourceConstraintError: + pass + + try: + obj1.res = bad_name2 + assert False, "A invalid name was accepted : %s" % bad_name2 + except ResourceConstraintError: + pass + + try: + obj1.res = bad_name3 + assert False, "A invalid name was accepted : %s" % bad_name3 + except ResourceConstraintError: + pass + if __name__ == "__main__": unittest.main() diff --git a/tests/translatortests.py b/tests/translatortests.py new file mode 100644 index 0000000..3b5ca6f --- /dev/null +++ b/tests/translatortests.py @@ -0,0 +1,131 @@ +# 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 +import os +import uuid + +from sugar.tutorius.translator import * +from sugar.tutorius.properties import * +from sugar.tutorius.tutorial import * +from sugar.tutorius.vault import Vault +from sugar.tutorius import addon + +############################################################################## +## Helper classes +class ResourceAction(TPropContainer): + resource = TResourceProperty() + + def __init__(self): + TPropContainer.__init__(self) + +class NestedResource(TPropContainer): + nested = TAddonProperty() + + def __init__(self): + TPropContainer.__init__(self) + self.nested = ResourceAction() + +class ListResources(TPropContainer): + nested_list = TAddonListProperty() + + def __init__(self): + TPropContainer.__init__(self) + self.nested_list = [ResourceAction(), ResourceAction()] + +## +############################################################################## + +class ResourceTranslatorTests(unittest.TestCase): + temp_path = "/tmp/" + file_name = "file.txt" + + def setUp(self): + # Generate a tutorial ID + self.tutorial_id = unicode(uuid.uuid1()) + + # Create a dummy fsm + self.fsm = Tutorial("TestTutorial1") + # Add a few states + act1 = addon.create('BubbleMessage', message="Hi", position=[300, 450]) + ev1 = addon.create('GtkWidgetEventFilter', "0.12.31.2.2", "clicked") + act2 = addon.create('BubbleMessage', message="Second message", position=[250, 150], tail_pos=[1,2]) + self.fsm.add_action("INIT", act1) + st2 = self.fsm.add_state((act2,)) + self.fsm.add_transition("INIT",(ev1, st2)) + + # Create a dummy metadata dictionnary + self.test_metadata_dict = {} + self.test_metadata_dict['name'] = 'TestTutorial1' + self.test_metadata_dict['guid'] = unicode(self.tutorial_id) + self.test_metadata_dict['version'] = '1' + self.test_metadata_dict['description'] = 'This is a test tutorial 1' + self.test_metadata_dict['rating'] = '3.5' + self.test_metadata_dict['category'] = 'Test' + self.test_metadata_dict['publish_state'] = 'false' + activities_dict = {} + activities_dict['org.laptop.tutoriusactivity'] = '1' + activities_dict['org.laptop,writus'] = '1' + self.test_metadata_dict['activities'] = activities_dict + + Vault.saveTutorial(self.fsm, self.test_metadata_dict) + + try: + os.mkdir(self.temp_path) + except: + pass + abs_file_path = os.path.join(self.temp_path, self.file_name) + new_file = file(abs_file_path, "w") + + # Add the resource in the Vault + self.res_name = Vault.add_resource(self.tutorial_id, abs_file_path) + + # Use a dummy prob manager - we shouldn't be using it + self.prob_man = object() + + self.translator = ResourceTranslator(self.prob_man, self.tutorial_id) + + def tearDown(self): + Vault.deleteTutorial(self.tutorial_id) + + os.unlink(os.path.join(self.temp_path, self.file_name)) + + def test_translate(self): + # Create an action with a resource property + res_action = ResourceAction() + res_action.resource = self.res_name + + self.translator.translate(res_action) + + assert getattr(res_action, "resource").type == "file", "Resource was not converted to file" + + assert res_action.resource.default == Vault.get_resource_path(self.tutorial_id, self.res_name), "Transformed resource path is not the same as the one given by the vault" + + def test_recursive_translate(self): + nested_action = NestedResource() + + self.translator.translate(nested_action) + + assert getattr(getattr(nested_action, "nested"), "resource").type == "file", "Nested resource was not converted properly" + + def test_list_translate(self): + list_action = ListResources() + + self.translator.translate(list_action) + + for container in list_action.nested_list: + assert getattr(container, "resource").type == "file", "Element of list was not converted properly" + diff --git a/tests/vaulttests.py b/tests/vaulttests.py index c6bd852..0af2d65 100644 --- a/tests/vaulttests.py +++ b/tests/vaulttests.py @@ -266,26 +266,17 @@ class VaultInterfaceTest(unittest.TestCase): and return the new resource id. It also test the deletion of the resource. """ # Path of an image file in the test folder - test_path = os.path.join(os.getenv("HOME"),".sugar", 'default', 'tutorius', 'tmp') - if os.path.isdir(test_path) == True: - shutil.rmtree(os.path.join(os.getenv("HOME"),".sugar", 'default', 'tutorius', 'tmp')) - os.makedirs(test_path) - file_path = os.path.join(test_path, 'test_resource.svg') - resource_file = open(file_path, 'wt') - resource_file.write('test') - resource_file.close() - #image_path = os.path.join(os.getcwd(), 'tests', 'resources', 'icon.svg') - #assert os.path.isfile(image_path), 'cannot find the test image file' - + image_path = os.path.join(os.getcwd(), 'tests', 'ressources', 'icon.svg') + assert os.path.isfile(image_path), 'cannot find the test image file' + # Create and save a tutorial - tutorial = Tutorial('test') - Vault.saveTutorial(tutorial, self.test_metadata_dict) + Vault.saveTutorial(self.fsm, self.test_metadata_dict) bundler = TutorialBundler(self.save_test_guid) tuto_path = bundler.get_tutorial_path(self.save_test_guid) - # add the resource to the tutorial - resource_id = Vault.add_resource(self.save_test_guid, file_path) + # add the ressource to the tutorial + ressource_id = Vault.add_resource(self.save_test_guid, image_path) # Check that the image file is now in the vault assert os.path.isfile(os.path.join(tuto_path, 'resources', resource_id)), 'image file not found in vault' diff --git a/tutorius/actions.py b/tutorius/actions.py index bb15459..d5a8641 100644 --- a/tutorius/actions.py +++ b/tutorius/actions.py @@ -81,6 +81,7 @@ class DragWrapper(object): """Callback for end of drag (stolen focus).""" self._dragging = False + def set_draggable(self, value): """Setter for the draggable property""" if bool(value) ^ bool(self._drag_on): diff --git a/tutorius/constraints.py b/tutorius/constraints.py index 519bce8..cd71167 100644 --- a/tutorius/constraints.py +++ b/tutorius/constraints.py @@ -24,6 +24,8 @@ for some properties. # For the File Constraint import os +# For the Resource Constraint +import re class ConstraintException(Exception): """ @@ -214,3 +216,40 @@ class FileConstraint(Constraint): raise FileConstraintError("Non-existing file : %s"%value) return +class ResourceConstraintError(ConstraintException): + pass + +class ResourceConstraint(Constraint): + """ + Ensures that the value is looking like a resource name, like + <filename>_<GUID>[.<extension>]. We are not validating that this is a + valid resource for the reason that the property does not have any notion + of tutorial guid. + + TODO : Find a way to properly validate resources by looking them up in the + Vault. + """ + + # Regular expression to parse a resource-like name + resource_regexp_text = "(.+)_([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})(\..*)?$" + resource_regexp = re.compile(resource_regexp_text) + + def validate(self, value): + # TODO : Validate that we will not use an empty resource or if we can + # have transitory resource names + if value is None: + raise ResourceConstraintError("Resource not allowed to have a null value!") + + # Special case : We allow the empty resource name for now + if value == "": + return value + + # Attempt to see if the value has a resource name inside it + match = self.resource_regexp.search(value) + + # If there was no match on the reg exp + if not match: + raise ResourceConstraintError("Resource name does not seem to be valid : %s" % value) + + # If the name matched, then the value is _PROBABLY_ good + return value diff --git a/tutorius/core.py b/tutorius/core.py index bfbe07b..80e1b4f 100644 --- a/tutorius/core.py +++ b/tutorius/core.py @@ -616,3 +616,25 @@ class FiniteStateMachine(State): # If we made it here, then all the states in this FSM could be matched to an # identical state in the other FSM. return True + if len(self._states) != len(otherFSM._states): + return False + + # For each state, try to find a corresponding state in the other FSM + for state_name in self._states.keys(): + state = self._states[state_name] + other_state = None + try: + # Attempt to use this key in the other FSM. If it's not present + # the dictionary will throw an exception and we'll know we have + # at least one different state in the other FSM + other_state = otherFSM._states[state_name] + except: + return False + # If two states with the same name exist, then we want to make sure + # they are also identical + if not state == other_state: + return False + + # If we made it here, then all the states in this FSM could be matched to an + # identical state in the other FSM. + return True diff --git a/tutorius/editor_interpreter.py b/tutorius/editor_interpreter.py new file mode 100644 index 0000000..d559266 --- /dev/null +++ b/tutorius/editor_interpreter.py @@ -0,0 +1,105 @@ +# Copyright (C) 2009, Tutorius.org +# Greatly influenced by sugar/activity/namingalert.py +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +""" Tutorial Editor Interpreter Module +""" + +import gtk +import pango +from sugar.tutorius.ipython_view import * + +from gettext import gettext as _ + +class EditorInterpreter(gtk.Window): + """ + Interpreter that will be shown to the user + """ + __gtype_name__ = 'TutoriusEditorInterpreter' + + def __init__(self, activity=None): + gtk.Window.__init__(self) + + self._activity = activity + + # Set basic properties of window + self.set_decorated(False) + self.set_resizable(False) + self.set_modal(False) + + # Connect to realize signal from ? + self.connect('realize', self.__realize_cb) + + # Use an expander widget to allow minimizing + self._expander = gtk.Expander(_("Editor Interpreter")) + self._expander.set_expanded(True) + self.add(self._expander) + self._expander.connect("notify::expanded", self.__expander_cb) + + + # Use the IPython widget to embed + self.interpreter = IPythonView() + self.interpreter.set_wrap_mode(gtk.WRAP_CHAR) + + # Expose the activity object in the interpreter + self.interpreter.updateNamespace({'activity':self._activity}) + + # Use a scroll window to permit scrolling of the interpreter prompt + swd = gtk.ScrolledWindow() + swd.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + swd.add(self.interpreter) + self.interpreter.show() + + # Notify GTK that expander is ready + self._expander.add(swd) + self._expander.show() + + # Notify GTK that the scrolling window is ready + swd.show() + + def __expander_cb(self, *args): + """Callback for the window expander toggling""" + if self._expander.get_expanded(): + self.__move_expanded() + else: + self.__move_collapsed() + + def __move_expanded(self): + """Move the window to it's expanded position""" + swidth = gtk.gdk.screen_width() + sheight = gtk.gdk.screen_height() + # leave room for the scrollbar at the right + width = swidth - 20 + height = 200 + + self.set_size_request(width, height) + # Put at the bottom of the screen + self.move(0, sheight-height) + + def __move_collapsed(self): + """Move the window to it's collapsed position""" + width = 150 + height = 40 + swidth = gtk.gdk.screen_width() + sheight = gtk.gdk.screen_height() + + self.set_size_request(width, height) + self.move(((swidth-width)/2)-150, sheight-height) + + def __realize_cb(self, widget): + """Callback for realize""" + self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) + self.window.set_accept_focus(True) + self.__move_expanded() diff --git a/tutorius/events.py b/tutorius/events.py new file mode 100644 index 0000000..bf0a8b9 --- /dev/null +++ b/tutorius/events.py @@ -0,0 +1,36 @@ +from sugar.tutorius.properties import * + +class Event(TPropContainer): + source = TUAMProperty() + type = TStringProperty("clicked") + + def __init__(self): + TPropContainer.__init__(self) + + + # Providing the hash methods necessary to use events as key + # in a dictionary, if new properties are added we should + # take them into account here + def __hash__(self): + return hash(str(self.source) + str(self.type)) + + def __eq__(self, e2): + return self.source == e2.source and self.type == e2.type + + + # Adding methods for pickling and unpickling an object with + # properties + def __getstate__(self): + return self._props.copy() + + def __setstate__(self, dict): + self._props.update(dict) + + +# Nothing more needs to be added, the additional +# information is in the object type +class ClickedEvent(Event): + def __init__(self): + Event.__init__(self) + self.type = "clicked" + diff --git a/tutorius/ipython_view.py b/tutorius/ipython_view.py new file mode 100644 index 0000000..c4294d0 --- /dev/null +++ b/tutorius/ipython_view.py @@ -0,0 +1,301 @@ +""" +Backend to the console plugin. + +@author: Eitan Isaacson +@organization: IBM Corporation +@copyright: Copyright (c) 2007 IBM Corporation +@license: BSD + +All rights reserved. This program and the accompanying materials are made +available under the terms of the BSD which accompanies this distribution, and +is available at U{http://www.opensource.org/licenses/bsd-license.php} +""" +# this file is a modified version of source code from the Accerciser project +# http://live.gnome.org/accerciser + +import gtk +import re +import sys +import os +import pango +from StringIO import StringIO + +try: + import IPython +except Exception,e: + raise "Error importing IPython (%s)" % str(e) + +ansi_colors = {'0;30': 'Black', + '0;31': 'Red', + '0;32': 'Green', + '0;33': 'Brown', + '0;34': 'Blue', + '0;35': 'Purple', + '0;36': 'Cyan', + '0;37': 'LightGray', + '1;30': 'DarkGray', + '1;31': 'DarkRed', + '1;32': 'SeaGreen', + '1;33': 'Yellow', + '1;34': 'LightBlue', + '1;35': 'MediumPurple', + '1;36': 'LightCyan', + '1;37': 'White'} + +class IterableIPShell: + def __init__(self,argv=None,user_ns=None,user_global_ns=None, + cin=None, cout=None,cerr=None, input_func=None): + if input_func: + IPython.iplib.raw_input_original = input_func + if cin: + IPython.Shell.Term.cin = cin + if cout: + IPython.Shell.Term.cout = cout + if cerr: + IPython.Shell.Term.cerr = cerr + + if argv is None: + argv=[] + + # This is to get rid of the blockage that occurs during + # IPython.Shell.InteractiveShell.user_setup() + IPython.iplib.raw_input = lambda x: None + + self.term = IPython.genutils.IOTerm(cin=cin, cout=cout, cerr=cerr) + os.environ['TERM'] = 'dumb' + excepthook = sys.excepthook + self.IP = IPython.Shell.make_IPython(argv,user_ns=user_ns, + user_global_ns=user_global_ns, + embedded=True, + shell_class=IPython.Shell.InteractiveShell) + self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd), + header='IPython system call: ', + verbose=self.IP.rc.system_verbose) + sys.excepthook = excepthook + self.iter_more = 0 + self.history_level = 0 + self.complete_sep = re.compile('[\s\{\}\[\]\(\)]') + + def execute(self): + self.history_level = 0 + orig_stdout = sys.stdout + sys.stdout = IPython.Shell.Term.cout + try: + line = self.IP.raw_input(None, self.iter_more) + if self.IP.autoindent: + self.IP.readline_startup_hook(None) + except KeyboardInterrupt: + self.IP.write('\nKeyboardInterrupt\n') + self.IP.resetbuffer() + # keep cache in sync with the prompt counter: + self.IP.outputcache.prompt_count -= 1 + + if self.IP.autoindent: + self.IP.indent_current_nsp = 0 + self.iter_more = 0 + except: + self.IP.showtraceback() + else: + self.iter_more = self.IP.push(line) + if (self.IP.SyntaxTB.last_syntax_error and + self.IP.rc.autoedit_syntax): + self.IP.edit_syntax_error() + if self.iter_more: + self.prompt = str(self.IP.outputcache.prompt2).strip() + if self.IP.autoindent: + self.IP.readline_startup_hook(self.IP.pre_readline) + else: + self.prompt = str(self.IP.outputcache.prompt1).strip() + sys.stdout = orig_stdout + + def historyBack(self): + self.history_level -= 1 + return self._getHistory() + + def historyForward(self): + self.history_level += 1 + return self._getHistory() + + def _getHistory(self): + try: + rv = self.IP.user_ns['In'][self.history_level].strip('\n') + except IndexError: + self.history_level = 0 + rv = '' + return rv + + def updateNamespace(self, ns_dict): + self.IP.user_ns.update(ns_dict) + + def complete(self, line): + split_line = self.complete_sep.split(line) + possibilities = self.IP.complete(split_line[-1]) + if possibilities: + common_prefix = reduce(self._commonPrefix, possibilities) + completed = line[:-len(split_line[-1])]+common_prefix + else: + completed = line + return completed, possibilities + + def _commonPrefix(self, str1, str2): + for i in range(len(str1)): + if not str2.startswith(str1[:i+1]): + return str1[:i] + return str1 + + def shell(self, cmd,verbose=0,debug=0,header=''): + stat = 0 + if verbose or debug: print header+cmd + # flush stdout so we don't mangle python's buffering + if not debug: + input, output = os.popen4(cmd) + print output.read() + output.close() + input.close() + +class ConsoleView(gtk.TextView): + def __init__(self): + gtk.TextView.__init__(self) + self.modify_font(pango.FontDescription('Mono')) + self.set_cursor_visible(True) + self.text_buffer = self.get_buffer() + self.mark = self.text_buffer.create_mark('scroll_mark', + self.text_buffer.get_end_iter(), + False) + for code in ansi_colors: + self.text_buffer.create_tag(code, + foreground=ansi_colors[code], + weight=700) + self.text_buffer.create_tag('0') + self.text_buffer.create_tag('notouch', editable=False) + self.color_pat = re.compile('\x01?\x1b\[(.*?)m\x02?') + self.line_start = \ + self.text_buffer.create_mark('line_start', + self.text_buffer.get_end_iter(), True + ) + self.connect('key-press-event', self._onKeypress) + self.last_cursor_pos = 0 + + def write(self, text, editable=False): + segments = self.color_pat.split(text) + segment = segments.pop(0) + start_mark = self.text_buffer.create_mark(None, + self.text_buffer.get_end_iter(), + True) + self.text_buffer.insert(self.text_buffer.get_end_iter(), segment) + + if segments: + ansi_tags = self.color_pat.findall(text) + for tag in ansi_tags: + i = segments.index(tag) + self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(), + segments[i+1], tag) + segments.pop(i) + if not editable: + self.text_buffer.apply_tag_by_name('notouch', + self.text_buffer.get_iter_at_mark(start_mark), + self.text_buffer.get_end_iter()) + self.text_buffer.delete_mark(start_mark) + self.scroll_mark_onscreen(self.mark) + + def showPrompt(self, prompt): + self.write(prompt) + self.text_buffer.move_mark(self.line_start,self.text_buffer.get_end_iter()) + + def changeLine(self, text): + iter = self.text_buffer.get_iter_at_mark(self.line_start) + iter.forward_to_line_end() + self.text_buffer.delete(self.text_buffer.get_iter_at_mark(self.line_start), iter) + self.write(text, True) + + def getCurrentLine(self): + rv = self.text_buffer.get_slice(self.text_buffer.get_iter_at_mark(self.line_start), + self.text_buffer.get_end_iter(), False) + return rv + + def showReturned(self, text): + iter = self.text_buffer.get_iter_at_mark(self.line_start) + iter.forward_to_line_end() + self.text_buffer.apply_tag_by_name('notouch', + self.text_buffer.get_iter_at_mark(self.line_start), + iter) + self.write('\n'+text) + if text: + self.write('\n') + self.showPrompt(self.prompt) + self.text_buffer.move_mark(self.line_start,self.text_buffer.get_end_iter()) + self.text_buffer.place_cursor(self.text_buffer.get_end_iter()) + + def _onKeypress(self, obj, event): + if not event.string: + return + insert_mark = self.text_buffer.get_insert() + insert_iter = self.text_buffer.get_iter_at_mark(insert_mark) + selection_mark = self.text_buffer.get_selection_bound() + selection_iter = self.text_buffer.get_iter_at_mark(selection_mark) + start_iter = self.text_buffer.get_iter_at_mark(self.line_start) + if start_iter.compare(insert_iter) <= 0 and \ + start_iter.compare(selection_iter) <= 0: + return + elif start_iter.compare(insert_iter) > 0 and \ + start_iter.compare(selection_iter) > 0: + self.text_buffer.place_cursor(start_iter) + elif insert_iter.compare(selection_iter) < 0: + self.text_buffer.move_mark(insert_mark, start_iter) + elif insert_iter.compare(selection_iter) > 0: + self.text_buffer.move_mark(selection_mark, start_iter) + + +class IPythonView(ConsoleView, IterableIPShell): + def __init__(self): + ConsoleView.__init__(self) + self.cout = StringIO() + IterableIPShell.__init__(self, cout=self.cout,cerr=self.cout, + input_func=self.raw_input) + self.connect('key_press_event', self.keyPress) + self.execute() + self.cout.truncate(0) + self.showPrompt(self.prompt) + self.interrupt = False + + def raw_input(self, prompt=''): + if self.interrupt: + self.interrupt = False + raise KeyboardInterrupt + return self.getCurrentLine() + + def keyPress(self, widget, event): + if event.state & gtk.gdk.CONTROL_MASK and event.keyval == 99: + self.interrupt = True + self._processLine() + return True + elif event.keyval == gtk.keysyms.Return: + self._processLine() + return True + elif event.keyval == gtk.keysyms.Up: + self.changeLine(self.historyBack()) + return True + elif event.keyval == gtk.keysyms.Down: + self.changeLine(self.historyForward()) + return True + elif event.keyval == gtk.keysyms.Tab: + if not self.getCurrentLine().strip(): + return False + completed, possibilities = self.complete(self.getCurrentLine()) + if len(possibilities) > 1: + slice = self.getCurrentLine() + self.write('\n') + for symbol in possibilities: + self.write(symbol+'\n') + self.showPrompt(self.prompt) + self.changeLine(completed or slice) + return True + + def _processLine(self): + self.history_pos = 0 + self.execute() + rv = self.cout.getvalue() + if rv: rv = rv.strip('\n') + self.showReturned(rv) + self.cout.truncate(0) + diff --git a/tutorius/properties.py b/tutorius/properties.py index 5422532..c7af821 100644 --- a/tutorius/properties.py +++ b/tutorius/properties.py @@ -24,7 +24,8 @@ from copy import copy, deepcopy from .constraints import Constraint, \ UpperLimitConstraint, LowerLimitConstraint, \ MaxSizeConstraint, MinSizeConstraint, \ - ColorConstraint, FileConstraint, BooleanConstraint, EnumConstraint + ColorConstraint, FileConstraint, BooleanConstraint, EnumConstraint, \ + ResourceConstraint class TPropContainer(object): """ @@ -89,6 +90,21 @@ class TPropContainer(object): except AttributeError: return object.__setattr__(self, name, value) + def replace_property(self, prop_name, new_prop): + """ + Changes the content of a property. This is done in order to support + the insertion of executable properties in the place of a portable + property. The typical exemple is that a resource property needs to + be changed into a file property with the correct file name, since the + installation location will be different on every platform. + + @param prop_name The name of the property to be changed + @param new_prop The new property to insert + @raise AttributeError of the mentionned property doesn't exist + """ + props = object.__getattribute__(self, "_props") + props.__setitem__(prop_name, new_prop) + def get_properties(self): """ Return the list of property names. @@ -261,8 +277,6 @@ class TFileProperty(TutoriusProperty): 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) @@ -351,7 +365,7 @@ class TEventType(TutoriusProperty): class TAddonListProperty(TutoriusProperty): """ - Reprensents an embedded tutorius Addon List Component. + Represents an embedded tutorius Addon List Component. See TAddonProperty """ def __init__(self): @@ -367,3 +381,34 @@ class TAddonListProperty(TutoriusProperty): return value raise ValueError("Value proposed to TAddonListProperty is not a list") +class TResourceProperty(TutoriusProperty): + """ + Represents a resource in the tutorial. A resource is a file with a specific + name that exists under the tutorials folder. It is distributed alongside the + tutorial itself. + + When the system encounters a resource, it knows that it refers to a file in + the resource folder and that it should translate this resource name to an + absolute file name before it is executed. + + E.g. An image is added to a tutorial in an action. On doing so, the creator + adds a resource to the tutorial, then saves its name in the resource + property of that action. When this tutorial is executed, the Engine + replaces all the TResourceProperties inside the action by their equivalent + TFileProperties with absolute paths, so that they can be used on any + machine. + """ + def __init__(self, resource_name=""): + """ + Creates a new resource pointing to an existing resource. + + @param resource_name The file name of the resource (should be only the + file name itself, no directory information) + """ + TutoriusProperty.__init__(self) + self.type = "resource" + + self.resource_cons = ResourceConstraint() + + self.default = self.validate("") + diff --git a/tutorius/translator.py b/tutorius/translator.py new file mode 100644 index 0000000..626e2c9 --- /dev/null +++ b/tutorius/translator.py @@ -0,0 +1,186 @@ +# 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 os +import logging +import copy + +logger = logging.getLogger("ResourceTranslator") + +from .properties import * +# TODO : Uncomment this line upon integration with the Vault +from .vault import Vault + +class ResourceTranslator(object): + """ + Handles the conversion of resource properties into file + properties before action execution. This class works as a decorator + to the ProbeManager class, as it is meant to be a transparent layer + before sending the action to execution. + + An architectural note : every different type of translation should have its + own method (translate_resource, etc...), and this function must be called + from the translate method, under the type test. The translate_* method + must take in the input property and give the output property that should + replace it. + """ + + def __init__(self, probe_manager, tutorial_id): + """ + Creates a new ResourceTranslator for the given tutorial. This + translator is tasked with replacing resource properties of the + incoming action into actually usable file properties pointing + to the correct resource file. This is done by querying the vault + for all the resources and creating a new file property from the + returned path. + + @param probe_manager The probe manager to decorate + @param tutorial_id The ID of the current tutorial + """ + self._probe_manager = probe_manager + self._tutorial_id = tutorial_id + + def translate_resource(self, res_value): + """ + Replace the TResourceProperty in the container by their + runtime-defined file equivalent. Since the resources are stored + in a relative manner in the vault and that only the Vault knows + which is the current folder for the current tutorial, it needs + to transform the resource identifier into the absolute path for + the process to be able to use it properly. + + @param res_prop The resource property's value to be translated + @return The TFileProperty corresponding to this resource, containing + an absolute path to the resource + """ + # We need to replace the resource by a file representation + filepath = Vault.get_resource_path(self._tutorial_id, res_value) + + # Create the new file representation + file_prop = TFileProperty(filepath) + + return file_prop + + def translate(self, prop_container): + """ + Applies the required translation to be able to send the container to an + executing endpoint (like a Probe). For each type of property that + requires it, there is translation function that will take care of + mapping the property to its executable form. + + This function does not return anything, but its post-condition is + that all the properties of the input container have been replaced + by their corresponding executable versions. + + An example of translation is taking a resource (a relative path to + a file under the tutorial folder) and transforming it into a file + (a full absolute path) to be able to load it when the activity receives + the action. + + @param prop_container The property container in which we want to + replace all the resources for file properties and + to recursively do so for addon and addonlist + properties. + """ + for propname in prop_container.get_properties(): + prop_value = getattr(prop_container, propname) + prop_type = getattr(type(prop_container), propname).type + + # If the property is a resource, then we need to query the + # vault to create its correspondent + if prop_type == "resource": + # Apply the translation + file_prop = self.translate_resource(prop_value) + # Set the property with the new value + prop_container.replace_property(propname, file_prop) + + # If the property is an addon, then its value IS a + # container too - we need to translate it + elif prop_type == "addon": + # Translate the sub properties + self.translate(prop_value) + + # If the property is an addon list, then we need to translate all + # the elements of the list + elif prop_type == "addonlist": + # Now, for each sub-container in the list, we will apply the + # translation processing. This is done by popping the head of + # the list, translating it and inserting it back at the end. + for index in range(0, len(prop_value)): + # Pop the head of the list + container = prop_value[0] + del prop_value[0] + # Translate the sub-container + self.translate(container) + + # Put the value back in the list + prop_value.append(container) + # Change the list contained in the addonlist property, since + # we got a copy of the list when requesting it + prop_container.replace_property(propname, prop_value) + + ### ProbeManager interface for decorator ### + + ## Unchanged functions ## + def setCurrentActivity(self, activity_id): + self._probe_manager.setCurrentActivity(activity_id) + + def getCurrentActivity(self): + return self._probe_manager.getCurrentActivity() + + currentActivity = property(fget=getCurrentActivity, fset=setCurrentActivity) + def attach(self, activity_id): + self._probe_manager.attach(activity_id) + + def detach(self, activity_id): + self._probe_manager.detach(activity_id) + + def subscribe(self, event, callback): + return self._probe_manager.subscribe(event, callback) + + def unsubscribe(self, event, callback): + return self._probe_manager.unsubscribe(event, callback) + + def unsubscribe_all(self): + return self._probe_manager.unsubscribe_all() + + ## Decorated functions ## + def install(self, action): + # Make a new copy of the action that we want to install, + # because translate() changes the action and we + # don't want to modify the caller's action representation + new_action = copy.deepcopy(action) + # Execute the replacement + self.translate(new_action) + + # Send the new action to the probe manager + return self._probe_manager.install(new_action) + + def update(self, action): + new_action = copy.deepcopy(action) + self.translate(new_action) + + return self._probe_manager.update(new_action) + + def uninstall(self, action): + new_action = copy.deepcopy(action) + self.translate(new_action) + + return self._probe_manager.uninstall(new_action) + + def uninstall_all(self): + return self._probe_manager.uninstall_all() + diff --git a/tutorius/vault.py b/tutorius/vault.py index 7ec0a23..4d817ec 100644 --- a/tutorius/vault.py +++ b/tutorius/vault.py @@ -325,14 +325,14 @@ class Vault(object): @staticmethod - def deleteTutorial(Guid): + def deleteTutorial(tutorial_id): """ Removes the tutorial from the Vault. It will unpublish the tutorial if need be, and it will also wipe it from the persistent storage. @returns true is the tutorial was deleted from the Vault """ - bundle = TutorialBundler(Guid) - bundle_path = bundle.get_tutorial_path(Guid) + bundle = TutorialBundler(tutorial_id) + bundle_path = bundle.get_tutorial_path(tutorial_id) # TODO : Need also to unpublish tutorial, need to interact with webservice module @@ -982,9 +982,3 @@ class TutorialBundler(object): xml_filename = config.get(INI_METADATA_SECTION, INI_XML_FSM_PROPERTY) serializer.save_tutorial(fsm, xml_filename, self.Path) - @staticmethod - def add_resources(typename, file): - """ - Add resources to metadata. - """ - raise NotImplementedError("add_resources not implemented") |