I think I found the problem. Could you confirm replacing the attached file in the activity dir? Thanks!
Gonzalo On Tue, May 12, 2015 at 5:39 PM, James Cameron <qu...@laptop.org> wrote: > On Tue, May 12, 2015 at 12:00:42PM -0400, Sugar Labs Activities wrote: > > Activity Homepage: > > http://activities.sugarlabs.org/addon/4191 > > > > Sugar Platform: > > 0.98 - 0.104 > > As it needs 0.104, can you please change it in ASLO? > > > Download Now: > > http://activities.sugarlabs.org/downloads/file/29101/clock-17.xo > > Applied using Software Update. > > Sometimes speaks. > > When it doesn't speak, it reports in log: > > (sugar-activity:965): GStreamer-CRITICAL **: gst_memory_new_wrapped: > assertion `offset + size <= maxsize' failed > > (sugar-activity:965): GStreamer-CRITICAL **: gst_buffer_insert_memory: > assertion `mem != NULL' failed > > Sometimes stops activity, and reports in log: > > Terminated by signal 11, pid 877 data (None, <open file '<fdopen>', mode > 'w' at 0xa2c3de0>, '1bb8235db05c7a9492d53840cbe64cf87c343a51') > > Always reports in log: > > /home/olpc/Activities/Clock.activity/clock.py:495: Warning: value "170" of > type `gint' is invalid or out of range for property `rate' of type `gint' > pipe = Gst.parse_launch(pipeline) > > Always takes an additional second to speak. > > -- > James Cameron > http://quozl.linux.org.au/ > -- Gonzalo Odiard SugarLabs - Software for children learning
#!/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 # <pie...@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 <pie...@alterna.tv> Based on work from Davyd Madeley, Lawrence Oluyede <l.oluy...@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. from gi.repository import Gtk from gi.repository import Gdk from gi.repository import Rsvg from gi.repository import Pango from gi.repository import GObject from gi.repository import PangoCairo import logging import os import re import math import cairo from datetime import datetime from gettext import gettext as _ from sugar3.graphics import style from sugar3.activity import activity from sugar3.activity.widgets import StopButton from sugar3.graphics.toolbarbox import ToolbarBox from sugar3.activity.widgets import ActivityToolbarButton from sugar3.graphics.radiotoolbutton import RadioToolButton from sugar3.graphics.toggletoolbutton import ToggleToolButton from speaker import Speaker from timewriter import TimeWriter import dbus # 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' # Tolerance for grabbing hands, in radians. Each hand can be grabbed # if the user press on a piece of the circle that is the angle of the # hand +- the tolerance angle. _ANGLE_TOLERANCE = 0.3 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 = _('<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 = _('<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._time_writer = None self._time_in_letters = self.get_title() self._time_letters = None self._date = None self._time_speaker = None self._write_time = False self._speak_time = False self._write_date = False self._display_mode_buttons = [] self._make_display() self._make_toolbars() # Show the activity on the screen self.show_all() # We want to be notified when the minutes change self._clock.connect("time_minute", self._minutes_changed_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: self.ohm_keystore = None def write_file(self, file_path): self.metadata['write-time'] = str(self._write_time) self.metadata['write-date'] = str(self._write_date) self.metadata['speak-time'] = str(self._speak_time) self.metadata['clock-mode'] = str(self._clock._mode) logging.debug('Saving metadata %s', (self.metadata['write-time'], self.metadata['write-date'], self.metadata['speak-time'], self.metadata['clock-mode'])) # Need write a empty file or the read_file is not called with open(file_path, 'w') as data: data.write('') def read_file(self, file_path): logging.debug('Reading metadata %s', (self.metadata['write-time'], self.metadata['write-date'], self.metadata['speak-time'], self.metadata['clock-mode'])) if 'clock-mode' not in self.metadata.keys(): display_mode = _MODE_SIMPLE_CLOCK else: display_mode = int(self.metadata['clock-mode']) if 'write-time' in self.metadata.keys(): self._write_time = str(self.metadata['write-time']) == 'True' if 'speak-time' in self.metadata.keys(): self._speak_time = str(self.metadata['speak-time']) == 'True' if 'write-date' in self.metadata.keys(): self._write_date = str(self.metadata['write-date']) == 'True' logging.debug('Read values %s', (self._write_time, self._speak_time, self._write_date, display_mode)) # apply the changes in the UI self._display_mode_buttons[display_mode].set_active(True) self._write_time_btn.set_active(self._write_time) self._write_date_btn.set_active(self._write_date) self._speak_time_btn.set_active(self._speak_time) 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. """ 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() 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(icon_name="simple-clock") button1.set_tooltip(_('Simple Clock')) button1.connect("toggled", self._display_mode_changed_cb, _MODE_SIMPLE_CLOCK) display_toolbar.insert(button1, -1) self._display_mode_buttons.append(button1) button2 = RadioToolButton(icon_name="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) self._display_mode_buttons.append(button2) button3 = RadioToolButton(icon_name="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) self._display_mode_buttons.append(button3) # 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 self._write_time_btn = ToggleToolButton("write-time") self._write_time_btn.set_tooltip(_('Display time in full letters')) self._write_time_btn.connect("toggled", self._write_time_clicked_cb) display_toolbar.insert(self._write_time_btn, -1) # The button to display the weekday and date self._write_date_btn = ToggleToolButton("write-date") self._write_date_btn.set_tooltip(_('Display weekday and date')) self._write_date_btn.connect("toggled", self._write_date_clicked_cb) display_toolbar.insert(self._write_date_btn, -1) # Another button to speak aloud the time self._speak_time_btn = ToggleToolButton("microphone") self._speak_time_btn.set_tooltip(_('Talking clock')) self._speak_time_btn.connect("toggled", self._speak_time_clicked_cb) display_toolbar.insert(self._speak_time_btn, -1) # A separator between the two groups of buttons separator = Gtk.SeparatorToolItem() separator.set_draw(True) display_toolbar.insert(separator, -1) # And another button to toggle grabbing the hands self._grab_button = ToggleToolButton("grab") self._grab_button.set_tooltip(_('Grab the hands')) self._grab_button.connect("toggled", self._grab_clicked_cb) display_toolbar.insert(self._grab_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() vbox.pack_start(self._clock, True, True, 0) vbox.pack_start(self._time_letters, False, False, 0) vbox.pack_start(self._date, False, False, 0) # Attach the display to the activity self.set_canvas(vbox) self._clock.active = True def _write_date_clicked_cb(self, button): """The user clicked on the "write date" button to display the current weekday and date. """ self._write_date = button.get_active() 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) self._clock.queue_draw() is_digital = display_mode == _MODE_DIGITAL_CLOCK # Exit grab hands mode if the clock is digital if self._clock.grab_hands_mode and is_digital: self._grab_button.set_active(False) # The hands can't be grabbed in the digital clock mode self._grab_button.props.sensitive = not is_digital 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() GObject.idle_add(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() GObject.idle_add(self._write_and_speak, self._speak_time) def _grab_clicked_cb(self, button): """The user clicked on the "grab hands" button to toggle grabbing the hands. """ self._clock.change_grab_hands_mode(button.get_active()) 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 GObject.idle_add(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): # 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() 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. """ 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 is 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 self._simple_background_cache = None self._nice_background_cache = None # 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 # Cache for the simple clock face background self._simple_background_cache = None # SVG Background handle self._svg_handle = None # This are calculated on widget resize self._center_x = 0 self._center_y = 0 self._radius = -1 self._line_width = 2 self._hand_sizes = {} self._hand_angles = {} # 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("draw", self._draw_cb) self.connect("size-allocate", self._size_allocate_cb) # The masks to capture the events we are interested in self.add_events(Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.VISIBILITY_NOTIFY_MASK | Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.BUTTON1_MOTION_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, []) # This flag is True if the clock is in grab hands mode self.grab_hands_mode = False # When grabbing a hand, this is the name of the hand. If # None, it means that no hand is being grabbed self._hand_being_grabbed = None # Event handlers for grabbing the hands. self._press_id = None self._motion_id = None self._release_id = None # This can be 'AM' or 'PM' to distinguish the user set time # while grabbing the hands of the clock self._am_pm = 'AM' self.am_pm_width = 0 self.am_pm_height = 0 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). """ # This callback can be called when the gdk window is not yet # set # 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._line_width = int(self._radius / 150) # lean ackground caches self._simple_background_cache = None self._nice_background_cache = None # The hands sizes are proportional to the radius self._hand_sizes['hour'] = self._radius * 0.5 self._hand_sizes['minutes'] = self._radius * 0.8 self._hand_sizes['seconds'] = self._radius * 0.7 self.initialized = True def _draw_cb(self, widget, cr): """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: self.queue_resize() if self._mode == _MODE_NICE_CLOCK: if self._nice_background_cache is None: # Reload the svg handle self._svg_handle = Rsvg.Handle.new_from_file("clock.svg") # Draw nice clock background 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._draw_nice_clock(cr) elif self._mode == _MODE_SIMPLE_CLOCK: if self._simple_background_cache is None: # Draw simple clock background logging.debug('init background cache radius %s', self._radius) self._simple_background_cache = cr.get_target().create_similar( cairo.CONTENT_COLOR_ALPHA, self._radius * 2, self._radius * 2) cache_ctx = cairo.Context(self._simple_background_cache) self._draw_simple_background(cache_ctx) self._draw_numbers(cache_ctx) self._draw_simple_clock(cr) elif self._mode == _MODE_DIGITAL_CLOCK: self._draw_digital_clock(cr) else: msg = "Unknown display mode: %d." % self._mode raise ValueError(msg) return False def _draw_digital_clock(self, cr): """Draw the digital clock. """ self._draw_time_scale(cr) self._draw_time(cr) def _draw_time_scale(self, cr): """Draw a time scale for digital clock. """ # Draw scales of hours, minutes and seconds, to give the children # an appreciation of the time flowing... cr.save() 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.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() cr.restore() def _draw_time(self, cr): """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 cr.save() markup = _('<markup>\ <span lang="en" font_desc="Sans,Monospace Bold 96">\ <span foreground="#005FE4">%I</span>:\ <span foreground="#00B20D">%M</span>:\ <span foreground="#E6000A">%S</span>%p</span></markup>') markup_time = self._time.strftime(markup) cr.set_source_rgba(*style.Color(self._COLOR_BLACK).get_rgba()) pango_layout = PangoCairo.create_layout(cr) 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.Alignment.CENTER) cr.translate(self._center_x - dx / 2.0, d - dy / 2.0) PangoCairo.update_layout(cr, pango_layout) PangoCairo.show_layout(cr, pango_layout) cr.restore() def _draw_simple_clock(self, cr): """Draw the simple clock variants. """ # Can be called before the cache is ready if self._simple_background_cache is None: return cr.save() # Place the simple background cr.translate(self._center_x - self._radius, self._center_y - self._radius) cr.set_source_surface(self._simple_background_cache) cr.paint() cr.restore() self._draw_hands(cr) def _draw_simple_background(self, cr): """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.save() 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._radius, self._radius, self._radius - self._line_width * 2, 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._radius + (self._radius - inset) * cos), int(self._radius + (self._radius - inset) * sin)) cr.line_to(int(self._radius + (self._radius - 6) * cos), int(self._radius + (self._radius - 6) * sin)) cr.stroke() cr.restore() def _draw_nice_background(self, cr): """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. """ cr.save() cr.translate(self._center_x - self._radius, self._center_y - self._radius) cr.set_source_surface(self._nice_background_cache) cr.paint() cr.restore() def _draw_nice_clock(self, cr): """Draw the nice clock. """ cr.save() self._draw_nice_background(cr) self._draw_hands(cr) cr.restore() def _draw_hands(self, cr): """Draw the hands of the analog clocks. """ cr.save() cr.translate(0, 0) cr.set_line_cap(cairo.LINE_CAP_ROUND) # AM/PM indicator: cr.set_source_rgba( *style.Color(self._COLOR_HOURS).get_rgba()) pango_layout = PangoCairo.create_layout(cr) if self._am_pm == 'AM': am_pm = _('<markup><span lang="en" font_desc="Sans Bold 28">\ <span foreground="white" background="black"> AM </span><span \ foreground="lightgray"> PM </span></span></markup>') else: am_pm = _('<markup><span lang="en" font_desc="Sans Bold 28">\ <span foreground="lightgray"> AM </span><span foreground="white" \ background="black"> PM </span></span></markup>') cr.save() pango_layout.set_markup(am_pm) self.am_pm_width, self.am_pm_height = pango_layout.get_pixel_size() cr.translate(- self.am_pm_width / 2.0 + self._center_x, - self.am_pm_height / 2.0 + (self._radius / 3) + self._center_y) PangoCairo.update_layout(cr, pango_layout) PangoCairo.show_layout(cr, pango_layout) cr.restore() # 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) sin = math.sin(self._hand_angles['hour']) cos = math.cos(self._hand_angles['hour']) cr.line_to( int(self._center_x + self._hand_sizes['hour'] * sin), int(self._center_y - self._hand_sizes['hour'] * cos)) 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) sin = math.sin(self._hand_angles['minutes']) cos = math.cos(self._hand_angles['minutes']) cr.line_to(int(self._center_x + self._hand_sizes['minutes'] * sin), int(self._center_y - self._hand_sizes['minutes'] * cos)) 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) sin = math.sin(self._hand_angles['seconds']) cos = math.cos(self._hand_angles['seconds']) cr.line_to(int(self._center_x + self._hand_sizes['seconds'] * sin), int(self._center_y - self._hand_sizes['seconds'] * cos)) cr.stroke() cr.restore() def _draw_numbers(self, cr): """Draw the numbers of the hours. """ cr.save() cr.set_source_rgba(*style.Color(self._COLOR_HOURS).get_rgba()) pango_layout = PangoCairo.create_layout(cr) for i in xrange(12): # TRANS: The format of the font used to print hour # numbers, from 1 to 12. hour_number = _('<markup><span lang="en" \ font_desc="Sans Bold 40">%d</span></markup>') % (i + 1) cr.save() pango_layout.set_markup(hour_number) dx, dy = pango_layout.get_pixel_size() cr.translate(- dx / 2.0 + self._radius + 0.75 * self._radius * math.cos((i - 2) * math.pi / 6.0), - dy / 2.0 + self._radius + 0.75 * self._radius * math.sin((i - 2) * math.pi / 6.0)) PangoCairo.update_layout(cr, pango_layout) PangoCairo.show_layout(cr, pango_layout) cr.restore() cr.restore() 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() self._hand_angles['hour'] = (math.pi / 6 * (self._time.hour % 12) + math.pi / 360 * self._time.minute) self._hand_angles['minutes'] = math.pi / 30 * self._time.minute self._hand_angles['seconds'] = math.pi / 30 * self._time.second if self._time.hour < 12: self._am_pm = 'AM' else: self._am_pm = 'PM' GObject.idle_add(self.queue_draw) # 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) or the mode changes to dragging the hands of # the clock return self._active and not self.grab_hands_mode def _get_time_from_hands_angles(self): """Uses the angles of the hands to generate hours and minute time. Due to the small movement of the hour hand the minute hand position must be used to correctly round/floor to the correct hour. """ if self._hand_angles['minutes'] > math.pi / 30.0: hour = int( (self._hand_angles['hour'] * 12) / (math.pi * 2)) % 12 else: hour = int( round((self._hand_angles['hour'] * 12) / (math.pi * 2))) % 12 if self._am_pm == 'PM': hour += 12 minute = int( round((self._hand_angles['minutes'] * 60) / (math.pi * 2))) # Second is not used by speech or to display time in full # letters, so we avoid that calculation second = 0 return datetime(self._time.year, self._time.month, self._time.day, hour=hour, minute=minute, second=second) def get_time(self): """Public access to the time member of the clock face. In grab hands mode, return the time according to the position of the clock hands. """ if self.grab_hands_mode: return self._get_time_from_hands_angles() else: 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) def toggle_am_pm(self): if self._am_pm == 'AM': self._am_pm = 'PM' else: self._am_pm = 'AM' def change_grab_hands_mode(self, toggle_grab): """Connect or disconnect the callbacks for to grab the hands of the clock. """ self.grab_hands_mode = toggle_grab if toggle_grab: self._press_id = self.connect("button-press-event", self._press_cb) self._motion_id = self.connect("motion-notify-event", self._motion_cb) self._release_id = self.connect("button-release-event", self._release_cb) # Put hand cursor self.get_window().set_cursor( Gdk.Cursor.new(Gdk.CursorType.HAND2)) else: self.disconnect(self._press_id) self.disconnect(self._motion_id) self.disconnect(self._release_id) # Put original cursor again self.get_window().set_cursor( Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR)) # Update again the clock every seconds. GObject.timeout_add(1000, self._update_cb) self.emit("time_minute") def _press_cb(self, widget, event): _pointer, mouse_x, mouse_y, state = event.window.get_pointer() # Only pay attention to the button 1 if not (state & Gdk.ModifierType.BUTTON1_MASK): return # Calculate the angle from the center of the clock to the # mouse pointer adjacent = mouse_x - self._center_x opposite = -1 * (mouse_y - self._center_y) pointer_angle = math.atan2(adjacent, opposite) # Calculate the distance from the center of the clock to the # mouse pointer pointer_distance = math.hypot(adjacent, opposite) # If the angle is negative, convert it to the equal angle # between 0 and 2 PI if pointer_angle < 0: pointer_angle += math.pi * 2 def in_range(hand_angle, angle): """Return True if the given angle is in a range of the hand_angle +- the angle tolerance. """ # This is the normalized angle, the equal angle that is # minor than 2 PI hand_normal = (hand_angle - (math.pi * 2) * int(hand_angle / (math.pi * 2))) return (hand_normal >= angle - _ANGLE_TOLERANCE and hand_normal < angle + _ANGLE_TOLERANCE) # Check if we can start grabbing a hand of the clock: for hand in ['hour', 'minutes', 'seconds']: if in_range(self._hand_angles[hand], pointer_angle): if pointer_distance <= self._hand_sizes[hand]: self._hand_being_grabbed = hand break # Toggle AM or PM if clock face AM/PM area pressed if self._hand_being_grabbed is None and \ mouse_x > self._center_x - self.am_pm_width / 2 and \ mouse_x < self._center_x + self.am_pm_width / 2 and \ mouse_y > self._center_y + self._radius / 3 - \ self.am_pm_height and \ mouse_y < self._center_y + self._radius / 3 + \ self.am_pm_height: self.toggle_am_pm() self.emit("time_minute") self.queue_draw() def _motion_cb(self, widget, event): if self._hand_being_grabbed is None: return if event.is_hint: _pointer, mouse_x, mouse_y, state = event.window.get_pointer() else: mouse_x = event.x mouse_y = event.y state = event.state # Only pay attention to the button 1 if not state & Gdk.ModifierType.BUTTON1_MASK: return # Calculate the angle from the center of the clock to the # mouse pointer adjacent = mouse_x - self._center_x opposite = -1 * (mouse_y - self._center_y) pointer_angle = math.atan2(adjacent, opposite) # If the angle is negative, convert it to the equal angle # between 0 and 2 PI if pointer_angle < 0: pointer_angle += math.pi * 2 # Auto spin hour hand and snap minute hand when minutes dragged if self._hand_being_grabbed is 'minutes': pointer_angle = int((pointer_angle * 60) / ( math.pi * 2)) * (math.pi * 2) / 60.0 self._hand_angles['hour'] += ( pointer_angle - self._hand_angles['minutes']) / 12.0 if pointer_angle - self._hand_angles['minutes'] > math.pi: self._hand_angles['hour'] -= math.pi * 2 / 12.0 elif pointer_angle - self._hand_angles['minutes'] < -math.pi: self._hand_angles['hour'] += math.pi * 2 / 12.0 # Toggle AM/PM as needed if self._hand_angles['hour'] >= math.pi * 2: self._hand_angles['hour'] -= math.pi * 2 self.toggle_am_pm() elif self._hand_angles['hour'] < 0: self._hand_angles['hour'] += math.pi * 2 self.toggle_am_pm() # Auto spin and snap minute hand when hour hand dragged if self._hand_being_grabbed is 'hour': tmp = self._hand_angles['hour'] * 12.0 while tmp >= math.pi * 2: tmp -= math.pi * 2 self._hand_angles['minutes'] = int( (tmp * 60) / (math.pi * 2)) * (math.pi * 2) / 60.0 # Toggle AM/PM as needed if abs(self._hand_angles['hour'] - pointer_angle) > math.pi: self.toggle_am_pm() # Update the angle of the hand being grabbed self._hand_angles[self._hand_being_grabbed] = pointer_angle # Force redraw of the clock: self.queue_draw() def _release_cb(self, widget, event): if self._hand_being_grabbed is None: return if self._hand_being_grabbed in ['hour', 'minutes']: self.emit("time_minute") self._hand_being_grabbed = None self.queue_draw()
_______________________________________________ Sugar-devel mailing list Sugar-devel@lists.sugarlabs.org http://lists.sugarlabs.org/listinfo/sugar-devel