pyntor-0.6/0000755000175300017530000000000010416470165012662 5ustar studentstudentpyntor-0.6/AUTHORS0000644000175300017530000000005010416470146013724 0ustar studentstudentJosef Spillner pyntor-0.6/components/0000755000175300017530000000000010416470165015047 5ustar studentstudentpyntor-0.6/components/slides.py0000644000175300017530000002432210416470146016706 0ustar studentstudent# Pyntor::Slides - Display slides # -*- coding: utf-8 -*- # Copyright (C) 2004-2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * import sys import time import random import os, pwd import re class Slides: def init(self, options): slideshow = options["slides"] option = options["pages"] if option.find("-") >= 0: (start, stop) = option.split("-") self.readslide = int(start) self.lastslide = int(stop) else: self.readslide = int(option) self.lastslide = -1 self.blackheading = options["inverse"] f = open(slideshow) blines = f.readlines() f.close() self.lines = [] for line in blines: self.lines.append(unicode(line.rstrip(), "utf-8")) self.fulltext = {} self.headings = {} counter = 0 oldline = "" useoldline = None for l in self.lines: savetext = 0 if counter >= self.readslide: savetext += 1 if counter <= self.lastslide or self.lastslide == -1: savetext += 1 if savetext == 2: xcounter = counter - self.readslide + 1 #print options, "::", xcounter, self.readslide, self.lastslide if not self.fulltext.has_key(xcounter): self.fulltext[xcounter] = [] self.fulltext[xcounter].append(l) if useoldline: self.fulltext[xcounter].append(useoldline) self.headings[xcounter] = useoldline useoldline = 0 if l.find("======") == 0: counter = 1 useoldline = oldline if l.find("-----") == 0: counter += 1 useoldline = oldline oldline = l #print "COUNTER", counter, self.readslide, self.lastslide if self.lastslide != -1: if self.lastslide < counter: counter = self.lastslide if self.readslide != 1: counter -= self.readslide counter += 1 #print "COUNTER-new", counter self.pages = counter def render(self, screen, page, globalpage): #screen = pygame.display.get_surface() # screenorig = pygame.Surface((screen.get_width(), screen.get_height())) # screenorig.blit(screen, (0, 0)) # white = pygame.Surface((screen.get_width(), screen.get_height())) # white.fill((255, 255, 255)) # #alphapart = 160 # alphapart = 0 # screencopy = pygame.Surface((screen.get_width(), screen.get_height())) # screencopy.blit(screen, (0, 0)) # screencopy2 = pygame.Surface((screen.get_width(), screen.get_height())) # screencopy2.blit(screen, (0, 0)) surface = pygame.Surface((screen.get_width(), screen.get_height())) surface.blit(screen, (0, 0)) titlefont = pygame.font.SysFont("Vera Sans", 40) bigfont = pygame.font.SysFont("Vera Sans", 42) smallfont = pygame.font.SysFont("Vera Sans", 24) verysmallfont = pygame.font.SysFont("Vera Sans", 18) smallfontmath = pygame.font.SysFont("Cmsy10", 26) # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx # RENDER HERE! heading = "" subheading = "" pageheading = "" counter = 0 rawmode = 0 rawlist = [] eventlist = {} # FIXME: abandon readslide readslide = self.readslide + page - 1 #print "RENDER", page, "WHICH IS", readslide oldline = "" for l in self.lines: if l.find("======") == 0: heading = oldline counter = 1 if oldline.find("======") == 0: subheading = l if readslide > 1 or readslide == 1: if l.find("-----") == 0: counter += 1 if counter == readslide: pageheading = oldline rawmode = 1 else: rawmode = 0 oldline = l if rawmode == 1: rawlist.append(l) # if readslide != oldslide: # surface.blit(screencopy, (0, 0)) # screencopy2.blit(screencopy, (0, 0)) # else: # surface.blit(screencopy2, (0, 0)) if readslide == 1 and heading is not "": f = titlefont.render(heading, 1, (0, 120, 255)) surface.blit(f, ((surface.get_width() - f.get_width()) / 2, 200)) f = smallfont.render(subheading, 1, (255, 255, 255)) surface.blit(f, ((surface.get_width() - f.get_width()) / 2, 300)) else: if len(rawlist) == 0: return if readslide == self.lastslide + 1: return if self.blackheading: #f = bigfont.render(pageheading, 1, (255, 255, 255)) f = bigfont.render(pageheading, 1, (200, 200, 100)) else: f = bigfont.render(pageheading, 1, (0, 120, 255)) surface.blit(f, (60, 25)) i = 0 xmarker = 0 rawlist.pop() rawlist.pop(0) for l in rawlist: reimg = re.compile("\[img:(\S+)\]") mimg = reimg.match(l) recimg = re.compile("\[cimg:(\S+)\]") mcimg = recimg.match(l) reurl = re.compile("\[url:(\S+)\]") murl = reurl.match(l) reexec = re.compile("\[exec:(\S+)\]") mexec = reexec.match(l) if mimg: img = mimg.group(1) pic = pygame.image.load(img) surface.blit(pic, (60, 100 + i * 30)) i += pic.get_height() / 30 + 1 elif mcimg: img = mcimg.group(1) pic = pygame.image.load(img) xpos = (surface.get_width() - pic.get_width()) / 2 ypos = (surface.get_height() - pic.get_height()) / 2 surface.blit(pic, (xpos, ypos)) #i += pic.get_height() / 30 + 1 elif murl: url = murl.group(1) smallfont.set_italic(1) f = smallfont.render(url, 1, (0, 0, 150)) smallfont.set_italic(0) surface.blit(f, (60, 100 + i * 30)) eventlist[i] = url i += 1 elif mexec: execurl = mexec.group(1) lmod = re.sub(reexec, execurl, l) smallfont.set_italic(1) f = smallfont.render(lmod, 1, (0, 0, 0)) surface.blit(f, (60, 100 + i * 30)) f = smallfont.render(execurl, 1, (0, 150, 150)) smallfont.set_italic(0) surface.blit(f, (60, 100 + i * 30)) eventlist[i] = execurl i += 1 else: ## begin math rmath = re.compile("(?P\[math:(?P\w+)\])") while 1: mmath = rmath.search(l) if mmath: print mmath.groupdict() print "STRING", l print "SYM", mmath.group("term") print "MATCH", mmath.start("exp"), mmath.end("exp") sym = mmath.group("term") font = smallfontmath offset = 6 if sym == "diamond": sym = u"â—Š" # Attention: diamond, not space font = smallfont offset = 0 f = font.render(sym, 1, (255, 255, 255)) l = l[:mmath.start("exp")] + " " + l[mmath.end("exp"):] ltmp = l[:mmath.start("exp")] ftmp = smallfont.render(ltmp, 1, (255, 255, 255)) w = ftmp.get_width() surface.blit(f, (60 + w, 100 + i * 30 + offset)) else: break ## end math #if len(l) == 0 or l[0] != " ": # xmarker = 0 #if marker == -1 or marker == i: # xmarker = 1 # if marker == -1 or marker == i or xmarker: # f = smallfont.render(l, 1, (255, 255, 100)) # else: f = smallfont.render(l, 1, (255, 255, 255)) #f = smallfont.render(l, 1, (200, 200, 100)) surface.blit(f, (60, 100 + i * 30)) i += 1 xpage = globalpage if self.blackheading: #f = verysmallfont.render(subheading + " (" + str(xpage) + ")", 1, (255, 255, 255)) f = verysmallfont.render(subheading + " (" + str(xpage) + ")", 1, (200, 200, 100)) else: f = verysmallfont.render(subheading + " (" + str(xpage) + ")", 1, (0, 120, 255)) surface.blit(f, (50, 704)) oldslide = readslide readslide = 0 screen.blit(surface, (0, 0)) #pygame.display.flip() # XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx # oldslide = 0 # marker = -1 # mouse = 0 # draw = 0 # dodraw = 0 # posx = 0 # posy = 0 # dirty = 0 # eventlist = {} #readslide = 1 # screen.blit(screencopy, (0, 0)) # RETURN codes: # "exit", "next", "previous" def interactive(self, event): if event.type == KEYDOWN: key = event.key if key == K_ESCAPE or pygame.event.peek(QUIT): return "exit" if key == K_RETURN: return "next" if key == K_BACKSPACE: return "previous" #if key == K_f: # mouse = 1 - mouse # pygame.mouse.set_visible(mouse) # pygame.display.toggle_fullscreen() #if key == K_m: #mouse = 1 - mouse #pygame.mouse.set_visible(mouse) #if key == K_s: #try: # os.mkdir("static") #except: # pass #filename = "slide" + str(oldslide) + ".bmp" #pygame.image.save(screen, os.path.join("static", filename)) #if key == K_PLUS: #alphapart += 15 #white.set_alpha(alphapart) #screencopy.blit(screenorig, (0, 0)) #screencopy.blit(white, (0, 0)) #screencopy2.blit(screenorig, (0, 0)) #screencopy2.blit(white, (0, 0)) #readslide = oldslide #if key == K_MINUS: #alphapart -= 15 #white.set_alpha(alphapart) #screencopy.blit(screenorig, (0, 0)) #screencopy.blit(white, (0, 0)) #screencopy2.blit(screenorig, (0, 0)) #screencopy2.blit(white, (0, 0)) #readslide = oldslide #if key == K_DOWN: # marker += 1 #readslide = oldslide #if key == K_UP: # marker -= 1 #readslide = oldslide # FIXME! mouse = 0 if mouse: if event.type == MOUSEBUTTONDOWN: draw = 1 dodraw = 0 (posx, posy) = event.pos line = (posy - 100) / 30 if self.eventlist.has_key(line): if self.eventlist[line].find("http") >= 0: os.spawnlp(os.P_NOWAIT, "konqueror", "konqueror", self.eventlist[line]) else: os.spawnlp(os.P_NOWAIT, self.eventlist[line], self.eventlist[line]) if event.type == MOUSEBUTTONUP: draw = 0 dirty = 1 if event.type == MOUSEMOTION: (posx, posy) = event.pos dodraw = 1 # if draw: # if dodraw: # #screencopy2.fill((255, 100, 0), ((posx, posy), (20, 20))) # pass # XXX # if dirty: # dirty = 0 # readslide = oldslide component = Slides() parameters = ( ("slides", "Filename which refers to slides file", None), ("pages", "Initial page number (x) or range (x-y) to use", "1"), ("inverse", "Whether to invert colours", 0) ) metainfo = { "version": "0.2", "author": "Josef Spillner", "authoremail": "", "licence": "GPL" } doc = """ Slides: Default slideshow component with wiki-like syntax Slides can display text with highlighting for URLs via [url:xxx] tags. It supports some mathematical expressions such as \\diamond, embedded images via [img:xxx], and program execution via [exec:xxx] tags. Images can be displayed centered with the [cimg:xxx] tag. Usage: 'slides' -slides [-pages ] [-inverse true] Where is the file containing the presentation markup text Display: yes Interactivity: switching pages with enter/backspace launching web browser with clicks on hyperlinks """ pyntor-0.6/components/flags.py0000644000175300017530000000416510416470146016522 0ustar studentstudent# Pyntor::Flags - Display flags # Copyright (C) 2004 - 2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * import sys import time import random import os, pwd class Flags: def walker(self, arg, dirname, fnames): for file in fnames: path = os.path.join(dirname, file) img = pygame.image.load(path) self.flags.append(img) def init(self, options): flagdir = options["flagdir"] self.flags = [] os.path.walk(flagdir, self.walker, None) if not self.flags: print "(Flags) no flags found" self.disabled = 1 return self.pages = 1 self.counter = 0 self.flag = None def render(self, screen, page, globalpage): if self.counter == 0: flag = random.choice(self.flags) flag = flag.convert() flag.set_alpha(5) w = screen.get_width() - flag.get_width() h = screen.get_height() - flag.get_height() if w < 0: w = 0 if h < 0: h = 0 flagw = random.randint(0, w) flagh = random.randint(0, h) self.flag = (flag, flagw, flagh) (flag, flagw, flagh) = self.flag #surface.blit(flag, (flagw, flagh)) screen.blit(flag, (flagw, flagh)) #screen.blit(surface, (0, 0)) #pygame.display.flip() def interactive(self, event): if event.type == KEYDOWN: key = event.key if key == K_ESCAPE or pygame.event.peek(QUIT): return "exit" if key == K_RETURN: return "next" if key == K_BACKSPACE: return "previous" if key == K_s: filename = "intro" + ".bmp" # FIXME: screen? pygame.image.save(screen, os.path.join("static", filename)) self.counter = (self.counter + 1) % 35 #if self.counter == 0: return "reload" component = Flags() parameters = ( ("flagdir", "Directory containing the flag images", None), ) metainfo = { "version": "0.2", "author": "Josef Spillner", "authoremail": "", "licence": "GPL" } doc = """ Flags: display flags (or other images) with blend effect Usage: 'flags' -flagdir Where is a directory containing images of various sizes Display: yes Interactivity: no (but contains timer events), cancel with escape """ pyntor-0.6/components/pyromaniac.py0000644000175300017530000015030110416470147017563 0ustar studentstudent# -*- coding: utf-8 -*- # Pyntor::Pyromaniac - Display HTML pages # Copyright (C) 2004 - 2006 Josef Spillner # Published under GNU GPL conditions # Based on the Pyromaniac program # http://pyro.coolprojects.org/pyromaniac.html ### External python modules ============================================== ### ### ### import formatter import htmllib import urllib import urlparse import re import md5 import os import sys import pygame ### Global variables ===================================================== ### ### ### progresscount = 0 progresstotal = 0 progressenabled = 0 zoomfactor = 100 globalx = 0 globaly = 0 globalpage = None pagewidth = 800 pageheight = 6000 displaywidth = 800 displayheight = 600 searchterm = None ### Table container class ================================================ ### ### ### class table: ### Initialize a table with inheritance def __init__(self, ancestor, name): self.name = name self.children = [] self.x = 0 self.y = 0 self.w = 0 self.h = 0 self.content = None if ancestor is not None: self.parent = ancestor if ancestor.align == "horizontal": self.align = "vertical" else: self.align = "horizontal" ancestor.children.append(self) else: self.parent = None self.align = "vertical" ### Dump a table structure def dump(self, x = 0): p = "" for i in range(0, x): p += " " p += self.name + " " p += str(self.x) + "/" + str(self.y) + " - " + str(self.w) + "/" + str(self.h) p += " " + self.align print p for c in self.children: c.dump(x + 1) ### Fix all absolute size values def cons(self): givenw = 0 givenh = 0 count = 0 for c in self.children: if self.align == "horizontal": if c.w > 0: givenw += c.w count += 1 c.h = self.h if self.align == "vertical": if c.h > 0: givenh += c.h count += 1 c.w = self.w if len(self.children) <= count: return if self.align == "horizontal": n = (self.w - givenw) / (len(self.children) - count) for c in self.children: if c.w == 0: c.w = n else: n = (self.h - givenh) / (len(self.children) - count) for c in self.children: if c.h == 0: c.h = n for c in self.children: c.cons() ### Normalize widths or heights def normalize(self, direction): if direction == "vertical": if len(self.children) == 0: print "##### weird!" return l = len(self.children[0].children) max = 0 for d in range(0, l): max = 0 for c in self.children: if d < len(c.children): if c.children[d].w > max: max = c.children[d].w for c in self.children: if d < len(c.children): c.children[d].w = max if self.w == 0: self.w = max * l elif direction == "horizontal": for c in self.children: max = 0 for d in c.children: if d.h > max: max = d.h for d in c.children: d.h = max c.h = max ### Finish a table by converting all sizes def finish(self): y = 0 for c in self.children: x = 0 for d in c.children: d.x = x d.w += x x = d.w c.y = y c.h += y y = c.h ### Helper classes ======================================================= ### ### ### ### Surface data without content allocation class dummypage: def __init__(self, w): self.w = w def get_width(self): return self.w ### Stylesheet top class class stylesheet: def __init__(self): self.ident = None self.element = None self.styles = {} ### Surface enhancements with absolute positions class surface: def __init__(self, surface): self.surface = surface self.original = None self.x = -1 self.y = -1 ### Render part class renderpart: def __init__(self): self.x = -1 self.y = -1 self.surfaces = [] self.content = [] self.attributes = {} self.children = [] self.parent = None self.activated = 0 self.container = None self.hyperlink = None ### Main render object containing the renderpart tree class autodom: def __init__(self, object): self.ref = object def __str__(self): return self.ref.attributes['content'] def __setitem__(self, key, value): if self.ref is not None: self.ref.attributes[key] = value def __getitem__(self, key): print "#>>", key if self.ref is not None: return self.ref.attributes['content'][key] def __getattr__(self, name): if name == "ref": return self.ref print ">>>", name, "<<<" newref = None if self.ref is not None: for childpart in self.ref.children: print "???", childpart.attributes['__type'] if childpart.attributes['__type'] == name: newref = childpart print "!!!", newref.attributes x = autodom(newref) return x class renderobject: def __init__(self): self.title = None self.root = renderpart() self.part = self.root self.bodypart = None self.stylepart = None self.resources = [] self.containers = [] self.dom = autodom(self.root) ### HTML validation actions def validator(str): print str ### Return a string backwards def reverse(s): rs = "" for x in s: rs = x + rs return rs ### Return the int representation of a color component def hex2bin(hex): number = 0 factor = 1 for x in reverse(hex): n = x if n == 'a' or n == "A": n = '10' if n == 'b' or n == "B": n = '11' if n == 'c' or n == "C": n = '12' if n == 'd' or n == "D": n = '13' if n == 'e' or n == "E": n = '14' if n == 'f' or n == "F": n = '15' #try: number = number + int(n) * factor #except: # print "**** unhandled color: " + hex factor = factor * 16 return number ### Return a color tuple def getcolor(str): map = { "blue": "#0000ff", "yellow": "#ffff00", "teal": "#207090", "white": "#ffffff", "red": "#0000ff", "gray": "#c0c0c0", "purple": "#ff00ff", "black": "#000000" } if str.lower() in map.keys(): rgbcolor = map[str.lower()] else: if str[0:1] != "#": str = "#" + str rgbcolor = str colors = (hex2bin(rgbcolor[1:3]), hex2bin(rgbcolor[3:5]), hex2bin(rgbcolor[5:7])) return colors ### Parser class ========================================================= ### ### ### class myparser(htmllib.HTMLParser): ### Initialize parser def __init__(self): f = formatter.NullFormatter() htmllib.HTMLParser.__init__(self, f) self.statelist = [] self.statestack = [] self.object = None self.container = None self.baseurl = "" self.tempdir = "/tmp/pyromaniac/" self.nextslide = "" self.previousslide = "" ### Invoke end tag handler def __getattr__(self, name): if name[0:3] == "end": tag = name[4:] self.handle_endtag(tag, None) ### Always return the absolute URL def correcturl(self, url): return urlparse.urljoin(self.baseurl, url) ### Download an external resource (may be cached) def download(self, url): url = self.correcturl(url) localfile = self.tempdir + md5.md5(url).hexdigest() try: file = open(localfile, "r") self.debug("DOWNLOADING (cached): " + url) except: self.debug("DOWNLOADING (live): " + url) try: file = urllib.urlopen(url) except: file = None validator("Warning! Resource " + url + " not found!") progress(-1) if file is not None: contents = file.read() file.close() try: os.mkdir(self.tempdir) except: pass local = open(localfile, "w") local.writelines(contents) local.close() return localfile return None ### Parse a file or URL def parse(self, filename, object): self.baseurl = filename if filename[0:7] == "http://": f = self.download(filename) filename = f try: contents = open(filename).read() except: return contents = re.sub("\\n", " ", contents) #### FIXME! TODO! contents = re.sub("\\t", " ", contents) #### FIXME! TODO! while 1: match = re.search("\\ \\ ", contents) if match: contents = re.sub("\\ \\ ", " ", contents) #### FIXME! TODO! else: break self.object = object # hack-ish: pyro navigation commands in comments self.nextslide = "" self.previousslide = "" pos = contents.find("pyro:next") if pos != -1: pos2 = contents.find(" ", pos + 10) self.nextslide = contents[pos + 10:pos2] pos = contents.find("pyro:previous") if pos != -1: pos2 = contents.find(" ", pos + 14) self.previousslide = contents[pos + 14:pos2] print "COMMENTS: next=", self.nextslide, "previous=", self.previousslide self.feed(contents) self.close() print "!!!! linklist: ", self.object.resources ### Debug function def debug(self, str): #print str pass ### Handler for HTML tag data def handle_data(self, data): if len(self.statelist) > 0: state = self.statelist[len(self.statelist) - 1] self.object.part = self.statestack[len(self.statestack) - 1] else: state = "[invalid]" stack = None self.debug("DATA " + data + " in " + state) if state == "html": return if state == "title": self.object.title = self.object.part self.object.part.attributes['content'] = [data] return if state == "style": self.object.part.attributes['content'] = [data] return if state == "table" or state == "tr": return tags = ["body", "h1", "h2", "h3", "h4", "h5", "h6", "h7", "li", "hr"] tags += ["td", "a", "b", "i", "p", "font", "tt", "sub", "sup", "s"] tags += ["strike", "big", "small", "u", "center", "th", "form", "div"] tags += ["span", "em", "strong"] if state in tags: if len(self.object.part.children) > 0: child = self.object.part.children[-1] if child.attributes['__type'] == "__text": child.attributes['content'] = [child.attributes['content'][0] + data] return if data == " ": return child = renderpart() child.parent = self.object.part self.object.part.children.append(child) child.attributes['__type'] = "__text" child.attributes['content'] = [data] if state == "span": child.attributes['css::color'] = "#106f10" else: child = renderpart() child.parent = self.object.part self.object.part.children.append(child) child.attributes['__type'] = "__text" child.attributes['css::color'] = "#c0c0c0" child.attributes['css::strikethrough'] = 1 child.attributes['content'] = [data] ### Handler for HTML tag begin def handle_starttag(self, tag, data, attributes): global pagewidth self.debug("<" + tag + ">") child = renderpart() child.parent = self.object.part self.object.part.children.append(child) child.attributes['__type'] = tag for attribute in attributes: child.attributes[attribute[0]] = attribute[1] single = ["br", "img", "input", "hr"] if tag not in single: self.object.part = child self.statelist.append(tag) self.statestack.append(child) state = tag self.debug(">> new state could be: " + state) resource = None if tag == "img": if child.attributes.has_key('src'): resource = child.attributes['src'] if tag == "td" or tag == "th" or tag == "body": if child.attributes.has_key('background'): resource = child.attributes['background'] if resource is not None: self.object.resources.append(resource) if tag == "body": self.object.bodypart = self.object.part if tag == "style": self.object.stylepart = self.object.part ### Container handling if tag == "table" or tag == "tr" or tag == "td" or tag == "th" or tag == "body": if tag == "body": # QUICK HACK self.container = table(self.container, "(body-table)") self.container = table(self.container, "(body-tr)") self.container = table(self.container, tag) a = self.object.part.attributes if a.has_key('width'): w = a['width'] if w[-1:] == "%": w = pagewidth self.container.w = int(w) if a.has_key('height'): h = a['height'] if h[-1:] == "%": h = 800 self.container.h = int(h) if tag == "td" or tag == "th" or tag == "body": self.container.content = self.object.part self.object.part.container = self.container print "##### TABLE #####", tag, "for", self.object.part.attributes['__type'] self.debug(">> new state is: " + state) ### Handler for HTML tag end def handle_endtag(self, tag, foo): single = ["br", "img", "input", "hr"] if tag in single: self.debug("** circumvent " + tag) return self.debug("") self.statelist = self.statelist[:-1] self.statestack = self.statestack[:-1] ### Container handling if self.container is not None: if self.container.name == tag: old = self.container self.container = self.container.parent if tag == "body": # QUICK HACK self.container = self.container.parent old = self.container self.container = self.container.parent if self.container == None: print "##### TABLE FINISHED #####", tag self.object.containers.append(old) if len(self.statelist) > 0: state = self.statelist[len(self.statelist) - 1] self.object.part = self.statestack[len(self.statestack) - 1] else: state = "[invalid]" stack = None self.debug(">> new state is now again: " + state) ### Optimize all headings (h1-h7) to __h def optimizer_headings(self, part): a = part.attributes if a.has_key('__type'): if a['__type'] == "h1": a['__type'] = "__h" a['__headingsize'] = 1 if a['__type'] == "h2": a['__type'] = "__h" a['__headingsize'] = 2 if a['__type'] == "h3": a['__type'] = "__h" a['__headingsize'] = 3 if a['__type'] == "h4": a['__type'] = "__h" a['__headingsize'] = 4 if a['__type'] == "h5": a['__type'] = "__h" a['__headingsize'] = 5 if a['__type'] == "h6": a['__type'] = "__h" a['__headingsize'] = 6 if a['__type'] == "h7": a['__type'] = "__h" a['__headingsize'] = 7 for part in part.children: self.optimizer_headings(part) ### Generic tree walker method def optimizer_treewalk(self, part, attribute, value): part.attributes[attribute] = value for part in part.children: self.optimizer_treewalk(part, attribute, value) ### Generic additive tree walker method def optimizer_treewalk_add(self, part, attribute, value): print "XXX ADD", value, "TO", attribute, "IN", part if part.attributes.has_key(attribute): part.attributes[attribute] += value else: part.attributes[attribute] = value for part in part.children: self.optimizer_treewalk_add(part, attribute, value) ### Non-destructive supplementive tree walker def optimizer_treewalk_fill(self, part, attribute, value): if not part.attributes.has_key(attribute): part.attributes[attribute] = value for childpart in part.children: self.optimizer_treewalk_fill(childpart, attribute, value) ### Optimize all attributes (except CSS) def optimizer_attributes(self, part): a = part.attributes if part.parent != None: clist = part.parent.children if a.has_key('__type'): types = ['td', 'th', 'tr', 'table', 'body'] if a['__type'] in types: if a.has_key('bgcolor'): for childpart in part.children: self.optimizer_treewalk_fill(childpart, "bgcolor", a['bgcolor']) if not a.has_key('border'): if a['__type'] != "body": a['border'] = 1 if a.has_key('border'): for childpart in part.children: self.optimizer_treewalk_fill(childpart, "border", a['border']) if a['__type'] == "font": for childpart in part.children: if a.has_key('size'): size = a['size'] if size == "small": size = "-1" if size[0:1] == "+" or size[0:1] == "-": rel = size[0:1] + str(int(size) * 5) self.optimizer_treewalk(childpart, "css::fontsizerel", rel) elif size != "": self.optimizer_treewalk(childpart, "css::font-size", int(size) * 7) if a.has_key('color'): self.optimizer_treewalk(childpart, "css::color", a['color']) childpart.parent = part.parent clist.insert(clist.index(part), childpart) part.parent.children.remove(part) part = part.parent if a['__type'] == "__h": size = 48 if a['__headingsize'] == 1: size = "49" if a['__headingsize'] == 2: size = "42" if a['__headingsize'] == 3: size = "35" if a['__headingsize'] == 4: size = "28" if a['__headingsize'] == 5: size = "21" if a['__headingsize'] == 6: size = "14" if a['__headingsize'] == 7: #size = "5" ### FIXME: pygame bug size = "10" childpart = part.children[0] if childpart is not None: childpart.attributes['__prebreak'] = 1 childpart = None for childpart in part.children: self.optimizer_treewalk(childpart, "css::font-size", size) childpart.parent = part.parent clist.insert(clist.index(part), childpart) newpart = renderpart() newpart.attributes['__type'] = "br" clist.insert(clist.index(part), newpart) part.parent.children.remove(part) part = part.parent if a['__type'] == "big": for childpart in part.children: self.optimizer_treewalk(childpart, "css::sizerel", 1); clist.insert(clist.index(part), childpart) childpart.parent = part.parent part.parent.children.remove(part) part = part.parent if a['__type'] == "small": for childpart in part.children: self.optimizer_treewalk(childpart, "css::sizerel", -1); clist.insert(clist.index(part), childpart) childpart.parent = part.parent part.parent.children.remove(part) part = part.parent if a['__type'] == "form": for childpart in part.children: self.optimizer_treewalk(childpart, "__form", 1); clist.insert(clist.index(part), childpart) childpart.parent = part.parent part.parent.children.remove(part) part = part.parent css = { "i": "css::italic", "b": "css::font-weight", "em": "css::font-weight", "strong": "css::font-weight", "tt": "css::fixedwidth", "u": "css::underline", "s": "css::strikethrough", "strike": "css::strikethrough", "sup": "css::superscript", "sub": "css::subscript", "pre": "css:formatted", "center": "css::centered" } if css.has_key(a['__type']): for childpart in part.children: self.optimizer_treewalk(childpart, css[a['__type']], 1); clist.insert(clist.index(part), childpart) childpart.parent = part.parent clist.remove(part) part = part.parent if a['__type'] == "ol" or a['__type'] == "dl": i = 0 for childpart in part.children: if childpart.attributes['__type'] == "li": i += 1 self.optimizer_treewalk(childpart, "css::ordered", i); self.optimizer_treewalk_add(childpart, "css::leftmargin", 20); clist.insert(clist.index(part), childpart) childpart.parent = part.parent part.parent.children.remove(part) part = part.parent if a['__type'] == "ul": for childpart in part.children: self.optimizer_treewalk(childpart, "css::unordered", 1); self.optimizer_treewalk_add(childpart, "css::leftmargin", 20); clist.insert(clist.index(part), childpart) childpart.parent = part.parent part.parent.children.remove(part) part = part.parent if a['__type'] == "body" or a['__type'] == "td" or a['__type'] == "table": print "XXX OPTIMIZE MARGIN", a['__type'] if a.has_key('leftmargin'): for childpart in part.children: self.optimizer_treewalk_add(childpart, "css::leftmargin", int(a['leftmargin'])); if a.has_key('topmargin'): for childpart in part.children: self.optimizer_treewalk_add(childpart, "css::topmargin", int(a['topmargin'])); if a['__type'] == "a": if a.has_key('href'): for childpart in part.children: self.optimizer_treewalk(childpart, "href", a['href']); clist.insert(clist.index(part), childpart) childpart.parent = part.parent elif a.has_key('name'): for childpart in part.children: self.optimizer_treewalk(childpart, "name", a['name']); clist.insert(clist.index(part), childpart) childpart.parent = part.parent else: validator("A is missing HREF or NAME") part.parent.children.remove(part) part = part.parent if a['__type'] == "tr" or a['__type'] == "table": a['__break'] = 1 if a['__type'] == "li" or a['__type'] == "dd" or a['__type'] == "dt": childpart = renderpart() if a.has_key('css::unordered'): childpart.attributes['__type'] = "bullet" childpart.attributes['css::bullettype'] = "round" elif a.has_key('css::ordered'): childpart.attributes['__type'] = "__text" childpart.attributes['content'] = [str(a['css::ordered']) + "."] else: validator("LI is not enclosed by OL or UL") clist.insert(clist.index(part), childpart) childpart.attributes['__prebreak'] = 1 if a.has_key('css::leftmargin'): childpart.attributes['css::leftmargin'] = a['css::leftmargin'] childpart.parent = part.parent childpart = None for childpart in part.children: clist.insert(clist.index(part), childpart) childpart.parent = part.parent clist.remove(part) part = part.parent if childpart != None: childpart.attributes['__break'] = 1 for childpart in part.children: if a.has_key('__type'): print "XXX I'm a", a['__type'], "my child is a", childpart.attributes['__type'] else: print "XXX ???" self.optimizer_attributes(childpart) ### Dump the optimized HTML tree def dump(self, part, level): a = part.attributes if a.has_key('__type'): tag = a['__type'] else: tag = "(unknown)" tagline = "" for i in range(0, level): tagline += " " tagline += "<" + tag for key in a: if key != "__type": value = a[key] if type(value) == type([]): value = "[list:" + value[0] + "]" if type(value) == type(0): value = str(value) tagline += " " + key + "=\"" + value + "\"" tagline += ">" print tagline if len(part.surfaces) > 0: self.debug("# surfaces" + str(part.surfaces)) if part.container is not None: self.debug("() container" + str(part.container)) for childpart in part.children: self.dump(childpart, level + 1) if len(part.children) > 0: tagline = "" for i in range(0, level): tagline += " " tagline += "" print tagline ### Apply a CSS style to a parse tree def optimizer_styleapply(self, part, style, active): a = part.attributes if a.has_key('__type'): self.debug("CSS DETECTION: " + part.attributes['__type']) if a.has_key('class'): if a['class'] == style.ident: active = 1 self.debug("CSS IS ACTIVE!") if active: if style.element == None or style.element == a['__type']: for s in style.styles.keys(): self.debug("CSS APPLY!") a["css::" + s] = style.styles[s] for childpart in part.children: self.optimizer_styleapply(childpart, style, active) ### Apply a list of CSS styles to a parse tree def optimizer_style(self, part, stylelist): if stylelist == None: return print "*** CASCADING STYLE SHEETS ***" for s in stylelist: print "NAME", s.ident, "ELEM", s.element, "STYLES", s.styles self.optimizer_styleapply(part, s, 0) ### Parse document style sheet (CSS) definitions def stylesheetparser(self, part): if part == None: return if not part.attributes.has_key('content'): return definition = part.attributes['content'] tokens = definition[0].split(" ") state = "ident" stylelist = [] style = None attribute = None for t in tokens: if t == "": continue if state == "ident": state = "element-or-brace" style = stylesheet() ident = t if ident[0] == ".": ident = ident[1:] style.ident = ident stylelist.append(style) elif state == "element-or-brace": if t == "{": state = "attribute" else: state = "brace" style.element = t elif state == "brace": state = "attribute" elif state == "attribute": if t == "}": state = "ident" else: state = "value" attribute = t[:-1] elif state == "value": state = "attribute" style.styles[attribute] = t[:-1] else: print "Invalid state! " + state return stylelist ### More helper classes and methods ====================================== ### ### ### ### Display download progress def progress(p): global navpage global progresscount global progresstotal global pagewidth global progressenabled if not progressenabled: return if p < 0: progresscount -= 1 else: progresscount = p progresstotal = p print "-- progress --", progresscount if progresstotal > 0: bar = pygame.Surface((pagewidth, 10)) bar.fill((0, 0, 140)) bar.fill((0, 0, 200), (0, 0, pagewidth - pagewidth * progresscount / progresstotal, 10)) #navpage.blit(bar, (0, 0)) #screen.blit(navpage, (0, 10)) screen.blit(bar, (0, 0)) pygame.display.update() ### Return a container background part def containerrender(part): a = part.attributes colors = None img = None if a.has_key('bgcolor'): rgbcolor = a['bgcolor'] colors = getcolor(rgbcolor) elif a.has_key('css::background'): rgbcolor = a['css::background'] colors = getcolor(rgbcolor) else: colors = (255, 255, 255) if a.has_key('background'): bg = parser.download(a['background']) img = pygame.image.load(bg) #img = pygame.transform.scale(img, (pagewidth, 600)) x = None if a['__type'] == "th" or a['__type'] == "td" or a['__type'] == "body": w = 100 h = 100 if a['__type'] == "body": w = pagewidth h = pageheight if a.has_key('width'): wx = a['width'] if type(wx) == type(""): if wx[-1:] == "%": wx = "200" ### FIXME! w = int(wx) if a.has_key('height'): hx = a['height'] if type(hx) == type(""): if hx[-1:] == "%": hx = "200" ### FIXME! h = int(hx) if w > 0 and h > 0: x = pygame.Surface((w, h)) x.fill(colors) if img is not None: x.blit(img, (0, 0)) if a.has_key('border'): for j in range(0, x.get_height()): for q in range(0, int(a['border'])): x.set_at((q, j), (0, 0, 0)) x.set_at((x.get_width() - 1 - q, j), (0, 0, 0)) for i in range(0, x.get_width()): for q in range(0, int(a['border'])): x.set_at((i, q), (0, 0, 0)) x.set_at((i, x.get_height() - 1 - q), (0, 0, 0)) return x ### Font enhancement class class fontobject: def __init__(self, font, rfontsize): self.font = font self.rfontsize = rfontsize ### Return an appropriate font def getfont(part): a = part.attributes fontsize = 15 if a.has_key('css::font-size'): param = str(a['css::font-size']) if len(param) > 2 and param[-2:] == "pt": size = param[:-2] fontsize = int(size) * 2 else: fontsize = int(param) if a.has_key('css::fontsizerel'): relsize = str(a['css::fontsizerel']) sign = relsize[0:1] amount = relsize[1:] if sign == "-": fontsize -= int(amount) elif sign == "+": fontsize += int(amount) rfontsize = fontsize if a.has_key('css::superscript'): fontsize = int(fontsize * 0.7) if a.has_key('css::subscript'): fontsize = int(fontsize * 0.7) if fontsize < 10: fontsize = 10 # FIXME! pygame bug font = pygame.font.Font(None, fontsize) if a.has_key('css::font-weight'): font.set_bold(1) if a.has_key('css::italic'): font.set_italic(1) if a.has_key('css::underline'): font.set_underline(1) return fontobject(font, rfontsize) ### Return appropriate font colors def getfontcolors(part): a = part.attributes if a.has_key('css::color'): rgbcolor = a['css::color'] colors = getcolor(rgbcolor) else: colors = (0, 0, 0) if a.has_key('href'): colors = (255, 255, 0) return colors ### Render a text using a given font def fontrender(part, text, fontobject = None, colors = None): global searchterm a = part.attributes if fontobject == None: fontobject = getfont(part) if colors == None: colors = getfontcolors(part) if a.has_key('bgcolor'): bgcol = getcolor(a['bgcolor']) else: bgcol = (255, 255, 255) if searchterm is not None: if text.find(searchterm) >= 0: print "*** SEARCH MATCH ***" bgcol = (200, 100, 0) #a['bgcolor'] = "#e09000" #print "'" + searchterm + "'", "'" + text + "'" font = fontobject.font rfontsize = fontobject.rfontsize fontsize = font.get_height() echar = chr(160) #print "1-RENDER '" + text + "'" #if text == echar: # print "TABBED!" # text = "" text = text.replace(chr(160), " ") textline = font.render(text, 1, colors) tmp = pygame.Surface((textline.get_width(), rfontsize)) tmp.fill(bgcol) ### ALPHA tmp.blit(textline, (0, 0)) textline = tmp if a.has_key('css::strikethrough'): j = textline.get_height() / 2 for i in range(0, textline.get_width()): textline.set_at((i, j), colors) if a.has_key('css::superscript') or a.has_key('css::subscript'): tmp = pygame.Surface((textline.get_width(), rfontsize)) tmp.fill(bgcol) ### ALPHA if a.has_key('css::superscript'): tmp.blit(textline, (0, 0)) else: tmp.blit(textline, (0, rfontsize - fontsize)) textline = tmp if a.has_key('css::centered'): ### FIXME! centering tmp = pygame.Surface((textline.get_width(), rfontsize)) tmp.fill(bgcol) ### ALPHA tmp.blit(textline, (tmp.get_width() / 2 - textline.get_width() / 2, 0)) textline = tmp return textline ### Render each element of a parse tree def render(part): global pagewidth if part == None: return a = part.attributes if a.has_key('__type'): if a['__type'] == "img": if a.has_key('src'): src = parser.download(a['src']) try: img = pygame.image.load(src) width = img.get_width() height = img.get_height() if a.has_key('width'): width = int(a['width']) if a.has_key('height'): height = int(a['height']) img = pygame.transform.scale(img, (width, height)) except: width = 20 height = 20 if a.has_key('width'): width = int(a['width']) if a.has_key('height'): width = int(a['height']) img = pygame.Surface((width, height)) img.fill((255, 0, 0)) s = surface(img) #part.surfaces.append(s) part.surfaces = [s] else: validator("IMG is missing SRC") elif a['__type'] == "bullet": if a.has_key('css::color'): rgbcolor = a['css::color'] colors = getcolor(rgbcolor) else: colors = (0, 0, 0) img = pygame.Surface((7, 7)) img.fill(colors) s = surface(img) part.surfaces.append(s) elif a['__type'] == "hr": img = pygame.Surface((pagewidth - 20, 5)) img.fill((255, 200, 200)) s = surface(img) part.surfaces.append(s) elif a['__type'] == "input": type = "text" if a.has_key('width'): width = int(a['width']) else: width = 300 if a.has_key('type'): type = a['type'] if type == "text": img = pygame.Surface((width, 20)) img.fill((255, 200, 200)) elif type == "checkbox": img = pygame.Surface((20, 20)) img.fill((255, 200, 200)) if a.has_key('checked'): if a['checked'] == "CHECKED": img.fill((0, 0, 0), (5, 5, 10, 10)) elif type == "submit": title = "Submit" if a.has_key('value'): title = a['value'] fontsize = 20 font = pygame.font.Font(None, fontsize) print "2-RENDER '", title, "'" text = font.render(title, 1, (0, 0, 0)) img = pygame.Surface((text.get_width(), text.get_height())) img.fill((255, 200, 200)) img.blit(text, (0, 0)) else: img = pygame.Surface((20, 20)) img.fill((255, 200, 200)) s = surface(img) part.surfaces.append(s) elif a['__type'] == "body": img = containerrender(part) font = pygame.font.Font(None, 20) for text in part.content: print "3-RENDER '", text, "'" textline = font.render(text, 1, (0, 0, 0), colors) s = surface(textline) part.surfaces.append(s) elif a['__type'] == "__text": fontobject = getfont(part) colors = getfontcolors(part) font = fontobject.font rfontsize = fontobject.rfontsize width = pagewidth - 100 ### FIXME: containers textstrategy = "words" ### words or width for text in a['content']: (x, y) = font.size(text) txt = text while len(txt): if textstrategy == "words": txt2 = "" ready = 0 while not ready: txt2 += txt[0:1] txt = txt[1:] if txt2[-1:] == " ": ready = 1 if len(txt) == 0: ready = 1 elif textstrategy == "width": txt2 = txt while font.size(txt2)[0] > width and len(txt2): txt2 = txt2[:-1] txt = txt[len(txt2):] if len(txt2) == 0: txt2 = txt txt = "" else: print "Error! Unknown text strategy" a['content'].insert(a['content'].index(text), txt2) a['content'].remove(text) for text in a['content']: if text != " ": textline = fontrender(part, text, fontobject, colors) s = surface(textline) part.surfaces.append(s) for part in part.children: render(part) ### Class representing a hyperlink class hyperlink: def __init__(self, surface, x, y, part, xoff, yoff, page): self.surface = surface self.x = x self.y = y self.part = part self.xoff = xoff self.yoff = yoff self.page = page ### Return user input from a text input field def getinput(h): url = "" shadow = {} shift = 0 while 1: pygame.event.pump() keystates = pygame.key.get_pressed() for keycode in range(0, len(keystates)): key = pygame.key.name(keycode) if not shadow.has_key(keycode): shadow[keycode] = 0 if keystates[keycode] and not shadow[keycode]: print "keycode", keycode, "key", key shadow[keycode] = 1 if key == "escape": return "" elif key == "return": return url elif key == "backspace": url = url[:-1] elif key == "right shift": shift = 1 elif key == "left shift": shift = 1 else: if shift == 1: if key == ".": key = ":" if key == "7": key = "/" chars = "abcdefghijklmnopqrstuvwxyz" bchars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" for c in range(len(chars)): if chars[c] == key: key = bchars[c] url = url + key if not keystates[keycode] and shadow[keycode]: shadow[keycode] = 0 if key == "right shift": shift = 0 if key == "left shift": shift = 0 h.page.blit(h.part.surfaces[0].surface, (h.part.x, h.part.y)) if url is not "": font = pygame.font.Font(None, 20) print "4-RENDER '", url, "'" textline = font.render(url, 1, (0, 0, 255)) h.page.blit(textline, (h.part.x, h.part.y)) screen.blit(h.page, (h.xoff, h.yoff)) pygame.display.update() ### Displayer class ====================================================== ### ### ### class displayer: global globalpage ### Initialize class, including reset def __init__(self): self.edgex = 0 ## w00t, should be 10, kludge! self.edgey = 0 ## w00t, should be 10, kludge! self.depth = 0 self.fontstrategy = "passive" # overlay or passive self.reset() ### Reset all values def reset(self): self.posx = self.edgex self.posy = self.edgey self.oldmaxy = -2 self.leftmargin = 0 self.totalheight = 0 ### Assign absolute positions to all renderparts def display(self, page, part, construction, reset = 1): global globalx, globaly global zoomfactor if part == None: return if reset == 1: print "##### rpage container?", part.container, reset if reset == 1: self.reset() print "reset image:", page.get_width() a = part.attributes dobreak = 0 centerparts = [] types = ["ol", "br", "p", "table", "tr"] if a.has_key('__type'): if a['__type'] in types: dobreak = 1 if a.has_key('__prebreak'): if a['__prebreak'] == 1: dobreak = 1 if a.has_key('css::leftmargin'): self.leftmargin = a['css::leftmargin'] else: self.leftmargin = 0 if a.has_key('css::topmargin'): self.posy += a['css::topmargin'] if dobreak: self.posx = self.edgex self.posy += self.oldmaxy + 2 self.oldmaxy = 0 if reset == 0: if part.container is not None: print "##### want rpage" d = displayer() rpage = containerrender(part) if rpage is not None: print "##### rpage", rpage.get_width(), rpage.get_height() d.display(rpage, part, 1) d.blitter(rpage, part, 1, 1) part.surfaces = [surface(rpage)] maxx = 0 lastx = 0 maxy = 0 rposx = 0 for s in part.surfaces: if a.has_key('css::centered'): centerparts.append(s) if a.has_key('bgcolor'): bgcol = getcolor(a['bgcolor']) else: bgcol = (255, 255, 255) if lastx > 0: self.posx += lastx if s.original == None: o = pygame.Surface((s.surface.get_width(), s.surface.get_height())) o.fill(bgcol) ### ALPHA o.blit(s.surface, (0, 0)) s.original = o ### FIXME: s.original = s.surface if zoomfactor != 100: zoomx = (s.original.get_width() * zoomfactor) / 100 zoomy = (s.original.get_height() * zoomfactor) / 100 print "zoom", s.original.get_width(), s.original.get_height(), "=>", zoomx, zoomy, zoomfactor s.surface = pygame.transform.scale(s.original, (zoomx, zoomy)) else: o = pygame.Surface((s.original.get_width(), s.original.get_height())) o.fill(bgcol) ### ALPHA o.blit(s.original, (0, 0)) s.surface = o ### FIXME: s.surface = s.original if self.posx + s.surface.get_width() > page.get_width() - self.leftmargin + 2: ### FIXME: containers self.posx = self.edgex self.posy += self.oldmaxy + 2 self.oldmaxy = 0 rposx = self.posx + self.leftmargin part.x = rposx part.y = self.posy s.x = rposx s.y = self.posy if construction == 1: if a.has_key('href'): h = hyperlink(s, rposx, self.posy, part, globalx, globaly, globalpage) links.append(h) part.hyperlink = h if a.has_key('__type'): if a['__type'] == "input": if a.has_key('name'): part.attributes['href'] = a['name'] else: part.attributes['href'] = "internal:input" h = hyperlink(s, rposx, self.posy, part, globalx, globaly, globalpage) links.append(h) part.hyperlink = h if s.surface.get_width() > maxx: maxx = s.surface.get_width() + 1 lastx = s.surface.get_width() + 1 if s.surface.get_height() > maxy: maxy = s.surface.get_height() + 1 if self.posy + s.surface.get_height() > self.totalheight: self.totalheight = self.posy + s.surface.get_height() if maxy > 0 and maxy > self.oldmaxy: self.oldmaxy = maxy dobreak = 0 types = ["p", "br", "table"] if a.has_key('__type'): if a['__type'] in types: dobreak = 1 if a.has_key('__break'): if a['__break'] == 1: dobreak = 1 ## center-kludge shift = page.get_width() / 2 - (rposx + lastx - self.leftmargin) / 2 shifted = 0 for p in centerparts: #print "CP", rposx - self.leftmargin, pagewidth p.x += shift shifted = 1 if part.hyperlink is not None: if shifted: part.hyperlink.x += shift print "yooo shift", shift subrender = 1 if reset == 0: if part.container is not None: subrender = 0 if subrender: for childpart in part.children: self.depth += 1 maxy = self.display(page, childpart, construction, 0) self.depth -= 1 maxy = maxy - self.oldmaxy if maxy < 0: maxy = 0 if dobreak: self.posx = self.edgex self.posy += maxy + 2 else: self.posx += lastx + 2 return maxy ### Blits renderparts with fixed positions onto a surface def blitter(self, page, part, construction, override): if part == None: return a = part.attributes ### Blit each surface of the renderpart counter = 0 for s in part.surfaces: rposx = s.x posy = s.y if a['__type'] == "__text" and self.fontstrategy == "overlay": tmp = fontrender(part, a['content'][counter]) page.blit(tmp, (rposx, posy)) else: page.blit(s.surface, (rposx, posy)) counter += 1 ### Some special effects if construction == 0: if part.activated == 1: for i in range(0, s.surface.get_width()): try: page.set_at((rposx + i, posy + s.surface.get_height() - 1), (0, 0, 0)) except: pass if construction == 1: if a.has_key('__type'): if a['__type'] == 'input': try: for j in range(0, s.surface.get_height()): page.set_at((rposx, posy + j), (0, 0, 0)) page.set_at((rposx + s.surface.get_width() - 1, posy + j), (0, 0, 0)) for i in range(0, s.surface.get_width()): page.set_at((rposx + i, posy), (0, 0, 0)) page.set_at((rposx + i, posy + s.surface.get_height() - 1), (0, 0, 0)) except: pass ### Also blit the renderpart's children if part.container == None or override == 1: #print "*** goblit childrenof", part.attributes['__type'] override = 0 for c in part.children: self.blitter(page, c, construction, override) ### Main program ========================================================= ### ### ### ### Initialization def setup(withprogress): global parser global screen global font global page global links global history global progressenabled progressenabled = withprogress parser = myparser() #pygame.init() #screen = pygame.display.set_mode((pagewidth, 600)) screen = pygame.display.get_surface() page = pygame.Surface((pagewidth, pageheight)) font = pygame.font.Font(None, 48) links = [] history = [] ### Navigation panel def setup_navigation_panel(): global globaly global parser global hosted global globalpage navigation = """ Zurück | Vor | URL: | Cache leeren | Neu laden | Suche: """ if hosted: navigation = "" ##### temp! FIXME TODO navigation = re.sub("\\n", " ", navigation) navigation = re.sub("\\t", "", navigation) navpage = pygame.Surface((pagewidth, 30)) globalpage = navpage object = initial_render(None, navigation, 10) final_render(object, 0) ### Setup navigation def setup_navigation(): global navlinks global globalpage global page global url global history global historyposition navlinks = [] for l in links: navlinks.append(l) globalpage = page if url is not None: history.append(url) historyposition = 0 ### Clearance functions def clearwalker(arg, dirname, fnames): for f in fnames: fn = dirname + "/" + f os.remove(fn) ### Initial rendering def initial_render(url, istext, height): global globalx global globaly ### Parse document object = renderobject() if istext is not None: parser.object = object parser.feed(istext) parser.close() else: parser.parse(url, object) progress(len(object.resources)) ### Prepare container documents for c in object.containers: c.w = pagewidth ### FIXME c.dump() c.normalize("vertical") c.cons() ### Optimizations style = parser.stylesheetparser(object.stylepart) parser.optimizer_style(object.root, style) parser.optimizer_headings(object.root) parser.optimizer_attributes(object.root) parser.dump(object.root, 0) # Rendering globalx = 0 globaly = height render(object.bodypart) ### Apply containers for c in object.containers: for d in c.children: for e in d.children: part = e.content dd = displayer() w = part.container.w - part.container.x rpage = dummypage(w) # FIXME: relative values dd.display(rpage, part, 1) e.h = dd.totalheight for c in object.containers: c.normalize("horizontal") for c in object.containers: for d in c.children: for e in d.children: part = e.content w = part.container.w - part.container.x h = part.container.h w += 20 h += 20 part.attributes['width'] = w part.attributes['height'] = h for c in object.containers: c.finish() c.dump() return object ### Final rendering def final_render(object, height): global screen global scrollx global scrolly global scrolled global zoomed global page global globalpage ### Display page off-screen d = displayer() # d.display(page, object.bodypart, 1) # page = containerrender(object.bodypart) # d.display(page, object.bodypart, 0) d.display(page, object.root, 1) # QUICK HACK page.fill((255, 255, 255)) d.blitter(page, object.bodypart, 1, 0) ### Display on screen scrollx = 0 scrolly = 0 scrolled = 0 zoomed = 0 screen.blit(page, (0, height), (scrollx, scrolly, scrollx + pagewidth, scrolly + displayheight - height)) if object.title is not None: pygame.display.set_caption(object.title.attributes['content'][0]) pygame.display.update() ### Main loop def mainloop(): global url global zoomed global links global scrolled global scrollx global scrolly global pagewidth global page global historyposition global history global navlinks global hosted global zoomfactor setup_navigation_panel() setup_navigation() if url == None: url = "" while url is not None: ### Reset navigation links = [] for l in navlinks: links.append(l) ### Rendering object = initial_render(url, None, 10) ### DOM access #print object.dom.html.head.title[0] #object.dom.html.head.title['content'] = ["FOOBAR"] #print object.dom.html.head.title[0] ### Rendering on screen topheight = 50 if hosted: topheight = 0 final_render(object, topheight) ### Page event loop # evil hack! zoomed = 1 loop = 1 activelink = None oldbutton = 0 while loop: ### Check for keyboard events link = None pygame.event.pump() key = pygame.key.get_pressed() mods = pygame.key.get_mods() if key[pygame.K_RETURN]: scrollx = 0 scrolly = 0 zoomfactor = 100 scrolled = 1 zoomed = 1 if key[pygame.K_ESCAPE]: loop = 0 url = None if key[pygame.K_PAGEDOWN]: scrolly += 50 scrolled = 1 if key[pygame.K_PAGEUP]: scrolly -= 50 scrolled = 1 if key[pygame.K_DOWN]: scrolly += 3 scrolled = 1 if key[pygame.K_UP]: scrolly -= 3 scrolled = 1 if key[pygame.K_RIGHT]: if mods & (pygame.KMOD_LALT | pygame.KMOD_RALT): link = "internal:forward" else: scrollx += 3 scrolled = 1 if key[pygame.K_LEFT]: if mods & (pygame.KMOD_LALT | pygame.KMOD_RALT): link = "internal:back" else: scrollx -= 3 scrolled = 1 if key[pygame.K_MINUS] or key[pygame.K_KP_MINUS]: if zoomfactor > 10: zoomfactor -= 10 zoomed = 1 if key[pygame.K_PLUS] or key[pygame.K_KP_PLUS]: if zoomfactor < 400: zoomfactor += 10 zoomed = 1 if key[pygame.K_1]: if pagewidth > 50: page = pygame.Surface((pagewidth, pageheight - 50)) page.fill((0, 0, 0)) screen.blit(page, (0, 50)) pagewidth = pagewidth - 50 page = pygame.Surface((pagewidth, pageheight)) loop = 0 if key[pygame.K_2]: pagewidth = pagewidth + 50 page = pygame.Surface((pagewidth, pageheight)) loop = 0 if key[pygame.K_f]: print "fullscreen" pygame.display.toggle_fullscreen() if key[pygame.K_r]: print "reload" loop = 0 if key[pygame.K_F12]: print "screenshot! (shot.bmp)" pygame.image.save(screen, "shot.bmp") if key[pygame.K_n]: print "goto next slide:", parser.nextslide if parser.nextslide is not "": #url = parser.correcturl(parser.nextslide) #loop = 0 link = parser.nextslide if key[pygame.K_p]: print "goto previous slide:", parser.previousslide if parser.previousslide is not "": link = parser.previousslide ### Execute keyboard events if zoomed: page = containerrender(object.bodypart) d = displayer() d.display(page, object.bodypart, 1) #d.display(page, object.root, 1) d.blitter(page, object.bodypart, 1, 0) #print "XXXVAL", scrolly + displayheight - topheight # XXX: next statement is part of the hack! screen.fill((255, 255, 255), (0, topheight, pagewidth, displayheight + topheight)) # XXX: next statement ensures right drawing after the previous # ones? screen.blit(page, (0, topheight), (scrollx, scrolly, scrollx + pagewidth, scrolly + displayheight - topheight)) pygame.display.update() zoomed = 0 if scrolled: xscrollx = scrollx xscrolly = scrolly if xscrollx >= pagewidth - pagewidth: xscrollx = pagewidth - pagewidth if xscrolly >= pageheight - (displayheight - topheight): xscrolly = pageheight - (displayheight - topheight) if xscrollx < 0: xscrollx = 0 if xscrolly < 0: xscrolly = 0 screen.fill((255, 255, 255), (0, topheight, pagewidth, displayheight + topheight)) #print "XXXVAL", scrolly + displayheight - topheight screen.blit(page, (0, topheight), (scrollx, scrolly, xscrollx + pagewidth, xscrolly + displayheight - topheight)) pygame.display.update() scrolled = 0 ### Check and execute mouse move events (mx, my) = pygame.mouse.get_rel() if mx != 0 or my != 0: (mx, my) = pygame.mouse.get_pos() if my > topheight: mx = mx + scrollx my = my + scrolly if activelink is not None: activelink.part.activated = 0 #posx = activelink.x # FIXME! #posy = activelink.y #d = displayer() #d.display(activelink.page, activelink.part, 0) activelink = None #print "mouse reset" for h in links: s = h.surface.surface x = h.x + h.xoff #y = h.y + h.yoff y = h.y print "///", mx, my, "--", x, y, ">>", h.part.attributes['href'] if mx < x + s.get_width() and my < y + s.get_height(): if mx >= x and my >= y: #print "-->", s, "at", x, y h.part.activated = 1 #posx = h.x #posy = h.y # FIXME! #d = displayer() #d.display(h.page, h.part, 0) activelink = h ### Check and execute mouse press events (button1, button2, button3) = pygame.mouse.get_pressed() if button1 is not oldbutton: oldbutton = button1 if button1 == 1: if activelink is not None: link = activelink.part.attributes['href'] print "HYPERREF", link ### Navigation if link is not None: if link == "internal:back": if historyposition > 0: historyposition -= 1 url = history[historyposition] loop = 0 print "*** GET", url, "FROM HISTORY" elif link == "internal:forward": if historyposition < len(history) - 1: historyposition += 1 url = history[historyposition] loop = 0 print "*** GET", url, "FROM HISTORY (forward)" elif link == "internal:clear": os.path.walk("/tmp/pyromaniac", clearwalker, None) pass elif link == "internal:url": if activelink.page is not None: newurl = getinput(activelink) if newurl is not "": url = newurl loop = 0 history.append(url) elif link == "internal:search": if activelink.page is not None: searchterm = getinput(activelink) loop = 0 elif link == "internal:reload": loop = 0 else: url = parser.correcturl(link) loop = 0 historyposition += 1 history = history[0:historyposition] history.append(url) ### Read command line parameters url = None hosted = 0 if sys.__dict__.has_key('argv'): for i in range(1, len(sys.argv)): if sys.argv[i] == "--help": print sys.argv[0] + " [--help] [--hosted] []" sys.exit() elif sys.argv[i] == "--hosted": hosted = 1 elif i < len(sys.argv) - 1: if sys.argv[i] == "--foo": myarg = sys.argv[i + 1] else: url = sys.argv[i] else: url = sys.argv[i] else: hosted = 1 ### Stand-alone kick starter ============================================= ### ### Used when run embedded by a host def kickstart(): url = "dynamic-desktop/index.html" setup(0) setup_navigation() object = initial_render(url, None, 10) final_render(object, 0) def keyevent(keycode, shift, alt): key = pygame.key.name(keycode) print "pyromaniac:", key, "(keycode:", keycode, "shift", shift, "alt", alt, ")" def mouseevent(x, y, clicked): print "pyromaniac: mouse at:", x, y, "(clicked:", clicked, ")" def redrawevent(): print "pyromaniac: redraw" pygame.display.update() ### Used when run from the command line if __name__ == "__main__": setup(1) mainloop() ### Pyntor integration =================================================== ### ### Convert pyntor options class Pyromaniac: def init(self, options): self.url = options ["url"] self.pages = 1 def render(self, screen, page, globalpage): global url global hosted global pagewidth, pageheight url = self.url hosted = 1 #screen = pygame.display.get_surface() pagewidth = screen.get_width() #pageheight = screen.get_height() setup(1) pygame.mouse.set_visible(1) mainloop() pygame.mouse.set_visible(0) def interactive(self, event): if event.type == pygame.KEYDOWN: key = event.key if key == pygame.K_ESCAPE or pygame.event.peek(pygame.QUIT): return "exit" if key == pygame.K_RETURN: return "next" if key == pygame.K_BACKSPACE: return "previous" component = Pyromaniac() parameters = ( ("url", "URL or HTML file to display", None), ) metainfo = { "version": "0.2", "author": "Josef Spillner", "authoremail": "", "licence": "GPL" } doc = """ Pyromaniac: rendering of HTML pages Usage: 'pyromaniac' -url Where is either a file or a HTTP URL containing a HTML document Display: yes Interactivity: not really :/ """ ### Program end ========================================================== ### ### ### pyntor-0.6/components/blend.py0000644000175300017530000000251010416470147016503 0ustar studentstudent# Pyntor::Blend - Blend screen # Copyright (C) 2004 - 2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * import sys import time import random import os, pwd import re class Blend: def init(self, options): self.pages = 1 #pass def render(self, screen, page, globalpage): screenorig = pygame.Surface((screen.get_width(), screen.get_height())) screenorig.blit(screen, (0, 0)) white = pygame.Surface((screen.get_width(), screen.get_height())) white.fill((255, 255, 255)) alphapart = 200 for i in range(0, alphapart, 5): white.set_alpha(i) screen.blit(screenorig, (0, 0)) screen.blit(white, (0, 0)) pygame.display.flip() def interactive(self, event): return "next" #def interactive(self, event): #if event.type == KEYDOWN: # key = event.key # if key == K_ESCAPE or pygame.event.peek(QUIT): # return "exit" # if key == K_RETURN: # return "next" #self.counter += 1 #if self.counter == 150000: # self.counter = 0 # return "reload" component = Blend() parameters = () metainfo = { "version": "0.2", "author": "Josef Spillner", "authoremail": "", "licence": "GPL" } doc = """ Blend: blend over the screen Usage: simply 'blend' Display: yes Interactivity: no (but contains timer events) """ pyntor-0.6/components/video.py0000644000175300017530000000535010416470147016532 0ustar studentstudent# Pyntor::Video - Plays video # Copyright (C) 2004 - 2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * import sys import time import random import os, pwd class Video: def init(self, options): self.videofile = options["video"] self.preview = options["preview"] pygame.mixer.quit() try: self.video = pygame.movie.Movie(self.videofile) except: print "(Video) failed opening video file" self.disabled = 1 return size = self.video.get_size() if size == (0, 0): print "(Video) invalid video format, need MPEG" self.disabled = 1 return #screen = pygame.display.get_surface() #screencopy = pygame.Surface((screen.get_width(), screen.get_height())) #screencopy.blit(screen, (0, 0)) #surface = pygame.Surface((screen.get_width(), screen.get_height())) #surface.blit(screen, (0, 0)) self.pages = 1 def render(self, screen, page, globalpage): size = self.video.get_size() print "XXX SIZE", size vsurface = pygame.Surface(size) self.video.set_display(vsurface) #video.set_display(screen) self.video.set_volume(1.0) while 1: self.video.play() #(width, height) = size #xsurface = pygame.transform.scale(vsurface, (width * 2, height * 2)) if self.preview: xsurface = pygame.transform.scale(vsurface, (200, 150)) screen.blit(xsurface, (412, 500)) else: xsurface = pygame.transform.scale(vsurface, (1024, 768)) screen.blit(xsurface, (0, 0)) #screen.blit(vsurface, (0, 0)) #screen.blit(surface, (0, 0)) #pygame.display.update() pygame.display.flip() #screen.blit(screencopy, (0, 0)) def interactive(self, event): #oldpos = self.scrollpos #h = pygame.display.get_surface().get_height() if event.type == KEYDOWN: key = event.key if key == K_ESCAPE or pygame.event.peek(QUIT): return "exit" if key == K_s: filename = "video" + ".bmp" pygame.image.save(screen, os.path.join("static", filename)) if key == K_RIGHT: video.skip(10.0) if key == K_UP: video.skip(50.0) if key == K_v: #screen.blit(screencopy, (0, 0)) self.preview = 1 - self.preview component = Video() parameters = ( ("video", "Video file name", None), ("preview", "Display as preview only", 0) ) metainfo = { "version": "0.2", "author": "Josef Spillner", "authoremail": "", "licence": "GPL" } doc = """ Video: play MPEG videos Usage: 'pod' -video [-preview 1] Where is an MPEG-encoded video, which can be played full-screen or in preview size Display: yes Interactivity: Preview/fullscreen can be toggled with the 'v' key, the playback can be controlled with right and up cursor keys, and screenshots are possible via the 's' key """ pyntor-0.6/components/background.py0000644000175300017530000000237510416470147017547 0ustar studentstudent# Pyntor::Background - Display a simple background image # Copyright (C) 2004-2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * import re class Background: def init(self, options): self.image = None self.colour = None self.image = options["background"] r_rgb = re.compile("[0-9a-f]{6}") m_rgb = r_rgb.match(self.image) if m_rgb: colour = self.image c1 = int(colour[0:2], 16) c2 = int(colour[2:4], 16) c3 = int(colour[4:6], 16) self.colour = (c1, c2, c3) self.image = None def render(self, screen, page, globalpage): if self.image: surface = pygame.image.load(self.image) screen.blit(surface, (0, 0)) else: screen.fill(self.colour) component = Background() parameters = ( ("background", "Image file or colour to use for background", None), ) metainfo = { "version": "0.2", "author": "Josef Spillner", "authoremail": "", "licence": "GPL" } doc = """ Background: fills the background with a colour or an image Usage: 'background' -background Where is a file containing a full-sized image, or alternatively a can be given in hex format (like 00ffab) Display: yes Interactivity: no """ pyntor-0.6/components/tpp.py0000644000175300017530000001021410416470147016222 0ustar studentstudent# Pyntor::TPP - Slides component for TPP plain-text format # Copyright (C) 2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * import re class TPPPage: def __init__(self): self.name = None self.heading = None self.text = [] class TPP: def init(self, options): self.title = None self.author = None self.date = None self.tpppages = [] self.abstract = [] tpppage = None tppfile = options["tppfile"] f = open(tppfile) lines = f.readlines() f.close() pagekeywords = ( "beginoutput", "endoutput", "beginshelloutput", "endshelloutput", "huge", "horline", "color" ) rkeyword = re.compile("^--(\w+)(?: ((?:\ ?\S+)+))?$") for line in lines: line = line[:-1] mkeyword = rkeyword.match(line) if mkeyword: keyword = mkeyword.group(1) if keyword == "title": self.title = mkeyword.group(2) elif keyword == "author": self.author = mkeyword.group(2) elif keyword == "date": self.date = mkeyword.group(2) elif keyword == "newpage": tpppage = TPPPage() tpppage.name = mkeyword.group(2) self.tpppages.append(tpppage) elif keyword == "heading": if tpppage: tpppage.heading = mkeyword.group(2) else: print "(TPP) Error: heading outside of page", keyword elif keyword in pagekeywords: if tpppage: tpppage.text.append(line) else: print "(TPP) Error: page keyword outside of page", keyword else: print "(TPP) Error: unsupported keyword", keyword else: if line[:4] == "--##": pass else: if tpppage: tpppage.text.append(line) else: self.abstract.append(line) self.pages = len(self.tpppages) def render(self, screen, page, globalpage): surface = pygame.Surface((screen.get_width(), screen.get_height())) #surface.fill((255, 200, 100)) surface.fill((0, 0, 0)) tpppage = self.tpppages[page - 1] titlefont = pygame.font.SysFont("Vera Sans", 30) textfont = pygame.font.SysFont("Vera Sans", 18) hugefont = pygame.font.SysFont("Vera Sans", 36) f = titlefont.render(tpppage.heading, 1, (0, 0, 0)) surface.blit(f, ((surface.get_width() - f.get_width()) / 2, 20)) i = 0 defaultcolor = (255, 255, 255) color = defaultcolor for line in tpppage.text: rkeyword = re.compile("^--(\w+)(?: ((?:\ ?\S+)+))?$") mkeyword = rkeyword.match(line) if mkeyword: keyword = mkeyword.group(1) if keyword == "beginoutput": color = (255, 0, 0) elif keyword == "endoutput": color = defaultcolor elif keyword == "beginshelloutput": color = (0, 0, 255) elif keyword == "endshelloutput": color = defaultcolor elif keyword == "color": colstr = mkeyword.group(2) if colstr == "white": color = defaultcolor elif colstr == "red": color = (255, 0, 0) elif colstr == "green": color = (0, 255, 0) elif colstr == "blue": color = (0, 0, 255) else: print "(TPP) Unknown color", color elif keyword == "huge": hugetext = mkeyword.group(2) f = hugefont.render(hugetext, 1, color) surface.blit(f, (30, 50 + i * 28)) i += 2 elif keyword == "horline": surface.fill(defaultcolor, ((0, 50 + i * 28), (screen.get_width(), 2))) else: print "(TPP) Unhandled page keyword", keyword else: line = line.replace("\t", " " * 8) f = textfont.render(line, 1, color) surface.blit(f, (30, 50 + i * 28)) i += 1 screen.blit(surface, (0, 0)) def interactive(self, event): if event.type == KEYDOWN: key = event.key if key == K_ESCAPE or pygame.event.peek(QUIT): return "exit" if key == K_RETURN: return "next" if key == K_BACKSPACE: return "previous" component = TPP() parameters = ( ("tppfile", "TPP file to use for presentation", None), ) metainfo = { "version": "0.2", "author": "Josef Spillner", "authoremail": "", "licence": "GPL" } doc = """ TPP: creation of slides from Text Presentation Program (TPP) files Usage: 'tpp' -tppfile Where is a usual .tpp text file Display: yes Interactivity: switching pages with enter/backspace """ pyntor-0.6/components/wait.py0000644000175300017530000000137010416470147016366 0ustar studentstudent# Pyntor::Wait - Wait for a key stroke # Copyright (C) 2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * class Wait: def init(self, options): pass def interactive(self, event): if event.type == KEYDOWN: key = event.key if key == K_ESCAPE or pygame.event.peek(QUIT): return "exit" if key == K_RETURN: return "next" component = Wait() parameters = () metainfo = { "version": "0.2", "author": "Josef Spillner", "authoremail": "", "licence": "GPL" } doc = """ Wait: waits for a key stroke to proceed with the presentation Usage: simply 'wait' Display: none Interactivity: waits for a key stroke, then proceeds to next slide """ pyntor-0.6/components/barslider.py0000644000175300017530000000240110416470147017365 0ustar studentstudent# Pyntor::Barslider - Slide in top and bottom bar # Copyright (C) 2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * class Barslider: def init(self, options): self.height = options["height"] def render(self, screen, page, globalpage): fillcolor = (0, 0, 0) linecolor = (255, 255, 255) y = screen.get_height() for i in range(self.height): screen.fill(fillcolor, ((0, i * 3), (screen.get_width(), 3))) screen.fill(linecolor, ((0, i * 3 + 4), (screen.get_width(), 1))) if i < 27: screen.fill(fillcolor, ((0, y - i * 3), (screen.get_width(), 3))) screen.fill(linecolor, ((0, y - i * 3 - 1), (screen.get_width(), 1))) if pygame.display.get_surface(): pygame.display.update() component = Barslider() parameters = ( ("height", "Number of pixels to slide into the screen", 32), ) metainfo = { "version": "0.1", "author": "Josef Spillner", "authoremail": "", "licence": "GPL" } doc = """ Barslider: Slides top and bottom bars into the screen Usage: 'barslider' [-height ] Where is the number of pixels which the bar should slide from the top and the bottom Display: yes Interactivity: no (but contains timer events) """ pyntor-0.6/components/nowait.py0000644000175300017530000000123410416470147016722 0ustar studentstudent# Pyntor::Nowait - Do not wait for a key stroke, but proceed immideately # Copyright (C) 2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * class Nowait: def init(self, options): self.pages = 0 def interactive(self, event): return "next" component = Nowait() parameters = () metainfo = { "version": "0.2", "author": "Josef Spillner", "authoremail": "", "licence": "GPL" } doc = """ Nowait: does not wait, proceed immediately, thus forcing a page break Usage: simply 'nowait' Display: none Interactivity: none (proceeds to next slide immediately) """ pyntor-0.6/components/titlepage.py0000644000175300017530000000452010416470147017400 0ustar studentstudent# Pyntor::Titlepage - Display text on a titlepage # Copyright (C) 2004-2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * import sys import time import random import os, pwd import re class Titlepage: def init(self, options): titlefile = options["title"] f = open(titlefile) blines = f.readlines() f.close() self.dict = {} self.dict["title"] = "" self.dict["subtitle"] = "" self.dict["date"] = "" self.dict["author"] = "" self.dict["???"] = "" word = None for line in blines: line = unicode(line.rstrip(), "utf-8") if word: self.dict[word] = line word = None if line[-1:] == ":" and line[0] != "#": word = line[:-1] if word not in self.dict.keys(): print "(Titlepage) Error: unknown parameter", word word = None #self.pages = 1 def render(self, screen, page, globalpage): surface = pygame.Surface((screen.get_width(), screen.get_height())) surface.blit(screen, (0, 0)) titlefont = pygame.font.SysFont("Vera Sans", 40) smallfont = pygame.font.SysFont("Vera Sans", 24) f = titlefont.render(self.dict["title"], 1, (200, 200, 100)) surface.blit(f, ((surface.get_width() - f.get_width()) / 2, 300)) f = smallfont.render(self.dict["subtitle"], 1, (200, 200, 100)) surface.blit(f, ((surface.get_width() - f.get_width()) / 2, 370)) f = smallfont.render(self.dict["author"], 1, (255, 255, 255)) surface.blit(f, ((surface.get_width() - f.get_width()) / 2, 500)) f = smallfont.render(self.dict["date"], 1, (255, 255, 255)) surface.blit(f, ((surface.get_width() - f.get_width()) / 2, 550)) screen.blit(surface, (0, 0)) component = Titlepage() parameters = ( ("title", "Filename containing title information", None), ) metainfo = { "version": "0.2", "author": "Josef Spillner", "authoremail": "", "licence": "GPL" } doc = """ Titlepage: creation of slides from Plain Old Documentation Title pages contain several entries consisting of two lines each, the first one being the keyword and the second one being the string to be used, for example: title: Pyntor Usage: 'titlepage' -title Where is a special text file containing information about the title, subtitle, author, date and event. Display: yes Interactivity: proceed/go back with enter/backspace """ pyntor-0.6/components/image.py0000644000175300017530000000241310416470147016503 0ustar studentstudent# Pyntor::Image - Position an image for display # Copyright (C) 2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * class Image: def init(self, options): self.xpos = options["xpos"] self.ypos = options["ypos"] self.image = options["image"] def render(self, screen, page, globalpage): if self.xpos[-1] == "%": x = self.xpos[:-1] x = screen.get_width() / 100.0 * int(x) else: x = int(self.xpos) if self.ypos[-1] == "%": y = self.ypos[:-1] y = screen.get_height() / 100.0 * int(y) else: y = int(self.ypos) surface = pygame.image.load(self.image) screen.blit(surface, (x, y)) component = Image() parameters = ( ("xpos", "Horizontal position of the image", None), ("ypos", "Vertical position of the image", None), ("image", "Image file to display", None), ) metainfo = { "version": "0.2", "author": "Josef Spillner", "authoremail": "", "licence": "GPL" } doc = """ Image: displays an image on a specified position Usage: 'image' -xpos -ypos -image Where and are the coordinates in pixels, and contains a pygame-displayable picture in formats such as PNG, JPEG or BMP Display: yes Interactivity: no """ pyntor-0.6/components/pod.py0000644000175300017530000001751010416470147016207 0ustar studentstudent# Pyntor::POD - Pyntor component for Plain Old Documentation # -*- coding: utf-8 -*- # Perl meets Python, hump me! # Copyright (C) 2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * import re class POD: def init(self, options): podfile = options["podfile"] f = open(podfile) lines = f.readlines() f.close() paragraphs = [] tmp = [] for line in lines: line = line.rstrip() if not line: paragraphs.append(tmp) tmp = [] else: tmp.append(unicode(line, "utf-8")) if tmp != []: paragraphs.append(tmp) #print "POD paragraphs", paragraphs podcommands = ( "pod", "cut", "head1", "head2", "head3", "head4", "over", "item", "back", "begin", "end", "for", "encoding", "cut" ) rcommand = re.compile("^=(\w+)(?: ((?:\ ?\S+)+))?$") rverbatim = re.compile("^\s(.*)$") self.podparagraphs = [] insidepod = False kind = None for paragraph in paragraphs: firstline = paragraph[0] mcommand = rcommand.match(firstline) mverbatim = rverbatim.match(firstline) if mcommand: # -> command paragraph insidepod = True kind = "command" keyword = mcommand.group(1) if keyword not in podcommands: print "(POD) Error: unknown pod command", keyword self.disabled = 1 return if keyword == "pod": pass elif keyword == "cut": insidepod = False elif mverbatim: # -> verbatim paragraph kind = "verbatim" else: # -> normal paragraph kind = "ordinary" if insidepod: self.podparagraphs.append((kind, paragraph)) #print "(POD) got", len(paragraphs), "paragraphs" #print "(POD) of which", len(self.podparagraphs), "are pods" #print self.podparagraphs self.scrollpos = 0 self.scrollbottom = 0 self.pages = 1 def render(self, screen, page, globalpage): fore = (0, 0, 0) back = (230, 200, 140) surface = pygame.Surface((screen.get_width(), screen.get_height())) surface.fill(back) head1font = pygame.font.SysFont("Vera Sans", 42) head2font = pygame.font.SysFont("Vera Sans", 30) head3font = pygame.font.SysFont("Vera Sans", 24) head4font = pygame.font.SysFont("Vera Sans", 20) textfont = pygame.font.SysFont("Vera Sans", 18) codefont = pygame.font.SysFont("Fixed", 18) codefont.set_bold(True) x = 50 y = 50 - self.scrollpos overstack = [] for podparagraph in self.podparagraphs: (kind, paragraph) = podparagraph #print "para", kind if kind == "command": rcommand = re.compile("^=(\w+)(?: ((?:\ ?\S+)+))?$") mcommand = rcommand.match(paragraph[0]) keyword = mcommand.group(1) if keyword == "head1": y += 10 heading = mcommand.group(2) #f = head1font.render(heading, 1, fore) #surface.blit(f, (x, y)) (x, y) = self.render_interior(surface, heading, (x, y), head1font) y += 10 elif keyword == "head2": y += 8 heading = mcommand.group(2) #f = head2font.render(heading, 1, fore) #surface.blit(f, (x, y)) (x, y) = self.render_interior(surface, heading, (x, y), head2font) y += 8 elif keyword == "head3": y += 5 heading = mcommand.group(2) #f = head3font.render(heading, 1, fore) #surface.blit(f, (x, y)) (x, y) = self.render_interior(surface, heading, (x, y), head3font) y += 5 elif keyword == "head4": y += 2 heading = mcommand.group(2) #f = head4font.render(heading, 1, fore) #surface.blit(f, (x, y)) (x, y) = self.render_interior(surface, heading, (x, y), head4font) y += 2 elif keyword == "over": indentstr = mcommand.group(2) if indentstr: indent = int(mcommand.group(2)) else: indent = 4 f = textfont.render("M", 1, fore) indentwidth = indent * f.get_width() overstack.append(indentwidth) x += indentwidth elif keyword == "item": y += 10 item = mcommand.group(2) f = textfont.render(u"·", 1, fore) surface.blit(f, (x - 10, y)) (x, y) = self.render_interior(surface, item, (x, y), textfont) y += 10 elif keyword == "back": if overstack: x -= overstack.pop() elif keyword == "begin": format = mcommand.group(2) elif keyword == "end": format = mcommand.group(2) elif keyword == "for": format = mcommand.group(2) elif keyword == "encoding": type = mcommand.group(2) elif kind == "verbatim": sh = 0 sw = 0 for text in paragraph: f = codefont.render(text, 1, fore) if f.get_width() > sw: sw = f.get_width() sh += f.get_height() + 5 surface.fill((255, 230, 160), ((x - 2, y), (sw + 4, sh))) for text in paragraph: f = codefont.render(text, 1, fore) surface.blit(f, (x, y)) y += f.get_height() + 5 elif kind == "ordinary": text = " ".join(paragraph) (x, y) = self.render_interior(surface, text, (x, y), textfont) if self.scrollbottom == 0: self.scrollbottom = y screen.blit(surface, (0, 0)) # HACK! if pygame.display.get_surface(): pygame.display.update() def render_interior(self, surface, text, coords, textfont): (x, y) = coords # FIXME: duplication of those attributes #textfont = pygame.font.SysFont("Vera Sans", 18) fore = (0, 0, 0) rinterior = re.compile("(\S)\<([^\>]*)\>") sinterior = rinterior.split(text) #print "SINT:" state = "normal" xorig = x foreorig = fore flagstack = [] for part in sinterior: #print "***", sint words = part.split(" ") if state == "interior-flag": flag = words[0] flagstack.append(flag) if flag == "I": textfont.set_italic(1) if flag == "B": textfont.set_bold(1) if flag == "L": fore = (255, 0, 0) else: for word in words: f = textfont.render(word, 1, fore) if x + f.get_width() > surface.get_width() - 100: y += f.get_height() + 1 x = xorig surface.blit(f, (x, y)) x += f.get_width() + 10 x -= 10 if state == "interior-content": flag = flagstack.pop() if flag == "I": textfont.set_italic(0) if flag == "B": textfont.set_bold(0) if flag == "L": fore = foreorig if state == "normal": state = "interior-flag" elif state == "interior-flag": state = "interior-content" elif state == "interior-content": state = "normal" y += f.get_height() + 1 x = xorig return (x, y) def interactive(self, event): oldpos = self.scrollpos h = pygame.display.get_surface().get_height() if event.type == KEYDOWN: key = event.key if key == K_ESCAPE or pygame.event.peek(QUIT): return "exit" if key == K_RETURN: return "next" if key == K_BACKSPACE: return "previous" if key == K_UP: if self.scrollpos >= 25: self.scrollpos -= 25 else: self.scrollpos = 0 if key == K_DOWN: if self.scrollpos <= self.scrollbottom - h - 25: self.scrollpos += 25 else: self.scrollpos = self.scrollbottom - h if key == K_PAGEUP: if self.scrollpos >= 700: self.scrollpos -= 700 else: self.scrollpos = 0 if key == K_PAGEDOWN: if self.scrollpos <= self.scrollbottom - h - 700: self.scrollpos += 700 else: self.scrollpos = self.scrollbottom - h if key == K_g: if event.mod == KMOD_LSHIFT: self.scrollpos = self.scrollbottom - h else: self.scrollpos = 0 if self.scrollpos != oldpos: self.render(pygame.display.get_surface(), 1, 1) component = POD() parameters = ( ("podfile", "Filename from which to extract POD information", None), ) metainfo = { "version": "0.1", "author": "Josef Spillner", "authoremail": "", "licence": "GPL" } doc = """ POD: creation of slides from Plain Old Documentation Usage: 'pod' -podfile Where is any file with embedded POD, like Perl source files Display: yes Interactivity: scrolling with up/down, page up/page down, jumping with g/G """ pyntor-0.6/components/fortune.py0000644000175300017530000000406110416470147017104 0ustar studentstudent# Pyntor::Fortune - Display fortune quotes on image # Copyright (C) 2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * import random class Fortune: def init(self, options): self.pages = 1 # FIXME! self.polling = 1 # FIXME! self.fn = options["fortune"] f = open(self.fn) lines = f.readlines() f.close() self.quotes = [] tmp = [] for line in lines: line = line.rstrip() line = line.replace("\t", " ") line = unicode(line, "utf-8") if line == "%": self.quotes.append(tmp) tmp = [] continue tmp.append(line) self.counter = 0 def render(self, screen, page, globalpage): r = random.randint(0, len(self.quotes) - 1) font = pygame.font.SysFont("Vera Sans", 18) i = 0 x = len(self.quotes[r]) h = 20 for line in self.quotes[r]: i += 1 f = font.render(line, 1, (255, 255, 255)) screen.blit(f, (50, screen.get_height() - x * h + i * h - 30)) if pygame.display.get_surface(): pygame.display.update() if pygame.display.get_surface(): self.xscreen = pygame.display.get_surface() def interactive(self, event): if event.type == KEYDOWN: key = event.key if key == K_ESCAPE or pygame.event.peek(QUIT): return "exit" if key == K_RETURN: return "next" self.counter += 1 if self.counter == 150000: self.counter = 0 return "reload" component = Fortune() parameters = ( ("fortune", "Fortune file to use for display", None), ) metainfo = { "version": "0.1", "author": "Josef Spillner", "authoremail": "", "licence": "GPL" } doc = """ Fortune: display fortune cookies randomly This component displays fortune cookies at the bottom left area of the screen. These cookies are dynamically updated every few seconds. Fortune files can usually be found under /usr/share/fortunes/$lang or /usr/share/games/fortunes/$lang. Usage: 'fortune' -fortune Where is any file containing fortune cookies Display: yes Interactivity: no (but contains timer events) """ pyntor-0.6/Makefile0000644000175300017530000000333710416470147014330 0ustar studentstudentprefix = /usr/local all: @echo "Installation: make install prefix=/..." @echo "Default prefix is /usr/local (requires root access)" install: @echo "Installing pyntor to prefix $(prefix)..." @echo "-- executables ($(prefix)/bin)" @mkdir -p $(prefix)/bin @cp pyntor $(prefix)/bin @cp selfrun/pyntor-selfrun $(prefix)/bin @cp tools/pyntor-components $(prefix)/bin @echo "-- libraries ($(prefix)/lib/site-python)" @mkdir -p $(prefix)/lib/site-python @cp tools/sdlnewstuffpyntor.py $(prefix)/lib/site-python @echo "-- data files ($(prefix)/share/pyntor)" @mkdir -p $(prefix)/share/pyntor @cp -r components $(prefix)/share/pyntor @cp selfrun/pyntor-selfrun.template $(prefix)/share/pyntor @cp data/pyntor-splash.png $(prefix)/share/pyntor @cp data/pyntor-logo.png $(prefix)/share/pyntor @echo "-- desktop files ($(prefix)/share/{applications,mimelnk,icons})" @mkdir -p $(prefix)/share/applications @cp data/pyntor.desktop $(prefix)/share/applications @mkdir -p $(prefix)/share/mimelnk/application @cp data/x-pyntor.desktop $(prefix)/share/mimelnk/application @mkdir -p $(prefix)/share/icons @cp data/pyntor.png $(prefix)/share/icons @mkdir -p $(prefix)/share/doc/pyntor @echo "-- documentation ($(prefix)/share/doc/pyntor)" @cp doc/documentation.txt doc/components.txt README $(prefix)/share/doc/pyntor @cp selfrun/README.selfrun $(prefix)/share/doc/pyntor @cp tools/pyntor-minigal.sh $(prefix)/share/doc/pyntor @gzip -f -9 $(prefix)/share/doc/pyntor/pyntor-minigal.sh || true @mkdir -p $(prefix)/share/man/man1 @cp doc/pyntor.1 $(prefix)/share/man/man1 @cp tools/pyntor-components.1 $(prefix)/share/man/man1 @cp selfrun/pyntor-selfrun.1 $(prefix)/share/man/man1 @echo "Done with installation." clean: rm -f components/*.pyc pyntor-0.6/NEWS0000644000175300017530000000443310416470147013365 0ustar studentstudent10.04.2006 - Version 0.6 ------------------------ - manual pages documenting command line options and keyboard shortcuts - background component can now be used for coloured (image-less) backgrounds - single-component presentations don't need script anymore, thanks to --direct - support for named parameters to most components - improved developer documentation - the pyntor component repository goes live! at http://pyntor.coolprojects.org/repository/ - new component: image, to display single images - new component: fortune, to show fortune cookies - new component: pod, to render formatted POD documents - tool: mini gallery convenience script to create HTML for PNG exports - tool: pyntor-components can list, inform about & update installed components 12.01.2006 - Version 0.5 ------------------------ - desktop file for *.pyntor links, icon file, and MIME type - add-on tool: pyntor-selfrun to create self-extracting presentations - new component API: splitting run() into init()/render()/interactive() - new run mode (--components) to list page numbers and involved components - page selection and full-text search menus - automatic export to PDF and HTML+PNG - new component TPP: supports a decent subset of TPP slides markup - some user documentation about creating and running presentations - up-to-date components documentation in components.txt - installation of documentation 14.07.2005 - Version 0.4 ------------------------ - installation is now supported - support to extract files from tarball first (*.pyntor) - therefore, no more need to ship pyntor within presentations - less CPU usage in slides module 07.02.2005 - Version 0.3 ------------------------ - new component: video - new component: blend (taken out of slides) - slides supports page ranges now - slides supports local program execution - path separators fixed for increased OS compatibility - program can run in windowed mode (-w) 27.11.2004 - Version 0.2 ------------------------ - now loads components dynamically - support for run script with component options - browser support and other improvements in slides component 23.11.2004 - Version 0.1 ------------------------ - first public release - project came into existance from real life usage: * LUG talk about Internationalization * CCC talk about Free Software in South America pyntor-0.6/script0000644000175300017530000000107210416470147014111 0ustar studentstudent# Example script to let pyntor know what to do # Intro: display some flags #flags # Background and blend effects (could also be ffffff for example) background data/pyntor-splash.png #blend #barslider # Presentation title titlepage title wait # Run presentation background data/pyntor-splash.png slides outline 2-4 #(could also be: slides outline 2, or just slides outline) # Video #video presentation.mpg preview # HTML browser #pyromaniac http://www.gnome.org/ # Usage of TPP slide files #tpp debian-packaging.tpp # Misc interactivity components #nowait #wait pyntor-0.6/pyntor0000755000175300017530000004370110416470147014150 0ustar studentstudent#!/usr/bin/env python # -*- coding: utf-8 -*- # # Pyntor - Python presentation program # Copyright (C) 2004 - 2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * import imp import sys import os import os.path import tarfile import tempfile import shutil import getopt import time import atexit prefix = "/usr/local" pyntorpath = prefix + "/share/pyntor" version = "0.6" starttime = time.time() origdir = os.getcwdu() chrootdir = None class Script: def __init__(self): self.slots = [] self.options = {} self.components = {} self.mods = {} self.index = 0 self.basepages = {} self.pages = {} def addcomponent(self, fullstring): ar = fullstring.split(" ") component = ar[0] options = ar[1:] self.slots.append(self.index) self.components[self.index] = component self.options[self.index] = options self.mods[self.index] = None self.basepages[self.index] = -1 self.pages[self.index] = -1 self.index += 1 def pagesplash(dowait): screen = pygame.display.get_surface() try: splash = pygame.image.load(pyntorpath + "/pyntor-splash.png") except: splash = pygame.Surface((screen.get_width(), screen.get_height())) splash.fill((200, 200, 255)) screen.blit(splash, (0, 0)) pygame.display.update() if dowait: pygame.event.clear() pygame.event.pump() event = pygame.event.wait() def pageselect(maxpages, currentpage, pagegroups, script): screen = pygame.display.get_surface() surface = pygame.Surface((screen.get_width(), screen.get_height())) surface.blit(screen, (0, 0)) font = pygame.font.SysFont("Vera Sans", 26) f = font.render("Page selection", 1, (0, 100, 180)) surface.blit(f, (50, 50)) for page in range(1, maxpages + 1): f = font.render("Page " + str(page), 1, (0, 100, 180)) surface.blit(f, (50, 60 + page * 25)) # Display headings (merge/unify!) for index in pagegroups[page]: mod = script.mods[index] if "headings" in dir(mod): headings = mod.headings basepage = script.basepages[index] xpage = page - basepage if headings.has_key(xpage): t = headings[xpage] f = font.render(t, 1, (0, 100, 180)) surface.blit(f, (250, 60 + page * 25)) newcurrentpage = currentpage pygame.event.clear() while 1: screen.blit(surface, (0, 0)) f = font.render("<=", 1, (0, 100, 180)) screen.blit(f, (150, 60 + newcurrentpage * 25)) pygame.display.update() pygame.event.pump() event = pygame.event.wait() if event.type == KEYDOWN: key = event.key if key == K_ESCAPE or pygame.event.peek(QUIT): return currentpage if key == K_RETURN: return newcurrentpage if key == K_UP: if newcurrentpage > 1: newcurrentpage -= 1 if key == K_DOWN: if newcurrentpage < maxpages: newcurrentpage += 1 def pagesearch(maxpages, pagegroups, script): screen = pygame.display.get_surface() surface = pygame.Surface((screen.get_width(), screen.get_height())) surface.blit(screen, (0, 0)) font = pygame.font.SysFont("Vera Sans", 26) f = font.render("Full-text page search", 1, (0, 100, 180)) surface.blit(f, (50, 50)) f = font.render("Search term:", 1, (0, 100, 180)) surface.blit(f, (50, 85)) #newcurrentpage = currentpage newcurrentpage = 0 foundpages = [] searchterm = "" pygame.event.clear() while 1: screen.blit(surface, (0, 0)) f = font.render(searchterm, 1, (0, 100, 180)) screen.blit(f, (210, 85)) i = 0 for page in foundpages: i += 1 f = font.render("Page " + str(page), 1, (0, 100, 180)) screen.blit(f, (50, 120 + i * 25)) # Display headings (merge/unify!) for index in pagegroups[page]: mod = script.mods[index] if "headings" in dir(mod): headings = mod.headings basepage = script.basepages[index] xpage = page - basepage if headings.has_key(xpage): t = headings[xpage] f = font.render(t, 1, (0, 100, 180)) screen.blit(f, (250, 120 + i * 25)) if newcurrentpage != 0: f = font.render("<=", 1, (0, 100, 180)) screen.blit(f, (150, 120 + newcurrentpage * 25)) pygame.display.update() pygame.event.pump() event = pygame.event.wait() if event.type == KEYDOWN: key = event.key if key == K_ESCAPE or pygame.event.peek(QUIT): return 0 if key == K_RETURN: if newcurrentpage == 0: return 0 return foundpages[newcurrentpage - 1] if key == K_UP: if newcurrentpage > 1: newcurrentpage -= 1 if key == K_DOWN: if newcurrentpage < len(foundpages): newcurrentpage += 1 if key == K_BACKSPACE: searchterm = searchterm[:-1] newcurrentpage = 0 character = event.unicode if character: if ord(character) >= 32: searchterm += character newcurrentpage = 0 xfound = {} for page in range(1, maxpages + 1): for index in pagegroups[page]: #print "PPP", page, "=>", index mod = script.mods[index] if "fulltext" in dir(mod): #print "MOD", mod, "has fulltext!" ft = mod.fulltext basepage = script.basepages[index] xpage = page - basepage #print "search", page, "as", xpage if ft.has_key(xpage): for line in ft[xpage]: if line.find(searchterm) != -1: xfound[page] = 1 foundpages = [] for page in xfound: foundpages.append(page) def pageclock(): global starttime screen = pygame.display.get_surface() font = pygame.font.SysFont("Vera Sans", 16) timestr = time.strftime("%H:%M:%S") starttimestr = str(int(time.time() - starttime) / 60) + u"â€" f = font.render("Time: " + timestr + " - " + starttimestr, 1, (255, 255, 255)) x = screen.get_width() - f.get_width() - 2 y = screen.get_height() - f.get_height() - 2 target = pygame.Surface((f.get_width(), f.get_height())) target.fill((0, 0, 0)) target.blit(f, (0, 0)) target.set_alpha(30) screen.blit(target, (x, y)) def execute(cmd): cmdstr = cmd.encode("utf-8") # FIXME: python os.system() doesn't handle unicode #print ">>", type(cmdstr), cmdstr return os.system(cmdstr) def finishexport(export, exportdir): print "Finishing export..." if export != exportdir: for f in os.listdir(exportdir): fn = os.path.join(exportdir, f) cmd = "convert " + fn + " " + fn + ".png" execute(cmd) cmd = "rm -f " + fn execute(cmd) cmd = "convert " + exportdir + "/*.png " + export execute(cmd) cmd = "rm -rf " + exportdir execute(cmd) else: for f in os.listdir(exportdir): fn = os.path.join(exportdir, f) cmd = "convert " + fn + " " + fn + ".png" execute(cmd) cmd = "rm -f " + fn execute(cmd) print "You might want to run pyntor-minigal.sh" print "or another gallery script to create the HTML page!" def loadscript(scriptfile): try: f = open(scriptfile) except: print "Error: Script file ('" + scriptfile + "') not found" sys.exit(1) script = Script() reading = 1 while reading: line = f.readline() if not line: reading = 0 else: line = line.replace("\n", "") if line == "": continue elif line[0] == "#": continue script.addcomponent(line) f.close() return script def parseparameters(params, args): #params = line.split(" ") #print params options = {} arguments = [] skip = 0 for i in range(len(params)): #if i == 0: # continue if skip: skip = 0 continue #print "arg", params[i] if params[i][0] == "-": for arg in args: (option, help, default) = arg if params[i][1:] == option: #print "option!", option if options.has_key(option): print "-> ewww, multiple occurence of", option return None value = params[i + 1] options[option] = value #print "value", value skip = 1 if not skip: #print "hm, which option?" found = 0 for arg in args: (option, help, default) = arg if not options.has_key(option): #print "got it!", option value = params[i] if option is None: arguments.append(value) else: options[option] = value found = 1 break if not found: print "-> uff, dropping superfluous parameter", params[i] return None for arg in args: (option, help, default) = arg if not options.has_key(option): if default is not None: if len(arguments) > 0: #print "-> set arguments", arguments options[option] = arguments else: #print "-> defaulting to", default, "for", option options[option] = default else: print "-> missing non-default option", option return None return options def main(script, fullscreen, components, export): """ Presentation preparation """ scriptpos = os.path.join(origdir, __file__) scriptdir = os.path.dirname(scriptpos) localpath = os.path.join(scriptdir, "components") installpath = os.path.join(scriptdir, "..", "share", "pyntor", "components") installpath = os.path.normpath(installpath) homepath = os.path.join(os.getenv("HOME"), ".pyntor", "components") """ Component initialization """ searchpaths = ["components", localpath, homepath, installpath] for index in script.slots: component = script.components[index] options = script.options[index] try: (fileobj, filename, desc) = imp.find_module(component, searchpaths) mod = imp.load_module(component, fileobj, filename, desc) fileobj.close() except: print "Error: Cannot load component", component mod = None sys.exit(1) if "parameters" in dir(mod): #print "NEW-STYLE!", options xoptions = component + " " + " ".join(options) options = parseparameters(options, mod.parameters) if options is None: print "Error: wrong component parameters in '" + xoptions + "'" sys.exit(1) try: mod.component.init(options) script.mods[index] = mod.component if "disabled" in dir(mod.component): if mod.component.disabled == 1: sys.exit(1) except: print "Error: Cannot initialize component", component print "Error:", sys.exc_info()[1] sys.exit(1) basepage = 0 maxpages = 0 newinteractive = 0 for index in script.slots: mod = script.mods[index] if mod: pages = 0 render = 0 interactive = 0 d = dir(mod) if "pages" in d: pages = mod.pages if "render" in d: render = 1 if "interactive" in d: interactive = 1 if not render and newinteractive: newinteractive = 0 pages = 1 if "pages" in d: if mod.pages == 0: pages = 0 if not interactive: newinteractive = 1 #print "XXX", basepage, pages script.pages[index] = pages script.basepages[index] = basepage pages = script.pages[index] basepage += pages maxpages = basepage pagegroups = {} pending = [] if components: fmt = ("Component", "Status", "Render", "Interactive", "Pages") print "%15s %10s %8s %12s %8s" % fmt for index in script.slots: component = script.components[index] mod = script.mods[index] interactive = "" render = "" init = "" status = "broken!" pagestr = "???" if mod: d = dir(mod) if "interactive" in d: interactive = "[x]" if "render" in d: render = "[x]" if "init" in d: init = "[x]" if init and (interactive or render): status = "ok" basepage = script.basepages[index] + 1 pages = script.pages[index] if pages > 0: if pages == 1: pagestr = str(basepage) else: pagestr = str(basepage) + "-" + str(basepage + pages - 1) for xpage in range(basepage, basepage + pages): if not pagegroups.has_key(xpage): pagegroups[xpage] = [] for pindex in pending: pagegroups[xpage].append(pindex) pagegroups[xpage].append(index) if not (render and interactive): pending = [] else: if render: pagestr = "---" pending.append(index) else: pagestr = "+++" pending = [] if components: fmt = (component, status, render, interactive, pagestr) print "%15s %10s %8s %12s %8s" % fmt if components: return if not pagegroups.has_key(1): return """ Pygame setup """ if export: pygame.init() screen = pygame.Surface((1024, 768)) #screen = pygame.display.set_mode((1024, 768), DOUBLEBUF) #print "SCREEN!", screen else: pygame.init() pygame.display.set_caption("Presentation") if fullscreen: screen = pygame.display.set_mode((1024, 768), DOUBLEBUF) pygame.display.toggle_fullscreen() pygame.mouse.set_visible(0) else: screen = pygame.display.set_mode((640, 480), DOUBLEBUF) """ Splash """ #pagesplash(1) """ Main loop """ if export: exportdir = export if export[-4:] == ".pdf": print "Exporting to PDF file", export exportdir = export + ".tmp" else: print "Exporting to HTML+PNG directory", export export = os.path.join(origdir, exportdir) exportdir = os.path.join(origdir, exportdir) print "Using temporary directory", exportdir try: os.mkdir(exportdir) except: print "Error: could not create directory", exportdir print "If it exists, delete it first" return #for page in range(1, maxpages + 1): page = 1 mouse = 0 while 1: for index in pagegroups[page]: #print "PPP", page, "=>", index mod = script.mods[index] needtopoll = 0 if "polling" in dir(mod): if mod.polling == 1: needtopoll = 1 if "render" in dir(mod): xpage = page - script.basepages[index] if script.pages[index] <= 0: xpage = -1 #page = script.pages[index] #print "PAGE", xpage #mod.render(page - script.basepages[index]) #mod.render(xpage) mod.render(screen, xpage, page) if export: if script.pages[index] > 0: print "export page", page pagestr = str(page).zfill(3) filename = "slide" + pagestr + ".bmp" fn = os.path.join(exportdir, filename) # FIXME: pygame cannot handle unicode filenames here # FIXME: therefore we have to use tiff # FIXME: but 'convert' doesn't handle tiff! pygame.image.save(screen, fn) #fo = open(fn, "w") #pygame.image.save(screen, fo) #fo.close() if not export: pygame.display.update() pygame.event.clear() origpage = page savescreen = pygame.Surface((screen.get_width(), screen.get_height())) savescreen.blit(screen, (0, 0)) reload = 0 polled = 0 while page == origpage and not reload: pygame.event.pump() if not polled: event = pygame.event.poll() if not needtopoll: polled = 1 else: event = pygame.event.wait() if event.type == KEYDOWN: key = event.key if key == K_t: pageclock() pygame.display.update() continue if key == K_f: mouse = 1 - mouse pygame.mouse.set_visible(mouse) pygame.display.toggle_fullscreen() continue if key == K_s: pagesplash(0) page = pagesearch(maxpages, pagegroups, script) if page == 0: page = origpage if page == origpage: screen.blit(savescreen, (0, 0)) pygame.display.update() continue if key == K_p: pagesplash(0) page = pageselect(maxpages, page, pagegroups, script) if page == origpage: screen.blit(savescreen, (0, 0)) pygame.display.update() continue for index in pagegroups[page]: #print "PPP", page, "=>", index mod = script.mods[index] # FIXME: update or flip? if "interactive" in dir(mod): r = mod.interactive(event) if r == "exit": return if r == "next": if page < maxpages: page += 1 if r == "previous": if page > 1: page -= 1 if r == "reload": reload = 1 screen.blit(savescreen, (0, 0)) else: page += 1 if page == maxpages + 1: break if export: if chrootdir: os.chdir(origdir) finishexport(export, exportdir) def cleanup(tmpdir): if tmpdir is not None: #print "(cleanup...)", tmpdir shutil.rmtree(tmpdir) if __name__ == "__main__": fullscreen = 1 components = 0 scriptfile = "script" export = None direct = None try: longopts = ["help", "windowed", "components", "usage", "version"] longopts += ["export=", "direct="] options = getopt.gnu_getopt(sys.argv[1:], "hwcuvx:d:", longopts) except: print "Error:", sys.exc_info()[1] sys.exit(1) (options, arguments) = options for opt in options: (optkey, optval) = opt if optkey in ("-h", "--help"): print "Pyntor - componentized presentation program" print "Copyright (C) 2004-2006 Josef Spillner " print "Published under GNU GPL conditions" print "" print "Synopsis: pyntor [options] " print "" print "Options:" print "[-c | --components] No presentation, but list needed components" print "[-x | --export=...] Export to or " print "[-d | --direct=...] Take script instructions from the prompt" print "[-w | --windowed ] Do not run in fullscreen mode" print "[-v | --version ] Print Pyntor's version" print "[-h | --help ] Display this help" print "[-u | --usage ] Short usage examples" sys.exit(0) elif optkey in ("-u", "--usage"): print "Run: pyntor [--windowed] " print " pyntor --components " print " pyntor --direct ' '" sys.exit(0) elif optkey in ("-v", "--version"): print version sys.exit(0) elif optkey in ("-w", "--windowed"): fullscreen = 0 elif optkey in ("-c", "--components"): components = 1 elif optkey in ("-x", "--export"): export = optval elif optkey in ("-d", "--direct"): direct = optval if len(arguments) == 0: #print "Error: no presentation given" #sys.exit(1) pass elif len(arguments) == 1: scriptfile = arguments[0] else: print "Error: more than one presentation given" sys.exit(1) tmpdir = None try: tar = tarfile.open(scriptfile) tmpdir = tempfile.mkdtemp() except: pass if tmpdir: atexit.register(cleanup, tmpdir) for tarinfo in tar: tar.extract(tarinfo, tmpdir) os.chdir(tmpdir) chrootdir = None for d in os.listdir(tmpdir): if os.path.isdir(d): if chrootdir: print "Error: presentation archive contains many subdirs" sys.exit(1) chrootdir = d if chrootdir: os.chdir(chrootdir) scriptfile = "script" if direct: script = Script() script.addcomponent(direct) script.addcomponent("wait") else: script = loadscript(scriptfile) main(script, fullscreen, components, export) pyntor-0.6/README0000644000175300017530000000326610416470147013551 0ustar studentstudentPyntor - Python presentation program ==================================== http://pyntor.coolprojects.org/ Description ----------- Pyntor is a simple pygame-based slide show program, which uses dynamically loaded modules (components) in order to create a decent presentation. Only python and pygame are needed. However some components might require additional libraries, like smpeg for embedding video or rsvg for svg images. Have a look at components.txt for an in-depth components description. Please read documentation.txt for advanced usage information; this README file will only give general information and some feature advertising... General Features are -------------------- - really fast start (try './pyntor'), will read 'script' as default script - comes with several components (documented in components.txt) * Pyntor::Slides: slide show * Pyntor::Flags: displays flags (or other images) in a loop * Pyntor::Pyromaniac: HTML browser (not good, but sometimes usable) * Pyntor::Video: run a video either fullscreen or within a slideshow * Pyntor::Blend: Blend effects (e.g. for intros) * ... and more! - run in window ('./pyntor -w'); default mode is fullscreen - presentations can be turned into self-extracting python scripts! Note: the demo presentation included in Pyntor is rather bad. For some better slides, have a look at: http://web.inf.tu-dresden.de/~js177634/beleg/beleg-verteidigung.pdf Future development ------------------ Some extensions like being able to mark some text with a visual marker are already work in progress. More fine-grained control should be possible. About the name -------------- Pyntor is a word play on Pintor, which is Portuguese/Spanish for painter. pyntor-0.6/TODO0000644000175300017530000000162010416470147013351 0ustar studentstudent(done) ***** urgent: port all components to new API ***** [0.6] use self.disabled, self.pages, self.polling(?), etc... (done) ***** [0.6] improved parameter support? => see args.py => integrate with pyntor-components => for varargs, use "" as option name and pass a list in the hash! (pending) ***** [0.6] "reload" etc. Legend: -- = just idea, -+ = on the way, ++ = basically done -- support for MagicPoint (cool format, bad implementation) ++ support for TPP [done!] -+ unified event model so screenshots, dimmers etc. can be global [pending] -- more demo components (like a pyntor intro scene) ++ automatic HTML+PNG/PDF export [done!] -- selfrun template in another scripting language (perl?) -+ selfrun ability to create *.pyntor files even from working dir -- unicode safety (requires Python and Pygame fixes!) => feel free to add something :) pyntor-0.6/outline0000644000175300017530000000145510416470147014271 0ustar studentstudentDemo Presentation ================= Quick introduction - Press Enter! (or Escape to abort) What is this all about? ----------------------- => Launch your favorite editor => Edit the file 'outline' - this file includes the presentation you look at! - read README to know about the markup style => Do not forget: - before the talk, run the presentation and press 's' on each slide - this will save a BMP copy in a directory 'static' - for i in *.bmp; do convert $i $i.png; done - convert -scale 500 *.png exported.pdf - save them on a USB stick or otherwise to be safe :-) Feature creep... ---------------- [img:data/pyntor-logo.png] [url:http://pyntor.coolprojects.org/] [exec:xterm] (press m to activate mouse) The last slide ------------------------------ Enjoy using Pyntor! pyntor-0.6/COPYING0000644000175300017530000004311010416470147013714 0ustar studentstudent GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. pyntor-0.6/title0000644000175300017530000000015710416470147013731 0ustar studentstudenttitle: Pyntor subtitle: Quick introduction date: 12.01.2006 author: Josef Spillner pyntor-0.6/data/0000755000175300017530000000000010416470165013573 5ustar studentstudentpyntor-0.6/data/pyntor.desktop0000644000175300017530000000037410416470150016517 0ustar studentstudent[Desktop Entry] Encoding=UTF-8 Name=Pyntor Comment=Viewer for presentation slides Comment[de]=Präsentationsprogramm Exec=pyntor %f Icon=pyntor Terminal=false Type=Application MimeType=application/x-pyntor; #FilePattern=pyntor Categories=Presentation; pyntor-0.6/data/x-pyntor.desktop0000644000175300017530000000026210416470150016760 0ustar studentstudent[Desktop Entry] Encoding=UTF-8 Type=MimeType MimeType=application/x-pyntor Icon=pyntor Patterns=*.pyntor; Comment=Pyntor presentation Comment[de]=Präsentationsdatei für Pyntor pyntor-0.6/data/pyntor.png0000644000175300017530000001555410416470150015640 0ustar studentstudent‰PNG  IHDR@@ªiqÞbKGDÿÿÿ ½§“ pHYs  šœtIMEÕ;÷‚–ftEXtCommentCreated with The GIMPïd%nÐIDATxÚÍ{[ŒeÛuÕs­½Ï©:Õ]Uý~Þ‡}¯¯_Qœ8Qüˆ‘#™ ;‚CÄ)?"!„øCð‘¿ñ…øŒøáƒ("(ùˆˆ@IÀæ!_‡Ä‰¯oçú>úÝ]]ÏóÜ{ÍÁÇ\ûT]Là^lG.õ鮪®:gï¹æsŒ1ç!þ·þ­/Ü|ôûß4¹»¯zÀïç#5†­Ü(£ çŽßúõÿ2Åù‡ýâGÿé߸²|vÔ¦ÖlÔšZ g€|ÏOèEXî%xÙùÐßùÙK?ìHÃ'Ÿø'sgñx÷øµ»„&ÂÌ@£@ ÷ðŒúÙ˽c¨/ùú~l²ý¡ÝÁ7ß.?´øä/ýBN›Í­'_{ÍúyGÍHˆ‰Æd)Õ|O™Ð¬öOМX»=Ù¼ú¹ö¾þ§³¶d˜œ¿|ûþ׿Þô'3'áykÃ6o^òfg’9€ Þ÷¾‡JÐ Ö6HãyÜ"›úh‘ØÀÐ £E‹ Ρ}ö—?wmúöý½_ýÿ²ûa ?ûËÿÚlúôÊýßù†—e'H¾yã¢o¿rëCÌI¸¸KÞõ’;ÌÈ´1jòÖFÓLƹ™Œ›´9ÊykÔ6yÜä´‘ç&m4mÚl&Ö¦‹fyËtAfMÊ)7ÉšQJ¹5Ë£œš&Y²”²™e&K–¬¡YJ)5Lff–-Yúâ…þ{΀ƒo_?|ýžärr•b6n°<œ¦£oßu/¢ä‚"ý)¹GY%ÒæH )lH‰ &C²˜A˜ ºÀ¶ÉÍöfVqïOæ ‰Ô4 L› ƒ*`&` É $áFЈöéûSOÿÇ·@§‘&™`Ž@ÀÌå*€C†(Kf#Tr&@ï­SXNp–ûS¶“qÛžŸ Ÿ­à]þùc€]ŽN`2Ò€ $À˜ Ip‡S‚Â’ÃE¸C4ÀÞã Xt˜r2Gi›œ7G&/(ËÖ³fÀŸHF‹ÈDÀ%HÙÀ5ûÍH@P f‰F*Ú¤Üé½PN€ Ýla Ú´Ùh𮼯çùž2€4ƒq„ð =t k/ŒDl0º9 Ò@ÖV a W\<ßG6$B«¤R5Iîô¾‡@¤pÌÉH¸ eòÈ03:@È]”Dƒ9P¢+ÈD‚ÖR"ÍÐý½—Ä:€²X‘£œ,3¡wxqÓ0’!åp8h$` 2.Ña2 f p1 p#²ÃÌ‘øABr 8dßÅžXÿúî§ A=@™S’`Þ$Hü ž¡4\–¼>’0Aèq¹2Æí8H23˜Ä‹; ”€²¨y½ñá1PJþÙ``ÕÈ43ÐK‰„²ôý€I—ÉÍtH0“ ¢ÈL4A.®›*x#t ÍP¿çÂIßsI`†dVzÁÌÁïcŒ™f‰–aÙÌ,É,Ð2•h4¦”hL4X´;’FÄ¿õŽAÂ@aVµë©›¨(DÕ{ŽB`,a &@JFkÿÁ?ú‡;ß;ZJ MrÂ`ÃkÓ\ÉL4Ñá‚LÀ`´¡ q¨õœ*¡ªß"IŠDy?éKÒTÔ°í—?ñ‘_üËËý“»oüëÿøôÿ+ùü&ÕøjeÞÄUã#ä&&’à"I$&È<¹«²‚ r=.C !#e Ò "Ĩ©÷™ÊÌ’I}gs€©Í¹Ÿ-vÒ(ï|ø+?·è«7ïü«ÿð}àÜ­‹!ÌäýÜ;gYvæ3g³‘ùªwõî0™Ö n@1ºd…´ÈA±ÝÞµO}Ìv?ú}UxðÚÛvøú=ž<|Ù@íÿ×z`2°xãE8€í­íoÞwùéëßÙ^î ðX™ÃQ ‰…ýã§åøñ^Xp]ÇÖ3Ú­1ò¨-&ªÑ”ì>È+»ç_úùOÿÜ›ÏË­½ŸážüÏocùì’z_¨/Xœ`öx‡ßzÇo>ÄÆµÝ­\ýÄK¿ð²Ñˆ¨È9Fcm¾ft®#—äƒN A&@¸z”J 4DŽvw‚nºŸ¼½'uЬâ„T †|®„ "¥ˆ Ò¼Ò‹ƒa–!G*Dñêÿ |ê3å¯}²/Žž-¦'OŸ8Ýýèƒ<žþ öÿøïû×V‡Sø¢ÃôÍǘ?ÜGÚlw²  L„ IA7TrÈdtŠ¡×\ü¯U½óH%ÄèÂ6P”§÷žÈu&<\ÓF„׿k†h^BZÁ¡A§Sôn¾‚àίý'Üù·¿§öÜF¾ñ¹_¹ýÓ?yå…|üC7\>™ÜÜ?zº::Ù?™ì<8Þþ‘“>Ä],p÷¯ãñW¿…é;Oà‹¤ p« \‰ -p‘PøDUUå''Pà•§Á¨á ?°ñîyHÍíÉŠ†g¥"ñ'ü%ÃõA/É`¡À*3 ’A ZÝF-TÜûÇxã7¿ÆïüæWñ»U€+?ñÒÖÍ/|rë¹Û?õ ོXž¬öÞ8z¸÷ÖôæíGøÌ}O*ŽP¤; …:%š<ΔD·zнàDk’ bt µ y2¶~¾L‹ƒ€ET˜I¢(Ö(˜Å“H0YA§D¥°¤Œ—­ZiÔ@½»]dÀ°åÄ'¯ÞÁ“WïàðoXæ·_¾5zñËŸyá¥ÿé^ñ'OöL÷çÔ4„îaFwGMLZb^©$CÈg8Š1 âà‡FL*„`i{ ©±vþør   ºX"o”AQRe"PÍ ÷úÚ2ï‡Áè0ú™d¸. $Ôîm§A9nqü虾ù+¿Õÿ±ÿV¼NJvãg~äÚ•Ÿúèõl5Ý’»ê/G~š…ñ&±<ÌhdJ óB‚äu~½TŒwÏîÍôÁ3a˜, ™ v”#F*ó[“d§¼Úáëo†unU[ ä<äbýŠXì‹#­Å$Qúrï·¿»¿ý dH hN™ƒ.ES,"’Ý ãµ}N€CtD2aê.˜KlÏO¨âíìÉA8¦È>«ŠPa`J"ìÖ¨}ƒ;) .C˜6’l›j<9”ZYÅ‹9@R¤»¹ÊvדÌZŸ/F…‚S£ÄA"iÈÑ)͇C5Eø )yœYV}^M+âUM¯™Õ„“ÕJsÕƒ#€D¸»¡Ä©8RË$)¤ A,NZSÕq£EB fÆ€`†Çm3I“ÇHp "ÌÜR’£‘ÍB”QrJDÜ ¯þ )rÄl£“åÕá”êúŠ(q:‚( á tH‡‰f„1y×ê(·zC‚hS†uQqoïšRƒ>|[î°!w‘YL-ArQ²@’” lBl\+b=FçQ®CÀ i£¡z-÷O轌l4fÉ(jŠ‘°NB eÈ=ÉŠñ<ÖíRÑ@%ÀRB)ξwU ?€uƒ&X©>ÎPÌ”½(“pH‘‰’j&g¹àž ˆü~Q”ƒ13*®& r†Ve´Ú?¶ðâÊTó”‹`˜Âeµœ ÑªOUžÅeUÊ¡`MHŠÖ®Sû€{y æ• ’É„ÀlUÊ‘IOffp¸¤ Tc3IÈrÏÎhÜ¡¤H¼êÕÎ î `MF¿èšÕÑœ)¹ák „®d_vðâë‘ÛðàPÜ*”ÈeÙ1©âðRjͱ–ë©s`C/Å DL3$xLqa¬Á,È€ïjB‡ ì° ¦r’Õ{4&É’$C)UÂW §Ú”×ÒD§³Åâì;Y)e`>§×ÂI ¼L Þy륓Á‹+þoH £%9cZsypU§<ú[Œè݆ M<‚QÖ? ù˜ûÙ"[›ÅQª@Uð›Ìs‹. Ú}`trù­:”Á{§÷½‰5-Åu£€“2$ZÊ ÐtË•Ñ(u ^Yíqê¢9+ ,(y,zJáß$˜¥1Ô¬`-¢«®ÔôfCUšVç`½Hê½›÷%H÷ºJüÝ$]@е²¦,–Æ”„¾7[«¡ÓÞ–òuOs ê TœN«îÍÐôŠb½L‹aCæSU 1ŒàÔ_Ó—š ŽŸÛ~%ïþ¥nÞ½gó‡ûf›Ù¼øNV Üãtƒž„–ƒ p“d±_#2ÐÔØ÷4!تPÓ J5þ%SÛ˜¯úQï"J[к M-èK¥yñ (‚›W/Pîeqo4 =£SVÓŠÐŽZ¦ñ¼ëúYYu•ñ¨vX2µÛ_~ôÈîy›_+/¬ný…OO鈇¯ß5%;¯ãeƒV`½(“Wõ®º¨jÖ&‡Á N“C³f9DÛ-–‰F ¬sŸëîAƒ@ <ïô¹6®nSR?»ÿŒk,&î¯Çñdƒi<:ZÍ õ][Óå´ûÊí¿ê]±G¿ÿM;ÎûýöäÖô…ýô/©9xõÎùnÞ5(ƒ£zQv¦ÎÀaa‹;kCŽÃC,Õ¾AÁÈ&YéúÅ#?©xdq=vzeá‹ˆÇøò6å(‹‡ûdƒð#ÏdƒÀÑÖó¨Ù_Ìg+¹‡r«Â†ÓÎÇnÿ¼Èdmâñëwíéý;hnž;¹þâGGlÊÅ£·6ÝÉ‚kÒ B¨J+X«j.¬Ó·:/ÕôG(>h¼ëêÖÕ:;Ö‚Ÿk郪vÂO<õsƶéÅËòéq]r9PÔühãÉÓ(.糕z¯Ruz¥Ý½ð×i$ÁÄœÓêpjÇX{eTF[“ N÷ó9ûéœîu ^o.ðÇ}¸ ' kF <)Ð{`K–%oÕõ¡¨Y‡rvjSUœ‰åš·2ö­H9¾pŽ’ûòé!+ÛdÈl¯Ïv4™0òÁr>ïŠ×aFmÞ„,“–«_êDLݳ?ø£·RF†¯J9ÿÒÍœF#Îî?A™ %™ úНuõƒ|—Šž¯õ^UÉ.RnýÔE¨4£†‹ëùÙ`Sœê…Ë ¶hœqAB¯ÚéŠQV)0ŠªH*y4®¬„ŒAsp©•úeŸ»£©ÞyÛìÂØÇWv,µÙ¦÷Ÿ¢;^ÄÐB&*k—§öس‹QUdÇ}4Þz Ášårøyêl· ̪žâ)¥,ð¾€.óa*ÝE§¿^M€ ™Qp˜Â0¡öj%R4¨_¬ä‹U2¹­Žf\bëvÑæµ]5;;~ë1¤"U¨U÷h0ì5²Š¥RÜ¢*ôØåƒ”~s¤ª¹+ŒÈ‰J ëgÔÏWêöO49¿E¦ã޾}O˽mݾTÎ=w9çQc³û{(‹UÕå Ýg@ eb´;‘'*çsyµB.;¨Äªax¤ÃŠÕ©ð ¦’A}WDs€IV×åûãúÙÒxêUÔ˜k]@¬[¾hh¦¡ä3rΓB5¼Ò¨ÜÉRʉÍd XNfØÿ£·0yî’.ž36f³{ÏØÏß½ó5ÌQ‹”Ïo o¹<8)icÜmݾ”ÛÝIZìiyx.IYøã(g0ƒë—e+È qê¿ièΨrVðu¯+Ѝ ‘,Á¥:• ™Ê³¸®8¡îÍZΦ^úbî~z.$¦ßy‚îhª«»>¹u9Íísµ?!…†EcGj¶;•Eç«'Ç\íM5¾t®k·'eëæå4º°'Xž€Ë~ð]# Û—:Še¼uE‘ §×ì•¶“±úºa¼ºr Èá0äŠa]qdÂÓ°‹§Áe4ù²—¾«fP%Ûõ ݳûéWv}¼{ΰ;:÷‘JLD³½€Z<9¦{¡I˜ß†Ùô;“2Ú™”Éåói¼³‘–ûS¬ögÀª£…!Ý¡¶ªË£ÏÔ®ª3Ž¤â­ ñKâ«Ð î2©ª7a˜<—,0³ÎRS™È#/êVή+ƒÿV7ëÎ, ¦ž©ÝÙTH#W‡Ç(K¡™Œ‘FV3öÇ XˆŸx;€~ŠåÞ š­‘v6|cg’Æ“±u³%Çs`ÞA}D¾†—ZÐCÓdd9×òI(I°6ÆüÔáPª£ÌPۤÑ2b£SëOHr¡,:±£¡­ Ö`ý'@sùôXyÖìîv]­‡\/Zf÷öØMç1<­VoÚ­©s°½(½Y=JÀF3H,–Ý,Æ’ÎLWä}‡²\^ê¸tí¢qyßíø ÷º–Žñž"©Q5Œy†×ïÅ®8F¨iœá])eѧQÓbõ`ï>ÁøâöjãêÎjrób3ºt®Y<9âüѺ£à2¶ Å›V¹G2%cr“Ëi±©-çþxžhƒ°;%UæTßÍQ¼„°Dü»ßüQ›Óе¢ ?C¼{x½>yùËLß”J_ØM穟-ë³»fö0{ø —wÊèÒ9Ï[ãÔÌ&¹?YÒ½£äaÁÈ:”Lôè¬_V [èH0©ñ§â´¬"jøIEND®B`‚pyntor-0.6/data/pyntor-splash.png0000644000175300017530000014374610416470150017135 0ustar studentstudent‰PNG  IHDRºº pHYs  šœtIMEÖ 19ªõƒtEXtCommentCreated with The GIMPïd%n IDATxÚìÝër#;—æ÷™|Uí¤­¤Ê¤º(äéÀŸ­P8?ÞgþcÀ†Ë3s¦©óšÄó\(t‰ÏÝ­mqP%yPXǃãT.Ë,yY‡ÌÂÏi’[“*9ÕÎÉx¶àƒƒýE±¿G޶«I{ùä>I®ÿöýâ}¸þÏ{Ó·»’.è¤Ò×¹ôuæôu!MŽd¸¼$_‡`Þ›TסqhFû›;Ë92lÎ…„Yl´ÊÂ…†ËI“24fsyým&™+ôs+ýÚJ÷5 €ËèœÌ4Sr¡“Ñö¾éhÄAgŸtÆ“$‚´ïüEø¸ ÊØ7)L*¼45ÉÕ^ÞII[WÈ;ž­Cüí&ü]U9__Ò­¤œ®Ñ›:Ó,û)Œ`–ñýî™ÑkÖ#¨%ç¼T„„ãVNuAuíêyfÎtWx-Ëà¿®Â`Ǧ ï#UžÕ®¶¯ÿäc/©º‚kî¢ÿþ«Ö"M˜£R~ªZòIk¿Žõ}«uÎ+Qëa.“7^ÿ׿¼ÁMTJÿéÖé÷¥T´JüW¯Çméo2g]ecMï»Þ{WÐßú·oýþÍë:ˆuì°NJÓ$6&E!µT–^ÛBúá ­Fœ(æ¥þÜÖò—n§á÷ë:&C®ÅWqÓkbÎõ_kéo3ã²™ÇMäªLkÞ‚7éW%ýÿý}ù~­G“~>„ë L3…ŽÂß߸ƒ²(ãh¿…ɶ AdUí;òíçà[ü®–|PÇNZš hž­r’m¥yµOŒq£×¾þÒRÑÚç»X­KîýªAâv«ÃQ,]×½˜«eáuãlÿŒP¬ŠAþ¶ ÉÁæ<¦Á¾ÏàœY«Á©ã*i»ÅÔÜ>)°K$ GÕRi&µ6NÚ– *Ю·(B»:/öƒ.Û˜˜m‚þ]­½Íáy•D €|²U?î«Á'Þu]TÉ'ΉÕìÈ¥¶ÇtÿŠW[!é?~’þ›ONE2Êc&ݯk­6>ŒJµ2gêÈ6«üK‡«Êj`2 o»¦ÃZÒd:«ó©ä*é³÷ZÒ½ÚÚø:«Î…ÿUan¦î$”}ðCÇ'çÖ¼ÉW^Æ·•l‚ÿÇÍaUL&ooüKÒdâÞõZÙT¡ãškÀ´Õë/”VHúRÖº‰zoá8¬·Òz;(öv“³ÚXR¼­ã³µØ'|%•/›I~:®Îù®¿7?ÍôŸüòoq/樔é6VMÜ>ˆé­²¼Ú”³’qTÖù0Rêªdª@ü3åVšÖµ|)mœ“§*€v51u¦¯“P%ÓÜ3ëJZ%ƒ/æÙEŒÀsVÞz+Ÿ³M@;àž<ÌÛUÿ~ÍÎâ·…ôßÝ9Í“` ö¦_^ëÊ«ªâbS]﫯¬Èвv<áñ$s–´%•uÌŠ§åeË™ä¶ÒÔ{­ éÞo…ëÅM¡û¯­´Û¢¤hÝy4Yý™¤µ¤_÷^wW“ÆÇÒŽBòÿóx/~ŸÀ˶“úŠ>^we؊˃þøg[%#x9‹Xj\×Í:,ûY7kÉmjÙÒ–'å~‹f|ýámïÅÜÌ]˜ ´(öAa=¥ô¿cEЇ&êîê£é$ü»ª¥¹7©¬µ)¤šª€«oWïÊZŸ›Rÿ&ð_‡¿¦ÎÀ3úñ/þ­oÚÀ™Á_%À«4@¥ôŸ?;}]¸ƒ×þùPëqãC™Y+ð?Ø>ÆŽƒ¢’žÿêJ XW2 ™vPÅ´ªCã1-Cäì7’j¯Ué´Õx:«eé´ô P 097õNeßÛ£¤;]¯²öšO㵘aæÙb¢¦ñ厎ԛuî Ó—Òk­UF&V›8š7¢Þ‰×~…ä‡Îùl•úÞ¤yæŽ8i곡 üë´"(NÊ¡"è݃Ĥú¨ª›ižÍzOáÿÊT¸Z~&ÕÏœk¼w¾–¡Ü_Š•–þ$Þ2À9'¸µ3ä!û¬žûþ†ú¾þ˧2)Ù¾_׺_ùÝBÖUj>4pð>홿˱Ÿ±µòa`jZJ‹i\¦6­]­ÕˆŽå, ¹MírI2bªPPIÚ\ób€Ûp š™s{†UºŽj>Úç²Ö]¨“Eâ¶U2…j„v‰Û—ê±Àok·%;}}fgú)+bÉò*)YÞÖû­=¯™).Þ“®ÞWÌ'q=„•T”µê)S®Å]ÚÕÂ…ëbµIÚU¢~oüúzöš§æú¿e/ñ¿½súŸö]µmmúõPk÷ë›{*bß}Ÿvݹ¿kÏzÕ±´Ìû}ãQ1HÛÖÚLÇ1%`:/5ÝÔzTiÏ1´n¦¬tÅ‹6åÿŠ ÎdÖ yîMû™_])Ó·É~tbÝŒNlã³èB~Oo’ÅgkQ„Áÿu-Õ?k•·뀉B!)x›¬Ò$wÏF.{Û-5[ÖqËÒ"¬õTÕ’V¦rRk;ç¤æ[\CG Õ2ë°~NŽk\ÃU8á’Ôémíž‘,°eõÏi,¦…ô_¿9ÝÍ’QÿUõßÖ'¶êë þ­7ª?~úö´ï<>6 Z yßé[ð~?·n:‘3ÉÖ’Ö^Ûé8ÊÈ…ôÓKËØ™ÈåゟP °Ž×ðµmé;†òÿÇôššS>ùº÷h(Mœ¤£ë°—¿Àž}3Jg1Á:/¥U-­ï½æ$IaUÿ¯k€¬’Å?1üyã+©vûDÀlÂbã¥ùø&ÅqE þE\îç+ü¿ñ³'.ú¼›ù$<¶÷^V{Ј…Wƒÿ‡µô0’àÿÚú@—Ñüëù?0Û‰„Â9Á~ÛïËPößÿ¿Vµ~­üá¼T{Ù¡±'Qý°DÉÑÅîím@ò$&º¾¬öq»@,*©Î8 °˜…sW)ÏEæ¤P4‹ïÏ®¡ÓãMóŒËÿMaw†]à–²È—(eú%ª}Øuò9$ÚlæŸ þÓ«ÈŽ$Ž]lw3é¿|9 þÖ!ø?ºý`òõL98µéø›¶äw;ùZG^×éteðyŽ[Yy ÞL%·•êL‹¥ ó¹+åt4Ó&’OD®åÿéâßÌ…|~ƒJ'Å~¥ÿ‡µ´e„¯³ñ±“0‹Ï‚%\–›Âëû$–+WÒýcýßÖTåÐöU±Mž6I€uÞž×ìÎ…ªºfÑÌÕf|ÁÓþ]C/ë*f’z=å`ZÔivzÛ¾Î`øD€ÞhóÎKéj•ý§Áÿ±UýÓDÅ©hzðV„²&Öó>Nø= gý‰„¾ŸkŠ»!X([”!rª·ù5‹8‰»= à£:iÂ$&$©Ú^pW(–ÿ›ò,ÿ¯[ €É”žëkáÃèÄÈ%úÈç@³îT¬€Ër[ÔúRš í÷&iÐrÉj }»² ;”Ô•´á9”WŸVûtš3ÆÚ¶Rp ¬gkº£™€Ö—™Ð;•àÔðuL/5[Ú-’O•NúŸ¿9MŠý‚iÙÿà-ýN”œ³"WÜo’éªÑvÆMg:L` y¯é¹¨c`>•fNªV’e¶Ÿ—sN7 [íUCí<›Å¬.wä¯)ÿ7åWþo:\üoN?cpÃÖN¬~+jÍãjÿÿçw‚¦ñú»7ɨ‹ÆÈ-]­»"\çÍbW¶õçØ’µß÷™½I›{’Yô¡ÌôÉ…{i·Ñ­*ªêH|t'úD ÿ$a`IoÏüY§¾&þí$M énJ›$éüê´œ†®×jëuß*û?µž@ç4;oªÃk{;sWK;ž6ðíY«³ѦŸOñ]?ä×Ó_Þ„wí•߃2Ýp“+f`üÊÿ¿Ü±øßÐà5M¬~-j-ÊüoBi"ÁÿùÇt®8àžƒ‡ñZ8¯/E&×qäÃ|ÿìù¸Þ“+³¨öÒ–éîÎíWüÜĵ8,$²O(ÙWþß+;=âðéÖ°J餻é¿¿•þóg§¯q~ï¶6ýz¬w7Ò©€úØVƒn`²âX‚àX‚£ëg»3¦Ù»PŸ†ÿO¾vÈâ‡/ã†í`Ð;½ •°ð ‹.㔋*³E—Ó|*χfSPJúuOõ¢öšf¶úÿ®£Ó þ?Oi°Î¹nKI_&ÒÜö#.¤ŠAÇÒZσ‰¤¿9Š—©L_KSQHÛmXt³%øe@! 0S¨â¨Ö$Þ#`œ¤÷“yÝÄ¢ÄMÎ÷ÒxL®!Øö÷&ž®àvP0}dH7}Mmúó¾’SýO3ÒCJémPDÿôsöŒ¯±ŽŸßâ& ìHŽÁž×ÝAm7.& fiç&•“+òXæc¶(5ÙÖZk?Òž^2õ.ÓŸ_Æ÷V+Œú•“ ÊV¡UËuõÿÇäß‹åÿç<»›ý¤ïWáPÕqš‡ç…ATèt?HòÞTìJ1/¦/…WéâjÿkiÍhå(Ÿí.öï 'MÝ~j×MQ«œÒN¾e¿ðFÒx&¾”¦I*ëV´xæµä0. €Wø^ŸüŸ=çgvíE˜”æ[ÜïôqczØÿzfðß·åaû?Ý‘o¯Ô?dû@³á_ßþùΞ;w|{ÁvÈ›Taw…‰¤ûÌæ¯æ¾%`³ ˜$ý¸ Q¿¦üß+îŸQù¿µ®Öý?¯“Ò\Hv.åÿé¹®[ÁUYRfïü¥)ÍüA:ÜÈÙÔ|آ̅@å’J•±ïCO¬%­x&½UÅ+¬£s¿–~<ÄûéƒÿkèqM®éÂul˜¶Gï‡L¡wC~è‘ úÜø6 ”•ýÛÓ2þ¾ßÏz:zOž;ý»õ¾ïžµ¬ã ˜÷§®)L1( i椉I÷?kÝf²­Úréôðh»r{ø¤‹þ¬C%ŨîÉêÿ¹”ÿ§™4?uCìLŸ…¤{“n9$ÈP!ÓçÂTa‘²‡‘ÿö÷õ˜Íš@-M/m½¢ŒâÚ$_¿Ï5 /~`šF·ÍBuçnýç†$ ZÜÑ2õûÝ÷ŽÛÓßqWˆàãèf3½Àö#i"¡Y(¯paÕRáoçöŸ4:*õ8¹ y:Y_ã0 0)¥e%ý%iY›Š FWËI¡¹j=Ä›²Pæ‹Ö6êQé¦üß[ÈfçVþ¿Jþýé‹]rÇ{LwQûy0Q˜ªb>Ÿ…UÆ­yMÊ8ï?ÎS[ülG¥fDÖëéRS‡‰ýý[Ä?®õ¹öÏÛÝìµ_¯¨RX¯èÛ÷ÀÛÄÜ$Fz…Z_pyÆö®ýmCæØ€;ïFk¿ÿ®-þ¼‹]m«0Ïíч•ćÎÕ,Fw’E(™Ÿ”RYôþê0ù&{îØÓ¤Æ±„DáBÀܤ^_s©˜ÆmT’Æ,« 9&V’~ftÜž%®þß$ÆrjL«˜dAþ‚ÆÆc IDATïæï®Žw_§»èù9cëp4σªŸîHT!S_kŸñë­Wñ³§Ù2¸Ò~ñàç˜Ç`ÚólS2`·3@’ØHúõ³æ¹\cÀ^éû¬7Ðzú´‚Q;•œ8ã}XG ðÖuhäî×ad{õÌßÝKºÂjyÒ¤ONZLC2 h%œN,{plѾä?Ü‘cb§ØÀ:)¥y~Ïj[k’ÁÖ1Í–€«äÆÌ¡ }Ó¤q«ÜËÿÓcûuÁ¨jí†O:ßMÇûÜeDš…4›©4EëžS" y¯Íº ’>½óϲ+Ï‹žÒŸùèçÙG¸/ú*Û*n÷7’¡ÿ®þgð¯^é笵šÄ„@úlRO7Ë6½ÕWjžµ÷’n©Nºø˜íRžñ$rºO-1oÇÔÎÀøÌ+üÉë¶Jþë:Œôÿµ–þíÄk-$ͦ¡Q,ËBeéT×&ïMµ7U[ieO쟒~š4ÛH_$ÝL¥Ù$Œ¤ì0àŽ>È¡Øy ‘cçhPB ®-à\H6Ìb'áÇJú>Íãú[ºp¬«Žá£Wé°ìwõXk±_v½)ÿ· Ëÿ}«ƒ7›2‡ñ£ÿ:^ï/ì TÚÖMãó¸épµ³Ý,X)L9sÅûÜ#k0¯Q)ÓTg”ç"× ºÖ~$8÷{ªú€Ÿ;óµfe˜¹ÚŽc‹²®÷W+\çŽãDҼʉ4)œ\éT¸¦çà QÕ¼OŸM71xn'rOPºÖÇóxì~Ü{}¡ `tAZY×$àÛ d÷Mui®—´S§¦¼à¢;ýɎȾ//p,ø°[ЯŽem®¤¿÷œØÏ §É‰¢,ÊÒ…‘œùá(N]›~=ø]–x#é_$-¶Ò×­´œKÓ2®Ðuxì¼Ã:$ òÜ×h¿žSØp^‡Æµ®½ÊòロÅM¡Ÿ÷^U<‡e†n³ý×*1£SIVì×ÇÈåYÔî,Ïè|X{ÐY+q›Hš—Òd"•EH¬z3ÕµW]I«êiµÀ6þi’“‘u¶ÓNV¯ÛÕºÖò‚I¿ÞàuobR¦Ìø¸7B?¹eû:fº‰çpS‡ €ÜKÿ»ž?U üûªíî&t¿M&nדh/Þ»ÙÔú±>L$=Ä?·1ˆS‚2}oMUÃOåSå‰þ¾z{z]´“/Mô6ëW5IÞtJžÓõV\ü€ÁþÐ}ÿŽòCæù`{FÇ-ÿ÷ÒÿW=Ûÿy&-æ¯ó +K·ËœVµéßB3±’ôO’¾¬¥/“XUàöÇÐô\{§7œü·§[ 8¸Í<1)®‰y,ãŠý–€SÎ#Îi1ÀI|(×ÞTލ¼®)ÿ×t~åÿi°ù…Åÿ>¤#Òþ}b!}º=~nJçT¥4=ìtW•éÏG¿{mâŸOÚº©³ÝT¬%ÝWï³;È[M?zPwEFnÇüžÛö¨¹÷šLB?i³É¿ôß:ž?ëžó|ã^wQØÙ¬Ôï1Óü¸ªõ3ÉTÞÇàë&ÞãnÏ¥v?iûw­¤ß¦Ü9¶·Íè~¯·•^d¾îH"”Ú¯ƒQ¶$.õêë[ÉÎ^­¿UG­û‚ëøØ[Èfÿû½ôÏ­ïÿ:ó7»PJ§?îJÕµéï1ð—ÂûùRK7ó°Pàî lÏ ø‡<Ü;“nx¢%½&…4ó!Kì½©È ˜Í}KÀf‘µ¤_#+¯Ë­üßZT:R옶ø®‘fÔí^OËm'’¾Ý–/.qŸLœ~÷Ë¿ÿ¬wçû—Bð¼ÔaÕO®—é{›&Çï=üñFÏ›þYgßLçÔÿAYóÓç»÷ZÄ©~›JÚf¾êûþ®QíÛBº½}Ûó½\”Z.¤ÕªÖø`ÚÄvéV‡9'ÚA^ÓWë”ÅKüë$诞Ϲ ÕvE!E×$sª½WU[Xع[®O$Ò$ÿ<^+M5Þ5L¾¼üEEëÞh6ðeÚÁÿЎܱò¼I›­ô÷‡Ã’ÿ¹ô®Xµ~VáªM²µt3 eõƒw °”þ§ßÛq€þËú7ELLæŠå°²}³%à½ö™É\\í¡W¨ë¼ÊB}+ø¿cu–wþ»J«¤¿½Ñ3áÛ]©íÖëßWáÝ4k 4Sr¹çO%šÎ’ù5qŸi…ÏjñhuhkS1 £þ›*ôMÆò Ú*$«wzþôY,J-ûeŸ‹M•RîI€ô™™öUF;eñÂÚÚt´ÿX5Ó\Òí²ˆSXÜûE¡É‰~S]›½:ž 题K]ÇÌÉU]y¾¦kŸûçþé?Ï÷ïùAMðÿ¯‡ ý½õ¨ÿ1Ëe©Y¬XKúw“ÜFZÆ$À€ßŽdFž{Ì;ÿÛu$’–kꤩõ—ü~ÈñÍpKÀ¾Å׫ZóEþ™õƒÕÿ}~åÿ­û ïwìW‘ïK§Éämï¼é´ÐÓ0ú,í§€ÜÄ{Ì2îh§ï«lŽãªÖb1Îk·T¨3‚ÿQrfš+ þ3ÞKuHð?tŽÿ[ùvWjµ®õcs8õd,IuôUX àc¯ó&Ñþ«§ëþÚS\ž<ãK§OŸÊÝZgíuέþØ¥*®êJ´ã‘y{uÉ®?§^ûTÐß™8R2o±ìÿï­àÿ·ÛâÂÿôfjÊ›$Àã&̽ë:<Öw>ºŽÍ‰¬‰=ó)ä’šŽ^áö™°Õc½†Ù¢Tk;È¥ÚtþÚŽã°+ÿW~åÿ•Æ»ü˜;%}Áÿwå›ÿíŸ×ŒN­â}ïGt=4-ÑãvÜ×÷ÞxM*¯Ù4ÜßÛ:ïѵžýíiGß.‹$ðb^ê·›b÷¬|ˆÉŠ1<›Òö´Y_åÏ÷É{‡[Í¿›$×ÏŽ>ì÷eˆ%>½óºGÍ:gÄ?×V!R\啨 $­çËýÖŸ_8ö-ƒû®2xKVü¯¼ôW«ìÿ÷Û"«…ך$ÀJÒ}LX,¼Óªª0;~\ÝÐì‰,Ýp*1cû-c¦ eb¹¸q!¨2 úÛ‹6óm 5³ñ@ú ËÿӅ;߰õßGÿ}ÜÜêÏwå.™Þ÷¹%þÒgA3 9b[.1|Yì[TõxFÿk…‘Æmëù?Éhûײtúý¶Ø½ßt¾v®Ï¦ö±žÄ¾ŠWXë ïs}§ý›?õtÊèo7Å»'ÛOµÁÜ•WÓ+®åb<6ºßõù!AÿɯïËAXäº Žc rÿ(ýSòµ¿ÝY,T×—ø!é¡ °«Ø9wXVéNô*ÅÿCü‘Ä@»¤=·`vB•G«]FfžÔÏû¼gçVþßî®[.¼}ð¿iÿSIß?x æçWñýù‘SŠjñ¡Aj]kVîJÎ5Æk?ƒÖ:Üùåû²Ð$ÃçQ8ý“›Ž$€”g‚²«¯òãÞsücð¿ŽqAê¦1C®}fÑóK_hµ¸¦‹òØhÿɀߎæ?ˆÌúß\{åÿÕVúçdDúË\Yo¹Öd‰ÿß»·}¥Ã KÇñ±ž,й£ÿn@ÃÔlXì÷™4é–€¹5¶éb€Þ+ýÃt™•ÿ§ d:ú³ÞC³°•’ö[&}ó}TþUWÕ‘@¾ €ØOªë¸½ëÞsSú߸›hð‚grŒ §oóðñ§)5‹n¸]Þ­o³R(ûOýí¶8¹.h·_-Pé ØOþîôk ý¹fGFípοœTUaïÒ&°ZHšÏò¾iŠÂé{œDó 0¯ þŽT>Hƒ++¬çäõ­1лþ@ëÿšybSe¶àÒí¦¹­ívšÌúf“qÝe®¿ÜÊÿM‡£ÿwlíõfÇ9 þÛ÷øo÷¢pú2Ýw¢Æ̤ Jlñ®×ž÷šqô¿ÎwÇ®ÒÿÆDãXøu:+󓻤¼§)YòŒÚ­UòXsã¼áõ}lmWPáHà/È“_dÝÁ¸=ãg¦£Þ½+@Mß‚÷a1½M¾üóHƒÉ´ÔÂèÚ¦Š 8ˆæN$dÔJ’<÷ü§¯•$yÚsYséĆ-Ã4ÇÆ¶©($ý•鞀Mù¿”_ù¥§{Îãí4%‰›V§$7óE¹[¹º¨ÌUs×kJlñŽ*Ûí<”Su×±gЦõ ú>¢Äïm½ÝèpAÀ1hª<Òè¾ið¿ÖÓéu0¸A ›«µ#òn—ª?÷&°¾±îÀ? HM¡„-]Yýûr\³ÛOûõ¶u²àáÙÑú3}ëé,wí ®MìðøO³¶œî+rªh/°cÊs1ÀœËÿÓNà×9 Ò[wNêVÇäKÆÇü¦xšÈ•Kž·[:×xÏk¯NÿÉÜÿ´éë—ovXkQc©(’ÀjÅ4€×Ö^[Çi\Õ-$.,ð7ë_ñÿ¹y„'ÿjn»Žÿ%Á^ú‘Œþ7 BÍlYj¦XºÞ*»¶Žstìø6Lgç«®“w4wT L”çŠÖK·Ï¶[^·ØAfý!³ãVxÓ¼ÜWÖäRþo­@iJ[ô¦Ç»éœ¤ €ÜK]á´Pþ«m |XÄaš–á"Kù:÷ÿ·o7v{»¯Èq—¢¾¾Ên{–¾zûúØú¿¿ü“ÈáâÔ+ÿI;±°_ë‡]Ô.Y/`Sn™QŒxÁŒ›i| øþE‡¬ÐŸ££ßo§+:¬ã"hþéÜa†¸ª2š; ÛŒÛöb€ÍqÛf´ kÊÿ-¿òÿtÉ„¯·¬£þÖÇ; þÇ2Û¢Œu3Òžó'W0ÞÝv?úŸûÖMÒ7mƒÆ¼å«+œn*j½Î Û[÷U\|¦–ÇÔª1µ¯›VŸæwú4$²Ë ü¶ƒÒþV€9äíD0j­÷Òýzß@Œýš-J-$m|øÝ\;p´Ó{Ágkëÿ’vb§Ð>Cüð˜O‘ó–€ÍƒdÏmN‹æTþßµ}z~ñvÇ»=úÿe4‹ªîïw§Ã’ûÜ:Ø\ÁxߨÃ4sáÙ^û|WÿOŸGé3Ì£ÿ›ØGÍ­2ñ˜& e¾sÑÈÚ×tÞÿçÙ¸/I\Á…Ûµ__éøÑ ´cmS‰…ödËÿÓ“s7ÐÌ…ß©n—úÛéãî^ïþz)Œþ77ëä¶·}Ž[¦Çv¢°€W±Âû}ùFÄvðÿ‰U‰Þüx§ÕžcZs«˜ºƒ ;WNTàýŸï“øì¬}þåÿµGHÇ<ú¿»ïãÀÄ£Æ5 `·Øóšûèµû3’´˜SúO £›¾sÕýäc¯ÃàÔZkgö@Gæ¶ @[ ƒæuÖ›}Ðy{!÷Ïd¹_i¯9¶n@4ylÑ¿Ë-œõEÖº)rlÀš-ÛUYìZÇ­iXî?>³îj˶ü?M.Ý,i,ßúùŸïÏ#š—X”ù/8†.0ð¨bÛgù—ÿ·“7t³Ì燕²9Ohþì¶,å6zÑu­ØM×~ú¥ÿ$r üÓôÍå—X̯õd³Ž`õhâáØS1–(¯’Õ“no.#((ÊBKIi¥Õ ’þ¡-Hk[Eë½N¼|r.Ó@nÛÚ-&ûUwsñhØ™(]r-ÿӪɗ Y©z¬(º¦Ãꜥ €OŸ.'é;•škÓš÷GÊý•îAŽþÏÅtF9ÝðíùøvfÚ±}`_|j}‰‡mÓˆÝ_êEV† ¡÷‡Á¢ ÂûÖ[ÜÀ´+3ŽlÅØî<¤%bëM^¡Ú|™ß–€éÏn¦HRµý¸c×”ÿ{åWþŸŽ:|¿![þ–=¯ÃUé¿/ÝxúUÀ4ý¹mÿw©fIŸäÉZO™>MËkžåŸÓ¶JOGÿ¿°ê? €Ñ_Ý­j¡Û:Ÿ.Л[°°EYS¦º¼°Žž›>ëšóP-áNÿvòz€uCiŸ!^e¸UÌ26¶U†ýžfE'é¯Õǽ;W…òÿ¶‡j7˜i`RÕ½õ±?8Þ“ñ5yE r¿R¸’ñnךyÍ&áš«móÿ‹ <åt\ Žtgµ¦ï9ÚÓZØÊ˜Àxü'Qx÷~ô6ðå‚Wëú»Y3i³Ý7Ÿn/+‹VNC‰˜8y¿oî×<6òÝ·õàÁ¿­û¦Ø­›áñ\Æ9Ví-…\·–bÃ:ÕÇ–]»:Î Íh{¨1/F7æNÊØ×yvÅþϹÁ&€÷‹¨Me×oÊ<~kW!Ý]àéé<ôñÆ2–ž&*n§çö÷Òµu¾1úO Û€¿]nO?=4àòñ©Íç{^¿YsÀ[Ø*o÷pºÀžTYH۪㸥™Ëg=ÍcóþíX@ý'; bó œ–ÊkKÀöb€M6øþ,¼×"¶E9•ÿ·G£?Ó`¾ùñNûv_ç#M¸Þ¢(à*ùmþý¤´MÞ¶ÚïKã\˜Pîb}xàßüÝ´¾lø<ÇŽÀbÿ¾ÀÝžñz»×=ñ"6 /ЬEàý¬FZ„Ñtë8˜ÖEkDìùç®}ìû¾0÷ã&g¿à¼9W™&“¸¶FFåÿUG ï×ñžÍFšp!òÔu8ñ~ Ïm¯àœ8wؾå~^H¿?¿mmWµŒ5¹Nà ®ÖÎÅù†ù:\8ðÅ„ž~Ù>p±÷Q¹`¸žNí9[þu~þH½¿pÚÚŸoÞbá"1Í´ŠÜ¶LWÙmª(ªê}Cð¢ÞoýWgTþŸN'ùBƒIÇ{è}ÅJ=ÀÓgªÇéÛ›]ðù('‡}<§¼s—äU_ÖŸI-G›\'p½Ã¾¤@oùø‘¹ýCFüû’µßq·‹Ë|,“âð;`?ë9Ð'“'ÁÎÉ*´¯\Íþ˜œ·,’ÎÎÇ÷ë¡5«ÿ+ÞWÊ(Η›Ó`¾Ë1÷—ÐйÓURÀõtÞl7zk–ÿHsÚ6ß,.÷.§Å¨‚ëô=Ö5;œ?QþO`TlW<ÿ$¶ÅÛ ßLúó¶~Ÿ9L/ó”¸Âíæ…?©²8°w$ >¶Ö1uçuèE’4Í™Øf:Œ8_–*”×–€é{˜ª{^à›Ê°ü¿=}Ýß¿ã};⃞®cÇšœÞ÷Æ6MÜîÃQJ³éåv¹‹òio,ÐvËNϽ¦?Ï8&$Æô7A¤õ|ÑÀEüΙRÐU`É úXª| œ ¿«;ék<¬'èlŸ§aÉëèÐ6£ ÛŒ[³Ü¶ì[ ðñ,3-ÿOËå¾ÞRÓý^ÏþÝ}²¤â¸Œ›ÛTãH<]ÛHi©ü·,MlZâÍ–ÛjhŸ»=µn1§m%0–ÞàYÑ{ÅÀYwL×¹}2¢²ë¹ÚjWïuçî%ç©wÕÿôC×ÿÐ:µ=cûßÍKåÜ6ÜÄ`²™ ЈdC;‚Ÿï`/¼×<Žô~tù¿µéüÿ¢`Ì”Ž÷÷— pð@-›rÅÛ½¶Ó“}væÉ wÕYmë†Ã@`4Wë_ÖUpV yO5A߃«ÊÏ/e=¶b¿õüŸ=ó؈nŽfKÀ{åµ%`ºbSðÖóì\eš”y”ÿ§¡´sÁàÿû¹˜Èûö¹Þ72cØàšE±¯p™Ÿ—4À|öóú4±¶®k €¾ èU¦žv°§Ÿ“µVÇ¿`®Ø‹¾ó`'ŽéÑãoç}î )ÓÈŽÁ2ó-›)b?ÞöÝå¸ú¿IzL·”ËÑñ~~û\}«»l$7ŵ%ÆÔ¢5~Y[tÂF$²½PíxõÿÙ£Ä]ßlOƒÚΠҺž·}qñ÷’6hb ïX?9ôî¼Ó;¶›c·|T>[¦ï¡©x˲±\Ëÿ+]_ (ï×~nR잯>Iv`Àæ94½‚sãŠî©˜™vGñ‚ëY’sjH\újY ¶‚~ÐQ½øl¤ sñáëz‚{š¼yòä:µcÈtŒ °˜ìƒMŸÇ©Þ)“ÎÏããÛeZþŸ.þ÷}I—ã#:* Rø¨ç®¥þ]ßjë=§=8Ö3"pyOë¾yüÖSŽn‡ëØÒ’¤Á.C|é=UçökôÍá³çÈúË÷­üi™Õ}ÎÆôhËuKÀæ8ÎbèçMÌÎqõÿº•˜LxÜ~DÇ{ra¿pÍüHï‹éd"› ça¿T‡€À(:MÏÝONOWÿO·´ŽOœ³h`¸¦ï±¼ô±s»FÁÎÊŸÕ~æö‚cvÎZùl ؼ´ àµ<(ÿ¯?vn¨%Ôtñ¿9m oïÛ¿ËHy%ÎÙ/Øî£à¼þ SIŒëŠí›üŸáfý_Þµ-€Ùy¥©{Ô:}Ùâšçz&%c½Ó&ìøZ§vxt¸øàH-ãóíyç.“M“øõÊ‹¦«ÿW>si:ýÿrÇrCt¼é¥WÛÙ¾†þÞ'Æ8‘¸W36’ì}£ÐÇ2Ê]åÔƒ´³#?4}v^ú|×süZÕ}ÕÖs€Û_ïìHå@Ï÷Úô»Ó-' £îéuûQ¿Oó³§ S֯ݱª%+ãÜÿŒÿ£TŽŽ7àí¸c 4ÝÍ—Îaê!OÖ·éã°¶Îõ®á‚í ômÀ± {VßÖG‡ËO»Þ_Þ†žÀ'Álø´S œ1žŽÜ¶l/Ø4«Õë,Ø”ÿ7‹ÿåPê[ÁÿçkXþ™Ž7€wz¾Ú‡˜¯¢ÀÓ,)Ãítv<%$FyÅ6Á¤ëþ¼ô[_ào'¢Îžî‹+¨è;î§ëùBÑÛÐsw&ÓR3å·%`£Y ðÇö•^0–ÿKy•ÿ?&ÿ^,(ÿ§ã à5²ck·¯aÅt—¬ó„ËÆÚ:$Fÿ[{[¹Ž ÿØ sßj¿ž xG‚Ùk½à\_0n§§ehH ?d/Ɖƒ1ÈqKÀ& ‘VøW¨×OWÿÏ¡üßÿK?ïß@.¢}¾ø$ÀˆžY\C/è{ѹ!0Æ›{H<Ø[ зUà‘af;6Å ùï/üqdû`ÐN ;>º?tû@ëhŒ†æF›èØ0—6¹P¨¤_÷/KOä´úzͤk|½eë?ÿ¯Þ·Y}ñý»Ø×u#º†H¼ ]¢´ŽÀ¥&v{ç'N?w£ý§ö›OöK5»Ž£îZ " þíxðoGŽ[×¢×òÀ_Æ@´:ïÒ}ó³ß,8•´zé f²úúskI›äß%óÏà]úo¹ÇW‘Ð8¦]9‘xQðH߆@î­D_ù¾Sﮀ‡-ÌÀ¡b³ÃíO%ÒÿØ'.¿ }êÅw=­ü“©gÌû?·ó0æ³qGžkí§|äïÓ^ °Yo³~þb€¹”ÿ7¼BùÿîÐ>€kèß]EP]ŸŽþ ÷êèÚ k]¥üæòýG~æró«IH*݉vÜúײÿ®ËÐóp)Á¿V>_(l X·®µþÝœÂ4€BÒŸ›g>¸¼×"Ãòÿ´ªáÓ'&ÉÀ›5$#z‹þJcšÀžö/© 0Š»|hbØØÚ „Áãirû“P×—}*œLEÑ“9lWØ÷÷WhkÇÞ8Üd¸%`º8^S`Ͼ¯Leÿ\Êÿ+&[×`>I\AãàÌTŽäQþûŸýõÖÚ9 åP’~ÎÂrÖ: uuùC“4쪜°çÑ܉ÿ_áz¸„Æ!ç-_ºà®üßçSþŸ3|[€·ªÇ4˜ÞÔUÕœœØÇS@àÒŸK]~Ç"CGOÛ:0ý‡Kˆ+xÅÓÆÁN¬øßþÚA¥ÿGÎÍ9AÝØå¶%`zzš-Ͻ†2,ÿ¯uXþ?2Þ‚O’¹'+Š+êߙŠO7®ë pA¿ÙÓ9û}‹ž X^4¼c^@³_ꮸ‚V{Wô=ÇçI’åŒÕþ»ÎÛU&2ÛÐ%Oµ¯ØlΨ̬üßt¸øßŒvÜtEý»Ø–Åx*H€:cÐs¶O{2Òß·ö@ëëÓ €ú pYì;2`pE€Ž'^r-\ÊùÈqKÀ& Ð,ø×zø÷åXþŸ¾ý/,þoÖpT±C Y^S°Ù1È“sŸH\Y€ßŸˆ>™,è øO¼‡ÆÄ…RèkhŽ-x4Þ·—Ÿó÷üÞœ¤[æüwm x¬Âãàåë,ÊÿÓ[ëpt‡íqàÚrß–y½NÂÿ-;Ø×”¶:LóC`º’ª à*vö.½ŒÞdÁÀìL2ÄÊò: ó­ã6 ø?¶aC×ñiÙúýMÀØ“3é–€U+ÿèòù"9¾÷¿Ô\TÊ¢ü¿áuXþ7¥Q€·kÏö#·…ò¯¸¦Q6ï/šc"@’æÜV pÝú*:3Fú‡¬àœFµjê‹ît‡©¿ä¿}ì_p_ãZ¸„êŒÜ¶Lñ4ã‡ßSÆ¡ŸœÊÿÓE — ÊÿàÍ ’6¾ IDATúå~údÓqʹÿtM-‚‹ »·qUPÎ&ÜW pÑý±8ÞúO;?²´^@ ;—¬P_èR±fšÇ_²öý£úi²@Çýëøü[•Î.`T7·-ûÜnû«šò“TePþß”Ú5=Là#;­±ÆÜr®ýïITÕÏ:÷¦y*4l €ôLÌf´Þ pÑÁ‹vgÎXàT"¡+`µV£ÕA… A$­/³(Ík6 ¿³·ýt€ÎÃl'ÊþÏüÜK‡KÙÒ-Ç-Ó$€“ô×êÈÆòoýI¤÷~¶l’»¡oÚ^.ô#Æò~µ¯"\=^î^€Î¼¦“8¨0‚òÿt¨¡,Y¸$.+ø·žÝ òít€y2?ü¬ƒm­»upn¿úÃ¥¶^šOœuœ³SÕ¯½âWð_]`ãÐl ¸Q[¦§r¢pø#‡fõÿåÿíÅÿÖt"àÝ;®µžU ù®¿Óá:7|N¬Ú÷ñríÆZO p-®Òo¿uWÒoÃæþ§_ßle3+Bt±ùá*´†¦}ðÖüŸ^QPÙš§AùÁh¾:Jù<¸Ú/muÒ‡eYHóøñf}eb…™nâÞí¾Þ?*øÿ€Àÿš‡E¹_à£Ûëöb€ÍHÉ&N…™Å7˜Cùûú˜Ò†øµ¿îe …u>r!ÛsÚ¸2iÛV4  ð^ËIsM*û,Z»‚ pÁI€ƒyæ6ð[ìmþvrâàm9i^„•Ú\Èjâ½æÓ±¯êãâ¾…ßs«Ÿki7û-}O0þQ¥¹¤/’ŠJZ¦O1+–Kùºõß×[Fÿ¼‡Í_{  ÏbŸ´¹¦ë›35‹‰€4ͳ¨L“¦ü¿Îþ𣠸´'WàhÐi‡sþÓu^#êL« ìDÅÁ¤”ÍCêøYü=«Zªz‚S‚æ½÷ùM‡Kߺ§Ù°jß^ °TØVoYJ7Îk: # Õ•ÿ§?³Öa‚Èу.KæE_Í#§¾òUH˹ël·rÐT4Uc—’ÀiªªZª3/ÿ÷:¬àû4ãq—ø'üÁb~éjþo•phý 0§½,¤¹ £ ÷#_-fêkÝLcÙt\ýßõWÖü¿G£Þ×8Ü-.{nX³%`­ü²öÎI“B²­´ZK›íÇoŸäu¸õÔÝ5lî \‘ÜC2§}búêe¡‰§dÿžúv…¤Ÿ°à´®µHûx#Øþ/­à[Ì©à €ëhÑßáá”N0õüvä½Î&Ò¤åÿ@=`J*]½Ý6åÿ}§Ã}Lðß–xÓéeß][æ° )Ì÷ßl¥_+é×£´É ´°½øßrI縸þBÆmnšx4N×4ö“êþ®Dv]Ñ©B•çZã¯òœ[HÖ×>&2¿íA€ÀÄþoÙY8¨(8ѧp}ﯵàÂ…úá×8ÓüS ûÂz…@®=Zaƒ2"ïß8\Ûâ07óýïþÑv:‡ÓÊ «ZÚf°øŸ´_4À…ö2`Òñ¿4]„à,ÓšN÷,žÇ1WLëZóI¸g¶Uþ«ÿ·+øX¾$pòéq0ÝÚ9ÀNçzÿÝÙùˆš*€Jãœ+ö©‰ _ïGÿÈGJþß3Оßݘ_Éå=™í·ôÉ1ùÈ*€¾óó‘ÚÉ¡ïì \\gh éöKÞNîìc1-5×ø’³S…žµ¤º_ÀÉtë¤Â…µyª,þgíÀ Ðæ]tðþܯ=˜³ŸÎçð²ƒØÖëÊíåÂI‹¸-àÃȲÄKÕZ”¡Œ{Õý?¶ÍßG–îµ¼OWà5[æ¸@.Z‡s'%{×Üeø(ÛÉ­ê«?gÍ4kõ%r¬H+Ag S~p.Ç÷šNBu³ }½Üïg¶ÿ®)pä©p0Wßž.¨óøÛŸ¹¶Žo8ò 2IÓIIw’¶#ièK™>—!±­Ci˜oËžßÝëãf´çw—Wà¥[Z«Ã‚§¥ƒ  pyÜ~;Þ"ï·¹+!ÿ±å´MgãL^— U&iu?žDÎÔ¼šåo6ÛØÇY¾¤s\Á.Öà·>"WðäßNgÌ5ë˜p0°ÃÓ¯q.L¸“Tm%«óÏŸxMËP¶Ú„ÅaŽÐ 8Ós˜. 3½ÂA³%`Ýql®]{åàÏw”×*âóϲZš¦³}lJÈ¥q–¿j@:/5éh¿ÆP0U˜ê¹öRµÍ? PÈô¹4M ©ª¤u•ÿÖÍÇÉ¿ï>ц×S`Ïÿ–vgÀ†ü[ ¶÷»ß%#¬;Óà}ḨaÔqûà³^¤è{Yk^„ ÿqKÃ’*‹SÛü}äÊóiiØ—Ûë+ŒYÆ),t÷ô¯ÄÊÁÀ5ô†|OÀ§0=ðVÒã£z· ×0 €JãØÊ1MR4SV«üwøê¼¦E˜÷¿Ú„ž1´å[ú5À'zξ2þÁsøÕÿMŸ¶Ã…màK{F&n¦![ìïóÜkåKY‡UUMZoÂÜß3âYÇú ¼öü¾ú°¢Üo ˜û(Ê{jÏü:€ äŠ}é2N4Ïã‰Âèq)iûëº×¸¹íN`ç¾% W\ïI¡·º÷Ù&¾µæeàY­¥Mõq»óœÛ†§Ó;c_àò}A¦ ÏÊHGW²üÛð­R\+ P–ÒíLššdy%¾–µ¾ÄºùÇmýoþ³äå¸Å2sÃ’NÔ|Ÿ![ÞÝy˜Í(.2PáÙ—qPÓÞFxâ¤O.N[ûU«ð×ùäv… Û&kŸÄÉyÛAÿÎB`éB2gYÀ)Tw.Êð^›žœg¤¶+øÒ>^ɾÀå'^5ƒÐz²tVt¬7pNëcGr µÄÍ"4öî¡þðW¤ß&!ø·¸âÿÃ*,þg­à¿/—òÑsÃö&³RÓx<®¹  om¶Þ.¸3T:•í$@ÓÏ(œ´œJÓB*צeUkêk¹+›p³÷i;÷$À®Í±0Ýó¦ý»êÞg±´~)Óoe­eûx›0À“sžéXuççk\à èëó_CG~ø'ú·£ëú/Éu•óÛóßgï6y2®“RZÎbùÕ£—_HuñþAëÔ™~Ÿú}IØFúµŠûÁZw²$·†˜¹aO- é‡ÇÄëº3„¾ÕyøzK¾¸de &ú¹<£šlR†?NRQI‹ÂË•’¹t›×â ï/h¯—rRh¦Z÷ñ<ºØ~!í%>LCY”qåÞT.jùéÇ J, Ó×Ò«pûEVaþÿXRKU« _,¨à®"Ðq¦#š®çKû¶þsñ›v?³a9kTÕ?¬ê0`1 eöõV²²ÖfR¼Kcî$}™z})m·Õßcl6ÕÓ­þrËÀ··[LGÿ¿37Ló›RîW­Mëáp-EsÇö ¾Æµ!€kâ’Âc ¼Âˆ¬ƒÓò0` Í…ßÏ˹Ž-y_ñÑfú&ÿô -ÞyÇ”›™´Ù„çvÇú|¹GóRY„ÝŸ$©ZKEUKs'_¼Oÿ¤”éëÄkܦ’×qѿ̃ÿöè:}oÉã ¸®€ù¿c[ü9ëùúgþƒÖ°ãÿe’ I‹ÕÓ„O¶^u)­‹·I’>•^Ÿ§¦i|ùÕVzXïWƒí;>–éu±U«Ä›¹a¡ Qa1ÀyìD¥õÚ±4ø¿eชçßÚ¢lzýØøMÕ&ù*¸H–eø»(Âßráwtñw} U-ýù ýÐáq|³y©rSk¥PJ_h\Ul¦d—ÎÝ|*,mešLkUåÛ%&Îô©ðº-õὴŽ}¼Mæsþûúwi~Çö½Àõ%veú.ŷ㺽ôg};ý©ö¨¤Lò±Ô~R„ŒQ„†w”V&mõ²d€“´(L7ehšøx[ïç‚íük}èµÇ4;üuÁà±¼)tÿàUÅÄ5ÖEXëú¸½¡ó\ºrF]Ç:ý©YÀ[$]Ká]+øoe6Ü+þüm%ýýÁ×¥ÓßM+í§4¿ãX’صI– ô”E8§åÆTLjm ©r…ü ³8¥LóÂtãL‹ø›…~Ý*.öw0­3ókwü„l0÷¸²Ànåýä?ì•£Ó¾rþ¡#ü/ škJÆ|ÌOӆ¤²ðò…TY\ Õ‡UŽMî``!©p¦Ò…Š‚‰¼¦NZNBÐï\xÍMËÁ6¡‘¯êî‘þÜŽkwKW†M ðé–€Í(Ê5T´WfmàÊÓRźëûŒ0pìJ4å{Ùj¿ˆì‡œÃI¡¹j­F€ í+Ù4¢séc è™”Ò4²®–æò*J©vñ¤z·Æƒ;˜Ýád*v(W)iæ¤yÖp #þ›*Œú¯·¡çmóý»¦î¥× sÿ+K¼åÃÆéÌjëþ~wNàœ~Âí _…Qù*–ùM'û¬±œ4Ûµ~7 `±alÈEüÿ"6»Q„:üÙl¥Uü9Þnm¸ëlèŒÊ‡n Èw3{ýÖÚÏ¥¼íÅÿ¾/™\çÂ|ùJ‡£Ç8#éÁûø|[è_î½b'×µÚ°±$š ¼ß¯ÿÔ¬ñPÄþÚ¤Õz¹¤˜N÷(’þ]óÚ>™6²Þ†@+Æš¯/¿±¶@à%Aã9Ÿë‹äÓŒl;ypöÔƒök[LÔ!s[–ûy“bÿï&èoÞCáöÁ¾bÀï}(l^«ªc#ÔÎd¾Wp—Îý';Üñ`˜•š®ë]å¥WÛ:h2¡\M@ÚÍ!/“ÿÈÎaáôÉI¿,<ËӵƸ­­)N ˆý±]ð§fîÖzpûAöìoRjn UþªéßÙ8MÚv§ƒ;…¤’µÒÓQê¾_š-0kíóÚ B½É‹>ù¼ÛoXÛáèþAÃÐj, ä½ßg†ÍïË­ï÷a‘f‡¿/hú¤[^Ë€^‡SCæ\ÀuuŠfÒÏÍáhöµ-‚z n>•úõ³>HŒ}QÛÝP :«d‡¦”¿½¾CÚjúž– èxï9n÷«›ißYø¸ÞÀ±Q÷cŸ Ì{FøÝ‰×Úठ„Áë Xò,™ÿ§§sò¬õ¦mÀb„]¿¯äü7 Ī}LÝíÓÞðFÃL‡£ÿ_è@We:/5ÝÔÚêié8ÆåûÒéßMñ<¦;ÛHãNê ŽíÔû°§uš_°ö‹‰º»¾¬y/ÞÿXRîeöôû,ùšôg¦¯;Æ"MüApwT³%àJûÒkì’‘óuRépz€ë3‹Á„oŠÆ¡•ɤÐ]ìå>Æ>€?òüÇøÚì¦Ýþ•ü{*i1§\mଧI;¸Nƒ^{ú©ôo:6ïüy» ;®æŸÜÖõ~¼íô“¯5àWoÿÞ]_7Ö†³Ý@°®Û0‹¸€Î5¬Šß.ÿg¸Nóyxx±ÈØ-oKÚ—ˆ×#ïËÐ]?ÔÌûOïÓo î×°®(¹ãÈ ºÏ^´¯c>½W2:Ÿ$Ú?ߎÇè¿ x;v"·žŸ;ÆmaV­âî Äe²%`Zp‰£a¦Ã F€ë4™•šèiõUãô[lï7 •Õ€Àùkúvóþo×®>p,p×€@ß÷|ÿ Æ¢§d +¨®½´6iea5Övêçî>70ð׉À¬A_ð¿ivßÙæ,˸^­Ë K¯ómòoBàºÍFŒ©?ç¤ï‹ðñ:öÒ$ãìÛ­t¸êÿÝD* Î&@@ýsðûFý»¾þä–:-[OÝŒþ[xˆý›¤ÖqZÀ©©–&løƒÓNL+Æ;×»«<ìgòï'MØæ,ÓY©©«(.m4¬)m|e¸j7ñД_âsïšL¦¥>ÅÇúJ¡€ó:Þà­ÃEÿæ’–KR÷ €ž_zæ¨ö±L=]Hïàãž gÍÞ®i³Šµ’uZ{úšgþÇ"åÖÃõ‚ÿªÕ@HÒ'JÿŸeQ„òKªèªi”Œ"WÍN71ÐØv´‹‹ãss[j©}àAÝ Ürnóo«Ó5&bÇ€À[D’Ñkgm@Ö¡ùgSŠÖ¬JßÌSÛZkƒäumÀÛ7u$ ìt^ãxíWýUëøâ €›R.^›é5â.äZI¯fˆ¤ÛXð Ã)B¤Çëî®Ô¼' 0°Û‡wìŠwÿ?[_÷¾@`ð“Dǃó¾€ýÉËu­ÚoÃóvGâ·}<Æ?Õ€òþ!9Œ¾äñÃ3¶ÓÛþÓyÿ_Üì/᜴Ôen Ø^üïÓ- ¡ à6>#ÖÚ'ë)·/wa‘Ç4 À€ùöéŽÿ ì$º GûÝ~÷é§íHµ¿)ëˆëé.©²túÓÓÚÏS;(ÿ?•ã°ãAߺ—ü·†¹qÒlJ#ñRË Ú0½f*uÀm 0Ö1¹´èµJGW Ó·$² þëxŽþÏz˜´ƒ^×<·ÿÑ·jà‘–áØÖCLg¥šçÚ½öÙi³ý¨ƒu$+NõJ\_Âà‚‚ÿçýOżÿ×r‰[¶ÿû¶ ¸@+XŒd:\ qÜÒ²™WÞìüÀùÍ'øÐÓõœþgÿMà¼û¿S[œxúÛ‘|ù†S#úË›ý<µ& °Õ‰µìøûëâ~çÿí ñ7‰WÕl x)[cÕ­ÀtÊM¦ûÅãî /. Ðôµª˜Øôœ_ÎñÛöçÚÝïm<é½’à pöSÅôâ¥^Ø€,Á9å÷_îÂökÒ>º=ãmŸQ´0ꆂ¹aïg: s'Ǽ…’µ:9§@»»rWEw¯ÃõPÇíË]©O“}âg«ÔÏËÛävŸî‡wçYJú~@àEO˜3ÍsçÍl hýôCÞÒ·»R³øq:OÍŸÈkø íŒt5k‚ÿwµ,ž^ƒcÔ.ÿg!Çü-yFô%ÇéfY,ü ªÞ£?×7ßÿG븞†$ÏŠù•ë?w;¼ÝÖÚzÏþöîd»÷û Š(ɲ½íó¾9IN’ÉÉ(ãsŹÜB†d­¬óïÞfw¶eQ$«dY,±“D‰EéûYK[ö¶DQ ø¡{ÂnpÓ9¶æ®õPjOëo<³nêüÏ´~,ÿ—× Xuòœ9³<äµyçgؘصNâ݆ºNây”NŸ[÷w!é»vz¸ÇÇkÏ539»ëý?ŒF#Úu€GT0ÏXÞ¿õgc§Ó¯¸{gÿcøpåÔ§›ïfâ[:ÎO>d½Ò=6†y}Í‘€÷¹}ŽKL‚Ö§ÿ_—ÜWÔÖ¬=g湓8§“ø&¸|Û„;mŸ îï³ÚsQ«Qÿïç²Émº‚½y/^3éá~£VG¿³žÿÙC]_9ͪüCiÂëíI·tÜnµ~ÔŸ¥óÿªš#ý™æ¹ F<Æ×+·l@ÅÜAܶ‹<€óóñÊéSëT˜f6À½˜ñqÌŽ@ëæ\Ö—Ü8®â]T5b<(¾Ò¯?Ó#iž¿&J;™»-ð¹<Œ6m–Xç‡Å¬óo×%£·§2H³Åj/ {Fù«ÝèøxAÀëÁ¡Óh(ývë—£ÂM  Ì—¿ÌŽ`Æþú|å´h ¼HiÄÚHºP øØ ÏÀøNïë¶A¯ Õtÿù†Ÿ¹*¥1í9€@ß+8³¡²‹ûz²GŠ?“¢¤Þúåš©fsÀ±V£²çô0Ú”^«³`»ÿþebe,M¦S Š…×L«Q¾Îènþ×n|8Gð<¿\9yõût59¼ÒjMó0w›ºÒ (Уç]éôµ”îg^·Õê~Ý)Í¸Ðæï)°m´¿Ä™oéø_éò’Ž?@ §•Z·Ã»Ñ€øø×|®WN³™×j½Á1Òjä¡Ï£]‘â:?X»;µÅ‘m}1¶ÒmXåµ¾7pº›ÿ]кp$Î¥ ä|ˆúýn}•x»óS´‚퀀 ‚ªí…žy#§ñHº»ójnqhÚ'C­–dÚ÷ò-Ü£]§m58ÛNQ`ù&@àÕ**³§S¿ë—£vüRÜýž/¹9Ìhä4im6@3]~ÔzÙ·9Á½ØÕñß)þ42ìÛ#£ §ÛŸ)ßZ-?éó,“ö2FžžŽ}Ýôª}<*iNšŸ$Ð:)àçO¯iç¦ÕzØ´êÐîì€îŸwuúnÓže2qšHú~ë×Ú*MÛÅj}6æ¾çºD³[?5Óü·µá”óøg:þÔñ ðÚÌ?³mn»sÈ{ÉÇ #M `í‡Ñk⎊%j)®6üÌU!Ç<0úƘ4õqªÕ:ÈvqéÛ,“Z›G"ð¸†À<¤rÚÇ´Œ:W¤9i~*——N—ùÏ?nýƒ}l;ÚÑi²:vS§’|ðºšÙˆí™˜M™½k5´›O¡óZþ±¯Ãßîô/vtú%éÂJ—ÚqÔñ ð™6nèÀkKM9xD%¹+øpì`@3òÐÞ™¸Ý¸°ZíNü’¢¸çߺSĦ[~–éþý7¾0šN£êVžêë¶ÝÀ¾qïžb¡´'Hs<Îõ•Óuþ³Qwwak@ Ë‹àeŸ531cˆúó.¬u–º3>FZß/ yfÚÚQ¯=@³­Ãß^׿¯cøéª`¯êxxµÎÿck·=ÿl´J|Î5óa”w&®« ?fq­4=Òƒè1Ÿ±)nFûEÇÿì9g5”ïý‘€¡“ç%ùë© æxf½iÍZ@ 1›{͛ϚGÿkôiˬ€å=î<‹šešíY¦Óé¥qÃ÷ ‡£ü‡Œ³±u<œM°Àìéônš û~ƒó9¶1Jþô*îöƒÈæ\Ñy™ £}î(Óéß7‚ña( <4ÎÍx Í{x$`;O¶ó~É-{2Îh&Íñršà=ÞÀ½Ì³$©®£~܇çMëå›™m§FÚ.Û×Në¶ÙÔi³=&u3”´ß¨ãA OûmKû·uø7ô¿ôf~/É-£ÒUôctƃ¶oÜbZAÛú{wt?èñÓ‡’®&V–#ýÎVûHÀâ ’—Öý¿a"àµÛÅjf€$ý¼óšîè]·Ü¦Ý3Ú¼/DètòCçûs %]N¬m7€@_ƒÝÑûMÓùö¯Ù ÊÒêsktÓ.Å›Òð˜k¯i¤oÇØH·1å§ÓOÿogißiL’œÈådµ9¤$yuö¶ÅÚBëùöÒ†’&œÂ8§Îÿ®?ŸÓtþ{]vD!j6 šûçïúé$¬4Y96yÓF§»Ÿ~ÙéÓè»óÅÂ'@8g´ÅšöXµZT/»C¼Q:©`P¤A¢¢ ½x<âAd&éŒ[àà„‘.äãjyH_íéÿ' 8—ö˜kí%ôÊ` «4ž®O·‹u;øPkuŒià=c1€ãU(^ ¥Âõ£·´~¼Î§ ª<€gäïñ[ ?Qwó¿ö©˜¢ý8 IDATìC€÷Œ%ŽÂy¯Ñ üûxºéÿîæ,¡À{Ç ÇQIÖH!J!œþr¢ÖGÿ¯¯Øüà™½í¨I½nRàTþF­çc €‚†eîxûÓÝÍÿn†Ü#€€çê¼ö?¤¯Sènþ7ký}0`ú?@Àó*‘4ÎÛ‰Ö>ŸpBQë›ÿ•Ü"€€çƨҥŽwíû±û{ó¿› £ÿÏâBÐÈäQ÷:ÿw »6ÿ3ÔrÏ3QTY¤cÿuÿ6ÿ»pže½†Eêô/ê~nþ72ý àÉJ]:Éš4õ^õcô¿½ùƒÿÏàuc£ ›6ý›-Ò€SˆÀ´õ÷›+Fÿ€¶‚$p¨‰úhƒœMÓþ§s©êÉÎÿ‹N@O0±A\”1©ó?—'œúßýo¯ýÿ#54A#'•F2fµÞÿ~qú#ÿv­ýÿ8¢ó +Éä.´•äL”3Q…$'i`¥Â¦NŒéx¿E:ÿ‹*ý=ôä³D¥ÿÛ£ÿeÉô€ð&:ðQ)ƒb”ê:é‡0F2?›Žò3­uˆi´¿éø/j©®ûÓñß5úÿiÌè?@x#B”~û!}¹– 'ÅpØî÷¦ÓŽJ¿ëczÍÒWV€ ¡é i®õÑÿ¢`ô ¼1JÓZúž‡¾}ÐÁçßÅÎ_Bë5—_ëÕgoýÙKºkýýÓ˜Î?@xc‚òF|y”>¼ƒÏÜú?ïVfÓÿ€]2εCû;=ÿ¥U’î[ÿ2qd€€s×úßîü•63@Àéü7Sÿ«ÖÿûpÅè?@À›Rk}ã¿ëipövMý—¤ÑÑ€€7Óùo¦þ/Zÿï+SÿÞVç¡õ©ÿ†¤@À›éüKiêÿmëï…¤á€Ñ€€7Óù¯$ýìü¿OLýx;ÿZÒ4o|¹¤óðf:ÿ>wþÛ›þ}CZOUúÔùoFþç­ÿw=Ê’x%@ÀÙwü¥´æ¿;òUH£!Sÿξó”Fþï´¾æb¥ñ˜Î?@ÀYwü›ÎÿBëGýIÒu)Ftþκó•Fûgù«íãȰæ à\;ý ¯´Þÿvÿý2±²–íþβ㕦ûW’îµ¾Ö_’†’>\1å àì:ýÊþvÇßoø™Oc«¢`Ô ð’>h:FÀ[*W±õU·:þ›\ìò8R{ÚóNTMÄtê÷ý^ó´Zß?Ûñ;+M&tüG2ÓæéÆßÁo Bë»Ï_ íŸÑr=FC:þ€#£ó<žétôc§£_å¯CtuI§ ð‚¾²£8ðd›:ý‡ÕŒ¤+FúžÙ1Ò7Ißz0ÅÅI9i<²²–]üŽ×év†Ù-Í’@@€ € @çª œ£¯ÖËY©R Rìü{UKÿv/]_9 ä1€²òÎÕ¿|—®¬¢ä[-€(©’ô-ÿýš¤y  ,¼@Kx˜àèþRxå)zùy1JÞKŸJ#¦èrA¹ùðj˜àè~ý&ÉH¥“ +¹Ö—$}›J?Žð>qÃWÈ_À{-õ5Þ2òð<Ìpt÷Qº¦?û å.=QiƒžßðAÒ<¤5¾Ó0¨¹x§å ¾Æ[F^èézQ§z}XÊwÚOµô“¤å ¾Æ»A^ž%^D i—Þvc²™ªw¬F@¹¨¯ñ¾ž‡ÎÒW6¤êk—< 3¼c%Ò \€| àMW,ÎnnOZIÄïA¹ \€| x},x¦›¡ôidtYJC'Y“ÎÍ]xég%ývõíD –F6j¤ 'ÉÄt]u~½“œBUš¨ *• 6¿¦âj —TGi¥ÚZÅNpŠÙ(§˜>Wë:ŒÖƒi®ÇKªUxëéKº<ÆÀDLPÑJ¿fíg¥y”*c%óôëür‘¾×õ¦{(MôvŽ;{ôìÛ¨a«l7ù­–Ti.ÉÛój¶[I…‰r&•o+ÉærÝ|Iëe]ZýÔ”³JFÞô3¶}êrqŽõÕ1Ÿc§.Ë}Íã奄~mçXæÀ½{)}kÃxçþÛ_÷?Hÿå·JÿQ­ŸFÒ¹23¤1ÒÈJƒBš ÏéŸ3é¶~|æý¯×AŠÒ¢ŽiwÞÜjþ¼¨¥¿ßI£VCh`¢nŠ I×¢B:ÎçöNú&éëïm$M¬×…•ʦ͓ß?(}ÍqA15d\”¬—¢ª´°FÁ>¿¡2¶A—6jо­Ò£ù{s-Í÷ ”V1“v4^Di!+ãžV™ô!]þRx¥†pììÒÔäMù⃠ækŽQò1mþ•òˆ‚jɆ ÚI¾t_‹Ï¯3(Ò}™ÎÒ÷Mé7ÊÓÁ½×Õxs{:FÉ{éïÓõÏqpìøÝ>§ç¾rðÁE²sGeÙ8©%/¯º‚s/’fÇöQ^ãrÙÿYþ!´þGŒoÊ[Nï"J¥ŠÖk!iaxyJ}írñ–ê«§<Çú\–ûÇϱ¾þì¼ÆNª}¾/qýwC~›JîòiõÖ/Ϋ4«v•Z÷Æ©òÒï3ix`½Ø§¶ÓSô=½_ò9vª{÷Åy lúL!¬×Qé^ü±Üxÿgº±^c›Ëx§mìó½+¸w—ÖëÚåû´åšþ9=¬\ô© Kß§^.¶gàyôÿ!}½Jÿß>}½x˜ñB”jBÊàÖH£Òȇ 9ÌW!ýí÷¨¿~2*ܪÄÄžÆÁ§³{ätSÈn\”³éçuúª½4_H¿ø¾¹£áò°Dé5š/ßêtHé³Z#Y+5ý _Ine¬—>­!n%}*¼Fù××®#¤Š>4ÑØÖµ˜|-Íu5ƒ%¡–ª{éW…'m$Ó—tùõ›ôõ£TºNŬ”&w³õ|q•+rks#ÚK‹*]sûlTØü ˜IõÜËMÜÎKl®¥pMæ•t¿xøpn¿Ï¥“†eúønó²öÒ·Nþ>4 öýnŸÓskçÁy]äYFuH£uU.ÛM:¥¼^R¬$M%^q”Þó÷ÒºI?Ÿ™fÇöç­TÜh­a·,O¡SÎ㪵֔w—‹R“µ\Š)”¯Ð8U¹8Çúê˜Ï±>—å>äñs¬¯ÿñSrµô×ù´~߇tí¿GéSTëx™jÉ WmŒØº®ïSéïù|=£6Âsô=½ùìï˽ûÇ­t3®ÆR0«Ž{Ì}ß§ÒïæÁÙ½4¹”l±ºæ&mn§Òñ°×™Ï$3Iéì[×¢tw/ý÷ÅþrÑ·6<¬ùö3h40iC„Naõ!êû×uþûýhôqdÖ:ýw3¯EÖ¢mFíÌk4(¤r.••W58<ÓÞÍ¥?ï¢bL “öh_奟­šo  eMjÀÜÏ¥y ˜éè”ú cÒï.ji¶HïW×é5j­¦ê4k$×T†ùkP¤‡E]¹ #ãXXE}²iT&Äô™æù:|®÷9Öç²Ü‡<~ŽõµZýYéOi\äY­ŸV)}nï£>^=rÁ™"•yµ Ì„(ý¬ÓLIú|aϪÌ=GßÓû˜Ïþ¾Ü»ÚHÿz/}©ÒëI둃—à„(¥é<Õ/M¹mêÚùE«Ê«Ü4 UꃨÝÿPJ›_ãþNdßÚð ðÀÂK?¦~õ@3«µ(͈Úÿr#'ëÿ_Tº›¥Be̪0Íï熆³©¤ Ë4úPßÞõ¹Áï}*4±½ @«]­¢n\j4ÍÒ;é>¬*,éáÃ}“Oyêв£1O…¶ ©áu¯´¾x“¡¤Q”^2~Uh 'ùZšO£Ê±—+ûìãäò4´Ù"W¨¹ÑTí¹–µë é«Èið”Ê£OéÒÜËEò憛•t-i"¯Ë<…k:—nï¥Ylu[ßc~âøzõ*”‘g÷^£-SÏ‚R¥ÞLÕڥݠöyš­ ›®Ê†§¤Á¾ßíszvMä5ÌÓ%çMÞkN¶×Ë5S‰MÞk#ÖK×Îk\¤ëž-žŸfÇæ•Êø4§Q8ð÷y¤§ÌS1R'ÉåQƒRÒm·^¯8bpªrqnõU<òs¬Ïe¹yüëkc’þô¡~Øp¹£P=¡œÚZò6µ±î90ªTN¾µ;Å{¦÷­Ì=GßÓû˜Ïþ¾Ü»rhõ³º¯nºÙÎf^£Ñöײ!¨Èíæy‚¡Sž¥´äçC©¯3(Ò}j¿NÌéѤñõÈœE6?är%ÒÎÆÍ:ÀfºÛ—k«IÎè¿ý¨ôÿþ]úg'!?HºIãájGÝ&êÖLi•iꌯ¼ÜSö¢ZþÖÁÊCIŸ$]Û “~ܧ59OÙ°çZ^ünhV¥FQ]§‚:; ÐÍó×Un°(¤k¶6˜TAšÞK—“(cw?Lm*rô²òi„£j¢’¾?âsÍ[•Œ=ótéæÛ6¦›Èéu! ò5ÿy+ý#>lpŽš†egÄÇÇõ(ìzûtº 4Vç?·#ãÝ‘¤vCØoˆü¶?GýŒ4ˆO¬úžk#11hlÓuÌsÞ«êõrš馯X¤AºJEÈëçªÕÈãsÓì˜j­×©pó×EN{§|óúL™´núVÒ ò*^i9À©ÊŹÕW:òs¬¯e¹/yü\ëëA™Fž}+0cZÏõ¦Cº˜{ †‡•q‚†NË–M@$têÓ sžeî9úœÞÇ|ö÷åÞ5A—zK;µIái%vT8¡J•Vl¦×oy½ï:¦åÊË“ZmŽö¾æEi{߆€Äöt?™ôP]®I ÒÝ"èÛ]Ðü!ý-n~°ÿ.éÇLúO•t}‘"€M¥Ôì P©’ø6“®Ë3ÝÐ]{]¢¤±“ª™ôO/ý{ý´†ü xòf@óEšöSùUa½ÛõœŒŒœ³ª¼×ílœj5e1äÏîLúÜ Ißï‚nöŒR˜:Êäèã¬%¹³Ó½–ñH*ò4«ÚÕµt_?Œ‡3O—CóKPzˆýQ§ŠvÛA³ü³­†n·Á¼l„.¼–¯,$ýìü¿vCÕlŠÞ+5VÏ"@øÊé¹–Ž1†VõæÎÿ¾J¥´^z1—&U D†°y³¯S›mjDHi0¬µyí±‘QóEÐԯ껩Ö7,3¹“Ôîàü1“¾–¯óyNQ.α¾2G|Žõ¹,÷%Ÿk}=9•W•®¦s9}¿/¤/Ãó^Uæòâýúµq¹côs,sçœÞGýŒ=»w.§Ã@—Ø­¾ÆN^ŠÅjóƸá=ìmáP¥;-†ufÛð °·UÛsÝ:QÅÚKÓ…ô¯·ëÁ‰•F£”ag³ »<Âöo^úŸgÒå(EÛK«<%·>|ê`l974f¼—¾ù‡•ÕÄJÃaª¬|4›GM7íò£.šÆA•>kS Vÿ@zP‰ ¬ÓçR !ê·» Ÿϵ)>¿o™ ÿ¾Ï^-Òš¨ØzPlª|6m2°NƒRËÏ”î×GÎWëcº<¦¡;Õô&ÏÆõý>Êçô,µ> [ÿ"G_ïæ©‘º©AÙ5m½æ¦k»;¿*âÕÒsù ö^ekd¢öëåSeS¤|Ý>HãGЛÏöilU»‡€ kTN“ü÷?n½êœ&à ÓúÜ>D¹WE{írq®õÕ±žc}.Ë}Êãç\_óµ 6ÔcEþ yÌ»°ÚÍÜ·òT³.ù-·Î1½©÷Εi ‡3rŠC:î!jà´]¹µrm:5Ûú÷wáÑ›0ëó¼d¹8÷úê¹Ï±>—å>åñs®¯ÇVwÓÔÙr­ô7­ç2wŽé}ÔÎOï]9°rUظ”¡ ,$Íg^à û4ëö¥Ö¦­ß7­Ž_¥´'Ù¦}l«¶x{ú»>¼œØ^·á±§I¬¦¸4»i7›ì5kìf éÏÖÏØQ\]9]h5â¶Œžµ–4; K’÷LhétüãŽÆÌç û Ñ´7Ô«ó• CÚ…ìók®Lª$6M'lWBuv6Ò¼ÅtkìÓ†>^¹ƒ2}L—CUŠúã–k R~Ý´ûs|F%1ÉMûFŽi}íô4!ªÈuHUåsÈ[ùoÑy]uÒõ•[–™¦1õö\òí[:2Ë5”'¼Æ—,ç\_ã9v.ucóø¹Ô×Ör°hÓÆfÍHõíÃɦJ{5…¸šžÜWç Ó[,sç˜Þo½¾löØÔ¹mRÚ`“P¯Êp³ÑÞ,µ÷ühúüó½÷f ó¦¶Ç²¾ÙQaô¡ ‡ö¯—ôöWòæùg™ìO²É¥ÓX­5NM­µƒ“uï˜Ëâúñ@j5æÛõÀØìß©öAð>ñSEØ]l¶üy—Á(=nUµLM%ös¶û³ûøðz¬Ò4æ¤êþåº1}N—½ù¥SIï;¾hT®"úÚòày,§´Î[èÿŸ"=O»ç6uPw££öõ|9`½äuþ™·¼~ΩޫòÕî¬õ¡¹ðRåâÜë«ç>ÇΩnìc?§úzX¬:3±s/\+_Æ=¹Ñ×Þ¥Ûù¾Ñ2wŽéý^êK×ꘛ֗Íí^iÇŒ£z5xéãj ;¥ýNšç±§óòQŸíÑû¦þ¬‘ΧlÃÀa€ÖûÍ®¢ÍLïÓ1DË; DnŒ4Éço·{îËMµZ‡³/RßîøÇøðßÚ™«§lœâóFB!ŸÇÚ©øšéJŸQ3»ÂéçÆ“yøYšL·Øó¹qühoÑ0P:q¡”äf^eð²ÇÓìiºÐj? ö5¦miÒÁwȼ•úáéêUð¯ýÝ´ ¯9à=ŒI»7; ¿•{ÓýŒKÃÁæ4éËg~‘ë8óúêÙϱ3«û˜ÇÏ¥NŽÝrú¸!O6£›wwÛÃÎ{ÊœüúéJíQÑ]3«Î¹Ìez¿“ú²®.›:lvGGfàV{,4£ÿݶƒ´¾_Ȧ ‡¬ÿßwRÃÉÛðØ‹=ZÃtj·f÷þ¦À–LÙP§3z›=Ú³ l>iàÐO6íæÙ9yR=XI¡\]c;M¬RDöIe%]^ÅžÞƒ5J»¼–ÒÏé*-;m²Ù¨éBG5¬ÒæŠc—[ôé¸4k¥`RšÖQò2ªQ|BÓ¦¯érPçQë‘ÞýJ#§´Û|¤îEzÖµÜÃxÛ éq#&E™Ž4|×W¥‰˜°­höh1M"˜õŽÑ —¯»ûÍénÞp=÷úªzÃe™<~|#mßœ®ÙÌpª{ET’¬¦(/ë]>ÂyÎeîÓû½Ô—nàTÌýrº~·|7øuO±!¡*¥t^›é4±úý.¬=ó‹œöÓY\;‘Ì䣥‡ëÿÛõôdâz݆€Ã;×àrê~«“>zLÀÙVÃ!vgØ_þNÜýãOݦnö)Ðæë±’J—63t6Úm#Ž&ÿ§ö©£1¯ŸÞH±6UܬrFœ‚äMº¦Â¦}ŒÉSÒbzP…œðMŵ‘·ö¬Ó屯µr©ú–žË³w÷lûž×o"¾u.€ñ¯»Í¡S¾®‹tlÊ"WvƒI,[žå&#aUI7ßm«i¦h-*)Ì%ï¼ÜŽckúœ.zp¸gŹL ?yÃó•Ò3j½îÙ^Œ5i‡â®¬×µKiV{i¾H ¯Ê¯:DMÃÆksP¤9«ý­îqp®õøc;o­,“Ç_ÆÀI?¼–GÔuÓ©Ðæ]ÎMM ¥Ñî0 ÞQ™;—ô~Oõe9”~ÎÒ5¸ ¶÷UæåÆ>¬¿W³V¿Hw‹UàÆ´^§®ƒŠÂ®:Û{Öÿ_Øs·<~õõpìdzÕ­NQsÝ…VkœëÊ«(ÓsÞ„¨¡ÍÓÑý*ÈÚ=ŠîÃQ¾…2wNé}Ôº·Ç÷ΕNƒ™—׿}Ц=œ÷°!Ͳ0Jëÿ×ÖêçNr1°*am GsÏî®òúÿ"¿Ngý{ÆÁx|ØýzÉ6ü½—ž6<ènÚü¿&{…n Ååô•vç¿YßçûôùwDA§'~€]_9]ÔQܯ7‹z8ªÙ‘v õ‘’f'u´œþls?òÛïiŸÓï ^ÚÒ oGîƒröÀ`”ùAÚŸú'j˜¢ó:­k\Ô«MS½ÒæSó_mÚjT¼ËüB}ÕËyü8fµ9]Ùé(5ë¨g’~̤Oyÿ&ëCÚH±s¼jw3:c(s}Lï÷T_–¹[nhç6€û¼@¬%Wä%õÿM Ó£RË•kK7î•N 2>¦ûv¯ÿ?y>ç¹»=mxØ_„õã1Ö61«X×AîÀu'ViÇ^kÖ§5Ë ª}þ:®7&Ú_SI[Åi YQ}½r !êû]ØšvÍ‘2MCª;RµZ÷Õ¸Ýl‰ ö=]Ϊñ@<²Ëêˆ2>ÌÍF@sI‹¹T¸9©‰i”À©êɰ¸óAΤ³˜gÕªcÔ|Þù†ŽÑÄI£¡•uæA¾úç­_}Oå‚úªŸÎ1ŸK¤él5RÙžQhµ™Ú²]–ç¯ûV§»»ùõ€2××ô~Oí^c¥yÆzx¥Ö—\Ä:o¸eýÿ².Ê¿3ìܳåÞ^²×›\¤ IDAT./}è¬ÿ­¾MÚðÍæ‚›ö€ƒ…ÜAßÔ&6fµ>©ZHÃ7)²1¬N ˆ‹}þjÇ¿Ùqt±*ŠÓ2k>¶ zí£f÷A³-¥Mó÷Mhóyo ]ú܉ÅÓà³V¾m§e;r?ÒäÐ×ô)(éC>§_Hq®©ª×—=­oõidTìØ¸-FéFÒÐ<<Þé=” ê«~:—<~ŽõuQ: g^óܘu­ç{ÓAXHеנ´š V礷Ϣ¿owr‡Ž2×Óô~Oõe92é¿¥ã6VšÅj$]”M›x}v_÷TWJuõðžÍ%YE]æÎNèÖ6ÏØ¨õ˜mx›òˆ!ðÜ'_ «SšéHÍÈýÀI~Ï9§Ý„é‘EÞå²]ùõ-p¯‡kºb«£QIšúTØz•yÑå¥{pOjõç4,Ï!mή}왬çš.}êèl9Í û:ë¹îµ|íü[¶܇æéÿuìÏ&€u--rëžÇÜÞux(íìI’‰Q6Õ»•ïwà%ÊõU?K?×úz`¥Û„î=«s¡³û;m®Æ‡÷W^² ßä¥[/ÏÂêl¦ÓlØ,6ÉÿßšpN2µd¬×ÂZù¼qÀÀI×eÔÍ *é~‘Žû©üªóïÃúèÿ°'Ÿ|a5ݵ*¾öÚ.§´ÊIª§Aƒ‰Q´‡Wcta¢Æ.¥ão”ÚéTú8’¦ ¯ÊH•Œ¼}\°äÚŠ´órûÉÓœózŽérNšMìl«2o*6}îÂKT¸Öþ1MUÿûT½Ó^GN·?½îZtÛi8IÃ"ÍR²A²JÇ..­¶&5 ¦3éö> ÛyX:ý`ù¬ÓXi¯ókFõjçâu㢬R½[×ýžvûRå‚úªß÷¼ïyüœëëbà4˜{-64j·ÍnènFwèÙæ”¹Ó¥÷[o÷>è[£¡VG÷ió;¶–W´»Û“-KŠÊ¼@÷ù7õ‹:A›Çœˆð’møn^€'kNPkó?WÇú¢bÖHã’I›fŒ ©(Òš+£:DÝÏ¥ŸÍÔ§\ÎbÈ»v·Þ·/GW8—¦tÝkuþéP«u7Ai½Ñ…I»»*†^µ•¼µ 9á¬R¥0RiBڨ̥ß5y?„»…ôë Š~Τ«qêÜh!1Ê^Á¤ÊÐË(“+)³¬ E lÔÄÅ´“«ÏkžÃªB›ë°ÆSÓå\˜BšÕišzsfpw †‹©ãZ䫳«eí¥oS釶G²ß:c¤ëBúQ¯êa§!à[!ú¢È~»êP,jéÏ;éצ#¡´‘iŸŽÊ»Õêüßv§£9¦è2ìÌ—¯¥‰º°A—y+ꟳ4å´ï³\P_õWßóø[¨¯‡Fº«Sãž:®½þû©ó0ßs™;Ez¿õvï&6·Y{Ò­;³Õ9³5¨Pæûa÷ܳCË^» ¿Ðúlxv ý_FÖ” ï}ÈGMDi4“¾zér$•¹ôÄ ÍC”÷Q³*M{ZÔù¸­˜*…¤ï­÷»ìYÊ_]9Ýßz¥µ]ÝÊÐçÚ}àRÀJ*ƒädmêtؼ˸myùDíS!ž-¤Ÿs-G4wY(X6¯U´:6ƒ™1ZMw6ÍèD¾–¨4åù~‘1­Š£ýþ7£óJ—s19é§×­R½yxµ`>¦ókëî‘m…·«ZújI£±Óí­Wlå¿A§âöQ uzHÚE* ©N’¾u”Í4߾̩Œt5Óê|cÛªƒRGã2—#WG K¯²é„˜4:ð·ïÒÏü»ãž8^³\P_õÏ9äñ·P_.¬nï‚òÑóÒX­hètl>^<½Kú^ËÜ©Òû-·{7*J©ª¶ÏÔۖƇv½æ®#Ï¥ ;Eåõyf½‹1=ì~¶~v&é?*éC%]ä´i |>E ÙSÀç‚ö£óž˜þõðˆžîÚ«cø2±úõ.½ê4´qn°8­Ö¥‘Ʋ™zÜ Qòy߃ڧ¼¨Ófˆ3¾fÇ+-¡XV‚6U EÞÌlù¾Z½·Qº_!¿ï¼ÊkšòΨ ­ÏÀ¤AéÎ*]^2_óõŒI Éi®¬çùR´¢ÎM¹ñ­71­H¶åkîkzþréôëOÿ ÿ¹N' 9Þχ”§º_òëlë8œb)€+¬ ùå:Ìv¹jÒ+†ÜI*RÙ²6}ÎÙBú1•þ½“Q6”¼§rñ^ê«s)ËçÇϱ¾~ðZ›Ó¹VG§]·ÅÖõ¶ëÇm£¥çØvz-§Lïc楾߻rhUTAA›g4iÐ.û–»RšæNõ¶×ì.ybCŸÚð °UsÞ¥âúy AëÓj¾Lœ~½óª•¢Ý?ki\¯¦õÙÖë5GxÝohˆ}ÄÔÿ 4’×}À6;ª»BÿÒêlÔZM]l.æskÉ-Ö+ÁØÚ8ÑÇÕŽ¤³m•¤?òýp’J/9/¹*w|rÔÕtê¤æÇæýÛ× 'Þ‡>¥ËKå‹—Èg—WNÓÛUšýl5š†å¦)h‡¾ç1¯¹ÏéiŒ6æ?mèH,¶6rÒO}4È ÞÝ×èT{ÜL¬~» ËÏ5Ì_E«ãQ©^HóÅês~ÛÒ«6¤ñ{*:·²Üç<~Žõõ6ÃA1œiûôæî±fÇ8‰®eî5œ*½ú|íù½3&MÙ¿×ö™{±ÓÆÝw¤h9°R¶nüÙtàÛ¯ùØúÔ†€ÝÑĸ¹!Ö*Šõ†ø¼U±µ×æz­ï²ùœ »Ðú „êštßoýò³…NÔÍ5 ‚˜vnG^ý•ÞdO2Ì:I·ð»(Q2ááÆhÍÆ%»:4½}I——Ê/•Ͼ^9ý~ë×>ûc¦ª½Ö5÷==›ü÷Ç­_kàtöÆyø —ô±Éë!j }ïdO°ÖèÓÈèY|P¯:ÕmùæóÄÊX#§‡#ï­\¼‡úêÜÊrŸóø9Ö×Û †NZøG6™¸7Yæ^éÒû¥ž¯}¾wIã}3,Œ5*ô²³JúÖ†€‚6©c6³ÓTÞúµ†÷¾ý=Öâ¬èN¡Ù °ÛÐ#*»Mip“1ûÜ ¥ïóíÀS¯áfd4xÆÑ‹§N——Ê/™Ï>ç4k?\ûvÍç’žŸ®œb¾Ýù½ÿ›K·~¶xŒä©wÍþ$í©š§<]ª(­¾¸¸œŠyh:ZI¿´êUO¹xõÕ9–徿ñs¬¯÷¥×)÷íK™ÓNïÅ;»w¶ÐѧJ=¦l?eÖF_ÛðhõgÿÏÿëoïþîû[¿<ЧÕÿ_N'ú#ÿ¿M§o·~ge” ¿{0ÍåÜTUÐY|T-$]”Òht„ˆoåu?Ó“$Fº¼to2]€ƒ•×$Åt_åµ­‡ñVã”Ñõª ús÷–¡=mSƒ<Ê÷ýoÿgÌØby ÀžŸ»y'ÓPÊÒêsyº÷”Nƒ’tžÊ…´ùOÏcê.uª{T¦¾R¦¨¯Þø} SæÀ½{/úÚ†ϘGo½¢Q“2mºããúò¦îß O€ÓøRz¹glÓ÷Á Ë|´i½>³©ÖúfG“ SíN¢žKMÐÈ{Ùpø–KNQ¿8¯Q‘ŽÖ™åµÿÛ:ÿ<Þ6ö€žû>•þúIªjÉTQ®ðòFª‘7FQFQÍYÝQ¥›¨±ËgŒ{i¶Hççäs翽ûÿ/­@p:³(Ýݧ5üÃ"GZHÆ(«(c$kÓ¿;›þl”Fýçµt?_þ7þMµ¾öÿš zN+Hš.VÇ“–N*\êì;›Ƥ÷Aª}úZÔé«®S§¿Vñ¿ë¼þØplÀÉE¥Ž|s4é¼–œò¨^¸oZ?CÚíßÇ4Õ¿VíŸmx퉓&tþN®’tŸ;òNR™+ï"H6¤µÿM (uú}îôÏw¼îç +ç @Ðí‘û¦so$s0@­@ÓñŸîx½›‘4(õ ðÝæ/è£Oc£é}\ Ä=ü®«Béôxç¾rô€>WÔ…Õõ•tMRà,I@@ € €@@€ € €@@€ € @@€ € @@€ € @@€ € @@@€ € @@€ € @@€ € @@€ € @@ € € @@ € €@@ € €ÀS¼…ñßþº?Žñÿý£Ò?‚ãŽð|µ^ÎJubbçß«Zú·{éúж÷œ•ý­ÒÍÄÊÉÉ##IFš-‚þŸú§¤¯WÜpÞƒù.]Y)DÉ·z„QR%é[þû5IŽo€áæâuXEýuä½äCúª½ôë½4|Âhñ_ÀqÚú!J!H¿;϶ù› ÄüÓ³Îÿÿ~UUQuÎ01÷QZÔ)ž™þRxå)pùo1JÞKŸ>ÿ}°ÎùT!.åÁïSéßêôo_{ðz8”aÏ( ßmýŸSé¿/ηmþ&7ŒŠRL•QŒׂ½¶¿ýd$•…Qá$ç$gÓwEéûôãïóë7IF*TØüùK’¾Mó>XªĹ›I¿}_uÖ/l?^çƒ2 Omû=ü ù <£€c¶õ›Îÿ¹¶ÍßÄ €¨ÔÑ_ŽüG))ƨãɯo:—¾ÝE…˜6¢ 1mR¢4¯¥ßô>÷Qº¦?û eä#*½ïï”ßÈ|Q.¦hø¢’¾·²ÛåÄþõpV(Ãð„Æ¹¤yHëý}§}X“<<£€>µõ í¾¬¾-×ÄVÅtJ>H³*Mªüz`÷p JÑ©˜×·£Þ݇"ŽÃ… Q™þ¼¨¥iÏ^ç׈¥ Àã,$ý$xFgÐÖ1©8®fÄîŸ{°  N„¸êü/¯ñØïÓlL±ž<½Hƒ·(ViæI)˜Óöäõp†yŠ2 €gpmý>x3›ÆeŒYmJbúÓùÇÛår©öÒ¬õÿ¯Ÿ8%èØ¯À[÷•MçœI[¿ÞÄ €åf/1.GÖCeo¾÷Aûp‚—ÚšÀXqâkï5*RzWõz¥`ìé_ç‰2 ༽¶>€cRï?OùOÿ¥)÷ýèú›¼cjsLasUÍ–îˆ7ÓÙÍõ²=âû «SzÇ<%¨™Wôåõp–2eÀ3 xƒmýžx3§If¹  $ó¬€Ý3.KéóÈèj É™ô;U¦µôÇ}ÔŸsÉ?#–ð—é5gÕæ s¢ã›òå"çÕ » º#¾ÏSlÔPA.ß%“ggx¥ôžIòö|ÆK¡H»‚¶#‚žx&ȱ_ï=Þ“—40Q#T4iWë ë(Í£TÛ:ŽäüÊpi¢†­Ïhóç\î³’ëÚ&TÆÈ÷ >²Q£&7Sõ‚ôë4x¥©Á#Ušt Mº­§_K;«`Ì›¿wçš§ÞK={̲óîui¢&¨Üñ}~&,¢T[«ø Ãå¯Uÿœâå5²QNqU†ò‡jöýnç­:æïÆ*œÉT…÷üì·ëÇÖg®ógŸ¸zж9€ÇDòÑŠqY!„œ7Ý¢I)ý¯×F—ƒ‡•ƒ3)Ú3*¤¡‚>–Òo3éÏjEò|NïYÕQ>D £:ý¸‹—#I£\Ø÷^WãÍý‡fYÃßî¤Ñ•Ó_ Ÿ*ôŠéóNg點ñ>ÞKŸ¦÷yÌ(¼ŒRAèN°ˆ1]ÏßïÖwl£®]Piò&Ífˆ&ςȯ5¨$/¯º”‚{Zaß{};>ÛcØ4*Òk.:S‚œ3¯úz}º'Ÿ×Ø¥uR>®_O“‡ÿ˜JæòiéÿÙy LÞ}8®×Í1›¿Í¤á–ûû”´˜¨.h˜ûö1æÏÖZf¤ …:ÝÇÚI¾tçUŸ_çµÊðÆ ¤õ[©lâùÞ…üzk­Jr!­‰+|”Œ×ÜH•}^ðã©÷榴óqHùàöNú&éë …&6hdÓsCí´ëlBcj ‡ Qø ÙÔ¸™GÉ/}¹wçr]ÏÑçgß)ËÎÙÔÏxö›ü/v|ÆöiT6¦Ïg½}Pm¤…5 ÖžUýsêgÔØ]Ú¨A;Í[m{u>ß²Ñ\s2&N°ˆÒBVÆõ' ðÞŸ½côÁE×hòï2Pçú±X¯_£½ì¶>€c÷ÿãjz½LëϹF[­mWúŸ/¥ÿ骩-×ã•©A/É©,Œ|ˆÅ IîöD¡þý7¯ÿá§Ôþ7ZÔA÷ iºÈ…öIZ½Ï¥“†e*nÃ3Â{énž"«#IÿüžfØ\hj/Í+é~±}CÞ§öÒ·éê}º~ý&}ý˜–4t>HÓùúïÞ8¯‰KïíCŠ/êôyšˆQ^&Q¤‚§R,¼Ìøñ…v×õíûlRG•.}¦EX}cNðz}º'ÿ¸“L%ýç©p«Nrs-³…ôg”®^åÀ=º ûJr£ÕµÇVùø~'ý-ÿ¯OI+/ÝÍÖÓêÊz];ÉÚüpóé ØÚ¯7€¬‘ ›:3©ž{¹‰ÛùLn®¥p¯[†×¡6èÚÅåë„~¿ö)=š@KóYm~P[›ëÇ|ív!• ò…ʧ=l÷Ý›ÿ¿½3ÛrGÒô€¤6wÈÈèî9s5gž½_£ançb.{ºª2+cq¹6’ú€RÔê”DIÿwŽWFexR" ¶À`0Lï4’¯þ»[ãæp^úqÌžy·æ“r·.!¤ DÖãfêhA§”ÿoŸ([,ÝÝÙßžû}’Ý-|¯Ògßw-ݹûñß?ö‹”°ÀŽß±ôïhï(ý;†µŠ.•[©¡Oj]Êþ\ËGI_¡¬:¯¯ÇÜT:d£…snIQÏ6%P,€?azÓ,òÑ}ïg¥1öö±ôö±ðϰ±}”Þ>pwï%vèb«¿Þ€yõë¾sÅû]ÇúLt‹ñÁ=‹QxCAìÿú$ðm\Ýl©‘—¥©ïÒ ¸ ^—ù” @/\`¿÷%ðcª}€›ØaR7w,×ßßVk€¢p,¿Zjàg”ÄXàmæËºt•9kÞËOÔC>§(3ØÆÂ:c‚žð!+»ˆÊ¾øÝ`X`Q˰*« ßzœ#‡Ó¢Ê©>:s·ëûí{·£v4`½ޝzŠ‹?¯O2Q……†ý Œ÷߯, 7ç~­€¯Ùqc¤|6‘ûExxÍKà{xÇ‘8y¬–Ñ%Æ!€ª gU8}³m•rK"¥–ïO;ƪö].¨Ãáy_”ÆP¹?kØ­ —äÁ#ìæw‘ÑüÒxÁA–KÀ¬4ÄDBäî’6À{d33¼J )Ü\Xøy¬}åIW>x®À퓲>øY–nÜò²x7ÇNDs%Œ1Çß]ÞGÙõù{uEŸ}ߥuçÖìÇ©¾?,R…DýB IDAT„p.‰]h·`)ýX… p^ùØSú÷SÒÅ‹( 5JöÞþ\ÃGIX|‘®âÎX7GW~̵O”p ±¶÷[{¿I6ëÉz…¾øì«iàF/ò*9j£÷–ql%€$ñ¿7ÓHžƃzëž62¥á³ ¯Gù©°ïtroð½³ùµD~h¬^L ñ¤ª½éXÚÈ)Eÿ Gºôe˜Þ°Î,Ã-»y.° 劗Òaà‹t»+Ö;à¥w¢aLÂOd‰(À•¡<ÏÏ즉ßé|7L$„èn®C€øª\²Ì]"taª@Ø”kW¼Hg¿ (JŸÊ«o|Ö6Œ¡ì¼ðc'­+åÔ^§Ž {(»¾~¯®7úêû.©;·h?Nñý_|iöz‘²rïX'ÏÅÝZ Ón£‰r~b5·HGêÈc?—¶?×ðQ#c ¼þ,sïë}œ[ì÷š ŒûIü{ô!@ßëb«¯ŠZ½Š6Hmóý- J'ÇÌ'Ò¤^”Æ(qñÙ2?O¼ßu¬ÏÀ9ÖÿÞ‰JÔËÿ­_$ øö*ñä36M ü¿¿ÿˆžñ àK < ,ÃV“H §€?ràë[ó•/‡ñß)”îõòÿuùrôc¶VzòÀÀí-™çfFG|Î!»fÖTÍÏš‰ à9qÎe‘ß§Àhï}0öŽ2þÎ:$IY` Í5²#ÊÅ·}¿®î„Ú¬K¥rídpíçõI&£±À|nQ‡ÍEáÎ ÀlÏySÉ3QÝÃŽéØF00è`¬^ 3Î9ÿ˜ÿhÉÄýOÚÐ5më;oåöò3W)PF‹ƒKèð 42édÎ^•ex”pŽn¹ÃiŒü»+TeŠÊúŒ Ìfæpùî‘Mí/Ò SÀÛøc~¹Æ¦hL¤–rw6/«l»Ü'ö‚C O_e×ç9Õi¬Ñcßw)ݹEûaOxÇAË;þ½ö-&Wþç.¡ã¾›”®B¬0À|gÊ̇2¼`l´à†–¹ÁÛÂàï?€ÿl±>SÓø%ðé È|«Ñ¸¹K¨,0ÊB#i9o³ðÆ,y±ÀØF÷EÚz¶,÷Ÿ#[³tkìºj1ʧ~ÎQ$(p üU:c]îøÝ™wcÿC ’9‰ÿù¹¾eý™o¶t^ÝØúâó%íÇó®-¥$Иùß•ÑsàWØ!:ÔqhƒAê“a¶ÒùæƧ,ÂX•%ðÝU¾åw—þwÇQ ¹ ä[‚øü :<0ƒ¤Ê¾ÏW.Ð Û!Ï/ýï4¿khú(„ rïSÝÉ‚-쀌P,?4ðÿËî’zûH­;b}Ij¾M4oŽ‘E½¢ë–ew«sêQ|_—ºó²ÎŒ»óÛX·û=óï¡ÍÅÿÀd( ”D¡5¦ËjÓh%Á ÜBBù*À¯™Áëïw-ûsi%J ‘¸ÊÖeTågZ¾ïÀh$þ(E© ÊÒmŒé–ïumè{±uÕEÙ¾øß—\+àú ä+`Røh¦½!ec}&Îå}£ì¹…/‘ JãwåÿsZÏXM0JXcñ¶p;–ÿeAîµd’évgKàSË$hËP¾ùEBf·ÇÒ–‰šG™Á®>çQ,±™K`4t†z¹4˜™j!%}VS6 ¶ðï³0ŸiŒ'ê˜é°Q1щ;ÑpÆ;ÎÔ‡êúÏë‰LF©?/@ˆþûÌ;³m‰´Ö Ÿ9ç©#ï®ÑíYoëõ¨9Vém…µøµ°k‡ôM5ÿµJ‡eíÁÕ%uXX‹Qøl_>P–Ù @R“!*‰B[Ì—vý½ç¨ï$…äh¸~*õÏ•*‘–‹“ýO½9 .×¥Áre1ï0(ûàªÐíÁ÷¶Àe,€l *áÁÌ‹ãçl_ewËsê}ß9uçd-¬Å8¼cáš7‡Åóý2`cñžI…ßRÀ‹ÎŒ»² õJ¸°cŸúEí®cb×¶?—öQE”¢êàÞÜ( ´5òˤB–b-?§oo=ض¥ï”ÖH£ê‡R×cô¶äZˆ»š¶ó î9MÛÙ­A?lÎ@WÙwÝ Ý„÷ý½r̆[ |yv;u?WnrŽ2@¨õñÂô˜î¤Û>G ½;婟s,º¡ÄlîÊN&ÊÝ;Õ.À‰…m4ðnê†}ÀÊlžÿ9ôxÃ>âè¢Ü¾C|­çõE&ÙP!)4V¨vÈãÅqè ðs |Ý—M ±Ž:ÄšF"ì9íf¬æ 'üù¹^ðø[d+vÍ)¹#Yw NJ•Vg+Ã=Ρ’b±çûeR¬Gý9Õëãià û¦ü<ù±°øúÜl ù|гxI*ñ”^]²TëuÇìy8¯¸'pi“O"$Qú3!Z[ü577-»[ŸS÷äûέ; ëD¨¤ý‹†mßµs/¥À׉Ä?gÚ¦)ÿ‰]Ûþ\ÚGi[u·ÑQÞ8fþ2:|É7Vïûcª¯VÂMß È°™ÛÍ‹ú™ÿSlg¨0ÍpžŽü—ˆÍ¯¼‡—°¦2áFý9\=Øf¸³Lá7ßøK n„LTbsìÆÒ+\ó Ùh0Ø5¯ Wê‘ÔÚÜeWIö˳Z>Ûvèe4IËr°œÃuþ|ó"üÌÑ[ïAŒ®—Md?ž×'™ŒDÕÖ¶·¢»n0Ä!@ˆw>bÇ9ê +[4EŸwØŠ1Ú;!Û×sê°ÔUsÒ¢¬÷²àî Þ~÷ΆÚ-® ›p_ewësêž|ß¹uçd-Ëê.óJ³ÌpŸ­¦Uó¯V§×#–Z¯ï ŽƒÈ¶÷>t‡%Ì¥fƒRÛ²€™Ïõ‡eS4’KJßÒIãš6­ÇmOàòrB€™¦rgÐ×WÙÝúœº7ßwNÝyY‡wÔ-ïØ´ç‡Ž^6t‹åLmnÅ>ð}i{k.gj»9öá¨L Xô(óGß{X@»›¬uf‡íüýiÿ\|‰lçMÄú=ånn°¦jü¿PÃÑüß;q¥D¦ TX¬›(±àgëI“NTÏ:'—kŒÕÕÊäH áʅʧwWÜnï¾j¦sfx•1úÚõ¦QèÃóú&!ƨÊ\›·„f€3ëv¶)÷¶òÿšãËÎÇjŸ­©@RØu`×U#šsè°-«{ŸK³4Äc™$‡9w)&>‘;‚E˜™2>aõüt!'\:}0>ù´3p9S`ÐWÙÝúœº7ßwNÝyYëJ×›ïÎ[¯|þH•(üÏ‘†µ®Ÿ@|C¢÷Î{l.gZ¹uqAsü33hj©!3@K ƒ~ßÇNß뮄FRUgÇÏnÆr‡l² áìaÞˆ+ûë3pfç`ÄIÀ—NùwÐaoPX=†ÿ S®—G,”ñ˜ãØIâ2£b‹ƒéK™Š-‘ºè<æ°'Ïë£LF#ù®˴DÃA%Í ’–’²C»ÿw±Õ!†Q—°=WnS&­ß–ÛÎcÞ;&Ü=¼mž¨çñ5Îoê°ieÃw.ò±dwësêQ|_ºó²ÖÅöw”p%è_¤ðœh${VR¸÷3)ð>¯^êùöÁþ\:–Ÿ£º£6žp×(Ž”«¨Ú]+'%`„ol RØž$è{ýU‡ª:®Ý”yàˆÜßhZœ!!u‰Øœ €®„e“*ºÐØê\Þ𰨾J ØÍÿ“ï—|€@Ìðˆ™¥R@•¾D®¥Â¢/~MÙªª$ΞºÙõóú(•TW†si! á²ú+¿–¿µEÎ-Ýÿ›»mç0Ê£ƒlÅíèci\BÓb·éTˆ²šæCtI'¬û U`Û—áÊîžæÔ£ø¾!íÇÉï(¤ ¤¾´Ü¾[)üÿ”Ú-RVe{’XÜ€ý¹$RKSùW…(  …ÿDJ9(‰õ}…ü‚ ,\shy½(’¾×';Âu};Æ`xLuM* ¢ Ì.7a.›3ðÁltÀF‰H1ÒLõÌZOøJ{º+±õßöm T×O™ŽæH%¡üØŸK2œ(¼M݆Aáœq§{×”M`%\Â@E?Ò'@Z»±°B#W€Q—_ÀÑ÷FUÐ{ŽP'êˆÆ‚B@ž¡ó±9L„ÌRк_dxÕÒ I*c‰ÿ—§x¨í꘱B¬›–˜¾¾Pé2®¡$è”rϳ>¯Ç2Ɇ Ê_ ˜D:ªB`6Óµ+J …cÔ8¦D7W:~t¬„8OùÙYl$Z*¥>:Gây"ºë°W6êr»9æÀqKÎøú*»{šSâûNÕGõ¾w ìÒ/,íÏÝôÞþ\š—Ä]˜û¸ ƒ«H£˜aÝ(ÐT‰˜ðO%Â1Œ¼Ì ÐJCU¯æÕ#øÞ°j÷>ÿð1R¸Û5º>‚y…Øœ € ~"Ôí뮺ÎÝÚMˆ:¯ÆG>ø}Å…ÆåVV ÛWR}ˆ…L²Yôrb󹮟×w™Œðn]Fk3Àf³š¨ü?è`ó<ÚKv&y÷D ]êð!AÈ)Ÿ¸kžœcQs‰îÿ‡Œ›èÁw¸¶ìîeN=Šïûˆî<‚¬·½ch„6½B<ÒûsÉ8s8RÈý]ï@•ªF€áZŶúg˜4RT‰!%€™0Õ˜\ø:@úÞíIŠø‰ÚØÃ!¾S×»ò·›wÅ}\h£Ý~»ùsjB!<3þ²CqjÊ~ø` k×™¼VY\[IŒÁ0qòÏFáƒØõónA&#o< l^ šÑ}×Ö"S›åÿõñï;¹Ï è#+¬u%šb3véƒîvÁ¶®ñ5mZÛ‡”çÔ}û¾G“õ®Ýÿù•äÑûsi^žUë•x9€€7¿üô?ï>.ÈQ]gü•syéJ¹­OÌ®0^´“Û7Oãylôi d¢º^³±~ß¹Ÿ#A“ì¤ûFÀºÀvÓÍÿRÓèÒÓU êê^êàóVîª7%\ƒ—X†¡ãûU)­»ع©¾ìËón@&BÖ¯T '˜ùäÀtañùÙ•ÿ|Ö¸ü¿@¿»÷Y‡K[3Û2GÊ 9朤µH}C¬ ¤^énèã–Âõ£ÈWizVÔKÙqNݹï{0Yo{G *×ÿ2’_×v/öçqf’|{V0Æâ×ÌlíDz…¾@c¸JÃP%`[×Ó¹Áë«ÝN†ÆÍ©Ý7qð<ÖÝ%Ptuó‚±y¸‹÷ŠÏIÅ?§.ØÝ<`íDzjö‚Šví pg®þïŒÅ ó†l‹á¸jp§«Pܬîu(zñ¼[‘Éh$Öújß1õŽ;ò¹tx‰úˆæHè´¼:Áa¦ªrÂñ÷6¸À²1_EcQpŸóˆX_eÇ9u¿¾ïe½h,RDÛ"E?žýéSœ™(§'…¯QR $D”ÐùzÍÏ£ÛI¡ªãms%õɬc¦ðÀ—ÿŸzÔûÚ±y¸›#ñˆÿ|J@­`4)›;—G[Ì-3õʪµýø9“K}΂CZ°ÆB°H Š\¶8–²ñì}ük¢!àÊÆ7î͵î<ùßçÀðÀò/© Rå¯þÑ7ª]?ïd3L€_e¥Wñ7Qø¤€'a!$/BW¿Õø>×þgÓa™ËÂUY¨-Ž`§CŸm-Æ>i»æ*ž'ƒ+ c¶@eÀ*F-Á£ˆW%€ùûyšKõUv8§nÅÎ>ÊìúW…Óõ´E×÷Ê_Ž>ØŸ>ƙۿ?+¼O5樎ðµÏêCh'$ôÂ%d‹,¸9^XÌ4F“Ýóxh5&©[ß…QÖÿ+Ŀ׿>š¢¥ñ_t5à)×öÅ׆grŶï§Þ=Þùù?+óáq¸ÄçlÍ E)0Ÿíÿœ¡ÕûÔz©ëB›÷½?P†óçO÷ER$;bàçÜ59xLË*‘T»{þÄîó]?ïdR xGjíÞ(ó:ö>~Neî¯ÿÝ8#ûôtŸÎ¥Ãé@Aùq×-8Ì‘€åü°gO„ÁÈGÚlΓZÝ+×褅ĿKÙ·ðÏ À“W¿=(¤…¾9Ù=✺;ÛyrãdÝ;æ{ÞQøEJ'Ÿ),ÔR÷Úþô-ÎÜ;¯&™tïױߨ+òèvR% ©Orl[Xgp ÚVoŸ™°xQ€R.>,t7GR.›÷ûºUÊïл7J(à´DBL¸Ï´-px`5 ¤æÄbªK}ζqþó_¼2¹†ÌÔvEöW´,JÅ4K¾ÓÊp˜Î+Äfá:ÁþuÔ ¹MÎÈ|¸û|×Ï»™4Áuâ ~% K„†=±S+q 宥ÃB¸g΢1—-ÎàÉϽÐP£ísr,5žDuL#ö×añïBˆ+×èÊ ü¸©h¡×  ‡H,`W¨j3`, ^”Åÿ¯émÉîçÔ-ÙÙN ë`_çÑŸUËß®AØP[ˆw¸añ, Æø?ðmØ_ûsÉ8ó[ªñ³ò´ø¾yA- @˜ªºæØ]Ý.|í¤·y˜šêȃª/٠ᮋN- @2ÔÐJÂáz¬‹‰4˜('€Eî6‰:¹Ðá ±9:_ÓL‹úƧví¯õ0URàÔœæÒOxYÿzë `"])`¢L$ñÙž#J×/õ9‡B œ5J…†I´¿\=ciðäyY¸ÝÈb.Nð ¹Áß±ÆgF£¿+Ž\D*íºŒZÿÌ–þuý¼[‘Ɇ›HÌff}FOlKæÅ2þÿëw͹tx<X,,æÁ!70îZ €.t¥aSÀHÀI™tNx(Ýg-VÀª¬Ï“Â;×–WW¶`äçë,š÷ªñ9 @ˆl$VÃ&@)¬0>ðs›3Œý®Ò|¹ßôUv6§nÉÎvÍ#Èz4–˜Ï®‹–$µBUÊ]Î ²‰€•‡'gFÒ`,,FÊÙðNoÃþ\ÊGÍçÀç!0Ï5 Ðò¸ä׋rW¹-‹úŠßâø÷ìʇÐNñÂô]¯¿ß°‘YÏãÄUXHHd (—Ó’(Œ›³ÓPØú8ÆKÀ[ˆÍ™è(Ðk ]ÄÍOy^Øý7«&‘ËÒ)wÈ~5Ë’”u“>ñ“]ÉÊ`–º*5öàsv¡ýðÙ¼Aæd!K‹,Õ5EÖÆéÙ(ËÍ_¼ò鈄„õ%Q¦¹°<6 +œ¥Öºn&'&»~Þ-ÉŒt¨H¼3l&И·qF6Ëî7#{NvM5VÑ#D‚Ž$Ê—O ¿™ã>KVŸUj—}Ÿ­ªyvXÞ›™ÿ+Ê« [ ¥À“Þ­›³Õ5SaÞj/¥\gâp5ÓHXHi!ýØIéz]¿ƒ“ÀßßPz*»GœS·bg»æd­”óM ¸J-¸E°¿Âõ ÑP ‹d QJ@Ëj§[ÂBH ãšœ©J÷f9ðçê6ìÏ¥|Ôûx¹E r ±2Ñ0"T !üçˆõâ9u de]¿3n£#„ñ±ÃKûÚI÷Ý_à­Ä:'³ÖóØgU’ÄÛKŸ%ÈK÷ócüáÚû=ÜBlÎ@WI€†6®3’'0>«_ÿgNÌ. † xטúìqPü8û¥­;ƒR׈F¢šÑ‡–]êsv%NVQòÝ_VÀÓÀ¶ È…ö÷lúÝ2j º—Ɖñ§kÌRk‘ *³Š “±ºþón\&£!°XV5ãÆ0¢P”÷h°®¤ÃŸžþ˜j”>X7œ°‰n^™wå-ÆF»¿_ån÷¡Ô• Émä·çûð ã'×\ ¨:LP킉H.Öøà%ñcç:x¿bàB»ŒŸ~‡¾ÊŽsêÎ|߃ËúùYa1Õ0¨Üq@{'•)§ç@jãtÝë»ôWÙ QÅ’¥×ûe¼¯ê»µ}·?—ðQ9ÜÎn·Xw2Ð ?C* ü¸‡ÒüEî>×DIµÚÎøºs †#…éTÃFº•5â:mSº9*s'ck€•qsµlÌàCoz¥Øœ €¯ÖõPXDœrn8ìøëF毹9&ó5ö†{æ…Ð,(ïõ÷®QfO÷èsví€Ì£ÿ_øÀrŒWΘ­ËÀüYïøˆ†n €`{Õ¢äÇf1 \¦°édŽ}?["õ†8šW§&Ž»~Þ-Édk6Ø—ƒ5«šòŒK²äÚt5?Îõ¼Kè°RbˆÀoîƒÛ¸¬SX@6Z'Ç»•A6Ë-αW@Ý‚lâ <0s¯Ç)ê;rÒl~D[ٸݪìeNõÕÎ^ò=û*ë.R¬ÁOý{­lÖUpä% òúâ?5Ö-êÊè]?²ÉrmûsnUøîc @ª¥Uø¢¯¬`!,ŽÃX—;æÖ5õŠvÒéH›n!šÃm±_=Aç~ë\ï`³à¡ €kÆæLtÀÝÞÙ¸ÂqWÅc¼ÿšêš‚íšä}ÿœÝô0%Yì u Çæ9¨“‚œ¨d¯v%ȉ‹ß®ŸwK2Ùš)̦ùCdzÝÅm6]Ís=ïR:¬”Ø&=“Ös"ìè„À£Äî]SçÈ-Éf±Ð˜F‘Ñ2 ÆDÈÉFÐVâãWõQv2§újg/ýž}ƒkì 3¸î»üšêµÏ6E³ ^ëÎlõÝÙCûÇž3¾¶ý9§Z6’Ë–8*±®³³\ˆl‡sëzE;YéÖw$bÛ84I|ïh¬«~Qõ›‚m=¸ ±9 èøy«3~×ßü¤ŠÊ-Î!Jþcªk2:JÉO0dEjaâ,oQ~¼$¨ëçÝšLv&àJáÆ-ñ›m“:²tËo\‡¿=+”¥Å÷…iµ«ÇØÖ/CäוݒlF#…€·©ÞÐçSºMŸ2µû$»G™S}µ³×zÏ>ÉZìXüdÑš06»8b‘ß&ç׉<ùж>ØŸsø¨×ðkµ]^§Ž÷ëP ;anå¢;ײ_ž¬~Îô^»øú¤Ö6n¢Éß#æIDAT[dþXH¸¢1®Ö>düú›_ÅOýûüÍ‚YN5žeUÚ††ÂýŒŒ[“Ÿ~·w·STå]—F®4^gT~.«†5€×‚³®ŸwO2±ÆâÇÌàKcálVâ|{€Œìµ™Í4fGú àé‰rÑÚâ}nŽ ‚'²›¤Vßew/sê|ßÍÊÚù»ÆSâvµ­/üã3÷õ#Eað¶´G-DãÕÝÚŸs‹åi›r·æ›è{Œ« ü•ˆ…ïË%ŠÞP_ئóçŽÍûNB.Ä­(”ЀV› AžO¬Aïúy÷$!^à)ßðØlþ÷y(¨@`2Q˜pŽF)qõëÚú*;ΩÇ&¯*kk×eï:tÝGåKºld–¦¿¥´?— K²Ô²9ç  R¢Ýl^xHï‚>Çæ—@r)„1È”ëÀúVÔ¯ŽQ'”ëuý¼»ÃZ<¥îÞßx8t#¦4U„BZ þÚ»6¯Zr€¹Ÿ8ÝZLR3j†?^üÇÕVÛÖòŒÍY@H ½²˜–À÷¦±èÉóîÔŒ~ç¿h7à!„m c1Ê\'øæ™Öf5!äºüžj|/$4N[h’™ô×=–õ ŸæíÛŽ¹06g€nÙ*x‰^<ïžP°xñM\вº»y§ö'žý'„²…'¤Xæþ >ÿï›×˜1ñNÈõ)WÀgeð^¹0ò0ÍT°ø¬ RéþË¢:6Ú¶ø—ŒÍ™ äPºn4ÇÆuíF|(-ž•…„+ÁÊKWÊeP?‹Å4 !„&@*,ž¥A*œ)u½x#™Ì^2„\_sàß¾¸QX¨DC  ZXX„«,-Ri1#ħÐ.Ù—ûÝÿxã(îþÿuGüÍØœ BÈIañ/™Ai\y¦@")] ¶,€Eî º†+ý_ÙažBˆç_ )\ЯüEéEéÊ‹²Þ |úµqì%CÈõYZ`¶pgø‰  ¬…„….F”ÂUˆJéþÆ«X¬ªÝÿÐðoŽúÑÑ—”ãÌ!äjÚâÇ;ð:„¬qF;×@Q8#ž.k›£ºª †@ýÞWB!ÍŸ?o¯~á¯Ý¢U¸ÁPEVÂ%’ãdò'6“!¤ó¼ºú9U@âƒ*éâ>!Üßkã*{JívüóÒ•ÿ—^Ï ÔøÀHœïÊM&!ä,,ÞW.“®ù3Öer©Œx3X€–hB‰XXàÍ×õÐÚ-ü5ÜOØ Œƒeý !ýˆ ÝBÞz=]•€‚ßõ÷E:"ú]k¼ŽÛJÇó–˜& ˜Œ©ëLB®néµ7ðÚïþ£¯·,üžÏ"„²‰ñ c€ÒT ÿÂ/ü›·¤^éOé ÜñœnáŸúÅhbiÜÙé{Ðñ»oôøm,¡ËF™ „ô'`]dÿa׿iÌ%v7n!„òàþÄ8Ÿ‹- ƒç±˜^oúF>€(þóÏ{YJ=g€œ•7o†ã@™FQ?ƒßcˆ^'R2{K¡%§Ëá%å`BúÊ—‘À|ak‰»g‘ßä9F#êøGÿþ³B!„B!ä¾á(„B!„B„B!„Ba€B!„B!LB!„B!„ B!„B!„0@!„B!„&!„B!„Â!„B!„B˜ „B!„B˜ „B!„B„B!„Ba€B!„B!LB!„B!„ B!„B!„0@!„B!„&!„B!„Â!„B!„Â!„B!„B˜ „B!„B„B!„Ba€B!„B!LB!„B!„ B!„B!„0@!„B!„&!„B!„&8„B!„B„B!„Ba€B!„B!LB!„B!„ B!„B!„0@!„B!„&!„B!„Â!„B!„B˜ „B!„B˜ „B!„B„B!„Ba€B!„B!LB!„B!„ B!„B!„0@!„B!„&!„B!„Â!„B!„Â!„B!„B˜ „B!„B„B!„Ba€B!„B!LB!„B!äŒü7eûÀIEND®B`‚pyntor-0.6/data/pyntor-logo.png0000644000175300017530000003341710416470151016575 0ustar studentstudent‰PNG  IHDRôxÓbKGDÿÿÿ ½§“ pHYs  šœtIMEÔ  Z+ªêtEXtCommentCreated with The GIMPïd%n IDATxÚí½{|×uçù;·ªú'ð ñ!F’-‘’m‘©‡#[Š×òØŽäM Ùlfg3»Ñd’ÌLÖóÈL²Ëef73ÞI²qâdÃÉ$ñŽ“lF̬¬8¶äd([ )Q6e[IÉ’EñaP&)ăèWÕýíU 4 ÕÝÕ@àóý|ø! è®Ç­ªû;çÜsîEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE¹ˆ6¢¬2¸ß<ôT¿wuo`ÍU×>ý‘+È«£(*芢¬!ß÷|Ov}_o_.“l/­ääÌçOª¨+ÊêÅÕ&P”•ïòŽ×õºÓ›[—Ýæzî­0Ü#ƒ N"à½Ñ?ù4PÒFStEQ–›7þZo_ö]î Ä€k°öˆàV!¶QЋPÀE|ùjwn“цStEQ–‘˜?2¼£ÏÙ(7¹»`°Ç¡Ý H?€@ö‘ÈB  £oº¢¨ +вÌxè~ÏËr»qñ°Pîr!k¸ <ˆ€ `Àˆ›(¾­ã犢‚®(Êr¡;·É }¹ äíé&éÈt’+ñIÃò,º_:âkë)ÊêEÇÔe¥1~É¡o³°ÌÈ‚tE`¤ÊLñJEDFÄ5çP™;ôè¡@OQTÐEY =>äÀëê5®3HA¯ˆ8P#â IˆÇ@ž·‘‰LPÑÖStEQ– ï½Ï•¼³YÈ;Dýa3ŸÀ0E¾ÁBp¡û[nWtEQ– ݹM´½‚èéˆÈÜ ¢H+Ęe0¬ávEQAWe™Q¸4*RÜ&ïoaYĔг^Å\QTÐEYn01‚ĉ›§ÇÏc  k¹š¢|ÿ ek+¦oケ}Uù¾]Ä#¡=Ü‹´@J>W1Æd\KSS¦6ŸÀ0ªãçßïåjó¹‹ñL(Ê"£‹³¬€Î(iÁ cAΟ.–G^ÜóX%Í~ªs~÷îÈõd‹î âJO 4àPlËûlÒ.û0¡m¡q(v^ šÔiãyݯ„}ß™…-n4´‰È߃È ³µcè$ H´Çùíòص#ß™€wÊ“€µ™.©Ý^µB5Ÿw§Á3Ñ–÷CQTп¿…ÜsÍöpÁ ìËA x”Ãö¹Û¶Ÿ¹t ^g\³ŸlƽÁVçüvÍû£( áCxÄWJeçØ¹ñ©‹-uZ±Ž¬tû‚ ·`½¦ÝÇz;r™›åŽé¶­¶Y&ä”XÿÉÀí~ùÐÖG i¯}NG÷k^m[»ðÊf§7—íÜæ vü÷‚X„õç³]¤DâhŸ°Ä ” é`æ³H×0 ÜѲk϶e5¶Ø=ªqÕ˜˜ç»Óø™€OÁ9F&óÜ»6|ûòõÔt¥e±»WÄ„ n€‘—K ÿÒûÙñâÄËOßòK©D«:ç7q=˜É£°FHðÿ È# …F'° .À♦FGaX›é’‰\ÅtyNoòƒÕȼ?‡­¯Ì«­eY cèà| Àßð£.¬T¯<g½Éã.㺰 ”íÕ7X`ÄÈÐéMZE«ÚA9ÙgŒSÓAEs~ S‡%@dE°À¨ž}áþQ4Xns÷ñƒÞŽ“™»®¶óÄ­Bn£  Ä„"†ç›í+Þ ïèónáö‘>œ<3®¬FL˜å]jeiÐéóÍr‡—7»Ä5€ÜKbPÓ¶@àv!ÞÀ} CCï1A¯s¯’Úš¶mìš ›5 Â3ð ÐG‹,'ñe’@ŸˆtƒX§•§E <@œy{µ+¾Ø‚üÌ=ª7Îo „À'8œw2ÇݾW‡NöŸ>}üà†$ZwÜñœ¾+2®½—‚‡ÜG‹õ³ž 2@x• E½}Ü `'€]Þ ` 6` ÀÅë•ײÕ7D  =4š©K£’Ô±y®·/©ƒªv²öÿU×€x õ …FCmç @8ŒŽzTq\|‚²ãìr˜Ž,fiÐ:=Á HxÝ3m (ÑZeó3 ¦ÛxzŸQÛR¶sÛcÎ5ÓtPª×+½˜»ð ëÏg\u§žèOpd ‚+N:ðߥ¬U¯roÝLH{Úð° #„a\ÐkžTÖc4ØåfÝâÙÃ[3r Ƕ4ܳwÇt:ÎŽœÓãH°" x/(æ<"Áï|¥¥¶Rôºlðzƒ¿°â^´$qŒ‹MعÈtO -f:õxǃ²ä¬Jœ¦È³PÇP˜s¾s=šÚÎ3:ŦûJ¢ºøÀ]î!1 ¼OË6[´ng¬V=]N‹cضâ| wš{%q­TÛV@JRº;·ÉH¥Ð‡»@ÜC°C(¯wÚÓ ÷“(挒ک­PƃÀ^@¡8žfò™úQl#£È„À óÛi‡¼zœyøÂ?;Hô‹Á Ez3nÖÙÚcަõy½;€¡Xz#ÁçA÷²DfÎwH `\Àá`2][)-Óà €O8à¿ðœ6ËÊtp;€S’Jm.ÈGÄ€ÏÆþ~t¥†ØgyÓÄú$±‰»X€8$6ÕïØ@€p ôŽ;¬·ÏèÄ …zbs<šªÖ„ÉY÷U—ñKóY8&O²|mè6rO€ —mØÙsöùÆ”1±óN4dRÜ«Ys«#]mx°J5{tq#ï»Î%è73lezüœqM‰)&Ÿi•d e#‹ÈXdƒôÁX¬àÍ$”ÈP˃Ü,à½tY;2G>Æý ó.Z½³ç¸—a ™B³KÈÛ£$ÃLt~ oŠT EO“á‡ßðßD?ßà÷ܺB¯¥™–­jAß àQ7j„Û¼šð9  ýüõ„¿¿°RCì³¼éªØÔñ¦«m!Bß@l¾ï†^o ã8÷T;6„áF¤oŒŒ2)`/}dØ©Îßø°ûŒƒ‡ Þ L{4CÀ)™Y|Ä ì¤Ì-Í‚O"yiÐ…J‘m€ Œ_í¼ë2ÛRæ”çÒ°6|¢ø¶íÎöŽ‚8dk -ûÈ6ý$çd·Kø~ŒZ²a2ÜŒwŒ€“G›¢2Œ+ŒðÁSp<0v8l³‘}7€ÈáMŽ^ˆlàn3æ>u~çX4¬’îY¤¤yw|@¾D#ÅŒ‘;EäZÞ(‚l I|.¬} ±ùõ}„²üXlû¦x iµlÕ ú©˜vÀk)®–À7VlˆÝrÈ>,åiO,Ö+Ž ¶kÖåg÷,¡ E`D o‘xà%’ï†È]ÖÇ¡Uãƒ}"1G[ÄéÉ9›ÅÚ;! ©ˆÈ\9‡©ñ™¹Ê£ä2ëËM™NÞAà‡g %8X!„fILŒ~ÄIJ#Âr¤»UÏ9¹¦½þ`•ksæVúæ+•G†×œA‘Oˆà«Óó`z( k»,ð‰0ÙÙ9‡©8 â‰ÀgÝrµéßQ,‰ÑJNÎ<}ó•J1OŠÊˆ ÈEX£åa?0'ŠöÚÙÉJ0¹oT„Ü+µFe4ÜÃèYZb—¹ÍáøÙ¡Ç‡ÊµmÕäYt ô%#7HRtÒ 9NòÈ€~Ä^¬á$U Ô¾o0:óÞ"RÐQ³}l…ÿ|µlU ú±íÿ…·šqslûåDk~†Ùw¼í®‘’Ù#"ƒ¼À:1$GA^p r«}u„w–—a2™Ý@­Ð³:XÚW Í”ãš} 7A¤/I’=æÇ \00÷á‡Ä˜ZãC°lãÂáâ#…hñéf/>2»ƒÅy+fÖÒ Õä2×àa÷FVþ’¾ˆ\1E O€ÞFMBôÃÞŠîd-€ À«Þè4¹_ÍC¶rÀ~žûGzª²¶Î¾ZÇí¹AW¦·Óí8`l=£Á£>q¢R”£þT±P­÷®Ý¾¼VÌ“¢2´àiŸð ÁóN÷Ø¥/n™)AÛ}ü`ikOöhƱˆŒÔ÷ÇÊiQd…ÜN#{,²/O¼÷¾‘j…Á~î7ß¾ØÝÏBñ#fæYlåþ H tAd£v!Lfôæ]QÚÆÿàßE?ŸðÓ+ìüç«e«FÐ{ä! ·¬Äpûý€ïõ:of€Û`d-HÀð"ˆ¯Cð‰^@>AW¢ð’V„㔪—Â`¬lˆŠÐ. p”OV þ±r!¸œïƒtl$8&¡·ØœMÀDÏÛnÓ½E,äCa‚ Æ…˜¤ÈkZñøQ¸4*벆aÉ“iµƒíÎùF=ÖÊÍBÞ*@'É«"òˆ7!(‚ØK‘ŽxÛ6~à$®Š‘Ó¤}•”Wèkx¿Ò†lå€M*½z|ÈÁ{>Ú#kt"Ì…H”t ÆAÑŸ*þbÏcS $Íxæ³Å|ÚL)‹‘Ó xØ/ÏŸ-^ÜùÉYÞþ‹{«àøÁ‹ÛúcÇë±=ɨŒDÝÐ+´72«àvº(MÝàˆóCy–›#C8ÕýΗ"0€làfÛdcÃ9-EW”¶ñ«þ€µN„FóŠa!ZÖ6®÷â,·$üî«)¿{ÛJôg‹ ˜ È‹F^ø-B¾ ‘?++IÊËE᥅±4k(rª^†C1—£6À“eß=[.üŞǦ £¨Xk}#¢1ÚtßxÎ^@¶\ˆ ‘ËBƒà0‰·¶¾<ù\Åé!,¹ª¾®·4èDѵ ŒÚu‚ þ !ÿÑø\à9# ÚÖqF^ ¬ã7s—©Š91`Àúös>ÍgY‘§ݯv,–ÒÂèV„>Û0Î[w˜ÈL‡²€+OÁØNwÏ¥z™é/îy¬âLG@{žÀ•‘AGSH¨Ž ˆ!è†ÇçE1ÒôþFBmL² ‚APÞÐ3ß‹"S5ÿŠñ£ïiBÜÒpÀ7W˜˜/TËV‡¾3áfžŸçwWFBœ°8÷ø˜ƒ©×hä(È„yÅ/ù¯WN{®-ÀáV¦«l’öczá›HÜ(€ØKµbÞòô­uŽ\3p„À1 ž‹IˆuÙ‘ ë­Í’jBÅ ìÄ ¹+\s³³÷Þ|¥rêLÏyžˆ´ûe À ˆÞÐË—‹Öò˜|Öžk|ÒºFdÐyÀµx-M5@›Ø à'ü½É¯ÜS8ƒ"?/z*ÖNº9ÿÂÔ®}—”ñpøva4óD§…«^¹ZMxÜièäÎ+ð¥¨}?vü ‡þ:"ž`,² @/« ^€Ý¾áØöÂ" i£Fªe|uޱP£¡æxÇû¬dnƒÅ.ëP­Ížýºú¾‹ÇmÅœÅK‡–d^óC[ÆK=·ñt¶X­¸öÚå‹ããǾ{¬<44¼>Õðݵäft"4‚‹¥Å“*ÇF¦Š#ÝEÏV×èt»¾ c»üãͪÚÀ€OGýN•?Âìù56FŸy4áûÿ¢Î~×øíXßú;‘äøCÿ]ôûïøŸRœëžè;@˜ œC8KçßøõyDLwG}êÇ£þü%î£ûüƒ¨n‹ö= àyOFÿ®-‚–­XAß‹ùÍúól‚Es{“pûNY¯I/MšsýÞà3þ€nûkŒ‰n¿à‡æz?–i¤öPÃN‰†‚nJža¶÷±iŸWôh€šìëÚ¬èǺý¹æ!Ôöz¢Éåj)Ú·ÙZâqc ļŀ‡+ÿعB0ݾíˆ(,4jÑ£aÎñ$;(Â=b¹õk³Ó݇v#ì÷ï»ÿ˜xö«°8fÒO¼~_¶'ß_·ªsÜA.„sî½ò§ßàâ‹{~væÝáþ©‡žêg‰–¾ÀŸ"œ¯±èc•ûŠ:Î#uýž¨/ÜûýHôÿ¿«s8Þă¼Àÿ eauÑp$ê›"Cc(òò'ÅuoŠúÑÿ¶æwŒDÓ.A]‚üK`z!¥*[¢Fû<¹Z¶býk‘×\û`|­f{3s²Käô!99⎂þ!ÿ9úy, ¯ÕòV“ÐÍÏø?£ãþ0€¿®ùû«³/«ìkg5óP«’]äA–yÖΛyS[ƒ]Ó‰>ÝÆÄ·ùx¢ÍÊÕÒî¿‘7]k,èŠ&¼¿LÈ+FðŠÓÝséÅN³»ìx&Í}ô‚u" +hü|úšˆ­™sM ½máÀûìÙ}ªèŽ@ÂÅrD ,’ µ¥|î#áú$€“`ÄÀ›5‚ýŸ–s}Àû£þ©Ê¶„}ÿTôÙxIÞYï¸À?‰ýídƒ¾ñŸøT´¿ÏF}åÕèï?àñH á¬oÏøVƒkÿÛþ﨟G,Šj— ¾ÀïEÆÈwüûèØ7øhÍyеK;µ¬íÓR —©þ»#Að Ñ¿©èAy Éc IßE4Žñ€_‰¬Ø%|·Q(è_G…kþ:ö÷ø<òSíl Â¥QÐ0\IÌ4ÐÜpœI0ƒFä•FÆ-Ò¶LéV=ÙÜ>aVÅñ[éűÙþED¢¹Æ»Ø`Y/“8&ÖöaߺmËÉÒÁ·[I ^sƒŒü6>ón—D£§Ù5µéxKýîL-ˆÑ4Ùù‹Œ àÏæõìGX¶UË·£>æ‡"±ü½ÈSüeÌ­ÑÎÄ~þÝHÌ#ê㞢Džvœ3uDôÓQ(Ýð'~²FÌ]ǧbÚòê\wGt-Šä¼““‹ÜG €ÿÀW"ñþ½(ÌþÏ¢¶ý‰È3¯^ÏáEÔ²+èqîª#ÊÍpb!÷³ÇnîðÇQ¸çß"´¬'cß-8Rg¿? ࣟ¿ƒp‘4 õ?ÓÎIU²>މˆ¼â™¸ØÔÛ·1Öµ„ƒ„„¡v”W-(:Qv:vÕûü¹:¡ã?‰m á3?€pL{_äÕ$x®‹ÝGÿJ$ÜàÅÈ8ŠŸÃµÈ BŠðú|µlE‡Üãü` žs-7cv’Û Çaž@˜ÔðTÍßâµê_ÅLbCܺýõšíßÅÜ õ³Ç¨J‘UÝÖsŠ«p2Á[ôí3•‚ÿµ³…`äÐõÇöZØ÷u™«Ö3„‘ñŠã<ƉÓLX3³zjÔ¾ ±K oá¾µeá”C º‹úü4i§h&ºŒjç—(;¿ ¿Tóó¦„>ñ·#/ô_ÆD4þŽÔVîL"‡®~>>Mö±Èùù „ãÃUcà_E!çZv ¨í¯Ô¹–Ó±í- Ÿ)G}õHD×&\˳‹ØGßàj¶ÿ ê/dT=ÿfIuóÕ²U#èùK*m¶ø ßûãÈ:<ûÛݱí/ÖÙçÅú?KxP~3™¥…èçWÚrn–ü4§Cª† kÆuç»ïiÏ« ™Ò-³o(cìä6ˆÙ#Ä X„ä²ÐƒËx„ÔõàZjß%(%k!!nÁ ‡¤1ª–ë¼æ-´S˵óKL܈¼è;<â¸ø¿•`ä%y­cÉ:&¢Õzö—Îé0;œÿ‡ Î_šla‰]­³+á¼,b]+æ‘QSQÛ^$-k;×+äþÞØKw¦ÎÔFÐïBX ðK Ÿ—>®³Ïÿ¡æç׆ñ«Ü€p¬çWj¼üÝM„Ö½£4!áy† Sï{ááù ‰Ãñ>÷VÜ ¯QrÙ|Æmg<8g‚žpÍöù‡cgøœA zëîo¡éf÷­Ã$- !,þø9ÃúÁY‰™ |¾gŒ‘hìÜú¯rݽó8wÌzÃ~é1ÌÎt¯ò®ØöKMúÛÚpð/",çLáxÕfŸ¿ àŸ' ¦Ô‚Z¾‰ä°;úèõËÌjõ ÑPåD$ê\$-[5‚¾1‡¸ ÿ×K$â7憘Å{ a¶"¬àZáÿ+„e-ð9„Y¦Dá£Ï¶}9¼fI= êÆ/9¤Í‚’èMN{¦ÀÅ|w)ÇÏ'Þ{Ÿk%;`ˆmYÞ@[Ä1MÂ]M\iÖ¾-Lź i)âÒ”ó¯o–Ç8¤MÌL›LŠÚÚù’9»Ì¼s‰ ºy—_®óù=±í£ ö[+šýÑïþ<Å9} ê «<Ûäóƒ±í3)Ž¿Žg±~(ö|½ÎZÖv®WÈ}¾cÀûb¿{ɉNø\Rçðñ›ü „åoD¡Ÿð—u¬ä¶‘&$ À§à»€9nKH5™Gú‰I"Ï8nX^ªÌåfeRsÄqã¶)'¬IŽ]ªR²TÏD›I¹(NâZñ­EIêsÏ·ÝR¾;ÀõªOÇ6kb¿û× >_íuE"—ÄÌ®¯¾áÄ1i¢ñg³Pr|Ì“óÄg±~l»Ëm/›ñóë)èóµjnNxè?Uç³Jôf/¢PÔÓ˜›²h¤N~šÇwºÉO®O¢Ð’Û6ïny*Ó%?_ê„8` â8QOßæ‚Åƹ›Î¸0Cá:ÕηŒp’—$:0{²”/ 9LÑ,!,KÃîØv³ÈdÜÙz¾ÉçûbFÎ4·X}t<¼ÿÆjóЯGÈ}Mì&Z„ã&iØÛ>]G¨ ™Œj9œò!üâRŠyµ³[¬nºr°ëTƳo(c²vìÌM†‹‹ãBÆÏ›Žw§ Ç.ÕøùõXamQ#1éó8*"2"®9—¦ÝZh'ŸÄ0-—Mí|Œ{bÛBâÆì¥:£Ágã‰Á_n¡‹¯ v®ÉçïmÿÕ<Œ˜+‹ØGÇ×+¿xµlÕúž+éZZ ˆm?^Ǘ؉°¢ÊwQ†¸›âÑÇåÖÙÍ7ù©ÙD!­Œ·›ÝÇzÁÄøzX³«I2Ü¢Ÿ·"$K5~¾” qKBª<ŽªÍó’Ê‹^,Caz£9.~¢æç/Õñjëõ™_náœÖǶ¯4·Úkø:g‡'y·Ï.rŸ‘.M„ºc‘´lÕzü&žMhôǼ'…µù¥:Çøª‰½€gIDAT¡˯*üf—a¬}¶'Å5Ü„¹³-Zg7o/µ<½ÞQ"ä ¸tÞù~î7;2^Wæ.qÍ$n¬› ·@qL-Œ)…d©„¶m qÜoúÒ§³ûžûµüC_útÜ¿äï|KQ’½èôɤ­ KL³Cãê‡n·"œ¥*°?Ý`¿Ì ¹iá¼âC`2Â4&¿1-¨'èíê£ã}Ç} ¾ßƒ0kþØ"iÙªôø¸Œâ—ÖX¾ûÜ@¬aЍŸ€þêBw"œõçGba’Z>ÒàÜ7øM„Ùò[ÒήE/5E9!¨@p' yj©¼ópyNs#€Ü+‚µ ê—-DÓKi…d!Æ÷›¡g†N<ži&¬í˜!n÷ñƒÞÐÉíëóïê¾cë¦}Ý·ôîzdxGß’‹zÕ°„Mœg`!^tÊâ–ÝTµ ž^­x¾ÔÀÓûÅèZÇfy7 ƒß‰pnõ*SH®ô©G|\¾¿ŽÔNózaY#sÊþºÎgÛÕGÇ—ØþMÌüfcÔÆoø;¨?}íB´lU z¼ÿƒ§Øû¹uÿ(úÝpìs÷Ç^Øo!¶šVìA®åA„ã./D¡¬/Ôü-ž‰ù[×Õ­¶Ma–åg–a<‚p’ƒ_iGc,ZwßP†4ÛA»7^V µ“x§•éc[†K8ÆX7Ÿ«R µ[òv!o±¬;gø‚:ã<ÃTBÒŠñ5-´k:Æ04„}ÏýZþG^Û²¹©ÛÑyí¶Fšºn^@#¬eœs¿:÷kùmyg³éÀû3Žù{b亞<â¹ý¡7ú½¥ôΛÍ30“˜É1 Î¥õ¢S·Ó2œª6F|ìy¸Ž±ø „ê§£ï4›’ôþØö ÔŸ-‰xßXoIÓÿ±Æ>pÑ•f†÷ fOŽóRákWýDZíw! “ðy„ãßVüi¤U_]-[4®G–{GB¸ég&(ü-Ô㉗¡½Üà;bÛ›#/ýÌ-½ø„«ìÔ†w¾YÀ“çRv¢pÓ¯FIÛÆI£ªÚ‰Òdnò6ý œpµ©p-1’%I=}ì¼1{g=]½÷ŸùC¿¿Ç]ë¹vŸ>b޶®q¹€ÙëZ[Þ´yösêLíji—“£¦2ë×õíp=÷Vo0*¾ùÂCoôO&­æÕÒÌgq—áøAoÇÉÌ€Ír‡—7»Ä5üAZä¬$ËžnúKfÈO/Ç ÎÌ3ÀØ=¿pèÇ⸑te“K½´ì"?ÿxäMþi䏸Qÿ÷S×åþI¤›´å# ‚Þ ÿ9flü2Âò°«±cüfôó‘HLGRì;®>Üà³íê£#ŠÎÖFp³1Cå0ÂùïÓ”´ÍWËV• ÅÌô|ÕpÊ œ’¯Þƒà!£©rá¤úõ(Ö„šF#‹ë3H^¾î÷#Këçb¿ï‰þ]‹,¿‹EX-'eýok%Pû†2´“Û»”™N”¨zæ%1æ{´xÅ&, ÚÎçK€Aw yr½ïçkvÃàa³‡ â"€5t‘4 Mó¯?oóò¦-Ô³ VòßlÎæœ]&‹ÄÚ]ÖCÌ !Žtç6™v‡`ÖíÈåäȧ7ïô›<î2®û€»h¹C€!/¼DÚɉ¢»´‹îÔ™g`:CPp‚&à)?eÙäR/-»HH‚ á"&¿X³=yŸEº¢ÂÌ#~¹Ås;ó¶ö¥¨¿EX×ý£‘×ÿ¿G‚Ÿ¶‰ ú—|¶]}t1ŠÖþs„µë?{µZê³h­ìl>Z¶êýg¢FßYŒÿ/šgC ©]_ŠÂ/Í&ø©È;…Y•8X„ã$ÿÂYçvDÇF˜ñšO‘8oÒM+‚I‚3âLüñÐ!ÛÈC &Æ×›¼»K™ñΣÐ2ïˆÈiZœùWXŒP;¦×nw Ò `;(·˜Löf¡ý0DöÂÒxbŠn¡ ¤iWgœzÁ—"é'_c‰>Þ‘ÍyÛ@Þ#À2 @ ´‘z{hõ8F¸Ë˸as±[Dä ÷X+€à2EŽ ùr~ïÍW*OÏ÷ÆbQ3ŒÒ$Zt8¸Ý fî«Õî‚2À‹„<.SËôÏã̰܉ïLÓ„ÈM‘#s)ꇞDk¥Z^ä9‰úÌ3h}m÷©È{ýÝÈó¤¼ÿ-úù*€ÿyů·¸ïÚñó 4NÖkg]@¸0Ìë¤e«NÐßðÉ¿SŽª´<Žô“'Ty±‰×ßvRO"0 ºAn.g80tþñÊ!ž,¡f9Op¿:ß“ &œõn‡s7D¤å1zç‚2 ayŒÖö{”äôéb¹ý¡öxÇ"Ü "÷í:„5¥=¿CÊ· Â0©3na’‘ÅǦ‘,JÒÚÁà ‡6GžÄ8€ïä]Ž62PÒLò"¤'"Û,ð בû ½´Ü""kZ\$.ë;ÇÎOÕ½ßâ–ÒxíÑÚa¯«wèñ¡B’´ûøAokOÇϱû@|P€­Û%sÎó£iÎ/éÝi>b{fÒ[DâãçßD{Vqü XÊE„c÷›æ&­‰<Ý7#›db‚þ…”ÆÆ’÷Ñ‹ e‹î™Åß9÷¹g¡, ´4,ûÁÈÔFdªŸÅJ¤Ì¾-D(Y·â¬É›žÜ¨É¹×àš’8âB€Â€.|›µÅJ§(­ ÆŠ=,T2$Ec%ëTœ5cNonDòÞ„8¦,FìBÏß*½Á¥‰AÿJ¡~à$;<'0žSa`úÖ Ï%?&Y·hÇ‹½ÁÕbwâw]'pú;ÆÜuL‡7&ÆéÏÍ:vªÒë_¾¶9¸2U÷Ü$ç•ÝŽ+Î@Ç÷$ã^«×&$¾Í“å~ÿòäF;^ìD`M²®G×mdZa$ã„÷¯;{Õä¼IÉ8EqLe~íšp‰ŽE ˆçø¦3S0Þ”éÎŽJΛ×”E„Žé_š ê³ÚfnÙè¼âôç/JÖ›Œ·I¡o3,Tz‚±â@pµÐËb%Ë™Î}&Çß¼ß× Üþü˜³¾{Øä½±…>óíæoûô-çŸ:¾±º}ãÝ7|÷¯?öæjî÷F^~«ãËû_§k¸ïùÍŸ>±ý‘{®¨"4çO¶þÄýËÍCWfybtmÙϲ¸s;¤Èæ"…eß Æ =,ûvd®I‡7)žS‚A +A–S•.;Uî´…JŽåÀ%q@²NÅéÍϧã\°½H *cýÀ©ŠÓ“›0]Ù±Dáðßdœ’¸¦.)Û”tY ²¬ÞÜýWý„ð|¤#3)Ž)CŒMÂÀf8UîbÑŸ-NÉ'!°5Â5Óþm1¦f' €ˆpúx=¹‰Ðxp¯‰çá˜J³{.Žø&ë–¬çø ¬™ûò­7»g"ï¿`<—PTÐW´”OwJ6KߺM=]0Fß: ¬aÙ÷PÛQ‘R.Y€—6¯kiøÛ,çN"° •ÎFÞn+bÛ(òA2J}S±$ S‹ãX1bÃvl¾q$ÏTÄ1%pf¼Âšk,ÈKžu¼éÏJ@q¶d-Ð+¯=ñMÖb§7eK•løŒZ3[ÔëDŒòÞ5`¡Ò9%*úÞô2©ŽY°ÓÜ`‹µMôÜIƉ†§–ß;r²»v;¿¡¯Ø±y ²Úû¾ÑSg»ÀëÊû;ÿáÇÞV5PA_Šž&„Ëd¡ ‹ÏdV§nÂŽÝd_ò^q^^Ú<™+<ÓHöÈŒ¬Ø\áBÀ´b[÷¼ (ޱUãG/çÝ’xNE:¼kâHóNtŽë»ôéH­‘à+žã›Ž…·¿8âKÆ)IÖ©47HDØÎû-F,ŒÁXa:lF:fÍ #ôÕˆQ$ôÂ!Y|S\˜Œã#ë”§‡ ¢û%§dº³W%çM Nâõäo|g– ÷¾ë†k«±ŸyétÇÀ{vLÀ¹/}½Ÿ•þÛ·íü™_PPAÿ¾qá!Bɸ¾äÝ¢xag÷ßç”c*♌ñÛ#i(< <22J¸ª—;`„& •¸¦ÔÒøyíyue¯JÆ)Á1¾¸¦<í¥;â‹kJ0´íTëÁ )Ùª*§~ê·¿°5»¶§tßïþ£WÅ1ÚÍ« ¯Ánžg„’÷Šn~DrÞ5Ôé°ç+Pm!Ax@2NÉteÇâYãܪãTLGæšÉ:S0ÆŸWg옊É{ã̺SbTÛf–8ÏózMÞ×)ÑÒ#mVI:Ö"ßïÙÇ6eéÈtpªÜmÊAf&!Ñ]34¬œ’éɾ#WŒpMy¦Œ3öÙeÈ…ÿò­^vVSvnê/¯¶^nüÍ·sôsì“ÿ~'æ <ðGÿâå®-ëVݵ.D›`éz|ÈÁ{>ºÑäÌGþ¤@Þ2;gÚJ @ਵüL¥(Gý©báòäq[sÕµOäJeÖD3KÈî㽹̀ä¹p¢0b0Æ‚œ?],¼¸ç±épçC¯:Û“ë¿ÃXû÷!ø( «ÓF³Ã•H¾Ë?°|/}ñ{óž dÿ~sÿ`žý*,´±m¸ß<ôT¿wuÍÌœè‹v–òX ÇÞ÷|Ov}_o_.“lo 4’5=éP,}Ž—rþwÇNÇó… ²Øír½Ÿÿyòw®vÀq„“´Œ¯ÂînÂÙä*gtûÕUz*è߯4µ˜ OBø¬…|š’?zhë£Ë7$žzìйÇó½üÇ Ü «fnoBdÀß@ð[Áøø‘C»~vRŸ˜åuo×fºûª±¹BVQV<r¿¬’E%b¦áûtšéëÌ¿=k M#ç˜r MeÝ[EQ®+š•p=¿ä6 JÓE%æ³ør¥ézâD‚³Ž¦[BSQEQA_ž¢6ã²Z!Æ,ƒaT&—ã¢-3ñÞû\É;›…¼S0³`Ê´w\!x Ö5tKh*Š¢(*èËJÔWSB‹ë€/WºËŽgP]Ÿ<\»Æ;/r4/ˆØ38vH³`EQZ@ÇЗZÔVËø9÷›¡“;]8´ó¤ß,ªáúä‚ €Ëœ0OùF½sEQôåN¤°>aTÇÏ_ZFcÉÕR¦×zû›Úëäöá‹ÇÎ*O›Ã¾¡Œ±“Û f7L?{‚€Ä;bäÏø°oݶå¤&`)Š¢¨ /_fÆÏÍ Á^˜p¢Ê¸hÒè\[6ãç»ôvœÌ ØuÜázî­"|/ˆ »Sþº³3óÜǸÿÒO}èñ!ÇáxMæV!oÐ7n'J"x‹¾}¦Rð¿v¶ŒºQËEQTЗ1ï½ÏíÉ9›ÅÚ;! ÇÏ  H¥Pô®¿¸q¿:ß“ &œõ&»Œë> Öîd DJº"<ÿÂý£H*oÚ7”¡Üؽ lƒ -àUp™"¯Á+NwÏ¥w>ª¥jŠ¢(*èË›fãç5Xúbóëû®ïÔÜoÞÑg}¹)ÓÉ;`äA{ Y ² [I§·;·É4õÎ} L(æ¼HÈóbíaÔP»¢(Š ú ¡ÉøùrLˆ{è~ÏËr»kð0{iy“âEsÐ;´ðDh¦.Jsï\2V¹(ÀQx²ä;ÇÎOi¨]QehÙÚ‘ºþ|™M(ÓóôX#7CäVY"aõü€#¤—ÏUf]Óîã½`b|=¬Ù%pì´/ xÔx²ì›£çƧ.6L¨SEQÔC_.¤?_f qE×öäœIX¾° 8†«£!{IÙxùõ÷Ÿø º‹žÈUÌ@‡3àŠs7D$qc¸à;/ DÅ\QE}eÒÂøù²JˆëþÖßìýð0Mæ8ˆÛAôÈ× [ æ/¬Ïvž“ãçMÆÍ|P{Aô@p™ç¨b®(Š¢‚¾bI?VT«UrBˆ b¹ âzôP0tnhÔØÉW)æ ÛØÈ™YÞŒï#[DdL–4†Â^³™dw(æ8sEQô•ÊÐãCŽÓëÍfz@ `£YÒ,ªÛÀ%Xž‡±Ëg†¸c‡Ê²÷ÃgH÷ß#] :I:ˆ%\ÖXÂF†‰ŠOáÈsB¬=%À©˜+Š¢¨ ¯DEr‚sS›`' ý0, Q1NpXDÆ@ |‚çA9Ì@Îï½ùJåéepúU/Ý Š'ààyXB7BÐ À!³ °B#p–Ö¾ 1/ø%ÿUS’ÓçŠS#*抢(*è+‡|3 àˆ7!ð)2 Ÿß h‡EŒ"ì rþt±¼¬Ê¸m9YzdxÇwœ²<á¼AÁA¸"3‚y羆aqÜ÷í«ß?sitlôØÝã¥fs¾+Š¢(óC´ –€hr–Œo¶ ü>ߊãP,}ŽO¡0 $f << EOF Presentation exported by Pyntor EOF z=0 echo "" >> $f for i in slide*.png; do echo "" >> $f if test $[$z % 3] == 2; then echo "" >> $f echo "" >> $f fi z=$[$z + 1] done while test $[$z % 3] -gt 0; do echo "" >> $f z=$[$z + 1] done echo "" >> $f cat >> $f << EOF
" >> $f echo "" >> $f echo "
EOF pyntor-0.6/tools/pyntor-components0000755000175300017530000001356710416470151017475 0ustar studentstudent#!/usr/bin/env python # -*- coding: utf-8 -*- # # Pyntor Components - component management for the Pyntor presentation program # Copyright (C) 2006 Josef Spillner # Published under GNU GPL conditions import pygame from pygame.locals import * import imp import sys import os import os.path import getopt import glob import sdlnewstuffpyntor version = "0.0+svn-20060225" def componentfrompathname(pn): fn = pn.split("/")[-1] component = fn.replace(".py", "") return component def list(): scriptpos = os.path.join(os.getcwd(), __file__) scriptdir = os.path.dirname(scriptpos) installpath = os.path.join(scriptdir, "..", "share", "pyntor", "components") installpath = os.path.normpath(installpath) homepath = os.path.join(os.getenv("HOME"), ".pyntor", "components") componentlocs = {} componentfiles = {} componentfiles[installpath] = glob.glob(installpath + "/*.py") componentfiles[homepath] = glob.glob(homepath + "/*.py") componentfiles["components"] = glob.glob("components" + "/*.py") for componentdir in componentfiles: for pn in componentfiles[componentdir]: component = componentfrompathname(pn) componentlocs.setdefault(component, []) componentlocs[component].append(componentdir) fmt = ("Component", "Valid", "Doc", "Version", "Location(s)") print "%15s %5s %5s %7s %s" % fmt for component in componentlocs: first = 1 for componentdir in componentlocs[component]: try: (fileobj, filename, desc) = imp.find_module(component, [componentdir]) mod = imp.load_module(component, fileobj, filename, desc) fileobj.close() except: print "Error: Cannot load component", component, "in", componentdir mod = None continue valid = "" doc = "" version = "???" loc = componentdir if "component" in dir(mod): valid = "x" if "metainfo" in dir(mod): m = mod.metainfo if m.has_key("version"): version = m["version"] if "doc" in dir(mod): doc = "x" if first: fmt = (component, valid, doc, version, loc) first = 0 else: fmt = ("", valid, doc, version, loc) print "%15s %5s %5s %7s %s" % fmt del sys.modules[component] def doc(component): scriptpos = os.path.join(os.getcwd(), __file__) scriptdir = os.path.dirname(scriptpos) installpath = os.path.join(scriptdir, "..", "share", "pyntor", "components") installpath = os.path.normpath(installpath) homepath = os.path.join(os.getenv("HOME"), ".pyntor", "components") searchpaths = ["components", homepath, installpath] try: (fileobj, filename, desc) = imp.find_module(component, searchpaths) mod = imp.load_module(component, fileobj, filename, desc) fileobj.close() except: print "Error: Cannot load component", component mod = None return print "=== Documentation ===" if "doc" in dir(mod): print mod.doc else: print "Warning: component", component, "has no documentation" print "=== Meta information ===" if "metainfo" in dir(mod): m = mod.metainfo if m.has_key("author"): print "Author:", m["author"] if m.has_key("authoremail"): print "Author's email address:", m["authoremail"] if m.has_key("licence"): print "Licence agreement:", m["licence"] if m.has_key("version"): print "Version number:", m["version"] if m.has_key("homepage"): print "Homepage:", m["homepage"] else: print "Warning: component", component, "has no meta information" print "=== Parameters ===" if "parameters" in dir(mod): varargs = None for param in mod.parameters: (name, description, default) = param if not name: varargs = description continue xdefault = "" if default is not None: xdefault = "(default: '" + str(default) + "')" else: xdefault = "(required!)" print name + ":", description, xdefault if varargs: print print "Additional parameters can be used:", varargs if len(mod.parameters) == 0: print "(This component doesn't take any parameters.)" else: print "Warning: component", component, "has no parameters" def update(): pygame.init() pygame.display.set_caption("Get Hot New Pyntor Components!") fullscreen = 0 if fullscreen: screen = pygame.display.set_mode((1024, 768), DOUBLEBUF) pygame.display.toggle_fullscreen() else: screen = pygame.display.set_mode((800, 600), DOUBLEBUF) pygame.display.flip() engine = sdlnewstuffpyntor.ghnsengine() engine.conf.providers = "http://pyntor.coolprojects.org/repository/providers.xml" engine.conf.installdir = ".pyntor/components" engine.conf.unpack = 0 files = engine.gethotnewstuff("pyntor") def main(command, arg): if command == "list": list() elif command == "doc": doc(arg) elif command == "update": update() else: print "Error: invalid command", command if __name__ == "__main__": command = None arg = None try: longopts = ["help", "list", "version", "doc=", "update"] options = getopt.gnu_getopt(sys.argv[1:], "d:hlvu", longopts) except: print "Error:", sys.exc_info()[1] sys.exit(1) (options, arguments) = options for opt in options: (optkey, optval) = opt if optkey in ("-h", "--help"): print "Pyntor Components - component management tool" print "Copyright (C) 2006 Josef Spillner " print "Published under GNU GPL conditions" print "" print "Synopsis: pyntor-components [options]" print "" print "Options:" print "[-l | --list ] List all components" print "[-d | --doc=component] Document component" print "[-u | --update ] Get new components via GHNS" print "[-v | --version ] Print Pyntor's version" print "[-h | --help ] Display this help" sys.exit(0) elif optkey in ("-l", "--list"): command = "list" elif optkey in ("-d", "--doc"): command = "doc" arg = optval elif optkey in ("-u", "--update"): command = "update" elif optkey in ("-v", "--version"): print version sys.exit(0) if not command: print "Error: no command given" sys.exit(1) main(command, arg) pyntor-0.6/tools/README.tools0000644000175300017530000000201010416470151016025 0ustar studentstudentPyntor-minigal.sh can create a simple HTML files for a collection of images which can serve as a quick-and-dirty static picture gallery. Useful for the PNG export option of Pyntor. Pyntor-components is the tool to let users manage their components: * Which ones are available? * What is their version number? * Do they come with documentation? How to use them? * Can I get a newer version from the component repository? Pyntor components will be made available through a GHNS content repository in the future for easy submission and updates. Please use your favourite GHNS upload/download tools. For convenience, SDLNewStuff is distributed with Pyntor in a slightly modified version. Downloaded components will be stored in ~/.pyntor though, so copy them to the current directory to make them get higher priority than local files. The search order for Pyntor components is like this: ./components (in current directory) ~/.pyntor/components (in user's home directory) /usr/local/share/pyntor/components (installation directory) pyntor-0.6/tools/pyntor-components.10000644000175300017530000000313010416470151017612 0ustar studentstudent.TH "pyntor-components" "1" "0.6" "Cool Projects" "Pyntor" .SH "NAME" .LP pyntor-components \- manage components for the presentation program Pyntor .SH "SYNOPSIS" .LP pyntor-components [OPTIONS] .SH "DESCRIPTION" .LP Pyntor-components is a helper utility which deals with all the components which are made available to Pyntor. Components can reside in the installation directory, but the user can override them with a user directory and again with components in the current directory. Pyntor-components helps to sort through the available components by listing them. In case the component comes with documentation included, it can be displayed as well. .SH "OPTIONS" .TP \fB\-l\fR, \fB\-\-list\fR Displays a formatted list of all components. For each component, all occurrences are reported, which might be between one and three directories. .TP \fB\-d\fR, \fB\-\-doc\fR=\fIcomponent\fR Displays the documentation for the specified component. If no documentation is included, or the component could not be loaded, an error is displayed instead. .TP \fB\-u\fR, \fB\-\-update\fR Displays a graphical dialog which allows the download of new components via the GHNS (Get Hot New Stuff) framework. Components are installed into ~/.pyntor/components and can be used immediately. \fBThis is an experimental feature based on the SDLNewStuff library.\fR .TP \fB\-v\fR, \fB\-\-version\fR Displays the version number of Pyntor-components. .TP \fB\-h\fR, \fB\-\-help\fR Displays a summary of all available command line options. .SH "AUTHORS" .LP Josef Spillner .SH "SEE ALSO" pyntor(1), pyntor-selfrun(1) pyntor-0.6/tools/sdlnewstuffpyntor.py0000644000175300017530000002120510416470151020207 0ustar studentstudent# Python/SDL library for Get Hot New Stuff access # Copyright (C) 2004 - 2006 Josef Spillner # Published under GNU GPL conditions # COPY FOR PYNTOR import pygame from pygame.locals import * import sys import time import os, pwd import urllib import xml.dom.minidom import gettext import imp """ Internationalization """ gettext.install("ggzpython", "/usr/local/share/locale", 1) """ GHNS data entry class """ class Stuff: def __init__(self): self.name = None self.category = None self.author = None self.licence = None self.summary = None self.version = None self.release = None self.releasedate = None self.preview = None self.payload = None self.rating = None self.downloads = None self.previewimage = None self.provider = None self.status = None """ SDLNewstuff configuration class """ class Configuration: def __init__(self): self.providers = "http://ftp.ggzgamingzone.org/pub/hotstuff/providers.xml" self.installdir = None self.unpack = 1 self.keyboard = 1 self.mouse = 1 self.alpha_darkness = 150 self.color_background = (50, 100, 150) self.color_foreground = (255, 255, 255) self.color_box = (100, 100, 255) self.color_activebox = (130, 130, 255) self.color_titlebox = (255, 120, 0) self.color_action = (120, 255, 120) self.lang = os.getenv("LANG", None) def parse(self, conf): for attr in vars(self): if hasattr(conf, attr): setattr(self, attr, getattr(conf, attr)) def element(stuff, key): value = stuff.getElementsByTagName(key) if len(value) >= 1: child = value[0].firstChild if child: return child.nodeValue return "" def downloaddata(conf, category): stufflist = [] providers = conf.providers try: (filename, data) = urllib.urlretrieve(providers) except: return stufflist print "FILENAME", filename dom = xml.dom.minidom.parse(filename) root = dom.documentElement print "root", root.tagName providers = root.getElementsByTagName("provider") for provider in providers: upload = provider.getAttribute("uploadurl") download = provider.getAttribute("downloadurl") icon = provider.getAttribute("icon") title = element(provider, "title") print "provider", upload, download, icon try: (filename, data) = urllib.urlretrieve(download) except: continue print "FILENAME", filename try: dom = xml.dom.minidom.parse(filename) except: print "ouch!" continue root = dom.documentElement print "root", root.tagName stuffs = root.getElementsByTagName("stuff") for stuff in stuffs: s = Stuff() s.name = element(stuff, "name") s.category = stuff.getAttribute("category") s.author = element(stuff, "author") s.licence = element(stuff, "licence") s.summary = element(stuff, "summary") s.version = element(stuff, "version") s.release = element(stuff, "release") s.releasedate = element(stuff, "releasedate") s.preview = element(stuff, "preview") s.payload = element(stuff, "payload") s.rating = element(stuff, "rating") s.downloads = element(stuff, "downloads") print "stuff", s.name, s.category if s.category != category: continue try: (filename, data) = urllib.urlretrieve(s.preview) s.previewimage = pygame.image.load(filename) except: pass s.provider = title s.summary = s.summary.replace("\n", "") stufflist.append(s) return stufflist def rect(surface, color, x1, y1, w, h, bgcolor): surface.fill(bgcolor, ((x1, y1), (w, h))) surface.fill(color, ((x1, y1), (1, h))) surface.fill(color, ((x1, y1), (w, 1))) surface.fill(color, ((x1, y1 + h), (w, 1))) surface.fill(color, ((x1 + w, y1), (1, h))) def menurender(surface, stufflist, highlighted, conf): titlefont = pygame.font.SysFont("Vera Sans", 24) font = pygame.font.SysFont("Vera Sans", 12) surface.fill(conf.color_background) i = 10 c = 0 if highlighted > 5: i -= (highlighted - 5) * 80 oldprovider = "" for s in stufflist: if s.provider is not oldprovider: rect(surface, conf.color_foreground, 10, i, 780, 30, conf.color_titlebox) f = titlefont.render(s.provider, 1, (0, 0, 0)) surface.blit(f, (15, i - 4)) i += 40 oldprovider = s.provider bgcolor = conf.color_box if highlighted == c: bgcolor = conf.color_activebox rect(surface, conf.color_foreground, 10, i, 780, 70, bgcolor) if s.previewimage: surface.blit(s.previewimage, (15, i + 3)) f = titlefont.render(s.name, 1, conf.color_foreground) surface.blit(f, (90, i)) f = font.render(s.author, 1, conf.color_foreground) surface.blit(f, (90, i + 30)) f = font.render(s.version + " (" + s.releasedate + ")", 1, conf.color_foreground) surface.blit(f, (90, i + 45)) f = font.render(s.summary, 1, conf.color_foreground) surface.blit(f, (780 - f.get_width(), i + 15)) if not s.status: s.status = _("not installed") if s.status == _("installed"): f = font.render(s.status, 1, conf.color_foreground) surface.blit(f, (780 - f.get_width(), i + 45)) else: f = font.render(s.payload, 1, conf.color_foreground) surface.blit(f, (780 - f.get_width(), i + 30)) f = font.render(s.status, 1, conf.color_action) surface.blit(f, (780 - f.get_width(), i + 45)) i += 80 c += 1 def homedir(): return pwd.getpwuid(os.geteuid())[5] class GHNSEngine: def __init__(self): self.conf = Configuration() os.system("mkdir -p " + homedir() + "/.sdlnewstuff") try: (fileobj, filename, desc) = imp.find_module(gamename, [homedir() + "/.sdlnewstuff/"]) config = imp.load_module("config", fileobj, filename, desc) fileobj.close() conf.parse(config.conf) except: pass def gethotnewstuff(self, category): """ Prepare GHNS """ screen = pygame.display.get_surface() screencopy = pygame.Surface((screen.get_width(), screen.get_height())) screencopy.blit(screen, (0, 0)) conf = self.conf if conf.alpha_darkness: dark = pygame.Surface((screen.get_width(), screen.get_height())) dark.fill((0, 0, 0)) dark.set_alpha(conf.alpha_darkness) screen.blit(dark, (0, 0)) stufflist = downloaddata(conf, category) try: f = file(homedir() + "/.sdlnewstuff/sdlnewstuff.install", "r") lines = f.readlines() f.close() for s in stufflist: if s.name + "::" + s.version + "\n" in lines: s.status = _("installed") except: pass surface = pygame.Surface((800, 600)) pygame.event.clear() selection = 0 updatescreen = 1 install = -1 while 1: """ Control """ pygame.event.pump() event = pygame.event.poll() if conf.keyboard and event.type == KEYDOWN: key = event.key if key == K_ESCAPE or pygame.event.peek(QUIT): break if key == K_UP: if selection > 0: selection -= 1 updatescreen = 1 if key == K_DOWN: if selection < len(stufflist) - 1: selection += 1 updatescreen = 1 if key == K_RETURN: install = selection if conf.mouse and event.type == MOUSEMOTION: (posx, posy) = event.pos left = (screen.get_width() - 800) / 2 up = (screen.get_height() - 600) / 2 if posx > left + 10 and posx < left + 800 - 10: c = 0 i = 10 oldprovider = "" for s in stufflist: if s.provider is not oldprovider: i += 40 oldprovider = s.provider if posy > up + i and posy < up + i + 70: if selection is not c: selection = c updatescreen = 1 i += 80 c += 1 if conf.mouse and event.type == MOUSEBUTTONDOWN: install = selection """ Logics """ if install >= 0 and len(stufflist) > 0: s = stufflist[install] if s.status is not _("installed"): try: (filename, data) = urllib.urlretrieve(s.payload) if conf.installdir: target = homedir() + "/" + conf.installdir else: target = homedir() + "/.ggz/games/" + gamename os.system("mkdir -p " + target) if conf.unpack: os.system("tar -C " + target + " -xvzf " + filename) else: xtarget = target + "/" + s.payload.split("/")[-1] os.system("cp " + filename + " " + xtarget) f = file(homedir() + "/.sdlnewstuff/sdlnewstuff.install", "a") f.write(s.name + "::" + s.version + "\n") f.close() s.status = _("installed") except: s.status = _("error") install = -1 updatescreen = 1 """ Drawing """ if updatescreen: menurender(surface, stufflist, selection, conf) xpos = (screen.get_width() - 800) / 2 ypos = (screen.get_height() - 600) / 2 screen.blit(surface, (xpos, ypos)) updatescreen = 0 pygame.display.flip() screen.blit(screencopy, (0, 0)) pygame.display.flip() # Get a new engine def ghnsengine(): return GHNSEngine() # Legacy wrapper functions for compatibility def gethotnewstuff(category): engine = GHNSEngine() engine.gethotnewstuff(category) pyntor-0.6/tools/sdlnewstuffpyntor.pyc0000644000175300017530000002313010416470151020351 0ustar studentstudentmò 9H6Dc@sçdkZdkTdkZdkZdkZdkZdkZdkZdk Z dk Z e i dddƒdfd„ƒYZ dfd„ƒYZ d „Zd „Zd „Zd „Zd „Zdfd„ƒYZd„Zd„ZdS(N(t*t ggzpythons/usr/local/share/localeitStuffcBstZd„ZRS(NcCs‹d|_d|_d|_d|_d|_d|_d|_d|_ d|_ d|_ d|_ d|_ d|_d|_d|_dS(N(tNonetselftnametcategorytauthortlicencetsummarytversiontreleaset releasedatetpreviewtpayloadtratingt downloadst previewimagetprovidertstatus(R((tQ/home/student/Vorträge/PYNTOR-devel/PYNTOR-svn/pyntor/tools/sdlnewstuffpyntor.pyt__init__s              (t__name__t __module__R(((RRst ConfigurationcBstZd„Zd„ZRS(NcCs…d|_d|_d|_d|_d|_d|_d |_d |_ d |_ d|_ d|_ d|_ tid dƒ|_dS(Ns7http://ftp.ggzgamingzone.org/pub/hotstuff/providers.xmlii–i2idiÿi‚ixitLANG(i2idi–(iÿiÿiÿ(ididiÿ(i‚i‚iÿ(iÿixi(ixiÿix(Rt providersRt installdirtunpacktkeyboardtmousetalpha_darknesstcolor_backgroundtcolor_foregroundt color_boxtcolor_activeboxtcolor_titleboxt color_actiontostgetenvtlang(R((RR-s            cCsHxAt|ƒD]3}t||ƒot||t||ƒƒq q WdS(N(tvarsRtattrthasattrtconftsetattrtgetattr(RR,R*((Rtparse?s (RRRR/(((RR,s cCsI|i|ƒ}t|ƒdjo#|di}|o |iSqEndS(Niit(tstufftgetElementsByTagNametkeytvaluetlent firstChildtchildt nodeValue(R1R3R4R7((RtelementDs  cCsÝg} |i}yti|ƒ\}}Wn | SnXdG|GHtii i |ƒ}|i }dG|i GH|idƒ}x^|D]V} | idƒ}| idƒ} | idƒ}t| dƒ} dG|G| G|GHyti| ƒ\}}Wn qnXdG|GHytii i |ƒ}WndGHqnX|i }dG|i GH|id ƒ}x‚|D]z}tƒ} t|d ƒ| _|id ƒ| _t|d ƒ| _t|d ƒ| _t|dƒ| _t|dƒ| _t|dƒ| _ t|dƒ| _!t|dƒ| _"t|dƒ| _#t|dƒ| _$t|dƒ| _%d G| iG| iGH| i|joqWny1ti| i"ƒ\}}t&i'i(|ƒ| _)WnnX| | _| ii*ddƒ| _| i+| ƒqWWqW| S(NtFILENAMEtrootRt uploadurlt downloadurlticonttitlesouch!R1RRRRR R R R R RRRs R0(,t stufflistR,Rturllibt urlretrievetfilenametdatatxmltdomtminidomR/tdocumentElementR;ttagNameR2Rt getAttributetuploadtdownloadR>R9R?tstuffsR1RtsRRRRR R R R R RRRtpygametimagetloadRtreplacetappend(R,RR1RR;RFRMRKRCRNR@RRLR?RDR>((Rt downloaddataLst         cCs¶|i|||f||ffƒ|i|||fd|ffƒ|i|||f|dffƒ|i||||f|dffƒ|i||||fd|ffƒdS(Ni(tsurfacetfilltbgcolortx1ty1twthtcolor(RUR\RXRYRZR[RW((Rtrect”s """&c Cs]tiiddƒ} tiiddƒ} |i|iƒd}d}|djo||dd8}nd}xç|D]ß} | i|j okt||id|d d |iƒ| i| id dƒ}|i|d |d fƒ|d7}| i}n|i}||jo |i}nt||id|d d|ƒ| io!|i| id |dfƒn| i| id |iƒ}|i|d|fƒ| i| id |iƒ}|i|d|d fƒ| i| id| idd |iƒ}|i|d|dfƒ| i| id |iƒ}|i|d |iƒ|d fƒ| ipt dƒ| _n| it dƒjoC| i| id |iƒ}|i|d |iƒ|dfƒn| i| i!d |iƒ}|i|d |iƒ|d fƒ| i| id |i"ƒ}|i|d |iƒ|dfƒ|d7}|d 7}qvWdS(Ns Vera Sansii i iiiPR0i iiiii(iFiiZs (t)i-s not installedt installed(iii(#ROtfonttSysFontt titlefontRURVR,R titct highlightedt oldproviderR@RNRR]R!R$trendertftblitR"RWR#RRRR R R t get_widthRt_RR%( RUR@ReR,RdRhRcRfRWRNRbR`((Rt menurender›sR "      !*$ ($$ cCstitiƒƒdS(Ni(tpwdtgetpwuidR&tgeteuid(((RthomedirÊst GHNSEnginecBstZd„Zd„ZRS(NcCstƒ|_tidtƒdƒy[tittƒdgƒ\}}}ti d|||ƒ}|iƒti|iƒWnnXdS(Ns mkdir -p s /.sdlnewstuffs/.sdlnewstuff/tconfig(RRR,R&tsystemRptimpt find_moduletgamenametfileobjRCtdesct load_moduleRrtcloseR/(RRrRCRwRx((RRÎs % cCs¥tiiƒ} ti| iƒ| iƒfƒ}|i| dƒ|i }|i oRti| iƒ| iƒfƒ}|i dƒ|i|i ƒ| i|dƒnt||ƒ}ysttƒddƒ}|iƒ}|iƒx@|D]8}|id|id|jotdƒ|_qêqêWWnnXtidƒ}tiiƒd}d }d }x"tii#ƒtii$ƒ}|i%oÇ|i&t'jo·|i(}|t)jptii*t+ƒoPn|t,jo%|djo|d 8}d }qøn|t-jo/|t.|ƒd jo|d 7}d }q4n|t/jo |}qOn|i0o|i&t1jo |i2\}}| iƒdd }| iƒdd }||d jo¾||dd jo©d}d }d }x”|D]ˆ}|i:|j o|d7}|i:}n|||jo6|||djo!||j o|}d }qSn|d7}|d 7}qßWqsn|i0o|i&t;jo |}n|djolt.|ƒdjoY||}|itdƒj o5y t<i=|i>ƒ\} }|iAotƒd|iA}ntƒdtC}tDiEd|ƒ|iFotDiEd|d| ƒn8|d|i>iGdƒd } tDiEd| d| ƒttƒddƒ}|iI|id|idƒ|iƒtdƒ|_Wntdƒ|_nXd }d }qn|o[tJ||||ƒ| iƒdd } | iƒdd } | i|| | fƒd}ntiiMƒqbW| i|dƒtiiMƒdS( s Prepare GHNS is!/.sdlnewstuff/sdlnewstuff.installtrs::s R_i iXiiÿÿÿÿii R0i(iFiPt/s /.ggz/games/s mkdir -p star -C s -xvzf scp t taterrorN(ii(iii(ii(i iX(ii(NROtdisplayt get_surfacetscreentSurfaceRjt get_heightt screencopyRiRR,RtdarkRVt set_alphaRTRR@tfileRpRht readlinestlinesRzRNRR RkRRUteventtcleart selectiont updatescreentinstalltpumptpollRttypetKEYDOWNR3tK_ESCAPEtpeektQUITtK_UPtK_DOWNR5tK_RETURNRt MOUSEMOTIONtpostposxtposytlefttupRdRcRfRtMOUSEBUTTONDOWNRARBRRCRDRttargetRvR&RsRtsplittxtargettwriteRltxpostypostflip(RRRR,RŽRURfR@R‹RCR£R¥R‚R¦R†R3RœRDRdR¡RhRcR…RŠRŸRRNRRž((RtgethotnewstuffÛsÄ!  !             &  &         (RRRR¨(((RRqÍs cCstƒS(N(Rq(((Rt ghnsengineUscCstƒ}|i|ƒdS(N(RqtengineR¨R(RRª((RR¨Zs (ROt pygame.localstsysttimeR&RmRAtxml.dom.minidomREtgettextRtRRRR9RTR]RlRpRqR©R¨(RERlRTRpR¯R¨R©RAR9R¬RtRmRROR­RqRR&R]((Rt?s,         H  / ˆ pyntor-0.6/selfrun/0000755000175300017530000000000010416470165014340 5ustar studentstudentpyntor-0.6/selfrun/pyntor-selfrun.template0000644000175300017530000000340010416470151021074 0ustar studentstudent#!/usr/bin/env python # # Template file for self-extracting Pyntor presentation archives # Copyright (C) 2005 Josef Spillner # Available as Public Domain so everyone can freely licence presentations # Included presentation archives and pyntor sources are licenced separately! import base64 import os import os.path ### Templates for presentation and pyntor sources archive = """ %ARCHIVE% """ pyntor = """ %PYNTOR% """ ### Prepare temporary directory tempdirname = "/tmp/pyntor-selfrun.tmp" print "[selfrun] extract into temporary directory", tempdirname os.mkdir(tempdirname) ### Extract presentation archivename = "pyntor-extracted-archive.tar.gz" archivefile = tempdirname + "/" + archivename tmpfile = archivefile + ".b64" f = open(tmpfile, "w") f.write(archive) f.close() infile = open(tmpfile) outfile = open(archivefile, "w") base64.decode(infile, outfile) os.unlink(tmpfile) infile.close() outfile.close() ### Extract pyntor sources pyntorname = "pyntor-extracted-sources.tar.gz" pyntorfile = tempdirname + "/" + pyntorname tmpfile = pyntorfile + ".b64" f = open(tmpfile, "w") f.write(pyntor) f.close() infile = open(tmpfile) outfile = open(pyntorfile, "w") base64.decode(infile, outfile) os.unlink(tmpfile) infile.close() outfile.close() # Run pyntor print "[selfrun] extract pyntor from", pyntorfile os.system("tar -C " + tempdirname + " -xzf " + pyntorfile) os.chdir(tempdirname) dir = None for d in os.listdir(tempdirname): if os.path.isdir(d): dir = d if dir: print "[selfrun] run pyntor in", dir, "on", archivename os.chdir(dir) os.system("./pyntor ../" + archivename) else: print "[selfrun] Error: no pyntor source directory found" # Clean up print "[selfrun] clean up", tempdirname os.system("rm -rf " + tempdirname) pyntor-0.6/selfrun/pyntor-selfrun0000755000175300017530000001042010416470151017265 0ustar studentstudent#!/usr/bin/env python # -*- coding: utf-8 -*- # # Creation of self-extracting Pyntor presentations # Copyright (C) 2006 Josef Spillner # Published under GNU GPL conditions import base64 import os import sys import getopt #import tempfile import atexit ### Parse command line options templatename = "pyntor-selfrun.template" releasefile = None archiveonly = False try: longopts = ["help", "archive", "template=", "release="] options = getopt.gnu_getopt(sys.argv[1:], "hat:r:", longopts) except: print "Error:", sys.exc_info()[1] sys.exit(1) (options, arguments) = options for opt in options: (optkey, optvalue) = opt if optkey in ("-h", "--help"): print "Pyntor Selfrun - tool to create self-extracting presentations" print "Copyright (C) 2006 Josef Spillner " print "Published under GNU GPL conditions" print "" print "Synopsis: pyntor-selfrun [options] |" print "" print "Options:" print "[-t | --template=...] Template file to use instead of the default" print "[-r | --release=... ] Take pyntor files from release tarball" print "[-a | --archive ] Only create archive from presentation directory" print "[-h | --help ] Display this help" sys.exit(0) elif optkey in ("-t", "--template"): templatename = optvalue elif optkey in ("-r", "--release"): releasefile = optvalue elif optkey in ("-a", "--archive"): archiveonly = True if len(arguments) != 1: print "Error: either presentation archive or presentation directory not specified" sys.exit(-1) ### Check if presentation archive or directory was given filename = arguments[0] tempname = None try: contents = os.listdir(filename) dirname = filename + "/" dirname = dirname.replace("//", "/") dirname = dirname.split("/")[-2] tempname = dirname + ".pyntor" except: pass if tempname: #tempdir = tempfile.mkdtemp() tempdir = "." os.system("tar czf " + tempdir + "/" + tempname + " " + filename) def cleanup(tmpfile): if tmpfile is not None: #print "(cleanup...)", tmpfile os.remove(tmpfile) if archiveonly: if not tempname: print "Error: must specify a presentation directory for archive creation" sys.exit(-1) else: print "Created presentation archive", tempname, "from", filename sys.exit(0) else: if tempname: atexit.register(cleanup, tempname) filename = tempname ### Get the tarball filename if releasefile: pyntorname = releasefile else: print "Error: pyntor release tarball argument missing" sys.exit(1) ### Load template file try: templatefile = open(templatename) except: print "Error: could not open template file", templatename sys.exit(1) template = templatefile.readlines() template = str.join("", template) templatefile.close() ### Create tempfile with archive in base64 format try: infile = open(filename) except: print "Error: could not open presentation file", filename sys.exit(1) packfilename = filename + ".b64" try: outfile = open(packfilename, "w") except: print "Error: could not write to temporary file", packfilename sys.exit(1) base64.encode(infile, outfile) infile.close() outfile.close() ### Same for pyntor source archive try: infile = open(pyntorname) except: print "Error: could not open pyntor source archive", pyntorname sys.exit(1) packpyntorname = pyntorname + ".b64" try: outfile = open(packpyntorname, "w") except: print "Error: could not write to temporary file", packpyntorname sys.exit(1) base64.encode(infile, outfile) infile.close() outfile.close() ### Read in base64 strings and delete tempfiles packfile = open(packfilename) lines = packfile.readlines() lines = str.join("", lines) packfile.close() os.unlink(packfilename) packfile = open(packpyntorname) pyntorlines = packfile.readlines() pyntorlines = str.join("", pyntorlines) packfile.close() os.unlink(packpyntorname) ### Write out templates with archive string replacements template = template.replace("%ARCHIVE%", lines) template = template.replace("%PYNTOR%", pyntorlines) extractorname = filename + ".selfrun.py" try: extractorfile = open(extractorname, "w") except: print "Error: could not write to self-extracting file", extractorname sys.exit(1) extractorfile.write(template) extractorfile.close() os.chmod(extractorname, 0755) print "Created self-extracting archive", extractorname pyntor-0.6/selfrun/README.selfrun0000644000175300017530000000270110416470151016670 0ustar studentstudentPyntor-selfrun is a tool which allows presentation archives to be distributed as self-extracting python archives. This may be useful if the target system doesn't have Pyntor installed. Syntax: * pyntor-selfrun -r Explanation of arguments: - presentation.pyntor: your presentation in archive format (i.e. *.tar.gz) - pyntor-x.y.tar.gz: original pyntor release tarball Results: A file named presentation.pyntor.selfrun.py will be created. It contains both the pyntor release tarball and the presentation archive as base64-encoded strings. Therefore, retrieving the original data in any case is easy. The file supports the following modes: * xyz.selfrun.py: runs the presentation (default) * xyz.selfrun.py --presentation: extracts the presentation archive * xyz.selfrun.py --pyntor: extracts the pyntor release tarball * xyz.selfrun.py --help: shows all available option (in case more are added) Template: The template file used by pyntor-selfrun is named pyntor-selfrun.template. It can be given as --template option to the script in case custom templates are available. Otherwise, it must exist in the current directory or in /usr/share/pyntor. Licence: The template file is released under the public domain. You might want to strip the copyleft information and put in your own name before distributing a self-extracting archive containing your own presentation. The pyntor licence disclaimer should not be touched, though. pyntor-0.6/selfrun/pyntor-selfrun.10000644000175300017530000000501710416470152017430 0ustar studentstudent.TH "pyntor-selfrun" "1" "0.6" "Cool Projects" "Pyntor" .SH "NAME" .LP pyntor-selfrun \- creation of self-extracting presentation archives .SH "SYNOPSIS" .LP pyntor-selfrun [OPTIONS] \fIpresentation-archive\fR|\fIpresentation-directory\fR .SH "DESCRIPTION" .LP Pyntor is a presentation tool which can display slides and effects of various formats. The tool pyntor-selfrun allows one to create self-extracting archives containing both the presentation and Pyntor itself, so that it can be run on computers where Pyntor itself is not installed. It also ensures independence of the corresponding Pyntor version, as future changes of the application do not affect the previously created presentations. The way pyntor-selfrun works is that it takes the \fIpresentation-archive\fR in question, packed as a tarball (which might be named \fB*.pyntor\fR), and the release tarball of Pyntor which must be present somewhere. Using a template file, it then creates the self-running and self-extracting script for distribution. .LP In case the \fIpresentation-archive\fR does not exist yet, pyntor-selfrun can create it automatically from a \fIpresentation-directory\fR. This is useful even when not creating self-extracting archives, see the \fB-a\fR option. .SH "OPTIONS" .TP \fB\-t\fR, \fB\-\-template\fR=\fItemplatefile\fR Uses a template different from that one which is shipped with Pyntor and used by default. The template is a script in Python or another scripting language, which contains the variables %PYNTOR% and %ARCHIVE% which are replaced with the base64-encoded contents of the two files given as arguments to pyntor-selfrun. This option is not recommended for most cases. .TP \fB\-a\fR, \fB\-\-archive\fR Creates a pyntor \fIpresentation-archive\fR from a directory which contains a script file, local data files and optionally some local components. This is a convenience operation, since presentation archives are just tarballs, but in the future some checks might be done here. .TP \fB\-r\fR, \fB\-\-release\fR=\fIsourcetarball\fR Specifies where to find Pyntor itself to include it into the self-extracting archive. The source tarball should be a released \fBpyntor-*.tar.gz\fR file. .TP \fB\-h\fR, \fB\-\-help\fR Displays a summary of all available command line options. .SH "BUGS" In a future version, pyntor-selfrun should allow to create *.pyntor archives automatically by examining a script file, including only those files of Pyntor which it really needs. .SH "AUTHORS" .LP Josef Spillner .SH "SEE ALSO" pyntor(1), pyntor-components(1) pyntor-0.6/doc/0000755000175300017530000000000010416470165013427 5ustar studentstudentpyntor-0.6/doc/components.txt0000644000175300017530000000760010416470152016354 0ustar studentstudentPyntor components ----------------- Pyntor is a very flexible presentation program. Although it provides a general framework such as for setting up the screen, tracking the mouse and keyboard, handling errors etc., the real work is all done by components. Some components are included and documented below. Others can be added for custom presentations. Maybe you might even want to contribute your hyper-cool module for inclusion! There's a section in 'documentation.txt' about component sharing. Note that from version 0.6 on, component documentation is in many cases also available by running pyntor-components --doc . To see a list of all installed components, use pyntor-components --list. Overview -------- Major slide-show components: - TPP - POD - Slides Minor graphical components: - Background - Titlepage - Image - Fortune - Flags - Barslider - Video Special graphical components: - Pyromaniac Interactivity components: - Wait - Nowait Pyntor::POD: Plain Old Documentation ------------------------------------ POD comes from the Perl world and is used as an embedded documentation format suitable for source code files. Some people who are familar with POD can now use Pyntor to give a POD-slide show! (Pod-Blasting, haha) Note that the POD component currently renders everything on one huge page. Supported keys are up/down for scrolling, page up/page down for faster scrolling, and g/G for vi-like jumps to the beginning/the end. Pyntor::TPP: Text-slide features -------------------------------- TPP is a presentation program written by Andreas Krennmair and Nico Golde. Since the 'cool' presentation programs will stand united against the 'uncool' ones, Pyntor supports TPP slides as good as it can. As a matter of fact, this saves the TPP authors the work of hacking in HTML or PDF export :-) Navigation only happens via enter/backspace to navigate forth and back. A compatibility mode for TPP keyboard shortcuts might be introduced in the future. Pyntor::Slides: Slideshow features ---------------------------------- - saving slides statically as to use them elsewhere ('s' key) - toggle between fullscreen and window mode ('f' key) - adjust brightness of background ('+' and '-' keys) - going forth and back ('enter' and 'backspace' keys) - browser launch support on urls, and program launch support - wiki-style markup: * title page (title line above ====, subtitle below) * pages (page titles above ----, page content below) * urls ([url:foo]) and images ([img:foo]) - page ranges or single pages out of a document - switch mouse cursor on and off ('m' key) - mark text (left mouse button if mouse cursor is visible) Pyntor::Pyromaniac: Browser features ------------------------------------ - HTML browser - works on remote URLs also - scaling of webpages ('+' and '-' keys) - jump to previous/next slide ('p' and 'n' keys) * if HTML pages contain comment about their order - history always available ('alt+left' and 'alt+right' keys) - screenshot ('f12' key) - reload ('r' key) - page width adjustments ('1' and '2' keys) - toggle fullscreen ('f' key) Pyntor::Background: Background image settings --------------------------------------------- - type: non-interactive rendering component - arguments: image (PNG, JPEG etc.) or colour in rgb-hex format (like ffffff) Pyntor::Titlepage: Opening slide for a presentation --------------------------------------------------- - type: non-interactive rendering component - arguments: titlepage file - this file contains title, subtitle, author and date fields - see 'title' for a format example Pyntor::Image: Displays a positioned image ------------------------------------------ - type: non-interactive rendering component - arguments: x position, y position, and image file name - positions can be absolute pixels or xx% values Pyntor::Fortune: TODO Pyntor::Flags: TODO Pyntor::Barslider: TODO Pyntor::Video: TODO Pyntor::Wait: TODO Pyntor::Nowait: TODO pyntor-0.6/doc/CONCEPT-components0000644000175300017530000001604410416470152016631 0ustar studentstudentComponent documentation for developers ====================================== This file is heavily work in progress. Use at your own risk. Components might eat your cat, dog and girlfriend all at once! About components ---------------- Pyntor is not a slideshow program by itself, it merely manages to load the correct components for each part of a presentation. As it is based on Pygame, all component rendering and interactivity also requires Pygame knowledge. In the end, every component results in a small *.py file which is then placed into the 'components' directory of Python. Component structure ------------------- Each component must have at least one global variable ("component") which is an object of class Component. Additional global variables should be present, but are not required. Global variables are: - 'component', a Component object which handles rendering and user input - 'metainfo', a dictionary containing some meta information about the component - 'documentation', plain-text documentation as a multi-line string - 'parameters', a tuple describing the interface to this component These global variables will now be described in detail. Meta information: Information about a component's author, licence etc. should be present here. The variable is a hash map, and the following keys are currently defined: - "author" - "authoremail" - "licence" - "homepage" - "version" Parameters: Pyntor versions prior to 0.6 only accepted unnamed parameters to components as a list. However, with a growing number of parameters and components, one is easily lost. Now it is possible to have "new-style" parameters which are passed as a hash map to the component's initialisation method. This mode is automatically activated if the 'parameters' global variable is present. It should contain a list of possible parameters, together with their description and default values in case the option is not present. An example follows: parameters = ( ("duration", "Duration of the cookie display, in seconds", 10), ("cookiefile", "Cookie file to use", None), (None, "Additional parameters", []) ) A default value of None means that the option is required. A parameter value of None means that a variable number of arguments is accepted in addition to the named arguments. This is called varargs, and its default must always be the empty list([]). Parameters can still be given unnamed to the component - they will then be assigned to the next previously unassigned named parameter. If there are too many of them, they will end up in the varargs list, or in case no varargs is supported, an error will be given. To get back to the example above, the following would happen: - -cookiefile foo: valid - : invalid, since cookiefile is missing - foo: invalid, since foo relates to duration (earlier in list) - 5 foo bar baz: valid, with [bar, baz] being the varargs API documentation for class Component ------------------------------------- class Component: attributes: - disabled (unset by default) - pages (must be set to number of pages by the component) - fulltext (dictionary containing text for full-text search) - headings (like fulltext, but for headings only) def init(options) def render(screen, page, globalpage) def interactive(event) init: This is called whenever the component is used in a presentation. It gets a list of options (old-style) or a dictionary of options (new-style) which the component can use to initialise itself. If there is an error, for example a file is missing or a parameter is of the wrong type, the component should print an error message and, the important part, set 'self.disabled' to true. Pyntor will then know that something went wrong. The same can be done in the other methods as well. At the end, the component should have set 'self.pages' to indicate how many pages it needs with the given options. Optionally, in the case of text-centric components, 'self.fulltext' and 'self.headings' should be created as dictionaries, containing the full text for each of the pages. Pyntor will use this information for its fulltext search feature. render: This method is only needed for components which render graphics. It gets the global pygame screen where to render, and the information about which of its own pages is active (usually the 1st, unless it's a slide display component) and which is the corresponding global page number. Pyntor ensures that the display is updated by itself, the the component should not call update() or flip(), or it might lead to flicker. interactive: This method is only needed for components which accept user input. It gets as its parameter a pygame event object, which should be evaluated. Pyntor checks to see what the return value of 'interactive' is. One of the following values is possible: - "reload": Pyntor should display the current page again - "previous": Pyntor should go one page back - "next": Pyntor should proceed - "quit": Pyntor should quit Note that previous/next might well lead into other components becoming active! Component interaction --------------------- Components are automatically grouped by Pyntor depending on whether they render some graphics, accept user input, or both. A grouping transition appears whenever an interactivity component is followed by a render component. Or, expressed in other terms, render components followed by components doing both followed by interactivity components are grouped together. The following gives an example: background.py: [render] slide.py: [render][interactive] marker.py: [interactive] Page numbers are given not to each component, but to each group: [r][r+i][i]|[r][i]|[r+i][i]|[r+i]|[r+i]|[r] 1 | 2 | 3 | 4 | 5 | - Of course, whenever a render component indicates that it renders multiple pages, the numbers will reflect this. Global gadgets -------------- Pyntor supports some global gadgets which do not need to be implemented by all components again. These are currently: - a clock (activate with "T") - page/progress display - not available yet! - page switcher (activate with "P") - full-text search (activate with "S") All key combinations which are not detected by Pyntor are passed to the currently active component. No mouse events are currently passed, they are all used by Pyntor itself, but this might change. Screenshots and exports ----------------------- It is important to know that when Pyntor runs in export mode (--export), some restrictions apply to the component. Well-designed components should always work in export mode, too. The components should not handle any interactivity in the render() method. They should also not assume that the pygame display is active, because it is actually not. In export mode, components render to off-screen images. Future versions --------------- Pyntor always tries to support old component formats, from version 0.5 on. However the format might break again one day. It's important to submit new components to the Pyntor component repository, so they can be tested with new versions as well. pyntor-0.6/doc/documentation.txt0000644000175300017530000001271610416470152017044 0ustar studentstudentPyntor user documentation ------------------------- This documentation is work in progress. Whenever there are some questions, the answers will be added here. Real documentation is better than a FAQ :-) *** Documentation index The following topics are documented already: 1) Creating presentations 2) Running pyntor 3) Exporting presentations 4) Component sharing *** *** (1) Creating presentations *** Pyntor presentations must be written by hand. There is no WYSIWYG tool and no graphical editor. It's like a mixture of of LaTeX document editing and programming, since custom Pyntor components can be added. Some of the components of Pyntor can be used without any others to create some slides. Those are 'slides', 'tpp' and 'pod'. In case a pure slide creation is wanted, simply call: pyntor -d "pod my.pl" or similar! For larger presentations, some more efforts are needed. It is recommended to start with an empty directory. A 'script' file should be added there to orchestrate the overall presentation. For custom components, a 'components' directory must be added, although for many cases Pyntor already ships with sufficient components. All other file positions can be chosen feely by the user. The 'script' file contains sequential calls to components. For example, an intro module could be called first, and afterwards the 'slides' module, which takes as option the slides text file. Read 'components.txt' to learn about how to use the already present components. Otherwise, have a look at the included demo presentation. To finish, consider this example: $ find ./mypresentation ./mypresentation/script ./mypresentation/components ./mypresentation/other-files $ pyntor-selfrun -a mypresentation/ $ pyntor mypresentation.pyntor The presentation archive 'mypresentation.pyntor' can then be distributed to other people. The script 'pyntor-selfrun' can also aid in converting 'mypresentation.pyntor' into a self-extracting python script, if desired. This is useful for people who have python and pygame installed, but not pyntor. It also protects against API changes in the pyntor components. *** *** (2) Running pyntor *** There are 3 different ways of running pyntor, depending on the presentation format: - in-dir files: If the presentation files have been added to a pyntor source tree, just run 'pyntor', it will read 'script' and proceed accordingly. - presentation archive: If all presentation files have been added to an archive ('foo.pyntor'), which is really a .tar.gz file, just run 'pyntor foo.pyntor'. Also, clicking on the 'foo.pyntor' file with a graphical file manager should execute pyntor, if the MIME info is registered correctly. Note that foo.pyntor must contain a directory with the files, not just a collection of files. The tool pyntor-selfrun should be used with the -a option to create the presentation archive. - self-extracting archives: In order to not require a pyntor installation, it might be packed together with the presentation files. In this case, just run './foo.pyntor.selfrun.py'. This will create 2 temporary directories: one for extracting pyntor, and one for extracting the presentation. Important keys: t - display time p - page selection s - full text search in slides enter/backspace - navigation in 'slides' component Components might support other key combinations. Again, read 'components.txt' to learn about them. *** *** (3) Exporting presentations *** It is always a good idea to export presentation slides into a static format so in case trouble strikes, another computer can be used easily. It is also suitable for putting presentations onto webpages. Pyntor exports presentations when run with the -x option, which needs an argument either designating a filename or a directory name. Two ways of exporting slides exist: - PDF export If -x foo.pdf is used, PNG images will be created first and then be assembled to a PDF file. Note that this will *not* result in nice scalable PDF, since the screen resolution is fixed. There are two advantages however, copying the export around will only take one file, and looking at the PDF at another resolution will still be possible on any computer, even if it does look a bit blurred. - HTML + PNG export If -x foodir is used, PNG images will be created (by way of BMP images and converting them with 'convert', just like with the first step for PDF), but then no further editing is done, instead all of those files will stay in foodir. There is now a helper script to create thumbnail images and an index.html file for the PNG images: $ zcat /usr/local/share/doc/pyntor/pyntor-minigal.sh.gz | sh This script is a very basic one. In most cases, one might want to install a dedicated gallery script or tool. One tool for each job, they say... :-) *** *** (4) Component sharing *** In order to let Pyntor dominate the world, people should write new components which can then be used by others. With a bit of luck, these might even end up in the next Pyntor release! Pyntor also has a component repository. If components are sent to the author of Pyntor, they will be made available there. Use the tool 'pyntor-components' to browse them, or alternatively access the web pages at http://pyntor.coolprojects.org/repository/. Using pyntor-components makes it easy to use new components, it installs them into the user's home directory (~/.pyntor) and Pyntor will find them automatically! Documentation about new components can also be read using pyntor-components, via the --doc option. pyntor-0.6/doc/pyntor.10000644000175300017530000000666010416470153015051 0ustar studentstudent.TH "pyntor" "1" "0.6" "Cool Projects" "Pyntor" .SH "NAME" .LP pyntor \- modular slides viewer and presentation tool .SH "SYNOPSIS" .LP pyntor [OPTIONS] \fIpresentation-file\fR|\fIpresentation-archive\fR .SH "DESCRIPTION" .LP Pyntor is a presentation tool which can display slides and effects of various formats thanks to its collection of components which are loaded as specified in the \fIpresentation-file\fR, commonly called \fBscript\fR. Components can either affect the rendering or the input handling, or both. They are documented in detail in \fIcomponents.txt\fR in the pyntor documentation directory, while this manual page tries to only briefly document the user interface to the program on the command line and some keyboard shortcuts to be used while the pyntor is running. .LP Since presentations are normally distributed as \fIpresentation-archive\fR files, usually named \fB*.pyntor\fR, those too are accepted as parameters to pyntor. To learn about the creation of presentations and archives, see below for some references. .SH "OPTIONS" .TP \fB\-c\fR, \fB\-\-components\fR Creates and outputs a formatted list of slides, tracking the components in use like in a real presentation. Additionally, all components are checked for presence and usability. This option is mainly useful for debugging, but also to get a quick overview of a presentation. .TP \fB\-d\fR, \fB\-\-direct\fR=\fIscriptline\fR IF a presentation only uses a single component, no script needs to be created. Instead, the component invocation line which would normally be written into the script can be passed directly on the command line. .TP \fB\-x\fR, \fB\-\-export\fR=\fIfile.pdf\fR|\fIhtmldir\fR Exports a presentation to a (fixed-size) PDF file, if the option argument suffix is .pdf, or otherwise to a directory of PNG images and a simple HTML file with thumbnail preview images. .TP \fB\-w\fR, \fB\-\-windowed\fR When running the presentation, do not switch to fullscreen, but run in a window instead. This option is also mainly useful for debugging. .TP \fB\-v\fR, \fB\-\-version\fR Displays the version number of Pyntor. .TP \fB\-h\fR, \fB\-\-help\fR Displays a summary of all available command line options. This option works without providing a \fBscript\fR. .TP \fB\-u\fR, \fB\-\-usage\fR Displays common usage scenarios so that the desired effect can be achieved quickly. The examples are for running a presentation and debugging one, respectively. This option works without providing a \fBscript\fR. .SH "SHORTCUTS" .LP The following keyboard shortcuts are supported globally throughout Pyntor. Note that individual components can make use of additional shortcuts, please consult the component documentation about this. .TP \fBF\fR Toggle between fullscreen mode and windowed mode for displaying slides. .TP \fBP\fR Pyntor menu to allow jumping to arbitrary slides of the presentation. .TP \fBS\fR Pyntor menu to allow type-as-you-find fulltext search, followed by slide jumping as with \fBP\fR. This is useful for the situation of having people reminded of some slide without knowing the page number after a talk. .TP \fBT\fR Gadget displaying the clock time and already spent presentation time in the lower right corner of the screen. Consecutive presses of the \fBT\fR key will increase the brightness of the gadget, but usually the indicator is for the presenter and not for the audience. .SH "AUTHORS" .LP Josef Spillner .SH "SEE ALSO" pyntor-components(1), pyntor-selfrun(1)