Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--addons/bubblemessage.py14
-rw-r--r--addons/dialogmessage.py6
-rw-r--r--data/icons/chain.svg65
-rw-r--r--data/icons/clock.svg269
-rw-r--r--data/icons/once_wrapper.svg74
-rw-r--r--tests/actiontests.py169
-rw-r--r--tests/bundlertests.py77
-rw-r--r--tests/constraintstests.py15
-rw-r--r--tests/coretests.py195
-rw-r--r--tests/filterstests.py6
-rw-r--r--tests/linear_creatortests.py2
-rw-r--r--tests/propertiestests.py23
-rw-r--r--tests/serializertests.py37
-rw-r--r--tutorius/actions.py284
-rw-r--r--tutorius/addon.py6
-rw-r--r--tutorius/bundler.py201
-rw-r--r--tutorius/core.py76
-rw-r--r--tutorius/filters.py214
-rw-r--r--tutorius/properties.py42
-rw-r--r--tutorius/uam/__init__.py3
20 files changed, 1280 insertions, 498 deletions
diff --git a/addons/bubblemessage.py b/addons/bubblemessage.py
index a859ef8..c499bdb 100644
--- a/addons/bubblemessage.py
+++ b/addons/bubblemessage.py
@@ -22,22 +22,22 @@ class BubbleMessage(Action):
# Do the same for the tail position
tail_pos = TArrayProperty([0,0], 2, 2)
- def __init__(self, message=None, pos=None, speaker=None, tailpos=None):
+ def __init__(self, message=None, position=None, speaker=None, tail_pos=None):
"""
Shows a dialog with a given text, at the given position on the screen.
@param message A string to display to the user
- @param pos A list of the form [x, y]
+ @param position A list of the form [x, y]
@param speaker treeish representation of the speaking widget
- @param tailpos The position of the tail of the bubble; useful to point to
+ @param tail_pos The position of the tail of the bubble; useful to point to
specific elements of the interface
"""
Action.__init__(self)
- if pos:
- self.position = pos
- if tailpos:
- self.tail_pos = tailpos
+ if position:
+ self.position = position
+ if tail_pos:
+ self.tail_pos = tail_pos
if message:
self.message = message
diff --git a/addons/dialogmessage.py b/addons/dialogmessage.py
index 22a223b..298466a 100644
--- a/addons/dialogmessage.py
+++ b/addons/dialogmessage.py
@@ -22,19 +22,19 @@ class DialogMessage(Action):
message = TStringProperty("Message")
position = TArrayProperty([0, 0], 2, 2)
- def __init__(self, message=None, pos=None):
+ def __init__(self, message=None, position=None):
"""
Shows a dialog with a given text, at the given position on the screen.
@param message A string to display to the user
- @param pos A list of the form [x, y]
+ @param position A list of the form [x, y]
"""
super(DialogMessage, self).__init__()
self._dialog = None
if message:
self.message = message
- if pos: self.position = pos
+ if position: self.position = position
def do(self):
"""
diff --git a/data/icons/chain.svg b/data/icons/chain.svg
new file mode 100644
index 0000000..e268f4f
--- /dev/null
+++ b/data/icons/chain.svg
@@ -0,0 +1,65 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg2597"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="chain.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs2599">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ id="perspective2605" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="7"
+ inkscape:cx="33.017737"
+ inkscape:cy="33.013898"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="645"
+ inkscape:window-height="726"
+ inkscape:window-x="625"
+ inkscape:window-y="25" />
+ <metadata
+ id="metadata2602">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <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>
diff --git a/data/icons/clock.svg b/data/icons/clock.svg
new file mode 100644
index 0000000..8adb898
--- /dev/null
+++ b/data/icons/clock.svg
@@ -0,0 +1,269 @@
+<?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://web.resource.org/cc/" 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" width="231" height="231" id="svg2" sodipodi:version="0.32" inkscape:version="0.44+devel" sodipodi:docbase="C:\Documents and Settings\Molumen\Desktop" sodipodi:docname="clock_beige.svg" inkscape:output_extension="org.inkscape.output.svg.inkscape" sodipodi:modified="true" version="1.0">
+ <defs id="defs4">
+ <linearGradient y2="84.524567" x2="302" y1="365.95651" x1="302" gradientUnits="userSpaceOnUse" id="linearGradient20470" xlink:href="#linearGradient13034" inkscape:collect="always"/>
+ <radialGradient r="90.78125" fy="691.20294" fx="527" cy="691.20294" cx="527" gradientTransform="matrix(1, 0, 0, 0.231842, -340, 200.219)" gradientUnits="userSpaceOnUse" id="radialGradient20468" xlink:href="#linearGradient12977" inkscape:collect="always"/>
+ <radialGradient r="113.53125" fy="368.17188" fx="528" cy="368.17188" cx="528" gradientTransform="matrix(1, 0, 0, 0.262455, -341, 27.5432)" gradientUnits="userSpaceOnUse" id="radialGradient20466" xlink:href="#linearGradient12977" inkscape:collect="always"/>
+ <radialGradient r="2.625" fy="468.57623" fx="504.125" cy="468.57623" cx="504.125" gradientTransform="matrix(1.05261, 0, 0, 1.05261, -26.5224, -23.8951)" gradientUnits="userSpaceOnUse" id="radialGradient20464" xlink:href="#linearGradient13172" inkscape:collect="always"/>
+ <radialGradient r="138" fy="467.18744" fx="525.49945" cy="467.18744" cx="525.49945" spreadMethod="pad" gradientTransform="matrix(1.77314, 0, 0, 1.77314, -744.784, -597.014)" gradientUnits="userSpaceOnUse" id="radialGradient20462" xlink:href="#linearGradient12953" inkscape:collect="always"/>
+ <radialGradient r="138" fy="239.93021" fx="302" cy="239.93021" cx="302" gradientTransform="matrix(3.14096, 0, 0, 3.14096, -646.57, -549.905)" gradientUnits="userSpaceOnUse" id="radialGradient20460" xlink:href="#linearGradient20428" inkscape:collect="always"/>
+ <radialGradient r="138" fy="239.93021" fx="302" cy="239.93021" cx="302" gradientTransform="matrix(3.14096, 0, 0, 3.14096, -646.57, -549.905)" gradientUnits="userSpaceOnUse" id="radialGradient20438" xlink:href="#linearGradient20428" inkscape:collect="always"/>
+ <radialGradient r="138" fy="467.18744" fx="525.49945" cy="467.18744" cx="525.49945" spreadMethod="pad" gradientTransform="matrix(1.77314, 0, 0, 1.77314, -744.784, -597.014)" gradientUnits="userSpaceOnUse" id="radialGradient19456" xlink:href="#linearGradient12953" inkscape:collect="always"/>
+ <radialGradient r="113.53125" fy="368.17188" fx="528" cy="368.17188" cx="528" gradientTransform="matrix(1, 0, 0, 0.262455, -341, 27.5432)" gradientUnits="userSpaceOnUse" id="radialGradient19449" xlink:href="#linearGradient12977" inkscape:collect="always"/>
+ <radialGradient r="90.78125" fy="691.20294" fx="527" cy="691.20294" cx="527" gradientTransform="matrix(1, 0, 0, 0.231842, -340, 200.219)" gradientUnits="userSpaceOnUse" id="radialGradient19446" xlink:href="#linearGradient12977" inkscape:collect="always"/>
+ <linearGradient y2="84.524567" x2="302" y1="365.95651" x1="302" gradientUnits="userSpaceOnUse" id="linearGradient19441" xlink:href="#linearGradient13034" inkscape:collect="always"/>
+ <radialGradient r="90.78125" fy="691.20294" fx="527" cy="691.20294" cx="527" gradientTransform="matrix(1, 0, 0, 0.231842, -1, 463.219)" gradientUnits="userSpaceOnUse" id="radialGradient19439" xlink:href="#linearGradient12977" inkscape:collect="always"/>
+ <radialGradient r="113.53125" fy="368.17188" fx="528" cy="368.17188" cx="528" gradientTransform="matrix(1, 0, 0, 0.262455, -2, 290.543)" gradientUnits="userSpaceOnUse" id="radialGradient19437" xlink:href="#linearGradient12977" inkscape:collect="always"/>
+ <radialGradient r="2.625" fy="468.57623" fx="504.125" cy="468.57623" cx="504.125" gradientTransform="matrix(1.05261, 0, 0, 1.05261, -26.5224, -23.8951)" gradientUnits="userSpaceOnUse" id="radialGradient19435" xlink:href="#linearGradient13172" inkscape:collect="always"/>
+ <radialGradient r="138" fy="467.18744" fx="525.49945" cy="467.18744" cx="525.49945" spreadMethod="pad" gradientTransform="matrix(1.77314, 0, 0, 1.77314, -405.784, -334.014)" gradientUnits="userSpaceOnUse" id="radialGradient19433" xlink:href="#linearGradient12953" inkscape:collect="always"/>
+ <radialGradient r="138" fy="239.93021" fx="302" cy="239.93021" cx="302" gradientTransform="matrix(3.14096, 0, 0, 3.14096, -646.57, -549.905)" gradientUnits="userSpaceOnUse" id="radialGradient19431" xlink:href="#linearGradient13012" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16295" xlink:href="#linearGradient16243" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -781.919, -183.605)" gradientUnits="userSpaceOnUse" id="radialGradient16293" xlink:href="#linearGradient16243" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16285" xlink:href="#linearGradient16243" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -781.919, -183.605)" gradientUnits="userSpaceOnUse" id="radialGradient16283" xlink:href="#linearGradient16243" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16275" xlink:href="#linearGradient16243" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -781.919, -183.605)" gradientUnits="userSpaceOnUse" id="radialGradient16273" xlink:href="#linearGradient16243" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16265" xlink:href="#linearGradient16243" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -781.919, -183.605)" gradientUnits="userSpaceOnUse" id="radialGradient16263" xlink:href="#linearGradient16243" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16249" xlink:href="#linearGradient16243" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -781.919, -183.605)" gradientUnits="userSpaceOnUse" id="radialGradient16241" xlink:href="#linearGradient16243" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16197" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, 24.3952)" gradientUnits="userSpaceOnUse" id="radialGradient16195" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16189" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, 24.3952)" gradientUnits="userSpaceOnUse" id="radialGradient16187" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16181" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, 24.3952)" gradientUnits="userSpaceOnUse" id="radialGradient16179" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16173" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, 24.3952)" gradientUnits="userSpaceOnUse" id="radialGradient16171" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16165" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, 24.3952)" gradientUnits="userSpaceOnUse" id="radialGradient16163" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16095" xlink:href="#linearGradient15913" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, -173.605)" gradientUnits="userSpaceOnUse" id="radialGradient16093" xlink:href="#linearGradient15913" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16091" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, 24.3952)" gradientUnits="userSpaceOnUse" id="radialGradient16089" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16087" xlink:href="#linearGradient15913" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, -173.605)" gradientUnits="userSpaceOnUse" id="radialGradient16085" xlink:href="#linearGradient15913" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16083" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, 24.3952)" gradientUnits="userSpaceOnUse" id="radialGradient16081" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16079" xlink:href="#linearGradient15913" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, -173.605)" gradientUnits="userSpaceOnUse" id="radialGradient16077" xlink:href="#linearGradient15913" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16075" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, 24.3952)" gradientUnits="userSpaceOnUse" id="radialGradient16073" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16071" xlink:href="#linearGradient15913" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, -173.605)" gradientUnits="userSpaceOnUse" id="radialGradient16069" xlink:href="#linearGradient15913" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16067" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, 24.3952)" gradientUnits="userSpaceOnUse" id="radialGradient16065" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16063" xlink:href="#linearGradient15913" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, -173.605)" gradientUnits="userSpaceOnUse" id="radialGradient16061" xlink:href="#linearGradient15913" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" gradientUnits="userSpaceOnUse" id="radialGradient16059" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient r="131" fy="361.61154" fx="296.26508" cy="361.61154" cx="296.26508" gradientTransform="matrix(1.25538, -1.25538, 2.63349, 2.63349, -1015.92, 24.3952)" gradientUnits="userSpaceOnUse" id="radialGradient16057" xlink:href="#linearGradient3346" inkscape:collect="always"/>
+ <radialGradient xlink:href="#linearGradient10759" cx="202.5" cy="578.86218" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="578.86218" fx="202.5" id="radialGradient13728" r="91.5"/>
+ <linearGradient y2="84.524567" y1="365.95651" xlink:href="#linearGradient13034" x2="302" x1="302" inkscape:collect="always" gradientUnits="userSpaceOnUse" id="linearGradient13265"/>
+ <radialGradient gradientTransform="matrix(1, 0, 0, 0.231842, -1, 463.219)" xlink:href="#linearGradient12977" cx="527" cy="691.20294" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="691.20294" fx="527" id="radialGradient13263" r="90.78125"/>
+ <radialGradient gradientTransform="matrix(1, 0, 0, 0.262455, -2, 290.543)" xlink:href="#linearGradient12977" cx="528" cy="368.17188" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="368.17188" fx="528" id="radialGradient13261" r="113.53125"/>
+ <radialGradient gradientTransform="matrix(1.05261, 0, 0, 1.05261, -26.5224, -23.8951)" xlink:href="#linearGradient13172" cx="504.125" cy="468.57623" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="468.57623" fx="504.125" id="radialGradient13259" r="2.625"/>
+ <radialGradient gradientTransform="matrix(1.77314, 0, 0, 1.77314, -405.784, -334.014)" xlink:href="#linearGradient12953" cx="525.49945" cy="467.18744" inkscape:collect="always" gradientUnits="userSpaceOnUse" spreadMethod="pad" fy="467.18744" fx="525.49945" id="radialGradient13257" r="138"/>
+ <radialGradient gradientTransform="matrix(3.14096, 0, 0, 3.14096, -646.57, -549.905)" xlink:href="#linearGradient13012" cx="302" cy="239.93021" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="239.93021" fx="302" id="radialGradient13255" r="138"/>
+ <linearGradient y2="84.524567" y1="365.95651" xlink:href="#linearGradient13034" x2="302" x1="302" inkscape:collect="always" gradientUnits="userSpaceOnUse" id="linearGradient13231"/>
+ <radialGradient gradientTransform="matrix(1, 0, 0, 0.231842, -1, 463.219)" xlink:href="#linearGradient12977" cx="527" cy="691.20294" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="691.20294" fx="527" id="radialGradient13229" r="90.78125"/>
+ <radialGradient gradientTransform="matrix(1, 0, 0, 0.262455, -2, 290.543)" xlink:href="#linearGradient12977" cx="528" cy="368.17188" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="368.17188" fx="528" id="radialGradient13227" r="113.53125"/>
+ <radialGradient gradientTransform="matrix(1.05261, 0, 0, 1.05261, -26.5224, -23.8951)" xlink:href="#linearGradient13172" cx="504.125" cy="468.57623" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="468.57623" fx="504.125" id="radialGradient13225" r="2.625"/>
+ <radialGradient gradientTransform="matrix(1.77314, 0, 0, 1.77314, -405.784, -334.014)" xlink:href="#linearGradient12953" cx="525.49945" cy="467.18744" inkscape:collect="always" gradientUnits="userSpaceOnUse" spreadMethod="pad" fy="467.18744" fx="525.49945" id="radialGradient13223" r="138"/>
+ <radialGradient gradientTransform="matrix(3.14096, 0, 0, 3.14096, -646.57, -549.905)" xlink:href="#linearGradient13012" cx="302" cy="239.93021" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="239.93021" fx="302" id="radialGradient13221" r="138"/>
+ <radialGradient gradientTransform="matrix(1.77314, 0, 0, 1.77314, -405.784, -334.014)" xlink:href="#linearGradient12953" cx="525.49945" cy="467.18744" inkscape:collect="always" gradientUnits="userSpaceOnUse" spreadMethod="pad" fy="467.18744" fx="525.49945" id="radialGradient13206" r="138"/>
+ <radialGradient gradientTransform="matrix(1, 0, 0, 0.262455, -2, 290.543)" xlink:href="#linearGradient12977" cx="528" cy="368.17188" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="368.17188" fx="528" id="radialGradient13203" r="113.53125"/>
+ <radialGradient gradientTransform="matrix(1, 0, 0, 0.231842, -1, 463.219)" xlink:href="#linearGradient12977" cx="527" cy="691.20294" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="691.20294" fx="527" id="radialGradient13200" r="90.78125"/>
+ <linearGradient y2="84.524567" y1="365.95651" xlink:href="#linearGradient13034" x2="302" x1="302" inkscape:collect="always" gradientUnits="userSpaceOnUse" id="linearGradient13195"/>
+ <radialGradient gradientTransform="matrix(1, 0, 0, 0.231842, -1, 463.219)" xlink:href="#linearGradient12977" cx="527" cy="691.20294" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="691.20294" fx="527" id="radialGradient13193" r="90.78125"/>
+ <radialGradient gradientTransform="matrix(1, 0, 0, 0.262455, -2, 290.543)" xlink:href="#linearGradient12977" cx="528" cy="368.17188" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="368.17188" fx="528" id="radialGradient13191" r="113.53125"/>
+ <radialGradient gradientTransform="matrix(1.77314, 0, 0, 1.77314, -405.784, -334.014)" xlink:href="#linearGradient12953" cx="525.49945" cy="467.18744" inkscape:collect="always" gradientUnits="userSpaceOnUse" spreadMethod="pad" fy="467.18744" fx="525.49945" id="radialGradient13189" r="138"/>
+ <radialGradient gradientTransform="matrix(3.14096, 0, 0, 3.14096, -646.57, -549.905)" xlink:href="#linearGradient13012" cx="302" cy="239.93021" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="239.93021" fx="302" id="radialGradient13187" r="138"/>
+ <radialGradient gradientTransform="matrix(1.05261, 0, 0, 1.05261, -26.5224, -23.8951)" xlink:href="#linearGradient13172" cx="504.125" cy="468.57623" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="468.57623" fx="504.125" id="radialGradient13170" r="2.625"/>
+ <linearGradient y2="84.524567" y1="365.95651" xlink:href="#linearGradient13034" x2="302" x1="302" inkscape:collect="always" gradientUnits="userSpaceOnUse" id="linearGradient13146"/>
+ <radialGradient gradientTransform="matrix(1.77314, 0, 0, 1.77314, -405.784, -334.014)" xlink:href="#linearGradient12953" cx="525.49945" cy="467.18744" inkscape:collect="always" gradientUnits="userSpaceOnUse" spreadMethod="pad" fy="467.18744" fx="525.49945" id="radialGradient13143" r="138"/>
+ <radialGradient gradientTransform="matrix(1, 0, 0, 0.262455, -2, 290.543)" xlink:href="#linearGradient12977" cx="528" cy="368.17188" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="368.17188" fx="528" id="radialGradient13140" r="113.53125"/>
+ <radialGradient gradientTransform="matrix(1, 0, 0, 0.231842, -1, 463.219)" xlink:href="#linearGradient12977" cx="527" cy="691.20294" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="691.20294" fx="527" id="radialGradient13137" r="90.78125"/>
+ <linearGradient y2="150.36218" y1="426.36218" xlink:href="#linearGradient13034" x2="302" x1="302" inkscape:collect="always" gradientUnits="userSpaceOnUse" id="linearGradient13133"/>
+ <radialGradient gradientTransform="matrix(1, 0, 0, 0.231842, -1, 463.219)" xlink:href="#linearGradient12977" cx="527" cy="691.20294" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="691.20294" fx="527" id="radialGradient13131" r="90.78125"/>
+ <radialGradient gradientTransform="matrix(1, 0, 0, 0.262455, -2, 290.543)" xlink:href="#linearGradient12977" cx="528" cy="368.17188" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="368.17188" fx="528" id="radialGradient13129" r="113.53125"/>
+ <radialGradient gradientTransform="matrix(1.77314, 0, 0, 1.77314, -405.784, -334.014)" xlink:href="#linearGradient12953" cx="525.49945" cy="467.18744" inkscape:collect="always" gradientUnits="userSpaceOnUse" spreadMethod="pad" fy="467.18744" fx="525.49945" id="radialGradient13127" r="138"/>
+ <radialGradient gradientTransform="matrix(3.14096, 0, 0, 3.14096, -646.57, -549.905)" xlink:href="#linearGradient13012" cx="302" cy="239.93021" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="239.93021" fx="302" id="radialGradient13125" r="138"/>
+ <linearGradient y2="150.36218" y1="426.36218" xlink:href="#linearGradient13034" x2="302" x1="302" inkscape:collect="always" gradientUnits="userSpaceOnUse" id="linearGradient13032"/>
+ <radialGradient gradientTransform="matrix(3.14096, 0, 0, 3.14096, -646.57, -549.905)" xlink:href="#linearGradient13012" cx="302" cy="239.93021" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="239.93021" fx="302" id="radialGradient13010" r="138"/>
+ <radialGradient gradientTransform="matrix(1, 0, 0, 0.231842, -1, 463.219)" xlink:href="#linearGradient12977" cx="527" cy="691.20294" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="691.20294" fx="527" id="radialGradient13000" r="90.78125"/>
+ <radialGradient gradientTransform="matrix(-0.932879, 0, 0, -0.244839, 1018.94, 683.505)" xlink:href="#linearGradient12977" cx="528" cy="368.17188" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="368.17188" fx="528" id="radialGradient12987" r="113.53125"/>
+ <radialGradient gradientTransform="matrix(1, 0, 0, 0.262455, -2, 290.543)" xlink:href="#linearGradient12977" cx="528" cy="368.17188" inkscape:collect="always" gradientUnits="userSpaceOnUse" fy="368.17188" fx="528" id="radialGradient12983" r="113.53125"/>
+ <radialGradient gradientTransform="matrix(1.77314, 0, 0, 1.77314, -405.784, -334.014)" xlink:href="#linearGradient12953" cx="525.49945" cy="467.18744" inkscape:collect="always" gradientUnits="userSpaceOnUse" spreadMethod="pad" fy="467.18744" fx="525.49945" id="radialGradient12959" r="138"/>
+ <linearGradient id="linearGradient5553">
+ <stop id="stop5555" style="stop-color: rgb(53, 155, 68); stop-opacity: 1;" offset="0"/>
+ <stop id="stop5557" style="stop-color: rgb(50, 204, 73); stop-opacity: 1;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient5613">
+ <stop id="stop5615" style="stop-color: rgb(255, 255, 255); stop-opacity: 0.888889;" offset="0"/>
+ <stop id="stop5617" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="0.5"/>
+ <stop id="stop5619" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="0.51142859"/>
+ <stop id="stop5621" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient5675">
+ <stop id="stop5677" style="stop-color: rgb(0, 138, 0); stop-opacity: 0.890196;" offset="0"/>
+ <stop id="stop5679" style="stop-color: rgb(72, 143, 51); stop-opacity: 0;" offset="0.5"/>
+ <stop id="stop5681" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="0.51142859"/>
+ <stop id="stop5683" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient5563">
+ <stop id="stop5565" style="stop-color: rgb(255, 255, 255); stop-opacity: 0.703704;" offset="0"/>
+ <stop id="stop5571" style="stop-color: rgb(255, 255, 255); stop-opacity: 0.189815;" offset="0.50850612"/>
+ <stop id="stop5573" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="0.51142859"/>
+ <stop id="stop5567" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient5537">
+ <stop id="stop5539" style="stop-color: rgb(74, 206, 96); stop-opacity: 1;" offset="0"/>
+ <stop id="stop5541" style="stop-color: rgb(180, 234, 135); stop-opacity: 1;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient10743">
+ <stop id="stop10745" style="stop-color: rgb(255, 255, 255); stop-opacity: 0.703704;" offset="0"/>
+ <stop id="stop10747" style="stop-color: rgb(255, 255, 255); stop-opacity: 0.189815;" offset="0.50850612"/>
+ <stop id="stop10749" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="0.51142859"/>
+ <stop id="stop10751" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient10753">
+ <stop id="stop10755" style="stop-color: rgb(53, 155, 68); stop-opacity: 1;" offset="0"/>
+ <stop id="stop10757" style="stop-color: rgb(50, 204, 73); stop-opacity: 1;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient10759">
+ <stop id="stop10761" style="stop-color: rgb(74, 206, 96); stop-opacity: 1;" offset="0"/>
+ <stop id="stop10763" style="stop-color: rgb(180, 234, 135); stop-opacity: 1;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient10767">
+ <stop id="stop10769" style="stop-color: rgb(53, 155, 68); stop-opacity: 1;" offset="0"/>
+ <stop id="stop10771" style="stop-color: rgb(50, 204, 73); stop-opacity: 1;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient10773">
+ <stop id="stop10775" style="stop-color: rgb(0, 138, 0); stop-opacity: 0.890196;" offset="0"/>
+ <stop id="stop10777" style="stop-color: rgb(72, 143, 51); stop-opacity: 0;" offset="0.5"/>
+ <stop id="stop10779" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="0.51142859"/>
+ <stop id="stop10781" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient10783">
+ <stop id="stop10785" style="stop-color: rgb(0, 138, 0); stop-opacity: 0.890196;" offset="0"/>
+ <stop id="stop10787" style="stop-color: rgb(72, 143, 51); stop-opacity: 0;" offset="0.5"/>
+ <stop id="stop10789" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="0.51142859"/>
+ <stop id="stop10791" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient10793">
+ <stop id="stop10795" style="stop-color: rgb(0, 138, 0); stop-opacity: 0.890196;" offset="0"/>
+ <stop id="stop10797" style="stop-color: rgb(72, 143, 51); stop-opacity: 0;" offset="0.5"/>
+ <stop id="stop10799" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="0.51142859"/>
+ <stop id="stop10801" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient10803">
+ <stop id="stop10805" style="stop-color: rgb(255, 255, 255); stop-opacity: 0.888889;" offset="0"/>
+ <stop id="stop10807" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="0.5"/>
+ <stop id="stop10809" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="0.51142859"/>
+ <stop id="stop10811" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient10813">
+ <stop id="stop10815" style="stop-color: rgb(53, 155, 68); stop-opacity: 1;" offset="0"/>
+ <stop id="stop10817" style="stop-color: rgb(50, 204, 73); stop-opacity: 1;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient10819">
+ <stop id="stop10821" style="stop-color: rgb(74, 206, 96); stop-opacity: 1;" offset="0"/>
+ <stop id="stop10823" style="stop-color: rgb(180, 234, 135); stop-opacity: 1;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient12953">
+ <stop id="stop12955" style="stop-color: rgb(0, 0, 0); stop-opacity: 1;" offset="0"/>
+ <stop id="stop12965" style="stop-color: rgb(0, 0, 0); stop-opacity: 1;" offset="0.47816542"/>
+ <stop id="stop12961" style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" offset="0.49808899"/>
+ <stop id="stop12967" style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" offset="0.50756544"/>
+ <stop id="stop12963" style="stop-color: rgb(0, 0, 0); stop-opacity: 1;" offset="0.53007674"/>
+ <stop id="stop12957" style="stop-color: rgb(0, 0, 0); stop-opacity: 1;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient12977">
+ <stop id="stop12979" style="stop-color: rgb(255, 255, 255); stop-opacity: 0.319444;" offset="0"/>
+ <stop id="stop12981" style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient13012">
+ <stop id="stop13014" style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" offset="0"/>
+ <stop id="stop13018" style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" offset="0.20165709"/>
+ <stop id="stop13020" style="stop-color: rgb(239, 245, 251); stop-opacity: 1;" offset="0.32675916"/>
+ <stop id="stop13016" style="stop-color: rgb(4, 72, 127); stop-opacity: 1;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient13034">
+ <stop id="stop13036" style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="0"/>
+ <stop id="stop13038" style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient13172">
+ <stop id="stop13174" style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" offset="0"/>
+ <stop id="stop13176" style="stop-color: rgb(0, 0, 0); stop-opacity: 1;" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient3346">
+ <stop style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" offset="0" id="stop3348"/>
+ <stop style="stop-color: rgb(233, 233, 233); stop-opacity: 1;" offset="1" id="stop3350"/>
+ </linearGradient>
+ <filter id="filter4767" inkscape:collect="always">
+ <feGaussianBlur id="feGaussianBlur4769" stdDeviation="0.77125" inkscape:collect="always"/>
+ </filter>
+ <linearGradient id="linearGradient3916">
+ <stop style="stop-color: rgb(139, 139, 139); stop-opacity: 0.639175;" offset="0" id="stop3918"/>
+ <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 style="stop-color: black; stop-opacity: 1;" offset="0" id="stop3442"/>
+ <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 style="stop-color: white; stop-opacity: 1;" offset="0.60522962" id="stop3542"/>
+ <stop style="stop-color: black; stop-opacity: 1;" offset="0.6964286" id="stop3448"/>
+ <stop style="stop-color: black; stop-opacity: 1;" offset="1" id="stop3444"/>
+ </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 style="stop-color: white; stop-opacity: 0.498039;" offset="1" id="stop3592"/>
+ <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 style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" offset="0" id="stop16245"/>
+ <stop style="stop-color: rgb(35, 178, 240); stop-opacity: 1;" offset="1" id="stop16247"/>
+ </linearGradient>
+ <linearGradient id="linearGradient20428">
+ <stop offset="0" style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" id="stop20430"/>
+ <stop offset="0.20165709" style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" id="stop20432"/>
+ <stop offset="0.32675916" style="stop-color: rgb(251, 248, 239); stop-opacity: 1;" id="stop20434"/>
+ <stop offset="1" style="stop-color: rgb(127, 98, 4); stop-opacity: 1;" id="stop20436"/>
+ </linearGradient>
+ <radialGradient inkscape:collect="always" xlink:href="#linearGradient3346" id="radialGradient2855" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.03888, -1.98608, 4.32211, 2.26874, -1497.58, 2.13654)" cx="296.26508" cy="361.61154" fx="296.26508" fy="361.61154" r="131"/>
+ </defs>
+ <sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" gridtolerance="10000" guidetolerance="10" objecttolerance="10" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.49497475" inkscape:cx="448.89881" inkscape:cy="364.07566" inkscape:document-units="px" inkscape:current-layer="layer1" inkscape:window-width="1208" inkscape:window-height="1070" inkscape:window-x="222" inkscape:window-y="20"/>
+ <metadata id="metadata7">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(-436.343, -114.661)">
+ <g transform="matrix(0.836957, 0, 0, 0.836957, 111.604, -183.61)" id="g19409">
+ <path sodipodi:ry="138" sodipodi:rx="138" transform="matrix(0.862319, 0, 0, 0.862319, 265.58, 245.715)" d="M 440,288.36218 A 138,138 0 1 1 164,288.36218 A 138,138 0 1 1 440,288.36218 z" sodipodi:type="arc" sodipodi:cy="288.36218" sodipodi:cx="302" id="path19411" style="fill: url(#radialGradient19431) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-dashoffset: 0pt; stroke-opacity: 1;"/>
+ <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) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-dashoffset: 0pt; stroke-opacity: 1;"/>
+ <g id="g19415">
+ <path id="path19417" sodipodi:nodetypes="ccccc" 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" style="fill: rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dashoffset: 0pt; stroke-opacity: 1;"/>
+ <path id="path19419" sodipodi:nodetypes="ccccc" 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" style="fill: rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dashoffset: 0pt; stroke-opacity: 1;"/>
+ <path sodipodi:ry="2.625" sodipodi:rx="2.625" transform="matrix(1.85714, 0, 0, 1.85714, -410.232, -381.244)" 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" sodipodi:type="arc" sodipodi:cy="471.48718" sodipodi:cx="504.125" id="path19421" style="opacity: 1; fill: url(#radialGradient19435) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-dashoffset: 0pt; stroke-opacity: 1;"/>
+ </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) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-dashoffset: 0pt; 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) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-dashoffset: 0pt; 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" sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" id="path19427" style="fill: rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-dashoffset: 0pt; stroke-opacity: 1;"/>
+ <path sodipodi:ry="138" sodipodi:rx="138" transform="matrix(0.728261, 0, 0, 0.601449, 306.065, 286.927)" d="M 440,288.36218 A 138,138 0 1 1 164,288.36218 A 138,138 0 1 1 440,288.36218 z" sodipodi:type="arc" sodipodi:cy="288.36218" sodipodi:cx="302" id="path19429" style="fill: url(#linearGradient19441) rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 1; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-dasharray: none; stroke-dashoffset: 0pt; stroke-opacity: 1;"/>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/data/icons/once_wrapper.svg b/data/icons/once_wrapper.svg
new file mode 100644
index 0000000..ad48720
--- /dev/null
+++ b/data/icons/once_wrapper.svg
@@ -0,0 +1,74 @@
+<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg2393"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="once_wrapper.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs2395">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 24 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="48 : 24 : 1"
+ inkscape:persp3d-origin="24 : 16 : 1"
+ id="perspective2401" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="7"
+ inkscape:cx="24"
+ inkscape:cy="24"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="645"
+ inkscape:window-height="726"
+ inkscape:window-x="625"
+ inkscape:window-y="25" />
+ <metadata
+ id="metadata2398">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <flowRoot
+ xml:space="preserve"
+ id="flowRoot2403"
+ style="font-size:40px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:OpenSymbol;-inkscape-font-specification:OpenSymbol"
+ transform="matrix(1.2841451,0,0,1.2841451,-10.095321,-10.594455)"><flowRegion
+ id="flowRegion2405"><rect
+ id="rect2407"
+ width="45"
+ height="45"
+ x="1.8571428"
+ y="1.4285715"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:OpenSymbol;-inkscape-font-specification:OpenSymbol" /></flowRegion><flowPara
+ id="flowPara2409"> 1</flowPara></flowRoot> </g>
+</svg>
diff --git a/tests/actiontests.py b/tests/actiontests.py
index 4e126b3..7b8d1cb 100644
--- a/tests/actiontests.py
+++ b/tests/actiontests.py
@@ -25,6 +25,7 @@ import unittest
import gtk
from sugar.tutorius import addon
+from sugar.tutorius.addons.triggereventfilter import *
from sugar.tutorius.actions import *
from sugar.tutorius.services import ObjectStore
@@ -65,7 +66,7 @@ class DialogMessageTest(unittest.TestCase):
class BubbleMessageTest(unittest.TestCase):
def setUp(self):
- self.bubble = addon.create('BubbleMessage', message="Message text", pos=[200, 300], tailpos=[-15, -25])
+ self.bubble = addon.create('BubbleMessage', message="Message text", position=[200, 300], tail_pos=[-15, -25])
def test_properties(self):
props = self.bubble.get_properties()
@@ -115,7 +116,7 @@ class OnceWrapperTests(unittest.TestCase):
CountAction
"""
act = CountAction()
- wrap = OnceWrapper(act)
+ wrap = addon.create('OnceWrapper', act)
assert act.do_count == 0, "do() should not have been called in __init__()"
assert act.undo_count == 0, "undo() should not have been called in __init__()"
@@ -152,7 +153,7 @@ class ChainActionTest(unittest.TestCase):
def test_empty(self):
"""If the expected empty behavior (do nothing) changes
and starts throwing exceptions, this will flag it"""
- a = ChainAction()
+ a = addon.create('ChainAction')
a.do()
a.undo()
@@ -161,7 +162,7 @@ class ChainActionTest(unittest.TestCase):
first = ChainTester(witness)
second = ChainTester(witness)
- c = ChainAction(first, second)
+ c = addon.create('ChainAction', [first, second])
assert witness == [], "Actions should not be triggered on init"""
c.do()
@@ -194,13 +195,171 @@ class DisableWidgetActionTests(unittest.TestCase):
assert btn.props.sensitive is True, "Callback should have been called"
- act = DisableWidgetAction("0")
+ act = addon.create('DisableWidgetAction', "0")
assert btn.props.sensitive is True, "Callback should have been called again"
act.do()
assert btn.props.sensitive is False, "Callback should not have been called again"
act.undo()
assert btn.props.sensitive is True, "Callback should have been called again"
+class TrueWhileActiveAction(Action):
+ """
+ This action's active member is set to True after a do and to False after
+ an undo.
+
+ Used to verify that a State correctly triggers the do and undo actions.
+ """
+ def __init__(self):
+ Action.__init__(self)
+ self.active = False
+
+ def do(self):
+ self.active = True
+
+ def undo(self):
+ self.active = False
+
+class ClickableWidget():
+ """
+ This class fakes a widget with a clicked() method
+ """
+ def __init__(self):
+ self.click_count = 0
+
+ def clicked(self):
+ self.click_count += 1
+
+class FakeTextEntry():
+ """
+ This class fakes a widget with an insert_text() method
+ """
+ def __init__(self):
+ self.text_lines = []
+ self.last_entered_line = ""
+ self.displayed_text = ""
+
+ def insert_text(self, text, index):
+ self.last_entered_line = text
+ self.text_lines.append(text)
+ self.displayed_text = self.displayed_text[0:index] + text + self.displayed_text[index+1:]
+
+class FakeParentWidget():
+ """
+ This class fakes a widet container, it implements the get_children() method
+ """
+ def __init__(self):
+ self._children = []
+
+ def add_child(self, child):
+ self._children.append(child)
+
+ def get_children(self):
+ return self._children
+
+class FakeEventFilter(TriggerEventFilter):
+ """
+ This is a fake event that is connected to the tutorial.
+
+ The difference between this one and the TriggerEventFilter is that the
+ tutorial's set_state will be called on the callback.
+
+ Do not forget to add the do_callback() after creating the object.
+ """
+ def set_tutorial(self, tutorial):
+ self.tutorial = tutorial
+
+ def _inner_cb(self, event_filter):
+ self.toggle_on_callback = not self.toggle_on_callback
+ self.tutorial.set_state(event_filter.get_next_state())
+
+class TypeTextActionTests(unittest.TestCase):
+ """
+ Test class for type text action
+ """
+ def test_do_action(self):
+ activity = FakeParentWidget()
+ widget = FakeTextEntry()
+ activity.add_child(widget)
+ ObjectStore().activity = activity
+
+ test_text = "This is text"
+
+
+ action = addon.create('TypeTextAction', "0.0", test_text)
+
+ assert widget == ObjectStore().activity.get_children()[0],\
+ "The clickable widget isn't reachable from the object store \
+ the test cannot pass"
+
+ action.do()
+
+ assert widget.last_entered_line == test_text, "insert_text() should have been called by do()"
+
+ action.do()
+
+ assert widget.last_entered_line == test_text, "insert_text() should have been called by do()"
+ assert len(widget.text_lines) == 2, "insert_text() should have been called twice"
+
+ def test_undo(self):
+ activity = FakeParentWidget()
+ widget = FakeTextEntry()
+ activity.add_child(widget)
+ ObjectStore().activity = activity
+
+ test_text = "This is text"
+
+
+ action = addon.create('TypeTextAction', "0.0", test_text)
+
+ assert widget == ObjectStore().activity.get_children()[0],\
+ "The clickable widget isn't reachable from the object store \
+ the test cannot pass"
+
+ action.undo()
+
+ #There is no undo for this action so the test should not fail
+ assert True
+
+class ClickActionTests(unittest.TestCase):
+ """
+ Test class for click action
+ """
+ def test_do_action(self):
+ activity = FakeParentWidget()
+ widget = ClickableWidget()
+ activity.add_child(widget)
+ ObjectStore().activity = activity
+
+ action = addon.create('ClickAction', "0.0")
+
+ assert widget == ObjectStore().activity.get_children()[0],\
+ "The clickable widget isn't reachable from the object store \
+ the test cannot pass"
+
+ action.do()
+
+ assert widget.click_count == 1, "clicked() should have been called by do()"
+
+ action.do()
+
+ assert widget.click_count == 2, "clicked() should have been called by do()"
+
+ def test_undo(self):
+ activity = FakeParentWidget()
+ widget = ClickableWidget()
+ activity.add_child(widget)
+ ObjectStore().activity = activity
+
+ action = addon.create('ClickAction', "0.0")
+
+ assert widget == ObjectStore().activity.get_children()[0],\
+ "The clickable widget isn't reachable from the object store \
+ the test cannot pass"
+
+ action.undo()
+
+ #There is no undo for this action so the test should not fail
+ assert True
if __name__ == "__main__":
unittest.main()
diff --git a/tests/bundlertests.py b/tests/bundlertests.py
index 8da2310..ad8d1bb 100644
--- a/tests/bundlertests.py
+++ b/tests/bundlertests.py
@@ -26,7 +26,76 @@ import unittest
import os
import uuid
-from sugar.tutorius import bundler
+from sugar.tutorius import bundler
+
+##class VaultTests(unittest.TestCase):
+## def setUp(self):
+## pass
+##
+## def tearDown(self):
+## pass
+##
+## def test_basicQuery(self):
+## vault = Vault()
+##
+## list_metadata = vault.query(keyword='tutorial', startIndex=2, numResults=5)
+##
+## assert len(list_metadata) <= 5
+##
+## def test_advancedQuery(self):
+## vault = Vault()
+##
+## list_metadata = vault.query(keyword='', category='Math', startIndex=10, numResults=10)
+##
+## assert len(list_metadata) <= 10
+##
+## pass
+##
+## def test_installTutorial(self):
+## # Create a new tutorial
+##
+##
+## xml_serializer = XmlSerializer()
+##
+##
+## xml_serializer.save_fsm()
+##
+## def test_deleteTutorial(self):
+## pass
+##
+## def test_saveTutorial(self):
+## pass
+##
+## def test_readTutorial(self):
+## pass
+##
+## def _generateSampleTutorial(self):
+## """
+## Creates a new tutorial and bundles it.
+##
+## @return The UUID for the new tutorial.
+## """
+## self._fsm = FiniteStateMachine("Sample testing FSM")
+## # Add a few states
+## act1 = addon.create('BubbleMessage', message="Hi", pos=[300, 450])
+## ev1 = addon.create('GtkWidgetEventFilter', "0.12.31.2.2", "clicked", "FINAL")
+## act2 = addon.create('BubbleMessage', message="Second message", pos=[250, 150], tailpos=[1,2])
+##
+## st1 = State("INIT")
+## st1.add_action(act1)
+## st1.add_event_filter(ev1)
+##
+## st2 = State("FINAL")
+## st2.add_action(act2)
+##
+## self._fsm.add_state(st1)
+## self._fsm.add_state(st2)
+##
+## xml_ser = XmlSerializer()
+##
+## os.makedirs(os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)))
+##
+## # xml_ser.save_fsm(self._fsm, TUTORIAL_FILENAME,
class TutorialBundlerTests(unittest.TestCase):
@@ -51,15 +120,15 @@ class TutorialBundlerTests(unittest.TestCase):
os.rmdir(self.guid_path)
def test_add_ressource(self):
- bund = bundler.TutorialBundler(self.test_guid)
+ bund = bundler.TutorialBundler(unicode(self.test_guid))
temp_file = open("test.txt",'w')
temp_file.write('test')
temp_file.close()
- bund.add_resource("test.txt")
+ bund.add_resources("text", "test.txt")
assert os.path.exists(os.path.join(self.guid_path,"test.txt")), "add_ressource did not create the file"
if __name__ == "__main__":
- unittest.main() \ No newline at end of file
+ unittest.main()
diff --git a/tests/constraintstests.py b/tests/constraintstests.py
index b7b0a47..4e19a92 100644
--- a/tests/constraintstests.py
+++ b/tests/constraintstests.py
@@ -16,6 +16,9 @@
import unittest
+import uuid
+import os
+
from sugar.tutorius.constraints import *
class ConstraintTest(unittest.TestCase):
@@ -218,10 +221,18 @@ class EnumConstraintTest(unittest.TestCase):
assert False, "Wrong exception type thrown"
class FileConstraintTest(unittest.TestCase):
+ def setUp(self):
+ self.temp_filename = "sample_file_" + str(uuid.uuid1()) + ".txt"
+ self.file1 = file(self.temp_filename, "w")
+ self.file1.close()
+
+ def tearDown(self):
+ os.unlink(self.temp_filename)
+
def test_validate(self):
cons = FileConstraint()
- cons.validate("run-tests.py")
+ cons.validate(self.temp_filename)
try:
cons.validate("unknown/file.py")
@@ -230,4 +241,4 @@ class FileConstraintTest(unittest.TestCase):
pass
if __name__ == "__main__":
- unittest.main() \ No newline at end of file
+ unittest.main()
diff --git a/tests/coretests.py b/tests/coretests.py
index eadea01..f90374f 100644
--- a/tests/coretests.py
+++ b/tests/coretests.py
@@ -29,12 +29,12 @@ and event filters. Those are in their separate test module
import unittest
import logging
-from sugar.tutorius.actions import Action, OnceWrapper, ClickAction, TypeTextAction
+from sugar.tutorius.actions import *
+from sugar.tutorius.addon import *
from sugar.tutorius.core import *
from sugar.tutorius.filters import *
-
-from actiontests import CountAction
+from actiontests import CountAction, FakeEventFilter
# Helper classes to help testing
class SimpleTutorial(Tutorial):
@@ -73,173 +73,6 @@ class TrueWhileActiveAction(Action):
def undo(self):
self.active = False
-
-class ClickableWidget():
- """
- This class fakes a widget with a clicked() method
- """
- def __init__(self):
- self.click_count = 0
-
- def clicked(self):
- self.click_count += 1
-
-class FakeTextEntry():
- """
- This class fakes a widget with an insert_text() method
- """
- def __init__(self):
- self.text_lines = []
- self.last_entered_line = ""
- self.displayed_text = ""
-
- def insert_text(self, text, index):
- self.last_entered_line = text
- self.text_lines.append(text)
- self.displayed_text = self.displayed_text[0:index] + text + self.displayed_text[index+1:]
-
-class FakeParentWidget():
- """
- This class fakes a widet container, it implements the get_children() method
- """
- def __init__(self):
- self._children = []
-
- def add_child(self, child):
- self._children.append(child)
-
- def get_children(self):
- return self._children
-
-
-
-
-class TriggerEventFilter(EventFilter):
- """
- This event filter can be triggered by simply calling its do_callback function.
-
- Used to fake events and see the effect on the FSM.
- """
- def __init__(self, next_state):
- EventFilter.__init__(self, next_state)
- self.toggle_on_callback = False
-
- def install_handlers(self, callback, **kwargs):
- """
- Forsakes the incoming callback function and just set the inner one.
- """
- self._callback = self._inner_cb
-
- def _inner_cb(self, event_filter):
- self.toggle_on_callback = not self.toggle_on_callback
-
-class FakeEventFilter(TriggerEventFilter):
- """
- This is a fake event that is connected to the tutorial.
-
- The difference between this one and the TriggerEventFilter is that the
- tutorial's set_state will be called on the callback.
-
- Do not forget to add the do_callback() after creating the object.
- """
- def set_tutorial(self, tutorial):
- self.tutorial = tutorial
-
- def _inner_cb(self, event_filter):
- self.toggle_on_callback = not self.toggle_on_callback
- self.tutorial.set_state(event_filter.get_next_state())
-
-
-class ClickActionTests(unittest.TestCase):
- """
- Test class for click action
- """
- def test_do_action(self):
- activity = FakeParentWidget()
- widget = ClickableWidget()
- activity.add_child(widget)
- ObjectStore().activity = activity
-
- action = ClickAction("0.0")
-
- assert widget == ObjectStore().activity.get_children()[0],\
- "The clickable widget isn't reachable from the object store \
- the test cannot pass"
-
- action.do()
-
- assert widget.click_count == 1, "clicked() should have been called by do()"
-
- action.do()
-
- assert widget.click_count == 2, "clicked() should have been called by do()"
-
- def test_undo(self):
- activity = FakeParentWidget()
- widget = ClickableWidget()
- activity.add_child(widget)
- ObjectStore().activity = activity
-
- action = ClickAction("0.0")
-
- assert widget == ObjectStore().activity.get_children()[0],\
- "The clickable widget isn't reachable from the object store \
- the test cannot pass"
-
- action.undo()
-
- #There is no undo for this action so the test should not fail
- assert True
-
-
-
-class TypeTextActionTests(unittest.TestCase):
- """
- Test class for type text action
- """
- def test_do_action(self):
- activity = FakeParentWidget()
- widget = FakeTextEntry()
- activity.add_child(widget)
- ObjectStore().activity = activity
-
- test_text = "This is text"
-
-
- action = TypeTextAction("0.0", test_text)
-
- assert widget == ObjectStore().activity.get_children()[0],\
- "The clickable widget isn't reachable from the object store \
- the test cannot pass"
-
- action.do()
-
- assert widget.last_entered_line == test_text, "insert_text() should have been called by do()"
-
- action.do()
-
- assert widget.last_entered_line == test_text, "insert_text() should have been called by do()"
- assert len(widget.text_lines) == 2, "insert_text() should have been called twice"
-
- def test_undo(self):
- activity = FakeParentWidget()
- widget = FakeTextEntry()
- activity.add_child(widget)
- ObjectStore().activity = activity
-
- test_text = "This is text"
-
-
- action = TypeTextAction("0.0", test_text)
-
- assert widget == ObjectStore().activity.get_children()[0],\
- "The clickable widget isn't reachable from the object store \
- the test cannot pass"
-
- action.undo()
-
- #There is no undo for this action so the test should not fail
- assert True
# State testing class
class StateTest(unittest.TestCase):
@@ -274,7 +107,7 @@ class StateTest(unittest.TestCase):
Tests the fact that the event filters are correctly installed on setup
and uninstalled on teardown.
"""
- event_filter = TriggerEventFilter("second_state")
+ event_filter = addon.create('TriggerEventFilter', "second_state")
state = State("event_test", event_filter_list=[event_filter])
state.set_tutorial(SimpleTutorial())
@@ -345,9 +178,9 @@ class StateTest(unittest.TestCase):
def test_add_event_filter(self):
state = State("INIT")
- event1 = TriggerEventFilter("s")
- event2 = TriggerEventFilter("t")
- event3 = TriggerEventFilter("r")
+ event1 = addon.create('TriggerEventFilter', "s")
+ event2 = addon.create('TriggerEventFilter', "t")
+ event3 = addon.create('TriggerEventFilter', "r")
# Insert the event filters
assert state.add_event_filter(event1), "Could not add event filter 1"
@@ -472,9 +305,9 @@ class FSMTest(unittest.TestCase):
This test removes a state from the FSM. It also verifies that the links
from other states going into the removed state are gone.
"""
- st1 = State("INIT", event_filter_list=[TriggerEventFilter("second")])
- st2 = State("second", event_filter_list=[TriggerEventFilter("third")])
- st3 = State("third", event_filter_list=[TriggerEventFilter("second")])
+ st1 = State("INIT", event_filter_list=[addon.create('TriggerEventFilter', "second")])
+ st2 = State("second", event_filter_list=[addon.create('TriggerEventFilter', "third")])
+ st3 = State("third", event_filter_list=[addon.create('TriggerEventFilter', "second")])
fsm = FiniteStateMachine("StateRemovalTest")
@@ -547,13 +380,13 @@ class FSMExplorationTests(unittest.TestCase):
"""
st1 = State("INIT")
st1.add_action(CountAction())
- st1.add_event_filter(TriggerEventFilter("Second"))
- st1.add_event_filter(TriggerEventFilter("Third"))
+ st1.add_event_filter(addon.create('TriggerEventFilter', "Second"))
+ st1.add_event_filter(addon.create('TriggerEventFilter', "Third"))
st2 = State("Second")
st2.add_action(TrueWhileActiveAction())
- st2.add_event_filter(TriggerEventFilter("Third"))
- st2.add_event_filter(TriggerEventFilter("Fourth"))
+ st2.add_event_filter(addon.create('TriggerEventFilter', "Third"))
+ st2.add_event_filter(addon.create('TriggerEventFilter', "Fourth"))
st3 = State("Third")
st3.add_action(CountAction())
diff --git a/tests/filterstests.py b/tests/filterstests.py
index 3e79bcc..c45f924 100644
--- a/tests/filterstests.py
+++ b/tests/filterstests.py
@@ -26,7 +26,7 @@ import time
import gobject
import gtk
-from sugar.tutorius.filters import EventFilter, TimerEvent, GtkWidgetTypeFilter
+from sugar.tutorius.filters import EventFilter
from sugar.tutorius import addon
from gtkutilstests import SignalCatcher
@@ -79,7 +79,7 @@ class TestTimerEvent(unittest.TestCase):
ctx = gobject.MainContext()
main = gobject.MainLoop(ctx)
- e = TimerEvent("Next",1) #1 second should be enough :s
+ e = addon.create('TimerEvent', "Next", 2) # 2 seconds should be enough :s
s = SignalCatcher()
e.install_handlers(s.callback)
@@ -122,7 +122,7 @@ class TestTimerEvent(unittest.TestCase):
ctx = gobject.MainContext()
main = gobject.MainLoop(ctx)
- e = TimerEvent("Next",1) #1 second should be enough :s
+ e = addon.create('TimerEvent', "Next", 2) # 2 seconds should be enough :s
s = SignalCatcher()
e.install_handlers(s.callback)
diff --git a/tests/linear_creatortests.py b/tests/linear_creatortests.py
index dcded57..999f4d5 100644
--- a/tests/linear_creatortests.py
+++ b/tests/linear_creatortests.py
@@ -19,7 +19,7 @@ from sugar.tutorius.core import *
from sugar.tutorius.actions import *
from sugar.tutorius.filters import *
from sugar.tutorius.linear_creator import *
-from coretests import TriggerEventFilter
+from sugar.tutorius.addons.triggereventfilter import *
from actiontests import CountAction
import unittest
diff --git a/tests/propertiestests.py b/tests/propertiestests.py
index 46346c4..e1f6f4b 100644
--- a/tests/propertiestests.py
+++ b/tests/propertiestests.py
@@ -15,6 +15,8 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import unittest
+import uuid
+import os
from sugar.tutorius.constraints import *
from sugar.tutorius.properties import *
@@ -378,18 +380,31 @@ class TEnumPropertyTest(unittest.TestCase):
class TFilePropertyTest(unittest.TestCase):
def setUp(self):
+ # Create some sample, unique files for the tests
+ self.temp_filename1 = "sample_file1_" + str(uuid.uuid1()) + ".txt"
+ self.temp_file1 = file(self.temp_filename1, "w")
+ self.temp_file1.close()
+ self.temp_filename2 = "sample_file2_" + str(uuid.uuid1()) + ".txt"
+ self.temp_file2 = file(self.temp_filename2, "w")
+ self.temp_file2.close()
+
class klass(TPropContainer):
- prop = TFileProperty("propertiestests.py")
+ prop = TFileProperty(self.temp_filename1)
self.obj = klass()
+
+ def tearDown(self):
+ # Unlink the files from the disk when tests are over
+ os.unlink(self.temp_filename1)
+ os.unlink(self.temp_filename2)
def test_basic_file(self):
- assert self.obj.prop == "propertiestests.py", "Could not set initial value"
+ assert self.obj.prop == self.temp_filename1, "Could not set initial value"
assert type(self.obj).prop.type == "file", "Wrong type for TFileProperty : %s"%type(self.obj).prop.type
- self.obj.prop = "run-tests.py"
+ self.obj.prop = self.temp_filename2
- assert self.obj.prop == "run-tests.py", "Could not change value"
+ assert self.obj.prop == self.temp_filename2, "Could not change value"
try:
self.obj.prop = "unknown/file/on/disk.gif"
diff --git a/tests/serializertests.py b/tests/serializertests.py
index 6c25bae..c939b7a 100644
--- a/tests/serializertests.py
+++ b/tests/serializertests.py
@@ -74,9 +74,9 @@ class XMLSerializerTest(unittest.TestCase):
self.fsm = FiniteStateMachine("testingMachine")
# Add a few states
- act1 = addon.create('BubbleMessage', message="Hi", pos=[300, 450])
+ act1 = addon.create('BubbleMessage', message="Hi", position=[300, 450])
ev1 = addon.create('GtkWidgetEventFilter', "0.12.31.2.2", "clicked", "Second")
- act2 = addon.create('BubbleMessage', message="Second message", pos=[250, 150], tailpos=[1,2])
+ act2 = addon.create('BubbleMessage', message="Second message", position=[250, 150], tail_pos=[1,2])
st1 = State("INIT")
st1.add_action(act1)
@@ -107,11 +107,10 @@ class XMLSerializerTest(unittest.TestCase):
def test_save(self):
"""
Writes an FSM to disk, then compares the file to the expected results.
- "Remove" boolean argument specify if the test data must be removed or not
+ "Remove" boolean member specifies if the test data must be removed or not
"""
xml_ser = XMLSerializer()
os.makedirs(os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)))
- #rpdb2.start_embedded_debugger('flakyPass')
xml_ser.save_fsm(self.fsm, bundler.TUTORIAL_FILENAME, os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)))
def test_save_and_load(self):
@@ -143,14 +142,14 @@ class XMLSerializerTest(unittest.TestCase):
"""
st = State("INIT")
- act1 = addon.create('BubbleMessage', "Hi!", pos=[10,120], tailpos=[-12,30])
- act2 = addon.create('DialogMessage', "Hello again.", pos=[120,10])
- act3 = WidgetIdentifyAction()
- act4 = DisableWidgetAction("0.0.0.1.0.0.0")
- act5 = TypeTextAction("0.0.0.1.1.1.0.0", "New text")
- act6 = ClickAction("0.0.1.0.1.1")
- act7 = OnceWrapper(act1)
- act8 = ChainAction([act1, act2, act3, act4])
+ act1 = addon.create('BubbleMessage', "Hi!", position=[10,120], tail_pos=[-12,30])
+ act2 = addon.create('DialogMessage', "Hello again.", position=[120,10])
+ act3 = addon.create('WidgetIdentifyAction')
+ act4 = addon.create('DisableWidgetAction', "0.0.0.1.0.0.0")
+ act5 = addon.create('TypeTextAction', "0.0.0.1.1.1.0.0", "New text")
+ act6 = addon.create('ClickAction', "0.0.1.0.1.1")
+ act7 = addon.create('OnceWrapper', action=act1)
+ act8 = addon.create('ChainAction', actions=[act1, act2, act3, act4])
actions = [act1, act2, act3, act4, act5, act6, act7, act8]
for action in actions:
@@ -163,9 +162,9 @@ class XMLSerializerTest(unittest.TestCase):
xml_ser = XMLSerializer()
self.test_save()
-
+
reloaded_fsm = xml_ser.load_fsm(str(self.uuid))
- assert self.fsm == reloaded_fsm, "Expected equivalence before saving vs after loading."
+ assert self.fsm.is_identical(reloaded_fsm), "Expected equivalence before saving vs after loading."
def test_all_filters(self):
"""
@@ -173,10 +172,10 @@ class XMLSerializerTest(unittest.TestCase):
"""
st = State("INIT")
- ev1 = TimerEvent("Second", 1000)
- ev2 = addon.create('GtkWidgetEventFilter', "Second", "0.0.1.1.0.0.1", "clicked")
- ev3 = GtkWidgetTypeFilter("Second", "0.0.1.1.1.2.3", text="Typed stuff")
- ev4 = GtkWidgetTypeFilter("Second", "0.0.1.1.1.2.3", strokes="acbd")
+ ev1 = addon.create('TimerEvent', "Second", 1000)
+ ev2 = addon.create('GtkWidgetEventFilter', next_state="Second", object_id="0.0.1.1.0.0.1", event_name="clicked")
+ ev3 = addon.create('GtkWidgetTypeFilter', "Second", "0.0.1.1.1.2.3", text="Typed stuff")
+ ev4 = addon.create('GtkWidgetTypeFilter', "Second", "0.0.1.1.1.2.3", strokes="acbd")
filters = [ev1, ev2, ev3, ev4]
for filter in filters:
@@ -191,7 +190,7 @@ class XMLSerializerTest(unittest.TestCase):
reloaded_fsm = xml_ser.load_fsm(str(self.uuid))
- assert self.fsm == reloaded_fsm, "Expected equivalence before saving vs after loading."
+ assert self.fsm.is_identical(reloaded_fsm), "Expected equivalence before saving vs after loading."
if __name__ == "__main__":
unittest.main()
diff --git a/tutorius/actions.py b/tutorius/actions.py
index 4269cd7..0db7988 100644
--- a/tutorius/actions.py
+++ b/tutorius/actions.py
@@ -176,149 +176,149 @@ class Action(TPropContainer):
x, y = self._drag.position
self.position = [int(x), int(y)]
self.__edit_img.destroy()
-
-class OnceWrapper(Action):
- """
- Wraps a class to perform an action once only
-
- This ConcreteActions's do() method will only be called on the first do()
- and the undo() will be callable after do() has been called
- """
-
- _action = TAddonProperty()
-
- def __init__(self, action):
- Action.__init__(self)
- self._called = False
- self._need_undo = False
- self._action = action
-
- def do(self):
- """
- Do the action only on the first time
- """
- if not self._called:
- self._called = True
- self._action.do()
- self._need_undo = True
-
- def undo(self):
- """
- Undo the action if it's been done
- """
- if self._need_undo:
- self._action.undo()
- self._need_undo = False
-
-class WidgetIdentifyAction(Action):
- def __init__(self):
- Action.__init__(self)
- self.activity = None
- self._dialog = None
-
- def do(self):
- os = ObjectStore()
- if os.activity:
- self.activity = os.activity
-
- self._dialog = WidgetIdentifier(self.activity)
- self._dialog.show()
-
-
- def undo(self):
- if self._dialog:
- self._dialog.destroy()
-
-class ChainAction(Action):
- """Utility class to allow executing actions in a specific order"""
- def __init__(self, *actions):
- """ChainAction(action1, ... ) builds a chain of actions"""
- Action.__init__(self)
- self._actions = actions
-
- def do(self,**kwargs):
- """do() each action in the chain"""
- for act in self._actions:
- act.do(**kwargs)
-
- def undo(self):
- """undo() each action in the chain, starting with the last"""
- for act in reversed(self._actions):
- act.undo()
-
-class DisableWidgetAction(Action):
- def __init__(self, target):
- """Constructor
- @param target target treeish
- """
- Action.__init__(self)
- self._target = target
- self._widget = None
-
- def do(self):
- """Action do"""
- os = ObjectStore()
- if os.activity:
- self._widget = gtkutils.find_widget(os.activity, self._target)
- if self._widget:
- self._widget.set_sensitive(False)
-
- def undo(self):
- """Action undo"""
- if self._widget:
- self._widget.set_sensitive(True)
-
-
-class TypeTextAction(Action):
- """
- Simulate a user typing text in a widget
- Work on any widget that implements a insert_text method
-
- @param widget The treehish representation of the widget
- @param text the text that is typed
- """
- def __init__(self, widget, text):
- Action.__init__(self)
-
- self._widget = widget
- self._text = text
-
- def do(self, **kwargs):
- """
- Type the text
- """
- widget = gtkutils.find_widget(ObjectStore().activity, self._widget)
- if hasattr(widget, "insert_text"):
- widget.insert_text(self._text, -1)
-
- def undo(self):
- """
- no undo
- """
- pass
+
+##class OnceWrapper(Action):
+## """
+## Wraps a class to perform an action once only
+##
+## This ConcreteActions's do() method will only be called on the first do()
+## and the undo() will be callable after do() has been called
+## """
+##
+## _action = TAddonProperty()
+##
+## def __init__(self, action):
+## Action.__init__(self)
+## self._called = False
+## self._need_undo = False
+## self._action = action
+##
+## def do(self):
+## """
+## Do the action only on the first time
+## """
+## if not self._called:
+## self._called = True
+## self._action.do()
+## self._need_undo = True
+##
+## def undo(self):
+## """
+## Undo the action if it's been done
+## """
+## if self._need_undo:
+## self._action.undo()
+## self._need_undo = False
+##
+##class WidgetIdentifyAction(Action):
+## def __init__(self):
+## Action.__init__(self)
+## self.activity = None
+## self._dialog = None
+
+## def do(self):
+## os = ObjectStore()
+## if os.activity:
+## self.activity = os.activity
+
+## self._dialog = WidgetIdentifier(self.activity)
+## self._dialog.show()
+
+
+## def undo(self):
+## if self._dialog:
+## self._dialog.destroy()
+
+##class ChainAction(Action):
+## """Utility class to allow executing actions in a specific order"""
+## def __init__(self, *actions):
+## """ChainAction(action1, ... ) builds a chain of actions"""
+## Action.__init__(self)
+## self._actions = actions
+##
+## def do(self,**kwargs):
+## """do() each action in the chain"""
+## for act in self._actions:
+## act.do(**kwargs)
+##
+## def undo(self):
+## """undo() each action in the chain, starting with the last"""
+## for act in reversed(self._actions):
+## act.undo()
+
+##class DisableWidgetAction(Action):
+## def __init__(self, target):
+## """Constructor
+## @param target target treeish
+## """
+## Action.__init__(self)
+## self._target = target
+## self._widget = None
+
+## def do(self):
+## """Action do"""
+## os = ObjectStore()
+## if os.activity:
+## self._widget = gtkutils.find_widget(os.activity, self._target)
+## if self._widget:
+## self._widget.set_sensitive(False)
-class ClickAction(Action):
- """
- Action that simulate a click on a widget
- Work on any widget that implements a clicked() method
+## def undo(self):
+## """Action undo"""
+## if self._widget:
+## self._widget.set_sensitive(True)
- @param widget The threehish representation of the widget
- """
- def __init__(self, widget):
- Action.__init__(self)
- self._widget = widget
- def do(self):
- """
- click the widget
- """
- widget = gtkutils.find_widget(ObjectStore().activity, self._widget)
- if hasattr(widget, "clicked"):
- widget.clicked()
-
- def undo(self):
- """
- No undo
- """
- pass
+##class TypeTextAction(Action):
+## """
+## Simulate a user typing text in a widget
+## Work on any widget that implements a insert_text method
+##
+## @param widget The treehish representation of the widget
+## @param text the text that is typed
+## """
+## def __init__(self, widget, text):
+## Action.__init__(self)
+##
+## self._widget = widget
+## self._text = text
+##
+## def do(self, **kwargs):
+## """
+## Type the text
+## """
+## widget = gtkutils.find_widget(ObjectStore().activity, self._widget)
+## if hasattr(widget, "insert_text"):
+## widget.insert_text(self._text, -1)
+##
+## def undo(self):
+## """
+## no undo
+## """
+## pass
+##
+##class ClickAction(Action):
+## """
+## Action that simulate a click on a widget
+## Work on any widget that implements a clicked() method
+##
+## @param widget The threehish representation of the widget
+## """
+## def __init__(self, widget):
+## Action.__init__(self)
+## self._widget = widget
+##
+## def do(self):
+## """
+## click the widget
+## """
+## widget = gtkutils.find_widget(ObjectStore().activity, self._widget)
+## if hasattr(widget, "clicked"):
+## widget.clicked()
+##
+## def undo(self):
+## """
+## No undo
+## """
+## pass
diff --git a/tutorius/addon.py b/tutorius/addon.py
index 51791d1..e311a65 100644
--- a/tutorius/addon.py
+++ b/tutorius/addon.py
@@ -56,6 +56,12 @@ def create(name, *args, **kwargs):
if not _cache:
_reload_addons()
try:
+ comp_metadata = _cache[name]
+ try:
+ return comp_metadata['class'](*args, **kwargs)
+ except:
+ logging.error("Could not instantiate %s with parameters %s, %s"%(comp_metadata['name'],str(args), str(kwargs)))
+ return None
return _cache[name]['class'](*args, **kwargs)
except KeyError:
logging.error("Addon not found for class '%s'", name)
diff --git a/tutorius/bundler.py b/tutorius/bundler.py
index 8808d93..734c679 100644
--- a/tutorius/bundler.py
+++ b/tutorius/bundler.py
@@ -48,6 +48,46 @@ INI_XML_FSM_PROPERTY = "FSM_FILENAME"
INI_FILENAME = "meta.ini"
TUTORIAL_FILENAME = "tutorial.xml"
NODE_COMPONENT = "Component"
+NODE_SUBCOMPONENT = "SubComponent"
+NODE_SUBCOMPONENTLIST = "SubComponentList"
+
+class Vault(object):
+ """
+ The Vault is the primary interface for the storage and installation of tutorials
+ on the machine. It needs to accomplish the following tasks :
+ - query() : Lists the
+ - installTutorial() :
+ - deleteTutorial() :
+ - readTutorial() :
+ - saveTutorial() :
+ """
+ def query(keyword="", category="", start_index=0, num_results=10):
+ """
+ Returns a list of tutorial meta-data corresponding to the keywords
+ and category mentionned.
+
+ @param keyword The keyword to look for in the tutorial title and description.
+ @param category The category in which to look for tutorials
+ @param start_index The first result to be shown (e.g. )
+ @param num_results The number of results to show
+ @return The list of tutorial metadata that corresponds to the query parameters.
+ """
+ raise NotImplementedError("The query function on the Vault is not implemented")
+
+ def installTutorial(path ,force_install=False):
+ """
+ Inserts the tutorial inside the Vault. Once installed, it will show up
+ """
+ raise NotImplementedError("Installation in the Vault not supported yet")
+
+ def deleteTutorial(tutorial_id):
+ raise NotImplementedError("")
+
+ def readTutorial(tutorial_id):
+ raise NotImplementedError("")
+
+ def saveTutorial(tutorial, metadata, resource_list):
+ raise NotImplementedError("")
class TutorialStore(object):
@@ -150,6 +190,71 @@ class XMLSerializer(Serializer):
eventfiltersList = stateNode.appendChild(self._create_event_filters_node(state.get_event_filter_list(), doc))
return statesList
+ def _create_addon_component_node(self, parent_attr_name, comp, doc):
+ """
+ Takes a component that is embedded in another component (e.g. the content
+ of a OnceWrapper) and encapsulate it in a node with the property name.
+
+ e.g.
+ <Component Class="OnceWrapper">
+ <SubComponent property="addon">
+ <Component Class="BubbleMessage" message="'Hi!'" position="[12,32]"/>
+ </SubComponent>
+ </Component>
+
+ When reloading this node, we should look up the property name for the parent
+ in the attribute of the node, then examine the subnode to create the addon
+ object itself.
+
+ @param parent_attr_name The name of the parent's attribute for this addon
+ e.g. the OnceWrapper has the action attribute, which corresponds to a
+ sub-action it must execute once.
+ @param comp The component node itself
+ @param doc The XML document root (only used to create the nodes)
+ @returns A NODE_SUBCOMPONENT node, with the property attribute and a sub node
+ that represents another component.
+ """
+ subCompNode = doc.createElement(NODE_SUBCOMPONENT)
+ subCompNode.setAttribute("property", parent_attr_name)
+
+ subNode = self._create_component_node(comp, doc)
+
+ subCompNode.appendChild(subNode)
+
+ return subCompNode
+
+ def _create_addonlist_component_node(self, parent_attr_name, comp_list, doc):
+ """
+ Takes a list of components that are embedded in another component (ex. the
+ content of a ChainAction) and encapsulate them in a node with the property
+ name.
+
+ e.g.
+ <Component Class="ChainAction">
+ <SubComponentList property="actions">
+ <Component Class="BubbleMessage" message="'Hi!'" position="[15,35]"/>
+ <Component Class="DialogMessage" message="'Multi-action!'" position="[45,10]"/>
+ </SubComponentList>
+ </Component>
+
+ When reloading this node, we should look up the property name for the parent
+ in the the attribute of the node, then rebuild the list by appending the
+ content of all the subnodes.
+
+ @param parent_attr_name The name of the parent component's property
+ @param comp_list A list of components that comprise the property
+ @param doc The XML document root (only for creating new nodes)
+ @returns A NODE_SUBCOMPONENTLIST node with the property attribute
+ """
+ subCompListNode = doc.createElement(NODE_SUBCOMPONENTLIST)
+ subCompListNode.setAttribute("property", parent_attr_name)
+
+ for comp in comp_list:
+ compNode = self._create_component_node(comp, doc)
+ subCompListNode.appendChild(compNode)
+
+ return subCompListNode
+
def _create_component_node(self, comp, doc):
"""
Takes a single component (action or eventfilter) and transforms it
@@ -169,10 +274,10 @@ class XMLSerializer(Serializer):
for propname in comp.get_properties():
propval = getattr(comp, propname)
if getattr(type(comp), propname).type == "addonlist":
- for subval in propval:
- compNode.appendChild(self._create_component_node(subval, doc))
- elif getattr(type(comp), propname).type == "addonlist":
- compNode.appendChild(self._create_component_node(subval, doc))
+ compNode.appendChild(self._create_addonlist_component_node(propname, propval, doc))
+ elif getattr(type(comp), propname).type == "addon":
+ #import rpdb2; rpdb2.start_embedded_debugger('pass')
+ compNode.appendChild(self._create_addon_component_node(propname, propval, doc))
else:
# repr instead of str, as we want to be able to eval() it into a
# valid object.
@@ -282,6 +387,27 @@ class XMLSerializer(Serializer):
# Error : none of these directories contain the tutorial
raise IOError(2, "Neither the global nor the bundle directory contained the tutorial with GUID %s"%guid)
+ def _get_direct_descendants_by_tag_name(self, node, name):
+ """
+ Searches in the list of direct descendants of a node to find all the node
+ that have the given name.
+
+ This is used because the Document.getElementsByTagName() function returns the
+ list of all the descendants (whatever their distance to the start node) that
+ have that name. In the case of complex components, we absolutely need to inspect
+ a single layer of the tree at the time.
+
+ @param node The node from which we want the direct descendants with a particular
+ name
+ @param name The name of the node
+ @returns A list, possibly empty, of direct descendants of node that have this name
+ """
+ return_list = []
+ for childNode in node.childNodes:
+ if childNode.nodeName == name:
+ return_list.append(childNode)
+ return return_list
+
def _load_xml_properties(self, properties_elem):
"""
Changes a list of properties into fully instanciated properties.
@@ -298,7 +424,7 @@ class XMLSerializer(Serializer):
@param filters_elem An XML Element representing a list of event filters
"""
reformed_event_filters_list = []
- event_filter_element_list = filters_elem.getElementsByTagName(NODE_COMPONENT)
+ event_filter_element_list = self._get_direct_descendants_by_tag_name(filters_elem, NODE_COMPONENT)
new_event_filter = None
for event_filter in event_filter_element_list:
@@ -309,6 +435,42 @@ class XMLSerializer(Serializer):
return reformed_event_filters_list
+ def _load_xml_subcomponents(self, node, properties):
+ """
+ Loads all the subcomponent node below the given node and inserts them with
+ the right property name inside the properties dictionnary.
+
+ @param node The parent node that contains one or many SubComponent nodes.
+ @param properties A dictionnary where the subcomponent property names
+ and the instantiated components will be stored
+ @returns Nothing. The properties dict will contain the property->comp mapping.
+ """
+ subCompList = self._get_direct_descendants_by_tag_name(node, NODE_SUBCOMPONENT)
+
+ for subComp in subCompList:
+ property_name = subComp.getAttribute("property")
+ internal_comp_node = self._get_direct_descendants_by_tag_name(subComp, NODE_COMPONENT)[0]
+ internal_comp = self._load_xml_component(internal_comp_node)
+ properties[str(property_name)] = internal_comp
+
+ def _load_xml_subcomponent_lists(self, node, properties):
+ """
+ Loads all the subcomponent lists below the given node and stores them
+ under the correct property name for that node.
+
+ @param node The node from which we want to read the subComponent lists
+ @param properties The dictionnary that will contain the mapping of prop->subCompList
+ @returns Nothing. The values are returns inside the properties dict.
+ """
+ listOf_subCompListNode = self._get_direct_descendants_by_tag_name(node, NODE_SUBCOMPONENTLIST)
+ for subCompListNode in listOf_subCompListNode:
+ property_name = subCompListNode.getAttribute("property")
+ subCompList = []
+ for subCompNode in self._get_direct_descendants_by_tag_name(subCompListNode, NODE_COMPONENT):
+ subComp = self._load_xml_component(subCompNode)
+ subCompList.append(subComp)
+ properties[str(property_name)] = subCompList
+
def _load_xml_component(self, node):
"""
Loads a single addon component instance from an Xml node.
@@ -318,20 +480,23 @@ class XMLSerializer(Serializer):
@return The addon component object of the correct type according to the XML
description
"""
- new_action = addon.create(node.getAttribute("Class"))
- if not new_action:
- return None
+ class_name = node.getAttribute("Class")
+
+ properties = {}
+
+ for prop in node.attributes.keys():
+ if prop == "Class" : continue
+ # security : keep sandboxed
+ properties[str(prop)] = eval(node.getAttribute(prop))
+
+ # Read the complex attributes
+ self._load_xml_subcomponents(node, properties)
+ self._load_xml_subcomponent_lists(node, properties)
- for attrib in node.attributes.keys():
- if attrib == "Class": continue
- # security note: keep sandboxed
- setattr(new_action, attrib, eval(node.getAttribute(attrib), {}, {}))
+ new_action = addon.create(class_name, **properties)
- # recreate complex attributes
- for sub in node.childNodes:
- name = getattr(new_action, sub.nodeName)
- if name == "addon":
- setattr(new_action, sub.getAttribute("Name"), self._load_xml_action(sub))
+ if not new_action:
+ return None
return new_action
@@ -342,7 +507,7 @@ class XMLSerializer(Serializer):
@param actions_elem An XML Element representing a list of Actions
"""
reformed_actions_list = []
- actions_element_list = actions_elem.getElementsByTagName(NODE_COMPONENT)
+ actions_element_list = self._get_direct_descendants_by_tag_name(actions_elem, NODE_COMPONENT)
for action in actions_element_list:
new_action = self._load_xml_component(action)
diff --git a/tutorius/core.py b/tutorius/core.py
index dd2435e..41089f1 100644
--- a/tutorius/core.py
+++ b/tutorius/core.py
@@ -258,6 +258,42 @@ class State(object):
tutorial.
"""
self._event_filters = []
+
+ def is_identical(self, otherState):
+ """
+ Compares two states and tells whether they contain the same states and
+
+` """
+ if not isinstance(otherState, State):
+ return False
+ if self.name != otherState.name:
+ return False
+
+ # Do they have the same actions?
+ if len(self._actions) != len(otherState._actions):
+ return False
+ for act in self._actions:
+ found = False
+ for otherAct in otherState._actions:
+ if act.is_identical(otherAct):
+ found = True
+ break
+ if found == False:
+ return False
+
+ # Do they have the same event filters?
+ if len(self._actions) != len(otherState._actions):
+ return False
+ for event in self._event_filters:
+ found = False
+ for otherEvent in otherState._event_filters:
+ if event.is_identical(otherEvent):
+ found = True
+ break
+ if found == False:
+ return False
+
+ return True
class FiniteStateMachine(State):
"""
@@ -526,3 +562,43 @@ class FiniteStateMachine(State):
for st in self._states.itervalues():
out_string += st.name + ", "
return out_string
+
+ def is_identical(self, otherFSM):
+ """
+ Compares the elements of two FSM to ensure and returns true if they have the
+ same set of states, containing the same actions and the same event filters.
+
+ @returns True if the two FSMs have the same content false otherwise
+ """
+ if not isinstance(otherFSM, FiniteStateMachine):
+ return False
+
+ if not (self.name == otherFSM.name) or \
+ not (self.start_state_name == otherFSM.start_state_name):
+ return False
+
+ if len(self._actions) != len(otherFSM._actions):
+ return False
+ # Test that we have all the same FSM level actions
+ for act in self._actions:
+ found = False
+ for otherAct in otherFSM._actions:
+ if act.is_identical(otherAct):
+ found = True
+ break
+ if found == False:
+ return False
+
+ if len(self._states) != len(otherFSM._states):
+ return False
+
+ for state in self._states.itervalues():
+ found = False
+ for otherState in otherFSM._states.itervalues():
+ if state.is_identical(otherState):
+ found = True
+ break
+ if found == False:
+ return False
+
+ return True
diff --git a/tutorius/filters.py b/tutorius/filters.py
index aa8c997..fc58562 100644
--- a/tutorius/filters.py
+++ b/tutorius/filters.py
@@ -94,111 +94,111 @@ class EventFilter(properties.TPropContainer):
if self._callback:
self._callback(self)
-class TimerEvent(EventFilter):
- """
- TimerEvent is a special EventFilter that uses gobject
- timeouts to trigger a state change after a specified amount
- of time. It must be used inside a gobject main loop to work.
- """
- def __init__(self,next_state,timeout_s):
- """Constructor.
-
- @param next_state default EventFilter param, passed on to EventFilter
- @param timeout_s timeout in seconds
- """
- super(TimerEvent,self).__init__(next_state)
- self._timeout = timeout_s
- self._handler_id = None
-
- def install_handlers(self, callback, **kwargs):
- """install_handlers creates the timer and starts it"""
- super(TimerEvent,self).install_handlers(callback, **kwargs)
- #Create the timer
- self._handler_id = gobject.timeout_add_seconds(self._timeout, self._timeout_cb)
-
- def remove_handlers(self):
- """remove handler removes the timer"""
- super(TimerEvent,self).remove_handlers()
- if self._handler_id:
- try:
- #XXX What happens if this was already triggered?
- #remove the timer
- gobject.source_remove(self._handler_id)
- except:
- pass
-
- def _timeout_cb(self):
- """
- _timeout_cb triggers the eventfilter callback.
-
- It is necessary because gobject timers only stop if the callback they
- trigger returns False
- """
- self.do_callback()
- return False #Stops timeout
-
-class GtkWidgetTypeFilter(EventFilter):
- """
- Event Filter that listens for keystrokes on a widget
- """
- def __init__(self, next_state, object_id, text=None, strokes=None):
- """Constructor
- @param next_state default EventFilter param, passed on to EventFilter
- @param object_id object tree-ish identifier
- @param text resulting text expected
- @param strokes list of strokes expected
-
- At least one of text or strokes must be supplied
- """
- super(GtkWidgetTypeFilter, self).__init__(next_state)
- self._object_id = object_id
- self._text = text
- self._captext = ""
- self._strokes = strokes
- self._capstrokes = []
- self._widget = None
- self._handler_id = None
-
- def install_handlers(self, callback, **kwargs):
- """install handlers
- @param callback default EventFilter callback arg
- """
- super(GtkWidgetTypeFilter, self).install_handlers(callback, **kwargs)
- logger.debug("~~~GtkWidgetTypeFilter install")
- activity = ObjectStore().activity
- if activity is None:
- logger.error("No activity")
- raise RuntimeWarning("no activity in the objectstore")
-
- self._widget = find_widget(activity, self._object_id)
- if self._widget:
- self._handler_id= self._widget.connect("key-press-event",self.__keypress_cb)
- logger.debug("~~~Connected handler %d on %s" % (self._handler_id,self._object_id) )
-
- def remove_handlers(self):
- """remove handlers"""
- super(GtkWidgetTypeFilter, self).remove_handlers()
- #if an event was connected, disconnect it
- if self._handler_id:
- self._widget.handler_disconnect(self._handler_id)
- self._handler_id=None
-
- def __keypress_cb(self, widget, event, *args):
- """keypress callback"""
- logger.debug("~~~keypressed!")
- key = event.keyval
- keystr = event.string
- logger.debug("~~~Got key: " + str(key) + ":"+ keystr)
- self._capstrokes += [key]
- #TODO Treat other stuff, such as arrows
- if key == gtk.keysyms.BackSpace:
- self._captext = self._captext[:-1]
- else:
- self._captext = self._captext + keystr
-
- logger.debug("~~~Current state: " + str(self._capstrokes) + ":" + str(self._captext))
- if not self._strokes is None and self._strokes in self._capstrokes:
- self.do_callback()
- if not self._text is None and self._text in self._captext:
- self.do_callback()
+##class TimerEvent(EventFilter):
+## """
+## TimerEvent is a special EventFilter that uses gobject
+## timeouts to trigger a state change after a specified amount
+## of time. It must be used inside a gobject main loop to work.
+## """
+## def __init__(self,next_state,timeout_s):
+## """Constructor.
+##
+## @param next_state default EventFilter param, passed on to EventFilter
+## @param timeout_s timeout in seconds
+## """
+## super(TimerEvent,self).__init__(next_state)
+## self._timeout = timeout_s
+## self._handler_id = None
+##
+## def install_handlers(self, callback, **kwargs):
+## """install_handlers creates the timer and starts it"""
+## super(TimerEvent,self).install_handlers(callback, **kwargs)
+## #Create the timer
+## self._handler_id = gobject.timeout_add_seconds(self._timeout, self._timeout_cb)
+##
+## def remove_handlers(self):
+## """remove handler removes the timer"""
+## super(TimerEvent,self).remove_handlers()
+## if self._handler_id:
+## try:
+## #XXX What happens if this was already triggered?
+## #remove the timer
+## gobject.source_remove(self._handler_id)
+## except:
+## pass
+##
+## def _timeout_cb(self):
+## """
+## _timeout_cb triggers the eventfilter callback.
+##
+## It is necessary because gobject timers only stop if the callback they
+## trigger returns False
+## """
+## self.do_callback()
+## return False #Stops timeout
+##
+##class GtkWidgetTypeFilter(EventFilter):
+## """
+## Event Filter that listens for keystrokes on a widget
+## """
+## def __init__(self, next_state, object_id, text=None, strokes=None):
+## """Constructor
+## @param next_state default EventFilter param, passed on to EventFilter
+## @param object_id object tree-ish identifier
+## @param text resulting text expected
+## @param strokes list of strokes expected
+##
+## At least one of text or strokes must be supplied
+## """
+## super(GtkWidgetTypeFilter, self).__init__(next_state)
+## self._object_id = object_id
+## self._text = text
+## self._captext = ""
+## self._strokes = strokes
+## self._capstrokes = []
+## self._widget = None
+## self._handler_id = None
+##
+## def install_handlers(self, callback, **kwargs):
+## """install handlers
+## @param callback default EventFilter callback arg
+## """
+## super(GtkWidgetTypeFilter, self).install_handlers(callback, **kwargs)
+## logger.debug("~~~GtkWidgetTypeFilter install")
+## activity = ObjectStore().activity
+## if activity is None:
+## logger.error("No activity")
+## raise RuntimeWarning("no activity in the objectstore")
+##
+## self._widget = find_widget(activity, self._object_id)
+## if self._widget:
+## self._handler_id= self._widget.connect("key-press-event",self.__keypress_cb)
+## logger.debug("~~~Connected handler %d on %s" % (self._handler_id,self._object_id) )
+##
+## def remove_handlers(self):
+## """remove handlers"""
+## super(GtkWidgetTypeFilter, self).remove_handlers()
+## #if an event was connected, disconnect it
+## if self._handler_id:
+## self._widget.handler_disconnect(self._handler_id)
+## self._handler_id=None
+##
+## def __keypress_cb(self, widget, event, *args):
+## """keypress callback"""
+## logger.debug("~~~keypressed!")
+## key = event.keyval
+## keystr = event.string
+## logger.debug("~~~Got key: " + str(key) + ":"+ keystr)
+## self._capstrokes += [key]
+## #TODO Treat other stuff, such as arrows
+## if key == gtk.keysyms.BackSpace:
+## self._captext = self._captext[:-1]
+## else:
+## self._captext = self._captext + keystr
+##
+## logger.debug("~~~Current state: " + str(self._capstrokes) + ":" + str(self._captext))
+## if not self._strokes is None and self._strokes in self._capstrokes:
+## self.do_callback()
+## if not self._text is None and self._text in self._captext:
+## self.do_callback()
diff --git a/tutorius/properties.py b/tutorius/properties.py
index abf76e5..6d30a8d 100644
--- a/tutorius/properties.py
+++ b/tutorius/properties.py
@@ -95,6 +95,39 @@ class TPropContainer(object):
"""
return object.__getattribute__(self, "_props").keys()
+ def is_identical(self, otherContainer):
+ for prop in self._props.keys():
+ found = False
+ for otherProp in otherContainer._props.keys():
+ if prop == otherProp:
+ this_type = getattr(type(self), prop).type
+ other_type = getattr(type(otherContainer), prop).type
+ if this_type != other_type:
+ return False
+ if this_type == "addonlist":
+ for inner_cont in self._props[prop]:
+ inner_found = False
+ for other_inner in otherContainer._props[prop]:
+ if inner_cont.is_identical(other_inner):
+ inner_found = True
+ break
+ if inner_found == False:
+ return False
+ found = True
+ break
+ elif this_type == "addon":
+ if not self._props[prop].is_identical(otherContainer._props[prop]):
+ return False
+ found = True
+ break
+ else:
+ if self._props[prop]== otherContainer._props[prop]:
+ found = True
+ break
+ if found == False:
+ return False
+ return True
+
class TutoriusProperty(object):
"""
The base class for all actions' properties. The interface is the following :
@@ -317,8 +350,15 @@ class TAddonListProperty(TutoriusProperty):
See TAddonProperty
"""
def __init__(self):
- super(TAddonProperty, self).__init__()
+ TutoriusProperty.__init__(self)
self.type = "addonlist"
self.default = []
+ def validate(self, value):
+ if isinstance(value, list):
+ for component in value:
+ if not (isinstance(component, TPropContainer)):
+ raise ValueError("Expected a list of TPropContainer instances inside TAddonListProperty value, got a %s" % (str(type(component))))
+ return value
+ raise ValueError("Value proposed to TAddonListProperty is not a list")
diff --git a/tutorius/uam/__init__.py b/tutorius/uam/__init__.py
index 7cf5671..bcd67e1 100644
--- a/tutorius/uam/__init__.py
+++ b/tutorius/uam/__init__.py
@@ -65,7 +65,8 @@ for subscheme in [".".join([SCHEME,s]) for s in __parsers]:
class SchemeError(Exception):
def __init__(self, message):
Exception.__init__(self, message)
- self.message = message
+ ## Commenting this line as it is causing an error in the tests
+ ##self.message = message
def parse_uri(uri):