#!/usr/bin/env python """ A desklet to show Google Calendar events Copyright 2008 Antonio Cuni """ import sys import string import time import os import os.path import socket from datetime import datetime, date import gdata.calendar.service import adesklets def center_text(x, y, w, h, text): text_w, text_h = adesklets.get_text_size(text) x_offs = (w-text_w) / 2 y_offs = (h-text_h) / 2 adesklets.text_draw(x+x_offs, y+y_offs, text) def fullpath(name): dirname = os.path.dirname(__file__) return os.path.join(dirname, name) def parse_datetime(text): import iso8601 t = iso8601.parse(text) return datetime.fromtimestamp(t) class Config(adesklets.ConfigFile): cfg_default = { 'email': 'XXX', 'password': 'XXX', 'bg_color' : (210, 210, 210, 130), 'day_font': 'comic', 'day_font_size': 12, 'day_color' : (0, 0, 0, 200), 'event_font': 'Vera', 'event_font_size': 10, 'event_padding': 5, 'event_color': (0, 0, 0, 200), 'month_font': 'VeraBd', 'month_font_size': 8, 'today_image': 'images/calendar/blue.png', 'today_month_color': (255, 255, 255, 200), 'otherday_image': 'images/calendar/lightred.png', 'otherday_month_color': (0, 0, 0, 150), 'max_height': 460, # 250, 'width': 246, 'top_margin': 16, 'bottom_margin': 16, 'left_margin': 16, 'right_margin': 16, 'vertical_padding': 16, 'icon_padding': 8, 'offline_image': 'images/calendar/offline.png', 'offline_timeout': 60, 'bg_images' : ["images/shared/bg_top.png", "images/shared/bg_middle.png", "images/shared/bg_bottom.png"], } def __getattr__(self, attr): return self[attr] def _load_and_save(self): adesklets.ConfigFile._load_and_save(self) for key, value in self.iteritems(): if isinstance(value, str) and value.startswith('$'): varname = value[1:] value = os.getenv(varname, value) # try to convert to int try: value = int(value) except ValueError: pass dict.__setitem__(self, key, value) class Widget(object): def getWidth(self): raise NotImplementedError def getHeight(self): raise NotImplementedError def draw(self, x, y): raise NotImplementedError class IconWidget(Widget): def __init__(self, config, iconname, width, lines): self.config = config self.lines = lines self.text_height = self._calc_height() self.width = width if iconname: self.icon = adesklets.load_image(iconname) adesklets.context_set_image(self.icon) self.icon_width = adesklets.image_get_width() self.icon_height = adesklets.image_get_height() else: self.icon = None self.icon_width = 0 self.icon_height = 0 def _calc_height(self): adesklets.context_set_font(self.config.event_font) # XXX text_w, text_h = adesklets.get_text_size('HELLO WORLD') n = len(self.lines) return text_h*n + self.config.event_padding*(n-1) def getWidth(self): return self.width #214 def getHeight(self): return max(self.icon_height, self.text_height) def stripText(self, s, max_width): for i in range(len(s), 0, -1): s1 = s[:i] text_w, text_h = adesklets.get_text_size(s1) if text_w <= max_width: return s1 return '' def draw(self, x, y): self.drawIcon(x, y) self.drawText(x + self.icon_width + self.config.icon_padding, y) def drawIcon(self, x, y): adesklets.blend_image_onto_image(self.icon, 1, 0, 0, self.icon_width, self.icon_height, x, y, self.icon_width, self.icon_height) def drawText(self, x, y): adesklets.context_set_color(*self.config.event_color) adesklets.context_set_font(self.config.event_font) for line in self.lines: y += self.drawLine(x, y, line) + self.config.event_padding def drawLine(self, x, y, line): line = self.stripText(line, self.getWidth()-x) adesklets.text_draw(x, y, line) text_w, text_h = adesklets.get_text_size(line) return text_h class CalendarWidget(IconWidget): def __init__(self, config, width, date, events): self.date = date today = date.today() if date == today: img = config.today_image self.month_color = config.today_month_color else: img = config.otherday_image self.month_color = config.otherday_month_color IconWidget.__init__(self, config, fullpath(img), width, events) def drawIcon(self, x, y): month = self.date.strftime('%b') day = str(self.date.day) IconWidget.drawIcon(self, x, y) adesklets.context_set_color(*self.month_color) adesklets.context_set_font(self.config.month_font) center_text(x, y, 48, 18, month) adesklets.context_set_color(0, 0, 0, 255) adesklets.context_set_font(self.config.day_font) center_text(x, y+18, 48, 30, day) def drawLine(self, x, y, event): text = event.title.text if isinstance(text, unicode): text = text.encode('utf-8') allday = 'T' not in event.when[0].start_time # XXX: very hackish, suggestions welcome :-) if allday: return self._draw_allday_event(x, y, text) # XXX: sort time = event.start_time line = '%s %s' % (time.strftime('%H:%M'), text) return IconWidget.drawLine(self, x, y, line) def _draw_allday_event(self, x, y, text): text = self.stripText(text, self.getWidth()-x) text_w, text_h = adesklets.get_text_size(text) padding = 3 #adesklets.context_set_color(0x33, 0x66, 0xCC, 200) adesklets.context_set_color(0xFF, 0xFF, 0xFF, 100) adesklets.image_fill_rectangle(x, y, text_w+padding*2, text_h+padding*2) adesklets.context_set_color(*self.config.event_color) adesklets.text_draw(x+padding, y+padding, text) return text_h + padding*2 class Events(adesklets.Events_handler): def __init__(self): self.config = None #self.height = 300 #self.width = 246 self.widgets = [] self.calendar = {} adesklets.Events_handler.__init__(self) def _setup(self): if self.config is None: adesklets.set_charset('utf-8') config = Config(adesklets.get_id(), fullpath('config.txt')) self.config = config self.bg_top = adesklets.load_image(fullpath(self.config.bg_images[0])) self.bg_middle = adesklets.load_image(fullpath(self.config.bg_images[1])) self.bg_bottom = adesklets.load_image(fullpath(self.config.bg_images[2])) config.month_font = self._load_font('month') config.day_font = self._load_font('day') config.event_font = self._load_font('event') self.calc_dimensions() def alarm(self): self._setup() try: self.calendar = querycalendar(self.config.email, self.config.password) except socket.error, e: self.display_offline(str(e)) return self.config.offline_timeout else: self.display_calendar() return 0 def calc_widget_width(self): config = self.config return config.width - config.left_margin - config.right_margin count = 0 def display_offline(self, msg = None): img = fullpath(self.config.offline_image) width = self.calc_widget_width() lines = ['No network %d' % self.count] if msg: lines.append(msg) widget = IconWidget(self.config, img, width, lines) self.count += 1 self.widgets = [widget] self.draw() def display_calendar(self): self.widgets = [] config = self.config height = config.top_margin + config.bottom_margin widget_width = self.calc_widget_width() for day in sorted(self.calendar.keys()): events = self.calendar[day] w = CalendarWidget(config, widget_width, day, events) height += w.getHeight() + config.vertical_padding if height > config.max_height: break self.widgets.append(w) self.draw() def _load_font(self, prefix): name = self.config[prefix + '_font'] size = self.config[prefix + '_font_size'] fullname = '%s/%s' % (name, size) return adesklets.load_font(fullname) def calc_dimensions(self): config = self.config max_width = 0 total_height = 0 for w in self.widgets: total_height += w.getHeight() max_width = max(max_width, w.getWidth()) self.height = config.top_margin + config.bottom_margin + total_height + config.vertical_padding*(len(self.widgets)) self.width = config.left_margin + max_width + config.right_margin def draw(self): self.calc_dimensions() config = self.config adesklets.window_resize(self.width, self.height) #adesklets.window_reset(adesklets.WINDOW_MANAGED) adesklets.window_set_transparency(True) adesklets.window_show() self.display_bg() x = config.left_margin y = config.top_margin for w in self.widgets: # draw the background for the widget adesklets.context_set_blend(False) adesklets.context_set_color(*config.bg_color) adesklets.image_fill_rectangle(x, y, w.getWidth(), w.getHeight()) adesklets.context_set_blend(True) # draw the widget w.draw(x, y) y = y + config.vertical_padding + w.getHeight() def display_bg(self): adesklets.context_set_image(0) adesklets.context_set_color(0,0,0,0) adesklets.context_set_blend(False) adesklets.image_fill_rectangle(0,0,self.width, self.height) adesklets.context_set_image(self.bg_top) top_width = adesklets.image_get_width() top_height = adesklets.image_get_height() adesklets.context_set_image(0) adesklets.blend_image_onto_image(self.bg_top, 1, 0,0,top_width,top_height, 0,0,self.width,top_height) adesklets.context_set_image(self.bg_bottom) bot_width = adesklets.image_get_width() bot_height = adesklets.image_get_height() adesklets.context_set_image(0) adesklets.blend_image_onto_image(self.bg_bottom,1, 0,0,bot_width,bot_height, 0,self.height-bot_height,self.width,bot_height) adesklets.context_set_image(self.bg_middle) mid_width = adesklets.image_get_width() mid_height = adesklets.image_get_height() adesklets.context_set_image(0) adesklets.blend_image_onto_image(self.bg_middle,1, 0,0,mid_width,mid_height, 0,top_height,self.width,self.height-(bot_height + top_height)) def querycalendar(email, password): gcal = gdata.calendar.service.CalendarService() gcal.email = email gcal.password = password gcal.source = 'gcalendarlet' gcal.ProgrammaticLogin() query = gdata.calendar.service.CalendarEventQuery() query.futureevents = 'true' query.orderby = 'starttime' query.sortorder = 'ascending' feed = gcal.CalendarQuery(query) calendar = {} # date --> [events] for event in feed.entry: when = event.when[0] # XXX event.start_time = parse_datetime(when.start_time) event.start_date = event.start_time.date() text = event.title.text calendar.setdefault(event.start_date, []).append(event) return calendar Events().pause()