Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPierre Métras <pierre@alterna.tv>2008-11-15 04:25:59 (GMT)
committer Pierre Métras <pierre@alterna.tv>2008-11-15 04:25:59 (GMT)
commit5dc157569de2087873deb5af431767196dd175e6 (patch)
tree7f63ff4f794cee3e95837bbd58b06f6d5e92d6a9
Initial import (version 5)v5
-rw-r--r--MANIFEST32
-rw-r--r--NEWS24
-rw-r--r--README174
-rwxr-xr-xactivity/activity-clock.svg16
-rw-r--r--activity/activity.info9
-rwxr-xr-xclock.py699
-rw-r--r--clock.svg238
-rw-r--r--dist/Clock-3.xobin0 -> 21511 bytes
-rw-r--r--dist/Clock-4.xobin0 -> 21430 bytes
-rw-r--r--dist/Clock-5.xobin0 -> 53330 bytes
-rwxr-xr-xicons/digital-clock.svg9
-rwxr-xr-xicons/nice-clock.svg14
-rwxr-xr-xicons/simple-clock.svg13
-rwxr-xr-xicons/speak-time.svg11
-rwxr-xr-xicons/write-date-long.svg11
-rwxr-xr-xicons/write-date.svg10
-rwxr-xr-xicons/write-day.svg18
-rwxr-xr-xicons/write-time.svg9
-rw-r--r--locale/en/LC_MESSAGES/tv.alterna.Clock.mobin0 -> 6347 bytes
-rw-r--r--locale/en/activity.linfo2
-rw-r--r--locale/es/LC_MESSAGES/tv.alterna.Clock.mobin0 -> 6936 bytes
-rw-r--r--locale/es/activity.linfo2
-rw-r--r--locale/fr/LC_MESSAGES/tv.alterna.Clock.mobin0 -> 5985 bytes
-rw-r--r--locale/fr/activity.linfo2
-rwxr-xr-xmisc/activity-clock.svg16
-rw-r--r--pgettext.py37
-rw-r--r--po/Clock.pot179
-rw-r--r--po/en.po303
-rw-r--r--po/es.po305
-rw-r--r--po/fr.po288
-rwxr-xr-xsetup.py13
-rw-r--r--speaker.py71
-rw-r--r--test_timewriter/__init__.py9
-rw-r--r--test_timewriter/en_rules.py127
-rw-r--r--test_timewriter/es_rules.py127
-rw-r--r--test_timewriter/fr_rules.py110
-rw-r--r--timewriter.py522
37 files changed, 3400 insertions, 0 deletions
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..e2a999e
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,32 @@
+po/fr.po
+po/Clock.pot
+po/es.po
+po/en.po
+clock.py
+locale/en/activity.linfo
+locale/en/LC_MESSAGES/tv.alterna.Clock.mo
+locale/es/activity.linfo
+locale/es/LC_MESSAGES/tv.alterna.Clock.mo
+locale/fr/activity.linfo
+locale/fr/LC_MESSAGES/tv.alterna.Clock.mo
+activity/activity-clock.svg
+activity/activity.info
+clock.svg
+setup.py
+README
+NEWS
+timewriter.py
+pgettext.py
+speaker.py
+icons/write-day.svg
+icons/write-date.svg
+icons/nice-clock.svg
+icons/speak-time.svg
+icons/write-time.svg
+icons/simple-clock.svg
+icons/digital-clock.svg
+icons/write-date-long.svg
+test_timewriter/en_rules.py
+test_timewriter/es_rules.py
+test_timewriter/__init__.py
+test_timewriter/fr_rules.py
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..33d0460
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,24 @@
+A simple Clock face to know the time of the day, and eventually learn how to read it.
+
+20080129 1.0 - Initial creation of the Basic Clock.
+ - 3 display modes: simple, with hours ticks and date, nice clock face.
+
+20080321 2.0 - Added a digital clock with two views: basic and complete.
+ - Support context in gettext with custom function pgettext().
+ - Changed fonts as they could not display some characters in Portuguese. Use Pango to layout text now.
+ - Added a color code to help learning time. Made clock hands a bit wider too.
+ - A few bugs solved and made code more complex to read!
+
+20080923 3.0 - Added license information required by the new 8.2 activity bundle format.
+
+20080925 4.0 - When the activity is not active, we don't update the clock to save CPU resources.
+
+20081022 5.0 - Refactoring of the code: cairo drawing has been removed and we use Gdk now. The big drawing method has been splitted in small and easy to understand methods.
+ - Display the date and weekday combined out of the clock face.
+ - Use XO approved colors for better contrast.
+ - New toolbar instead of the hidden right button. Only 3 display modes have been kept for the clock faces: simple, nice and digital.
+ - New activity icon.
+ - Write the current time in full letters, with the same colors code for the clock hands.
+ - Inference engine to translate a numeric time to letters. Three set of localized rules, for English, French and Spanish have been provided as model for translatorsand procedures to test them.
+ - Speak aloud the current time through espeak TTS.
+ - Most methods are now private.
diff --git a/README b/README
new file mode 100644
index 0000000..e10c9bf
--- /dev/null
+++ b/README
@@ -0,0 +1,174 @@
+Notes about the Clock activity
+
+License
+=======
+This activity and its code is put under public domain. Do whatever you want with it!
+If you want to contact me: Pierre Métras <pierre@alterna.tv>
+
+
+Localization of the Clock
+=========================
+The code for the Clock activity tries to be easily localizable to be used in Sugar with different languages and locales. To achieve that, we tried to put into gettext dictionnary all elements which could be culturally different, like the fonts used to display texts in the clock. We used also Pango to have a better font management and give the translators the ability to adapt the display of text to their locale.
+
+The Clock uses small messages. As with GUI software, it could be that the same messages have to be used in different contexts, and sometimes localized with different values (depending of the context). For instance, the font used to print the date in the Analog and Digital clock views are the same in the English/USA environment, but the font for the Digital one has to be reduced when using the Indi environment.
+GNU gettext tool provides the pgettext() function to deal with such situations (see http://www.gnu.org/software/gettext/manual/gettext.html#Ambiguities). Saddly, the Python binding of gettext does not offer this function. I had to define a custom pgettext() and use it as a workaround, while we wait to have it included in a future Python version.
+
+
+Note to translators: how to translate Pango markup?
+---------------------------------------------------
+Usage of Pango markup gives you a great deal of flexibility in the localization of the Clock activity. Let's take an example from the po/fr.po French localization file:
+
+# TRANS: datetime.strftime() format
+#, no-python-format
+msgctxt "Digital Clock"
+msgid "<markup><span lang=\"en\" font_desc=\"Sans Bold 80\">%x</span></markup>
+msgstr "<markup><span lang=\"fr\" font_desc=\"Sans Bold 70\">%d %B %Y</span></markup>
+
+First, I changed the language code from "en" to "fr". Using the right language code let Pango determine the right rules for the layout of the text, for instance from right to left generally for Arabic.
+
+Second, the "%x" format was not correctly supported by the internalization libraries on Build 653: it printed the date as "2008-03-26" instead of "26/03/2008" in French usage. So I decided to use a custom date layout to work around that bug. While at it, I decided that I would display the month name in full letters instead of the number of the month, like "26 mars 2008", with the strftime format "%d %B %Y".
+
+After having changed the date format, I felt that some dates, like "31 octobre 2008" won't display correctly with all display orientations, and I went to reduce the font size from 80 pt to 70 pt. You could also decide to change the font family and use a better font instead of "Sans".
+
+Don't hesitate to adapt the Clock to your local needs...
+
+
+How to write the time in full letters for your locale?
+------------------------------------------------------
+The activity use a small inference engine to translate a numeric time to words. This translation is done following rules which have to be localized. I've provided 3 examples, for English, French and Spanish.
+This time in full letters is then used by the Text To Speech engine espeak to read aloud the time on demand or when minutes change.
+
+The inference engine tries to find a rule whose pattern is "time(h,m)" where the variable "h" is bound to the current hours and the variable "m" is bound to then current minutes. The rules are scanned in the order they are written, until we find one matching.
+
+For the match to succeed, we can add range conditions with the syntax "[val1 < val2]" or "[val1 < val2 < val3]" after the rule pattern. For instance, if we want to write two rules for hours in the morning and the afternoon, we could write:
+ time(h, m) [h < 12] => ... |
+ time(h, m) [11 < h] => ... |
+
+As you can see from the previous example, the conditions of the rules precedes the sign "=>" which specifies the right side of the rules with the actions to execute when the match on the left side succeeds. The rules ends at the "|" character.
+
+On the right side of a rule, after the "=>" sign, you can put a combination of text or other rules calls. The text is used as-is and substituted in the result. If it's a rule call, the inference engine will try again from the start to find a matching rule. For instance:
+ time(12, 0) => noon |
+ time(24, 0) => midnight |
+ time(h, 0) => number(h) o'clock |
+With the first two rules, the engine will display "noon" or "midnight" when called respectively with the values "time(12, 0)" or "time(24, 0)". With the last rule, when called with the pattern "time(h, 0)" where "h" is value different from 12 or 24 (because caught by the two previous rules), the engine will try to find a rule name "number(_)" and when found and matched, will replace the value returned in the text to be concatenated with the text "o'clock".
+
+Inside right side of rules, that is between "=>" and "|", spaces are significative. You can use the operator "#" to concatenate two pieces of text or rules calls, like in:
+ hour(h) => number(h) hour#plural(h) |
+ plural(1) => |
+ plural(_) => s |
+Use "\#" to get the character "#". This is usefull when using pango markup in the text.
+
+As you have probably understod from now, rules patterns or calls use functional syntax: the name of the rule, with arguments between parenthesis. Numerical arguments are considered constants (could be hours or minutes, for example), while alphabetic are variables, bould during the pattern matching or call. The special variable "_" (underscore) is a anonymous variable: its value is not important to the rule firing. For instance, in the previous example, then rule "plural(_) => |" can be read "the plural of anything which is not 1 is 's'".
+
+To test your set of rules, you can create the file 'test_timewriter/LANG_rules.py' where LANG is the ISO code for your language. It's simpler to copy an existing file from the test_timewriter directory and adapt it to your language.
+In that file, you just have to declare the '_time_rules' variable containing the source code of your rules. For instances, something like the following:
+ #! /usr/bin/env python
+ # -*- coding: utf-8 -*-
+ _time_rules = """
+ time(h, m) => ...
+ """
+Then, you can check your rules with the command:
+ $ python timewriter.py LANG
+where LANG is the ISO code for your language. It will display all the rules analyzed by the timewriter parser, and then display all times from 0:00 to 23:59.
+
+
+Pango markup in the rules
+-------------------------
+As this activity is aimed at learning how to read the time, we have to offer ways for the children to recognize the important parts of the time expression. The use of a consistent color conventions is part of the job. Hours are displayed in blue, minutes in green, seconds in red as frequently seen in analog clock faces.
+
+This color convention has to be kept, whenever possible, in the time displayed in full letters. The colors have been selected to maximize contrast on the XO. It means that the inference rules used to write the time must include pango markup around the different parts of the time in the sentence, like:
+
+ time(h, m) [m < 31] => <span foreground="\#00B20D">min(m)</span> past <span foreground="\#005FE4">hour(h)</span> am_pm(h) |
+
+As this reduce the readability of the rules, I suggest you to write the set of localized rules without the coloring, and later add the pango codes.
+
+The color codes to use:
+- Hours blue: #005FE4
+- Minutes green: #00B20D
+- Seconds red: #E6000A (You probably never have to use this one)
+- Days dark red: #B20008
+- Months purple: #5E008C
+- Years brown: #9A5200
+
+
+How to debug your localization file?
+------------------------------------
+As you've seen from previous paragraphs, a lot of parameters and logic for the activity is taken from the localization file. It could happen that a change or translation you've done breaks the activity.
+To find where the problem lies, it helps to start the Log activity and look at the log file tv.alterna.Clock-N.log. You could find some hints where Python or Sugard breaks when running the activity.
+You can also send me an email for assistance, to <pierre@alterna.tv>. Don't forget to attach your locale file and some explanations where it breaks.
+
+
+Commands used to generate localized messages files
+==================================================
+CAREFULL: _p() Python method is mapped to pgettext(msgctxt, msgstr).
+
+The following procedure has to be done from a XO or a computer using UTF-8 as system encoding.
+
+Clock$ is the prompt when I'm in the Clock directory.
+
+First run
+---------
+# Create the first messages template
+Clock$ xgettext --output=po/Clock.pot --add-comment --keyword=_p:1c,2 clock.py timewriter.py speaker.py
+
+# Adapt po/Clock.pot file with author, copyright, etc.
+Clock$ vi po/Clock.pot
+
+# Initialize a new locale for French
+Clock$ msginit --input=po/Clock.pot --output-file=po/fr.po --locale=fr
+
+# Translate the messages in French
+Clock$ vi po/fr.po
+
+# Create the binary messages file
+Clock$ msgfmt --output-file=locale/fr/LC_MESSAGES/tv.alterna.Clock.mo --check po/fr.po
+
+Messages update
+---------------
+When the sources have changed
+
+# Extract the new messages from the source like upper, in the po/Clock.pot file.
+
+# Update a translated message file
+Clock$ $ msgmerge --update --backup=simple po/fr.po po/Clock.pot
+
+# Translate the new messages and generate the binary file.
+
+
+
+BUGS
+====
+- SOLVED: Python does not support pgettext (particular gettext). So it makes it difficult to localize identical short strings used in different contexts. clock.py includes a custom function _p(). Better localization support has been added to Python 2.6, in particular pgettext.
+
+- SOLVED: Python 2.5 as used on OLPC XO has a bug with datetime.strftime(pattern), when pattern is longer than around 100 characters. time.strftime() does not exhibit that bug and is used as a workaround. Python 2.5.1 seems not to have this bug. http://bugs.python.org/issue2490.
+Version 5 of the Clock activity was developped specifically for XO build 8.2.0 which solved this bug. Use version 4 of the Clock with older builds.
+
+- Whenever the label where the time is displayed becomes wider than the screen width, forinstance when you rotate the display, it would be nice to have line wrapping. I was unable to get it working. The Label.set_line_wrap(True) method does not give the expected result.
+
+- SOLVED: The first time the talking clock or the "time in letters" toolbar button ispressed, their is a few seconds latence when Python loads the code. Some can think that these buttons have no action; please wait a few seconds... This effect has been lowered by using threads in GObject library.
+
+
+POINT OF INTEREST IN THE CODE
+=============================
+- Developped at 99% on a XO. I was only missing a SVG editor for the icons. But even the icons where hand crafted...
+
+- How to create a new GTK+ widget and paint its content.
+
+- The use of pango markup to offer better flexibility in localization.
+
+- The signal "notify::active" sent by Sugar to an activity when it comes on the front. In this activity, we stop refreshing the display when we are not active.
+
+- Full pydoc documentation.
+
+- Using the badly documented gobject.threads_init() to have better messages processing.
+
+
+TODO
+====
+A drop box for ideas. While the goal is to keep the source simple and understandable by Python starters, here are listed some ideas of features to include in the Clock activity to help learning how to read the time and have at the same time a usefull clock.
+- An alarm clock mode
+- A stopwatch mode
+- Print the timezone
+- A talking clock: speaking the time aloud every minute. DONE in version 5.
+- A Big Ben chime...
+- Add a second toolbar to print calendars and to learn how to read the date.
diff --git a/activity/activity-clock.svg b/activity/activity-clock.svg
new file mode 100755
index 0000000..47926ab
--- /dev/null
+++ b/activity/activity-clock.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
+[
+ <!ENTITY fill_color "#FFFFFF">
+ <!ENTITY stroke_color "#000000">
+]>
+<svg contentScriptType="text/ecmascript" width="55px" xmlns:xlink="http://www.w3.org/1999/xlink" baseProfile="full" zoomAndPan="magnify" contentStyleType="text/css" height="55px" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" version="1.1">
+ <g id="Icon">
+ <circle fill="&fill_color;" r="22.5" stroke-width="3.5" cx="27.5" cy="27.5" stroke="&stroke_color;"/>
+ <line fill="none" x1="27.5" x2="27.5" y1="10" y2="14" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="none" x1="27.5" x2="27.5" y1="41" y2="45" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="none" x1="41" x2="45" y1="27.5" y2="27.5" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="none" x1="10" x2="14" y1="27.5" y2="27.5" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line stroke-linecap="round" fill="none" x1="37.5" x2="27.5" y1="14" y2="27.5" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line stroke-linecap="round" fill="none" x1="27.5" x2="38" y1="27.5" y2="34" stroke="&stroke_color;" stroke-width="3.5"/>
+ </g>
+</svg>
diff --git a/activity/activity.info b/activity/activity.info
new file mode 100644
index 0000000..dd17c5f
--- /dev/null
+++ b/activity/activity.info
@@ -0,0 +1,9 @@
+[Activity]
+name = Clock
+service_name = tv.alterna.Clock
+class = clock.ClockActivity
+icon = activity-clock
+activity_version = 5
+host_version = 1
+show_launcher = yes
+license=Public Domain
diff --git a/clock.py b/clock.py
new file mode 100755
index 0000000..a778efe
--- /dev/null
+++ b/clock.py
@@ -0,0 +1,699 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Code released in the Public Domain. You can do whatever you want with this package.
+# Though I'm learning Python, I've tried to use the best practices in XO development.
+# Look at NOTES file to see how to adapt this program.
+# Originally written by Pierre Métras <pierre@alterna.tv> for the OLPC XO laptop.
+
+
+"""Learning time.
+==============
+The XO is missing a simple clock for kids to learn how to
+read time, but more importantly to know what time is is. When you
+don't own a clock, the XO can be used to display the time to
+arrive in time at school...
+A clock can also be used to learn how to count and read numbers.
+
+Display and behavior can be changed with the buttons in the toolbar:
+- A simple clock with hours figures to learn to tell the time.
+- A nice clock face, without hours numbers.
+- A digital clock with a time scale.
+Also, the clock can print the current time in full letters. Or speak it aloud.
+
+To help learning the time, all the clocks displays use a consistent color code:
+- Hours blue: #005FE4
+- Minutes green: #00B20D
+- Seconds red: #E6000A
+- Days dark red: #B20008
+- Months purple: #5E008C
+- Years brown: #9A5200
+
+
+An analog clock is also very helpfull to determine where the North is when you
+don't have a compass!
+Check http://www.wikihow.com/Find-True-North-Without-a-Compass
+And knowing where the True North is, you can build a Sun Clock!
+
+Author: Pierre Metras <pierre@alterna.tv>
+Based on work from Davyd Madeley, Lawrence Oluyede <l.oluyede@gmail.com>
+SVG background adapted from Open ClipArt: http://openclipart.org/people/rihard/rihard_Clock_Calendar_2.svg
+
+More about clocks and time in the World
+---------------------------------------
+- Clock face: http://en.wikipedia.org/wiki/Clock_face
+- 12 hours clock: http://en.wikipedia.org/wiki/12-hour_clock
+- 24 hours clock: http://en.wikipedia.org/wiki/24-hour_clock
+- Thai 6 hours clock: http://en.wikipedia.org/wiki/Thai_six-hour_clock
+- Time and date in the World: http://en.wikipedia.org/wiki/Date_and_time_notation_by_country
+"""
+
+# We initialize threading in gobject. As we will detach another thread to translate the
+# time to text, this other thread will eventually update the display with idle_add()
+# calls, because it is not running in the main event thread. But idle_add() puts a
+# callback in the message queue with the lowest priority. When the nice clock is
+# displayed, it can spend a few seconds (20 to 30 is common) before the GTK loop will
+# process this low priority message. When we enable the threads, the processing is
+# almost instantaneous.
+import gobject
+gobject.threads_init()
+
+import pygtk
+import gtk
+from gtk import gdk
+import pango
+
+import math
+from datetime import datetime
+import threading
+import gc
+import re
+
+from pgettext import pgettext as _p
+
+from sugar.activity import activity
+from sugar.graphics.toggletoolbutton import ToggleToolButton
+from sugar.graphics.radiotoolbutton import RadioToolButton
+
+from speaker import Speaker
+from timewriter import TimeWriter
+
+
+# The display modes of the clock
+_MODE_SIMPLE_CLOCK = 0
+_MODE_NICE_CLOCK = 1
+_MODE_DIGITAL_CLOCK = 2
+
+
+class ClockActivity(activity.Activity):
+ """The clock activity displays a simple clock widget.
+ """
+
+ def __init__(self, handle):
+ """Create and initialize the clock activity.
+ """
+ super(ClockActivity, self).__init__(handle)
+
+ # TRANS: Title of the activity
+ self.set_title(_p("Activity", "What Time Is It?"))
+
+ # TRANS: The format used when writing the time in full letters.
+ # You must take care to use a font size large enough so that kids can read it easily,
+ # but also small enough so that all times combination fit on the screen, even
+ # when the screen is rotated.
+ # Pango markup: http://www.pygtk.org/docs/pygtk/pango-markup-language.html
+ self._TIME_LETTERS_FORMAT = _p("Write Time", """<markup><span lang="en" font_desc="Sans 20">%s</span></markup>""")
+
+ # TRANS: The format used to display the weekday and date (example: Tuesday 10/21/2008)
+ # We recommend to use the same font size as for the time display.
+ # See http://docs.python.org/lib/module-time.html for
+ # available strftime formats.
+ # xgettext:no-python-format
+ self._DATE_SHORT_FORMAT = _p("Write Date", """<markup><span lang="en" font_desc="Sans 20"><span foreground="#B20008">%A</span>, <span foreground="#5E008C">%m</span>/<span foreground="#B20008">%d</span>/<span foreground="#9A5200">%Y</span></span></markup>""")
+
+ # Should we write the time in full letters?
+ self._write_time = False
+ self._time_writer = None
+ self._time_in_letters = self.get_title()
+
+ # The optional labels to display the date, the day of week or time.
+ self._time_letters = None
+ self._date = None
+
+ # Should we talk?
+ self._speak_time = False
+ self._time_speaker = None
+
+ toolbox = self._make_toolbars()
+ self._make_display()
+
+ # Show the activity on the screen
+ self.show_all()
+
+ # Hide the tools we don't use from the activity toolbar
+ toolbox.get_activity_toolbar().share.hide()
+ toolbox.get_activity_toolbar().keep.hide()
+
+ # We want to be notified when the minutes change
+ self._clock.connect("time_minute", self._minutes_changed_cb)
+
+ # We want also to be notified when the activity gets the focus or loses it.
+ # When it is not active, we don't need to update the clock.
+ self.connect("notify::active", self._notify_active_cb)
+
+
+ def _make_toolbars(self):
+ """Prepare and set the toolbars of the activity.
+ Load and show icons. Associate them to the call back methods.
+ """
+ # Default toolbar
+ toolbox = activity.ActivityToolbox(self)
+ self.set_toolbox(toolbox)
+
+ # In the activity toolbar, we find first, the name of the activity field, a
+ # spring separator, the share combobox, the keep and quit buttons.
+ #activity_toolbar = toolbox.get_activity_toolbar()
+ # Hide the tools we don't use
+ #activity_toolbar.share.set_no_show_all(True)
+ #activity_toolbar.keep.set_no_show_all(True)
+
+
+ # Create the display tool bar
+ display_toolbar = gtk.Toolbar()
+
+ # First group of radio button to select the type of clock to display
+ button1 = RadioToolButton(named_icon = "simple-clock")
+ button1.set_tooltip(_p("Toolbar", "Simple Clock"))
+ button1.connect("toggled", self._display_mode_changed_cb, _MODE_SIMPLE_CLOCK)
+ display_toolbar.insert(button1, -1)
+ button2 = RadioToolButton(named_icon = "nice-clock", group = button1)
+ button2.set_tooltip(_p("Toolbar", "Nice Clock"))
+ button2.connect("toggled", self._display_mode_changed_cb, _MODE_NICE_CLOCK)
+ display_toolbar.insert(button2, -1)
+ button3 = RadioToolButton(named_icon = "digital-clock", group = button1)
+ button3.set_tooltip(_p("Toolbar", "Digital Clock"))
+ button3.connect("toggled", self._display_mode_changed_cb, _MODE_DIGITAL_CLOCK)
+ display_toolbar.insert(button3, -1)
+
+ # A separator between the two groups of buttons
+ separator = gtk.SeparatorToolItem()
+ separator.set_draw(True)
+ display_toolbar.insert(separator, -1)
+
+ # Now the options buttons to display other elements: date, day of week...
+ # A button in the toolbar to write the time in full letters
+ button = ToggleToolButton("write-time")
+ button.set_tooltip(_p("Toolbar", "Display time in full letters"))
+ button.connect("toggled", self._write_time_clicked_cb)
+ display_toolbar.insert(button, -1)
+
+ # The button to display the weekday and date
+ button = ToggleToolButton("write-date")
+ button.set_tooltip(_p("Toolbar", "Display weekday and date"))
+ button.connect("toggled", self._write_date_clicked_cb)
+ display_toolbar.insert(button, -1)
+
+ # A separator between the two groups of buttons
+ separator = gtk.SeparatorToolItem()
+ separator.set_draw(True)
+ display_toolbar.insert(separator, -1)
+
+ # Another button to speak aloud the time
+ button = ToggleToolButton("speak-time")
+ button.set_tooltip(_p("Toolbar", "Talking clock"))
+ button.connect("toggled", self._speak_time_clicked_cb)
+ display_toolbar.insert(button, -1)
+
+ # Add the toolbar to the activity menu
+ toolbox.add_toolbar(_p("Toolbar", "Clock"), display_toolbar)
+ toolbox.set_current_toolbar(1)
+
+ # Return the toolbox (this is necessary because there is no activity.get_toolbox()
+ # method).
+ return toolbox
+
+
+ def _make_display(self):
+ """Prepare the display of the clock.
+ The display has two parts: the clock face at the top, and the time in full letters
+ at the bottom, when the user selects to show it.
+ """
+ # The clock face
+ self._clock = ClockFace()
+
+ # The label to print the time in full letters
+ self._time_letters = gtk.Label()
+ self._time_letters.set_no_show_all(True)
+ # Following line in ineffective!
+ #self._time_letters.set_line_wrap(True)
+ # Resize the invisible label so that gtk will know in advance the height when
+ # we show it.
+ self._time_letters.set_markup(self._TIME_LETTERS_FORMAT % self._time_in_letters)
+
+ # The label to write the date
+ self._date = gtk.Label()
+ self._date.set_no_show_all(True)
+ self._date.set_markup(self._clock.get_time().strftime(self._DATE_SHORT_FORMAT))
+
+ # Put all these widgets in a vertical box
+ vbox = gtk.VBox(False)
+ vbox.pack_start(self._clock, True)
+ vbox.pack_start(self._time_letters, False)
+ vbox.pack_start(self._date, False)
+
+ # Attach the display to the activity
+ self.set_canvas(vbox)
+
+
+ def _write_date_clicked_cb(self, button):
+ """The user clicked on the "write date" button to display the current weekday and
+ date.
+ """
+ if button.get_active():
+ gobject.idle_add(self._date.show)
+ else:
+ gobject.idle_add(self._date.hide)
+
+
+ def _display_mode_changed_cb(self, radiobutton, display_mode):
+ """The user selected a clock display mode (simple clock, nice or digital).
+ """
+ self._clock.set_display_mode(display_mode)
+
+
+ def _write_time_clicked_cb(self, button):
+ """The user clicked on the "write time" button to print the current time.
+ """
+ self._write_time = button.get_active()
+ if self._write_time:
+ gobject.idle_add(self._time_letters.show)
+ self._write_and_speak(False)
+ else:
+ gobject.idle_add(self._time_letters.hide)
+
+
+ def _speak_time_clicked_cb(self, button):
+ """The user clicked on the "speak time" button to hear the talking clock.
+ """
+ self._speak_time = button.get_active()
+ if self._speak_time:
+ self._write_and_speak(True)
+
+
+ def _minutes_changed_cb(self, clock):
+ """Minutes have changed on the clock face: we have to update the display
+ of the time in full letters if the user has chosen to have it and eventually
+ croak the time.
+ """
+ # Change time display and talk, if necessary
+ self._write_and_speak(True)
+
+ # Update the weekday and date in case it was midnight
+ gobject.idle_add(self._date.set_markup, \
+ clock.get_time().strftime(self._DATE_SHORT_FORMAT))
+
+
+ def _notify_active_cb(self, widget, event):
+ """Sugar notify us that the activity is becoming active or inactive.
+ When we are inactive, we change the activity status of the clock face widget,
+ so that it can stop updating every seconds.
+ """
+ self._clock.active = self.props.active
+
+
+ def _write_and_speak(self, speak):
+ """
+ Write and speak the time (called in another thread not to block the clock).
+ """
+ # A helper function for the running thread
+ def thread_write_and_speak():
+ # Only update the time in full letters when necessary
+ if self._write_time or self._speak_time:
+ self._do_write_time()
+
+ # And if requested, say it aloud
+ if self._speak_time and speak:
+ self._do_speak_time()
+
+ # Now detach a thread to do the big job
+ thread = threading.Thread(target = thread_write_and_speak)
+ thread.start()
+
+
+ def _do_write_time(self):
+ """Translate the time to full letters.
+ """
+ if self._time_writer is None:
+ self._time_writer = TimeWriter()
+ self._time_in_letters = self._time_writer.write_time(self._clock.get_time().hour, \
+ self._clock.get_time().minute)
+ gobject.idle_add(self._time_letters.set_markup, \
+ self._TIME_LETTERS_FORMAT % self._time_in_letters)
+
+
+ def _do_speak_time(self):
+ """Speak aloud the current time.
+ """
+ if self._time_speaker is None:
+ self._time_speaker = Speaker()
+ self._time_speaker.speak(self._untag(self._time_in_letters))
+
+
+ def _untag(self, text):
+ """Remove all the tags (pango markup) from a text.
+ """
+ if text == False or "<" not in text:
+ return text
+ else:
+ result = ""
+ for s in re.findall(r"(<.*?>)|([^<>]+)", text):
+ result += s[1]
+ return result
+
+
+class ClockFace(gtk.DrawingArea):
+ """The Pango widget of the clock.
+ This widget draws a simple analog clock, with 3 hands (hours, minutes and seconds)
+ or a digital clock. Depending on the display mode, different information is displayed.
+ """
+
+ def __init__(self):
+ """Initialize the clock widget.
+ The mode defaults to the basic analog clock, with no hours mark or date.
+ """
+ super(ClockFace, self).__init__()
+
+ # The time on the clock face
+ self._time = datetime.now()
+ self._old_minute = self._time.minute
+
+ # Update the clock only when the widget is active to save resource
+ self._active = False
+
+ # The display mode of the clock
+ self._mode = _MODE_SIMPLE_CLOCK
+
+ # SVG Background cache
+ self._cache_pixbuf = None
+ self._radius = -1
+
+ # The graphic context used for drawings
+ self._gc = None
+ self._line_width = 2
+
+ # Color codes (approved colors for XO screen:
+ # http://wiki.laptop.org/go/XO_colors)
+ colormap = self.get_colormap()
+ self._COLOR_HOURS = colormap.alloc_color("#005FE4") # XO Medium Blue
+ self._COLOR_MINUTES = colormap.alloc_color("#00B20D") # XO Medium Green
+ self._COLOR_SECONDS = colormap.alloc_color("#E6000A") # XO Medium Red
+ self._COLOR_WHITE = colormap.alloc_color("#FFFFFF") # White
+ self._COLOR_BLACK = colormap.alloc_color("#000000") # Black
+
+ # gtk.Widget signals
+ self.connect("expose-event", self._expose_cb)
+ self.connect("size-allocate", self._size_allocate_cb)
+
+ # The masks to capture the events we are interested in
+ self.add_events(gdk.EXPOSURE_MASK | gdk.VISIBILITY_NOTIFY_MASK)
+
+ # Define a new signal to notify the application when minutes change.
+ # If the user wants to display the time in full letters, the method of the
+ # activity will be called back to refresh the display.
+ gobject.signal_new("time_minute", ClockFace, gobject.SIGNAL_RUN_LAST, \
+ gobject.TYPE_NONE, [])
+
+
+ def set_display_mode(self, mode):
+ """Set the type of clock to display (simple, nice, digital).
+ 'mode' is one of MODE_XXX_CLOCK constants.
+ """
+ self._mode = mode
+
+
+ def _size_allocate_cb(self, widget, allocation):
+ """We know the size of the widget on the screen, so we keep the parameters
+ which are important for our rendering (center of the clock, radius).
+ """
+ if widget.window:
+ # Store the measures of the clock face widget
+ self._center_x = int(allocation.x + allocation.width / 2.0)
+ self._center_y = int(allocation.y + allocation.height / 2.0)
+ self._radius = max(min(int(allocation.width / 2.0), \
+ int(allocation.height / 2.0)) - 20, 0)
+ self._width = allocation.width
+ self._height = allocation.height
+ self._line_width = int(self._radius / 150)
+
+ # Reload the cached pixbuf
+ self._cache_pixbuf = gdk.pixbuf_new_from_file_at_size("clock.svg", 2 * self._radius, 2 * self._radius)
+ gc.collect() # Reclaim memory from old pixbuf
+
+
+ def _expose_cb(self, widget, event):
+ """The widget is exposed and must draw itself on the graphic context.
+ In GTK+, widgets are double-buffered. It means that an off-screen buffer is
+ automatically created to draw on it before the expose event is called and
+ it prevents the screen from flickering.
+ """
+ if self._active:
+ self._gc = self.window.new_gc()
+
+ if self._mode == _MODE_NICE_CLOCK:
+ self._draw_nice_clock()
+ elif self._mode == _MODE_SIMPLE_CLOCK:
+ self._draw_simple_clock()
+ elif self._mode == _MODE_DIGITAL_CLOCK:
+ self._draw_digital_clock()
+ else:
+ raise ValueError, "Unknown display mode: %d." % self._mode
+
+ return False
+
+
+ def _draw_markup(self, x, y, markup):
+ """Write the markup text given as parameter, centered on (x, y) coordinates.
+ The markup must follow Pango markup syntax
+ See http://www.pygtk.org/pygtk2reference/pango-markup-language.html
+ It allows to specify the fonts, colors and styles and to display rich
+ text fully localizable.
+ """
+ pango_context = self.get_pango_context()
+ layout = pango.Layout(pango_context)
+
+ layout.set_markup(markup)
+ layout.set_alignment(pango.ALIGN_CENTER)
+
+ x_bearing, y_bearing, width, height = layout.get_pixel_extents()[1][:4]
+ self.window.draw_layout(self._gc, int(x - width / 2 - x_bearing), int(y - height / 2 - y_bearing), layout)
+
+
+ def _draw_digital_clock(self):
+ """Draw the digital clock.
+ """
+ self._draw_time_scale()
+ self._draw_time()
+
+
+ def _draw_time_scale(self):
+ """Draw a time scale for digital clock.
+ """
+ # Draw scales of hours, minutes and seconds, to give the children
+ # an appreciation of the time flowing...
+ hours_length = 2 * self._radius / 24 * self._time.hour
+ minutes_length = 2 * self._radius / 60 * self._time.minute
+ seconds_length = 2 * self._radius / 60 * self._time.second
+
+ # Fill background
+ self._gc.set_line_attributes(self._line_width, gdk.LINE_SOLID, \
+ gdk.CAP_BUTT, gdk.JOIN_BEVEL)
+ self._gc.set_foreground(self._COLOR_WHITE)
+ self.window.draw_rectangle(self._gc, True, \
+ int(self._center_x - 1.1 * self._radius), \
+ int(self._center_y - 0.8 * self._radius), \
+ int(2.2 * self._radius), \
+ int(0.55 * self._radius))
+
+ h = int(0.15 * self._radius)
+ x = int(self._center_x - self._radius)
+
+ # Hours scale
+ self._gc.set_foreground(self._COLOR_HOURS)
+ y = int(self._center_y - 0.75 * self._radius)
+ self.window.draw_rectangle(self._gc, True, x, y, hours_length, h)
+
+ # Minutes scale
+ self._gc.set_foreground(self._COLOR_MINUTES)
+ y = int(self._center_y - 0.60 * self._radius)
+ self.window.draw_rectangle(self._gc, True, x, y, minutes_length, h)
+
+ # Seconds scale
+ self._gc.set_foreground(self._COLOR_SECONDS)
+ y = int(self._center_y - 0.45 * self._radius)
+ self.window.draw_rectangle(self._gc, True, x, y, seconds_length, h)
+
+
+ def _draw_time(self):
+ """Draw the time in colors (digital display).
+ """
+ # TRANS: The format used to display the time for digital clock
+ # You can add AM/PM indicator or use 12/24 format, for example "%I:%M:%S %p".
+ # See http://docs.python.org/lib/module-time.html for
+ # available strftime formats
+ # If the display of the time is moving horizontally, it means that the glyphs
+ # of the digits used in the font don't have the same width. Try to use a
+ # Monospace font.
+ # xgettext:no-python-format
+ markup = _p("Digital Clock", """<markup><span lang="en" font_desc="Sans,Monospace Bold 48"><span foreground="#005FE4">%I</span>:<span foreground="#00B20D">%M</span>:<span foreground="#E6000A">%S</span>%p</span></markup>""")
+ # BUG: The following line kills Python 2.5 but is valid in 2.4
+ markup_time = self._time.strftime(markup)
+ #markup_time = time.strftime(markup)
+
+ self._gc.set_foreground(self._COLOR_BLACK)
+ self._draw_markup(self._center_x, int(self._center_y + 0.3 * self._radius), markup_time)
+
+
+
+ def _draw_simple_clock(self):
+ """Draw the simple clock variants.
+ """
+ self._draw_simple_background()
+ self._draw_numbers()
+ self._draw_hands()
+
+
+ def _draw_simple_background(self):
+ """Draw the background of the simple clock.
+ The simple clock background is a white disk, with hours and minutes
+ ticks, and the hour numbers.
+ """
+ # Simple clock background
+ self._gc.set_foreground(self._COLOR_WHITE)
+ self.window.draw_arc(self._gc, True, self._center_x - self._radius, self._center_y - self._radius, 2 * self._radius, 2 * self._radius, 0, 360 * 64)
+ self._gc.set_foreground(self.get_style().fg[gtk.STATE_NORMAL])
+ self._gc.set_line_attributes(4 * self._line_width, gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND)
+ self.window.draw_arc(self._gc, False, self._center_x - self._radius, self._center_y - self._radius, 2 * self._radius, 2 * self._radius, 0, 360 * 64)
+
+ # Clock ticks
+ self._gc.set_line_attributes(4 * self._line_width, gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND)
+ for i in xrange(60):
+ if i % 15 == 0:
+ inset = 0.175 * self._radius
+ elif i % 5 == 0:
+ inset = 0.1 * self._radius
+ else:
+ inset = 0.05 * self._radius
+
+ self.window.draw_line(self._gc, \
+ int(self._center_x + (self._radius - inset) * math.cos(i * math.pi / 30.0)), \
+ int(self._center_y + (self._radius - inset) * math.sin(i * math.pi / 30.0)), \
+ int(self._center_x + self._radius * math.cos(i * math.pi / 30.0)), \
+ int(self._center_y + self._radius * math.sin(i * math.pi / 30.0)))
+
+
+ def _draw_nice_background(self):
+ """Draw the nice clock background.
+ The background has been loaded from the clock.svg file to a pixbuf, and we just
+ draw this pixbuf onto the pixmap where we will be drawing the hands.
+ """
+ # We draw the background from the SVG pixbuf
+ self.window.draw_pixbuf(None, self._cache_pixbuf, 0, 0, self._center_x - self._radius, self._center_y - self._radius)
+
+
+ def _draw_nice_clock(self):
+ """Draw the nice clock.
+ """
+ self._draw_nice_background()
+ self._draw_hands()
+
+
+ def _draw_hands(self):
+ """Draw the hands of the analog clocks.
+ """
+ hours = self._time.hour
+ minutes = self._time.minute
+ seconds = self._time.second
+
+ # Hour hand:
+ # The hour hand is rotated 30 degrees (pi/6 r) per hour +
+ # 1/2 a degree (pi/360) per minute
+ self._gc.set_foreground(self._COLOR_HOURS)
+ self._gc.set_line_attributes(8 * self._line_width, gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND)
+ self.window.draw_line(self._gc, self._center_x, self._center_y, \
+ int(self._center_x + self._radius * 0.5 * \
+ math.sin(math.pi / 6 * hours + math.pi / 360 * minutes)), \
+ int(self._center_y + self._radius * 0.5 * \
+ - math.cos(math.pi / 6 * hours + math.pi / 360 * minutes)))
+
+ # Minute hand:
+ # The minute hand is rotated 6 degrees (pi/30 r) per minute
+ self._gc.set_foreground(self._COLOR_MINUTES)
+ self._gc.set_line_attributes(6 * self._line_width, gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND)
+ self.window.draw_line(self._gc, self._center_x, self._center_y, \
+ int(self._center_x + self._radius * 0.8 * \
+ math.sin(math.pi / 30 * minutes)), \
+ int(self._center_y + self._radius * 0.8 * \
+ - math.cos(math.pi / 30 * minutes)))
+
+ # Seconds hand:
+ # Operates identically to the minute hand
+ self._gc.set_foreground(self._COLOR_SECONDS)
+ self._gc.set_line_attributes(2 * self._line_width, gdk.LINE_SOLID, gdk.CAP_ROUND, gdk.JOIN_ROUND)
+ self.window.draw_line(self._gc, self._center_x, self._center_y, \
+ int(self._center_x + self._radius * 0.7 * \
+ math.sin(math.pi / 30 * seconds)), \
+ int(self._center_y + self._radius * 0.7 * \
+ - math.cos(math.pi / 30 * seconds)))
+
+
+ def _draw_numbers(self):
+ """Draw the numbers of the hours.
+ """
+ self._gc.set_foreground(self._COLOR_HOURS)
+
+ for i in xrange(12):
+ # TRANS: The format of the font used to print hour numbers, from 1 to 12.
+ hour_number = _p("Hour Number", """<markup><span lang="en" font_desc="Sans Bold 20">%d</span></markup>""") % (i + 1)
+ self._draw_markup(self._center_x + 0.75 * self._radius * math.cos((i - 2) * math.pi / 6.0), \
+ self._center_y + 0.75 * self._radius * math.sin((i - 2) * math.pi / 6.0), \
+ hour_number)
+
+
+ def _redraw_canvas(self):
+ """Force a redraw of the clock on the screen.
+ """
+ # If we are attached to a window, redraw ourself.
+ if self.window:
+ self.queue_draw()
+ self.window.process_updates(True)
+
+
+ def _update_cb(self):
+ """Called every seconds to update the time value.
+ """
+ # update the time and force a redraw of the clock
+ self._time = datetime.now()
+
+ gobject.idle_add(self._redraw_canvas)
+
+ # When the minutes change, we raise the 'time_minute' signal. We can't test on
+ # 'self._time.second == 0' for instance because gtk timer does not guarantee
+ # to call us every seconds.
+ if self._old_minute != self._time.minute:
+ self.emit("time_minute")
+ self._old_minute = self._time.minute
+
+ # Keep running this timer as long as the clock is active (ie. visible)
+ return self._active
+
+
+ def get_time(self):
+ """Public access to the time member of the clock face.
+ """
+ return self._time
+
+
+ def _get_active(self):
+ """Get the activity status of the clock. When active, the clock face redraws
+ itself. When inactive, we do nothing to save resources.
+ """
+ return self._active
+
+
+ def _set_active(self, active):
+ """Set the activity state of the clock face. When Sugar reactivates the clock,
+ we start a timer to be called every seconds and update the clock.
+ """
+ self._active = active
+
+ if active:
+ # We must redraw the clock...
+ self._update_cb()
+
+ # And update again the clock every seconds.
+ gobject.timeout_add(1000, self._update_cb)
+
+
+ active = property(_get_active, _set_active)
+
+
diff --git a/clock.svg b/clock.svg
new file mode 100644
index 0000000..0847a95
--- /dev/null
+++ b/clock.svg
@@ -0,0 +1,238 @@
+<?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="1000"
+ height="1000"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.45.1"
+ version="1.0"
+ sodipodi:docname="big-clock.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:docbase="/home/pierre">
+ <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="138"
+ fy="239.93021"
+ fx="302"
+ cy="239.93021"
+ cx="302"
+ gradientTransform="matrix(3.1409616,0,0,3.1409616,-646.57041,-549.90486)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient20460"
+ xlink:href="#linearGradient20428"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient12953">
+ <stop
+ id="stop12955"
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0" />
+ <stop
+ id="stop12965"
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0.47816542" />
+ <stop
+ id="stop12961"
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0.49808899" />
+ <stop
+ id="stop12967"
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0.50756544" />
+ <stop
+ id="stop12963"
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0.53007674" />
+ <stop
+ id="stop12957"
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient12977">
+ <stop
+ id="stop12979"
+ style="stop-color:#ffffff;stop-opacity:0.31944445;"
+ offset="0" />
+ <stop
+ id="stop12981"
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient13034">
+ <stop
+ id="stop13036"
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="0" />
+ <stop
+ id="stop13038"
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient20428">
+ <stop
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;"
+ id="stop20430" />
+ <stop
+ offset="0.20165709"
+ style="stop-color:#ffffff;stop-opacity:1;"
+ id="stop20432" />
+ <stop
+ offset="0.32675916"
+ style="stop-color:#fbf8ef;stop-opacity:1;"
+ id="stop20434" />
+ <stop
+ offset="1"
+ style="stop-color:#7f6204;stop-opacity:1;"
+ id="stop20436" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient12977"
+ id="radialGradient2651"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.8369565,0,0,0.1940414,-182.96913,319.25473)"
+ cx="527"
+ cy="691.20294"
+ fx="527"
+ fy="691.20294"
+ r="90.78125" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient12977"
+ id="radialGradient2654"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.8369565,0,0,0.2196637,-183.80609,174.73256)"
+ cx="528"
+ cy="368.17188"
+ fx="528"
+ fy="368.17188"
+ r="113.53125" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient12953"
+ id="radialGradient2661"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.4840409,0,0,1.4840409,-521.7557,-347.99433)"
+ spreadMethod="pad"
+ cx="525.49945"
+ cy="467.18744"
+ fx="525.49945"
+ fy="467.18744"
+ r="138" />
+ </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.5"
+ inkscape:cx="772.16535"
+ inkscape:cy="526.09524"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="1024"
+ inkscape:window-height="708"
+ inkscape:window-x="-4"
+ inkscape:window-y="-4"
+ width="1000px"
+ height="1000px"
+ inkscape:showpageshadow="true" />
+ <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:license
+ rdf:resource="http://web.resource.org/cc/PublicDomain" />
+ </cc:Work>
+ <cc:License
+ rdf:about="http://web.resource.org/cc/PublicDomain">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-142.60695,-229.83088)">
+ <g
+ id="g2855"
+ transform="matrix(4.3290045,0,0,4.3290043,-474.73918,-765.10798)">
+ <path
+ style="fill:url(#radialGradient20460);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:0;stroke-opacity:1"
+ id="path13002"
+ sodipodi:cx="302"
+ sodipodi:cy="288.36218"
+ sodipodi:type="arc"
+ d="M 440 288.36218 A 138 138 0 1 1 164,288.36218 A 138 138 0 1 1 440 288.36218 z"
+ transform="matrix(0.7217233,0,0,0.7217233,40.146499,137.21317)"
+ sodipodi:rx="138"
+ sodipodi:ry="138" />
+ <path
+ style="fill:url(#radialGradient2661);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:0;stroke-opacity:1"
+ id="path12948"
+ d="M 258.10695,229.83088 C 194.35095,229.83088 142.60695,281.57488 142.60695,345.33088 C 142.60695,409.08688 194.35095,460.83088 258.10695,460.83088 C 321.86294,460.83088 373.60694,409.08688 373.60694,345.33088 C 373.60694,281.57488 321.86294,229.83088 258.10695,229.83088 z M 258.10695,245.73306 C 313.08494,245.73306 357.70477,290.35288 357.70477,345.33088 C 357.70476,400.30888 313.08494,444.9287 258.10695,444.9287 C 203.12895,444.9287 158.50912,400.30888 158.50912,345.33088 C 158.50912,290.35288 203.12895,245.73306 258.10695,245.73306 z " />
+ <path
+ style="fill:url(#radialGradient2654);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:0;stroke-opacity:1"
+ id="path12969"
+ d="M 258.10695,230.66784 C 218.72382,230.66784 183.93743,250.42193 163.08623,280.54522 C 187.01859,255.48654 220.7466,239.87436 258.10695,239.87436 C 295.46729,239.87436 329.1953,255.48655 353.12766,280.54522 C 332.27646,250.42193 297.49008,230.66784 258.10695,230.66784 z " />
+ <path
+ style="fill:url(#radialGradient2651);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:0;stroke-opacity:1"
+ id="path12991"
+ d="M 258.10695,453.37673 C 288.55269,453.37673 315.81294,439.69484 334.0869,418.1461 C 313.7727,435.91503 287.19562,446.68108 258.10695,446.68108 C 229.01827,446.68108 202.44119,435.91503 182.12699,418.1461 C 200.40095,439.69484 227.6612,453.37673 258.10695,453.37673 z " />
+ <path
+ style="fill:#000000;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:0;stroke-opacity:1"
+ id="rect13079"
+ sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+ d="M 256.04071,248.27008 L 256.04071,268.12164 C 257.41805,268.07894 258.79579,268.09719 260.17318,268.12164 L 260.17318,248.27008 L 256.04071,248.27008 z M 210.34812,260.82443 L 208.80498,261.71369 L 217.14839,276.17735 C 217.65829,275.87453 218.17445,275.57986 218.69153,275.28808 L 210.34812,260.82443 z M 305.86578,260.82443 L 297.52237,275.28808 C 298.03945,275.57986 298.5556,275.87453 299.0655,276.17735 L 307.40891,261.71369 L 305.86578,260.82443 z M 174.48976,296.02891 L 173.60049,297.57205 L 188.06415,305.91546 C 188.35593,305.39838 188.6506,304.88223 188.95341,304.37232 L 174.48976,296.02891 z M 341.72413,296.02891 L 327.26048,304.37232 C 327.5633,304.88223 327.85796,305.39838 328.14974,305.91546 L 342.6134,297.57205 L 341.72413,296.02891 z M 161.04615,343.26464 L 161.04615,347.39712 L 180.89771,347.39712 C 180.85501,346.01978 180.87325,344.64203 180.89771,343.26464 L 161.04615,343.26464 z M 335.31618,343.26464 C 335.35888,344.64198 335.34064,346.01973 335.31618,347.39712 L 355.16774,347.39712 L 355.16774,343.26464 L 335.31618,343.26464 z M 188.06415,384.7463 L 173.60049,393.08971 L 174.48976,394.63285 L 188.95341,386.28944 C 188.6506,385.77953 188.35593,385.26338 188.06415,384.7463 z M 328.14974,384.7463 C 327.85796,385.26338 327.5633,385.77953 327.26048,386.28944 L 341.72413,394.63285 L 342.6134,393.08971 L 328.14974,384.7463 z M 217.14839,414.48441 L 208.80498,428.94807 L 210.34812,429.83733 L 218.69153,415.37368 C 218.17445,415.0819 217.65829,414.78723 217.14839,414.48441 z M 299.0655,414.48441 C 298.5556,414.78723 298.03945,415.0819 297.52237,415.37368 L 305.86578,429.83733 L 307.40891,428.94807 L 299.0655,414.48441 z M 256.04071,422.54012 L 256.04071,442.39168 L 260.17318,442.39168 L 260.17318,422.54012 C 258.79584,422.58282 257.4181,422.56457 256.04071,422.54012 z " />
+ <path
+ style="fill:url(#linearGradient20470);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:0;stroke-opacity:1"
+ id="path13022"
+ sodipodi:cx="302"
+ sodipodi:cy="288.36218"
+ sodipodi:type="arc"
+ d="M 440 288.36218 A 138 138 0 1 1 164,288.36218 A 138 138 0 1 1 440 288.36218 z"
+ transform="matrix(0.6095226,0,0,0.5033868,74.031119,171.7059)"
+ sodipodi:rx="138"
+ sodipodi:ry="138" />
+ </g>
+ </g>
+</svg>
+
diff --git a/dist/Clock-3.xo b/dist/Clock-3.xo
new file mode 100644
index 0000000..1c6d260
--- /dev/null
+++ b/dist/Clock-3.xo
Binary files differ
diff --git a/dist/Clock-4.xo b/dist/Clock-4.xo
new file mode 100644
index 0000000..e5f7248
--- /dev/null
+++ b/dist/Clock-4.xo
Binary files differ
diff --git a/dist/Clock-5.xo b/dist/Clock-5.xo
new file mode 100644
index 0000000..b0735c6
--- /dev/null
+++ b/dist/Clock-5.xo
Binary files differ
diff --git a/icons/digital-clock.svg b/icons/digital-clock.svg
new file mode 100755
index 0000000..51db34d
--- /dev/null
+++ b/icons/digital-clock.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
+[
+ <!ENTITY fill_color "#FFFFFF">
+ <!ENTITY stroke_color "#000000">
+]>
+<svg contentScriptType="text/ecmascript" width="55px" xmlns:xlink="http://www.w3.org/1999/xlink" baseProfile="full" zoomAndPan="magnify" contentStyleType="text/css" height="55px" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" version="1.1">
+ <rect x="5" y="15" fill="&fill_color;" width="45" height="25" stroke="&stroke_color;" stroke-width="3.5"/>
+ <text x="10" stroke="&stroke_color;" xml:space="preserve" font-size="12" y="31" font-family="'Courier New', 'Courier', 'MS Courier New', 'Prestige', monospace" font-weight="800">19:50</text>
+</svg>
diff --git a/icons/nice-clock.svg b/icons/nice-clock.svg
new file mode 100755
index 0000000..fa056f1
--- /dev/null
+++ b/icons/nice-clock.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
+[
+ <!ENTITY fill_color "#FFFFFF">
+ <!ENTITY stroke_color "#000000">
+]>
+<svg contentScriptType="text/ecmascript" width="55px" xmlns:xlink="http://www.w3.org/1999/xlink" baseProfile="full" zoomAndPan="magnify" contentStyleType="text/css" height="55px" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" version="1.1">
+ <circle fill="&fill_color;" r="22.5" stroke-width="3.5" cx="27.5" cy="27.5" stroke="&stroke_color;"/>
+ <line x1="27.5" x2="27.5" y1="5.0" y2="11.0" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line x1="27.5" x2="27.5" y1="44" y2="50" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line x1="44" x2="50" y1="28" y2="28" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line x1="5" x2="11" y1="27" y2="27" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line stroke-linecap="round" x1="27.5" x2="21" y1="27.5" y2="37" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line stroke-linecap="round" x1="17" x2="27.5" y1="14" y2="27.5" stroke="&stroke_color;" stroke-width="3.5"/>
+</svg>
diff --git a/icons/simple-clock.svg b/icons/simple-clock.svg
new file mode 100755
index 0000000..13bde36
--- /dev/null
+++ b/icons/simple-clock.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
+[
+ <!ENTITY fill_color "#FFFFFF">
+ <!ENTITY stroke_color "#000000">
+]>
+<svg contentScriptType="text/ecmascript" width="55px" xmlns:xlink="http://www.w3.org/1999/xlink" baseProfile="full" zoomAndPan="magnify" contentStyleType="text/css" height="55px" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" version="1.1">
+ <line x1="27.5" x2="27.5" y1="5" y2="11" stroke="&fill_color;" stroke-width="3.5"/>
+ <line x1="27.5" x2="27.5" y1="44" y2="50" stroke="&fill_color;" stroke-width="3.5"/>
+ <line x1="44" x2="50" y1="28" y2="28" stroke="&fill_color;" stroke-width="3.5"/>
+ <line x1="5" x2="11" y1="27" y2="27" stroke="&fill_color;" stroke-width="3.5"/>
+ <line stroke-linecap="round" x1="27.5" x2="21" y1="27.5" y2="37" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line stroke-linecap="round" x1="17" x2="27.5" y1="14" y2="27.5" stroke="&stroke_color;" stroke-width="3.5"/>
+</svg>
diff --git a/icons/speak-time.svg b/icons/speak-time.svg
new file mode 100755
index 0000000..726a709
--- /dev/null
+++ b/icons/speak-time.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
+[
+ <!ENTITY fill_color "#FFFFFF">
+ <!ENTITY stroke_color "#000000">
+]>
+<svg contentScriptType="text/ecmascript" width="55px" xmlns:xlink="http://www.w3.org/1999/xlink" baseProfile="full" zoomAndPan="magnify" contentStyleType="text/css" height="55px" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" version="1.1">
+ <ellipse fill="&fill_color;" rx="10" cx="20.0" ry="10" cy="40.0" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line stroke-linecap="round" x1="20" x2="20" y1="35" y2="41" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line stroke-linecap="round" x1="20" x2="24" y1="41" y2="41" stroke="&stroke_color;" stroke-width="3.5"/>
+ <path fill="&fill_color;" stroke-width="3.5" d="M 33.25 24.75 C 32.625 20.375 32.625 20.375 32.625 20.375 Q 25.5 21.375 16.0 20.875 Q 14.375 21.125 8.875 18.875 Q 6.125 17.125 5.875 15.5 Q 5.125 11.5 7.125 8.125 Q 8.125 6.125 11.125 5.0 Q 11.875 4.875 15.75 4.375 Q 20.0 4.125 26.125 3.875 Q 30.875 4.0 36.75 4.25 Q 37.25 4.25 42.375 5.25 Q 46.375 6.625 47.875 8.625 C 48.625 9.5 48.75 10.875 48.5 13.375 Q 48.625 14.75 47.375 16.75 Q 46.5 18.0 43.375 19.375 Q 41.875 19.625 39.75 19.875 C 37.125 19.625 42.25 29.25 34.125 31.75 z" stroke-linejoin="round" stroke-linecap="round" stroke="&stroke_color;"/>
+</svg>
diff --git a/icons/write-date-long.svg b/icons/write-date-long.svg
new file mode 100755
index 0000000..fecc696
--- /dev/null
+++ b/icons/write-date-long.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
+[
+ <!ENTITY fill_color "#FFFFFF">
+ <!ENTITY stroke_color "#000000">
+]>
+<svg contentScriptType="text/ecmascript" width="55px" xmlns:xlink="http://www.w3.org/1999/xlink" baseProfile="full" zoomAndPan="magnify" contentStyleType="text/css" height="55px" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" version="1.1">
+ <rect x="16" y="9" fill="none" width="33" height="36" stroke="&stroke_color;" stroke-width="3.5"/>
+ <rect x="13" y="9" transform="matrix(1.0 0.0 -0.3 1.0 5 0.0)" fill="&fill_color;" width="32" height="31" stroke="&stroke_color;" stroke-width="3.5"/>
+ <text x="20" font-size="12" y="24" transform="matrix(1.0 0.0 -0.3 1.0 5 0.0)" font-family="'Courier New', 'Courier', 'MS Courier New', 'Prestige', monospace" font-weight="800" xml:space="preserve">26</text>
+ <text x="20" font-size="12" y="35" transform="matrix(1.0 0.0 -0.3 1.0 5 0.0)" font-family="'Courier New', 'Courier', 'MS Courier New', 'Prestige', monospace" font-weight="800" xml:space="preserve">Jul</text>
+</svg>
diff --git a/icons/write-date.svg b/icons/write-date.svg
new file mode 100755
index 0000000..88f4495
--- /dev/null
+++ b/icons/write-date.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
+[
+ <!ENTITY fill_color "#FFFFFF">
+ <!ENTITY stroke_color "#000000">
+]>
+<svg contentScriptType="text/ecmascript" width="55px" xmlns:xlink="http://www.w3.org/1999/xlink" baseProfile="full" zoomAndPan="magnify" contentStyleType="text/css" height="55px" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" version="1.1">
+ <rect x="16" y="9" width="33" height="36" fill="none" stroke="&stroke_color;" stroke-width="3.5"/>
+ <rect x="13" y="9" transform="matrix(1.0 0.0 -0.3 1.0 5 0.0)" fill="&fill_color;" width="32" height="31" stroke="&stroke_color;" stroke-width="3.5"/>
+ <text x="18" stroke="&stroke_color;" font-size="18" y="30.0" transform="matrix(1.0 0.0 -0.3 1.0 5 0.0)" font-family="'Courier New', 'Courier', 'MS Courier New', 'Prestige', monospace" font-weight="800" xml:space="preserve">26</text>
+</svg>
diff --git a/icons/write-day.svg b/icons/write-day.svg
new file mode 100755
index 0000000..c7ada7a
--- /dev/null
+++ b/icons/write-day.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
+[
+ <!ENTITY fill_color "#FFFFFF">
+ <!ENTITY stroke_color "#000000">
+]>
+<svg contentScriptType="text/ecmascript" width="55px" xmlns:xlink="http://www.w3.org/1999/xlink" baseProfile="full" zoomAndPan="magnify" contentStyleType="text/css" height="55px" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" version="1.1">
+ <circle fill="&fill_color;" r="22.5" stroke-width="3.5" cx="27.5" cy="27.5" stroke="&stroke_color;"/>
+ <line fill="none" x1="27.5" x2="27.5" y1="27.5" y2="6" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="none" x1="27.5" x2="36" y1="27.5" y2="47" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="none" x1="27.5" x2="20" y1="27.5" y2="48" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="none" x1="27.5" x2="5" y1="27.5" y2="23" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="none" x1="27.5" x2="13" y1="27.5" y2="9" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="none" x1="27.5" x2="8" y1="27.5" y2="38" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="none" x1="27.5" x2="49" y1="27.5" y2="23" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="none" x1="27.5" x2="42" y1="27.5" y2="10" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="none" x1="27.5" x2="47" y1="27.5" y2="37" stroke="&stroke_color;" stroke-width="3.5"/>
+ <polygon fill="&stroke_color;" points="6,23 27.5,27.5 8,38" stroke="&stroke_color;"/>
+</svg>
diff --git a/icons/write-time.svg b/icons/write-time.svg
new file mode 100755
index 0000000..8aede8c
--- /dev/null
+++ b/icons/write-time.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
+[
+ <!ENTITY fill_color "#FFFFFF">
+ <!ENTITY stroke_color "#000000">
+]>
+<svg contentScriptType="text/ecmascript" width="55px" xmlns:xlink="http://www.w3.org/1999/xlink" baseProfile="full" zoomAndPan="magnify" contentStyleType="text/css" height="55px" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" version="1.1">
+ <rect x="5" y="5" fill="&fill_color;" width="45" height="45" stroke="&stroke_color;" stroke-width="3.5"/>
+ <text x="15" y="42" stroke="&stroke_color;" font-size="40" font-family="'Courier New', 'Courier', 'MS Courier New', 'Prestige', monospace" xml:space="preserve">T</text>
+</svg>
diff --git a/locale/en/LC_MESSAGES/tv.alterna.Clock.mo b/locale/en/LC_MESSAGES/tv.alterna.Clock.mo
new file mode 100644
index 0000000..5a80e67
--- /dev/null
+++ b/locale/en/LC_MESSAGES/tv.alterna.Clock.mo
Binary files differ
diff --git a/locale/en/activity.linfo b/locale/en/activity.linfo
new file mode 100644
index 0000000..836eb52
--- /dev/null
+++ b/locale/en/activity.linfo
@@ -0,0 +1,2 @@
+[Activity]
+name = Clock
diff --git a/locale/es/LC_MESSAGES/tv.alterna.Clock.mo b/locale/es/LC_MESSAGES/tv.alterna.Clock.mo
new file mode 100644
index 0000000..27b3f86
--- /dev/null
+++ b/locale/es/LC_MESSAGES/tv.alterna.Clock.mo
Binary files differ
diff --git a/locale/es/activity.linfo b/locale/es/activity.linfo
new file mode 100644
index 0000000..a7d8f72
--- /dev/null
+++ b/locale/es/activity.linfo
@@ -0,0 +1,2 @@
+[Activity]
+name = Reloj
diff --git a/locale/fr/LC_MESSAGES/tv.alterna.Clock.mo b/locale/fr/LC_MESSAGES/tv.alterna.Clock.mo
new file mode 100644
index 0000000..0c6acb7
--- /dev/null
+++ b/locale/fr/LC_MESSAGES/tv.alterna.Clock.mo
Binary files differ
diff --git a/locale/fr/activity.linfo b/locale/fr/activity.linfo
new file mode 100644
index 0000000..d091edf
--- /dev/null
+++ b/locale/fr/activity.linfo
@@ -0,0 +1,2 @@
+[Activity]
+name = Horloge
diff --git a/misc/activity-clock.svg b/misc/activity-clock.svg
new file mode 100755
index 0000000..e540804
--- /dev/null
+++ b/misc/activity-clock.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"
+[
+ <!ENTITY fill_color "#FFFFFF">
+ <!ENTITY stroke_color "#000000">
+]>
+<svg width="55px" height="55px" xmlns="http://www.w3.org/2000/svg" version="1.1">
+ <g id="Icon">
+ <circle fill="&fill_color;" r="22.5" stroke-width="3.5" cx="27.5" cy="27.5" stroke="&stroke_color;"/>
+ <line fill="none" x1="27.5" x2="27.5" y1="10" y2="14" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="none" x1="27.5" x2="27.5" y1="41" y2="45" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="none" x1="41" x2="45" y1="27.5" y2="27.5" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line fill="none" x1="10" x2="14" y1="27.5" y2="27.5" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line stroke-linecap="round" fill="none" x1="37.5" x2="27.5" y1="14" y2="27.5" stroke="&stroke_color;" stroke-width="3.5"/>
+ <line stroke-linecap="round" fill="none" x1="27.5" x2="38" y1="27.5" y2="34" stroke="&stroke_color;" stroke-width="3.5"/>
+ </g>
+</svg>
diff --git a/pgettext.py b/pgettext.py
new file mode 100644
index 0000000..ac1473c
--- /dev/null
+++ b/pgettext.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Code released in the Public Domain. You can do whatever you want with this package.
+# Originally written by Pierre Métras <pierre@alterna.tv> for the OLPC XO laptop.
+
+
+from gettext import gettext
+
+
+# pgettext(msgctxt, msgid) from gettext is not supported in Python implementation < v2.6.
+# http://bugs.python.org/issue2504
+# Meanwhile we get official support, we have to simulate it.
+# See http://www.gnu.org/software/gettext/manual/gettext.html#Ambiguities for
+# more information about pgettext.
+
+# The separator between message context and message id.This value is the same as
+# the one used in gettext.h, so PO files should be still valid when Python gettext
+# module will include pgettext() function.
+GETTEXT_CONTEXT_GLUE = "\004"
+
+def pgettext(msgctxt, msgid):
+ """A custom implementation of GNU pgettext().
+ """
+ if msgctxt is not None and msgctxt is not "":
+ translation = gettext(msgctxt + GETTEXT_CONTEXT_GLUE + msgid)
+ if translation.startswith(msgctxt + GETTEXT_CONTEXT_GLUE):
+ return msgid
+ else:
+ return translation
+ else:
+ return gettext(msgid)
+
+# Map our pgettext() custom function to _p()
+_p = lambda msgctxt, msgid: pgettext(msgctxt, msgid)
+
+
diff --git a/po/Clock.pot b/po/Clock.pot
new file mode 100644
index 0000000..c9b6f16
--- /dev/null
+++ b/po/Clock.pot
@@ -0,0 +1,179 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-27 22:49-0400\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. TRANS: Title of the activity
+#: clock.py:98
+msgctxt "Activity"
+msgid "What Time Is It?"
+msgstr ""
+
+#. TRANS: The format used when writing the time in full letters.
+#. You must take care to use a font size large enough so that kids can read it easily,
+#. but also small enough so that all times combination fit on the screen, even
+#. when the screen is rotated.
+#. Pango markup: http://www.pygtk.org/docs/pygtk/pango-markup-language.html
+#: clock.py:105
+#, python-format
+msgctxt "Write Time"
+msgid "<markup><span lang=\"en\" font_desc=\"Sans 20\">%s</span></markup>"
+msgstr ""
+
+#. TRANS: The format used to display the weekday and date (example: Tuesday 10/21/2008)
+#. We recommend to use the same font size as for the time display.
+#. See http://docs.python.org/lib/module-time.html for
+#. available strftime formats.
+#: clock.py:112
+#, no-python-format
+msgctxt "Write Date"
+msgid ""
+"<markup><span lang=\"en\" font_desc=\"Sans 20\"><span foreground=\"#B20008\">"
+"%A</span>, <span foreground=\"#5E008C\">%m</span>/<span foreground=\"#B20008"
+"\">%d</span>/<span foreground=\"#9A5200\">%Y</span></span></markup>"
+msgstr ""
+
+#: clock.py:166
+msgctxt "Toolbar"
+msgid "Simple Clock"
+msgstr ""
+
+#: clock.py:170
+msgctxt "Toolbar"
+msgid "Nice Clock"
+msgstr ""
+
+#: clock.py:174
+msgctxt "Toolbar"
+msgid "Digital Clock"
+msgstr ""
+
+#: clock.py:186
+msgctxt "Toolbar"
+msgid "Display time in full letters"
+msgstr ""
+
+#: clock.py:192
+msgctxt "Toolbar"
+msgid "Display weekday and date"
+msgstr ""
+
+#: clock.py:203
+msgctxt "Toolbar"
+msgid "Talking clock"
+msgstr ""
+
+#. Add the toolbar to the activity menu
+#: clock.py:208
+msgctxt "Toolbar"
+msgid "Clock"
+msgstr ""
+
+#. TRANS: The format used to display the time for digital clock
+#. You can add AM/PM indicator or use 12/24 format, for example "%I:%M:%S %p".
+#. See http://docs.python.org/lib/module-time.html for
+#. available strftime formats
+#. If the display of the time is moving horizontally, it means that the glyphs
+#. of the digits used in the font don't have the same width. Try to use a
+#. Monospace font.
+#: clock.py:527
+#, no-python-format
+msgctxt "Digital Clock"
+msgid ""
+"<markup><span lang=\"en\" font_desc=\"Sans,Monospace Bold 48\"><span "
+"foreground=\"#005FE4\">%I</span>:<span foreground=\"#00B20D\">%M</span>:"
+"<span foreground=\"#E6000A\">%S</span>%p</span></markup>"
+msgstr ""
+
+#. TRANS: The format of the font used to print hour numbers, from 1 to 12.
+#: clock.py:636
+#, python-format
+msgctxt "Hour Number"
+msgid "<markup><span lang=\"en\" font_desc=\"Sans Bold 20\">%d</span></markup>"
+msgstr ""
+
+#. TRANS: The rules to print the time in the localized language.
+#.
+#. Example syntax:
+#. time(h, 15) => a quarter to hour(h) am_pm(h) |
+#. The left hand side of the rule defines a pattern with a variable 'h' and a
+#. value '15'.
+#. The right hand side, when applied, will use the text "a quarter to " and call
+#. the first rule matching hour(h) after substituting the variable 'h' by its value,
+#. and call the rule matching am_pm(h).
+#. Internal spaces are significant on the right side of a rule. In calls, all
+#. arguments which are not numbers are considered to be variables. The rule parser
+#. is very simple and will let many syntax errors go ignored.
+#.
+#. A rule ends with the character '|'.
+#. The character '_' is a anonymous variable.
+#. The character '#' can be used to concatenate two text fragments. For instance:
+#. plural(1) => |
+#. plural(_) => s |
+#. hour(h) => number(h) hour#plural(h) |
+#. Use '\#' to use a # character, for instance in a pango color
+#. tag like <span foreground="\#FF0055">
+#.
+#. You can put range conditions on firing a rule, with the syntax [var1 < var2] or
+#. [var1 < var2 < var3]. For instance:
+#. hours(h) [h < 12] => in the morning |
+#. hours(h) [12 < h < 18] => in the afternoon |
+#. hours(_) => in the night |
+#.
+#. These rules will be called with the root pattern "time(hour, minute)", with the
+#. variable 'hour' bound to the current hour and the variable 'minute' to the
+#. current minute.
+#. Order of rules is important. Rules are tried from first to last. So most precise
+#. rule must be placed first in the list.
+#.
+#. You can validate your set of rules by running the command line:
+#. python timewriter.py
+#.
+#. You should use pango markup to respect the same colors as for the clock hands.
+#. Look at the README file from the activity for explanations on how to create
+#. rules.
+#: timewriter.py:204
+msgid "time(h, m) => What Time Is It?"
+msgstr ""
+
+#. TRANS: The language pitch (range [0 - 99], default 50 for English)
+#. Look at http://espeak.sourceforge.net/commands.html for details
+#: speaker.py:29
+msgctxt "espeak-pitch"
+msgid "50"
+msgstr ""
+
+#. TRANS: The diction speed, in average words per minute (range [80 - 390], default 170 for English).
+#. Look at http://espeak.sourceforge.net/commands.html for details
+#: speaker.py:36
+msgctxt "espeak-speed"
+msgid "170"
+msgstr ""
+
+#. TRANS: The pause duration between words, in units of 10 ms.
+#. Look at http://espeak.sourceforge.net/commands.html for details
+#: speaker.py:43
+msgctxt "espeak-wgap"
+msgid "0"
+msgstr ""
+
+#. TRANS: The language and voice variant
+#. Look at http://espeak.sourceforge.net/commands.html for details, and
+#. http://espeak.sourceforge.net/languages.html to see if your language is supported.
+#: speaker.py:51
+msgctxt "espeak-voice"
+msgid "en"
+msgstr ""
diff --git a/po/en.po b/po/en.po
new file mode 100644
index 0000000..2e27216
--- /dev/null
+++ b/po/en.po
@@ -0,0 +1,303 @@
+# What Time Is It? OLPC activity
+# This file is Public Domain.
+# Pierre Métras <pierre@alterna.tv>, 2008
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Clock 5\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-27 22:49-0400\n"
+"PO-Revision-Date: 2008-10-26 18:14-0400\n"
+"Last-Translator: <pierre@alterna.tv>\n"
+"Language-Team: English\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANS: Title of the activity
+#: clock.py:98
+msgctxt "Activity"
+msgid "What Time Is It?"
+msgstr "What Time Is It?"
+
+#. TRANS: The format used when writing the time in full letters.
+#. You must take care to use a font size large enough so that kids can read it easily,
+#. but also small enough so that all times combination fit on the screen, even
+#. when the screen is rotated.
+#. Pango markup: http://www.pygtk.org/docs/pygtk/pango-markup-language.html
+#: clock.py:105
+#, python-format
+msgctxt "Write Time"
+msgid "<markup><span lang=\"en\" font_desc=\"Sans 20\">%s</span></markup>"
+msgstr "<markup><span lang=\"en\" font_desc=\"Sans 20\">%s</span></markup>"
+
+#. TRANS: The format used to display the weekday and date (example: Tuesday 10/21/2008)
+#. We recommend to use the same font size as for the time display.
+#. See http://docs.python.org/lib/module-time.html for
+#. available strftime formats.
+#: clock.py:112
+#, no-python-format
+msgctxt "Write Date"
+msgid ""
+"<markup><span lang=\"en\" font_desc=\"Sans 20\"><span foreground=\"#B20008\">"
+"%A</span>, <span foreground=\"#5E008C\">%m</span>/<span foreground=\"#B20008"
+"\">%d</span>/<span foreground=\"#9A5200\">%Y</span></span></markup>"
+msgstr ""
+"<markup><span lang=\"en\" font_desc=\"Sans 20\"><span foreground=\"#B20008\">"
+"%A</span>, <span foreground=\"#5E008C\">%m</span>/<span foreground=\"#B20008"
+"\">%d</span>/<span foreground=\"#9A5200\">%Y</span></span></markup>"
+
+#: clock.py:166
+msgctxt "Toolbar"
+msgid "Simple Clock"
+msgstr "Simple Clock"
+
+#: clock.py:170
+msgctxt "Toolbar"
+msgid "Nice Clock"
+msgstr "Nice Clock"
+
+#: clock.py:174
+msgctxt "Toolbar"
+msgid "Digital Clock"
+msgstr "Digital Clock"
+
+#: clock.py:186
+msgctxt "Toolbar"
+msgid "Display time in full letters"
+msgstr "Display time in full letters"
+
+#: clock.py:192
+msgctxt "Toolbar"
+msgid "Display weekday and date"
+msgstr "Display weekday and date"
+
+#: clock.py:203
+msgctxt "Toolbar"
+msgid "Talking clock"
+msgstr "Talking clock"
+
+#. Add the toolbar to the activity menu
+#: clock.py:208
+msgctxt "Toolbar"
+msgid "Clock"
+msgstr "Clock"
+
+#. TRANS: The format used to display the time for digital clock
+#. You can add AM/PM indicator or use 12/24 format, for example "%I:%M:%S %p".
+#. See http://docs.python.org/lib/module-time.html for
+#. available strftime formats
+#. If the display of the time is moving horizontally, it means that the glyphs
+#. of the digits used in the font don't have the same width. Try to use a
+#. Monospace font.
+#: clock.py:527
+#, no-python-format
+msgctxt "Digital Clock"
+msgid ""
+"<markup><span lang=\"en\" font_desc=\"Sans,Monospace Bold 48\"><span "
+"foreground=\"#005FE4\">%I</span>:<span foreground=\"#00B20D\">%M</span>:"
+"<span foreground=\"#E6000A\">%S</span>%p</span></markup>"
+msgstr ""
+"<markup><span lang=\"en\" font_desc=\"Sans,Monospace Bold 48\"><span "
+"foreground=\"#005FE4\">%I</span>:<span foreground=\"#00B20D\">%M</span>:"
+"<span foreground=\"#E6000A\">%S</span>%p</span></markup>"
+
+#. TRANS: The format of the font used to print hour numbers, from 1 to 12.
+#: clock.py:636
+#, python-format
+msgctxt "Hour Number"
+msgid "<markup><span lang=\"en\" font_desc=\"Sans Bold 20\">%d</span></markup>"
+msgstr ""
+"<markup><span lang=\"en\" font_desc=\"Sans Bold 20\">%d</span></markup>"
+
+#. TRANS: The rules to print the time in the localized language.
+#.
+#. Example syntax:
+#. time(h, 15) => a quarter to hour(h) am_pm(h) |
+#. The left hand side of the rule defines a pattern with a variable 'h' and a
+#. value '15'.
+#. The right hand side, when applied, will use the text "a quarter to " and call
+#. the first rule matching hour(h) after substituting the variable 'h' by its value,
+#. and call the rule matching am_pm(h).
+#. Internal spaces are significant on the right side of a rule. In calls, all
+#. arguments which are not numbers are considered to be variables. The rule parser
+#. is very simple and will let many syntax errors go ignored.
+#.
+#. A rule ends with the character '|'.
+#. The character '_' is a anonymous variable.
+#. The character '#' can be used to concatenate two text fragments. For instance:
+#. plural(1) => |
+#. plural(_) => s |
+#. hour(h) => number(h) hour#plural(h) |
+#. Use '\#' to use a # character, for instance in a pango color
+#. tag like <span foreground="\#FF0055">
+#.
+#. You can put range conditions on firing a rule, with the syntax [var1 < var2] or
+#. [var1 < var2 < var3]. For instance:
+#. hours(h) [h < 12] => in the morning |
+#. hours(h) [12 < h < 18] => in the afternoon |
+#. hours(_) => in the night |
+#.
+#. These rules will be called with the root pattern "time(hour, minute)", with the
+#. variable 'hour' bound to the current hour and the variable 'minute' to the
+#. current minute.
+#. Order of rules is important. Rules are tried from first to last. So most precise
+#. rule must be placed first in the list.
+#.
+#. You can validate your set of rules by running the command line:
+#. python timewriter.py
+#.
+#. You should use pango markup to respect the same colors as for the clock hands.
+#. Look at the README file from the activity for explanations on how to create
+#. rules.
+#: timewriter.py:204
+msgid "time(h, m) => What Time Is It?"
+msgstr ""
+" time(12, 0) => <span foreground=\"\\#005FE4\">hour(12)</span> |\n"
+" time(0, 0) => <span foreground=\"\\#005FE4\">hour(0)</span> |\n"
+" time(h, 0) => <span foreground=\"\\#005FE4\">hour(h)</span> o'clock "
+"am_pm(h) |\n"
+" time(h, m) [m < 31] => <span foreground=\"\\#00B20D\">min(m)</span> "
+"past <span foreground=\"\\#005FE4\">hour(h)</span> am_pm(h) |\n"
+" time(h, m) [30 < m] => <span foreground=\"\\#00B20D\">min(m)</span> "
+"to <span foreground=\"\\#005FE4\">hour1(h)</span> am_pm(h) |\n"
+" min(1) => one minute |\n"
+" min(2) => two minutes |\n"
+" min(3) => three minutes |\n"
+" min(4) => four minutes |\n"
+" min(5) => five minutes |\n"
+" min(6) => six minutes |\n"
+" min(7) => seven minutes |\n"
+" min(8) => eight minutes |\n"
+" min(9) => nine minutes |\n"
+" min(10) => ten minutes |\n"
+" min(11) => eleven minutes |\n"
+" min(12) => twelve minutes |\n"
+" min(13) => thirteen minutes |\n"
+" min(14) => fourteen minutes |\n"
+" min(15) => a quarter |\n"
+" min(16) => sixteen minutes |\n"
+" min(17) => seventeen minutes |\n"
+" min(18) => eighteen minutes |\n"
+" min(19) => nineteen minutes |\n"
+" min(20) => twenty minutes |\n"
+" min(21) => twenty-one minutes |\n"
+" min(22) => twenty-two minutes |\n"
+" min(23) => twenty-three minutes |\n"
+" min(24) => twenty-four minutes |\n"
+" min(25) => twenty-five minutes |\n"
+" min(26) => twenty-six minutes |\n"
+" min(27) => twenty-seven minutes |\n"
+" min(28) => twenty-eight minutes |\n"
+" min(29) => twenty-nine minutes |\n"
+" min(30) => half |\n"
+" min(31) => twenty-nine minutes |\n"
+" min(32) => twenty-eight minutes |\n"
+" min(33) => twenty-seven minutes |\n"
+" min(34) => twenty-six minutes |\n"
+" min(35) => twenty-five minutes |\n"
+" min(36) => twenty-four minutes |\n"
+" min(37) => twenty-three minutes |\n"
+" min(38) => twenty-two minutes |\n"
+" min(39) => twenty-one minutes |\n"
+" min(40) => twenty minutes |\n"
+" min(41) => nineteen minutes |\n"
+" min(42) => eighteen minutes |\n"
+" min(43) => seventeen minutes |\n"
+" min(44) => thirteen minutes |\n"
+" min(45) => a quarter |\n"
+" min(46) => fourteen minutes |\n"
+" min(47) => thirteen minutes |\n"
+" min(48) => twelve minutes |\n"
+" min(49) => eleven minutes |\n"
+" min(50) => ten minutes |\n"
+" min(51) => nine minutes |\n"
+" min(52) => eight minutes |\n"
+" min(53) => seven minutes |\n"
+" min(54) => six minutes |\n"
+" min(55) => five minutes |\n"
+" min(56) => four minutes |\n"
+" min(57) => three minutes |\n"
+" min(58) => two minutes |\n"
+" min(59) => one minute |\n"
+" min(60) => sixty minutes |\n"
+" hour(0) => midnight |\n"
+" hour(1) => one |\n"
+" hour(2) => two |\n"
+" hour(3) => three |\n"
+" hour(4) => four |\n"
+" hour(5) => five |\n"
+" hour(6) => six |\n"
+" hour(7) => seven |\n"
+" hour(8) => eight |\n"
+" hour(9) => nine |\n"
+" hour(10) => ten |\n"
+" hour(11) => eleven |\n"
+" hour(12) => noon |\n"
+" hour(13) => one |\n"
+" hour(14) => two |\n"
+" hour(15) => three |\n"
+" hour(16) => four |\n"
+" hour(17) => five |\n"
+" hour(18) => six |\n"
+" hour(19) => seven |\n"
+" hour(20) => eight |\n"
+" hour(21) => nine |\n"
+" hour(22) => ten |\n"
+" hour(23) => eleven |\n"
+" hour1(0) => one |\n"
+" hour1(1) => two |\n"
+" hour1(2) => three |\n"
+" hour1(3) => four |\n"
+" hour1(4) => five |\n"
+" hour1(5) => six |\n"
+" hour1(6) => seven |\n"
+" hour1(7) => eight |\n"
+" hour1(8) => nine |\n"
+" hour1(9) => ten |\n"
+" hour1(10) => eleven |\n"
+" hour1(11) => noon |\n"
+" hour1(12) => one |\n"
+" hour1(13) => two |\n"
+" hour1(14) => three |\n"
+" hour1(15) => four |\n"
+" hour1(16) => five |\n"
+" hour1(17) => six |\n"
+" hour1(18) => seven |\n"
+" hour1(19) => eight |\n"
+" hour1(20) => nine |\n"
+" hour1(21) => ten |\n"
+" hour1(22) => eleven |\n"
+" hour1(23) => midnight |\n"
+" am_pm(h) [ h < 12] => AM |\n"
+" am_pm(_) => PM"
+
+#. TRANS: The language pitch (range [0 - 99], default 50 for English)
+#. Look at http://espeak.sourceforge.net/commands.html for details
+#: speaker.py:29
+msgctxt "espeak-pitch"
+msgid "50"
+msgstr "50"
+
+#. TRANS: The diction speed, in average words per minute (range [80 - 390], default 170 for English).
+#. Look at http://espeak.sourceforge.net/commands.html for details
+#: speaker.py:36
+msgctxt "espeak-speed"
+msgid "170"
+msgstr "170"
+
+#. TRANS: The pause duration between words, in units of 10 ms.
+#. Look at http://espeak.sourceforge.net/commands.html for details
+#: speaker.py:43
+msgctxt "espeak-wgap"
+msgid "0"
+msgstr "0"
+
+#. TRANS: The language and voice variant
+#. Look at http://espeak.sourceforge.net/commands.html for details, and
+#. http://espeak.sourceforge.net/languages.html to see if your language is supported.
+#: speaker.py:51
+msgctxt "espeak-voice"
+msgid "en"
+msgstr "en"
diff --git a/po/es.po b/po/es.po
new file mode 100644
index 0000000..e4640b6
--- /dev/null
+++ b/po/es.po
@@ -0,0 +1,305 @@
+# What Time Is It? OLPC activity
+# This file is Public Domain.
+# Pierre Métras <pierre@alterna.tv>, 2008
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Clock 5\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-27 22:49-0400\n"
+"PO-Revision-Date: 2008-10-26 18:30-0400\n"
+"Last-Translator: <pierre@alterna.tv>\n"
+"Language-Team: Spanish\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#. TRANS: Title of the activity
+#: clock.py:98
+msgctxt "Activity"
+msgid "What Time Is It?"
+msgstr "¿Qué hora es?"
+
+#. TRANS: The format used when writing the time in full letters.
+#. You must take care to use a font size large enough so that kids can read it easily,
+#. but also small enough so that all times combination fit on the screen, even
+#. when the screen is rotated.
+#. Pango markup: http://www.pygtk.org/docs/pygtk/pango-markup-language.html
+#: clock.py:105
+#, python-format
+msgctxt "Write Time"
+msgid "<markup><span lang=\"en\" font_desc=\"Sans 20\">%s</span></markup>"
+msgstr "<markup><span lang=\"es\" font_desc=\"Sans 16\">%s</span></markup>"
+
+#. TRANS: The format used to display the weekday and date (example: Tuesday 10/21/2008)
+#. We recommend to use the same font size as for the time display.
+#. See http://docs.python.org/lib/module-time.html for
+#. available strftime formats.
+#: clock.py:112
+#, no-python-format
+msgctxt "Write Date"
+msgid ""
+"<markup><span lang=\"en\" font_desc=\"Sans 20\"><span foreground=\"#B20008\">"
+"%A</span>, <span foreground=\"#5E008C\">%m</span>/<span foreground=\"#B20008"
+"\">%d</span>/<span foreground=\"#9A5200\">%Y</span></span></markup>"
+msgstr ""
+"<markup><span lang=\"es\" font_desc=\"Sans 16\"><span foreground=\"#B20008\">"
+"%A</span>, <span foreground=\"#B20008\">%d</span>/<span foreground=\"#5E008C"
+"\">%m</span>/<span foreground=\"#9A5200\">%Y</span></span></markup>"
+
+#: clock.py:166
+msgctxt "Toolbar"
+msgid "Simple Clock"
+msgstr "El reloj"
+
+#: clock.py:170
+msgctxt "Toolbar"
+msgid "Nice Clock"
+msgstr "El bonito reloj"
+
+#: clock.py:174
+msgctxt "Toolbar"
+msgid "Digital Clock"
+msgstr "El reloj digital"
+
+#: clock.py:186
+msgctxt "Toolbar"
+msgid "Display time in full letters"
+msgstr "Visualizar la hora"
+
+#: clock.py:192
+msgctxt "Toolbar"
+msgid "Display weekday and date"
+msgstr "Visualizar el día y la fecha"
+
+#: clock.py:203
+msgctxt "Toolbar"
+msgid "Talking clock"
+msgstr "El reloj parlante"
+
+#. Add the toolbar to the activity menu
+#: clock.py:208
+msgctxt "Toolbar"
+msgid "Clock"
+msgstr "Reloj"
+
+#. TRANS: The format used to display the time for digital clock
+#. You can add AM/PM indicator or use 12/24 format, for example "%I:%M:%S %p".
+#. See http://docs.python.org/lib/module-time.html for
+#. available strftime formats
+#. If the display of the time is moving horizontally, it means that the glyphs
+#. of the digits used in the font don't have the same width. Try to use a
+#. Monospace font.
+#: clock.py:527
+#, no-python-format
+msgctxt "Digital Clock"
+msgid ""
+"<markup><span lang=\"en\" font_desc=\"Sans,Monospace Bold 48\"><span "
+"foreground=\"#005FE4\">%I</span>:<span foreground=\"#00B20D\">%M</span>:"
+"<span foreground=\"#E6000A\">%S</span>%p</span></markup>"
+msgstr ""
+"<markup><span lang=\"es\" font_desc=\"Sans,Monospace Bold 48\"><span "
+"foreground=\"#005FE4\">%H</span>:<span foreground=\"#00B20D\">%M</span>:"
+"<span foreground=\"#E6000A\">%S</span>%p</span></markup>"
+
+#. TRANS: The format of the font used to print hour numbers, from 1 to 12.
+#: clock.py:636
+#, python-format
+msgctxt "Hour Number"
+msgid "<markup><span lang=\"en\" font_desc=\"Sans Bold 20\">%d</span></markup>"
+msgstr ""
+"<markup><span lang=\"es\" font_desc=\"Sans Bold 20\">%d</span></markup>"
+
+#. TRANS: The rules to print the time in the localized language.
+#.
+#. Example syntax:
+#. time(h, 15) => a quarter to hour(h) am_pm(h) |
+#. The left hand side of the rule defines a pattern with a variable 'h' and a
+#. value '15'.
+#. The right hand side, when applied, will use the text "a quarter to " and call
+#. the first rule matching hour(h) after substituting the variable 'h' by its value,
+#. and call the rule matching am_pm(h).
+#. Internal spaces are significant on the right side of a rule. In calls, all
+#. arguments which are not numbers are considered to be variables. The rule parser
+#. is very simple and will let many syntax errors go ignored.
+#.
+#. A rule ends with the character '|'.
+#. The character '_' is a anonymous variable.
+#. The character '#' can be used to concatenate two text fragments. For instance:
+#. plural(1) => |
+#. plural(_) => s |
+#. hour(h) => number(h) hour#plural(h) |
+#. Use '\#' to use a # character, for instance in a pango color
+#. tag like <span foreground="\#FF0055">
+#.
+#. You can put range conditions on firing a rule, with the syntax [var1 < var2] or
+#. [var1 < var2 < var3]. For instance:
+#. hours(h) [h < 12] => in the morning |
+#. hours(h) [12 < h < 18] => in the afternoon |
+#. hours(_) => in the night |
+#.
+#. These rules will be called with the root pattern "time(hour, minute)", with the
+#. variable 'hour' bound to the current hour and the variable 'minute' to the
+#. current minute.
+#. Order of rules is important. Rules are tried from first to last. So most precise
+#. rule must be placed first in the list.
+#.
+#. You can validate your set of rules by running the command line:
+#. python timewriter.py
+#.
+#. You should use pango markup to respect the same colors as for the clock hands.
+#. Look at the README file from the activity for explanations on how to create
+#. rules.
+#: timewriter.py:204
+msgid "time(h, m) => What Time Is It?"
+msgstr ""
+" time(h, 55) => <span foreground=\"\\#005FE4\">hour1(h)</span> menos "
+"<span foreground=\"\\#00B20D\">cinco<span> am_pm(h) |\n"
+" time(h, 50) => <span foreground=\"\\#005FE4\">hour1(h)</span> menos "
+"<span foreground=\"\\#00B20D\">diez<span> am_pm(h) |\n"
+" time(h, 45) => <span foreground=\"\\#005FE4\">hour1(h)</span> menos "
+"<span foreground=\"\\#00B20D\">cuarto<span> am_pm(h) |\n"
+" time(h, 40) => <span foreground=\"\\#005FE4\">hour1(h)</span> menos "
+"<span foreground=\"\\#00B20D\">veinte<span> am_pm(h) |\n"
+" time(h, 35) => <span foreground=\"\\#005FE4\">hour1(h)</span> menos "
+"<span foreground=\"\\#00B20D\">veinticinco</span> am_pm(h) |\n"
+" time(h, m) => <span foreground=\"\\#005FE4\">hour(h)</span> <span "
+"foreground=\"\\#00B20D\">min(m)</span> am_pm(h) |\n"
+" am_pm(0) => |\n"
+" am_pm(12) => |\n"
+" am_pm(h) [0 < h < 7] => de la madrugada |\n"
+" am_pm(h) [h < 12] => de la mañana |\n"
+" am_pm(h) [12 < h < 19] => de la tarde |\n"
+" am_pm(_) => de la noche |\n"
+" hour(0) => Medianoche |\n"
+" hour(1) => Es la una |\n"
+" hour(12) => Mediodía |\n"
+" hour(13) => Es la una |\n"
+" hour(14) => Son las dos |\n"
+" hour(15) => Son las tres |\n"
+" hour(16) => Son las cuatro |\n"
+" hour(17) => Son las cinco |\n"
+" hour(18) => Son las seis |\n"
+" hour(19) => Son las siete |\n"
+" hour(20) => Son las ocho |\n"
+" hour(21) => Son las neuve |\n"
+" hour(22) => Son las diez |\n"
+" hour(23) => Son las once |\n"
+" hour(h) [h < 12] => Son las number(h) |\n"
+" hour1(0) => Es la una |\n"
+" hour1(1) => Son las dos |\n"
+" hour1(2) => Son las tres |\n"
+" hour1(3) => Son las cuatro |\n"
+" hour1(4) => Son las cinco |\n"
+" hour1(5) => Son las seis |\n"
+" hour1(6) => Son las siete |\n"
+" hour1(7) => Son las ocho |\n"
+" hour1(8) => Son las nueve |\n"
+" hour1(9) => Son las diez |\n"
+" hour1(10) => Son las once |\n"
+" hour1(11) => Mediodía |\n"
+" hour1(12) => hour1(0) |\n"
+" hour1(13) => hour1(1) |\n"
+" hour1(14) => hour1(2) |\n"
+" hour1(15) => hour1(3) |\n"
+" hour1(16) => hour1(4) |\n"
+" hour1(17) => hour1(5) |\n"
+" hour1(18) => hour1(6) |\n"
+" hour1(19) => hour1(7) |\n"
+" hour1(20) => hour1(8) |\n"
+" hour1(21) => hour1(9) |\n"
+" hour1(22) => hour1(10) |\n"
+" hour1(23) => Medianoche |\n"
+" min(0) => en punto |\n"
+" min(15) => y cuarto |\n"
+" min(30) => y media |\n"
+" min(m) => y number(m) |\n"
+" number(1) => uno |\n"
+" number(2) => dos |\n"
+" number(3) => tres |\n"
+" number(4) => cuatro |\n"
+" number(5) => cinco |\n"
+" number(6) => seis |\n"
+" number(7) => siete |\n"
+" number(8) => ocho |\n"
+" number(9) => nueve |\n"
+" number(10) => diez |\n"
+" number(11) => once |\n"
+" number(12) => doce |\n"
+" number(13) => trece |\n"
+" number(14) => catorce |\n"
+" number(15) => quince |\n"
+" number(16) => dieciséis |\n"
+" number(17) => diecisiete |\n"
+" number(18) => dieciocho |\n"
+" number(19) => diecinueve |\n"
+" number(20) => veinte |\n"
+" number(21) => veintiuno |\n"
+" number(22) => veintidós |\n"
+" number(23) => veintitrés |\n"
+" number(24) => veinticuatro |\n"
+" number(25) => veinticinco |\n"
+" number(26) => veintiséis |\n"
+" number(27) => veintisiete |\n"
+" number(28) => veintiocho |\n"
+" number(29) => veintinueve |\n"
+" number(30) => trenta |\n"
+" number(31) => trenta y uno |\n"
+" number(32) => trenta y dos |\n"
+" number(33) => trenta y tres |\n"
+" number(34) => trenta y cuatro |\n"
+" number(35) => trenta y cinco |\n"
+" number(36) => trenta y seis |\n"
+" number(37) => trenta y siete |\n"
+" number(38) => trenta y ocho |\n"
+" number(39) => trenta y nueve |\n"
+" number(40) => cuarenta |\n"
+" number(41) => cuarenta y uno |\n"
+" number(42) => cuarenta y dos |\n"
+" number(43) => cuatenta y tres |\n"
+" number(44) => curatenta y cuatro |\n"
+" number(45) => cuarenta y cinco |\n"
+" number(46) => cuarenta y seis |\n"
+" number(47) => cuarenta y siete |\n"
+" number(48) => cuarenta y ocho |\n"
+" number(49) => cuarenta y nueve |\n"
+" number(50) => cincuenta |\n"
+" number(51) => cincuenta y uno |\n"
+" number(52) => cincuenta y dos |\n"
+" number(53) => cincuenta y tres |\n"
+" number(54) => cincuenta y cuatro |\n"
+" number(55) => cincuenta y cinco |\n"
+" number(56) => cincuenta y seis |\n"
+" number(57) => cincuenta y siete |\n"
+" number(58) => cincuenta y ocho |\n"
+" number(59) => cincuenta y nueve"
+
+#. TRANS: The language pitch (range [0 - 99], default 50 for English)
+#. Look at http://espeak.sourceforge.net/commands.html for details
+#: speaker.py:29
+msgctxt "espeak-pitch"
+msgid "50"
+msgstr "50"
+
+#. TRANS: The diction speed, in average words per minute (range [80 - 390], default 170 for English).
+#. Look at http://espeak.sourceforge.net/commands.html for details
+#: speaker.py:36
+msgctxt "espeak-speed"
+msgid "170"
+msgstr "170"
+
+#. TRANS: The pause duration between words, in units of 10 ms.
+#. Look at http://espeak.sourceforge.net/commands.html for details
+#: speaker.py:43
+msgctxt "espeak-wgap"
+msgid "0"
+msgstr "0"
+
+#. TRANS: The language and voice variant
+#. Look at http://espeak.sourceforge.net/commands.html for details, and
+#. http://espeak.sourceforge.net/languages.html to see if your language is supported.
+#: speaker.py:51
+msgctxt "espeak-voice"
+msgid "en"
+msgstr "es"
diff --git a/po/fr.po b/po/fr.po
new file mode 100644
index 0000000..b2733ba
--- /dev/null
+++ b/po/fr.po
@@ -0,0 +1,288 @@
+# What Time Is It? OLPC activity
+# This file is Public Domain.
+# Pierre Métras <pierre@alterna.tv>, 2008
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Clock 5\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-10-27 22:49-0400\n"
+"PO-Revision-Date: 2008-10-26 18:29-0400\n"
+"Last-Translator: <pierre@alterna.tv>\n"
+"Language-Team: French\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#. TRANS: Title of the activity
+#: clock.py:98
+msgctxt "Activity"
+msgid "What Time Is It?"
+msgstr "Quelle heure est-il?"
+
+#. TRANS: The format used when writing the time in full letters.
+#. You must take care to use a font size large enough so that kids can read it easily,
+#. but also small enough so that all times combination fit on the screen, even
+#. when the screen is rotated.
+#. Pango markup: http://www.pygtk.org/docs/pygtk/pango-markup-language.html
+#: clock.py:105
+#, python-format
+msgctxt "Write Time"
+msgid "<markup><span lang=\"en\" font_desc=\"Sans 20\">%s</span></markup>"
+msgstr "<markup><span lang=\"fr\" font_desc=\"Sans 20\">%s</span></markup>"
+
+#. TRANS: The format used to display the weekday and date (example: Tuesday 10/21/2008)
+#. We recommend to use the same font size as for the time display.
+#. See http://docs.python.org/lib/module-time.html for
+#. available strftime formats.
+#: clock.py:112
+#, no-python-format
+msgctxt "Write Date"
+msgid ""
+"<markup><span lang=\"en\" font_desc=\"Sans 20\"><span foreground=\"#B20008\">"
+"%A</span>, <span foreground=\"#5E008C\">%m</span>/<span foreground=\"#B20008"
+"\">%d</span>/<span foreground=\"#9A5200\">%Y</span></span></markup>"
+msgstr ""
+"<markup><span lang=\"fr\" font_desc=\"Sans 20\"><span foreground=\"#B20008\">"
+"%A</span>, <span foreground=\"#B20008\">%d</span>/<span foreground=\"#5E008C"
+"\">%m</span>/<span foreground=\"#9A5200\">%Y</span></span></markup>"
+
+#: clock.py:166
+msgctxt "Toolbar"
+msgid "Simple Clock"
+msgstr "Une horloge simple"
+
+#: clock.py:170
+msgctxt "Toolbar"
+msgid "Nice Clock"
+msgstr "Une belle montre"
+
+#: clock.py:174
+msgctxt "Toolbar"
+msgid "Digital Clock"
+msgstr "Une montre à affichage numérique"
+
+#: clock.py:186
+msgctxt "Toolbar"
+msgid "Display time in full letters"
+msgstr "Afficher l'heure en lettres"
+
+#: clock.py:192
+msgctxt "Toolbar"
+msgid "Display weekday and date"
+msgstr "Afficher le jour et la date"
+
+#: clock.py:203
+msgctxt "Toolbar"
+msgid "Talking clock"
+msgstr "L'horloge parlante"
+
+#. Add the toolbar to the activity menu
+#: clock.py:208
+msgctxt "Toolbar"
+msgid "Clock"
+msgstr "Horloge"
+
+#. TRANS: The format used to display the time for digital clock
+#. You can add AM/PM indicator or use 12/24 format, for example "%I:%M:%S %p".
+#. See http://docs.python.org/lib/module-time.html for
+#. available strftime formats
+#. If the display of the time is moving horizontally, it means that the glyphs
+#. of the digits used in the font don't have the same width. Try to use a
+#. Monospace font.
+#: clock.py:527
+#, no-python-format
+msgctxt "Digital Clock"
+msgid ""
+"<markup><span lang=\"en\" font_desc=\"Sans,Monospace Bold 48\"><span "
+"foreground=\"#005FE4\">%I</span>:<span foreground=\"#00B20D\">%M</span>:"
+"<span foreground=\"#E6000A\">%S</span>%p</span></markup>"
+msgstr ""
+"<markup><span lang=\"fr\" font_desc=\"Sans,Monospace Bold 48\"><span "
+"foreground=\"#005FE4\">%H</span>:<span foreground=\"#00B20D\">%M</span>:"
+"<span foreground=\"#E6000A\">%S</span></span></markup>"
+
+#. TRANS: The format of the font used to print hour numbers, from 1 to 12.
+#: clock.py:636
+#, python-format
+msgctxt "Hour Number"
+msgid "<markup><span lang=\"en\" font_desc=\"Sans Bold 20\">%d</span></markup>"
+msgstr ""
+"<markup><span lang=\"fr\" font_desc=\"Sans Bold 20\">%d</span></markup>"
+
+#. TRANS: The rules to print the time in the localized language.
+#.
+#. Example syntax:
+#. time(h, 15) => a quarter to hour(h) am_pm(h) |
+#. The left hand side of the rule defines a pattern with a variable 'h' and a
+#. value '15'.
+#. The right hand side, when applied, will use the text "a quarter to " and call
+#. the first rule matching hour(h) after substituting the variable 'h' by its value,
+#. and call the rule matching am_pm(h).
+#. Internal spaces are significant on the right side of a rule. In calls, all
+#. arguments which are not numbers are considered to be variables. The rule parser
+#. is very simple and will let many syntax errors go ignored.
+#.
+#. A rule ends with the character '|'.
+#. The character '_' is a anonymous variable.
+#. The character '#' can be used to concatenate two text fragments. For instance:
+#. plural(1) => |
+#. plural(_) => s |
+#. hour(h) => number(h) hour#plural(h) |
+#. Use '\#' to use a # character, for instance in a pango color
+#. tag like <span foreground="\#FF0055">
+#.
+#. You can put range conditions on firing a rule, with the syntax [var1 < var2] or
+#. [var1 < var2 < var3]. For instance:
+#. hours(h) [h < 12] => in the morning |
+#. hours(h) [12 < h < 18] => in the afternoon |
+#. hours(_) => in the night |
+#.
+#. These rules will be called with the root pattern "time(hour, minute)", with the
+#. variable 'hour' bound to the current hour and the variable 'minute' to the
+#. current minute.
+#. Order of rules is important. Rules are tried from first to last. So most precise
+#. rule must be placed first in the list.
+#.
+#. You can validate your set of rules by running the command line:
+#. python timewriter.py
+#.
+#. You should use pango markup to respect the same colors as for the clock hands.
+#. Look at the README file from the activity for explanations on how to create
+#. rules.
+#: timewriter.py:204
+msgid "time(h, m) => What Time Is It?"
+msgstr ""
+" time(h, 35) => <span foreground=\"\\#005FE4\">hour1(h)</span> moins "
+"<span foreground=\"\\#00B20D\">vingt-cinq</span> |\n"
+" time(h, 40) => <span foreground=\"\\#005FE4\">hour1(h)</span> moins "
+"<span foreground=\"\\#00B20D\">vingt</span> |\n"
+" time(h, 45) => <span foreground=\"\\#005FE4\">hour1(h)</span> moins "
+"<span foreground=\"\\#00B20D\">le quart</span> |\n"
+" time(h, 50) => <span foreground=\"\\#005FE4\">hour1(h)</span> moins "
+"<span foreground=\"\\#00B20D\">dix</span> |\n"
+" time(h, 55) => <span foreground=\"\\#005FE4\">hour1(h)</span> moins "
+"<span foreground=\"\\#00B20D\">cinq</span> |\n"
+" time(h, m) => <span foreground=\"\\#005FE4\">hour(h)</span> min(m) |\n"
+" hour(0) => minuit |\n"
+" hour(1) => une heure |\n"
+" hour(12) => midi |\n"
+" hour(h) => number(h) heures |\n"
+" hour1(0) => number(1) heure |\n"
+" hour1(1) => number(2) heures |\n"
+" hour1(2) => number(3) heures |\n"
+" hour1(3) => number(4) heures |\n"
+" hour1(4) => number(5) heures |\n"
+" hour1(5) => number(6) heures |\n"
+" hour1(6) => number(7) heures |\n"
+" hour1(7) => number(8) heures |\n"
+" hour1(8) => number(9) heures |\n"
+" hour1(9) => number(10) heures |\n"
+" hour1(10) => number(11) heures |\n"
+" hour1(11) => midi |\n"
+" hour1(12) => number(13) heures |\n"
+" hour1(13) => number(14) heures |\n"
+" hour1(14) => number(15) heures |\n"
+" hour1(15) => number(16) heures |\n"
+" hour1(16) => number(17) heures |\n"
+" hour1(17) => number(18) heures |\n"
+" hour1(18) => number(19) heures |\n"
+" hour1(19) => number(20) heures |\n"
+" hour1(20) => number(21) heures |\n"
+" hour1(21) => number(22) heures |\n"
+" hour1(22) => number(23) heures |\n"
+" hour1(23) => minuit |\n"
+" min(0) => |\n"
+" min(1) => <span foreground=\"\\#00B20D\">une minute</span> |\n"
+" min(15) => et <span foreground=\"\\#00B20D\">quart</span> |\n"
+" min(30) => et <span foreground=\"\\#00B20D\">demie</span> |\n"
+" min(m) => <span foreground=\"\\#00B20D\">number(m)</span> |\n"
+" number(1) => une |\n"
+" number(2) => deux |\n"
+" number(3) => trois |\n"
+" number(4) => quatre |\n"
+" number(5) => cinq |\n"
+" number(6) => six |\n"
+" number(7) => sept |\n"
+" number(8) => huit |\n"
+" number(9) => neuf |\n"
+" number(10) => dix |\n"
+" number(11) => onze |\n"
+" number(12) => douze |\n"
+" number(13) => treize |\n"
+" number(14) => quatorze |\n"
+" number(15) => quinze |\n"
+" number(16) => seize |\n"
+" number(17) => dix-sept |\n"
+" number(18) => dix-huit |\n"
+" number(19) => dix-neuf |\n"
+" number(20) => vingt |\n"
+" number(21) => vingt-et-une |\n"
+" number(22) => vingt-deux |\n"
+" number(23) => vingt-trois |\n"
+" number(24) => vingt-quatre |\n"
+" number(25) => vingt-cinq |\n"
+" number(26) => vingt-six |\n"
+" number(27) => vingt-sept |\n"
+" number(28) => vingt-huit |\n"
+" number(29) => vingt-neuf |\n"
+" number(30) => trente |\n"
+" number(31) => trente-et-une |\n"
+" number(32) => trente-deux |\n"
+" number(33) => trente-trois |\n"
+" number(34) => trente-quatre |\n"
+" number(35) => trente-cinq |\n"
+" number(36) => trente-six |\n"
+" number(37) => trente-sept |\n"
+" number(38) => trente-huit |\n"
+" number(39) => trente-neuf |\n"
+" number(40) => quarante |\n"
+" number(41) => quarante-et-une |\n"
+" number(42) => quarante-deux |\n"
+" number(43) => quarante-trois |\n"
+" number(44) => quarante-quatre |\n"
+" number(45) => quarante-cinq |\n"
+" number(46) => quarante-six |\n"
+" number(47) => quarante-sept |\n"
+" number(48) => quarante-huit |\n"
+" number(49) => quarante-neuf |\n"
+" number(50) => cinquante |\n"
+" number(51) => cinquante-et-une |\n"
+" number(52) => cinquante-deux |\n"
+" number(53) => cinquante-trois |\n"
+" number(54) => cinquante-quatre |\n"
+" number(55) => cinquante-cinq |\n"
+" number(56) => cinquante-six |\n"
+" number(57) => cinquante-sept |\n"
+" number(58) => cinquante-huit |\n"
+" number(59) => cinquante-neuf"
+
+#. TRANS: The language pitch (range [0 - 99], default 50 for English)
+#. Look at http://espeak.sourceforge.net/commands.html for details
+#: speaker.py:29
+msgctxt "espeak-pitch"
+msgid "50"
+msgstr "50"
+
+#. TRANS: The diction speed, in average words per minute (range [80 - 390], default 170 for English).
+#. Look at http://espeak.sourceforge.net/commands.html for details
+#: speaker.py:36
+msgctxt "espeak-speed"
+msgid "170"
+msgstr "170"
+
+#. TRANS: The pause duration between words, in units of 10 ms.
+#. Look at http://espeak.sourceforge.net/commands.html for details
+#: speaker.py:43
+msgctxt "espeak-wgap"
+msgid "0"
+msgstr "0"
+
+#. TRANS: The language and voice variant
+#. Look at http://espeak.sourceforge.net/commands.html for details, and
+#. http://espeak.sourceforge.net/languages.html to see if your language is supported.
+#: speaker.py:51
+msgctxt "espeak-voice"
+msgid "en"
+msgstr "fr"
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..3fbb785
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,13 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+try:
+ from sugar.activity import bundlebuilder
+ bundlebuilder.start()
+except ImportError:
+ import os
+ os.system("find ./ | sed 's,^./,Clock/,g' > MANIFEST")
+ os.chdir("..")
+ os.system("zip -r Clock.xo Clock")
+ os.system("mv Clock.xo ./Clock")
+ os.chdir("Clock")
diff --git a/speaker.py b/speaker.py
new file mode 100644
index 0000000..bec926b
--- /dev/null
+++ b/speaker.py
@@ -0,0 +1,71 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Code released in the Public Domain. You can do whatever you want with this package.
+# Look at NOTES file to see how to adapt this program.
+# Originally written by Pierre Métras <pierre@alterna.tv> for the OLPC XO laptop.
+
+
+"""
+Speak aloud the text given in the XO configured language.
+
+Controls the espeak program available on the OLPC XO laptop.
+"""
+
+import sys
+import os
+
+from pgettext import pgettext as _p
+
+
+class Speaker:
+ """Speak aloud the given text.
+ """
+
+ """espeak parameter: language pitch.
+ """
+ # TRANS: The language pitch (range [0 - 99], default 50 for English)
+ # Look at http://espeak.sourceforge.net/commands.html for details
+ PITCH = _p("espeak-pitch", "50")
+
+
+ """espeak parameter: diction speed (average words per minute).
+ """
+ # TRANS: The diction speed, in average words per minute (range [80 - 390], default 170 for English).
+ # Look at http://espeak.sourceforge.net/commands.html for details
+ SPEED = _p("espeak-speed", "170")
+
+
+ """espeak parameter: word gap in units of 10 ms.
+ """
+ # TRANS: The pause duration between words, in units of 10 ms.
+ # Look at http://espeak.sourceforge.net/commands.html for details
+ WORD_GAP = _p("espeak-wgap", "0")
+
+
+ """espeak parameter: the language and voice variant.
+ """
+ # TRANS: The language and voice variant
+ # Look at http://espeak.sourceforge.net/commands.html for details, and
+ # http://espeak.sourceforge.net/languages.html to see if your language is supported.
+ VOICE = _p("espeak-voice", "en")
+
+
+ def speak(self, text):
+ """Speaks aloud the given text.
+ """
+ text = text.replace("\"", "\\\"")
+ child = os.popen("espeak -p%s -s%s -g%s -v%s \"%s\"" % (Speaker.PITCH, Speaker.SPEED, Speaker.WORD_GAP, Speaker.VOICE, text))
+ data = child.read()
+ err = child.close()
+ if err:
+ print "espeak terminated with return code %d" % err
+
+
+if __name__ == "__main__":
+ s = Speaker()
+ s.speak("It's two o'clock in the morning")
+ s.speak("It's seven hours and thirty-four minutes PM")
+ #s.speak("Il est quinze heures et vingt-neuf minutes")
+ #s.speak("vingt-deux heures dix-huit minutes")
+
diff --git a/test_timewriter/__init__.py b/test_timewriter/__init__.py
new file mode 100644
index 0000000..dce183a
--- /dev/null
+++ b/test_timewriter/__init__.py
@@ -0,0 +1,9 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Code released in the Public Domain. You can do whatever you want with this package.
+# Look at NOTES file to see how to adapt this program.
+# Originally written by Pierre Métras <pierre@alterna.tv> for the OLPC XO laptop.
+
+"""Module to test set of rules for writing time in full letters, in various languages.
+"""
diff --git a/test_timewriter/en_rules.py b/test_timewriter/en_rules.py
new file mode 100644
index 0000000..9433969
--- /dev/null
+++ b/test_timewriter/en_rules.py
@@ -0,0 +1,127 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Code released in the Public Domain. You can do whatever you want with this package.
+# Look at README file to see how to adapt this program.
+# Originally written by Pierre Métras <pierre@alterna.tv> for the OLPC XO laptop.
+#######################################
+# Timewriter rules for American English
+#######################################
+
+_time_rules = """
+ time(12, 0) => hour(12) |
+ time(0, 0) => hour(0) |
+ time(h, 0) => hour(h) o'clock am_pm(h) |
+ time(h, m) [m < 31] => min(m) past hour(h) am_pm(h) |
+ time(h, m) [30 < m] => min(m) to hour1(h) am_pm(h) |
+ min(1) => one minute |
+ min(2) => two minutes |
+ min(3) => three minutes |
+ min(4) => four minutes |
+ min(5) => five minutes |
+ min(6) => six minutes |
+ min(7) => seven minutes |
+ min(8) => eight minutes |
+ min(9) => nine minutes |
+ min(10) => ten minutes |
+ min(11) => eleven minutes |
+ min(12) => twelve minutes |
+ min(13) => thirteen minutes |
+ min(14) => fourteen minutes |
+ min(15) => a quarter |
+ min(16) => sixteen minutes |
+ min(17) => seventeen minutes |
+ min(18) => eighteen minutes |
+ min(19) => nineteen minutes |
+ min(20) => twenty minutes |
+ min(21) => twenty-one minutes |
+ min(22) => twenty-two minutes |
+ min(23) => twenty-three minutes |
+ min(24) => twenty-four minutes |
+ min(25) => twenty-five minutes |
+ min(26) => twenty-six minutes |
+ min(27) => twenty-seven minutes |
+ min(28) => twenty-eight minutes |
+ min(29) => twenty-nine minutes |
+ min(30) => half |
+ min(31) => twenty-nine minutes |
+ min(32) => twenty-eight minutes |
+ min(33) => twenty-seven minutes |
+ min(34) => twenty-six minutes |
+ min(35) => twenty-five minutes |
+ min(36) => twenty-four minutes |
+ min(37) => twenty-three minutes |
+ min(38) => twenty-two minutes |
+ min(39) => twenty-one minutes |
+ min(40) => twenty minutes |
+ min(41) => nineteen minutes |
+ min(42) => eighteen minutes |
+ min(43) => seventeen minutes |
+ min(44) => thirteen minutes |
+ min(45) => a quarter |
+ min(46) => fourteen minutes |
+ min(47) => thirteen minutes |
+ min(48) => twelve minutes |
+ min(49) => eleven minutes |
+ min(50) => ten minutes |
+ min(51) => nine minutes |
+ min(52) => eight minutes |
+ min(53) => seven minutes |
+ min(54) => six minutes |
+ min(55) => five minutes |
+ min(56) => four minutes |
+ min(57) => three minutes |
+ min(58) => two minutes |
+ min(59) => one minute |
+ min(60) => sixty minutes |
+ hour(0) => midnight |
+ hour(1) => one |
+ hour(2) => two |
+ hour(3) => three |
+ hour(4) => four |
+ hour(5) => five |
+ hour(6) => six |
+ hour(7) => seven |
+ hour(8) => eight |
+ hour(9) => nine |
+ hour(10) => ten |
+ hour(11) => eleven |
+ hour(12) => noon |
+ hour(13) => one |
+ hour(14) => two |
+ hour(15) => three |
+ hour(16) => four |
+ hour(17) => five |
+ hour(18) => six |
+ hour(19) => seven |
+ hour(20) => eight |
+ hour(21) => nine |
+ hour(22) => ten |
+ hour(23) => eleven |
+ hour1(0) => one |
+ hour1(1) => two |
+ hour1(2) => three |
+ hour1(3) => four |
+ hour1(4) => five |
+ hour1(5) => six |
+ hour1(6) => seven |
+ hour1(7) => eight |
+ hour1(8) => nine |
+ hour1(9) => ten |
+ hour1(10) => eleven |
+ hour1(11) => noon |
+ hour1(12) => one |
+ hour1(13) => two |
+ hour1(14) => three |
+ hour1(15) => four |
+ hour1(16) => five |
+ hour1(17) => six |
+ hour1(18) => seven |
+ hour1(19) => eight |
+ hour1(20) => nine |
+ hour1(21) => ten |
+ hour1(22) => eleven |
+ hour1(23) => midnight |
+ am_pm(h) [ h < 12] => AM |
+ am_pm(_) => PM
+ """
diff --git a/test_timewriter/es_rules.py b/test_timewriter/es_rules.py
new file mode 100644
index 0000000..92f6f36
--- /dev/null
+++ b/test_timewriter/es_rules.py
@@ -0,0 +1,127 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Code released in the Public Domain. You can do whatever you want with this package.
+# Look at README file to see how to adapt this program.
+# Originally written by Pierre Métras <pierre@alterna.tv> for the OLPC XO laptop.
+##############################
+# Timewriter rules for Spanish
+##############################
+
+_time_rules = """
+ time(h, 55) => hour1(h) menos cinco am_pm(h) |
+ time(h, 50) => hour1(h) menos diez am_pm(h) |
+ time(h, 45) => hour1(h) menos cuarto am_pm(h) |
+ time(h, 40) => hour1(h) menos vente am_pm(h) |
+ time(h, 35) => hour1(h) menos venticinco am_pm(h) |
+ time(h, m) => hour(h) min(m) am_pm(h) |
+ am_pm(0) => |
+ am_pm(12) => |
+ am_pm(h) [0 < h < 7] => de la madrugada |
+ am_pm(h) [h < 12] => de la mañana |
+ am_pm(h) [12 < h < 19] => de la tarde |
+ am_pm(_) => de la noche |
+ hour(0) => Medianoche |
+ hour(1) => Es la una |
+ hour(12) => Mediodía |
+ hour(13) => Es la una |
+ hour(14) => Son las dos |
+ hour(15) => Son las tres |
+ hour(16) => Son las cuatro |
+ hour(17) => Son las cinco |
+ hour(18) => Son las seis |
+ hour(19) => Son las siete |
+ hour(20) => Son las ocho |
+ hour(21) => Son las neuve |
+ hour(22) => Son las diez |
+ hour(23) => Son las once |
+ hour(h) [h < 12] => Son las number(h) |
+ hour1(0) => Es la una |
+ hour1(1) => Son las dos |
+ hour1(2) => Son las tres |
+ hour1(3) => Son las cuatro |
+ hour1(4) => Son las cinco |
+ hour1(5) => Son las seis |
+ hour1(6) => Son las siete |
+ hour1(7) => Son las ocho |
+ hour1(8) => Son las nueve |
+ hour1(9) => Son las diez |
+ hour1(10) => Son las once |
+ hour1(11) => Mediodía |
+ hour1(12) => hour1(0) |
+ hour1(13) => hour1(1) |
+ hour1(14) => hour1(2) |
+ hour1(15) => hour1(3) |
+ hour1(16) => hour1(4) |
+ hour1(17) => hour1(5) |
+ hour1(18) => hour1(6) |
+ hour1(19) => hour1(7) |
+ hour1(20) => hour1(8) |
+ hour1(21) => hour1(9) |
+ hour1(22) => hour1(10) |
+ hour1(23) => Medianoche |
+ min(0) => en punto |
+ min(15) => y cuarto |
+ min(30) => y media |
+ min(m) => y number(m) |
+ number(1) => uno |
+ number(2) => dos |
+ number(3) => tres |
+ number(4) => cuatro |
+ number(5) => cinco |
+ number(6) => seis |
+ number(7) => siete |
+ number(8) => ocho |
+ number(9) => nueve |
+ number(10) => diez |
+ number(11) => once |
+ number(12) => doce |
+ number(13) => trece |
+ number(14) => catorce |
+ number(15) => quince |
+ number(16) => dieciséis |
+ number(17) => diecisiete |
+ number(18) => dieciocho |
+ number(19) => diecinueve |
+ number(20) => veinte |
+ number(21) => veintiuno |
+ number(22) => veintidós |
+ number(23) => veintitrés |
+ number(24) => veinticuatro |
+ number(25) => veinticinco |
+ number(26) => veintiséis |
+ number(27) => veintisiete |
+ number(28) => veintiocho |
+ number(29) => veintinueve |
+ number(30) => trenta |
+ number(31) => trenta y uno |
+ number(32) => trenta y dos |
+ number(33) => trenta y tres |
+ number(34) => trenta y cuatro |
+ number(35) => trenta y cinco |
+ number(36) => trenta y seis |
+ number(37) => trenta y siete |
+ number(38) => trenta y ocho |
+ number(39) => trenta y nueve |
+ number(40) => cuarenta |
+ number(41) => cuarenta y uno |
+ number(42) => cuarenta y dos |
+ number(43) => cuatenta y tres |
+ number(44) => curatenta y cuatro |
+ number(45) => cuarenta y cinco |
+ number(46) => cuarenta y seis |
+ number(47) => cuarenta y siete |
+ number(48) => cuarenta y ocho |
+ number(49) => cuarenta y nueve |
+ number(50) => cincuenta |
+ number(51) => cincuenta y uno |
+ number(52) => cincuenta y dos |
+ number(53) => cincuenta y tres |
+ number(54) => cincuenta y cuatro |
+ number(55) => cincuenta y cinco |
+ number(56) => cincuenta y seis |
+ number(57) => cincuenta y siete |
+ number(58) => cincuenta y ocho |
+ number(59) => cincuenta y nueve
+ """
+
diff --git a/test_timewriter/fr_rules.py b/test_timewriter/fr_rules.py
new file mode 100644
index 0000000..8f512da
--- /dev/null
+++ b/test_timewriter/fr_rules.py
@@ -0,0 +1,110 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Code released in the Public Domain. You can do whatever you want with this package.
+# Look at README file to see how to adapt this program.
+# Originally written by Pierre Métras <pierre@alterna.tv> for the OLPC XO laptop.
+#############################
+# Timewriter rules for French
+#############################
+
+_time_rules = """
+ time(h, 35) => hour1(h) moins vingt-cinq |
+ time(h, 40) => hour1(h) moins vingt |
+ time(h, 45) => hour1(h) moins le quart |
+ time(h, 50) => hour1(h) moins dix |
+ time(h, 55) => hour1(h) moins cinq |
+ time(h, m) => hour(h) min(m) |
+ hour(0) => minuit |
+ hour(1) => une heure |
+ hour(12) => midi |
+ hour(h) => number(h) heures |
+ hour1(0) => une heure |
+ hour1(1) => deux heures |
+ hour1(2) => trois heures |
+ hour1(3) => quatre heures |
+ hour1(4) => cinq heures |
+ hour1(5) => six heures |
+ hour1(6) => sept heures |
+ hour1(7) => huit heures |
+ hour1(8) => neuf heures |
+ hour1(9) => dix heures |
+ hour1(10) => onze heures |
+ hour1(11) => midi |
+ hour1(12) => treize heures |
+ hour1(13) => quatorze heures |
+ hour1(14) => quinze heures |
+ hour1(15) => seize heures |
+ hour1(16) => dix-sept heures |
+ hour1(17) => dix-huit heures |
+ hour1(18) => dix-neuf heures |
+ hour1(19) => vingt heures |
+ hour1(20) => vingt-et-une heures |
+ hour1(21) => vingt-deux heures |
+ hour1(22) => vingt-trois heures |
+ hour1(23) => minuit |
+ min(0) => |
+ min(1) => et une minute |
+ min(15) => et quart |
+ min(30) => et demie |
+ min(m) => et number(m) minutes |
+ number(1) => une |
+ number(2) => deux |
+ number(3) => trois |
+ number(4) => quatre |
+ number(5) => cinq |
+ number(6) => six |
+ number(7) => sept |
+ number(8) => huit |
+ number(9) => neuf |
+ number(10) => dix |
+ number(11) => onze |
+ number(12) => douze |
+ number(13) => treize |
+ number(14) => quatorze |
+ number(15) => quinze |
+ number(16) => seize |
+ number(17) => dix-sept |
+ number(18) => dix-huit |
+ number(19) => dix-neuf |
+ number(20) => vingt |
+ number(21) => vingt-et-une |
+ number(22) => vingt-deux |
+ number(23) => vingt-trois |
+ number(24) => vingt-quatre |
+ number(25) => vingt-cinq |
+ number(26) => vingt-six |
+ number(27) => vingt-sept |
+ number(28) => vingt-huit |
+ number(29) => vingt-neuf |
+ number(30) => trente |
+ number(31) => trente-et-une |
+ number(32) => trente-deux |
+ number(33) => trente-trois |
+ number(34) => trente-quatre |
+ number(35) => trente-cinq |
+ number(36) => trente-six |
+ number(37) => trente-sept |
+ number(38) => trente-huit |
+ number(39) => trente-neuf |
+ number(40) => quarante |
+ number(41) => quarante-et-une |
+ number(42) => quarante-deux |
+ number(43) => quarante-trois |
+ number(44) => quarante-quatre |
+ number(45) => quarante-cinq |
+ number(46) => quarante-six |
+ number(47) => quarante-sept |
+ number(48) => quarante-huit |
+ number(49) => quarante-neuf |
+ number(50) => cinquante |
+ number(51) => cinquante-et-une |
+ number(52) => cinquante-deux |
+ number(53) => cinquante-trois |
+ number(54) => cinquante-quatre |
+ number(55) => cinquante-cinq |
+ number(56) => cinquante-six |
+ number(57) => cinquante-sept |
+ number(58) => cinquante-huit |
+ number(59) => cinquante-neuf
+ """
diff --git a/timewriter.py b/timewriter.py
new file mode 100644
index 0000000..7b714be
--- /dev/null
+++ b/timewriter.py
@@ -0,0 +1,522 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Code released in the Public Domain. You can do whatever you want with this package.
+# Look at README file to see how to adapt this program.
+# Originally written by Pierre Métras <pierre@alterna.tv> for the OLPC XO laptop.
+
+
+"""Inference engine to write times in English (or other language).
+
+Change the rules to adapt to another language. Caution: the rule parser does not
+strictly check the syntax and many errors will remained ignored.
+
+Usefull functions:
+- print_rules(): Dump the set of rules.
+- test_times(): Try to print all times from 00:00 to 23:59.
+- write_time(hour, minute): Write the (hour, minute) in natural language.
+- eval_rule(text): Translate the text according to the set of rules.
+
+Example of usage:
+-----------------
+import timewriter
+
+w = timewriter.TimeWriter("en")
+s = w.write_time(2, 33)
+print "It is %s."
+
+prints --> It is thirty-three minutes past two in the morning.
+
+How to tell time in English?
+----------------------------
+http://en.wikipedia.org/wiki/12-hour_clock
+"""
+
+#import pdb
+
+import tokenize
+import cStringIO
+import re
+import copy
+
+from gettext import gettext as _
+
+"""
+Grammar for the rules:
+----------------------
+Root := Pattern
+Pattern := Text? Pattern_call? Pattern?
+Pattern_call := Pattern_name ( Argument [, Argument]* )
+Text := string
+Pattern_name := string
+Argument := Dumb_variable_ | Variable | Value
+Dumb_variable := '_'
+Variable := string
+Value := number
+Rules := Rule ('|' Rules)*
+Rule := Pattern_call Range_condition* '=>' Text? Pattern_call? Pattern?
+Range_condition := '[' Argument '<' Argument ('<' Argument)? ']'
+
+'#' can be used to concatenate two Texts or Pattern_call without a space between.
+"""
+
+
+class _Rule:
+ """A rule is composed of conditions and a body.
+ Rule: Conditions => Body
+
+ Examples:
+ number(8) => eight
+ plural(1) =>
+ plural(_) => s
+
+ The inference engine tries to match a template with the conditions pattern of
+ the rule, eventually binding the variables. If the rule matches, then the body
+ of the rule is used, after substituting the variables by their values and
+ eventually firing the other rules called in the body definition.
+ """
+
+ def __init__(self, pattern, ranges, body):
+ """Create a new rule from its conditions, optional ranges and body.
+ """
+ self._pattern = pattern
+ self._ranges = ranges
+ self._body = body
+
+
+ def get_pattern(self):
+ """Gets the conditions pattern of the rule.
+ Returns a list [rule_name, arg1, arg2...].
+ """
+ return self._pattern
+
+
+ def get_ranges(self):
+ """Gets the range condition to apply the rule.
+ Returns a list [[arg1, arg2], [arg1, arg2, arg3]...].
+ """
+ return self._ranges
+
+
+ def get_body(self):
+ """Gets the body of the rule.
+ Returns a list, for instance with two text fragments around another rule
+ call, [text1, [rule_name, arg1, arg2], text2].
+ """
+ return self._body
+
+
+ def __str__(self):
+ """Gets the external representation of the rule as lists.
+ """
+ return "Rule: %s %s => %s" % (self._pattern, self._ranges, self._body)
+
+
+ def __repr__(self):
+ """
+ Returns the external representation of the rule.
+ """
+ return self._repr_call(self._pattern) + self._repr_ranges(self._ranges) + " => " + self._repr_body(self._body)
+
+
+ def _repr_call(self, call):
+ """Returns the external representation of a rule call.
+ """
+ return "%s(%s)" % (call[0], ", ".join(str(x) for x in call[1:]))
+
+
+ def _repr_ranges(self, ranges):
+ """Returns the external repressentation of a rule ranges.
+ """
+ result = ""
+ for r in ranges:
+ if len(r) == 2:
+ result += " [ %s < %s ]" % (r[0], r[1])
+ else:
+ result += " [ %s < %s < %s ]" % (r[0], r[1], r(2))
+ return result
+
+
+ def _repr_body(self, body):
+ """Returns the external representation of a rule body.
+ """
+ result = ""
+ for item in body:
+ if isinstance(item, list):
+ result += "#" + self._repr_call(item)
+ else:
+ result += item
+ return result
+
+
+class TimeWriter:
+ """A class to print the time in natural language.
+ """
+
+
+ def __init__(self):
+ """Create a time writer for the current language.
+ The rules localized for a language are stored in the localized messages file.
+ """
+ self._rules = self.parse_rules(self._time_rules)
+
+
+ # TRANS: The rules to print the time in the localized language.
+ #
+ # Example syntax:
+ # time(h, 15) => a quarter to hour(h) am_pm(h) |
+ # The left hand side of the rule defines a pattern with a variable 'h' and a
+ # value '15'.
+ # The right hand side, when applied, will use the text "a quarter to " and call
+ # the first rule matching hour(h) after substituting the variable 'h' by its value,
+ # and call the rule matching am_pm(h).
+ # Internal spaces are significant on the right side of a rule. In calls, all
+ # arguments which are not numbers are considered to be variables. The rule parser
+ # is very simple and will let many syntax errors go ignored.
+ #
+ # A rule ends with the character '|'.
+ # The character '_' is a anonymous variable.
+ # The character '#' can be used to concatenate two text fragments. For instance:
+ # plural(1) => |
+ # plural(_) => s |
+ # hour(h) => number(h) hour#plural(h) |
+ # Use '\#' to use a # character, for instance in a pango color
+ # tag like <span foreground="\#FF0055">
+ #
+ # You can put range conditions on firing a rule, with the syntax [var1 < var2] or
+ # [var1 < var2 < var3]. For instance:
+ # hours(h) [h < 12] => in the morning |
+ # hours(h) [12 < h < 18] => in the afternoon |
+ # hours(_) => in the night |
+ #
+ # These rules will be called with the root pattern "time(hour, minute)", with the
+ # variable 'hour' bound to the current hour and the variable 'minute' to the
+ # current minute.
+ # Order of rules is important. Rules are tried from first to last. So most precise
+ # rule must be placed first in the list.
+ #
+ # You can validate your set of rules by running the command line:
+ # python timewriter.py LANG
+ #
+ # You should use pango markup to respect the same colors as for the clock hands.
+ # Look at the README file from the activity for explanations on how to create
+ # rules.
+ _time_rules = _("""time(h, m) => What Time Is It?""")
+
+
+ def _syntax_error(self, rule):
+ """Print an error message when a rule can't be parsed.
+ """
+ raise SyntaxError("Syntax error in rule: %s" % rule)
+
+
+ def print_rules(self):
+ """Print the list of rules. Can be used to check the parser.
+ """
+ print "Rules = ["
+ for i, rule in enumerate(self._rules):
+ print "#%d %s" % (i, rule)
+ print "]\nTotal = %d rules\n" % len(self._rules)
+
+
+ def repr_rules(self):
+ """Gets the external representation of the rules.
+ """
+ return " |\n".join(repr(rule) for rule in self._rules)
+
+
+ def parse_rules(self, source):
+ """Parse all the rules for the current language.
+ Rules are a list of rule definitions separated by |.
+ Rules := Rule ( '|' Rule )*
+ Returns the list of rules.
+ """
+ self._rules = []
+ for rule in source.split("|"):
+ r = self._parse_rule(rule)
+ self._rules.append(r)
+ return self._rules
+
+
+ def _parse_rule(self, source):
+ """Parse a single rule.
+ A rule is composed of a pattern and a body, separated by =>.
+ Rule := Pattern_call Range_condition* '=>' Rule_body
+ Return a rule definition object.
+ """
+ r = re.findall(r"\s*(\w+\s*\(.*?\))\s*(\[.*\])?\s*=>(.*)", source)
+ if r[0] is None or r[0][0] == "" or r[0][2] == "":
+ self._syntax_error(rule)
+ pattern_call = self._parse_call(r[0][0])
+ range_conditions = self._parse_ranges(r[0][1])
+ rule_body = self._parse_body(r[0][2].strip())
+ rule = _Rule(pattern_call, range_conditions, rule_body)
+ return rule
+
+
+ def _parse_call(self, source):
+ """Parse a rule pattern or call.
+ A rule call is similar to a function call.
+ Rule_call := Rule_name '(' ( arg [',' arg]* ) ')'
+ Returns a list [Rule_name, arg1, arg2...]
+ """
+ src = cStringIO.StringIO(source).readline
+ src = tokenize.generate_tokens(src)
+ token = src.next()
+ if token[0] is not tokenize.NAME:
+ self._syntax_error(source)
+ call = [token[1]]
+ token = src.next()
+ if token[1] != "(":
+ self._syntax_error(source)
+ token = src.next()
+ while token[1] != ")":
+ try:
+ call.append(int(token[1]))
+ except ValueError:
+ call.append(token[1])
+ token = src.next()
+ if token[1] == ",":
+ token = src.next()
+ return call
+
+
+ def _parse_ranges(self, source):
+ """Parse zero or many range conditions.
+ Range_conditions := Range_condition*
+ Range_condition := '[' arg1 '<' arg2 ('<' arg3)? ']'
+ Returns a list [[arg11, arg12], [arg21, arg22, arg23]...]
+ """
+ if source == "":
+ return None
+ else:
+ ranges = []
+ for r in re.findall(r"\[\s*(.*?)\s*<\s*(.*?)\s*(?:<\s(.*?))?\s*\]", source):
+ rang = []
+ for x in r:
+ if x != "":
+ try:
+ rang.append(int(x))
+ except ValueError:
+ rang.append(x)
+ ranges.append(rang)
+ return ranges
+
+
+ def _parse_body(self, source):
+ """Parse the right hand side of a rule.
+ We must preserve spaces in the rule body, so we use regular expression for parsing.
+ Rule_body := text? Pattern_call? Rule_body?
+ Returns a list [text, (pattern, arg1, arg2...), text, ...]
+ '#' is a concatenation operator if not escaped by '\'
+ """
+ if not re.search(r"(\w+\s*\(.*?\))", source):
+ # No rull call present
+ return [source]
+ else:
+ body = []
+ text = ""
+ escaped = False
+ for item in re.findall(r"(?:(\w+\s*\(.*?\))|(.))", source):
+ if item[0] != "":
+ if text != "":
+ body.append(text)
+ text = ""
+ body.append(self._parse_call(item[0]))
+ else:
+ if item[1] == "\\":
+ escaped= True
+ elif item[1] == "#":
+ if escaped:
+ text += item[1]
+ escaped = False
+ else:
+ text += item[1]
+ escaped = False
+ if text != "":
+ body.append(text)
+ return body
+
+
+ def write_time(self, hour, minute):
+ """Gives the natural language translation of the time.
+ For instance, write_time(3, 41) returns "three hours and forty-one minutes in the morning" with an English TimeWriter.
+ """
+ return self.eval_rule("time(%s, %s)" % (hour, minute))
+
+
+ def eval_rule(self, source):
+ """Evaluate the source against the set of rules.
+ Example: eval_rule("It is time(15, 2).")
+ """
+ lst = self._parse_body(source)
+ # lst = [text, [call, arg1, arg2..], text, ...]
+ # The goal is now to flatten the list lst resolving all the calls
+ lst = self._match_pattern(lst)
+ return "".join(lst)
+
+
+ def _match_pattern(self, patterns):
+ """Match a list of patterns agains the set of rules.
+ This engine stops at the first rule matching the pattern and eventually
+ instanciates the variables, then recursively apply them in the body of the
+ matched rule.
+ Returns a list with all the patterns replaced by the rules bodies infered.
+ If a pattern can't be matched, the engine produces no result in the resulting
+ list. As we expect the set of rules to be complete (all submitted patterns
+ fire at least one rule), we raise an exception if the number of items in the
+ result is not the same as the number of patters submitted.
+ """
+ result = []
+
+ for pattern in patterns:
+ if isinstance(pattern, list):
+ for rule in self._rules:
+ cond = rule.get_pattern()
+
+ # Simple tests first
+ if len(pattern) != len(cond):
+ continue
+ # Check that we test the same set of rules
+ if pattern[0] != cond[0]:
+ continue
+
+ # We use lazy rule body copy, only when there is a variable in the
+ # pattern. I've found that I got a 50% boost in performance doing
+ # that instead of doing the deepcopy from start.
+ body = None
+
+ # Now we check that the premises match and substitute all
+ # variables in rule body.
+ match = True
+ # The dictionary will keep the variable bindings
+ bind = {}
+ for i in range(1, len(pattern)):
+ # Dumb variable
+ if cond[i] == "_":
+ continue
+
+ # Variable instanciation
+ if not isinstance(cond[i], int):
+ if body is None:
+ body = copy.deepcopy(rule.get_body())
+ body = self._apply_var(cond[i], pattern[i], body)
+ bind[cond[i]] = pattern[i]
+ continue
+
+ if pattern[i] != cond[i]:
+ match = False
+ break
+
+ # Checking the range conditions to see if we can apply the rule
+ ranges = rule.get_ranges()
+ if match and ranges:
+ ranges = copy.deepcopy(ranges)
+ for r in ranges:
+ # Bind all variables
+ for i in range(0, len(r)):
+ if not isinstance(r[i], int):
+ try:
+ r[i] = bind[r[i]]
+ except KeyError:
+ #self._syntax_error(rule)
+ match = False
+ break
+
+ # Now check that the range is valid
+ if r[0] >= r[1]:
+ match = False
+ break
+ if len(r) > 2 and r[1] >= r[2]:
+ match = False
+ break
+
+ if match:
+ # Then we apply all the rule calls in the body of the rule
+ # that matched
+ if body is None:
+ body = copy.deepcopy(rule.get_body())
+ calls = True
+ while calls:
+ calls = False
+ for i in range(0, len(body)):
+ if isinstance(body[i], list):
+ body[i] = "".join(self._match_pattern([body[i]]))
+ calls = True
+ result.append("".join(body))
+ break
+ else:
+ result.append(pattern)
+
+ if len(result) != len(patterns):
+ raise Exception("There is a missing rule; match failed for pattern %s..." % patterns)
+
+ return result
+
+
+ def _apply_var(self, var, value, body):
+ """Instanciate a variable in the body of a rule.
+ Returns the body of the rule with that variable substituted by its value in all calls. This function eventually changes the 'body' argument.
+ """
+ for elem in body:
+ if isinstance(elem, list):
+ for i in range(1, len(elem)):
+ if elem[i] == var:
+ elem[i] = value
+ return body
+
+
+ def test_times(self):
+ """Check that the time rules are complete, printing all combinations.
+ """
+ print "***** Checking all times *****"
+
+ for h in range(0, 24):
+ for m in range(0, 60):
+ str = self.write_time(h, m)
+ print "time(%d, %d) -> %s" % (h, m, str)
+
+
+ def set_rules(self, rules_source):
+ """Assign the source of rules to the timewriter instance.
+ The rules are parsed during the operation.
+ """
+ self._rules = self.parse_rules(rules_source)
+
+
+
+def main():
+ """Main entry point to test rules.
+ """
+ # import sys
+ if len(sys.argv) == 1:
+ print "Usage: python timewriter.py lang"
+ print "Where lang is a ISO language code (en, fr, es...)"
+ print "TimeWriter rules must be available in directory test-timewriter."
+ exit(1)
+ lang = sys.argv[1]
+ test_mod = "test_timewriter." + lang + "_rules"
+ import_mod = "from " + test_mod + " import _time_rules as test_rules"
+ exec import_mod
+ w = TimeWriter()
+ w.set_rules(test_rules)
+ print "***** Rules parsed *****"
+ w.print_rules()
+ w.test_times()
+
+
+# Run "$ python timewriter.py en" to check all rules for English ("en" argument)
+# Run "$ python timewriter.py en 1" to get profiling information.
+if __name__ == "__main__":
+ import sys
+ if len(sys.argv) > 2:
+ import cProfile
+ cProfile.run("main()", "genprof")
+ import pstats
+ p = pstats.Stats("genprof")
+ print
+ print "***** Profiling *****"
+ p.strip_dirs().sort_stats("time", "name").print_stats(0.1)
+ else:
+ main()
+