#!/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 # 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 Based on work from Davyd Madeley, Lawrence Oluyede 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 gst import cairo import pangocairo import rsvg OLD_TOOLBAR = False try: from sugar.graphics.toolbarbox import ToolbarBox from sugar.activity.widgets import StopButton from sugar.activity.widgets import ActivityToolbarButton except ImportError: OLD_TOOLBAR = True import math from datetime import datetime import threading import re from gettext import gettext as _ from sugar.activity import activity from sugar.graphics.toggletoolbutton import ToggleToolButton from sugar.graphics.radiotoolbutton import RadioToolButton from sugar.graphics import style from speaker import Speaker from timewriter import TimeWriter import dbus import os # The display modes of the clock _MODE_SIMPLE_CLOCK = 0 _MODE_NICE_CLOCK = 1 _MODE_DIGITAL_CLOCK = 2 # directory exists if powerd is running. create a file here, # named after our pid, to inhibit suspend. POWERD_INHIBIT_DIR = '/var/run/powerd-inhibit-suspend' 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(_('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 = _('\ %s') # 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 = _('\ \ %A, \ %m/\ %d/\ %Y') # 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() if OLD_TOOLBAR: # 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) if not self.powerd_running(): try: bus = dbus.SystemBus() proxy = bus.get_object('org.freedesktop.ohm', '/org/freedesktop/ohm/Keystore') self.ohm_keystore = dbus.Interface(proxy, 'org.freedesktop.ohm.Keystore') except dbus.DBusException, e: self.ohm_keystore = None def powerd_running(self): self.using_powerd = os.access(POWERD_INHIBIT_DIR, os.W_OK) return self.using_powerd def _inhibit_suspend(self): if self.using_powerd: fd = open(POWERD_INHIBIT_DIR + "/%u" % os.getpid(), 'w') fd.close() return True if self.ohm_keystore is not None: try: self.ohm_keystore.SetKey('suspend.inhibit', 1) return self.ohm_keystore.GetKey('suspend.inhibit') except dbus.exceptions.DBusException: return False else: return False def _allow_suspend(self): if self.using_powerd: os.unlink(POWERD_INHIBIT_DIR + "/%u" % os.getpid()) return True if self.ohm_keystore is not None: try: self.ohm_keystore.SetKey('suspend.inhibit', 0) return self.ohm_keystore.GetKey('suspend.inhibit') except dbus.exceptions.DBusException: return False else: return False 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 if OLD_TOOLBAR: toolbox = activity.ActivityToolbox(self) self.set_toolbox(toolbox) display_toolbar = gtk.Toolbar() # Add the toolbar to the activity menu self._add_clock_controls(display_toolbar) toolbox.add_toolbar(_('Clock'), display_toolbar) toolbox.set_current_toolbar(1) return toolbox else: self.max_participants = 1 toolbar_box = ToolbarBox() activity_button = ActivityToolbarButton(self) activity_button.show() toolbar_box.toolbar.insert(activity_button, 0) self._add_clock_controls(toolbar_box.toolbar) separator = gtk.SeparatorToolItem() separator.props.draw = False separator.set_size_request(0, -1) separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) toolbar_box.toolbar.insert(StopButton(self), -1) self.set_toolbar_box(toolbar_box) toolbar_box.show_all() display_toolbar = toolbar_box.toolbar return toolbar_box def _add_clock_controls(self, display_toolbar): # First group of radio button to select the type of clock to display button1 = RadioToolButton(named_icon="simple-clock") button1.set_tooltip(_('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(_('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(_('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(_('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(_('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(_('Talking clock')) button.connect("toggled", self._speak_time_clicked_cb) display_toolbar.insert(button, -1) 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(): self._date.show() else: 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: self._time_letters.show() self._write_and_speak(False) else: 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 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 if self.props.active: self._inhibit_suspend() else: self._allow_suspend() 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() hour = self._clock.get_time().hour minute = self._clock.get_time().minute self._time_in_letters = self._time_writer.write_time(hour, minute) self._time_letters.set_markup(self._TIME_LETTERS_FORMAT % self._time_in_letters) def _do_speak_time(self): """Speak aloud the current time. """ def gstmessage_cb(bus, message, pipe): if message.type in (gst.MESSAGE_EOS, gst.MESSAGE_ERROR): pipe.set_state(gst.STATE_NULL) if self._time_speaker is None: self._time_speaker = Speaker() pipeline = 'espeak text="%(text)s" voice="%(voice)s" pitch="%(pitch)s" \ rate="%(rate)s" gap="%(gap)s" ! autoaudiosink' % { 'text':self._untag(self._time_in_letters), 'voice':self._time_speaker.VOICE, 'pitch':self._time_speaker.PITCH, 'rate':self._time_speaker.SPEED, 'gap':self._time_speaker.WORD_GAP} try: pipe = gst.parse_launch(pipeline) bus = pipe.get_bus() bus.add_signal_watch() bus.connect('message', gstmessage_cb, pipe) pipe.set_state(gst.STATE_PLAYING) except: 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__() # Set to True when the variables to draw the clock are set: self.initialized = False # 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 handle self._svg_handle = None self._radius = -1 self._line_width = 2 # Color codes (approved colors for XO screen: # http://wiki.laptop.org/go/XO_colors) # XO Medium Blue self._COLOR_HOURS = "#005FE4" # XO Medium Green self._COLOR_MINUTES = "#00B20D" # XO Medium Red self._COLOR_SECONDS = "#E6000A" # White self._COLOR_WHITE = "#FFFFFF" # Black self._COLOR_BLACK = "#000000" # 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). """ # Store the measures of the clock face widget self._center_x = int(allocation.width / 2.0) self._center_y = int(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 svg handle self._svg_handle = rsvg.Handle(file="clock.svg") cr = self.window.cairo_create() self._nice_background_cache = cr.get_target().create_similar( cairo.CONTENT_COLOR_ALPHA, self._radius * 2, self._radius * 2) cache_ctx = cairo.Context(self._nice_background_cache) scale_x = self._radius * 2.0 / self._svg_handle.props.width scale_y = self._radius * 2.0 / self._svg_handle.props.height matrix = cairo.Matrix(xx=scale_x, yy=scale_y) cache_ctx.transform(matrix) self._svg_handle.render_cairo(cache_ctx) self.initialized = True 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 not self.initialized and self.window: self.queue_resize() if self._active: 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: msg = "Unknown display mode: %d." % self._mode raise ValueError(msg) 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] x_delta = int(x - width / 2 - x_bearing) y_delta = int(y - height / 2 - y_bearing) self.window.draw_layout(self._gc, x_delta, y_delta, 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 cr = self.window.cairo_create() cr.set_source_rgba(*style.Color(self._COLOR_WHITE).get_rgba()) cr.rectangle(round(self._center_x - 1.1 * self._radius), round(self._center_y - 0.85 * self._radius), round(2.2 * self._radius), round(0.65 * self._radius)) cr.fill() h = round(0.15 * self._radius) x = round(self._center_x - self._radius) # Hours scale cr.set_source_rgba(*style.Color(self._COLOR_HOURS).get_rgba()) y = round(self._center_y - 0.75 * self._radius) cr.rectangle(x, y, hours_length, h) cr.fill() # Minutes scale cr.set_source_rgba(*style.Color(self._COLOR_MINUTES).get_rgba()) y = round(self._center_y - 0.60 * self._radius) cr.rectangle(x, y, minutes_length, h) cr.fill() # Seconds scale cr.set_source_rgba(*style.Color(self._COLOR_SECONDS).get_rgba()) y = round(self._center_y - 0.45 * self._radius) cr.rectangle(x, y, seconds_length, h) cr.fill() 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 = _('\ \ %I:\ %M:\ %S%p') # 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) cr = self.window.cairo_create() cr = pangocairo.CairoContext(cr) cr.set_source_rgba(*style.Color(self._COLOR_BLACK).get_rgba()) pango_layout = cr.create_layout() d = int(self._center_y + 0.3 * self._radius) pango_layout.set_markup(markup_time) dx, dy = pango_layout.get_pixel_size() pango_layout.set_alignment(pango.ALIGN_CENTER) cr.translate(self._center_x - dx / 2.0, d - dy / 2.0) cr.show_layout(pango_layout) 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. """ cr = self.window.cairo_create() cr.set_line_width(4 * self._line_width) cr.set_line_cap(cairo.LINE_CAP_ROUND) # Simple clock background cr.set_source_rgba(*style.Color(self._COLOR_WHITE).get_rgba()) cr.arc(self._width / 2, self._height / 2, self._radius, 0, 2 * math.pi) cr.fill_preserve() cr.set_source_rgba(*style.Color(self._COLOR_BLACK).get_rgba()) cr.stroke() # Clock ticks for i in xrange(60): if i % 15 == 0: inset = 0.11 * self._radius cr.set_line_width(7 * self._line_width) elif i % 5 == 0: inset = 0.1 * self._radius cr.set_line_width(5 * self._line_width) else: inset = 0.05 * self._radius cr.set_line_width(4 * self._line_width) cos = math.cos(i * math.pi / 30.0) sin = math.sin(i * math.pi / 30.0) cr.move_to(int(self._center_x + (self._radius - inset) * cos), int(self._center_y + (self._radius - inset) * sin)) cr.line_to(int(self._center_x + (self._radius - 3) * cos), int(self._center_y + (self._radius - 3) * sin)) cr.stroke() def _draw_nice_background(self): """Draw the nice clock background. The background has been loaded from the clock.svg file to a rsvg handle, and we just transform this handle and render it with cairo. """ # We transform the background SVG cr = self.window.cairo_create() cr.translate(self._center_x - self._radius, self._center_y - self._radius) cr.set_source_surface(self._nice_background_cache) cr.paint() 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 cr = self.window.cairo_create() cr.set_line_cap(cairo.LINE_CAP_ROUND) # Hour hand: # The hour hand is rotated 30 degrees (pi/6 r) per hour + # 1/2 a degree (pi/360) per minute cr.set_source_rgba(*style.Color(self._COLOR_HOURS).get_rgba()) cr.set_line_width(9 * self._line_width) cr.arc(self._center_x, self._center_y, 5 * self._line_width, 0, 2 * math.pi) cr.fill_preserve() cr.move_to(self._center_x, self._center_y) cr.line_to(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))) cr.stroke() # Minute hand: # The minute hand is rotated 6 degrees (pi/30 r) per minute cr.set_source_rgba(*style.Color(self._COLOR_MINUTES).get_rgba()) cr.set_line_width(6 * self._line_width) cr.arc(self._center_x, self._center_y, 4 * self._line_width, 0, 2 * math.pi) cr.fill_preserve() cr.move_to(self._center_x, self._center_y) cr.line_to(int(self._center_x + self._radius * 0.7 * math.sin(math.pi / 30 * minutes)), int(self._center_y + self._radius * 0.7 * - math.cos(math.pi / 30 * minutes))) cr.stroke() # Seconds hand: # Operates identically to the minute hand cr.set_source_rgba(*style.Color(self._COLOR_SECONDS).get_rgba()) cr.set_line_width(2 * self._line_width) cr.arc(self._center_x, self._center_y, 3 * self._line_width, 0, 2 * math.pi) cr.fill_preserve() cr.move_to(self._center_x, self._center_y) cr.line_to(int(self._center_x + self._radius * 0.8 * math.sin(math.pi / 30 * seconds)), int(self._center_y + self._radius * 0.8 * - math.cos(math.pi / 30 * seconds))) cr.stroke() def _draw_numbers(self): """Draw the numbers of the hours. """ cr = self.window.cairo_create() cr = pangocairo.CairoContext(cr) cr.set_source_rgba(*style.Color(self._COLOR_HOURS).get_rgba()) pango_layout = cr.create_layout() for i in xrange(12): # TRANS: The format of the font used to print hour # numbers, from 1 to 12. hour_number = _('%d') % (i + 1) cr.save() pango_layout.set_markup(hour_number) dx, dy = pango_layout.get_pixel_size() cr.translate(- dx / 2.0 + self._center_x + 0.75 * self._radius * math.cos((i - 2) * math.pi / 6.0), - dy / 2.0 + self._center_y + 0.75 * self._radius * math.sin((i - 2) * math.pi / 6.0)) cr.update_layout(pango_layout) cr.show_layout(pango_layout) cr.restore() 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)