diff options
author | Martin Dengler <martin@martindengler.com> | 2009-06-10 02:44:56 (GMT) |
---|---|---|
committer | Martin Dengler <martin@martindengler.com> | 2009-06-26 19:38:39 (GMT) |
commit | 7fb887647086a5aaa413bab6c00fb2df43f2e073 (patch) | |
tree | dcf698787a48eea1f700269c6287b723d36e62b6 | |
parent | 014a004eb2a708bd368f494e98a6c3705495cf76 (diff) |
add simple clock device to framemtd-clock
-rw-r--r-- | extensions/deviceicon/clock.py | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/extensions/deviceicon/clock.py b/extensions/deviceicon/clock.py new file mode 100644 index 0000000..12947bf --- /dev/null +++ b/extensions/deviceicon/clock.py @@ -0,0 +1,326 @@ +# Copyright (C) 2008 Martin Dengler +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +from gettext import gettext as _ + +import logging +import math +import time + +import gconf +import gobject +import gtk +import gtk.gdk +import pango +import pangocairo + +import jarabe.frame + +from jarabe.frame.frameinvoker import FrameWidgetInvoker +from sugar.graphics import style +from sugar.graphics.palette import Palette +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.xocolor import XoColor + + + +DEFAULT_TEXT_FONT = "Bitstream Vera Sans" +DEFAULT_TIME_FORMAT_12H = _("%l:%M %p") +DEFAULT_TIME_FORMAT_24H = _("%H:%M") +DEFAULT_TIME_FORMAT = DEFAULT_TIME_FORMAT_12H + + +class TextIcon(gtk.Image): + def __init__(self, *args, **kwargs): + gtk.Image.__init__(self, *args, **kwargs) + client = gconf.client_get_default() + mycolor = XoColor(client.get_string('/desktop/sugar/user/color')) + self._fill_rgba = style.Color(mycolor.fill).get_rgba() + self._stroke_rgba = style.Color(mycolor.stroke).get_rgba() + + self.add_events(gtk.gdk.EXPOSURE_MASK) + self.connect("expose-event", self.my_expose_event) + + self.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK) + self.connect('event', self.__event_cb) + + def __event_cb(self, *args, **kwargs): + logging.debug("DigitalClock TextIcon event args: %s; kwargs %s" % (args, kwargs)) + + def my_expose_event(self, widget_, event): + cr = self.window.cairo_create() + x, y, w_, h_ = self.allocation + cr.translate(x, y) + self.texticon_draw(cr) + + def draw_rounded_rectangle(self, cr, x, y, width, height, radius=9.0): + """Draw a rounded rectangle path based on Guillaume Seguin's code""" + offset = radius / 3 + x0 = x + offset + y0 = y + offset + x1 = x + width - offset + y1 = y + height - offset + cr.new_path() + + pi = math.pi + + cr.arc (x0 + radius, y1 - radius, radius, pi / 2, pi) + cr.line_to (x0, y0 + radius) + cr.arc (x0 + radius, y0 + radius, radius, pi, 3 * pi / 2) + cr.line_to (x1 - radius, y0) + cr.arc (x1 - radius, y0 + radius, radius, 3 * pi / 2, 2 * pi) + cr.line_to (x1, y1 - radius) + cr.arc (x1 - radius, y1 - radius, radius, 0, pi / 2) + + cr.close_path() + + def write(self, cr, text, x=0, y=0, font=None, reverse_video=False): + pcr = pangocairo.CairoContext(cr) + layout = pcr.create_layout() + if font is None: + font = DEFAULT_TEXT_FONT + layout.set_font_description(pango.FontDescription(font)) + layout.set_markup(text) + + padding = 10 + w, h = layout.get_pixel_size() + w += 2 * padding + + fg = self._stroke_rgba + bg = self._fill_rgba + + if reverse_video: + fg, bg = bg, fg + + cr.save() + cr.set_source_rgba(*fg) + self.draw_rounded_rectangle(cr, x, y, w, h) + cr.fill() + cr.set_line_width(padding * 0.4) + cr.set_source_rgba(*bg) + self.draw_rounded_rectangle(cr, x, y, w, h) + cr.stroke() + cr.restore() + + cr.save() + + cr.move_to(x + padding, y) + + pcr.layout_path(layout) + cr.set_source_rgba(*fg) + cr.set_line_width(padding * 0.5) + cr.stroke_preserve() + cr.set_source_rgba(*bg) + cr.fill() + cr.restore() + + self.set_size_request(w, h) + + def texticon_draw(self, cr): + """ + draw the widget on the provided cairo surface + + Should be overridden by subclasses; example: + + def texticon_draw(self, cr): + self.write(cr, time.strftime(_("%m/%d %H:%M"), time.localtime())) + """ + raise Exception("TextIcon.texticon_draw(): subclasses must" + " override this method") + + +class DigitalClock(TextIcon): + + gconf_dir = "/desktop/sugar/desktop/clock" + gconf_keys = { + "time_format": ("strftime_format_string", DEFAULT_TIME_FORMAT_12H), + "font_size": ("font_size", 32), + "reverse_video": ("reverse_video", True), + } + + def __init__(self, *args, **kwargs): + TextIcon.__init__(self, *args, **kwargs) + + self.__update_sigid = None + self._setup() + + client = gconf.client_get_default() + client.add_dir(DigitalClock.gconf_dir, gconf.CLIENT_PRELOAD_ONELEVEL) + for keyname, default_ in DigitalClock.gconf_keys.values(): + client.notify_add(DigitalClock.gconf_dir + "/" + keyname, + self.__gconf_changed_cb) + + def _setup(self): + client = gconf.client_get_default() + for attr, (keyname, default) in DigitalClock.gconf_keys.iteritems(): + keypath = DigitalClock.gconf_dir + "/" + keyname + if client.get(keypath) is None: + client.set_value(keypath, default) + setattr(self, attr, client.get_value(keypath)) + + if "%S" in self.time_format: + self.update_interval = 1000 + else: + self.update_interval = 60000 + + def __gconf_changed_cb(self, *args, **kwargs): + self._setup() + + def texticon_draw(self, cr): + time_string = "..." + font_string = DEFAULT_TEXT_FONT + " 32" + reverse_video = True + try: + time_string = time.strftime(self.time_format, time.localtime()) + font_string = "%s %s" % (DEFAULT_TEXT_FONT, self.font_size) + reverse_video = self.reverse_video + except Exception, msg: + logging.debug("DigitalClock: failed trying to use" + " time/font string/reverse video settings:" + " %s/%s/%s: %s" + % (time_string, font_string, reverse_video, msg)) + time_string = time_string.strip() + self.write(cr, + time_string, + font=font_string, + reverse_video=reverse_video) + + def my_expose_event(self, widget, event): + TextIcon.my_expose_event(self, widget, event) + if jarabe.frame.get_view().visible and self.__update_sigid is None: + delay = self.update_interval + self.__update_sigid = gobject.timeout_add(delay, + self.__update_clock) + + def __update_clock(self): + if jarabe.frame.get_view().visible: + self.window.invalidate_rect(self.allocation, True) + else: + self.__update_sigid = None + return jarabe.frame.get_view().visible + + def set_time_format(self, time_str): + self.time_format = time_str + client = gconf.client_get_default() + client.set_value(DigitalClock.gconf_dir + "/" + + DigitalClock.gconf_keys["time_format"][0], + time_str) + if jarabe.frame.get_view().visible: + self.window.invalidate_rect(self.allocation, True) + + +class DigitalClockTrayItem(ToolButton): + + FRAME_POSITION_RELATIVE = 50 # all the way on the right + + def __init__(self): + ToolButton.__init__(self) + + self.clock = DigitalClock() + + self.hbox = gtk.HBox() + self.hbox.add(self.clock) + self.set_icon_widget(self.hbox) + self.hbox.show_all() + + self.set_palette_invoker(FrameWidgetInvoker(self)) + self.palette = DigitalClockPalette(_(""), self.clock) + self.palette.set_group_id("frame") + self.clock.connect("expose-event", self.palette.update) + + +class DigitalClockPalette(Palette): + + def __init__(self, primary_text, model): + Palette.__init__(self, primary_text) + + self.model = model + vbox = gtk.VBox() + + self.button_hidden = gtk.RadioButton(group=None, label="hidden") + + self.button_12h = gtk.RadioButton(group=self.button_hidden, + label=_("12 Hour")) + self.button_12h.show() + + self.button_24h = gtk.RadioButton(group=self.button_hidden, + label=_("24 Hour")) + self.button_24h.show() + + self.buttons = (self.button_hidden, self.button_12h, self.button_24h) + self.button_signals = {} + + for button in self.buttons: + vbox.pack_start(button) + + vbox.show() + self.set_content(vbox) + + #FIXME: track model state? + self.initialize_toggle_states() + + for button in self.buttons: + self.button_signals[button] = button.connect( + "toggled", self.__hour_toggled_cb) + + def update(self, *args_, **kwargs_): + local_time = time.localtime() + self.props.primary_text = time.strftime(_("%x"), local_time) + + def initialize_toggle_states(self): + time_format = self.model.time_format + button_to_activate = None + + if time_format == DEFAULT_TIME_FORMAT_24H: + button_to_activate = self.button_24h + elif time_format == DEFAULT_TIME_FORMAT_12H: + button_to_activate = self.button_12h + else: + button_to_activate = self.button_hidden + + if not button_to_activate.get_active(): + for button, signal_id in self.button_signals.iteritems(): + button.signal_handler_block(signal_id) + for button in self.buttons: + if button.get_active() != button == button_to_activate: + button.set_active(button == button_to_activate) + for button, signal_id in self.button_signals.iteritems(): + button.signal_handler_unblock(signal_id) + + def __hour_setting_updated(self): + time_format = self.model.time_format + if self.button_24h.get_active(): + time_format = DEFAULT_TIME_FORMAT_24H + elif self.button_12h.get_active(): + time_format = DEFAULT_TIME_FORMAT_12H + + logging.debug("clock: __h_s_u: time_format %s --> %s" % (self.model.time_format, time_format)) + logging.debug("clock: __h_s_u: active is %s/%s/%s" % ( + self.button_hidden.get_active(), + self.button_12h.get_active(), + self.button_24h.get_active())) + + if time_format != self.model.time_format: + self.model.set_time_format(time_format) + + def __hour_toggled_cb(self, widget): + if widget.get_active(): + self.__hour_setting_updated() + + + +def setup(tray): + tray.add_device(DigitalClockTrayItem()) |