[IAEP] [PATCH] add clock frame device

Martin Dengler martin at martindengler.com
Fri May 8 17:40:35 EDT 2009


The below patch adds a clock device to the frame:

http://www.martindengler.com/tmp/screenshot_clock_device_frame-06_a6_a689bf1e-3f34-4da3-afed-aa910ab4f677.png
http://www.martindengler.com/tmp/screenshot_clock_device_frame-07_84_841da15d-9bd7-4fe4-aada-8e8ab723f806.png
http://www.martindengler.com/tmp/screenshot_clock_device_frame-08_4b_4ba1059e-4db9-4f22-8661-7916f6a167f8.png

The patch incorporates almost all of the feedback already received.

Feedback welcome.

Martin

---
 extensions/deviceicon/clock.py |  325 ++++++++++++++++++++++++++++++++++++++++
 1 files changed, 325 insertions(+), 0 deletions(-)
 create mode 100644 extensions/deviceicon/clock.py

diff --git a/extensions/deviceicon/clock.py b/extensions/deviceicon/clock.py
new file mode 100644
index 0000000..7d3647d
--- /dev/null
+++ b/extensions/deviceicon/clock.py
@@ -0,0 +1,325 @@
+# 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"
+
+
+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", _("%l:%M")),
+        "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 = "Bitstream Vera Sans 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 "%H" in time_format or "%k" in time_format:
+            button_to_activate = self.button_24h
+        elif "%I" in time_format or "%l" in time_format:
+            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() \
+                and ("%I" in time_format or "%l" in time_format):
+            time_format = time_format.replace("%I", "%H").replace("%l", "%k")
+        elif self.button_12h.get_active() \
+                and ("%H" in time_format or "%k" in time_format):
+            time_format = time_format.replace("%H", "%I").replace("%k", "%l")
+
+        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())
-- 
1.6.0.6

-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: not available
Url : http://lists.sugarlabs.org/archive/iaep/attachments/20090508/8bb22e9e/attachment-0001.pgp 


More information about the IAEP mailing list