diff --git a/sugar/Makefile.am b/sugar/Makefile.am
new file mode 100644
index 00000000..7293e3c3
--- /dev/null
+++ b/sugar/Makefile.am
@@ -0,0 +1,10 @@
+SUBDIRS = chat browser shell
+
+bin_SCRIPTS = sugar
+
+sugardir = $(pythondir)/sugar
+sugar_PYTHON = \
+ __init__.py \
+ __installed__.py
+
+EXTRA_DIST = sugar
diff --git a/sugar/__init__.py b/sugar/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/sugar/__installed__.py.in b/sugar/__installed__.py.in
new file mode 100644
index 00000000..717669d8
--- /dev/null
+++ b/sugar/__installed__.py.in
@@ -0,0 +1,2 @@
+def internal_get_data_file(filename):
+ return "@prefix@/share/sugar"
diff --git a/sugar/__uninstalled__.py b/sugar/__uninstalled__.py
new file mode 100644
index 00000000..4ffafc83
--- /dev/null
+++ b/sugar/__uninstalled__.py
@@ -0,0 +1,13 @@
+import os
+
+data_dirs = [ 'sugar/browser', 'sugar/chat' ]
+
+def internal_get_data_file(filename):
+ basedir = os.path.dirname(os.path.dirname(__file__))
+
+ for data_dir in data_dirs:
+ path = os.path.abspath(os.path.join(basedir, data_dir, filename))
+ if os.path.isfile(path):
+ return path
+
+ return None
diff --git a/sugar/browser/Makefile.am b/sugar/browser/Makefile.am
new file mode 100644
index 00000000..d7e4b354
--- /dev/null
+++ b/sugar/browser/Makefile.am
@@ -0,0 +1,24 @@
+sugardir = $(pythondir)/sugar/browser
+sugar_PYTHON = browser.py
+
+icondir = $(pkgdatadir)
+icon_DATA = \
+ fold.png \
+ unfold.png
+
+# Dbus service file
+servicedir = $(datadir)/dbus-1/services
+service_in_files = com.redhat.Sugar.Browser.service.in
+service_DATA = $(service_in_files:.service.in=.service)
+
+# Rule to make the service file with bindir expanded
+$(service_DATA): $(service_in_files) Makefile
+ @sed -e "s|\@bindir\@|$(bindir)|" $< > $@
+
+EXTRA_DIST = \
+ $(service_in_files) \
+ $(service_DATA) \
+ $(icon_DATA)
+
+DISTCLEANFILES = \
+ $(service_DATA)
diff --git a/sugar/browser/browser.py b/sugar/browser/browser.py
new file mode 100755
index 00000000..633fa044
--- /dev/null
+++ b/sugar/browser/browser.py
@@ -0,0 +1,283 @@
+#!/usr/bin/env python
+
+import dbus
+import dbus.service
+import dbus.glib
+
+import pygtk
+pygtk.require('2.0')
+import gtk
+
+import geckoembed
+
+from sugar.shell import activity
+import sugar.env
+
+class AddressToolbar(gtk.Toolbar):
+ def __init__(self):
+ gtk.Toolbar.__init__(self)
+
+ address_item = AddressItem(self.__open_address_cb)
+ self.insert(address_item, 0)
+ address_item.show()
+
+ def __open_address_cb(self, address):
+ BrowserShell.get_instance().open_browser(address)
+
+class AddressItem(gtk.ToolItem):
+ def __init__(self, callback):
+ gtk.ToolItem.__init__(self)
+
+ address_entry = AddressEntry(callback)
+ self.add(address_entry)
+ address_entry.show()
+
+class AddressEntry(gtk.HBox):
+ def __init__(self, callback):
+ gtk.HBox.__init__(self)
+
+ self.callback = callback
+ self.folded = True
+
+ label = gtk.Label("Open")
+ self.pack_start(label, False)
+ label.show()
+
+ self.button = gtk.Button()
+ self.button.set_relief(gtk.RELIEF_NONE)
+ self.button.connect("clicked", self.__button_clicked_cb)
+ self.pack_start(self.button, False)
+ self.button.show()
+
+ self.entry = gtk.Entry()
+ self.entry.connect("activate", self.__activate_cb)
+ self.pack_start(self.entry, False)
+ self.entry.show()
+
+ self._update_folded_state()
+
+ def _update_folded_state(self):
+ if self.folded:
+ image = gtk.Image()
+ image.set_from_file(sugar.env.get_data_file('unfold.png'))
+ self.button.set_image(image)
+ image.show()
+
+ self.entry.hide()
+ else:
+ image = gtk.Image()
+ image.set_from_file(sugar.env.get_data_file('fold.png'))
+ self.button.set_image(image)
+ image.show()
+
+ self.entry.show()
+ self.entry.grab_focus()
+
+ def get_folded(self):
+ return self.folded
+
+ def set_folded(self, folded):
+ self.folded = not self.folded
+ self._update_folded_state()
+
+ def __button_clicked_cb(self, button):
+ self.set_folded(not self.get_folded())
+
+ def __activate_cb(self, entry):
+ self.callback(entry.get_text())
+ self.set_folded(True)
+
+class NavigationToolbar(gtk.Toolbar):
+ def __init__(self, embed):
+ gtk.Toolbar.__init__(self)
+ self.embed = embed
+
+ self.set_style(gtk.TOOLBAR_ICONS)
+
+ self.back = gtk.ToolButton(gtk.STOCK_GO_BACK)
+ self.back.connect("clicked", self.__go_back_cb)
+ self.insert(self.back, -1)
+ self.back.show()
+
+ self.forward = gtk.ToolButton(gtk.STOCK_GO_FORWARD)
+ self.forward.connect("clicked", self.__go_forward_cb)
+ self.insert(self.forward, -1)
+ self.forward.show()
+
+ self.reload = gtk.ToolButton(gtk.STOCK_REFRESH)
+ self.reload.connect("clicked", self.__reload_cb)
+ self.insert(self.reload, -1)
+ self.reload.show()
+
+ separator = gtk.SeparatorToolItem()
+ self.insert(separator, -1)
+ separator.show()
+
+ share = gtk.ToolButton("Share")
+ share.connect("clicked", self.__share_cb)
+ self.insert(share, -1)
+ share.show()
+
+ separator = gtk.SeparatorToolItem()
+ self.insert(separator, -1)
+ separator.show()
+
+ address_item = AddressItem(self.__open_address_cb)
+ self.insert(address_item, -1)
+ address_item.show()
+
+ self._update_sensitivity()
+
+ self.embed.connect("location", self.__location_changed)
+
+ def _update_sensitivity(self):
+ self.back.set_sensitive(self.embed.can_go_back())
+ self.forward.set_sensitive(self.embed.can_go_forward())
+
+ def __go_back_cb(self, button):
+ self.embed.go_back()
+
+ def __go_forward_cb(self, button):
+ self.embed.go_forward()
+
+ def __reload_cb(self, button):
+ self.embed.reload()
+
+ def __share_cb(self, button):
+ pass
+
+ def __location_changed(self, embed):
+ self._update_sensitivity()
+
+ def __open_address_cb(self, address):
+ self.embed.load_address(address)
+
+class BrowserActivity(activity.Activity):
+ def __init__(self, uri):
+ activity.Activity.__init__(self)
+ self.uri = uri
+
+ def activity_on_connected_to_shell(self):
+ self.activity_set_ellipsize_tab(True)
+ self.activity_set_can_close(True)
+ self.activity_set_tab_text("Web Page")
+ self.activity_set_tab_icon_name("web-browser")
+ self.activity_show_icon(True)
+
+ vbox = gtk.VBox()
+
+ self.embed = geckoembed.Embed()
+ self.embed.connect("title", self.__title_cb)
+ vbox.pack_start(self.embed)
+
+ self.embed.show()
+ self.embed.load_address(self.uri)
+
+ nav_toolbar = NavigationToolbar(self.embed)
+ vbox.pack_start(nav_toolbar, False)
+ nav_toolbar.show()
+
+ plug = self.activity_get_gtk_plug()
+ plug.add(vbox)
+ plug.show()
+
+ vbox.show()
+
+ def get_embed(self):
+ return self.embed
+
+ def __title_cb(self, embed):
+ self.activity_set_tab_text(embed.get_title())
+
+ def activity_on_close_from_user(self):
+ self.activity_shutdown()
+
+class WebActivity(activity.Activity):
+ def __init__(self):
+ activity.Activity.__init__(self)
+
+ def activity_on_connected_to_shell(self):
+ self.activity_set_tab_text("Web Browser")
+ self.activity_set_tab_icon_name("web-browser")
+ self.activity_show_icon(True)
+
+ vbox = gtk.VBox()
+
+ self.embed = geckoembed.Embed()
+ self.embed.connect("open-address", self.__open_address);
+ vbox.pack_start(self.embed)
+ self.embed.show()
+
+ address_toolbar = AddressToolbar()
+ vbox.pack_start(address_toolbar, False)
+ address_toolbar.show()
+
+ plug = self.activity_get_gtk_plug()
+ plug.add(vbox)
+ plug.show()
+
+ vbox.show()
+
+ self.embed.load_address("http://www.google.com")
+
+ def __open_address(self, embed, uri, data=None):
+ if uri.startswith("http://www.google.com"):
+ return False
+ else:
+ BrowserShell.get_instance().open_browser(uri)
+ return True
+
+ def activity_on_disconnected_from_shell(self):
+ gtk.main_quit()
+ gc.collect()
+
+class BrowserShell(dbus.service.Object):
+ instance = None
+
+ def get_instance():
+ if not BrowserShell.instance:
+ BrowserShell.instance = BrowserShell()
+ return BrowserShell.instance
+
+ get_instance = staticmethod(get_instance)
+
+ def __init__(self):
+ session_bus = dbus.SessionBus()
+ bus_name = dbus.service.BusName('com.redhat.Sugar.Browser', bus=session_bus)
+ object_path = '/com/redhat/Sugar/Browser'
+
+ dbus.service.Object.__init__(self, bus_name, object_path)
+
+ self.__browsers = []
+
+ def open_web_activity(self):
+ web_activity = WebActivity()
+ web_activity.activity_connect_to_shell()
+
+ @dbus.service.method('com.redhat.Sugar.BrowserShell')
+ def get_links(self):
+ links = []
+ for browser in self.__browsers:
+ embed = browser.get_embed()
+ link = {}
+ link['title'] = embed.get_title()
+ link['address'] = embed.get_address()
+ links.append(link)
+ return links
+
+ @dbus.service.method('com.redhat.Sugar.BrowserShell')
+ def open_browser(self, uri):
+ browser = BrowserActivity(uri)
+ self.__browsers.append(browser)
+ browser.activity_connect_to_shell()
+
+def main():
+ BrowserShell.get_instance().open_web_activity()
+
+ try:
+ gtk.main()
+ except KeyboardInterrupt:
+ pass
+
+if __name__=="__main__":
+ main()
diff --git a/sugar/browser/com.redhat.Sugar.Browser.service.in b/sugar/browser/com.redhat.Sugar.Browser.service.in
new file mode 100644
index 00000000..654095a6
--- /dev/null
+++ b/sugar/browser/com.redhat.Sugar.Browser.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=com.redhat.Sugar.Browser
+Exec=@bindir@/sugar browser
diff --git a/sugar/browser/fold.png b/sugar/browser/fold.png
new file mode 100644
index 00000000..cd4169ba
Binary files /dev/null and b/sugar/browser/fold.png differ
diff --git a/sugar/browser/unfold.png b/sugar/browser/unfold.png
new file mode 100644
index 00000000..f3f82fae
Binary files /dev/null and b/sugar/browser/unfold.png differ
diff --git a/sugar/chat/BuddyList.py b/sugar/chat/BuddyList.py
new file mode 100644
index 00000000..d35fa847
--- /dev/null
+++ b/sugar/chat/BuddyList.py
@@ -0,0 +1,121 @@
+# -*- tab-width: 4; indent-tabs-mode: t -*-
+
+import presence
+import avahi
+
+ACTION_BUDDY_ADDED = "added"
+ACTION_BUDDY_REMOVED = "removed"
+
+
+class Buddy(object):
+ def __init__(self, nick, realname, servicename, host, address, port, key=None):
+ self._nick = nick
+ self._realname = realname
+ self._servicename = servicename
+ self._key = key
+ self._host = host
+ self._address = str(address)
+ self._port = int(port)
+ self._chat = None
+
+ def set_chat(self, chat):
+ self._chat = chat
+
+ def chat(self):
+ return self._chat
+
+ def nick(self):
+ return self._nick
+
+ def realname(self):
+ return self._realname
+
+ def servicename(self):
+ return self._servicename
+
+ def host(self):
+ return self._host
+
+ def address(self):
+ return self._address
+
+ def port(self):
+ return self._port
+
+ def key(self):
+ return self._key
+
+class BuddyList(object):
+ """ Manage a list of buddies """
+
+ def __init__(self, servicename):
+ self._listeners = []
+ self._buddies = {}
+ self._servicename = servicename
+ self._pdiscovery = presence.PresenceDiscovery()
+ self._pdiscovery.add_service_listener(self._on_service_change)
+
+ def start(self):
+ self._pdiscovery.start()
+
+ def add_buddy_listener(self, listener):
+ self._listeners.append(listener)
+
+ def _add_buddy(self, host, address, port, servicename, data):
+ # Ignore ourselves
+ if servicename == self._servicename:
+ return
+
+ if len(data) > 0 and 'name' in data.keys():
+ buddy = self._find_buddy_by_service_name(servicename)
+ if not buddy:
+ buddy = Buddy(data['name'], data['realname'], servicename, host, address, port)
+ self._buddies[data['name']] = buddy
+ self._notify_listeners(ACTION_BUDDY_ADDED, buddy)
+
+ def _remove_buddy(self, buddy):
+ nick = buddy.nick()
+ self._notify_listeners(ACTION_BUDDY_REMOVED, buddy)
+ del self._buddies[nick]
+
+ def _find_buddy_by_service_name(self, servicename):
+ for buddy in self._buddies.values():
+ if buddy.servicename() == servicename:
+ return buddy
+ return None
+
+ def find_buddy_by_address(self, address):
+ for buddy_name in self._buddies.keys():
+ buddy = self._buddies[buddy_name]
+ if buddy.address() == address:
+ return buddy
+ return None
+
+ def _notify_listeners(self, action, buddy):
+ for listener in self._listeners:
+ listener(action, buddy)
+
+ def _on_service_change(self, action, interface, protocol, name, stype, domain, flags):
+ if stype != presence.OLPC_CHAT_SERVICE:
+ return
+ if action == presence.ACTION_SERVICE_NEW:
+ self._pdiscovery.resolve_service(interface, protocol, name, stype, domain, self._on_service_resolved)
+ elif action == presence.ACTION_SERVICE_REMOVED:
+ buddy = self._find_buddy_by_service_name(name)
+ if buddy:
+ self._remove_buddy(buddy)
+
+ def _pair_to_dict(self, l):
+ res = {}
+ for el in l:
+ tmp = el.split('=', 1)
+ if len(tmp) > 1:
+ res[tmp[0]] = tmp[1]
+ else:
+ res[tmp[0]] = ''
+ return res
+
+ def _on_service_resolved(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags):
+ data = self._pair_to_dict(avahi.txt_array_to_string_array(txt))
+ self._add_buddy(host, address, port, name, data)
+
diff --git a/sugar/chat/Makefile.am b/sugar/chat/Makefile.am
new file mode 100644
index 00000000..4ddf6e96
--- /dev/null
+++ b/sugar/chat/Makefile.am
@@ -0,0 +1,11 @@
+sugardir = $(pythondir)/sugar/chat
+sugar_PYTHON = \
+ chat.py \
+ richtext.py
+
+icondir = $(pkgdatadir)
+icon_DATA = \
+ bubble.png \
+ bubbleOutline.png
+
+EXTRA_DIST = $(icon_DATA)
diff --git a/sugar/chat/SVGdraw.py b/sugar/chat/SVGdraw.py
new file mode 100644
index 00000000..abcda112
--- /dev/null
+++ b/sugar/chat/SVGdraw.py
@@ -0,0 +1,1069 @@
+#!/usr/bin/env python
+##Copyright (c) 2002, Fedor Baart & Hans de Wit (Stichting Farmaceutische Kengetallen)
+##All rights reserved.
+##
+##Redistribution and use in source and binary forms, with or without modification,
+##are permitted provided that the following conditions are met:
+##
+##Redistributions of source code must retain the above copyright notice, this
+##list of conditions and the following disclaimer.
+##
+##Redistributions in binary form must reproduce the above copyright notice,
+##this list of conditions and the following disclaimer in the documentation and/or
+##other materials provided with the distribution.
+##
+##Neither the name of the Stichting Farmaceutische Kengetallen nor the names of
+##its contributors may be used to endorse or promote products derived from this
+##software without specific prior written permission.
+##
+##THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+##AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+##IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+##DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+##FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+##DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+##SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+##CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+##OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+##OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+##Thanks to Gerald Rosennfellner for his help and useful comments.
+
+__doc__="""Use SVGdraw to generate your SVGdrawings.
+
+SVGdraw uses an object model drawing and a method toXML to create SVG graphics
+by using easy to use classes and methods usualy you start by creating a drawing eg
+
+ d=drawing()
+ #then you create a SVG root element
+ s=svg()
+ #then you add some elements eg a circle and add it to the svg root element
+ c=circle()
+ #you can supply attributes by using named arguments.
+ c=circle(fill='red',stroke='blue')
+ #or by updating the attributes attribute:
+ c.attributes['stroke-width']=1
+ s.addElement(c)
+ #then you add the svg root element to the drawing
+ d.setSVG(s)
+ #and finaly you xmlify the drawing
+ d.toXml()
+
+
+this results in the svg source of the drawing, which consists of a circle
+on a white background. Its as easy as that;)
+This module was created using the SVG specification of www.w3c.org and the
+O'Reilly (www.oreilly.com) python books as information sources. A svg viewer
+is available from www.adobe.com"""
+
+__version__="1.0"
+
+# there are two possibilities to generate svg:
+# via a dom implementation and directly using text strings
+# the latter is way faster (and shorter in coding)
+# the former is only used in debugging svg programs
+# maybe it will be removed alltogether after a while
+# with the following variable you indicate whether to use the dom implementation
+# Note that PyXML is required for using the dom implementation.
+# It is also possible to use the standard minidom. But I didn't try that one.
+# Anyway the text based approach is about 60 times faster than using the full dom implementation.
+use_dom_implementation=0
+
+
+import exceptions
+if use_dom_implementation<>0:
+ try:
+ from xml.dom import implementation
+ from xml.dom.ext import PrettyPrint
+ except:
+ raise exceptions.ImportError, "PyXML is required for using the dom implementation"
+#The implementation is used for the creating the XML document.
+#The prettyprint module is used for converting the xml document object to a xml file
+
+import sys
+assert sys.version_info[0]>=2
+if sys.version_info[1]<2:
+ True=1
+ False=0
+ file=open
+
+sys.setrecursionlimit=50
+#The recursion limit is set conservative so mistakes like s=svg() s.addElement(s)
+#won't eat up too much processor time.
+
+xlinkNSRef = "http://www.w3.org/1999/xlink"
+
+#the following code is pasted form xml.sax.saxutils
+#it makes it possible to run the code without the xml sax package installed
+#To make it possible to have in your text elements, it is necessary to escape the texts
+def _escape(data, entities={}):
+ """Escape &, <, and > in a string of data.
+
+ You can escape other strings of data by passing a dictionary as
+ the optional entities parameter. The keys and values must all be
+ strings; each key will be replaced with its corresponding value.
+ """
+ data = data.replace("&", "&")
+ data = data.replace("<", "<")
+ data = data.replace(">", ">")
+ for chars, entity in entities.items():
+ data = data.replace(chars, entity)
+ return data
+
+def _quoteattr(data, entities={}):
+ """Escape and quote an attribute value.
+
+ Escape &, <, and > in a string of data, then quote it for use as
+ an attribute value. The \" character will be escaped as well, if
+ necessary.
+
+ You can escape other strings of data by passing a dictionary as
+ the optional entities parameter. The keys and values must all be
+ strings; each key will be replaced with its corresponding value.
+ """
+ data = _escape(data, entities)
+ if '"' in data:
+ if "'" in data:
+ data = '"%s"' % data.replace('"', """)
+ else:
+ data = "'%s'" % data
+ else:
+ data = '"%s"' % data
+ return data
+
+
+
+def _xypointlist(a):
+ """formats a list of xy pairs"""
+ s=''
+ for e in a: #this could be done more elegant
+ s+=str(e)[1:-1] +' '
+ return s
+
+def _viewboxlist(a):
+ """formats a tuple"""
+ s=''
+ for e in a:
+ s+=str(e)+' '
+ return s
+
+def _pointlist(a):
+ """formats a list of numbers"""
+ return str(a)[1:-1]
+
+class pathdata:
+ """class used to create a pathdata object which can be used for a path.
+ although most methods are pretty straightforward it might be useful to look at the SVG specification."""
+ #I didn't test the methods below.
+ def __init__(self,x=None,y=None):
+ self.path=[]
+ if x is not None and y is not None:
+ self.path.append('M '+str(x)+' '+str(y))
+ def closepath(self):
+ """ends the path"""
+ self.path.append('z')
+ def move(self,x,y):
+ """move to absolute"""
+ self.path.append('M '+str(x)+' '+str(y))
+ def relmove(self,x,y):
+ """move to relative"""
+ self.path.append('m '+str(x)+' '+str(y))
+ def line(self,x,y):
+ """line to absolute"""
+ self.path.append('L '+str(x)+' '+str(y))
+ def relline(self,x,y):
+ """line to relative"""
+ self.path.append('l '+str(x)+' '+str(y))
+ def hline(self,x):
+ """horizontal line to absolute"""
+ self.path.append('H'+str(x))
+ def relhline(self,x):
+ """horizontal line to relative"""
+ self.path.append('h'+str(x))
+ def vline(self,y):
+ """verical line to absolute"""
+ self.path.append('V'+str(y))
+ def relvline(self,y):
+ """vertical line to relative"""
+ self.path.append('v'+str(y))
+ def bezier(self,x1,y1,x2,y2,x,y):
+ """bezier with xy1 and xy2 to xy absolut"""
+ self.path.append('C'+str(x1)+','+str(y1)+' '+str(x2)+','+str(y2)+' '+str(x)+','+str(y))
+ def relbezier(self,x1,y1,x2,y2,x,y):
+ """bezier with xy1 and xy2 to xy relative"""
+ self.path.append('c'+str(x1)+','+str(y1)+' '+str(x2)+','+str(y2)+' '+str(x)+','+str(y))
+ def smbezier(self,x2,y2,x,y):
+ """smooth bezier with xy2 to xy absolut"""
+ self.path.append('S'+str(x2)+','+str(y2)+' '+str(x)+','+str(y))
+ def relsmbezier(self,x2,y2,x,y):
+ """smooth bezier with xy2 to xy relative"""
+ self.path.append('s'+str(x2)+','+str(y2)+' '+str(x)+','+str(y))
+ def qbezier(self,x1,y1,x,y):
+ """quadratic bezier with xy1 to xy absolut"""
+ self.path.append('Q'+str(x1)+','+str(y1)+' '+str(x)+','+str(y))
+ def relqbezier(self,x1,y1,x,y):
+ """quadratic bezier with xy1 to xy relative"""
+ self.path.append('q'+str(x1)+','+str(y1)+' '+str(x)+','+str(y))
+ def smqbezier(self,x,y):
+ """smooth quadratic bezier to xy absolut"""
+ self.path.append('T'+str(x)+','+str(y))
+ def relsmqbezier(self,x,y):
+ """smooth quadratic bezier to xy relative"""
+ self.path.append('t'+str(x)+','+str(y))
+ def ellarc(self,rx,ry,xrot,laf,sf,x,y):
+ """elliptival arc with rx and ry rotating with xrot using large-arc-flag and sweep-flag to xy absolut"""
+ self.path.append('A'+str(rx)+','+str(ry)+' '+str(xrot)+' '+str(laf)+' '+str(sf)+' '+str(x)+' '+str(y))
+ def relellarc(self,rx,ry,xrot,laf,sf,x,y):
+ """elliptival arc with rx and ry rotating with xrot using large-arc-flag and sweep-flag to xy relative"""
+ self.path.append('a'+str(rx)+','+str(ry)+' '+str(xrot)+' '+str(laf)+' '+str(sf)+' '+str(x)+' '+str(y))
+ def __repr__(self):
+ return ' '.join(self.path)
+
+
+class Attribute:
+ def __init__(self, name, value, nsname=None, nsref=None):
+ self.name = name
+ self.value = value
+ self.nsname = nsname
+ self.nsref = nsref
+
+class SVGelement:
+ """SVGelement(type,attributes,elements,text,namespace,**args)
+ Creates a arbitrary svg element and is intended to be subclassed not used on its own.
+ This element is the base of every svg element it defines a class which resembles
+ a xml-element. The main advantage of this kind of implementation is that you don't
+ have to create a toXML method for every different graph object. Every element
+ consists of a type, attribute, optional subelements, optional text and an optional
+ namespace. Note the elements==None, if elements = None:self.elements=[] construction.
+ This is done because if you default to elements=[] every object has a reference
+ to the same empty list."""
+ def __init__(self,type='',attributes=None,elements=None,text='',namespace='',cdata=None,**args):
+ self.type=type
+ self._attributes={}
+ if attributes:
+ for key, value in attributes.items():
+ attr = Attribute(key, value)
+ self._attributes[key] = attr
+ self.elements=[]
+ if elements:
+ self.elements=elements
+ self.text=text
+ self.namespace=namespace
+ self.cdata=cdata
+ for key, value in args.items():
+ attr = Attribute(key, value)
+ self._attributes[key] = attr
+ self._parent = None
+
+ def addElement(self,SVGelement):
+ """adds an element to a SVGelement
+
+ SVGelement.addElement(SVGelement)
+ """
+ self.elements.append(SVGelement)
+ SVGelement.setParent(self)
+
+ def setParent(self, parent):
+ self._parent = parent
+
+ def addAttribute(self, attribute):
+ self._attributes[attribute.name] = attribute
+
+ def toXml(self,level,f):
+ f.write('\t'*level)
+ f.write('<'+self.type)
+ if self.namespace:
+ f.write(' xmlns="'+ _escape(str(self.namespace))+'" ')
+ for attkey, attr in self._attributes.items():
+ if attr.nsname:
+ f.write(' xmlns:'+_escape(str(attr.nsname))+'="'+_escape(str(attr.nsref))+'" ')
+ f.write(' '+_escape(str(attr.nsname))+':'+_escape(str(attkey))+'='+_quoteattr(str(attr.value)))
+ else:
+ f.write(' '+_escape(str(attkey))+'='+_quoteattr(str(attr.value)))
+ if self.elements or self.text or self.cdata:
+ f.write('>')
+ if self.elements:
+ f.write('\n')
+ for element in self.elements:
+ element.toXml(level+1,f)
+ if self.cdata:
+ f.write('\n'+'\t'*(level+1)+'\n')
+ if self.text:
+ if type(self.text)==type(''): #If the text is only text
+ f.write(_escape(str(self.text)))
+ else: #If the text is a spannedtext class
+ f.write(str(self.text))
+ if self.elements:
+ f.write('\t'*level+''+self.type+'>\n')
+ elif self.text:
+ f.write(''+self.type+'>\n')
+ elif self.cdata:
+ f.write('\t'*level+''+self.type+'>\n')
+ else:
+ f.write('/>\n')
+
+class tspan(SVGelement):
+ """ts=tspan(text='',**args)
+
+ a tspan element can be used for applying formatting to a textsection
+ usage:
+ ts=tspan('this text is bold')
+ ts.attributes['font-weight']='bold'
+ st=spannedtext()
+ st.addtspan(ts)
+ t=text(3,5,st)
+ """
+ def __init__(self,text=None,**args):
+ SVGelement.__init__(self,'tspan',**args)
+ if self.text<>None:
+ self.text=text
+ def __repr__(self):
+ s="'
+ s+=self.text
+ s+=''
+ return s
+
+class tref(SVGelement):
+ """tr=tref(link='',**args)
+
+ a tref element can be used for referencing text by a link to its id.
+ usage:
+ tr=tref('#linktotext')
+ st=spannedtext()
+ st.addtref(tr)
+ t=text(3,5,st)
+ """
+ def __init__(self,link,**args):
+ SVGelement.__init__(self,'tref',**args)
+ self.addAttribute(Attribute('href', link, 'xlink', xlinkNSRef))
+ def __repr__(self):
+ s="'
+ return s
+
+class spannedtext:
+ """st=spannedtext(textlist=[])
+
+ a spannedtext can be used for text which consists of text, tspan's and tref's
+ You can use it to add to a text element or path element. Don't add it directly
+ to a svg or a group element.
+ usage:
+
+ ts=tspan('this text is bold')
+ ts.attributes['font-weight']='bold'
+ tr=tref('#linktotext')
+ tr.attributes['fill']='red'
+ st=spannedtext()
+ st.addtspan(ts)
+ st.addtref(tr)
+ st.addtext('This text is not bold')
+ t=text(3,5,st)
+ """
+ def __init__(self,textlist=None):
+ if textlist==None:
+ self.textlist=[]
+ else:
+ self.textlist=textlist
+ def addtext(self,text=''):
+ self.textlist.append(text)
+ def addtspan(self,tspan):
+ self.textlist.append(tspan)
+ def addtref(self,tref):
+ self.textlist.append(tref)
+ def __repr__(self):
+ s=""
+ for element in self.textlist:
+ s+=str(element)
+ return s
+
+class rect(SVGelement):
+ """r=rect(width,height,x,y,fill,stroke,stroke_width,**args)
+
+ a rectangle is defined by a width and height and a xy pair
+ """
+ def __init__(self,x=None,y=None,width=None,height=None,fill=None,stroke=None,stroke_width=None,**args):
+ if width==None or height==None:
+ if width<>None:
+ raise ValueError, 'height is required'
+ if height<>None:
+ raise ValueError, 'width is required'
+ else:
+ raise ValueError, 'both height and width are required'
+ SVGelement.__init__(self,'rect',{'width':width,'height':height},**args)
+ if x<>None:
+ self.addAttribute(Attribute('x', x))
+ if y<>None:
+ self.addAttribute(Attribute('y', y))
+ if fill<>None:
+ self.addAttribute(Attribute('fill', fill))
+ if stroke<>None:
+ self.addAttribute(Attribute('stroke', stroke))
+ if stroke_width<>None:
+ self.addAttribute(Attribute('stroke-width', stroke_width))
+
+class ellipse(SVGelement):
+ """e=ellipse(rx,ry,x,y,fill,stroke,stroke_width,**args)
+
+ an ellipse is defined as a center and a x and y radius.
+ """
+ def __init__(self,cx=None,cy=None,rx=None,ry=None,fill=None,stroke=None,stroke_width=None,**args):
+ if rx==None or ry== None:
+ if rx<>None:
+ raise ValueError, 'rx is required'
+ if ry<>None:
+ raise ValueError, 'ry is required'
+ else:
+ raise ValueError, 'both rx and ry are required'
+ SVGelement.__init__(self,'ellipse',{'rx':rx,'ry':ry},**args)
+ if cx<>None:
+ self.addAttribute(Attribute('cx', cx))
+ if cy<>None:
+ self.addAttribute(Attribute('cy', cy))
+ if fill<>None:
+ self.addAttribute(Attribute('fill', fill))
+ if stroke<>None:
+ self.addAttribute(Attribute('stroke', stroke))
+ if stroke_width<>None:
+ self.addAttribute(Attribute('stroke-width', stroke_width))
+
+
+class circle(SVGelement):
+ """c=circle(x,y,radius,fill,stroke,stroke_width,**args)
+
+ The circle creates an element using a x, y and radius values eg
+ """
+ def __init__(self,cx=None,cy=None,r=None,fill=None,stroke=None,stroke_width=None,**args):
+ if r==None:
+ raise ValueError, 'r is required'
+ SVGelement.__init__(self,'circle',{'r':r},**args)
+ if cx<>None:
+ self.addAttribute(Attribute('cx', cx))
+ if cy<>None:
+ self.addAttribute(Attribute('cy', cy))
+ if fill<>None:
+ self.addAttribute(Attribute('fill', fill))
+ if stroke<>None:
+ self.addAttribute(Attribute('stroke', stroke))
+ if stroke_width<>None:
+ self.addAttribute(Attribute('stroke-width', stroke_width))
+
+class point(circle):
+ """p=point(x,y,color)
+
+ A point is defined as a circle with a size 1 radius. It may be more efficient to use a
+ very small rectangle if you use many points because a circle is difficult to render.
+ """
+ def __init__(self,x,y,fill='black',**args):
+ circle.__init__(self,x,y,1,fill,**args)
+
+class line(SVGelement):
+ """l=line(x1,y1,x2,y2,stroke,stroke_width,**args)
+
+ A line is defined by a begin x,y pair and an end x,y pair
+ """
+ def __init__(self,x1=None,y1=None,x2=None,y2=None,stroke=None,stroke_width=None,**args):
+ SVGelement.__init__(self,'line',**args)
+ if x1<>None:
+ self.addAttribute(Attribute('x1', x1))
+ if y1<>None:
+ self.addAttribute(Attribute('y1', y1))
+ if x2<>None:
+ self.addAttribute(Attribute('x2', x2))
+ if y2<>None:
+ self.addAttribute(Attribute('y2', y2))
+ if stroke_width<>None:
+ self.addAttribute(Attribute('stroke-width', stroke_width))
+ if stroke<>None:
+ self.addAttribute(Attribute('stroke', stroke))
+
+class polyline(SVGelement):
+ """pl=polyline([[x1,y1],[x2,y2],...],fill,stroke,stroke_width,**args)
+
+ a polyline is defined by a list of xy pairs
+ """
+ def __init__(self,points,fill=None,stroke=None,stroke_width=None,**args):
+ SVGelement.__init__(self,'polyline',{'points':_xypointlist(points)},**args)
+ if fill<>None:
+ self.addAttribute(Attribute('fill', fill))
+ if stroke_width<>None:
+ self.addAttribute(Attribute('stroke-width', stroke_width))
+ if stroke<>None:
+ self.addAttribute(Attribute('stroke', stroke))
+
+class polygon(SVGelement):
+ """pl=polyline([[x1,y1],[x2,y2],...],fill,stroke,stroke_width,**args)
+
+ a polygon is defined by a list of xy pairs
+ """
+ def __init__(self,points,fill=None,stroke=None,stroke_width=None,**args):
+ SVGelement.__init__(self,'polygon',{'points':_xypointlist(points)},**args)
+ if fill<>None:
+ self.addAttribute(Attribute('fill', fill))
+ if stroke_width<>None:
+ self.addAttribute(Attribute('stroke-width', stroke_width))
+ if stroke<>None:
+ self.addAttribute(Attribute('stroke', stroke))
+
+class path(SVGelement):
+ """p=path(path,fill,stroke,stroke_width,**args)
+
+ a path is defined by a path object and optional width, stroke and fillcolor
+ """
+ def __init__(self,pathdata,fill=None,stroke=None,stroke_width=None,id=None,**args):
+ SVGelement.__init__(self,'path',{'d':str(pathdata)},**args)
+ if stroke<>None:
+ self.addAttribute(Attribute('stroke', stroke))
+ if fill<>None:
+ self.addAttribute(Attribute('fill', fill))
+ if stroke_width<>None:
+ self.addAttribute(Attribute('stroke-width', stroke_width))
+ if id<>None:
+ self.addAttribute(Attribute('id', id))
+
+
+class text(SVGelement):
+ """t=text(x,y,text,font_size,font_family,**args)
+
+ a text element can bge used for displaying text on the screen
+ """
+ def __init__(self,x=None,y=None,text=None,font_size=None,font_family=None,text_anchor=None,**args):
+ SVGelement.__init__(self,'text',**args)
+ if x<>None:
+ self.addAttribute(Attribute('x', x))
+ if y<>None:
+ self.addAttribute(Attribute('y', y))
+ if font_size<>None:
+ self.addAttribute(Attribute('font-size', font_size))
+ if font_family<>None:
+ self.addAttribute(Attribute('font-family', font_family))
+ if text<>None:
+ self.text=text
+ if text_anchor<>None:
+ self.addAttribute(Attribute('text-anchor', text_anchor))
+
+
+class textpath(SVGelement):
+ """tp=textpath(text,link,**args)
+
+ a textpath places a text on a path which is referenced by a link.
+ """
+ def __init__(self,link,text=None,**args):
+ SVGelement.__init__(self,'textPath',**args)
+ self.addAttribute(Attribute('href', link, 'xlink', xlinkNSRef))
+ if text<>None:
+ self.text=text
+
+class pattern(SVGelement):
+ """p=pattern(x,y,width,height,patternUnits,**args)
+
+ A pattern is used to fill or stroke an object using a pre-defined
+ graphic object which can be replicated ("tiled") at fixed intervals
+ in x and y to cover the areas to be painted.
+ """
+ def __init__(self,x=None,y=None,width=None,height=None,patternUnits=None,**args):
+ SVGelement.__init__(self,'pattern',**args)
+ if x<>None:
+ self.addAttribute(Attribute('x', x))
+ if y<>None:
+ self.addAttribute(Attribute('y', y))
+ if width<>None:
+ self.addAttribute(Attribute('width', width))
+ if height<>None:
+ self.addAttribute(Attribute('height', height))
+ if patternUnits<>None:
+ self.addAttribute(Attribute('patternUnits', patternUnits))
+
+class title(SVGelement):
+ """t=title(text,**args)
+
+ a title is a text element. The text is displayed in the title bar
+ add at least one to the root svg element
+ """
+ def __init__(self,text=None,**args):
+ SVGelement.__init__(self,'title',**args)
+ if text<>None:
+ self.text=text
+
+class description(SVGelement):
+ """d=description(text,**args)
+
+ a description can be added to any element and is used for a tooltip
+ Add this element before adding other elements.
+ """
+ def __init__(self,text=None,**args):
+ SVGelement.__init__(self,'desc',**args)
+ if text<>None:
+ self.text=text
+
+class lineargradient(SVGelement):
+ """lg=lineargradient(x1,y1,x2,y2,id,**args)
+
+ defines a lineargradient using two xy pairs.
+ stop elements van be added to define the gradient colors.
+ """
+ def __init__(self,x1=None,y1=None,x2=None,y2=None,id=None,**args):
+ SVGelement.__init__(self,'linearGradient',**args)
+ if x1<>None:
+ self.addAttribute(Attribute('x1', x1))
+ if y1<>None:
+ self.addAttribute(Attribute('y1', y1))
+ if x2<>None:
+ self.addAttribute(Attribute('x2', x2))
+ if y2<>None:
+ self.addAttribute(Attribute('y2', y2))
+ if id<>None:
+ self.addAttribute(Attribute('id', id))
+
+class radialgradient(SVGelement):
+ """rg=radialgradient(cx,cy,r,fx,fy,id,**args)
+
+ defines a radial gradient using a outer circle which are defined by a cx,cy and r and by using a focalpoint.
+ stop elements van be added to define the gradient colors.
+ """
+ def __init__(self,cx=None,cy=None,r=None,fx=None,fy=None,id=None,**args):
+ SVGelement.__init__(self,'radialGradient',**args)
+ if cx<>None:
+ self.addAttribute(Attribute('cx', cx))
+ if cy<>None:
+ self.addAttribute(Attribute('cy', cy))
+ if r<>None:
+ self.addAttribute(Attribute('r', r))
+ if fx<>None:
+ self.addAttribute(Attribute('fx', fx))
+ if fy<>None:
+ self.addAttribute(Attribute('fy', fy))
+ if id<>None:
+ self.addAttribute(Attribute('id', id))
+
+class stop(SVGelement):
+ """st=stop(offset,stop_color,**args)
+
+ Puts a stop color at the specified radius
+ """
+ def __init__(self,offset,stop_color=None,**args):
+ SVGelement.__init__(self,'stop',{'offset':offset},**args)
+ if stop_color<>None:
+ self.addAttribute(Attribute('stop-color', stop_color))
+
+class style(SVGelement):
+ """st=style(type,cdata=None,**args)
+
+ Add a CDATA element to this element for defing in line stylesheets etc..
+ """
+ def __init__(self,type,cdata=None,**args):
+ SVGelement.__init__(self,'style',{'type':type},cdata=cdata, **args)
+
+
+class image(SVGelement):
+ """im=image(url,width,height,x,y,**args)
+
+ adds an image to the drawing. Supported formats are .png, .jpg and .svg.
+ """
+ def __init__(self,url,x=None,y=None,width=None,height=None,**args):
+ if width==None or height==None:
+ if width<>None:
+ raise ValueError, 'height is required'
+ if height<>None:
+ raise ValueError, 'width is required'
+ else:
+ raise ValueError, 'both height and width are required'
+ SVGelement.__init__(self,'image',{'width':width,'height':height},**args)
+ self.addAttribute(Attribute('href', url, 'xlink', xlinkNSRef))
+ if x<>None:
+ self.addAttribute(Attribute('x', x))
+ if y<>None:
+ self.addAttribute(Attribute('y', y))
+
+class cursor(SVGelement):
+ """c=cursor(url,**args)
+
+ defines a custom cursor for a element or a drawing
+ """
+ def __init__(self,url,**args):
+ SVGelement.__init__(self,'cursor',**args)
+ self.addAttribute(Attribute('href', url, 'xlink', xlinkNSRef))
+
+
+class marker(SVGelement):
+ """m=marker(id,viewbox,refX,refY,markerWidth,markerHeight,**args)
+
+ defines a marker which can be used as an endpoint for a line or other pathtypes
+ add an element to it which should be used as a marker.
+ """
+ def __init__(self,id=None,viewBox=None,refx=None,refy=None,markerWidth=None,markerHeight=None,**args):
+ SVGelement.__init__(self,'marker',**args)
+ if id<>None:
+ self.addAttribute(Attribute('id', id))
+ if viewBox<>None:
+ self.addAttribute(Attribute('viewBox', _viewboxlist(viewBox)))
+ if refx<>None:
+ self.addAttribute(Attribute('refX', refx))
+ if refy<>None:
+ self.addAttribute(Attribute('refY', refy))
+ if markerWidth<>None:
+ self.addAttribute(Attribute('markerWidth', markerWidth))
+ if markerHeight<>None:
+ self.addAttribute(Attribute('markerHeight', markerHeight))
+
+class group(SVGelement):
+ """g=group(id,**args)
+
+ a group is defined by an id and is used to contain elements
+ g.addElement(SVGelement)
+ """
+ def __init__(self,id=None,**args):
+ SVGelement.__init__(self,'g',**args)
+ if id<>None:
+ self.addAttribute(Attribute('id', id))
+
+class symbol(SVGelement):
+ """sy=symbol(id,viewbox,**args)
+
+ defines a symbol which can be used on different places in your graph using
+ the use element. A symbol is not rendered but you can use 'use' elements to
+ display it by referencing its id.
+ sy.addElement(SVGelement)
+ """
+
+ def __init__(self,id=None,viewBox=None,**args):
+ SVGelement.__init__(self,'symbol',**args)
+ if id<>None:
+ self.addAttribute(Attribute('id', id))
+ if viewBox<>None:
+ self.addAttribute(Attribute('viewBox', _viewboxlist(viewBox)))
+
+class defs(SVGelement):
+ """d=defs(**args)
+
+ container for defining elements
+ """
+ def __init__(self,**args):
+ SVGelement.__init__(self,'defs',**args)
+
+class switch(SVGelement):
+ """sw=switch(**args)
+
+ Elements added to a switch element which are "switched" by the attributes
+ requiredFeatures, requiredExtensions and systemLanguage.
+ Refer to the SVG specification for details.
+ """
+ def __init__(self,**args):
+ SVGelement.__init__(self,'switch',**args)
+
+
+class use(SVGelement):
+ """u=use(link,x,y,width,height,**args)
+
+ references a symbol by linking to its id and its position, height and width
+ """
+ def __init__(self,link,x=None,y=None,width=None,height=None,**args):
+ SVGelement.__init__(self,'use',**args)
+ self.addAttribute(Attribute('href', link, 'xlink', xlinkNSRef))
+ if x<>None:
+ self.addAttribute(Attribute('x', x))
+ if y<>None:
+ self.addAttribute(Attribute('y', y))
+
+ if width<>None:
+ self.addAttribute(Attribute('width', width))
+ if height<>None:
+ self.addAttribute(Attribute('height', height))
+
+
+class link(SVGelement):
+ """a=link(url,**args)
+
+ a link is defined by a hyperlink. add elements which have to be linked
+ a.addElement(SVGelement)
+ """
+ def __init__(self,link='',**args):
+ SVGelement.__init__(self,'a',**args)
+ self.addAttribute(Attribute('href', link, 'xlink', xlinkNSRef))
+
+class view(SVGelement):
+ """v=view(id,**args)
+
+ a view can be used to create a view with different attributes"""
+ def __init__(self,id=None,**args):
+ SVGelement.__init__(self,'view',**args)
+ if id<>None:
+ self.addAttribute(Attribute('id', id))
+
+class script(SVGelement):
+ """sc=script(type,type,cdata,**args)
+
+ adds a script element which contains CDATA to the SVG drawing
+
+ """
+ def __init__(self,type,cdata=None,**args):
+ SVGelement.__init__(self,'script',{'type':type},cdata=cdata,**args)
+
+class animate(SVGelement):
+ """an=animate(attribute,from,to,during,**args)
+
+ animates an attribute.
+ """
+ def __init__(self,attribute,fr=None,to=None,dur=None,**args):
+ SVGelement.__init__(self,'animate',{'attributeName':attribute},**args)
+ if fr<>None:
+ self.addAttribute(Attribute('from', fr))
+ if to<>None:
+ self.addAttribute(Attribute('to', to))
+ if dur<>None:
+ self.addAttribute(Attribute('dur', dur))
+
+class animateMotion(SVGelement):
+ """an=animateMotion(pathdata,dur,**args)
+
+ animates a SVGelement over the given path in dur seconds
+ """
+ def __init__(self,pathdata,dur,**args):
+ SVGelement.__init__(self,'animateMotion',**args)
+ if pathdata<>None:
+ self.addAttribute(Attribute('path', str(pathdata)))
+ if dur<>None:
+ self.addAttribute(Attribute('dur', dur))
+
+class animateTransform(SVGelement):
+ """antr=animateTransform(type,from,to,dur,**args)
+
+ transform an element from and to a value.
+ """
+ def __init__(self,type=None,fr=None,to=None,dur=None,**args):
+ SVGelement.__init__(self,'animateTransform',{'attributeName':'transform'},**args)
+ #As far as I know the attributeName is always transform
+ if type<>None:
+ self.addAttribute(Attribute('type', type))
+ if fr<>None:
+ self.addAttribute(Attribute('from', fr))
+ if to<>None:
+ self.addAttribute(Attribute('to', to))
+ if dur<>None:
+ self.addAttribute(Attribute('dur', dur))
+class animateColor(SVGelement):
+ """ac=animateColor(attribute,type,from,to,dur,**args)
+
+ Animates the color of a element
+ """
+ def __init__(self,attribute,type=None,fr=None,to=None,dur=None,**args):
+ SVGelement.__init__(self,'animateColor',{'attributeName':attribute},**args)
+ if type<>None:
+ self.addAttribute(Attribute('type', type))
+ if fr<>None:
+ self.addAttribute(Attribute('from', fr))
+ if to<>None:
+ self.addAttribute(Attribute('to', to))
+ if dur<>None:
+ self.addAttribute(Attribute('dur', dur))
+class set(SVGelement):
+ """st=set(attribute,to,during,**args)
+
+ sets an attribute to a value for a
+ """
+ def __init__(self,attribute,to=None,dur=None,**args):
+ SVGelement.__init__(self,'set',{'attributeName':attribute},**args)
+ if to<>None:
+ self.addAttribute(Attribute('to', to))
+ if dur<>None:
+ self.addAttribute(Attribute('dur', dur))
+
+
+
+class svg(SVGelement):
+ """s=svg(viewbox,width,height,**args)
+
+ a svg or element is the root of a drawing add all elements to a svg element.
+ You can have different svg elements in one svg file
+ s.addElement(SVGelement)
+
+ eg
+ d=drawing()
+ s=svg((0,0,100,100),'100%','100%')
+ c=circle(50,50,20)
+ s.addElement(c)
+ d.setSVG(s)
+ d.toXml()
+ """
+ def __init__(self,viewBox=None, width=None, height=None,**args):
+ SVGelement.__init__(self,'svg',**args)
+ if viewBox<>None:
+ self.addAttribute(Attribute('viewBox', _viewboxlist(viewBox)))
+ if width<>None:
+ self.addAttribute(Attribute('width', width))
+ if height<>None:
+ self.addAttribute(Attribute('height', height_))
+ self.namespace="http://www.w3.org/2000/svg"
+
+class drawing:
+ """d=drawing()
+
+ this is the actual SVG document. It needs a svg element as a root.
+ Use the addSVG method to set the svg to the root. Use the toXml method to write the SVG
+ source to the screen or to a file
+ d=drawing()
+ d.addSVG(svg)
+ d.toXml(optionalfilename)
+ """
+
+ def __init__(self):
+ self.svg=None
+
+ def setSVG(self,svg):
+ self.svg=svg
+ #Voeg een element toe aan de grafiek toe.
+
+ if use_dom_implementation==0:
+ def toXml(self, filename='',compress=False):
+ import cStringIO
+ xml=cStringIO.StringIO()
+ xml.write("\n")
+ xml.write("\n")
+ self.svg.toXml(0,xml)
+ if not filename:
+ if compress:
+ import gzip
+ f=cStringIO.StringIO()
+ zf=gzip.GzipFile(fileobj=f,mode='wb')
+ zf.write(xml.getvalue())
+ zf.close()
+ f.seek(0)
+ return f.read()
+ else:
+ return xml.getvalue()
+ else:
+ if filename[-4:]=='svgz':
+ import gzip
+ f=gzip.GzipFile(filename=filename,mode="wb", compresslevel=9)
+ f.write(xml.getvalue())
+ f.close()
+ else:
+ f=file(filename,'w')
+ f.write(xml.getvalue())
+ f.close()
+
+ else:
+ def toXml(self,filename='',compress=False):
+ """drawing.toXml() ---->to the screen
+ drawing.toXml(filename)---->to the file
+ writes a svg drawing to the screen or to a file
+ compresses if filename ends with svgz or if compress is true
+ """
+ doctype = implementation.createDocumentType('svg',"-//W3C//DTD SVG 1.0//EN""",'http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd ')
+
+ global root
+ #root is defined global so it can be used by the appender. Its also possible to use it as an arugument but
+ #that is a bit messy.
+ root=implementation.createDocument(None,None,doctype)
+ #Create the xml document.
+ global appender
+ def appender(element,elementroot):
+ """This recursive function appends elements to an element and sets the attributes
+ and type. It stops when alle elements have been appended"""
+ if element.namespace:
+ e=root.createElementNS(element.namespace,element.type)
+ else:
+ e=root.createElement(element.type)
+ if element.text:
+ textnode=root.createTextNode(element.text)
+ e.appendChild(textnode)
+ for attr in element.attributes.values(): #in element.attributes is supported from python 2.2
+ if attr.nsname and attr.nsref:
+ e.setAttributeNS(attr.nsref, attr.nsname+":"+attr.name, str(attr.value))
+ else:
+ e.setAttribute(attr.name,str(attr.value))
+ if element.elements:
+ for el in element.elements:
+ e=appender(el,e)
+ elementroot.appendChild(e)
+ return elementroot
+ root=appender(self.svg,root)
+ if not filename:
+ import cStringIO
+ xml=cStringIO.StringIO()
+ PrettyPrint(root,xml)
+ if compress:
+ import gzip
+ f=cStringIO.StringIO()
+ zf=gzip.GzipFile(fileobj=f,mode='wb')
+ zf.write(xml.getvalue())
+ zf.close()
+ f.seek(0)
+ return f.read()
+ else:
+ return xml.getvalue()
+ else:
+ try:
+ if filename[-4:]=='svgz':
+ import gzip
+ import cStringIO
+ xml=cStringIO.StringIO()
+ PrettyPrint(root,xml)
+ f=gzip.GzipFile(filename=filename,mode='wb',compresslevel=9)
+ f.write(xml.getvalue())
+ f.close()
+ else:
+ f=open(filename,'w')
+ PrettyPrint(root,f)
+ f.close()
+ except:
+ print "Cannot write SVG file: " + filename
+
+ def validate(self):
+ try:
+ import xml.parsers.xmlproc.xmlval
+ except:
+ raise exceptions.ImportError,'PyXml is required for validating SVG'
+ svg=self.toXml()
+ xv=xml.parsers.xmlproc.xmlval.XMLValidator()
+ try:
+ xv.feed(svg)
+ except:
+ raise "SVG is not well formed, see messages above"
+ else:
+ print "SVG well formed"
+
+
+if __name__=='__main__':
+
+
+ d=drawing()
+ s=svg((0,0,100,100))
+ r=rect(-100,-100,300,300,'cyan')
+ s.addElement(r)
+
+ t=title('SVGdraw Demo')
+ s.addElement(t)
+ g=group('animations')
+ e=ellipse(0,0,5,2)
+ g.addElement(e)
+ c=circle(0,0,1,'red')
+ g.addElement(c)
+ pd=pathdata(0,-10)
+ for i in range(6):
+ pd.relsmbezier(10,5,0,10)
+ pd.relsmbezier(-10,5,0,10)
+ an=animateMotion(pd,10)
+ an.addAttribute(Attribute('rotate', 'auto-reverse'))
+ an.addAttribute(Attribute('repeatCount', "indefinite"))
+ g.addElement(an)
+ s.addElement(g)
+ for i in range(20,120,20):
+ u=use('#animations',i,0)
+ s.addElement(u)
+ for i in range(0,120,20):
+ for j in range(5,105,10):
+ c=circle(i,j,1,'red','black',.5)
+ s.addElement(c)
+ d.setSVG(s)
+
+ print d.toXml()
+
diff --git a/sugar/chat/bubble.png b/sugar/chat/bubble.png
new file mode 100644
index 00000000..3d1503df
Binary files /dev/null and b/sugar/chat/bubble.png differ
diff --git a/sugar/chat/bubbleOutline.png b/sugar/chat/bubbleOutline.png
new file mode 100644
index 00000000..334ddca9
Binary files /dev/null and b/sugar/chat/bubbleOutline.png differ
diff --git a/sugar/chat/chat.py b/sugar/chat/chat.py
new file mode 100755
index 00000000..2bc72305
--- /dev/null
+++ b/sugar/chat/chat.py
@@ -0,0 +1,405 @@
+#!/usr/bin/python -t
+# -*- tab-width: 4; indent-tabs-mode: t -*-
+
+import dbus
+import dbus.service
+import dbus.glib
+
+import pygtk
+pygtk.require('2.0')
+import gtk, gobject
+
+from sugar.shell import activity
+from sugar.p2p.Group import *
+from sugar.p2p.StreamReader import *
+from sugar.p2p.StreamWriter import *
+import sugar.env
+
+import richtext
+
+CHAT_SERVICE_TYPE = "_olpc_chat._tcp"
+CHAT_SERVICE_PORT = 6100
+
+GROUP_CHAT_SERVICE_TYPE = "_olpc_group_chat._udp"
+GROUP_CHAT_SERVICE_ADDRESS = "224.0.0.221"
+GROUP_CHAT_SERVICE_PORT = 6200
+
+class Chat(activity.Activity):
+ def __init__(self, controller):
+ self._controller = controller
+ activity.Activity.__init__(self)
+
+ def activity_on_connected_to_shell(self):
+ self.activity_set_tab_text(self._act_name)
+ self._plug = self.activity_get_gtk_plug()
+ self._ui_setup(self._plug)
+ self._plug.show_all()
+
+ def _create_chat(self):
+ chat_vbox = gtk.VBox()
+ chat_vbox.set_spacing(6)
+
+ sw = gtk.ScrolledWindow()
+ sw.set_shadow_type(gtk.SHADOW_IN)
+ sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
+ self._chat_view = richtext.RichTextView()
+ self._chat_view.connect("link-clicked", self.__link_clicked_cb)
+ self._chat_view.set_editable(False)
+ self._chat_view.set_cursor_visible(False)
+ sw.add(self._chat_view)
+ self._chat_view.show()
+ chat_vbox.pack_start(sw)
+ sw.show()
+
+ chat_view_sw = gtk.ScrolledWindow()
+ chat_view_sw.set_shadow_type(gtk.SHADOW_IN)
+ chat_view_sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self._editor = richtext.RichTextView()
+ self._editor.connect("key-press-event", self.__key_press_event_cb)
+ self._editor.set_size_request(-1, 50)
+ chat_view_sw.add(self._editor)
+ self._editor.show()
+
+ chat_vbox.pack_start(chat_view_sw, False)
+ chat_view_sw.show()
+
+ return chat_vbox, self._editor.get_buffer()
+
+ def _ui_setup(self, base):
+ vbox = gtk.VBox(False, 6)
+
+ self._hbox = gtk.HBox(False, 12)
+ self._hbox.set_border_width(12)
+
+ [chat_vbox, buffer] = self._create_chat()
+ self._hbox.pack_start(chat_vbox)
+ chat_vbox.show()
+
+ vbox.pack_start(self._hbox)
+ self._hbox.show()
+
+ toolbar = self._create_toolbar(buffer)
+ vbox.pack_start(toolbar, False)
+ toolbar.show()
+
+ base.add(vbox)
+ vbox.show()
+
+ def __link_clicked_cb(self, view, address):
+ self._browser_shell.open_browser(address)
+
+ def __key_press_event_cb(self, text_view, event):
+ if event.keyval == gtk.keysyms.Return:
+ buf = text_view.get_buffer()
+
+ serializer = richtext.RichTextSerializer()
+ text = serializer.serialize(buf)
+ self.send_message(text)
+
+ buf.set_text("")
+ buf.place_cursor(buf.get_start_iter())
+
+ return True
+
+ def _create_toolbar(self, rich_buf):
+ toolbar = richtext.RichTextToolbar(rich_buf)
+
+ item = gtk.MenuToolButton(None, "Links")
+ item.set_menu(gtk.Menu())
+ item.connect("show-menu", self.__show_link_menu_cb)
+ toolbar.insert(item, -1)
+ item.show()
+
+ return toolbar
+
+ def __link_activate_cb(self, item, link):
+ buf = self._editor.get_buffer()
+ buf.append_link(link['title'], link['address'])
+
+ def __show_link_menu_cb(self, button):
+ menu = gtk.Menu()
+
+ links = self._browser_shell.get_links()
+
+ for link in links:
+ item = gtk.MenuItem(link['title'], False)
+ item.connect("activate", self.__link_activate_cb, link)
+ menu.append(item)
+ item.show()
+
+ button.set_menu(menu)
+
+ def activity_on_close_from_user(self):
+ print "act %d: in activity_on_close_from_user"%self.activity_get_id()
+ self.activity_shutdown()
+
+ def activity_on_lost_focus(self):
+ print "act %d: in activity_on_lost_focus"%self.activity_get_id()
+
+ def activity_on_got_focus(self):
+ print "act %d: in activity_on_got_focus"%self.activity_get_id()
+ self._controller.notify_activate(self)
+
+ def recv_message(self, buddy, msg):
+ self._insert_rich_message(buddy.get_nick_name(), msg)
+ self._controller.notify_new_message(self, buddy)
+
+ def _insert_rich_message(self, nick, msg):
+ buffer = self._chat_view.get_buffer()
+ aniter = buffer.get_end_iter()
+ buffer.insert(aniter, nick + ": ")
+
+ serializer = richtext.RichTextSerializer()
+ serializer.deserialize(msg, buffer)
+
+ aniter = buffer.get_end_iter()
+ buffer.insert(aniter, "\n")
+
+ def _local_message(self, success, text):
+ if not success:
+ message = "Error: %s\n" % text
+ buffer = self._chat_view.get_buffer()
+ aniter = buffer.get_end_iter()
+ buffer.insert(aniter, message)
+ else:
+ owner = self._controller.get_group().get_owner()
+ self._insert_rich_message(owner.get_nick_name(), text)
+
+class BuddyChat(Chat):
+ def __init__(self, controller, buddy):
+ self._buddy = buddy
+ self._act_name = "Chat: %s" % buddy.get_nick_name()
+ Chat.__init__(self, controller)
+
+ def _start(self):
+ group = self._controller.get_group()
+ buddy_name = self._buddy.get_service_name()
+ service = group.get_service(buddy_name, CHAT_SERVICE_TYPE)
+ self._stream_writer = StreamWriter(group, service)
+
+ def activity_on_connected_to_shell(self):
+ Chat.activity_on_connected_to_shell(self)
+ self.activity_set_can_close(True)
+ self.activity_set_tab_icon_name("im")
+ self.activity_show_icon(True)
+ self._start()
+
+ def recv_message(self, sender, msg):
+ Chat.recv_message(self, self._buddy, msg)
+
+ def send_message(self, text):
+ if len(text) > 0:
+ self._stream_writer.write(text)
+ self._local_message(True, text)
+
+ def activity_on_close_from_user(self):
+ Chat.activity_on_close_from_user(self)
+ del self._chats[self._buddy]
+
+class GroupChat(Chat):
+
+ _MODEL_COL_NICK = 0
+ _MODEL_COL_ICON = 1
+ _MODEL_COL_BUDDY = 2
+
+ def __init__(self):
+ self._act_name = "Chat"
+ self._chats = {}
+
+ bus = dbus.SessionBus()
+ proxy_obj = bus.get_object('com.redhat.Sugar.Browser', '/com/redhat/Sugar/Browser')
+ self._browser_shell = dbus.Interface(proxy_obj, 'com.redhat.Sugar.BrowserShell')
+
+ Chat.__init__(self, self)
+
+ def get_group(self):
+ return self._group
+
+ def _start(self):
+ self._group = LocalGroup()
+ self._group.add_presence_listener(self._on_group_event)
+ self._group.join()
+
+ name = self._group.get_owner().get_service_name()
+ service = Service(name, CHAT_SERVICE_TYPE, '', CHAT_SERVICE_PORT)
+ self._buddy_reader = StreamReader(self._group, service)
+ self._buddy_reader.set_listener(self._buddy_recv_message)
+ service.register(self._group)
+
+ service = Service(name, GROUP_CHAT_SERVICE_TYPE,
+ GROUP_CHAT_SERVICE_ADDRESS,
+ GROUP_CHAT_SERVICE_PORT, True)
+ self._group.add_service(service)
+
+ self._buddy_reader = StreamReader(self._group, service)
+ self._buddy_reader.set_listener(self.recv_message)
+
+ self._stream_writer = StreamWriter(self._group, service)
+
+ def _create_sidebar(self):
+ vbox = gtk.VBox(False, 6)
+
+ label = gtk.Label("Who's around:")
+ label.set_alignment(0.0, 0.5)
+ vbox.pack_start(label, False)
+ label.show()
+
+ self._buddy_list_model = gtk.ListStore(gobject.TYPE_STRING, gtk.gdk.Pixbuf, gobject.TYPE_PYOBJECT)
+
+ image_path = sugar.env.get_data_file('bubbleOutline.png')
+ self._pixbuf_active_chat = gtk.gdk.pixbuf_new_from_file(image_path)
+
+ image_path = sugar.env.get_data_file('bubble.png')
+ self._pixbuf_new_message = gtk.gdk.pixbuf_new_from_file(image_path)
+
+ sw = gtk.ScrolledWindow()
+ sw.set_shadow_type(gtk.SHADOW_IN)
+ sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+
+ self._buddy_list_view = gtk.TreeView(self._buddy_list_model)
+ self._buddy_list_view.set_headers_visible(False)
+ self._buddy_list_view.connect("cursor-changed", self._on_buddyList_buddy_selected)
+ self._buddy_list_view.connect("row-activated", self._on_buddyList_buddy_double_clicked)
+
+ sw.set_size_request(120, -1)
+ sw.add(self._buddy_list_view)
+ self._buddy_list_view.show()
+
+ renderer = gtk.CellRendererPixbuf()
+ column = gtk.TreeViewColumn("", renderer, pixbuf=self._MODEL_COL_ICON)
+ column.set_resizable(False)
+ column.set_expand(False);
+ self._buddy_list_view.append_column(column)
+
+ renderer = gtk.CellRendererText()
+ column = gtk.TreeViewColumn("", renderer, text=self._MODEL_COL_NICK)
+ column.set_resizable(True)
+ column.set_sizing("GTK_TREE_VIEW_COLUMN_GROW_ONLY");
+ column.set_expand(True);
+ self._buddy_list_view.append_column(column)
+
+ vbox.pack_start(sw)
+ sw.show()
+
+ return vbox
+
+ def _ui_setup(self, base):
+ Chat._ui_setup(self, base)
+
+ sidebar = self._create_sidebar()
+ self._hbox.pack_start(sidebar, False)
+ sidebar.show()
+ self._plug.show_all()
+
+ def activity_on_connected_to_shell(self):
+ Chat.activity_on_connected_to_shell(self)
+
+ self.activity_set_tab_icon_name("stock_help-chat")
+ self.activity_show_icon(True)
+
+ aniter = self._buddy_list_model.append(None)
+ self._buddy_list_model.set(aniter, self._MODEL_COL_NICK, "Group",
+ self._MODEL_COL_ICON, self._pixbuf_active_chat, self._MODEL_COL_BUDDY, None)
+ self._start()
+
+ def activity_on_disconnected_from_shell(self):
+ Chat.activity_on_disconnected_from_shell(self)
+ gtk.main_quit()
+
+ def _on_buddyList_buddy_selected(self, widget, *args):
+ (model, aniter) = widget.get_selection().get_selected()
+ name = self._buddy_list_model.get(aniter, self._MODEL_COL_NICK)
+ print "Selected %s" % name
+
+ def _on_buddyList_buddy_double_clicked(self, widget, *args):
+ """ Select the chat for this buddy or group """
+ (model, aniter) = widget.get_selection().get_selected()
+ chat = None
+ buddy = self._buddy_list_model.get_value(aniter, self._MODEL_COL_BUDDY)
+ if buddy and not self._chats.has_key(buddy):
+ chat = BuddyChat(self, buddy)
+ self._chats[buddy] = chat
+ chat.activity_connect_to_shell()
+
+ def _on_group_event(self, action, buddy):
+ if buddy.get_nick_name() == self._group.get_owner().get_nick_name():
+ # Do not show ourself in the buddy list
+ pass
+ elif action == BUDDY_JOIN:
+ aniter = self._buddy_list_model.append(None)
+ self._buddy_list_model.set(aniter, self._MODEL_COL_NICK, buddy.get_nick_name(),
+ self._MODEL_COL_ICON, None, self._MODEL_COL_BUDDY, buddy)
+ elif action == BUDDY_LEAVE:
+ aniter = self._get_iter_for_buddy(buddy)
+ if aniter:
+ self._buddy_list_model.remove(aniter)
+
+ def _get_iter_for_buddy(self, buddy):
+ aniter = self._buddy_list_model.get_iter_first()
+ while aniter:
+ list_buddy = self._buddy_list_model.get_value(aniter, self._MODEL_COL_BUDDY)
+ if buddy == list_buddy:
+ return aniter
+ aniter = self._buddy_list_model.iter_next(aniter)
+
+ def notify_new_message(self, chat, buddy):
+ aniter = self._get_iter_for_buddy(buddy)
+ self._buddy_list_model.set(aniter, self._MODEL_COL_ICON, self._pixbuf_new_message)
+
+ def notify_activate(self, chat):
+ aniter = self._get_iter_for_buddy(buddy)
+ self._buddy_list_model.set(aniter, self._MODEL_COL_ICON, self._pixbuf_active_chat)
+
+ def send_message(self, text):
+ if len(text) > 0:
+ self._stream_writer.write(text)
+ self._local_message(True, text)
+
+ def recv_message(self, buddy, msg):
+ if buddy:
+ self._insert_rich_message(buddy.get_nick_name(), msg)
+ self._controller.notify_new_message(self, None)
+
+ def _buddy_recv_message(self, sender, msg):
+ if not self._chats.has_key(sender):
+ chat = BuddyChat(self, sender)
+ self._chats[sender] = chat
+ chat.activity_connect_to_shell()
+ else:
+ chat = self._chats[sender]
+ chat.recv_message(sender, msg)
+
+class ChatShell(dbus.service.Object):
+ instance = None
+
+ def get_instance():
+ if not ChatShell.instance:
+ ChatShell.instance = ChatShell()
+ return ChatShell.instance
+
+ get_instance = staticmethod(get_instance)
+
+ def __init__(self):
+ session_bus = dbus.SessionBus()
+ bus_name = dbus.service.BusName('com.redhat.Sugar.Chat', bus=session_bus)
+ object_path = '/com/redhat/Sugar/Chat'
+
+ dbus.service.Object.__init__(self, bus_name, object_path)
+
+ def open_group_chat(self):
+ group_chat = GroupChat()
+ group_chat.activity_connect_to_shell()
+
+ @dbus.service.method('com.redhat.Sugar.ChatShell')
+ def send_message(self, message):
+ pass
+
+def main():
+ ChatShell.get_instance().open_group_chat()
+ try:
+ gtk.main()
+ except KeyboardInterrupt:
+ pass
+
+if __name__ == "__main__":
+ main()
diff --git a/sugar/chat/richtext.py b/sugar/chat/richtext.py
new file mode 100644
index 00000000..0ac70b16
--- /dev/null
+++ b/sugar/chat/richtext.py
@@ -0,0 +1,374 @@
+#!/usr/bin/env python
+
+import pygtk
+import gobject
+pygtk.require('2.0')
+import gtk
+import pango
+import xml.sax
+
+class RichTextView(gtk.TextView):
+
+ __gsignals__ = {
+ 'link-clicked': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
+ ([gobject.TYPE_STRING]))
+ }
+
+ def __init__(self):
+ gtk.TextView.__init__(self, RichTextBuffer())
+ self.connect("motion-notify-event", self.__motion_notify_cb)
+ self.connect("button-press-event", self.__button_press_cb)
+ self.__hover_link = False
+
+ def _set_hover_link(self, hover_link):
+ if hover_link != self.__hover_link:
+ self.__hover_link = hover_link
+ display = self.get_toplevel().get_display()
+ child_window = self.get_window(gtk.TEXT_WINDOW_TEXT)
+
+ if hover_link:
+ cursor = gtk.gdk.Cursor(display, gtk.gdk.HAND2)
+ else:
+ cursor = gtk.gdk.Cursor(display, gtk.gdk.XTERM)
+
+ child_window.set_cursor(cursor)
+ gtk.gdk.flush()
+
+ def __iter_is_link(self, it):
+ item = self.get_buffer().get_tag_table().lookup("link")
+ if item:
+ return it.has_tag(item)
+ return False
+
+ def __get_event_iter(self, event):
+ return self.get_iter_at_location(int(event.x), int(event.y))
+
+ def __motion_notify_cb(self, widget, event):
+ if event.is_hint:
+ [x, y, state] = event.window.get_pointer();
+
+ it = self.__get_event_iter(event)
+ if it:
+ hover_link = self.__iter_is_link(it)
+ else:
+ hover_link = False
+
+ self._set_hover_link(hover_link)
+
+ def __button_press_cb(self, widget, event):
+ it = self.__get_event_iter(event)
+ if it and self.__iter_is_link(it):
+ buf = self.get_buffer()
+ address_tag = buf.get_tag_table().lookup("link-address")
+
+ address_end = it.copy()
+ address_end.backward_to_tag_toggle(address_tag)
+
+ address_start = address_end.copy()
+ address_start.backward_to_tag_toggle(address_tag)
+
+ address = buf.get_text(address_start, address_end)
+ self.emit("link-clicked", address)
+
+class RichTextBuffer(gtk.TextBuffer):
+ def __init__(self):
+ gtk.TextBuffer.__init__(self)
+
+ self.connect_after("insert-text", self.__insert_text_cb)
+
+ self.__create_tags()
+ self.active_tags = []
+
+ def append_link(self, title, address):
+ it = self.get_iter_at_mark(self.get_insert())
+ self.insert_with_tags_by_name(it, address, "link", "link-address")
+ self.insert_with_tags_by_name(it, title, "link")
+
+ def apply_tag(self, tag_name):
+ self.active_tags.append(tag_name)
+
+ bounds = self.get_selection_bounds()
+ if bounds:
+ [start, end] = bounds
+ self.apply_tag_by_name(tag_name, start, end)
+
+ def unapply_tag(self, tag_name):
+ self.active_tags.remove(tag_name)
+
+ bounds = self.get_selection_bounds()
+ if bounds:
+ [start, end] = bounds
+ self.remove_tag_by_name(tag_name, start, end)
+
+ def __create_tags(self):
+ tag = self.create_tag("link")
+ tag.set_property("underline", pango.UNDERLINE_SINGLE)
+ tag.set_property("foreground", "#0000FF")
+
+ tag = self.create_tag("link-address")
+ tag.set_property("invisible", True)
+
+ tag = self.create_tag("bold")
+ tag.set_property("weight", pango.WEIGHT_BOLD)
+
+ tag = self.create_tag("italic")
+ tag.set_property("style", pango.STYLE_ITALIC)
+
+ tag = self.create_tag("font-size-xx-small")
+ tag.set_property("scale", pango.SCALE_XX_SMALL)
+
+ tag = self.create_tag("font-size-x-small")
+ tag.set_property("scale", pango.SCALE_X_SMALL)
+
+ tag = self.create_tag("font-size-small")
+ tag.set_property("scale", pango.SCALE_SMALL)
+
+ tag = self.create_tag("font-size-large")
+ tag.set_property("scale", pango.SCALE_LARGE)
+
+ tag = self.create_tag("font-size-x-large")
+ tag.set_property("scale", pango.SCALE_X_LARGE)
+
+ tag = self.create_tag("font-size-xx-large")
+ tag.set_property("scale", pango.SCALE_XX_LARGE)
+
+ def __insert_text_cb(self, widget, pos, text, length):
+ for tag in self.active_tags:
+ pos_end = pos.copy()
+ pos_end.backward_chars(length)
+ self.apply_tag_by_name(tag, pos, pos_end)
+
+class RichTextToolbar(gtk.Toolbar):
+ def __init__(self, buf):
+ gtk.Toolbar.__init__(self)
+
+ self.buf = buf
+
+ self.set_style(gtk.TOOLBAR_ICONS)
+
+ self._font_size = "normal"
+ self._font_scales = [ "xx-small", "x-small", "small", \
+ "normal", \
+ "large", "x-large", "xx-large" ]
+
+ item = gtk.ToggleToolButton(gtk.STOCK_BOLD)
+ item.connect("toggled", self.__toggle_style_cb, "bold")
+ self.insert(item, -1)
+ item.show()
+
+ item = gtk.ToggleToolButton(gtk.STOCK_ITALIC)
+ item.connect("toggled", self.__toggle_style_cb, "italic")
+ self.insert(item, -1)
+ item.show()
+
+ self._font_size_up = gtk.ToolButton(gtk.STOCK_GO_UP)
+ self._font_size_up.connect("clicked", self.__font_size_up_cb)
+ self.insert(self._font_size_up, -1)
+ self._font_size_up.show()
+
+ self._font_size_down = gtk.ToolButton(gtk.STOCK_GO_DOWN)
+ self._font_size_down.connect("clicked", self.__font_size_down_cb)
+ self.insert(self._font_size_down, -1)
+ self._font_size_down.show()
+
+ def _get_font_size_index(self):
+ return self._font_scales.index(self._font_size);
+
+ def __toggle_style_cb(self, toggle, tag_name):
+ if toggle.get_active():
+ self.buf.apply_tag(tag_name)
+ else:
+ self.buf.unapply_tag(tag_name)
+
+ def _set_font_size(self, font_size):
+ if self._font_size != "normal":
+ self.buf.unapply_tag("font-size-" + self._font_size)
+ if font_size != "normal":
+ self.buf.apply_tag("font-size-" + font_size)
+
+ self._font_size = font_size
+
+ can_up = self._get_font_size_index() < len(self._font_scales) - 1
+ can_down = self._get_font_size_index() > 0
+ self._font_size_up.set_sensitive(can_up)
+ self._font_size_down.set_sensitive(can_down)
+
+ def __font_size_up_cb(self, button):
+ index = self._get_font_size_index()
+ if index + 1 < len(self._font_scales):
+ self._set_font_size(self._font_scales[index + 1])
+
+ def __font_size_down_cb(self, button):
+ index = self._get_font_size_index()
+ if index > 0:
+ self._set_font_size(self._font_scales[index - 1])
+
+class RichTextHandler(xml.sax.handler.ContentHandler):
+ def __init__(self, serializer, buf):
+ self.buf = buf
+ self.serializer = serializer
+ self.tags = []
+
+ def startElement(self, name, attrs):
+ if name != "richtext":
+ tag = self.serializer.deserialize_element(name, attrs)
+ self.tags.append(tag)
+ if name == "link":
+ self.href = attrs['href']
+
+ def characters(self, data):
+ start_it = it = self.buf.get_end_iter()
+ mark = self.buf.create_mark(None, start_it, True)
+ self.buf.insert(it, data)
+ start_it = self.buf.get_iter_at_mark(mark)
+
+ for tag in self.tags:
+ self.buf.apply_tag_by_name(tag, start_it, it)
+ if tag == "link":
+ self.buf.insert_with_tags_by_name(start_it, self.href,
+ "link", "link-address")
+
+ def endElement(self, name):
+ if name != "richtext":
+ self.tags.pop()
+
+class RichTextSerializer:
+ def __init__(self):
+ self._open_tags = []
+
+ def deserialize_element(self, el_name, attributes):
+ if el_name == "bold":
+ return "bold"
+ elif el_name == "italic":
+ return "italic"
+ elif el_name == "font":
+ return "font-size-" + attributes["size"]
+ elif el_name == "link":
+ return "link"
+ else:
+ return None
+
+ def serialize_tag_start(self, tag, it):
+ name = tag.get_property("name")
+ if name == "bold":
+ return ""
+ elif name == "italic":
+ return ""
+ elif name == "link":
+ address_tag = self.buf.get_tag_table().lookup("link-address")
+ end = it.copy()
+ end.forward_to_tag_toggle(address_tag)
+ address = self.buf.get_text(it, end)
+ return ""
+ elif name == "link-address":
+ return ""
+ elif name.startswith("font-size-"):
+ tag_name = name.replace("font-size-", "", 1)
+ return ""
+ else:
+ return ""
+
+ def serialize_tag_end(self, tag):
+ name = tag.get_property("name")
+ if name == "bold":
+ return ""
+ elif name == "italic":
+ return ""
+ elif name == "link":
+ return ""
+ elif name == "link-address":
+ return ""
+ elif name.startswith("font-size-"):
+ return ""
+ else:
+ return ""
+
+ def serialize(self, buf):
+ self.buf = buf
+
+ xml = ""
+
+ next_it = buf.get_start_iter()
+ while not next_it.is_end():
+ it = next_it.copy()
+ if not next_it.forward_to_tag_toggle(None):
+ next_it = buf.get_end_iter()
+
+ tags_to_reopen = []
+
+ for tag in it.get_toggled_tags(False):
+ while 1:
+ open_tag = self._open_tags.pop()
+ xml += self.serialize_tag_end(tag)
+ if open_tag == tag:
+ break
+ tags_to_reopen.append(open_tag)
+
+ for tag in tags_to_reopen:
+ self._open_tags.append(tag)
+ xml += self.serialize_tag_start(tag, it)
+
+ for tag in it.get_toggled_tags(True):
+ self._open_tags.append(tag)
+ xml += self.serialize_tag_start(tag, it)
+
+ xml += buf.get_text(it, next_it, False)
+
+ if next_it.is_end():
+ self._open_tags.reverse()
+ for tag in self._open_tags:
+ xml += self.serialize_tag_end(tag)
+
+ xml += ""
+
+ return xml
+
+ def deserialize(self, xml_string, buf):
+ parser = xml.sax.make_parser()
+ handler = RichTextHandler(self, buf)
+ parser.setContentHandler(handler)
+ parser.feed(xml_string)
+ parser.close()
+
+def test_quit(window, rich_buf):
+ print RichTextSerializer().serialize(rich_buf)
+ gtk.main_quit()
+
+def link_clicked(view, address):
+ print "Link clicked " + address
+
+if __name__ == "__main__":
+ window = gtk.Window()
+ window.set_default_size(400, 300)
+
+ vbox = gtk.VBox()
+
+ view = RichTextView()
+ view.connect("link-clicked", link_clicked)
+ vbox.pack_start(view)
+ view.show()
+
+ rich_buf = view.get_buffer()
+
+ xml_string = ""
+
+ xml_string += "Testone\n"
+ xml_string += "Test two"
+ xml_string += "Test three"
+ xml_string += "Test link"
+ xml_string += ""
+
+ RichTextSerializer().deserialize(xml_string, rich_buf)
+
+ toolbar = RichTextToolbar(rich_buf)
+ vbox.pack_start(toolbar, False)
+ toolbar.show()
+
+ window.add(vbox)
+ vbox.show()
+
+ window.show()
+
+ window.connect("destroy", test_quit, rich_buf)
+
+ gtk.main()
diff --git a/sugar/p2p/Buddy.py b/sugar/p2p/Buddy.py
new file mode 100644
index 00000000..19d7c0ef
--- /dev/null
+++ b/sugar/p2p/Buddy.py
@@ -0,0 +1,34 @@
+import pwd
+import os
+
+from Service import *
+
+PRESENCE_SERVICE_TYPE = "_olpc_presence._tcp"
+PRESENCE_SERVICE_PORT = 6000
+
+class Buddy:
+ def __init__(self, service, nick_name):
+ self._service = service
+ self._nick_name = nick_name
+
+ def get_service_name(self):
+ return self._service.get_name()
+
+ def get_nick_name(self):
+ return self._nick_name
+
+class Owner(Buddy):
+ def __init__(self, group):
+ self._group = group
+
+ nick = pwd.getpwuid(os.getuid())[0]
+ if not nick or not len(nick):
+ nick = "n00b"
+
+ service = Service(nick, PRESENCE_SERVICE_TYPE,
+ '', PRESENCE_SERVICE_PORT)
+
+ Buddy.__init__(self, service, nick)
+
+ def register(self):
+ self._service.register(self._group)
diff --git a/sugar/p2p/Group.py b/sugar/p2p/Group.py
new file mode 100644
index 00000000..dedbc1ed
--- /dev/null
+++ b/sugar/p2p/Group.py
@@ -0,0 +1,102 @@
+import avahi
+
+import presence
+from Buddy import *
+from Service import *
+
+SERVICE_ADDED = "service_added"
+SERVICE_REMOVED = "service_removed"
+
+BUDDY_JOIN = "buddy_join"
+BUDDY_LEAVE = "buddy_leave"
+
+class Group:
+ def __init__(self):
+ self._service_listeners = []
+ self._presence_listeners = []
+
+ def join(self, buddy):
+ pass
+
+ def add_service_listener(self, listener):
+ self._service_listeners.append(listener)
+
+ def add_presence_listener(self, listener):
+ self._presence_listeners.append(listener)
+
+ def _notify_service_added(self, service):
+ for listener in self._service_listeners:
+ listener(SERVICE_ADDED, buddy)
+
+ def _notify_service_removed(self, service):
+ for listener in self._service_listeners:
+ listener(SERVICE_REMOVED,buddy)
+
+ def _notify_buddy_join(self, buddy):
+ for listener in self._presence_listeners:
+ listener(BUDDY_JOIN, buddy)
+
+ def _notify_buddy_leave(self, buddy):
+ for listener in self._presence_listeners:
+ listener(BUDDY_LEAVE, buddy)
+
+class LocalGroup(Group):
+ def __init__(self):
+ Group.__init__(self)
+
+ self._services = {}
+ self._buddies = {}
+
+ self._pdiscovery = presence.PresenceDiscovery()
+ self._pdiscovery.add_service_listener(self._on_service_change)
+ self._pdiscovery.start()
+
+ def get_owner(self):
+ return self._owner
+
+ def add_service(self, service):
+ sid = (service.get_name(), service.get_type())
+ self._services[sid] = service
+ self._notify_service_added(service)
+
+ def remove_service(self, sid):
+ self._notify_service_removed(service)
+ del self._services[sid]
+
+ def join(self):
+ self._owner = Owner(self)
+ self._owner.register()
+
+ def get_service(self, name, stype):
+ return self._services[(name, stype)]
+
+ def get_buddy(self, name):
+ return self._buddies[name]
+
+ def _add_buddy(self, buddy):
+ bid = buddy.get_nick_name()
+ if not self._buddies.has_key(bid):
+ self._buddies[bid] = buddy
+ self._notify_buddy_join(buddy)
+
+ def _remove_buddy(self, buddy):
+ self._notify_buddy_leave(buddy)
+ del self._buddies[buddy.get_nick_name()]
+
+ def _on_service_change(self, action, interface, protocol, name, stype, domain, flags):
+ if action == presence.ACTION_SERVICE_NEW:
+ self._pdiscovery.resolve_service(interface, protocol, name, stype, domain,
+ self._on_service_resolved)
+ elif action == presence.ACTION_SERVICE_REMOVED:
+ if stype == PRESENCE_SERVICE_TYPE:
+ self._remove_buddy(name)
+ elif stype.startswith("_olpc"):
+ self.remove_service((name, stype))
+
+ def _on_service_resolved(self, interface, protocol, name, stype, domain,
+ host, aprotocol, address, port, txt, flags):
+ service = Service(name, stype, address, port)
+ if stype == PRESENCE_SERVICE_TYPE:
+ self._add_buddy(Buddy(service, name))
+ elif stype.startswith("_olpc"):
+ self.add_service(service)
diff --git a/sugar/p2p/Makefile.am b/sugar/p2p/Makefile.am
new file mode 100644
index 00000000..be343601
--- /dev/null
+++ b/sugar/p2p/Makefile.am
@@ -0,0 +1,10 @@
+sugardir = $(pythondir)/sugar/p2p
+sugar_PYTHON = \
+ __init__.py \
+ Buddy.py \
+ Group.py \
+ Service.py \
+ StreamReader.py \
+ StreamWriter.py \
+ network.py \
+ presence.py
diff --git a/sugar/p2p/Service.py b/sugar/p2p/Service.py
new file mode 100644
index 00000000..50bbf86c
--- /dev/null
+++ b/sugar/p2p/Service.py
@@ -0,0 +1,31 @@
+import presence
+
+class Service(object):
+ def __init__(self, name, stype, address, port, multicast=False):
+ self._name = name
+ self._stype = stype
+ self._address = str(address)
+ self._port = int(port)
+ self._multicast = multicast
+
+ def get_name(self):
+ return self._name
+
+ def get_type(self):
+ return self._stype
+
+ def get_address(self):
+ return self._address
+
+ def get_port(self):
+ return self._port
+
+ def set_port(self, port):
+ self._port = port
+
+ def is_multicast(self):
+ return self._multicast
+
+ def register(self, group):
+ pannounce = presence.PresenceAnnounce()
+ pannounce.register_service(self._name, self._port, self._stype)
diff --git a/sugar/p2p/StreamReader.py b/sugar/p2p/StreamReader.py
new file mode 100644
index 00000000..c108547b
--- /dev/null
+++ b/sugar/p2p/StreamReader.py
@@ -0,0 +1,51 @@
+import network
+
+class StreamReaderRequestHandler(object):
+ def __init__(self, reader):
+ self._reader = reader
+
+ def message(self, nick_name, message):
+ address = network.get_authinfo()
+ self._reader.recv(nick_name, message)
+ return True
+
+class StreamReader:
+ def __init__(self, group, service):
+ self._group = group
+ self._service = service
+
+ if self._service.is_multicast():
+ self._setup_multicast()
+ else:
+ self._setup_unicast()
+
+ def set_listener(self, callback):
+ self._callback = callback
+
+ def _setup_multicast(self):
+ address = self._service.get_address()
+ port = self._service.get_port()
+ server = network.GroupServer(address, port, self._recv_multicast)
+ server.start()
+
+ def _setup_unicast(self):
+ started = False
+ tries = 10
+ port = self._service.get_port()
+ while not started and tries > 0:
+ try:
+ p2p_server = network.GlibXMLRPCServer(("", port))
+ p2p_server.register_instance(StreamReaderRequestHandler(self))
+ started = True
+ except:
+ port = port + 1
+ tries = tries - 1
+ self._service.set_port(port)
+
+ def _recv_multicast(self, msg):
+ [ nick_name, data ] = msg['data'].split(" |**| ", 2)
+ self.recv(nick_name, data)
+
+ def recv(self, nick_name, data):
+ if nick_name != self._group.get_owner().get_nick_name():
+ self._callback(self._group.get_buddy(nick_name), data)
diff --git a/sugar/p2p/StreamWriter.py b/sugar/p2p/StreamWriter.py
new file mode 100644
index 00000000..f30801ea
--- /dev/null
+++ b/sugar/p2p/StreamWriter.py
@@ -0,0 +1,43 @@
+import xmlrpclib
+import traceback
+import socket
+
+import network
+
+class StreamWriter:
+ def __init__(self, group, service):
+ self._group = group
+ self._service = service
+ self._address = self._service.get_address()
+ self._port = self._service.get_port()
+
+ if self._service.is_multicast():
+ self._setup_multicast()
+ else:
+ self._setup_unicast()
+
+ def write(self, data):
+ if self._service.is_multicast():
+ self._multicast_write(data)
+ else:
+ self._unicast_write(data)
+
+ def _setup_unicast(self):
+ xmlrpc_addr = "http://%s:%d" % (self._address, self._port)
+ self._uclient = xmlrpclib.ServerProxy(xmlrpc_addr)
+
+ def _unicast_write(self, data):
+ try:
+ nick_name = self._group.get_owner().get_nick_name()
+ self._uclient.message(nick_name, data)
+ return True
+ except (socket.error, xmlrpclib.Fault, xmlrpclib.ProtocolError), e:
+ traceback.print_exc()
+ return False
+
+ def _setup_multicast(self):
+ self._mclient = network.GroupClient(self._address, self._port)
+
+ def _multicast_write(self, data):
+ nick_name = self._group.get_owner().get_nick_name()
+ self._mclient.send_msg(nick_name + " |**| " + data)
diff --git a/sugar/p2p/__init__.py b/sugar/p2p/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/sugar/p2p/network.py b/sugar/p2p/network.py
new file mode 100644
index 00000000..c88ede6c
--- /dev/null
+++ b/sugar/p2p/network.py
@@ -0,0 +1,176 @@
+# -*- tab-width: 4; indent-tabs-mode: t -*-
+
+import socket
+import threading
+import traceback
+import select
+import time
+import xmlrpclib
+import sys
+
+import gobject
+import SimpleXMLRPCServer
+import SocketServer
+
+__authinfos = {}
+
+def _add_authinfo(authinfo):
+ __authinfos[threading.currentThread()] = authinfo
+
+def get_authinfo():
+ return __authinfos.get(threading.currentThread())
+
+def _del_authinfo():
+ del __authinfos[threading.currentThread()]
+
+
+class GlibTCPServer(SocketServer.TCPServer):
+ """GlibTCPServer
+
+ Integrate socket accept into glib mainloop.
+ """
+
+ allow_reuse_address = True
+ request_queue_size = 20
+
+ def __init__(self, server_address, RequestHandlerClass):
+ SocketServer.TCPServer.__init__(self, server_address, RequestHandlerClass)
+ self.socket.setblocking(0) # Set nonblocking
+
+ # Watch the listener socket for data
+ gobject.io_add_watch(self.socket, gobject.IO_IN, self._handle_accept)
+
+ def _handle_accept(self, source, condition):
+ if not (condition & gobject.IO_IN):
+ return True
+ self.handle_request()
+ return True
+
+class GlibXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
+ """ GlibXMLRPCRequestHandler
+
+ The stock SimpleXMLRPCRequestHandler and server don't allow any way to pass
+ the client's address and/or SSL certificate into the function that actually
+ _processes_ the request. So we have to store it in a thread-indexed dict.
+ """
+
+ def do_POST(self):
+ _add_authinfo(self.client_address)
+ try:
+ SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.do_POST(self)
+ except socket.timeout:
+ pass
+ except socket.error, e:
+ print "Error (%s): socket error - '%s'" % (self.client_address, e)
+ except:
+ print "Error while processing POST:"
+ traceback.print_exc()
+ _del_authinfo()
+
+class GlibXMLRPCServer(GlibTCPServer, SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
+ """GlibXMLRPCServer
+
+ Use nonblocking sockets and handle the accept via glib rather than
+ blocking on accept().
+ """
+
+ def __init__(self, addr, requestHandler=GlibXMLRPCRequestHandler, logRequests=1):
+ self.logRequests = logRequests
+
+ SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
+ GlibTCPServer.__init__(self, addr, requestHandler)
+
+ def _marshaled_dispatch(self, data, dispatch_method = None):
+ """Dispatches an XML-RPC method from marshalled (XML) data.
+
+ XML-RPC methods are dispatched from the marshalled (XML) data
+ using the _dispatch method and the result is returned as
+ marshalled data. For backwards compatibility, a dispatch
+ function can be provided as an argument (see comment in
+ SimpleXMLRPCRequestHandler.do_POST) but overriding the
+ existing method through subclassing is the prefered means
+ of changing method dispatch behavior.
+ """
+
+ params, method = xmlrpclib.loads(data)
+
+ # generate response
+ try:
+ if dispatch_method is not None:
+ response = dispatch_method(method, params)
+ else:
+ response = self._dispatch(method, params)
+ # wrap response in a singleton tuple
+ response = (response,)
+ response = xmlrpclib.dumps(response, methodresponse=1)
+ except xmlrpclib.Fault, fault:
+ response = xmlrpclib.dumps(fault)
+ except:
+ print "Exception while processing request:"
+ traceback.print_exc()
+
+ # report exception back to server
+ response = xmlrpclib.dumps(
+ xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value))
+ )
+
+ return response
+
+class GroupServer(object):
+
+ _MAX_MSG_SIZE = 500
+
+ def __init__(self, address, port, data_cb):
+ self._address = address
+ self._port = port
+ self._data_cb = data_cb
+
+ self._setup_listener()
+
+ def _setup_listener(self):
+ # Listener socket
+ self._listen_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+
+ # Set some options to make it multicast-friendly
+ self._listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ try:
+ self._listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+ except:
+ pass
+ self._listen_sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_TTL, 20)
+ self._listen_sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_LOOP, 1)
+
+ def start(self):
+ # Set some more multicast options
+ self._listen_sock.bind(('', self._port))
+ self._listen_sock.settimeout(2)
+ intf = socket.gethostbyname(socket.gethostname())
+ self._listen_sock.setsockopt(socket.SOL_IP, socket.IP_MULTICAST_IF, socket.inet_aton(intf) + socket.inet_aton('0.0.0.0'))
+ self._listen_sock.setsockopt(socket.SOL_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(self._address) + socket.inet_aton('0.0.0.0'))
+
+ # Watch the listener socket for data
+ gobject.io_add_watch(self._listen_sock, gobject.IO_IN, self._handle_incoming_data)
+
+ def _handle_incoming_data(self, source, condition):
+ if not (condition & gobject.IO_IN):
+ return True
+ msg = {}
+ msg['data'], (msg['addr'], msg['port']) = source.recvfrom(self._MAX_MSG_SIZE)
+ if self._data_cb:
+ self._data_cb(msg)
+ return True
+
+class GroupClient(object):
+
+ _MAX_MSG_SIZE = 500
+
+ def __init__(self, address, port):
+ self._address = address
+ self._port = port
+
+ self._send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ # Make the socket multicast-aware, and set TTL.
+ self._send_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20) # Change TTL (=20) to suit
+
+ def send_msg(self, data):
+ self._send_sock.sendto(data, (self._address, self._port))
diff --git a/sugar/p2p/presence.py b/sugar/p2p/presence.py
new file mode 100644
index 00000000..e16fc928
--- /dev/null
+++ b/sugar/p2p/presence.py
@@ -0,0 +1,92 @@
+# -*- tab-width: 4; indent-tabs-mode: t -*-
+
+import avahi, dbus, dbus.glib
+
+ACTION_SERVICE_NEW = 'new'
+ACTION_SERVICE_REMOVED = 'removed'
+
+class PresenceDiscovery(object):
+ def __init__(self):
+ self.bus = dbus.SystemBus()
+ self.server = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
+ self._service_browsers = {}
+ self._service_type_browsers = {}
+ self._service_listeners = []
+
+ def add_service_listener(self, listener):
+ self._service_listeners.append(listener)
+
+ def start(self):
+ # Always browse .local
+ self.browse_domain(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, "local")
+ db = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, self.server.DomainBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, "", avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0))), avahi.DBUS_INTERFACE_DOMAIN_BROWSER)
+ db.connect_to_signal('ItemNew', self.new_domain)
+
+ def _error_handler(self, err):
+ print "Error resolving: %s" % err
+
+ def resolve_service(self, interface, protocol, name, stype, domain, reply_handler, error_handler=None):
+ if not error_handler:
+ error_handler = self._error_handler
+ self.server.ResolveService(int(interface), int(protocol), name, stype, domain, avahi.PROTO_UNSPEC, dbus.UInt32(0), reply_handler=reply_handler, error_handler=error_handler)
+
+ def new_service(self, interface, protocol, name, stype, domain, flags):
+# print "Found service '%s' (%d) of type '%s' in domain '%s' on %i.%i." % (name, flags, stype, domain, interface, protocol)
+ for listener in self._service_listeners:
+ listener(ACTION_SERVICE_NEW, interface, protocol, name, stype, domain, flags)
+
+ def remove_service(self, interface, protocol, name, stype, domain, flags):
+# print "Service '%s' of type '%s' in domain '%s' on %i.%i disappeared." % (name, stype, domain, interface, protocol)
+ for listener in self._service_listeners:
+ listener(ACTION_SERVICE_REMOVED, interface, protocol, name, stype, domain, flags)
+
+ def new_service_type(self, interface, protocol, stype, domain, flags):
+ # Are we already browsing this domain for this type?
+ if self._service_browsers.has_key((interface, protocol, stype, domain)):
+ return
+
+# print "Browsing for services of type '%s' in domain '%s' on %i.%i ..." % (stype, domain, interface, protocol)
+
+ b = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, self.server.ServiceBrowserNew(interface, protocol, stype, domain, dbus.UInt32(0))), avahi.DBUS_INTERFACE_SERVICE_BROWSER)
+ b.connect_to_signal('ItemNew', self.new_service)
+ b.connect_to_signal('ItemRemove', self.remove_service)
+
+ self._service_browsers[(interface, protocol, stype, domain)] = b
+
+ def browse_domain(self, interface, protocol, domain):
+ # Are we already browsing this domain?
+ if self._service_type_browsers.has_key((interface, protocol, domain)):
+ return
+
+# print "Browsing domain '%s' on %i.%i ..." % (domain, interface, protocol)
+
+ b = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, self.server.ServiceTypeBrowserNew(interface, protocol, domain, dbus.UInt32(0))), avahi.DBUS_INTERFACE_SERVICE_TYPE_BROWSER)
+ b.connect_to_signal('ItemNew', self.new_service_type)
+
+ self._service_type_browsers[(interface, protocol, domain)] = b
+
+ def new_domain(self,interface, protocol, domain, flags):
+ if domain != "local":
+ return
+ self.browse_domain(interface, protocol, domain)
+
+
+class PresenceAnnounce(object):
+ def __init__(self):
+ self.bus = dbus.SystemBus()
+ self.server = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
+ self._hostname = None
+
+ def register_service(self, rs_name, rs_port, rs_service, **kwargs):
+ g = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, self.server.EntryGroupNew()), avahi.DBUS_INTERFACE_ENTRY_GROUP)
+ if rs_name is None:
+ if self._hostname is None:
+ self._hostname = "%s:%s" % (self.server.GetHostName(), rs_port)
+ rs_name = self._hostname
+
+ info = ["%s=%s" % (k,v) for k,v in kwargs.items()]
+ g.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, 0, rs_name, rs_service,
+ "", "", # domain, host (let the system figure it out)
+ dbus.UInt16(rs_port), info,)
+ g.Commit()
+ return g
diff --git a/sugar/shell/Makefile.am b/sugar/shell/Makefile.am
new file mode 100644
index 00000000..f7652957
--- /dev/null
+++ b/sugar/shell/Makefile.am
@@ -0,0 +1,21 @@
+sugardir = $(pythondir)/sugar/shell
+sugar_PYTHON = \
+ __init__.py \
+ activity.py \
+ shell.py
+
+# Dbus service file
+servicedir = $(datadir)/dbus-1/services
+service_in_files = com.redhat.Sugar.Shell.service.in
+service_DATA = $(service_in_files:.service.in=.service)
+
+# Rule to make the service file with bindir expanded
+$(service_DATA): $(service_in_files) Makefile
+ @sed -e "s|\@bindir\@|$(bindir)|" $< > $@
+
+EXTRA_DIST = \
+ $(service_in_files) \
+ $(service_DATA)
+
+DISTCLEANFILES = \
+ $(service_DATA)
diff --git a/sugar/shell/__init__.py b/sugar/shell/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/sugar/shell/activity.py b/sugar/shell/activity.py
new file mode 100644
index 00000000..6f1f3fc1
--- /dev/null
+++ b/sugar/shell/activity.py
@@ -0,0 +1,178 @@
+# -*- tab-width: 4; indent-tabs-mode: t -*-
+
+import string
+
+import gc
+import dbus
+import dbus.service
+import dbus.glib
+import gobject
+import pygtk
+pygtk.require('2.0')
+import gtk,sys
+
+
+class Activity(dbus.service.Object):
+ """ Base Sugar activity object from which all other Activities should inherit """
+
+ def __init__(self):
+ pass
+
+ def name_owner_changed(self, service_name, old_service_name, new_service_name):
+ #print "in name_owner_changed: svc=%s oldsvc=%s newsvc=%s"%(service_name, old_service_name, new_service_name)
+ if service_name == "com.redhat.Sugar.Shell" and new_service_name == "":
+ self.activity_on_disconnected_from_shell()
+ #elif service_name == "com.redhat.Sugar.Shell" and old_service_name == "":
+ # self.activity_on_shell_reappeared()
+
+ def activity_connect_to_shell(self):
+ self.__bus = dbus.SessionBus()
+
+ self.__bus.add_signal_receiver(self.name_owner_changed, dbus_interface = "org.freedesktop.DBus", signal_name = "NameOwnerChanged")
+
+ self.__activity_container_object = self.__bus.get_object("com.redhat.Sugar.Shell", \
+ "/com/redhat/Sugar/Shell/ActivityContainer")
+ self.__activity_container = dbus.Interface(self.__activity_container_object, \
+ "com.redhat.Sugar.Shell.ActivityContainer")
+
+ self.__activity_id = self.__activity_container.add_activity("")
+ self.__object_path = "/com/redhat/Sugar/Shell/Activities/%d"%self.__activity_id
+
+ print "object_path = %s"%self.__object_path
+
+ self.__activity_object = dbus.Interface(self.__bus.get_object("com.redhat.Sugar.Shell", self.__object_path), \
+ "com.redhat.Sugar.Shell.ActivityHost")
+ self.__window_id = self.__activity_object.get_host_xembed_id()
+
+ print "XEMBED window_id = %d"%self.__window_id
+
+ self.__plug = gtk.Plug(self.__window_id)
+
+ # Now let the Activity register a peer service so the Shell can poke it
+ self.__peer_service_name = "com.redhat.Sugar.Activity%d"%self.__activity_id
+ self.__peer_object_name = "/com/redhat/Sugar/Activity/%d"%self.__activity_id
+ self.__service = dbus.service.BusName(self.__peer_service_name, bus=self.__bus)
+ dbus.service.Object.__init__(self, self.__service, self.__peer_object_name)
+
+ self.__activity_object.set_peer_service_name(self.__peer_service_name, self.__peer_object_name)
+
+ self.activity_on_connected_to_shell()
+
+ def activity_get_gtk_plug(self):
+ return self.__plug
+
+ def activity_set_ellipsize_tab(self, ellipsize):
+ self.__activity_object.set_ellipsize_tab(ellipsize)
+
+ @dbus.service.method("com.redhat.Sugar.Activity", \
+ in_signature="", \
+ out_signature="")
+
+ def activity_set_can_close(self, can_close):
+ self.__activity_object.set_can_close(can_close)
+
+ @dbus.service.method("com.redhat.Sugar.Activity", \
+ in_signature="", \
+ out_signature="")
+
+ def activity_show_icon(self, show_icon):
+ self.__activity_object.set_tab_show_icon(show_icon)
+
+ @dbus.service.method("com.redhat.Sugar.Activity", \
+ in_signature="", \
+ out_signature="")
+
+ def activity_set_icon(self, pixbuf):
+ pixarray = []
+ pixstr = pixbuf.get_pixels();
+ for c in pixstr:
+ pixarray.append(c)
+ self.__activity_object.set_tab_icon(pixarray, \
+ pixbuf.get_colorspace(), \
+ pixbuf.get_has_alpha(), \
+ pixbuf.get_bits_per_sample(), \
+ pixbuf.get_width(), \
+ pixbuf.get_height(), \
+ pixbuf.get_rowstride())
+
+ @dbus.service.method("com.redhat.Sugar.Activity", \
+ in_signature="", \
+ out_signature="")
+
+ def activity_set_tab_text(self, text):
+ self.__activity_object.set_tab_text(text)
+
+ @dbus.service.method("com.redhat.Sugar.Activity", \
+ in_signature="", \
+ out_signature="")
+
+ def activity_set_tab_icon_name(self, icon_name):
+ icon_theme = gtk.icon_theme_get_default()
+ icon_info = icon_theme.lookup_icon(icon_name, gtk.ICON_SIZE_MENU, 0)
+ pixbuf = icon_info.load_icon()
+ scaled_pixbuf = pixbuf.scale_simple(16, 16, gtk.gdk.INTERP_BILINEAR)
+ self.activity_set_icon(scaled_pixbuf)
+
+ def lost_focus(self):
+ self.activity_on_lost_focus()
+
+ @dbus.service.method("com.redhat.Sugar.Activity", \
+ in_signature="", \
+ out_signature="")
+ def got_focus(self):
+ self.activity_on_got_focus()
+
+
+ @dbus.service.method("com.redhat.Sugar.Activity", \
+ in_signature="", \
+ out_signature="")
+ def close_from_user(self):
+ self.activity_on_close_from_user()
+
+ def activity_get_id(self):
+ return self.__activity_id
+
+
+ def __shutdown_reply_cb(self):
+ print "in __reply_cb"
+
+ self.__plug.destroy()
+ self.__plug = None
+
+ self.__bus = None
+ self.__activity_container_object = None
+ self.__activity_container = None
+ self.__activity_object = None
+ self.__service = None
+
+ self.__bus.remove_signal_receiver(self.name_owner_changed, dbus_interface = "org.freedesktop.DBus", signal_name = "NameOwnerChanged")
+
+ self.activity_on_disconnected_from_shell()
+
+
+ del self
+
+
+
+ def __shutdown_error_cb(self, error):
+ print "in __error_cb"
+
+ def activity_shutdown(self):
+ self.__activity_object.shutdown(reply_handler = self.__shutdown_reply_cb, error_handler = self.__shutdown_error_cb)
+
+ # pure virtual methods
+
+ def activity_on_connected_to_shell(self):
+ print "act %d: you need to override activity_on_connected_to_shell"%self.activity_get_id()
+
+ def activity_on_disconnected_from_shell(self):
+ print "act %d: you need to override activity_on_disconnected_from_shell"%self.activity_get_id()
+
+ def activity_on_close_from_user(self):
+ print "act %d: you need to override activity_on_close_from_user"%self.activity_get_id()
+
+ def activity_on_lost_focus(self):
+ print "act %d: you need to override activity_on_lost_focus"%self.activity_get_id()
+
+ def activity_on_got_focus(self):
+ print "act %d: you need to override activity_on_got_focus"%self.activity_get_id()
diff --git a/sugar/shell/com.redhat.Sugar.Shell.service.in b/sugar/shell/com.redhat.Sugar.Shell.service.in
new file mode 100644
index 00000000..2a069a17
--- /dev/null
+++ b/sugar/shell/com.redhat.Sugar.Shell.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=com.redhat.Sugar.Shell
+Exec=@bindir@/sugar shell
diff --git a/sugar/shell/example-activity/example-activity.py b/sugar/shell/example-activity/example-activity.py
new file mode 100755
index 00000000..05ce55af
--- /dev/null
+++ b/sugar/shell/example-activity/example-activity.py
@@ -0,0 +1,82 @@
+#!/usr/bin/python
+# -*- tab-width: 4; indent-tabs-mode: t -*-
+
+import string
+
+import gc
+import dbus
+import dbus.service
+import dbus.glib
+import gobject
+import pygtk
+pygtk.require('2.0')
+import gtk,sys
+
+import activity
+
+def my_exit():
+ sys.exit(0)
+
+def deferred_exit():
+ gobject.timeout_add(0, my_exit)
+
+################################################################################
+
+class ExampleActivity(activity.Activity):
+
+ def __init__(self, name):
+ self.name = name
+
+ def entry_changed(self, entry):
+ self.activity_set_tab_text(entry.get_text())
+
+ def activity_on_connected_to_shell(self):
+ print "act %d: in activity_on_connected_to_shell"%self.activity_get_id()
+
+ self.activity_set_tab_text(self.name)
+
+ plug = self.activity_get_gtk_plug()
+ self.entry = gtk.Entry()
+ self.entry.set_text(self.name)
+ self.entry.connect("changed", self.entry_changed)
+ plug.add(self.entry)
+ plug.show_all()
+
+ icon_theme = gtk.icon_theme_get_default()
+ pixbuf = icon_theme.load_icon("gnome-dev-cdrom", gtk.ICON_SIZE_MENU, gtk.ICON_LOOKUP_USE_BUILTIN)
+ self.activity_set_icon(pixbuf)
+ self.activity_show_icon(True)
+
+ def activity_on_disconnected_from_shell(self):
+ print "act %d: in activity_on_disconnected_from_shell"%self.activity_get_id()
+ print "act %d: Shell disappeared..."%self.activity_get_id()
+
+ gc.collect()
+
+ def activity_on_close_from_user(self):
+ print "act %d: in activity_on_close_from_user"%self.activity_get_id()
+ self.activity_shutdown()
+
+ def activity_on_lost_focus(self):
+ print "act %d: in activity_on_lost_focus"%self.activity_get_id()
+
+ def activity_on_got_focus(self):
+ print "act %d: in activity_on_got_focus"%self.activity_get_id()
+
+ def __del__(self):
+ print "in __del__ for ExampleActivity"
+
+
+if len(sys.argv) != 2:
+ print "usage: example-activity.py "
+ sys.exit(1)
+
+gc.set_debug(gc.DEBUG_LEAK)
+
+example_activity = ExampleActivity(sys.argv[1])
+example_activity.activity_connect_to_shell()
+example_activity = None
+
+gtk.main()
+
+
diff --git a/sugar/shell/shell.py b/sugar/shell/shell.py
new file mode 100755
index 00000000..d5423079
--- /dev/null
+++ b/sugar/shell/shell.py
@@ -0,0 +1,305 @@
+#!/usr/bin/python
+# -*- tab-width: 4; indent-tabs-mode: t -*-
+
+import string
+
+import dbus
+import dbus.service
+import dbus.glib
+import gobject
+import pygtk
+pygtk.require('2.0')
+import gtk
+import pango
+
+activity_counter = 0
+
+class ActivityHost(dbus.service.Object):
+
+ def __init__(self, activity_container, activity_name):
+ global activity_counter
+
+ self.activity_name = activity_name
+ self.ellipsize_tab = False
+
+ self.activity_container = activity_container
+
+ self.activity_id = activity_counter
+ activity_counter += 1
+
+ self.dbus_object_name = "/com/redhat/Sugar/Shell/Activities/%d"%self.activity_id
+ #print "object name = %s"%self.dbus_object_name
+
+ dbus.service.Object.__init__(self, activity_container.service, self.dbus_object_name)
+ self.socket = gtk.Socket()
+ self.socket.set_data("sugar-activity", self)
+ self.socket.show()
+
+ hbox = gtk.HBox(False, 4);
+
+ self.tab_activity_image = gtk.Image()
+ self.tab_activity_image.set_from_stock(gtk.STOCK_CONVERT, gtk.ICON_SIZE_MENU)
+ hbox.pack_start(self.tab_activity_image)
+ #self.tab_activity_image.show()
+
+ self.label_hbox = gtk.HBox(False, 4);
+ self.label_hbox.connect("style-set", self.__tab_label_style_set_cb)
+ hbox.pack_start(self.label_hbox)
+
+ self.tab_label = gtk.Label(self.activity_name)
+ self.tab_label.set_single_line_mode(True)
+ self.tab_label.set_alignment(0.0, 0.5)
+ self.tab_label.set_padding(0, 0)
+ self.tab_label.show()
+
+ close_image = gtk.Image()
+ close_image.set_from_stock (gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
+ close_image.show()
+
+ self.tab_close_button = gtk.Button()
+ rcstyle = gtk.RcStyle();
+ rcstyle.xthickness = rcstyle.ythickness = 0;
+ self.tab_close_button.modify_style (rcstyle);
+ self.tab_close_button.add(close_image)
+ self.tab_close_button.set_relief(gtk.RELIEF_NONE)
+ self.tab_close_button.set_focus_on_click(False)
+ self.tab_close_button.connect("clicked", self.tab_close_button_clicked)
+
+ self.label_hbox.pack_start(self.tab_label)
+ self.label_hbox.pack_start(self.tab_close_button, False, False, 0)
+ self.label_hbox.show()
+
+ hbox.show()
+
+ notebook = self.activity_container.notebook
+ index = notebook.append_page(self.socket, hbox)
+ notebook.set_current_page(index)
+
+ def __close_button_clicked_reply_cb(self):
+ pass
+
+ def __close_button_clicked_error_cb(self, error):
+ pass
+
+ def tab_close_button_clicked(self, button):
+ self.peer_service.close_from_user(reply_handler = self.__close_button_clicked_reply_cb, \
+ error_handler = self.__close_button_clicked_error_cb)
+
+ @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
+ in_signature="", \
+ out_signature="t")
+ def get_host_xembed_id(self):
+ window_id = self.socket.get_id()
+ #print "window_id = %d"%window_id
+ return window_id
+
+ @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
+ in_signature="ss", \
+ out_signature="")
+ def set_peer_service_name(self, peer_service_name, peer_object_name):
+ #print "peer_service_name = %s, peer_object_name = %s"%(peer_service_name, peer_object_name)
+ self.__peer_service_name = peer_service_name
+ self.__peer_object_name = peer_object_name
+ self.peer_service = dbus.Interface(self.activity_container.bus.get_object( \
+ self.__peer_service_name, self.__peer_object_name), \
+ "com.redhat.Sugar.Activity")
+
+ @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
+ in_signature="b", \
+ out_signature="")
+ def set_ellipsize_tab(self, ellipsize):
+ self.ellipsize_tab = True
+ self.update_tab_size()
+
+ @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
+ in_signature="b", \
+ out_signature="")
+ def set_can_close(self, can_close):
+ if can_close:
+ self.tab_close_button.show()
+ else:
+ self.tab_close_button.hide()
+
+ @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
+ in_signature="b", \
+ out_signature="")
+ def set_tab_show_icon(self, show_icon):
+ if show_icon:
+ self.tab_activity_image.show()
+ else:
+ self.tab_activity_image.hide()
+
+ @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
+ in_signature="s", \
+ out_signature="")
+ def set_tab_text(self, text):
+ self.tab_label.set_text(text)
+
+ @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
+ in_signature="ayibiiii", \
+ out_signature="")
+ def set_tab_icon(self, data, colorspace, has_alpha, bits_per_sample, width, height, rowstride):
+ #print "width=%d, height=%d"%(width, height)
+ #print " data = ", data
+ pixstr = ""
+ for c in data:
+ pixstr += chr(c)
+
+ pixbuf = gtk.gdk.pixbuf_new_from_data(pixstr, colorspace, has_alpha, bits_per_sample, width, height, rowstride)
+ #print pixbuf
+ self.tab_activity_image.set_from_pixbuf(pixbuf)
+
+ @dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
+ in_signature="", \
+ out_signature="")
+ def shutdown(self):
+ #print "shutdown"
+ for owner, activity in self.activity_container.activities[:]:
+ if activity == self:
+ self.activity_container.activities.remove((owner, activity))
+
+ for i in range(self.activity_container.notebook.get_n_pages()):
+ child = self.activity_container.notebook.get_nth_page(i)
+ if child == self.socket:
+ #print "found child"
+ self.activity_container.notebook.remove_page(i)
+ break
+
+ del self
+
+ def get_host_activity_id(self):
+ return self.activity_id
+
+ def get_object_path(self):
+ return self.dbus_object_name
+
+ def update_tab_size(self):
+ if self.ellipsize_tab:
+ self.tab_label.set_ellipsize(pango.ELLIPSIZE_END)
+
+ context = self.label_hbox.get_pango_context()
+ font_desc = self.label_hbox.style.font_desc
+ metrics = context.get_metrics(font_desc, context.get_language())
+ char_width = metrics.get_approximate_digit_width()
+ [w, h] = self.__get_close_icon_size()
+ tab_width = 15 * pango.PIXELS(char_width) + 2 * w
+ self.label_hbox.set_size_request(tab_width, -1);
+ else:
+ self.tab_label.set_ellipsize(pango.ELLIPSIZE_NONE)
+ self.label_hbox.set_size_request(-1, -1)
+
+ def __get_close_icon_size(self):
+ settings = self.label_hbox.get_settings()
+ return gtk.icon_size_lookup_for_settings(settings, gtk.ICON_SIZE_MENU)
+
+ def __tab_label_style_set_cb(self, widget, previous_style):
+ [w, h] = self.__get_close_icon_size()
+ self.tab_close_button.set_size_request (w + 5, h + 2)
+ self.update_tab_size()
+
+class ActivityContainer(dbus.service.Object):
+
+ def __init__(self, service, bus):
+
+ self.activities = []
+
+ self.bus = bus
+ self.service = service
+
+ dbus.service.Object.__init__(self, self.service, "/com/redhat/Sugar/Shell/ActivityContainer")
+ bus.add_signal_receiver(self.name_owner_changed, dbus_interface = "org.freedesktop.DBus", signal_name = "NameOwnerChanged")
+
+ self.window = gtk.Window()
+ self.window.set_title("OLPC Sugar")
+ self.window.resize(640, 480)
+ self.window.set_geometry_hints(min_width = 640, max_width = 640, min_height = 480, max_height = 480)
+ self.notebook = gtk.Notebook()
+
+ #tab_label = gtk.Label("My Laptop")
+ #empty_label = gtk.Label("This activity could launch other activities / be a help page")
+ #empty_label.show()
+ #self.notebook.append_page(empty_label, tab_label)
+
+ self.notebook.show()
+ self.notebook.connect("switch-page", self.notebook_tab_changed)
+ self.window.add(self.notebook)
+
+ self.window.connect("destroy", lambda w: gtk.main_quit())
+ self.window.show()
+
+ self.current_activity = None
+
+
+ def __focus_reply_cb(self):
+ pass
+
+ def __focus_error_cb(self, error):
+ pass
+
+
+ def notebook_tab_changed(self, notebook, page, page_number):
+ #print "in notebook_tab_changed"
+ #print notebook.get_nth_page(page_number)
+ new_activity = notebook.get_nth_page(page_number).get_data("sugar-activity")
+ #print " Current activity: ", self.current_activity
+ #print " New activity: ", new_activity
+
+ if self.current_activity != None:
+ if self.has_activity(self.current_activity):
+ self.current_activity.peer_service.lost_focus(reply_handler = self.__focus_reply_cb, error_handler = self.__focus_error_cb)
+
+ self.current_activity = new_activity
+
+ if self.current_activity != None:
+ if self.has_activity(self.current_activity):
+ self.current_activity.peer_service.got_focus(reply_handler = self.__focus_reply_cb, error_handler = self.__focus_error_cb)
+
+
+ def has_activity(self, activity_to_check_for):
+ for owner, activity in self.activities[:]:
+ if activity_to_check_for == activity:
+ return True
+ return False
+
+
+ def name_owner_changed(self, service_name, old_service_name, new_service_name):
+ #print "in name_owner_changed: svc=%s oldsvc=%s newsvc=%s"%(service_name, old_service_name, new_service_name)
+ for owner, activity in self.activities[:]:
+ if owner == old_service_name:
+ self.activities.remove((owner, activity))
+ #self.__print_activities()
+
+
+ @dbus.service.method("com.redhat.Sugar.Shell.ActivityContainer", \
+ in_signature="s", \
+ out_signature="i", \
+ sender_keyword="sender")
+ def add_activity(self, activity_name, sender):
+ #print "hello world, activity_name = '%s', sender = '%s'"%(activity_name, sender)
+ activity = ActivityHost(self, activity_name)
+ self.activities.append((sender, activity))
+
+ #self.__print_activities()
+ return activity.get_host_activity_id()
+
+ def __print_activities(self):
+ print "__print_activities: %d activities registered"%len(self.activities)
+ i = 0
+ for owner, activity in self.activities:
+ print " %d: owner=%s activity_object_name=%s"%(i, owner, activity.dbus_object_name)
+ i += 1
+
+
+def main():
+ session_bus = dbus.SessionBus()
+ service = dbus.service.BusName("com.redhat.Sugar.Shell", bus=session_bus)
+
+ activityContainer = ActivityContainer(service, session_bus)
+
+ try:
+ gtk.main()
+ except KeyboardInterrupt:
+ pass
+
+if __name__=="__main__":
+ main()
diff --git a/sugar/sugar b/sugar/sugar
new file mode 100755
index 00000000..960971c4
--- /dev/null
+++ b/sugar/sugar
@@ -0,0 +1,28 @@
+#!/usr/bin/python
+
+import sys
+import os
+
+if len(sys.argv) == 1:
+ # FIXME Start a session
+
+ # We are lucky and this
+ # currently behave as we want.
+ # The chat depends on the
+ # web browser, so both activities
+ # are spanned. But obviously we
+ # need something better.
+
+ import sugar.chat
+ sugar.chat.main()
+elif sys.argv[1] == 'shell':
+ import sugar.shell
+ sugar.shell.main()
+elif sys.argv[1] == 'chat':
+ import sugar.chat
+ sugar.chat.main()
+elif sys.argv[1] == 'browser':
+ import sugar.browser
+ sugar.browser.main()
+else:
+ print "Unknown activity"