[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