Screenshot
Calendar 1.06
Simple PyS60 script which accesses the calendar and displays all entries of the next two weeks on a scrollable (touch) canvas. Pretty cool how easy one can create a Python application on a symbian mobile which can even be distributed as an SIS file.
Download
Download python script
Download SIS package
Known issues:
- Quit button not works (binding events on display-coordinates not works as expected)
- Time displayed in the header gets not updated (have to implemtent an timer here)
Python code
__package__ = "calendar"
__version__ = "1.06"
__author__ = "Kai Winter"
__url__ = "http://www.vorlesungsfrei.de"
__copyright__ = "Copyright (c) 2010 Kai Winter"
import appuifw
import key_codes
import e32
import e32calendar
import time
from datetime import datetime
from graphics import *
HEADER_HEIGHT = 30
FOOTER_HEIGHT = 30
class Painter():
""" Class handling the painting of the calender events on a dragable canvas. """
def __init__(self, calender_events):
# 1D-scrolling will do
self.down_coordinate_y = 0
self.move_pixels_y = HEADER_HEIGHT
self.canvas = appuifw.Canvas(redraw_callback=self.redraw_callback)
self.canvas_width = self.canvas.size[0]
# Bind handlers
self.canvas.bind(key_codes.EButton1Down, self.button_down)
self.canvas.bind(key_codes.EDrag, self.drag_canvas)
# Create content
self.create_timesheet(calender_events)
self.create_header()
self.create_footer()
appuifw.app.body = self.canvas
self.canvas.blit(self.imgInfo, target=(0, 0))
self.canvas.blit(self.imgFooter, target=(0, self.canvas.size[1] - FOOTER_HEIGHT))
def create_header(self):
""" Creates the header image to display the current time. """
# Create header with current time
self.imgInfo = Image.new((self.canvas_width, HEADER_HEIGHT))
# Display current time
current_time = unicode(render_datetime(datetime.now(), True, False))
text_width = self.imgInfo.measure_text(current_time)[1]
self.imgInfo.rectangle(((0, 0), (self.canvas_width, HEADER_HEIGHT)), fill=0xddddff)
self.imgInfo.text(((self.canvas_width / 2) - (text_width / 2), 20), current_time)
self.imgInfo.line(((0, HEADER_HEIGHT - 1), (self.canvas_width, HEADER_HEIGHT - 1)), outline=0x000000)
def create_footer(self):
""" Creates the footer image to display buttons. """
self.imgFooter = Image.new((self.canvas_width, FOOTER_HEIGHT))
self.imgFooter.rectangle(((0, 0), (self.canvas_width, FOOTER_HEIGHT)), fill=0xddddff)
self.imgFooter.line(((0, 0), (self.canvas_width, 0)), outline=0x000000)
self.imgFooter.text((10, 20), u"Quit")
#self.canvas.bind(key_codes.EButton1Down, quit, ((0, 520), (400, 800)))
def create_timesheet(self, calendar_events):
text_height = self.canvas.measure_text(u"test")[0]
text_height2 = self.canvas.measure_text(u"test", font='annotation')[0]
text_height = text_height[2] - text_height[0]
text_height2 = text_height2[2] - text_height2[0]
whole_text_height = text_height + text_height2 - 4
offset_top = 20
offset_left = 10
flip_color = False
canvas_height = len(calendar_events) * (whole_text_height + 1)
self.img = Image.new((self.canvas_width, canvas_height))
self.img.rectangle([(0, 0), self.img.size], fill=0x000000)
for entry in calendar_events:
color = 0xeeeeff
if flip_color:
color = 0xf7f7ff
flip_color = not flip_color
self.img.rectangle([(0, -20 + offset_top), (self.canvas_width, -20 + offset_top + (whole_text_height))], fill=color)
entry_time = entry[1]
entry_text = entry[0]
self.img.text((offset_left, offset_top + 5), unicode(entry_text), font='annotation')
offset_top += 20
self.img.text((offset_left, offset_top + 5), unicode(entry_time))
offset_top += 40
def drag_canvas(self, event):
img_size_y = self.img.size[1]
canvas_size_y = self.canvas.size[1]
previous_move_pixels = self.move_pixels_y
# save last, to skip rendering, if no not needed
result_scroll = -self.down_coordinate_y + event[1]
if result_scroll > 0 + HEADER_HEIGHT:
# scrolled to high
self.move_pixels_y = HEADER_HEIGHT
self.button_down(event)
elif result_scroll + img_size_y < canvas_size_y - FOOTER_HEIGHT + 1:
# scrolled too far down
self.move_pixels_y = -img_size_y + canvas_size_y - FOOTER_HEIGHT + 1
self.button_down(event)
elif img_size_y > canvas_size_y - HEADER_HEIGHT - FOOTER_HEIGHT:
#scroll
self.move_pixels_y = result_scroll
# only draw if canvas was scrolled
if (previous_move_pixels != self.move_pixels_y):
self.redraw_callback(event)
def button_down(self, event):
self.down_coordinate_y = event[1] - self.move_pixels_y
def redraw_callback(self, rect):
self.canvas.blit(self.img, source=((0, -self.move_pixels_y + HEADER_HEIGHT), (self.canvas_width, -self.move_pixels_y + self.canvas.size[1] - FOOTER_HEIGHT)), target=(0, HEADER_HEIGHT))
#self.canvas.blit(self.imgInfo, target=(0, 0))
#self.canvas.blit(self.imgFooter, target=(0, self.canvas.size[1] - FOOTER_HEIGHT))
#self.canvas.rectangle(((0, self.canvas.size[1] - FOOTER_HEIGHT), (self.canvas_width, self.canvas.size[1])), fill=0x000000)
def quit(self):
app_lock.signal()
def redraw(self):
self.canvas.blit(self.imgInfo, target=(0, 0))
self.canvas.blit(self.imgFooter, target=(0, self.canvas.size[1] - FOOTER_HEIGHT))
def render_datetime(timestamp, with_time=True, skipMidnight=True):
""" Creates a string representation of a timestamp """
return_string = timestamp.strftime("%a %d.%m.")
if with_time:
if skipMidnight and timestamp.hour == 0 and timestamp.minute == 0 and timestamp.second == 0:
pass
else:
return_string += timestamp.strftime(" / %H:%M")
return return_string
def get_calender_events():
""" Returns a tuple (title, subtitle) """
cal = e32calendar.open()
current_datetime = datetime.now()
current_time = time.time()
seconds_a_day = 24 * 60 * 60
entries = []
seen_ids = []
for i in range (0, 51):
day_to_fetch = current_time + (i * seconds_a_day)
day_entries = cal.daily_instances(day_to_fetch)
for entry in day_entries:
entry = cal[entry["id"]]
entry_id = entry.id
if entry_id in seen_ids:
continue
entry_time = datetime.fromtimestamp(entry.start_time)
entry_time_end = datetime.fromtimestamp(entry.end_time)
entry_time_end_check = datetime.fromtimestamp(entry.end_time - 1)
entry_text = entry.content
# Change timestamp to current year
entry_time = entry_time.replace(year=current_datetime.year)
if entry_time.day == entry_time_end_check.day:
entries.append((entry_text, render_datetime(entry_time)))
else:
entries.append((entry_text, render_datetime(entry_time) + " - " + render_datetime(entry_time_end)))
seen_ids.append(entry_id)
return entries
def quit():
app_lock.signal()
def is_visible(visible):
if visible:
p.redraw()
appuifw.app.exit_key_handler = quit
appuifw.app.directional_pad = False
calender_events = get_calender_events()
app_lock = appuifw.e32.Ao_lock()
p = Painter(calender_events)
appuifw.app.screen = 'large'
appuifw.app.orientation = 'portrait'
appuifw.app.focus = is_visible
app_lock.wait()