Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilip Withnall <philip@tecnocode.co.uk>2013-09-03 22:15:47 (GMT)
committer Philip Withnall <philip@tecnocode.co.uk>2013-09-03 22:15:47 (GMT)
commitdcc0955e675e19b0e8f1ac12bd4312cba5fac233 (patch)
treeb4bd39a4b7624560d811e647e803c46b481f7125
parent69473370bd1166538599afcb2913ff8c632cf995 (diff)
Add English exercises for part 3 of the course
-rw-r--r--Makefile29
-rw-r--r--exercises/en/16_gnome-devel-demos.txt28
-rw-r--r--exercises/en/17_button-and-label.py64
-rw-r--r--exercises/en/17_button-and-label_solution.py77
-rw-r--r--exercises/en/18_gtk-to-activity.txt3
-rw-r--r--exercises/en/19_translation.txt3
-rw-r--r--exercises/en/20_packaging.txt2
-rw-r--r--exercises/en/21_complete-activity.txt27
-rw-r--r--exercises/en/Exercise18.activity/activity/activity.info9
-rw-r--r--exercises/en/Exercise18.activity/activity/activity.svg163
-rw-r--r--exercises/en/Exercise18.activity/exercise18.py99
-rw-r--r--exercises/en/Exercise18.activity/exercise18_solution.py101
-rwxr-xr-xexercises/en/Exercise18.activity/setup.py4
-rw-r--r--exercises/en/Exercise19.activity/activity/activity.info9
-rw-r--r--exercises/en/Exercise19.activity/activity/activity.svg163
-rw-r--r--exercises/en/Exercise19.activity/exercise19.py106
-rw-r--r--exercises/en/Exercise19.activity/exercise19_solution.py107
-rwxr-xr-xexercises/en/Exercise19.activity/setup.py4
-rw-r--r--exercises/en/Exercise20.activity/.gitignore3
-rw-r--r--exercises/en/Exercise20.activity/activity/activity.info9
-rw-r--r--exercises/en/Exercise20.activity/exercise20.py76
-rw-r--r--exercises/en/Exercise20.activity/images/logonew2.pngbin0 -> 8818 bytes
-rwxr-xr-xexercises/en/Exercise20.activity/setup.py4
23 files changed, 1086 insertions, 4 deletions
diff --git a/Makefile b/Makefile
index 2653337..8467d7f 100644
--- a/Makefile
+++ b/Makefile
@@ -19,6 +19,7 @@ EN_EXERCISES = \
13_extend-class \
14_property \
15_private-methods \
+ 17_button-and-label \
$(NULL)
ES_EXERCISES = \
1_imprimir-nombres \
@@ -53,6 +54,7 @@ ZIPS = \
python-ejercicios.zip \
python-exercises.zip \
$(NULL)
+
EN_PYTHON_FILES = \
$(addprefix exercises/en/,$(addsuffix .py,$(EN_EXERCISES))) \
$(addprefix exercises/en/,$(addsuffix _solution.py,$(EN_EXERCISES))) \
@@ -63,6 +65,25 @@ ES_PYTHON_FILES = \
$(addprefix exercises/es/,$(addsuffix _solucion.py,$(ES_EXERCISES))) \
$(NULL)
+EN_EXERCISE_FILES = \
+ $(EN_PYTHON_FILES) \
+ exercises/en/16_gnome-devel-demos.txt \
+ exercises/en/17_button-and-label.py \
+ exercises/en/17_button-and-label_solution.py \
+ exercises/en/Exercise18.activity/ \
+ exercises/en/18_gtk-to-activity.txt \
+ exercises/en/Exercise19.activity/ \
+ exercises/en/19_translation.txt \
+ exercises/en/Exercise20.activity/ \
+ exercises/en/20_packaging.txt \
+ exercises/en/21_complete-activity.txt \
+ $(NULL)
+
+ES_EXERCISE_FILES = \
+ $(ES_PYTHON_FILES) \
+ exercises/es/16_gnome-devel-demos.txt \
+ $(NULL)
+
all: $(PDFS) $(ZIPS)
presentation.pdf: presentation.tex $(TEX_FILES) $(IMAGE_FILES)
@@ -74,15 +95,15 @@ handout.pdf: handout.tex $(TEX_FILES) $(IMAGE_FILES)
pdflatex "\providecommand\locale{$(LANG)}\input{$<}"
# Zip up the exercises.
-python-exercises.zip: $(EN_PYTHON_FILES)
+python-exercises.zip: $(EN_EXERCISE_FILES)
zip --quiet --no-dir-entries $@ $^
-python-ejercicios.zip: $(ES_PYTHON_FILES)
+python-ejercicios.zip: $(ES_EXERCISE_FILES)
zip --quiet --no-dir-entries $@ $^
check:
- $(foreach ex,$(EN_EXERCISES),pep8 exercises/en/${ex}.py exercises/en/${ex}_solution.py;)
- $(foreach ex,$(ES_EXERCISES),pep8 exercises/es/${ex}.py exercises/es/${ex}_solucion.py;)
+ pep8 $(EN_PYTHON_FILES)
+ pep8 $(ES_PYTHON_FILES)
clean:
rm -f $(PDFS:.pdf=.aux)
diff --git a/exercises/en/16_gnome-devel-demos.txt b/exercises/en/16_gnome-devel-demos.txt
new file mode 100644
index 0000000..a1c013e
--- /dev/null
+++ b/exercises/en/16_gnome-devel-demos.txt
@@ -0,0 +1,28 @@
+Exercise 16 is not assessed. Instead, you should go to:
+ https://developer.gnome.org/gnome-devel-demos/unstable/tutorial.py.html.en
+and work through the following examples. For each example, copy the code into
+a file and run it using Python. Read the documentation on the web pages and
+ensure you understand how the example works.
+
+You should look at the following tutorial pages, at a minimum:
+ • 1.1
+ • 2.1
+ • 2.2
+ • 2.3
+ • 3.1
+ • 4.1
+ • 4.2
+ • 4.3
+ • 4.4
+ • 5.1
+ • 5.2
+ • 5.4
+ • 5.5
+ • 5.7
+ • 7.3
+ • 8.2
+ • 8.3
+ • 9.1
+ • 11.3
+
+Note that tutorial page 14.1 is covered separately in a later exercise.
diff --git a/exercises/en/17_button-and-label.py b/exercises/en/17_button-and-label.py
new file mode 100644
index 0000000..2eee908
--- /dev/null
+++ b/exercises/en/17_button-and-label.py
@@ -0,0 +1,64 @@
+
+#!/usr/bin/python
+# coding=utf-8
+
+"""This program should display a GTK+ window containing a button and label.
+
+You must add two widgets to the box in the window: a Gtk.Button and a
+Gtk.Label.
+
+The Gtk.Button must show the text 'Change Label'. The Gtk.Label must show the
+text 'Initial label.'.
+
+After you have added the two widgets, you must connect a callback to the
+button's "clicked" signal. In this callback, you must change the Gtk.Label's
+text to 'Final label.'. This means that when the program is run and the button
+is clicked, the label should change its text.
+
+You can find documentation for the Gtk.Button "clicked" signal here:
+ https://developer.gnome.org/gtk3/stable/GtkButton.html#GtkButton-clicked
+
+Don't forget to document any new methods you add.
+
+Hint: Don't forget to call the show() method on the button and label to make
+them visible after adding them to the box.
+"""
+
+from gi.repository import Gtk
+
+
+class SimpleWindow(Gtk.Window):
+ """A simple window which displays a label and a button."""
+ def __init__(self):
+ super(SimpleWindow, self).__init__(title='Simple Window')
+
+ # Set a default size for the window.
+ self.set_default_size(200, 200)
+
+ # Set up a close handler for the window. Do not modify this.
+ self.connect('destroy', self.__window_destroy_cb)
+
+ # Add a box to the window.
+ box = Gtk.Box()
+ box.set_orientation(Gtk.Orientation.VERTICAL)
+ box.set_spacing(8)
+ box.set_border_width(8)
+ box.show()
+ self.add(box)
+
+ # You must add code here to create and set up the button and label.
+ # Don't forget to make them visible and add them to the box.
+
+ # You must add a signal callback here to handle the button's "clicked"
+ # signal and change the label's text.
+
+ def __window_destroy_cb(self, window):
+ """Callback to quit the program when the window is closed.
+
+ Do not modify this."""
+ Gtk.main_quit()
+
+
+# Run the program. Do not modify this.
+SimpleWindow().show()
+Gtk.main()
diff --git a/exercises/en/17_button-and-label_solution.py b/exercises/en/17_button-and-label_solution.py
new file mode 100644
index 0000000..30d644b
--- /dev/null
+++ b/exercises/en/17_button-and-label_solution.py
@@ -0,0 +1,77 @@
+#!/usr/bin/python
+# coding=utf-8
+
+"""This program should display a GTK+ window containing a button and label.
+
+You must add two widgets to the box in the window: a Gtk.Button and a
+Gtk.Label.
+
+The Gtk.Button must show the text 'Change Label'. The Gtk.Label must show the
+text 'Initial label.'.
+
+After you have added the two widgets, you must connect a callback to the
+button's "clicked" signal. In this callback, you must change the Gtk.Label's
+text to 'Final label.'. This means that when the program is run and the button
+is clicked, the label should change its text.
+
+You can find documentation for the Gtk.Button "clicked" signal here:
+ https://developer.gnome.org/gtk3/stable/GtkButton.html#GtkButton-clicked
+
+Don't forget to document any new methods you add.
+
+Hint: Don't forget to call the show() method on the button and label to make
+them visible after adding them to the box.
+"""
+
+from gi.repository import Gtk
+
+
+class SimpleWindow(Gtk.Window):
+ """A simple window which displays a label and a button."""
+ def __init__(self):
+ super(SimpleWindow, self).__init__(title='Simple Window')
+
+ # Set a default size for the window.
+ self.set_default_size(200, 200)
+
+ # Set up a close handler for the window. Do not modify this.
+ self.connect('destroy', self.__window_destroy_cb)
+
+ # Add a box to the window.
+ box = Gtk.Box()
+ box.set_orientation(Gtk.Orientation.VERTICAL)
+ box.set_spacing(8)
+ box.set_border_width(8)
+ box.show()
+ self.add(box)
+
+ # Create and set up the label and button.
+ label = Gtk.Label('Initial label.')
+ label.show()
+ box.pack_start(label, True, True, 0)
+
+ button = Gtk.Button('Change Label')
+ button.show()
+ box.pack_start(button, False, True, 0)
+
+ # Connect the button callback.
+ button.connect('clicked', self.__button_clicked_cb)
+
+ # Store the label as an object variable so it can be accessed from the
+ # button callback.
+ self._label = label
+
+ def __button_clicked_cb(self, button):
+ """Callback to change the label text when the button is clicked."""
+ self._label.set_text('Final label.')
+
+ def __window_destroy_cb(self, window):
+ """Callback to quit the program when the window is closed.
+
+ Do not modify this."""
+ Gtk.main_quit()
+
+
+# Run the program. Do not modify this.
+SimpleWindow().show()
+Gtk.main()
diff --git a/exercises/en/18_gtk-to-activity.txt b/exercises/en/18_gtk-to-activity.txt
new file mode 100644
index 0000000..8836099
--- /dev/null
+++ b/exercises/en/18_gtk-to-activity.txt
@@ -0,0 +1,3 @@
+The instructions for exercise 18 are in Exercise18.activity/exercise18.py,
+which is the only file you need to modify.
+The example solution is in Exercise18.activity/exercise18_solution.py.
diff --git a/exercises/en/19_translation.txt b/exercises/en/19_translation.txt
new file mode 100644
index 0000000..520f31a
--- /dev/null
+++ b/exercises/en/19_translation.txt
@@ -0,0 +1,3 @@
+The instructions for exercise 19 are in Exercise19.activity/exercise19.py.
+The example solution is in Exercise19.activity/exercise19_solution.py. No
+example solution files are provided for the POT and PO files.
diff --git a/exercises/en/20_packaging.txt b/exercises/en/20_packaging.txt
new file mode 100644
index 0000000..c43ffeb
--- /dev/null
+++ b/exercises/en/20_packaging.txt
@@ -0,0 +1,2 @@
+The instructions for exercise 20 are in Exercise20.activity/exercise20.py. There
+is no example solution, but there are detailed instructions to follow.
diff --git a/exercises/en/21_complete-activity.txt b/exercises/en/21_complete-activity.txt
new file mode 100644
index 0000000..325275a
--- /dev/null
+++ b/exercises/en/21_complete-activity.txt
@@ -0,0 +1,27 @@
+Exercise 20
+
+For this final exercise, you must create a new Sugar activity from scratch. You
+have two options of activities to create:
+ A. An ‘odd one out’ game. It should display four buttons on the screen, each
+ with a conjugation of a verb as its text. The four conjugations should be
+ different, and one should be incorrect. The user must click on the button
+ with the incorrect conjugation. If they do, their score is increased and
+ another set of four conjugations is displayed. If they click on the wrong
+ button, their score is not incremented and the conjugations do not change.
+ The user must click the odd one out to proceed.
+ The user’s score should be displayed in a label in the toolbar (ask if you
+ need help with this, or look at the code in PascalTriangle which does a
+ similar thing by putting a GtkScale in its toolbar).
+ B. Similar to the above game, but using mathematical equalities instead of
+ verb conjugations. The game should present the user with four equalities
+ (e.g. “5 × 6 + 3 = 33”, “(1 + 4) × 2 = 10”, etc.), one of which should be
+ incorrect. The user must pick the incorrect one to proceed.
+
+Once you’ve created the activity, tested it in emulation and tested it on a
+physical XO, you should get someone else to review all of your code and see if
+they can find any problems with it. Code review like this is always helpful, as
+every programmer makes mistakes (no matter how experienced they are) and a
+fresh pair of eyes can spot them. Ideally, your code should be reviewed by
+someone who wrote the other type of activity to you, so they have to rely on
+comments in your code to work out how the code functions. Remember to comment
+your code well!
diff --git a/exercises/en/Exercise18.activity/activity/activity.info b/exercises/en/Exercise18.activity/activity/activity.info
new file mode 100644
index 0000000..e0ffeda
--- /dev/null
+++ b/exercises/en/Exercise18.activity/activity/activity.info
@@ -0,0 +1,9 @@
+[Activity]
+name = Exercise 18
+summary = Python exercise 18. Not a real activity.
+activity_version = 1
+host_version = 1
+bundle_id = hn.educatrachos.exercise18
+icon = activity
+exec = sugar-activity exercise18.Exercise18Activity
+license = GPLv2+
diff --git a/exercises/en/Exercise18.activity/activity/activity.svg b/exercises/en/Exercise18.activity/activity/activity.svg
new file mode 100644
index 0000000..580b0e6
--- /dev/null
+++ b/exercises/en/Exercise18.activity/activity/activity.svg
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#000000">
+ <!ENTITY fill_color "#FFFFFF">
+]>
+
+<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="svg2985"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="New document 2">
+ <defs
+ id="defs2987" />
+ <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="1024"
+ inkscape:window-height="552"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata2990">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="star"
+ style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:bevel;stroke-opacity:1"
+ id="path2999"
+ sodipodi:sides="6"
+ sodipodi:cx="10.142857"
+ sodipodi:cy="10.714286"
+ sodipodi:r1="5.2527933"
+ sodipodi:r2="4.5490522"
+ sodipodi:arg1="0.78539816"
+ sodipodi:arg2="1.3089969"
+ inkscape:flatsided="true"
+ inkscape:rounded="-0.15648138"
+ inkscape:randomized="0"
+ d="m 13.857142,14.428572 c 0.581217,-0.581217 -5.8677649,1.146783 -5.0738083,1.359523 0.7939566,0.21274 -3.9270258,-4.508243 -3.7142858,-3.714286 0.21274,0.793956 1.9407395,-5.6550255 1.3595229,-5.0738089 -0.5812165,0.5812166 5.8677652,-1.1467829 5.0738092,-1.359523 -0.793957,-0.21274 3.927025,4.5082419 3.714285,3.7142858 -0.21274,-0.7939566 -1.940739,5.6550251 -1.359523,5.0738091 z"
+ transform="matrix(1.5454814,-0.41411048,0.41411048,1.5454814,3.8875073,0.04153494)" />
+ <g
+ id="g3815"
+ transform="matrix(1.6,0,0,1.6,-13.485714,-7.4857143)">
+ <path
+ transform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,6.5725486,11.990245)"
+ d="m 13.857142,14.428572 c 0.581217,-0.581217 -5.8677649,1.146783 -5.0738083,1.359523 0.7939566,0.21274 -3.9270258,-4.508243 -3.7142858,-3.714286 0.21274,0.793956 1.9407395,-5.6550255 1.3595229,-5.0738089 -0.5812165,0.5812166 5.8677652,-1.1467829 5.0738092,-1.359523 -0.793957,-0.21274 3.927025,4.5082419 3.714285,3.7142858 -0.21274,-0.7939566 -1.940739,5.6550251 -1.359523,5.0738091 z"
+ inkscape:randomized="0"
+ inkscape:rounded="-0.15648138"
+ inkscape:flatsided="true"
+ sodipodi:arg2="1.3089969"
+ sodipodi:arg1="0.78539816"
+ sodipodi:r2="4.5490522"
+ sodipodi:r1="5.2527933"
+ sodipodi:cy="10.714286"
+ sodipodi:cx="10.142857"
+ sodipodi:sides="6"
+ id="path2999-6"
+ style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:bevel;stroke-opacity:1"
+ sodipodi:type="star" />
+ <path
+ transform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,15.143977,11.990245)"
+ d="m 13.857142,14.428572 c 0.581217,-0.581217 -5.8677649,1.146783 -5.0738083,1.359523 0.7939566,0.21274 -3.9270258,-4.508243 -3.7142858,-3.714286 0.21274,0.793956 1.9407395,-5.6550255 1.3595229,-5.0738089 -0.5812165,0.5812166 5.8677652,-1.1467829 5.0738092,-1.359523 -0.793957,-0.21274 3.927025,4.5082419 3.714285,3.7142858 -0.21274,-0.7939566 -1.940739,5.6550251 -1.359523,5.0738091 z"
+ inkscape:randomized="0"
+ inkscape:rounded="-0.15648138"
+ inkscape:flatsided="true"
+ sodipodi:arg2="1.3089969"
+ sodipodi:arg1="0.78539816"
+ sodipodi:r2="4.5490522"
+ sodipodi:r1="5.2527933"
+ sodipodi:cy="10.714286"
+ sodipodi:cx="10.142857"
+ sodipodi:sides="6"
+ id="path2999-60"
+ style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:bevel;stroke-opacity:1"
+ sodipodi:type="star" />
+ </g>
+ <g
+ id="g3819"
+ transform="matrix(1.6,0,0,1.6,-13.6,-9.0857135)">
+ <path
+ transform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,2.2868344,20.20453)"
+ d="m 13.857142,14.428572 c 0.581217,-0.581217 -5.8677649,1.146783 -5.0738083,1.359523 0.7939566,0.21274 -3.9270258,-4.508243 -3.7142858,-3.714286 0.21274,0.793956 1.9407395,-5.6550255 1.3595229,-5.0738089 -0.5812165,0.5812166 5.8677652,-1.1467829 5.0738092,-1.359523 -0.793957,-0.21274 3.927025,4.5082419 3.714285,3.7142858 -0.21274,-0.7939566 -1.940739,5.6550251 -1.359523,5.0738091 z"
+ inkscape:randomized="0"
+ inkscape:rounded="-0.15648138"
+ inkscape:flatsided="true"
+ sodipodi:arg2="1.3089969"
+ sodipodi:arg1="0.78539816"
+ sodipodi:r2="4.5490522"
+ sodipodi:r1="5.2527933"
+ sodipodi:cy="10.714286"
+ sodipodi:cx="10.142857"
+ sodipodi:sides="6"
+ id="path2999-0"
+ style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:bevel;stroke-opacity:1"
+ sodipodi:type="star" />
+ <path
+ transform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,10.929691,20.20453)"
+ d="m 13.857142,14.428572 c 0.581217,-0.581217 -5.8677649,1.146783 -5.0738083,1.359523 0.7939566,0.21274 -3.9270258,-4.508243 -3.7142858,-3.714286 0.21274,0.793956 1.9407395,-5.6550255 1.3595229,-5.0738089 -0.5812165,0.5812166 5.8677652,-1.1467829 5.0738092,-1.359523 -0.793957,-0.21274 3.927025,4.5082419 3.714285,3.7142858 -0.21274,-0.7939566 -1.940739,5.6550251 -1.359523,5.0738091 z"
+ inkscape:randomized="0"
+ inkscape:rounded="-0.15648138"
+ inkscape:flatsided="true"
+ sodipodi:arg2="1.3089969"
+ sodipodi:arg1="0.78539816"
+ sodipodi:r2="4.5490522"
+ sodipodi:r1="5.2527933"
+ sodipodi:cy="10.714286"
+ sodipodi:cx="10.142857"
+ sodipodi:sides="6"
+ id="path2999-3"
+ style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:bevel;stroke-opacity:1"
+ sodipodi:type="star" />
+ <path
+ transform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,19.572549,20.20453)"
+ d="m 13.857142,14.428572 c 0.581217,-0.581217 -5.8677649,1.146783 -5.0738083,1.359523 0.7939566,0.21274 -3.9270258,-4.508243 -3.7142858,-3.714286 0.21274,0.793956 1.9407395,-5.6550255 1.3595229,-5.0738089 -0.5812165,0.5812166 5.8677652,-1.1467829 5.0738092,-1.359523 -0.793957,-0.21274 3.927025,4.5082419 3.714285,3.7142858 -0.21274,-0.7939566 -1.940739,5.6550251 -1.359523,5.0738091 z"
+ inkscape:randomized="0"
+ inkscape:rounded="-0.15648138"
+ inkscape:flatsided="true"
+ sodipodi:arg2="1.3089969"
+ sodipodi:arg1="0.78539816"
+ sodipodi:r2="4.5490522"
+ sodipodi:r1="5.2527933"
+ sodipodi:cy="10.714286"
+ sodipodi:cx="10.142857"
+ sodipodi:sides="6"
+ id="path2999-1"
+ style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:bevel;stroke-opacity:1"
+ sodipodi:type="star" />
+ </g>
+ </g>
+</svg>
diff --git a/exercises/en/Exercise18.activity/exercise18.py b/exercises/en/Exercise18.activity/exercise18.py
new file mode 100644
index 0000000..753eb4a
--- /dev/null
+++ b/exercises/en/Exercise18.activity/exercise18.py
@@ -0,0 +1,99 @@
+#!/usr/bin/python
+# coding=utf-8
+
+"""Convert a simple GTK+ program to be a Sugar activity.
+
+You must convert the simple GTK+ program from exercise 17 to be a Sugar
+activity.
+
+All the other files for the activity (activity.info, setup.py, etc.) have been
+provided already; you must simply modify the code in this file to derive the
+class from sugar3.activity.activity.Activity instead of from Gtk.Window, and
+make a few other changes.
+
+ 1. Remove the “#!/usr/bin/python” line from the top of the file.
+ The first line of the file should now be “# coding=utf-8”.
+ 2. Rename the class to “Exercise18Activity”.
+ 3. Add a ‘handle’ parameter to __init__ and propagate it to the parent
+ constructor in the super() call.
+ 4. Remove the window title from the constructor super() call.
+ 5. Remove the call to set_default_size() and the window destroy callback.
+ 6. Add an activity toolbar (ToolbarBox), and add an activity button
+ (ActivityToolbarButton) and a stop button (StopButton) to it.
+ 7. Change the self.add() call to self.set_canvas().
+ 8. In a terminal, change to this directory and run ‘./setup.py dev’ to add the
+ activity to Sugar on your computer.
+ 9. In a terminal, run ‘sugar-emulator’ and test the activity.
+
+Note that the necessary ‘import’ statements have already been added to the
+file.
+
+Don't forget to document any new methods you add.
+
+Hint: The code necessary for step 6 is:
+ # Create the standard activity toolbox.
+ toolbar_box = ToolbarBox()
+ self.set_toolbar_box(toolbar_box)
+ toolbar_box.show()
+
+ main_toolbar = toolbar_box.toolbar
+
+ activity_toolbar_button = widgets.ActivityToolbarButton(self)
+ main_toolbar.insert(activity_toolbar_button, 0)
+ activity_toolbar_button.show()
+
+ stop_button = widgets.StopButton(self)
+ stop_button.show()
+ main_toolbar.insert(stop_button, -1)
+"""
+
+from gi.repository import Gtk
+from sugar3.activity import activity, widgets
+from sugar3.graphics.toolbarbox import ToolbarBox
+from sugar3.graphics.toolbutton import ToolButton
+
+
+class SimpleWindow(Gtk.Window):
+ """A simple window which displays a label and a button."""
+ def __init__(self):
+ super(SimpleWindow, self).__init__(title='Simple Window')
+
+ # Set a default size for the window.
+ self.set_default_size(200, 200)
+
+ # Set up a close handler for the window. Do not modify this.
+ self.connect('destroy', self.__window_destroy_cb)
+
+ # Add a box to the window.
+ box = Gtk.Box()
+ box.set_orientation(Gtk.Orientation.VERTICAL)
+ box.set_spacing(8)
+ box.set_border_width(8)
+ box.show()
+ self.add(box)
+
+ # Create and set up the label and button.
+ label = Gtk.Label('Initial label.')
+ label.show()
+ box.pack_start(label, True, True, 0)
+
+ button = Gtk.Button('Change Label')
+ button.show()
+ box.pack_start(button, False, True, 0)
+
+ # Connect the button callback.
+ button.connect('clicked', self.__button_clicked_cb)
+
+ # Store the label as an object variable so it can be accessed from the
+ # button callback.
+ self._label = label
+
+ def __button_clicked_cb(self, button):
+ """Callback to change the label text when the button is clicked."""
+ self._label.set_text('Final label.')
+
+ def __window_destroy_cb(self, window):
+ """Callback to quit the program when the window is closed.
+
+ Do not modify this."""
+ Gtk.main_quit()
diff --git a/exercises/en/Exercise18.activity/exercise18_solution.py b/exercises/en/Exercise18.activity/exercise18_solution.py
new file mode 100644
index 0000000..da880e8
--- /dev/null
+++ b/exercises/en/Exercise18.activity/exercise18_solution.py
@@ -0,0 +1,101 @@
+# coding=utf-8
+
+"""Convert a simple GTK+ program to be a Sugar activity.
+
+You must convert the simple GTK+ program from exercise 17 to be a Sugar
+activity.
+
+All the other files for the activity (activity.info, setup.py, etc.) have been
+provided already; you must simply modify the code in this file to derive the
+class from sugar3.activity.activity.Activity instead of from Gtk.Window, and
+make a few other changes.
+
+ 1. Remove the “#!/usr/bin/python” line from the top of the file.
+ The first line of the file should now be “# coding=utf-8”.
+ 2. Rename the class to “Exercise18Activity”.
+ 3. Add a ‘handle’ parameter to __init__ and propagate it to the parent
+ constructor in the super() call.
+ 4. Remove the window title from the constructor super() call.
+ 5. Remove the call to set_default_size() and the window destroy callback.
+ 6. Add an activity toolbar (ToolbarBox), and add an activity button
+ (ActivityToolbarButton) and a stop button (StopButton) to it.
+ 7. Change the self.add() call to self.set_canvas().
+ 8. In a terminal, change to this directory and run ‘./setup.py dev’ to add the
+ activity to Sugar on your computer.
+ 9. In a terminal, run ‘sugar-emulator’ and test the activity.
+
+Note that the necessary ‘import’ statements have already been added to the
+file.
+
+Don't forget to document any new methods you add.
+
+Hint: The code necessary for step 6 is:
+ # Create the standard activity toolbox.
+ toolbar_box = ToolbarBox()
+ self.set_toolbar_box(toolbar_box)
+ toolbar_box.show()
+
+ main_toolbar = toolbar_box.toolbar
+
+ activity_toolbar_button = widgets.ActivityToolbarButton(self)
+ main_toolbar.insert(activity_toolbar_button, 0)
+ activity_toolbar_button.show()
+
+ stop_button = widgets.StopButton(self)
+ stop_button.show()
+ main_toolbar.insert(stop_button, -1)
+"""
+
+from gi.repository import Gtk
+from sugar3.activity import activity, widgets
+from sugar3.graphics.toolbarbox import ToolbarBox
+from sugar3.graphics.toolbutton import ToolButton
+
+
+class Exercise18Activity(activity.Activity):
+ """A simple window which displays a label and a button."""
+ def __init__(self, handle):
+ super(Exercise18Activity, self).__init__(handle)
+
+ # Create the standard activity toolbox.
+ toolbar_box = ToolbarBox()
+ self.set_toolbar_box(toolbar_box)
+ toolbar_box.show()
+
+ main_toolbar = toolbar_box.toolbar
+
+ activity_toolbar_button = widgets.ActivityToolbarButton(self)
+ main_toolbar.insert(activity_toolbar_button, 0)
+ activity_toolbar_button.show()
+
+ stop_button = widgets.StopButton(self)
+ stop_button.show()
+ main_toolbar.insert(stop_button, -1)
+
+ # Add a box to the window.
+ box = Gtk.Box()
+ box.set_orientation(Gtk.Orientation.VERTICAL)
+ box.set_spacing(8)
+ box.set_border_width(8)
+ box.show()
+ self.set_canvas(box)
+
+ # Create and set up the label and button.
+ label = Gtk.Label('Initial label.')
+ label.show()
+ box.pack_start(label, True, True, 0)
+
+ button = Gtk.Button('Change Label')
+ button.show()
+ box.pack_start(button, False, True, 0)
+
+ # Connect the button callback.
+ button.connect('clicked', self.__button_clicked_cb)
+
+ # Store the label as an object variable so it can be accessed from the
+ # button callback.
+ self._label = label
+
+ def __button_clicked_cb(self, button):
+ """Callback to change the label text when the button is clicked."""
+ self._label.set_text('Final label.')
diff --git a/exercises/en/Exercise18.activity/setup.py b/exercises/en/Exercise18.activity/setup.py
new file mode 100755
index 0000000..77fda74
--- /dev/null
+++ b/exercises/en/Exercise18.activity/setup.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+from sugar.activity import bundlebuilder
+if __name__ == "__main__":
+ bundlebuilder.start()
diff --git a/exercises/en/Exercise19.activity/activity/activity.info b/exercises/en/Exercise19.activity/activity/activity.info
new file mode 100644
index 0000000..3a0739c
--- /dev/null
+++ b/exercises/en/Exercise19.activity/activity/activity.info
@@ -0,0 +1,9 @@
+[Activity]
+name = Exercise 19
+summary = Python exercise 19. Not a real activity.
+activity_version = 1
+host_version = 1
+bundle_id = hn.educatrachos.exercise19
+icon = activity
+exec = sugar-activity exercise19.Exercise19Activity
+license = GPLv2+
diff --git a/exercises/en/Exercise19.activity/activity/activity.svg b/exercises/en/Exercise19.activity/activity/activity.svg
new file mode 100644
index 0000000..580b0e6
--- /dev/null
+++ b/exercises/en/Exercise19.activity/activity/activity.svg
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#000000">
+ <!ENTITY fill_color "#FFFFFF">
+]>
+
+<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="svg2985"
+ version="1.1"
+ inkscape:version="0.48.4 r9939"
+ sodipodi:docname="New document 2">
+ <defs
+ id="defs2987" />
+ <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="1024"
+ inkscape:window-height="552"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata2990">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:type="star"
+ style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:bevel;stroke-opacity:1"
+ id="path2999"
+ sodipodi:sides="6"
+ sodipodi:cx="10.142857"
+ sodipodi:cy="10.714286"
+ sodipodi:r1="5.2527933"
+ sodipodi:r2="4.5490522"
+ sodipodi:arg1="0.78539816"
+ sodipodi:arg2="1.3089969"
+ inkscape:flatsided="true"
+ inkscape:rounded="-0.15648138"
+ inkscape:randomized="0"
+ d="m 13.857142,14.428572 c 0.581217,-0.581217 -5.8677649,1.146783 -5.0738083,1.359523 0.7939566,0.21274 -3.9270258,-4.508243 -3.7142858,-3.714286 0.21274,0.793956 1.9407395,-5.6550255 1.3595229,-5.0738089 -0.5812165,0.5812166 5.8677652,-1.1467829 5.0738092,-1.359523 -0.793957,-0.21274 3.927025,4.5082419 3.714285,3.7142858 -0.21274,-0.7939566 -1.940739,5.6550251 -1.359523,5.0738091 z"
+ transform="matrix(1.5454814,-0.41411048,0.41411048,1.5454814,3.8875073,0.04153494)" />
+ <g
+ id="g3815"
+ transform="matrix(1.6,0,0,1.6,-13.485714,-7.4857143)">
+ <path
+ transform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,6.5725486,11.990245)"
+ d="m 13.857142,14.428572 c 0.581217,-0.581217 -5.8677649,1.146783 -5.0738083,1.359523 0.7939566,0.21274 -3.9270258,-4.508243 -3.7142858,-3.714286 0.21274,0.793956 1.9407395,-5.6550255 1.3595229,-5.0738089 -0.5812165,0.5812166 5.8677652,-1.1467829 5.0738092,-1.359523 -0.793957,-0.21274 3.927025,4.5082419 3.714285,3.7142858 -0.21274,-0.7939566 -1.940739,5.6550251 -1.359523,5.0738091 z"
+ inkscape:randomized="0"
+ inkscape:rounded="-0.15648138"
+ inkscape:flatsided="true"
+ sodipodi:arg2="1.3089969"
+ sodipodi:arg1="0.78539816"
+ sodipodi:r2="4.5490522"
+ sodipodi:r1="5.2527933"
+ sodipodi:cy="10.714286"
+ sodipodi:cx="10.142857"
+ sodipodi:sides="6"
+ id="path2999-6"
+ style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:bevel;stroke-opacity:1"
+ sodipodi:type="star" />
+ <path
+ transform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,15.143977,11.990245)"
+ d="m 13.857142,14.428572 c 0.581217,-0.581217 -5.8677649,1.146783 -5.0738083,1.359523 0.7939566,0.21274 -3.9270258,-4.508243 -3.7142858,-3.714286 0.21274,0.793956 1.9407395,-5.6550255 1.3595229,-5.0738089 -0.5812165,0.5812166 5.8677652,-1.1467829 5.0738092,-1.359523 -0.793957,-0.21274 3.927025,4.5082419 3.714285,3.7142858 -0.21274,-0.7939566 -1.940739,5.6550251 -1.359523,5.0738091 z"
+ inkscape:randomized="0"
+ inkscape:rounded="-0.15648138"
+ inkscape:flatsided="true"
+ sodipodi:arg2="1.3089969"
+ sodipodi:arg1="0.78539816"
+ sodipodi:r2="4.5490522"
+ sodipodi:r1="5.2527933"
+ sodipodi:cy="10.714286"
+ sodipodi:cx="10.142857"
+ sodipodi:sides="6"
+ id="path2999-60"
+ style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:bevel;stroke-opacity:1"
+ sodipodi:type="star" />
+ </g>
+ <g
+ id="g3819"
+ transform="matrix(1.6,0,0,1.6,-13.6,-9.0857135)">
+ <path
+ transform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,2.2868344,20.20453)"
+ d="m 13.857142,14.428572 c 0.581217,-0.581217 -5.8677649,1.146783 -5.0738083,1.359523 0.7939566,0.21274 -3.9270258,-4.508243 -3.7142858,-3.714286 0.21274,0.793956 1.9407395,-5.6550255 1.3595229,-5.0738089 -0.5812165,0.5812166 5.8677652,-1.1467829 5.0738092,-1.359523 -0.793957,-0.21274 3.927025,4.5082419 3.714285,3.7142858 -0.21274,-0.7939566 -1.940739,5.6550251 -1.359523,5.0738091 z"
+ inkscape:randomized="0"
+ inkscape:rounded="-0.15648138"
+ inkscape:flatsided="true"
+ sodipodi:arg2="1.3089969"
+ sodipodi:arg1="0.78539816"
+ sodipodi:r2="4.5490522"
+ sodipodi:r1="5.2527933"
+ sodipodi:cy="10.714286"
+ sodipodi:cx="10.142857"
+ sodipodi:sides="6"
+ id="path2999-0"
+ style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:bevel;stroke-opacity:1"
+ sodipodi:type="star" />
+ <path
+ transform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,10.929691,20.20453)"
+ d="m 13.857142,14.428572 c 0.581217,-0.581217 -5.8677649,1.146783 -5.0738083,1.359523 0.7939566,0.21274 -3.9270258,-4.508243 -3.7142858,-3.714286 0.21274,0.793956 1.9407395,-5.6550255 1.3595229,-5.0738089 -0.5812165,0.5812166 5.8677652,-1.1467829 5.0738092,-1.359523 -0.793957,-0.21274 3.927025,4.5082419 3.714285,3.7142858 -0.21274,-0.7939566 -1.940739,5.6550251 -1.359523,5.0738091 z"
+ inkscape:randomized="0"
+ inkscape:rounded="-0.15648138"
+ inkscape:flatsided="true"
+ sodipodi:arg2="1.3089969"
+ sodipodi:arg1="0.78539816"
+ sodipodi:r2="4.5490522"
+ sodipodi:r1="5.2527933"
+ sodipodi:cy="10.714286"
+ sodipodi:cx="10.142857"
+ sodipodi:sides="6"
+ id="path2999-3"
+ style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:bevel;stroke-opacity:1"
+ sodipodi:type="star" />
+ <path
+ transform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,19.572549,20.20453)"
+ d="m 13.857142,14.428572 c 0.581217,-0.581217 -5.8677649,1.146783 -5.0738083,1.359523 0.7939566,0.21274 -3.9270258,-4.508243 -3.7142858,-3.714286 0.21274,0.793956 1.9407395,-5.6550255 1.3595229,-5.0738089 -0.5812165,0.5812166 5.8677652,-1.1467829 5.0738092,-1.359523 -0.793957,-0.21274 3.927025,4.5082419 3.714285,3.7142858 -0.21274,-0.7939566 -1.940739,5.6550251 -1.359523,5.0738091 z"
+ inkscape:randomized="0"
+ inkscape:rounded="-0.15648138"
+ inkscape:flatsided="true"
+ sodipodi:arg2="1.3089969"
+ sodipodi:arg1="0.78539816"
+ sodipodi:r2="4.5490522"
+ sodipodi:r1="5.2527933"
+ sodipodi:cy="10.714286"
+ sodipodi:cx="10.142857"
+ sodipodi:sides="6"
+ id="path2999-1"
+ style="fill:&fill_color;;fill-rule:evenodd;stroke:&stroke_color;;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:bevel;stroke-opacity:1"
+ sodipodi:type="star" />
+ </g>
+ </g>
+</svg>
diff --git a/exercises/en/Exercise19.activity/exercise19.py b/exercises/en/Exercise19.activity/exercise19.py
new file mode 100644
index 0000000..d912d00
--- /dev/null
+++ b/exercises/en/Exercise19.activity/exercise19.py
@@ -0,0 +1,106 @@
+# coding=utf-8
+
+"""Add internationalisation support to a Sugar activity.
+
+Below is a simple Sugar activity which displays a different string every time
+the button is pressed. You must modify it to add internationalisation support
+so that its UI can be viewed in a different language.
+
+All the other files for the activity (activity.info, setup.py, etc.) have been
+provided already; you must modify the code in this file to add
+internationalisation support, generate a POT file, and then translate it to
+Spanish.
+
+Note that the original strings in the program are always in English, by
+convention.
+
+ 1. Add an ‘import’ statement for the gettext module:
+ from gettext import gettext as _
+ 2. Mark all the relevant strings in the program as translatable using the _()
+ function.
+ 3. Generate a POT file by executing ‘./setup.py genpot’ in a terminal (after
+ changing to this directory).
+ 4. Copy the generated po/Exercise19.pot file to po/es.po.
+ 5. Either open es.po in a text editor, or use a translation tool like
+ gtranslator, and translate the strings to Spanish.
+ 6. Run ‘./setup.py dev’ to add the activity to Sugar on your computer.
+ 7. Run ‘./setup.py build’ to compile the .po files into machine-readable .mo
+ files in the ‘locale’ directory.
+ 7. Run ‘LANG=es sugar-emulator’ to run the Sugar emulator in Spanish and test
+ the new translation. The whole activity should appear in Spanish.
+
+Note: It is not possible to use Pootle for this exercise because it’s an online
+service and would require the exercise’s code to be uploaded to the internet.
+"""
+
+from gi.repository import Gtk
+from sugar3.activity import activity, widgets
+from sugar3.graphics.toolbarbox import ToolbarBox
+
+
+class Exercise19Activity(activity.Activity):
+ """A simple window which displays a label and a button."""
+ def __init__(self, handle):
+ super(Exercise19Activity, self).__init__(handle)
+
+ # Create the standard activity toolbox.
+ toolbar_box = ToolbarBox()
+ self.set_toolbar_box(toolbar_box)
+ toolbar_box.show()
+
+ main_toolbar = toolbar_box.toolbar
+
+ activity_toolbar_button = widgets.ActivityToolbarButton(self)
+ main_toolbar.insert(activity_toolbar_button, 0)
+ activity_toolbar_button.show()
+
+ stop_button = widgets.StopButton(self)
+ stop_button.show()
+ main_toolbar.insert(stop_button, -1)
+
+ # Add a box to the window.
+ box = Gtk.Box()
+ box.set_orientation(Gtk.Orientation.VERTICAL)
+ box.set_spacing(8)
+ box.set_border_width(8)
+ box.show()
+ self.set_canvas(box)
+
+ # Set up a list of text strings to display in the label.
+ self._strings = (
+ 'Initial string.',
+ 'Second string.',
+ 'Handle object is ‘%s’.' % handle,
+ 'Another string.',
+ )
+ self._next_string = 0
+
+ # Create and set up the label and button.
+ label = Gtk.Label()
+ label.show()
+ box.pack_start(label, True, True, 0)
+
+ button = Gtk.Button('Change Label')
+ button.show()
+ box.pack_start(button, False, True, 0)
+
+ # Connect the button callback.
+ button.connect('clicked', self.__button_clicked_cb)
+
+ # Store the label as an object variable so it can be accessed from the
+ # button callback, and then set the initial text for the label.
+ self._label = label
+ self.__rotate_label_text()
+
+ def __button_clicked_cb(self, button):
+ """Callback to change the label text when the button is clicked."""
+ self.__rotate_label_text()
+
+ def __rotate_label_text(self):
+ """Change the label to display the next string in the rotation.
+
+ The modulus operation is used to wrap self._next_string around when
+ it reaches the final index in the self._strings array.
+ """
+ self._label.set_text(self._strings[self._next_string])
+ self._next_string = (self._next_string + 1) % len(self._strings)
diff --git a/exercises/en/Exercise19.activity/exercise19_solution.py b/exercises/en/Exercise19.activity/exercise19_solution.py
new file mode 100644
index 0000000..4818446
--- /dev/null
+++ b/exercises/en/Exercise19.activity/exercise19_solution.py
@@ -0,0 +1,107 @@
+# coding=utf-8
+
+"""Add internationalisation support to a Sugar activity.
+
+Below is a simple Sugar activity which displays a different string every time
+the button is pressed. You must modify it to add internationalisation support
+so that its UI can be viewed in a different language.
+
+All the other files for the activity (activity.info, setup.py, etc.) have been
+provided already; you must modify the code in this file to add
+internationalisation support, generate a POT file, and then translate it to
+Spanish.
+
+Note that the original strings in the program are always in English, by
+convention.
+
+ 1. Add an ‘import’ statement for the gettext module:
+ from gettext import gettext as _
+ 2. Mark all the relevant strings in the program as translatable using the _()
+ function.
+ 3. Generate a POT file by executing ‘./setup.py genpot’ in a terminal (after
+ changing to this directory).
+ 4. Copy the generated po/Exercise19.pot file to po/es.po.
+ 5. Either open es.po in a text editor, or use a translation tool like
+ gtranslator, and translate the strings to Spanish.
+ 6. Run ‘./setup.py dev’ to add the activity to Sugar on your computer.
+ 7. Run ‘./setup.py build’ to compile the .po files into machine-readable .mo
+ files in the ‘locale’ directory.
+ 7. Run ‘LANG=es sugar-emulator’ to run the Sugar emulator in Spanish and test
+ the new translation. The whole activity should appear in Spanish.
+
+Note: It is not possible to use Pootle for this exercise because it’s an online
+service and would require the exercise’s code to be uploaded to the internet.
+"""
+
+from gi.repository import Gtk
+from sugar3.activity import activity, widgets
+from sugar3.graphics.toolbarbox import ToolbarBox
+from gettext import gettext as _
+
+
+class Exercise19Activity(activity.Activity):
+ """A simple window which displays a label and a button."""
+ def __init__(self, handle):
+ super(Exercise19Activity, self).__init__(handle)
+
+ # Create the standard activity toolbox.
+ toolbar_box = ToolbarBox()
+ self.set_toolbar_box(toolbar_box)
+ toolbar_box.show()
+
+ main_toolbar = toolbar_box.toolbar
+
+ activity_toolbar_button = widgets.ActivityToolbarButton(self)
+ main_toolbar.insert(activity_toolbar_button, 0)
+ activity_toolbar_button.show()
+
+ stop_button = widgets.StopButton(self)
+ stop_button.show()
+ main_toolbar.insert(stop_button, -1)
+
+ # Add a box to the window.
+ box = Gtk.Box()
+ box.set_orientation(Gtk.Orientation.VERTICAL)
+ box.set_spacing(8)
+ box.set_border_width(8)
+ box.show()
+ self.set_canvas(box)
+
+ # Set up a list of text strings to display in the label.
+ self._strings = (
+ _('Initial string.'),
+ _('Second string.'),
+ _('Handle object is ‘%s’.') % handle,
+ _('Another string.'),
+ )
+ self._next_string = 0
+
+ # Create and set up the label and button.
+ label = Gtk.Label()
+ label.show()
+ box.pack_start(label, True, True, 0)
+
+ button = Gtk.Button(_('Change Label'))
+ button.show()
+ box.pack_start(button, False, True, 0)
+
+ # Connect the button callback.
+ button.connect('clicked', self.__button_clicked_cb)
+
+ # Store the label as an object variable so it can be accessed from the
+ # button callback, and then set the initial text for the label.
+ self._label = label
+ self.__rotate_label_text()
+
+ def __button_clicked_cb(self, button):
+ """Callback to change the label text when the button is clicked."""
+ self.__rotate_label_text()
+
+ def __rotate_label_text(self):
+ """Change the label to display the next string in the rotation.
+
+ The modulus operation is used to wrap self._next_string around when
+ it reaches the final index in the self._strings array.
+ """
+ self._label.set_text(self._strings[self._next_string])
+ self._next_string = (self._next_string + 1) % len(self._strings)
diff --git a/exercises/en/Exercise19.activity/setup.py b/exercises/en/Exercise19.activity/setup.py
new file mode 100755
index 0000000..77fda74
--- /dev/null
+++ b/exercises/en/Exercise19.activity/setup.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+from sugar.activity import bundlebuilder
+if __name__ == "__main__":
+ bundlebuilder.start()
diff --git a/exercises/en/Exercise20.activity/.gitignore b/exercises/en/Exercise20.activity/.gitignore
new file mode 100644
index 0000000..6a592c9
--- /dev/null
+++ b/exercises/en/Exercise20.activity/.gitignore
@@ -0,0 +1,3 @@
+*.pyc
+dist/
+locale/
diff --git a/exercises/en/Exercise20.activity/activity/activity.info b/exercises/en/Exercise20.activity/activity/activity.info
new file mode 100644
index 0000000..d258ee6
--- /dev/null
+++ b/exercises/en/Exercise20.activity/activity/activity.info
@@ -0,0 +1,9 @@
+[Activity]
+name = Exercise 20
+summary = Python exercise 20. Not a real activity.
+activity_version = 1
+host_version = 1
+bundle_id = hn.educatrachos.exercise20
+icon = activity
+exec = sugar-activity exercise20.Exercise20Activity
+license = GPLv2+
diff --git a/exercises/en/Exercise20.activity/exercise20.py b/exercises/en/Exercise20.activity/exercise20.py
new file mode 100644
index 0000000..766f453
--- /dev/null
+++ b/exercises/en/Exercise20.activity/exercise20.py
@@ -0,0 +1,76 @@
+# coding=utf-8
+
+"""Package a Sugar activity to produce a bundle.
+
+Below is a simple Sugar activity which displays a static image. It is very
+boring, but does not need changing. In this exercise, you must add the relevant
+files to git and then build a bundle for the activity and install it on a
+physical XO laptop.
+
+Before packaging the activity, an icon needs to be created.
+
+ 1. Open Inkscape and create a new 48×48 pixel icon (‘icon_48x48’ template).
+ 2. Draw the icon, using only a single stroke colour and a single background
+ colour. Save it as activity/activity.svg.
+ 3. Open the created SVG file with a text editor and replace the stroke colours
+ as described in the ‘Create An Icon’ here:
+ http://en.flossmanuals.net/make-your-own-sugar-activities/package-the-activity/
+ 4. Execute the following commands in a terminal open in the
+ Exercise20.activity/ directory. They will add the relevant files to git:
+ git init
+ git status
+ git add .gitignore exercise20.py setup.py images/logonew2.png
+ git add activity/activity.info
+ git add activity/activity.svg
+ git status
+ git commit
+ They create a new git repository in the current directory, check its
+ status, add the required files to the repository, check its status again
+ to ensure we don’t commit anything we don’t mean to, then commit the new
+ files and changes to the repository.
+
+ The .gitignore file is a special file which lists files that git shouldn’t
+ allow you to add to the repository, and which will be hidden from the
+ output of `git status`.
+ 5. Run ‘./setup.py dist_xo’ to build an XO bundle.
+ 6. Copy the dist/Exercise20-1.xo file to a USB stick and install it on a
+ physical XO laptop.
+"""
+
+from gi.repository import Gtk
+from sugar3.activity import activity, widgets
+from sugar3.graphics.toolbarbox import ToolbarBox
+import os
+
+
+class Exercise20Activity(activity.Activity):
+ """A simple window which displays a static image."""
+ def __init__(self, handle):
+ super(Exercise20Activity, self).__init__(handle)
+
+ # Create the standard activity toolbox.
+ toolbar_box = ToolbarBox()
+ self.set_toolbar_box(toolbar_box)
+ toolbar_box.show()
+
+ main_toolbar = toolbar_box.toolbar
+
+ activity_toolbar_button = widgets.ActivityToolbarButton(self)
+ main_toolbar.insert(activity_toolbar_button, 0)
+ activity_toolbar_button.show()
+
+ stop_button = widgets.StopButton(self)
+ stop_button.show()
+ main_toolbar.insert(stop_button, -1)
+
+ # Add an image to the window.
+ box = Gtk.Box()
+ box.show()
+ self.set_canvas(box)
+
+ image = Gtk.Image()
+ filename = os.path.join(os.path.dirname(__file__),
+ 'images', 'logonew2.png')
+ image.set_from_file(filename)
+ image.show()
+ box.pack_start(image, True, True, 8)
diff --git a/exercises/en/Exercise20.activity/images/logonew2.png b/exercises/en/Exercise20.activity/images/logonew2.png
new file mode 100644
index 0000000..d523b0b
--- /dev/null
+++ b/exercises/en/Exercise20.activity/images/logonew2.png
Binary files differ
diff --git a/exercises/en/Exercise20.activity/setup.py b/exercises/en/Exercise20.activity/setup.py
new file mode 100755
index 0000000..77fda74
--- /dev/null
+++ b/exercises/en/Exercise20.activity/setup.py
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+from sugar.activity import bundlebuilder
+if __name__ == "__main__":
+ bundlebuilder.start()