Add Eclipse project settings and make indent-level consistent
authorAndrew Flegg <andrew@bleb.org>
Sun, 10 Jan 2010 14:59:33 +0000 (14:59 +0000)
committerAndrew Flegg <andrew@bleb.org>
Sun, 10 Jan 2010 14:59:33 +0000 (14:59 +0000)
package/.project [new file with mode: 0644]
package/.pydevproject [new file with mode: 0644]
package/src/console.py
package/src/contacts.py
package/src/contactview.py
package/src/gui.py
package/src/hermes.py
package/src/mapcontact.py
package/src/names.py
package/src/pygobject.py
package/src/wimpworks.py

diff --git a/package/.project b/package/.project
new file mode 100644 (file)
index 0000000..4c41616
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>Hermes</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>org.python.pydev.PyDevBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>org.python.pydev.pythonNature</nature>
+       </natures>
+</projectDescription>
diff --git a/package/.pydevproject b/package/.pydevproject
new file mode 100644 (file)
index 0000000..83fd23c
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?eclipse-pydev version="1.0"?>
+
+<pydev_project>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.5</pydev_property>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Scratchbox Python</pydev_property>
+<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
+<path>/Hermes/src</path>
+</pydev_pathproperty>
+</pydev_project>
index 5f8cd84..8b4f887 100644 (file)
@@ -1,20 +1,20 @@
 class ConsoleUICallback:
-  """Meets the fb2contacts' authentication callback contract using
-     the console.
+    """Meets the fb2contacts' authentication callback contract using
+       the console.
+    
+       Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
+       Released under the Artistic Licence."""
 
-     Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
-     Released under the Artistic Licence."""
+    # -----------------------------------------------------------------------
+    def need_auth(self):
+        print 'Need authentication...'
 
-  # -----------------------------------------------------------------------
-  def need_auth(self):
-    print 'Need authentication...'
-
-  # -----------------------------------------------------------------------
-  def block_for_auth(self):
-    print 'Press enter when logged in...'
-    raw_input()
-
-  # -----------------------------------------------------------------------
-  def progress(self, current, maximum):
-    print current, maximum
+    # -----------------------------------------------------------------------
+    def block_for_auth(self):
+        print 'Press enter when logged in...'
+        raw_input()
+    
+    # -----------------------------------------------------------------------
+    def progress(self, current, maximum):
+        print current, maximum
 
index 56ebf6b..9fa159f 100644 (file)
@@ -17,130 +17,130 @@ E_CONTACT_BIRTHDAY_DATE = 107
 
 
 class ContactStore:
-  """Provide an API for changing contact data. Abstracts limitations
-     in the evolution-python bindings.
+    """Provide an API for changing contact data. Abstracts limitations
+       in the evolution-python bindings.
 
-     Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
-     Released under the Artistic Licence."""
+       Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
+       Released under the Artistic Licence."""
 
 
-  # -----------------------------------------------------------------------
-  def __init__(self, book):
-    """Create a new contact store for modifying contacts in the given
-       EBook."""
-
-    self.book = book
-
-  # -----------------------------------------------------------------------
-  def close(self):
-    """Close the store and tidy-up any resources."""
-
-    pass
-
-
-  # -----------------------------------------------------------------------
-  def set_photo(self, contact, url):
-    """Set the given contact's photo to the picture found at the URL. If the
-       photo is wider than it is tall, it will be cropped with a bias towards
-       the top of the photo."""
-
-    f = urllib.urlopen(url)
-    data = ''
-    while True:
-      read_data = f.read()
-      data += read_data
-      if not read_data:
-        break
-
-    im = Image.open(StringIO.StringIO(data))
-    (w, h) = im.size
-    if (h > w):
-      print "Shrinking photo for %s as it's %d x %d" % (contact.get_name(), w, h)
-      im = ImageOps.fit(im, (w, w), Image.NEAREST, 0, (0, 0.1))
-      
-    print "Updating photo for %s" % (contact.get_name())
-    f = StringIO.StringIO()
-    im.save(f, "JPEG")
-    image_data = f.getvalue()
-    photo = EContactPhoto()
-    photo.type = 0
-    photo.data = EContactPhoto_data()
-    photo.data.inlined = EContactPhoto_inlined()
-    photo.data.inlined.mime_type = cast(create_string_buffer("image/jpeg"), c_char_p)
-    photo.data.inlined.length = len(image_data)
-    photo.data.inlined.data = cast(create_string_buffer(image_data), c_void_p)
-    ebook.e_contact_set(hash(contact), E_CONTACT_PHOTO, addressof(photo))
-    return True
+    # -----------------------------------------------------------------------
+    def __init__(self, book):
+        """Create a new contact store for modifying contacts in the given
+           EBook."""
+        
+        self.book = book
     
     
-  # -----------------------------------------------------------------------
-  def set_birthday(self, contact, day, month, year = 0):
-    if year == 0:
-      year = datetime.date.today().year
-      
-    birthday = EContactDate()
-    birthday.year = year
-    birthday.month = month
-    birthday.day = day
-    print "Setting birthday for [%s] to %d-%d-%d" % (contact.get_name(), year, month, day)
-    ebook.e_contact_set(hash(contact), E_CONTACT_BIRTHDAY_DATE, addressof(birthday))
-    return True
+    # -----------------------------------------------------------------------
+    def close(self):
+        """Close the store and tidy-up any resources."""
+        
+        pass
     
     
-  # -----------------------------------------------------------------------
-  def get_urls(self, contact):
-    """Return a list of URLs which are associated with this contact."""
-
-    urls = []
-    ai = GList.new(ebook.e_contact_get_attributes(hash(contact), E_CONTACT_HOMEPAGE_URL))
-    while ai.has_next():
-      attr = ai.next(as_a = EVCardAttribute)
-      if not attr:
-        raise Exception("Unexpected null attribute for [" + contact.get_name() + "] with URLs " + urls)
-      urls.append(string_at(attr.value().next()))
+    # -----------------------------------------------------------------------
+    def set_photo(self, contact, url):
+        """Set the given contact's photo to the picture found at the URL. If the
+           photo is wider than it is tall, it will be cropped with a bias towards
+           the top of the photo."""
+        
+        f = urllib.urlopen(url)
+        data = ''
+        while True:
+            read_data = f.read()
+            data += read_data
+            if not read_data:
+                break
+        
+        im = Image.open(StringIO.StringIO(data))
+        (w, h) = im.size
+        if (h > w):
+            print "Shrinking photo for %s as it's %d x %d" % (contact.get_name(), w, h)
+            im = ImageOps.fit(im, (w, w), Image.NEAREST, 0, (0, 0.1))
+          
+        print "Updating photo for %s" % (contact.get_name())
+        f = StringIO.StringIO()
+        im.save(f, "JPEG")
+        image_data = f.getvalue()
+        photo = EContactPhoto()
+        photo.type = 0
+        photo.data = EContactPhoto_data()
+        photo.data.inlined = EContactPhoto_inlined()
+        photo.data.inlined.mime_type = cast(create_string_buffer("image/jpeg"), c_char_p)
+        photo.data.inlined.length = len(image_data)
+        photo.data.inlined.data = cast(create_string_buffer(image_data), c_void_p)
+        ebook.e_contact_set(hash(contact), E_CONTACT_PHOTO, addressof(photo))
+        return True
       
-    return urls
-
+      
+    # -----------------------------------------------------------------------
+    def set_birthday(self, contact, day, month, year = 0):
+        if year == 0:
+            year = datetime.date.today().year
+          
+        birthday = EContactDate()
+        birthday.year = year
+        birthday.month = month
+        birthday.day = day
+        print "Setting birthday for [%s] to %d-%d-%d" % (contact.get_name(), year, month, day)
+        ebook.e_contact_set(hash(contact), E_CONTACT_BIRTHDAY_DATE, addressof(birthday))
+        return True
+      
+      
+    # -----------------------------------------------------------------------
+    def get_urls(self, contact):
+        """Return a list of URLs which are associated with this contact."""
+        
+        urls = []
+        ai = GList.new(ebook.e_contact_get_attributes(hash(contact), E_CONTACT_HOMEPAGE_URL))
+        while ai.has_next():
+            attr = ai.next(as_a = EVCardAttribute)
+            if not attr:
+                raise Exception("Unexpected null attribute for [" + contact.get_name() + "] with URLs " + urls)
+            urls.append(string_at(attr.value().next()))
+          
+        return urls
     
-  # -----------------------------------------------------------------------
-  def add_url(self, contact, str, unique = ''):
-    """Add a new URL to the set of URLs for the given contact."""
-
-    urls = re.findall('(?:(?:ftp|https?):\/\/|\\bwww\.|\\bftp\.)[,\w\.\-\/@:%?&=%+#~_$\*]+[\w=\/&=+#]', str, re.I | re.S)
-    updated = False
-    for url in urls:
-      updated = self._add_url(contact, url, unique or re.sub('(?:.*://)?(\w+(?:[\w\.])*).*', '\\1', url)) or updated
-
-    return updated
-
-
-  # -----------------------------------------------------------------------
-  def _add_url(self, contact, url, unique):
-    """Do the work of adding a unique URL to a contact."""
-
-    url_attr = None
-    ai = GList.new(ebook.e_contact_get_attributes(hash(contact), E_CONTACT_HOMEPAGE_URL))
-    while ai.has_next():
-      attr = ai.next(as_a = EVCardAttribute)
-      existing = string_at(attr.value().next())
-      #print "Existing URL [%s] when adding [%s] to [%s] with constraint [%s]" % (existing, url, contact.get_name(), unique)
-      if existing == unique or existing == url:
-        return False
-      elif existing.find(unique) > -1:
-        url_attr = attr
       
-    if not url_attr:
-      ai.add()
-      url_attr = EVCardAttribute()
-      url_attr.group = ''
-      url_attr.name = 'URL'
+    # -----------------------------------------------------------------------
+    def add_url(self, contact, str, unique = ''):
+        """Add a new URL to the set of URLs for the given contact."""
+        
+        urls = re.findall('(?:(?:ftp|https?):\/\/|\\bwww\.|\\bftp\.)[,\w\.\-\/@:%?&=%+#~_$\*]+[\w=\/&=+#]', str, re.I | re.S)
+        updated = False
+        for url in urls:
+            updated = self._add_url(contact, url, unique or re.sub('(?:.*://)?(\w+(?:[\w\.])*).*', '\\1', url)) or updated
+        
+        return updated
+    
     
-    val = GList()
-    print "Setting URL for [%s] to [%s]" % (contact.get_name(), url)
-    val.set(create_string_buffer(url))
-    ai.set(addressof(url_attr))
-    url_attr.values = cast(addressof(val), POINTER(GList))
-    ebook.e_contact_set_attributes(hash(contact), E_CONTACT_HOMEPAGE_URL, addressof(ai))
-    return True
+    # -----------------------------------------------------------------------
+    def _add_url(self, contact, url, unique):
+        """Do the work of adding a unique URL to a contact."""
+        
+        url_attr = None
+        ai = GList.new(ebook.e_contact_get_attributes(hash(contact), E_CONTACT_HOMEPAGE_URL))
+        while ai.has_next():
+            attr = ai.next(as_a = EVCardAttribute)
+            existing = string_at(attr.value().next())
+            #print "Existing URL [%s] when adding [%s] to [%s] with constraint [%s]" % (existing, url, contact.get_name(), unique)
+            if existing == unique or existing == url:
+                return False
+            elif existing.find(unique) > -1:
+                url_attr = attr
+          
+        if not url_attr:
+            ai.add()
+            url_attr = EVCardAttribute()
+            url_attr.group = ''
+            url_attr.name = 'URL'
+        
+        val = GList()
+        print "Setting URL for [%s] to [%s]" % (contact.get_name(), url)
+        val.set(create_string_buffer(url))
+        ai.set(addressof(url_attr))
+        url_attr.values = cast(addressof(val), POINTER(GList))
+        ebook.e_contact_set_attributes(hash(contact), E_CONTACT_HOMEPAGE_URL, addressof(ai))
+        return True
 
index 67d42d0..569bb91 100644 (file)
@@ -6,64 +6,64 @@ from ctypes import *
 from pygobject import *
 
 class ContactView(hildon.PannableArea):
-  """Widget which shows a list of contacts in a pannable area.
-       
-     Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
-     Released under the Artistic Licence."""
-
-
-  # -----------------------------------------------------------------------
-  def __init__(self, contacts):
-    """Constructor. Passed a list of EContacts."""
-    
-    hildon.PannableArea.__init__(self)
-    self.contacts = contacts
-    self.treestore = gtk.ListStore(str, gtk.gdk.Pixbuf, gobject.TYPE_PYOBJECT)
-    for contact in self.contacts:
-      if not contact.get_name():
-        continue
-        
-      photo = contact.get_property('photo')
-      pi = cast(c_void_p(hash(photo)), POINTER(EContactPhoto))
-      pixbuf = None
-      if pi.contents.data.uri.startswith("image/"):
-        data = string_at(pi.contents.data.inlined.data, pi.contents.data.inlined.length)
-        pixbuf_loader = gtk.gdk.PixbufLoader()
-        pixbuf_loader.write(data)
-        pixbuf_loader.close()
-        pixbuf = pixbuf_loader.get_pixbuf()
-      elif pi.contents.data.uri.startswith("file://"):
-        filename = pi.contents.data.uri[7:]
-        pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
-            
-      if pixbuf:
-        size = min(pixbuf.get_width(), pixbuf.get_height())
-        pixbuf = pixbuf.subpixbuf(0, 0, size, size).scale_simple(48, 48, gtk.gdk.INTERP_BILINEAR)
-      self.treestore.append(row = [contact.get_name(), pixbuf, contact])
-
-    self.treeview = gtk.TreeView(self.treestore)
-    tvcolumn = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text = 0)
-    self.treeview.append_column(tvcolumn)
-
-    cell = gtk.CellRendererPixbuf()
-    cell.set_property('xalign', 1.0)
-    tvcolumn = gtk.TreeViewColumn('Picture', cell, pixbuf = 1)
-    self.treeview.append_column(tvcolumn)
+    """Widget which shows a list of contacts in a pannable area.
+         
+       Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
+       Released under the Artistic Licence."""
     
-    self.treeview.set_search_column(0)
-    self.treeview.connect('row-activated', self._activated)
-    self.add(self.treeview)
-    self.set_size_request(600, 380)
     
-    
-  # -----------------------------------------------------------------------
-  def _activated(self, treeview, path, column):
-    """Used to emit the `contact-activated' signal once a row has been
-       selected."""
-       
-    iter    = treeview.get_model().get_iter(path)
-    contact = treeview.get_model().get_value(iter, 2)
-    self.emit('contact-activated', contact)
+    # -----------------------------------------------------------------------
+    def __init__(self, contacts):
+        """Constructor. Passed a list of EContacts."""
+        
+        hildon.PannableArea.__init__(self)
+        self.contacts = contacts
+        self.treestore = gtk.ListStore(str, gtk.gdk.Pixbuf, gobject.TYPE_PYOBJECT)
+        for contact in self.contacts:
+            if not contact.get_name():
+                continue
+              
+            photo = contact.get_property('photo')
+            pi = cast(c_void_p(hash(photo)), POINTER(EContactPhoto))
+            pixbuf = None
+            if pi.contents.data.uri.startswith("image/"):
+                data = string_at(pi.contents.data.inlined.data, pi.contents.data.inlined.length)
+                pixbuf_loader = gtk.gdk.PixbufLoader()
+                pixbuf_loader.write(data)
+                pixbuf_loader.close()
+                pixbuf = pixbuf_loader.get_pixbuf()
+            elif pi.contents.data.uri.startswith("file://"):
+                filename = pi.contents.data.uri[7:]
+                pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
+                  
+            if pixbuf:
+                size = min(pixbuf.get_width(), pixbuf.get_height())
+                pixbuf = pixbuf.subpixbuf(0, 0, size, size).scale_simple(48, 48, gtk.gdk.INTERP_BILINEAR)
+            self.treestore.append(row = [contact.get_name(), pixbuf, contact])
+        
+        self.treeview = gtk.TreeView(self.treestore)
+        tvcolumn = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text = 0)
+        self.treeview.append_column(tvcolumn)
+        
+        cell = gtk.CellRendererPixbuf()
+        cell.set_property('xalign', 1.0)
+        tvcolumn = gtk.TreeViewColumn('Picture', cell, pixbuf = 1)
+        self.treeview.append_column(tvcolumn)
+        
+        self.treeview.set_search_column(0)
+        self.treeview.connect('row-activated', self._activated)
+        self.add(self.treeview)
+        self.set_size_request(600, 380)
+      
+      
+    # -----------------------------------------------------------------------
+    def _activated(self, treeview, path, column):
+        """Used to emit the `contact-activated' signal once a row has been
+           selected."""
+        
+        iter    = treeview.get_model().get_iter(path)
+        contact = treeview.get_model().get_value(iter, 2)
+        self.emit('contact-activated', contact)
 
 
 _contact_activated = gobject.signal_new('contact-activated', ContactView, gobject.SIGNAL_ACTION, gobject.TYPE_NONE, [gobject.TYPE_PYOBJECT])
index d7aec58..e558f44 100755 (executable)
@@ -14,280 +14,280 @@ from wimpworks import WimpWorks
 from hermes import Hermes
 
 class HermesGUI(WimpWorks):
-  """Provides the GUI for Hermes, allowing the syncing of Facebook and
-     Twitter friends' information with the Evolution contacts' database.
+    """Provides the GUI for Hermes, allowing the syncing of Facebook and
+       Twitter friends' information with the Evolution contacts' database.
        
-     Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
-     Released under the Artistic Licence."""
+       Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
+       Released under the Artistic Licence."""
 
 
-  # -----------------------------------------------------------------------
-  def __init__(self):
-    gettext.install('hermes','/opt/hermes/share/locale/')
-    WimpWorks.__init__(self, 'Hermes', version = '0.2.0', dbus_name = 'org.maemo.hermes')
-    self.set_background('background.png')
-    
-    layout = wimpworks.HildonMainScreenLayout(offset = 0.8, container = self)
-    layout.add_button('Retrieve', _("Get contacts' missing info"))
-    layout.add_button('Refresh', _("Update contacts' info"))
-
-    self.add_menu_action("Accounts")
-    self.menu.show_all()
+    # -----------------------------------------------------------------------
+    def __init__(self):
+        gettext.install('hermes','/opt/hermes/share/locale/')
+        WimpWorks.__init__(self, 'Hermes', version = '0.2.0', dbus_name = 'org.maemo.hermes')
+        self.set_background('background.png')
+        
+        layout = wimpworks.HildonMainScreenLayout(offset = 0.8, container = self)
+        layout.add_button('Retrieve', _("Get contacts' missing info"))
+        layout.add_button('Refresh', _("Update contacts' info"))
+        
+        self.add_menu_action("Accounts")
+        self.menu.show_all()
 
   
-  # -----------------------------------------------------------------------
-  def do_retrieve(self, widget):
-    self.sync(widget, False)
+    # -----------------------------------------------------------------------
+    def do_retrieve(self, widget):
+        self.sync(widget, False)
     
     
-  # -----------------------------------------------------------------------
-  def do_refresh(self, widget):
-    self.sync(widget, True)
-
-
-  # -----------------------------------------------------------------------
-  def do_accounts(self, widget = None):
-    dialog = gtk.Dialog(_('Accounts'), self.main_window)
-    dialog.add_button(_('Save'), gtk.RESPONSE_OK)
-
-    #pa = hildon.PannableArea()
-    #dialog.vbox.add(pa)
-    content = dialog.vbox 
-    #content = gtk.VBox()
-    #pa.add(content)
-    #pa.set_size_request(600, 380)
-
-    use_facebook = self.new_checkbox(_('Use Facebook'), content)
-    use_facebook.set_active(self.get_use_facebook())
-
-    indent = self.new_indent(content)
-    self.link_control(use_facebook, gtk.Label(_('Note: authentication via web page')), indent)
-    
-    fb_empty = self.link_control(use_facebook, self.new_checkbox(_('Create birthday-only contacts')), indent)
-    fb_empty.set_active(self.get_create_empty())
-
-    use_twitter = self.new_checkbox(_('Use Twitter'), content)
-    use_twitter.set_active(self.get_use_twitter())
+    # -----------------------------------------------------------------------
+    def do_refresh(self, widget):
+        self.sync(widget, True)
 
-    indent = self.new_indent(content)
-    tw_user = self.link_control(use_twitter, self.new_input(_('Twitter username')), indent)
-    tw_user.set_text(self.get_twitter_credentials()[0])
 
-    tw_pass = self.link_control(use_twitter, self.new_input(_('Twitter password'), password = True), indent)
-    tw_pass.set_text(self.get_twitter_credentials()[1])
-
-    dialog.show_all()
-    result = dialog.run()
-    dialog.hide()
-    if result == gtk.RESPONSE_OK:
-      self.set_use_facebook(use_facebook.get_active())
-      self.set_create_empty(fb_empty.get_active())
-      self.set_use_twitter(use_twitter.get_active(), tw_user.get_text(), tw_pass.get_text())
-
-    return result
+    # -----------------------------------------------------------------------
+    def do_accounts(self, widget = None):
+        dialog = gtk.Dialog(_('Accounts'), self.main_window)
+        dialog.add_button(_('Save'), gtk.RESPONSE_OK)
+        
+        #pa = hildon.PannableArea()
+        #dialog.vbox.add(pa)
+        content = dialog.vbox 
+        #content = gtk.VBox()
+        #pa.add(content)
+        #pa.set_size_request(600, 380)
+        
+        use_facebook = self.new_checkbox(_('Use Facebook'), content)
+        use_facebook.set_active(self.get_use_facebook())
+        
+        indent = self.new_indent(content)
+        self.link_control(use_facebook, gtk.Label(_('Note: authentication via web page')), indent)
+        
+        fb_empty = self.link_control(use_facebook, self.new_checkbox(_('Create birthday-only contacts')), indent)
+        fb_empty.set_active(self.get_create_empty())
+        
+        use_twitter = self.new_checkbox(_('Use Twitter'), content)
+        use_twitter.set_active(self.get_use_twitter())
+        
+        indent = self.new_indent(content)
+        tw_user = self.link_control(use_twitter, self.new_input(_('Twitter username')), indent)
+        tw_user.set_text(self.get_twitter_credentials()[0])
+        
+        tw_pass = self.link_control(use_twitter, self.new_input(_('Twitter password'), password = True), indent)
+        tw_pass.set_text(self.get_twitter_credentials()[1])
+        
+        dialog.show_all()
+        result = dialog.run()
+        dialog.hide()
+        if result == gtk.RESPONSE_OK:
+            self.set_use_facebook(use_facebook.get_active())
+            self.set_create_empty(fb_empty.get_active())
+            self.set_use_twitter(use_twitter.get_active(), tw_user.get_text(), tw_pass.get_text())
+        
+        return result
    
 
-  # -----------------------------------------------------------------------
-  def sync(self, widget, force, main = True):
-    if main and not self.get_use_facebook() and not self.get_use_twitter():
-      saved = self.do_accounts()
-      if saved == gtk.RESPONSE_DELETE_EVENT:
-        return
-
-    if main:
-      self.main_window.set_property('sensitive', False)
-      thread.start_new_thread(self.sync, (widget, force, False))
-    else:
-      try:
-        fb2c = Hermes(self,
-                      twitter = (self.get_use_twitter() and self.get_twitter_credentials()) or None,
-                      facebook = self.get_use_facebook(),
-                      empty = self.get_create_empty())
-        fb2c.load_friends()
-        fb2c.sync_contacts(resync = force)
-        gobject.idle_add(self.open_summary, fb2c)
-
-      except urllib2.HTTPError, e:
-        traceback.print_exc()
-        if e.code == 401:
-          gobject.idle_add(self.report_error, _('Authentication problem. Check credentials.'), True)
+    # -----------------------------------------------------------------------
+    def sync(self, widget, force, main = True):
+        if main and not self.get_use_facebook() and not self.get_use_twitter():
+            saved = self.do_accounts()
+            if saved == gtk.RESPONSE_DELETE_EVENT:
+                return
+        
+        if main:
+            self.main_window.set_property('sensitive', False)
+            thread.start_new_thread(self.sync, (widget, force, False))
         else:
-          gobject.idle_add(self.report_error, _('Network connection error. Check connectivity.'))
-
-      except urllib2.URLError, e:
-        traceback.print_exc()
-        gobject.idle_add(self.report_error, _('Network connection error. Check connectivity.'))
-      
-      except Exception, e:
-        traceback.print_exc()
-        gobject.idle_add(self.report_error, _('Something went wrong: ') + e.message)
-
-
-  # -----------------------------------------------------------------------
-  def open_summary(self, fb2c):
-    gobject.idle_add(self.main_window.set_property, 'sensitive', True)
-
-    dialog = gtk.Dialog(_('Summary'), self.main_window)
-    dialog.add_button(_('Done'), gtk.RESPONSE_OK)
+            try:
+                fb2c = Hermes(self,
+                              twitter = (self.get_use_twitter() and self.get_twitter_credentials()) or None,
+                              facebook = self.get_use_facebook(),
+                              empty = self.get_create_empty())
+                fb2c.load_friends()
+                fb2c.sync_contacts(resync = force)
+                gobject.idle_add(self.open_summary, fb2c)
+        
+            except urllib2.HTTPError, e:
+                traceback.print_exc()
+                if e.code == 401:
+                    gobject.idle_add(self.report_error, _('Authentication problem. Check credentials.'), True)
+                else:
+                    gobject.idle_add(self.report_error, _('Network connection error. Check connectivity.'))
+        
+            except urllib2.URLError, e:
+                traceback.print_exc()
+                gobject.idle_add(self.report_error, _('Network connection error. Check connectivity.'))
+          
+            except Exception, e:
+                traceback.print_exc()
+                gobject.idle_add(self.report_error, _('Something went wrong: ') + e.message)
     
-    button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
-                           title = _('Updated %d contacts') % (len(fb2c.updated)))
-    button.connect('clicked', self.show_contacts, fb2c, fb2c.updated)
-    button.set_property('sensitive', len(fb2c.updated) > 0)
-    dialog.vbox.add(button)
     
-    button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
-                           title = _('Matched %d contacts') % (len(fb2c.matched)))
-    button.connect('clicked', self.show_contacts, fb2c, fb2c.matched)
-    button.set_property('sensitive', len(fb2c.matched) > 0)
-    dialog.vbox.add(button)
+    # -----------------------------------------------------------------------
+    def open_summary(self, fb2c):
+        gobject.idle_add(self.main_window.set_property, 'sensitive', True)
     
-    button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
-                           title = _('%d contacts unmatched') % (len(fb2c.unmatched)))
-    button.connect('clicked', self.show_contacts, fb2c, fb2c.unmatched)
-    button.set_property('sensitive', len(fb2c.unmatched) > 0)
-    dialog.vbox.add(button)
+        dialog = gtk.Dialog(_('Summary'), self.main_window)
+        dialog.add_button(_('Done'), gtk.RESPONSE_OK)
+      
+        button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
+                               title = _('Updated %d contacts') % (len(fb2c.updated)))
+        button.connect('clicked', self.show_contacts, fb2c, fb2c.updated)
+        button.set_property('sensitive', len(fb2c.updated) > 0)
+        dialog.vbox.add(button)
+        
+        button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
+                               title = _('Matched %d contacts') % (len(fb2c.matched)))
+        button.connect('clicked', self.show_contacts, fb2c, fb2c.matched)
+        button.set_property('sensitive', len(fb2c.matched) > 0)
+        dialog.vbox.add(button)
+      
+        button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
+                               title = _('%d contacts unmatched') % (len(fb2c.unmatched)))
+        button.connect('clicked', self.show_contacts, fb2c, fb2c.unmatched)
+        button.set_property('sensitive', len(fb2c.unmatched) > 0)
+        dialog.vbox.add(button)
+      
+        dialog.show_all()
+        dialog.run()
+        dialog.hide()
     
-    dialog.show_all()
-    dialog.run()
-    dialog.hide()
-  
-
-  # -----------------------------------------------------------------------
-  def show_contacts(self, widget, fb2c, contacts):
-    view = contactview.ContactView(contacts)
-
-    dialog = gtk.Dialog(_('Contacts'), self.main_window)
-    view.connect('contact-activated', self.map_contact, fb2c)
-    dialog.vbox.add(view)
-    dialog.show_all()
     
-    dialog.run()
-    dialog.hide()
+    # -----------------------------------------------------------------------
+    def show_contacts(self, widget, fb2c, contacts):
+        view = contactview.ContactView(contacts)
     
+        dialog = gtk.Dialog(_('Contacts'), self.main_window)
+        view.connect('contact-activated', self.map_contact, fb2c)
+        dialog.vbox.add(view)
+        dialog.show_all()
+      
+        dialog.run()
+        dialog.hide()
+      
+      
+    # -----------------------------------------------------------------------
+    def map_contact(self, widget, contact, fb2c):
+        view = mapcontact.MapContact(fb2c.friends, contact)
     
-  # -----------------------------------------------------------------------
-  def map_contact(self, widget, contact, fb2c):
-    view = mapcontact.MapContact(fb2c.friends, contact)
-
-    dialog = gtk.Dialog(contact.get_name(), self.main_window)
-    dialog.add_button(_('Update'), gtk.RESPONSE_OK)
-    dialog.vbox.add(view)
-    dialog.show_all()
+        dialog = gtk.Dialog(contact.get_name(), self.main_window)
+        dialog.add_button(_('Update'), gtk.RESPONSE_OK)
+        dialog.vbox.add(view)
+        dialog.show_all()
+      
+        result = dialog.run()
+        dialog.hide()
+        if result == gtk.RESPONSE_OK:
+            friend = view.get_selected_friend()
+            if friend:
+                if 'contact' in friend and friend['contact'] == contact:
+                    hildon.hildon_banner_show_information(self.main_window, '', _("Removing existing mappings is not yet supported"))
+                elif view.contact_mapped:
+                    if fb2c.update_contact(contact, friend, True):
+                        fb2c.addresses.commit_contact(contact)
+                else:
+                    if fb2c.update_contact(contact, friend, False):
+                        fb2c.addresses.commit_contact(contact)
     
-    result = dialog.run()
-    dialog.hide()
-    if result == gtk.RESPONSE_OK:
-      friend = view.get_selected_friend()
-      if friend:
-        if 'contact' in friend and friend['contact'] == contact:
-          hildon.hildon_banner_show_information(self.main_window, '', _("Removing existing mappings is not yet supported"))
-        elif view.contact_mapped:
-          if fb2c.update_contact(contact, friend, True):
-            fb2c.addresses.commit_contact(contact)
+      
+    # -----------------------------------------------------------------------
+    def need_auth(self, main = False):
+        if main:
+            hildon.hildon_banner_show_information(self.main_window, '', _("Need to authenticate with Facebook"))
         else:
-          if fb2c.update_contact(contact, friend, False):
-            fb2c.addresses.commit_contact(contact)
-
+            gobject.idle_add(self.need_auth, True)
+      
     
-  # -----------------------------------------------------------------------
-  def need_auth(self, main = False):
-    if main:
-      hildon.hildon_banner_show_information(self.main_window, '', _("Need to authenticate with Facebook"))
-    else:
-      gobject.idle_add(self.need_auth, True)
+    # -----------------------------------------------------------------------
+    def block_for_auth(self, main = False, lock = None):
+        if main:
+            note = gtk.Dialog(_('Facebook authorisation'), self.main_window)
+            note.add_button(_("Validate"), gtk.RESPONSE_OK)
+            note.vbox.add(gtk.Label(_("\nPress 'Validate' once Facebook has\nbeen authenticated in web browser.\n")))
     
-
-  # -----------------------------------------------------------------------
-  def block_for_auth(self, main = False, lock = None):
-    if main:
-      note = gtk.Dialog(_('Facebook authorisation'), self.main_window)
-      note.add_button(_("Validate"), gtk.RESPONSE_OK)
-      note.vbox.add(gtk.Label(_("\nPress 'Validate' once Facebook has\nbeen authenticated in web browser.\n")))
-
-      note.show_all()
-      result = note.run()
-      note.hide()
-      lock.release()
-      
-    else:
-      time.sleep(2)
-      lock = thread.allocate_lock()
-      lock.acquire()
-      gobject.idle_add(self.block_for_auth, True, lock)
-      lock.acquire()
-      lock.release()
-
-
-  # -----------------------------------------------------------------------
-  def progress(self, i, j, main = False):
-    if main:
-      if i == 0:
-        self.progressbar = gtk.ProgressBar()
-        self.progressnote = gtk.Dialog(_("Fetching friends' info"), self.main_window)
-        self.progressnote.vbox.add(self.progressbar)
-        hildon.hildon_gtk_window_set_progress_indicator(self.progressnote, 1)
-        
-        self.progressnote.show_all()
-        
-      elif i < j:
-        if i == 1:
-          self.progressnote.set_title(_("Updating contacts"))
-          hildon.hildon_gtk_window_set_progress_indicator(self.progressnote, 0)
-          
-        self.progressbar.set_fraction(float(i) / float(j))
+            note.show_all()
+            result = note.run()
+            note.hide()
+            lock.release()
         
-      else:
-        self.progressnote.destroy()
+        else:
+            time.sleep(2)
+            lock = thread.allocate_lock()
+            lock.acquire()
+            gobject.idle_add(self.block_for_auth, True, lock)
+            lock.acquire()
+            lock.release()
+    
+    
+    # -----------------------------------------------------------------------
+    def progress(self, i, j, main = False):
+        if main:
+            if i == 0:
+                self.progressbar = gtk.ProgressBar()
+                self.progressnote = gtk.Dialog(_("Fetching friends' info"), self.main_window)
+                self.progressnote.vbox.add(self.progressbar)
+                hildon.hildon_gtk_window_set_progress_indicator(self.progressnote, 1)
+               
+                self.progressnote.show_all()
+              
+            elif i < j:
+                if i == 1:
+                    self.progressnote.set_title(_("Updating contacts"))
+                    hildon.hildon_gtk_window_set_progress_indicator(self.progressnote, 0)
+                
+                self.progressbar.set_fraction(float(i) / float(j))
+              
+            else:
+                self.progressnote.destroy()
+              
+            print i,j
+        else:
+            gobject.idle_add(self.progress, i, j, True)
+    
+    
+    # -----------------------------------------------------------------------
+    def report_error(self, e, prefs = False):
+        if self.progressnote:
+            self.main_window.set_property('sensitive', True)
+            self.progressnote.destroy()
+    
+        hildon.hildon_banner_show_information(self.main_window, '', e)
+        if prefs:
+            self.do_accounts()
+    
         
-      print i,j
-    else:
-      gobject.idle_add(self.progress, i, j, True)
-
-
-  # -----------------------------------------------------------------------
-  def report_error(self, e, prefs = False):
-    if self.progressnote:
-      self.main_window.set_property('sensitive', True)
-      self.progressnote.destroy()
-
-    hildon.hildon_banner_show_information(self.main_window, '', e)
-    if prefs:
-      self.do_accounts()
-
+    def get_use_facebook(self):
+        return self.gconf.get_bool("/apps/maemo/hermes/use_facebook")
+    
+    
+    def set_use_facebook(self, value):
+        self.gconf.set_bool("/apps/maemo/hermes/use_facebook", value)
+    
+    def get_create_empty(self):
+        return self.gconf.get_bool("/apps/maemo/hermes/create_empty")
+    
+    
+    def set_create_empty(self, value):
+        self.gconf.set_bool("/apps/maemo/hermes/create_empty", value)
+    
+    
+    def get_use_twitter(self):
+        return self.gconf.get_bool("/apps/maemo/hermes/use_twitter")
+    
+    
+    def set_use_twitter(self, value, user, password):
+        self.gconf.set_bool("/apps/maemo/hermes/use_twitter", value)
+        self.gconf.set_string("/apps/maemo/hermes/twitter_user", user)
+        self.gconf.set_string("/apps/maemo/hermes/twitter_pwd", password)
       
-  def get_use_facebook(self):
-    return self.gconf.get_bool("/apps/maemo/hermes/use_facebook")
-
-
-  def set_use_facebook(self, value):
-    self.gconf.set_bool("/apps/maemo/hermes/use_facebook", value)
-
-  def get_create_empty(self):
-    return self.gconf.get_bool("/apps/maemo/hermes/create_empty")
-
-
-  def set_create_empty(self, value):
-    self.gconf.set_bool("/apps/maemo/hermes/create_empty", value)
-
-
-  def get_use_twitter(self):
-    return self.gconf.get_bool("/apps/maemo/hermes/use_twitter")
-
-
-  def set_use_twitter(self, value, user, password):
-    self.gconf.set_bool("/apps/maemo/hermes/use_twitter", value)
-    self.gconf.set_string("/apps/maemo/hermes/twitter_user", user)
-    self.gconf.set_string("/apps/maemo/hermes/twitter_pwd", password)
     
-  
-  def get_twitter_credentials(self):
-    return (self.gconf.get_string("/apps/maemo/hermes/twitter_user") or '',
-            self.gconf.get_string("/apps/maemo/hermes/twitter_pwd") or '')
+    def get_twitter_credentials(self):
+        return (self.gconf.get_string("/apps/maemo/hermes/twitter_user") or '',
+                self.gconf.get_string("/apps/maemo/hermes/twitter_pwd") or '')
 
 
 # -------------------------------------------------------------------------
 if __name__ == '__main__':
-  gui = HermesGUI()
-  gui.run()
+    gui = HermesGUI()
+    gui.run()
 
index 76329ef..411ca56 100644 (file)
@@ -8,266 +8,266 @@ from contacts import ContactStore
 import names
 
 class Hermes:
-  """Encapsulate the process of syncing Facebook friends' information with the
-     Evolution contacts' database. This should be used as follows:
-     
-       * Initialise, passing in a callback (methods: need_auth(),
-         block_for_auth(), use_twitter(), use_facebook()).
-       * Call load_friends().
-       * Call sync_contacts().
-       * Retrieve information on changes effected.
+    """Encapsulate the process of syncing Facebook friends' information with the
+       Evolution contacts' database. This should be used as follows:
        
-     This requires two gconf paths to contain Facebook application keys:
-         /apps/maemo/hermes/key_app
-         /apps/maemo/hermes/key_secret
-       
-     Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
-     Released under the Artistic Licence."""
-
-
-  # -----------------------------------------------------------------------
-  def __init__(self, callback, twitter = None, facebook = False, empty = False):
-    """Constructor. Passed a callback which must implement three informational
-       methods:
-       
-         need_auth() - called to indicate a login is about to occur. The user
-                       should be informed.
+         * Initialise, passing in a callback (methods: need_auth(),
+           block_for_auth(), use_twitter(), use_facebook()).
+         * Call load_friends().
+         * Call sync_contacts().
+         * Retrieve information on changes effected.
+         
+       This requires two gconf paths to contain Facebook application keys:
+           /apps/maemo/hermes/key_app
+           /apps/maemo/hermes/key_secret
+         
+       Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
+       Released under the Artistic Licence."""
+    
+    
+    # -----------------------------------------------------------------------
+    def __init__(self, callback, twitter = None, facebook = False, empty = False):
+        """Constructor. Passed a callback which must implement three informational
+           methods:
+           
+             need_auth() - called to indicate a login is about to occur. The user
+                           should be informed.
+                           
+             block_for_auth() - prompt the user to take some action once they have
+                                successfully logged in to Facebook.
+                              
+             progress(i, j) - the application is currently processing friend 'i' of
+                              'j'. Should be used to provide the user a progress bar.
+                              
+          Other parameters:
+             twitter - a username/password tuple or None if Twitter should not be
+                       used. Defaults to None.
                        
-         block_for_auth() - prompt the user to take some action once they have
-                            successfully logged in to Facebook.
-                          
-         progress(i, j) - the application is currently processing friend 'i' of
-                          'j'. Should be used to provide the user a progress bar.
-                          
-      Other parameters:
-         twitter - a username/password tuple or None if Twitter should not be
-                   used. Defaults to None.
-                   
-         facebook - boolean indicating if Facebook should be used. Defaults to
-                    False.
-
-         empty - boolean indicating if 'empty' contacts consisting of a profile
-                 URL and birthday should be created.
-                          """
-
-    self.gc       = gnome.gconf.client_get_default()
-    self.callback = callback
-    self.twitter  = twitter
-    self.facebook = facebook
-    self.create_empty = empty
-
-    # -- Check the environment is going to work...
-    #
-    if (self.gc.get_string('/desktop/gnome/url-handlers/http/command') == 'epiphany %s'):
-      raise Exception('Browser in gconf invalid (see NB#136012). Installation error.')
-
-    # -- Get private keys for this app...
-    #
-    key_app    = self.gc.get_string('/apps/maemo/hermes/key_app')
-    key_secret = self.gc.get_string('/apps/maemo/hermes/key_secret')
-    if key_app is None or key_secret is None:
-      raise Exception('No Facebook application keys found. Installation error.')
-
-    self.fb = Facebook(key_app, key_secret)
-    self.fb.desktop = True
-
-
-  # -----------------------------------------------------------------------
-  def do_fb_login(self):
-    """Perform authentication against Facebook and store the result in gconf
-         for later use. Uses the 'need_auth' and 'block_for_auth' methods on
-         the callback class. The former allows a message to warn the user
-         about what is about to happen to be shown; the second is to wait
-         for the user to confirm they have logged in."""
-    self.fb.session_key = None
-    self.fb.secret = None
-    self.fb.uid = None
+             facebook - boolean indicating if Facebook should be used. Defaults to
+                        False.
+        
+             empty - boolean indicating if 'empty' contacts consisting of a profile
+                     URL and birthday should be created.
+                              """
+        
+        self.gc       = gnome.gconf.client_get_default()
+        self.callback = callback
+        self.twitter  = twitter
+        self.facebook = facebook
+        self.create_empty = empty
+        
+        # -- Check the environment is going to work...
+        #
+        if (self.gc.get_string('/desktop/gnome/url-handlers/http/command') == 'epiphany %s'):
+            raise Exception('Browser in gconf invalid (see NB#136012). Installation error.')
+        
+        # -- Get private keys for this app...
+        #
+        key_app    = self.gc.get_string('/apps/maemo/hermes/key_app')
+        key_secret = self.gc.get_string('/apps/maemo/hermes/key_secret')
+        if key_app is None or key_secret is None:
+            raise Exception('No Facebook application keys found. Installation error.')
+        
+        self.fb = Facebook(key_app, key_secret)
+        self.fb.desktop = True
     
-    self.callback.need_auth()
-    self.fb.auth.createToken()
-    self.fb.login()
-    self.callback.block_for_auth()
-    session = self.fb.auth.getSession()
-
-    self.gc.set_string('/apps/maemo/hermes/session_key', session['session_key'])
-    self.gc.set_string('/apps/maemo/hermes/secret_key', session['secret'])
-    self.gc.set_string('/apps/maemo/hermes/uid', str(session['uid']))
-
-
-  # -----------------------------------------------------------------------
-  def load_friends(self):
-    """Load information on the authenticated user's friends. If no user is
-       currently authenticated, prompts for a login."""
-
-    self.friends = {}
-    self.blocked_pictures = []
-    self.callback.progress(0, 0)
-    self.friends_by_url = {}
     
-    # -- Get a user session and retrieve Facebook friends...
-    #
-    if self.facebook:
-      print "+++ Opening Facebook..."
-      if self.fb.session_key is None:
-        self.fb.session_key = self.gc.get_string('/apps/maemo/hermes/session_key')
-        self.fb.secret = self.gc.get_string('/apps/maemo/hermes/secret_key')
-        self.fb.uid = self.gc.get_string('/apps/maemo/hermes/uid')
-
-      # Check the available session is still valid...
-      while True:
-        try:
-          if self.fb.users.getLoggedInUser() and self.fb.session_key:
-            break
-        except FacebookError:
-          pass
-        self.do_fb_login()
-
-      # Get the list of friends...
-      attrs = ['uid', 'name', 'pic_big', 'birthday_date', 'profile_url', 'first_name', 'last_name', 'website']
-      for friend in self.fb.users.getInfo(self.fb.friends.get(), attrs):
-        key = unicode(friend['name']).encode('trans')
-        self.friends[key] = friend
-
-        if 'profile_url' not in friend:
-          friend['profile_url'] = "http://www.facebook.com/profile.php?id=" + str(friend ['uid'])
-
-        self.friends_by_url[friend['profile_url']] = friend
-        friend['pic']  = friend[attrs[2]]
-        friend['account'] = 'facebook'
-
-        if 'website' in friend and friend['website']:
-          friend['homepage'] = friend['website']
-
-        if 'pic' not in friend or not friend['pic']:
-          self.blocked_pictures.append(friend)
-          
-          
-    # -- Retrieve following information from Twitter...
-    #
-    if self.twitter is not None:
-      print "+++ Opening Twitter..."
-      (user, passwd) = self.twitter
-      api = twitter.Api(username=user, password=passwd)
-      users = api.GetFriends()
-      for tweeter in api.GetFriends():
-        key    = unicode(tweeter.name).encode('trans')
-        url    = 'http://twitter.com/%s' % (tweeter.screen_name)
-        friend = {'name':          tweeter.name, 'pic': tweeter.profile_image_url,
-                  'birthday_date': None,         'twitter_url': url,
-                  'homepage':      tweeter.url,  'account': 'twitter'}
-        if friend['pic'].find('/default_profile') > -1:
-          friend['pic'] = None
-          
-        self.friends[key] = friend
-        self.friends_by_url[url] = friend
-  
-    # TODO What if the user has *no* contacts?
-
-  
-  # -----------------------------------------------------------------------
-  def sync_contacts(self, resync = False):
-    """Synchronise Facebook profiles to contact database. If resync is false,
-       no existing information will be overwritten."""
-
-    # -- Find addresses...
-    #
-    print "+++ Syncing contacts..."
-    self.addresses = evolution.ebook.open_addressbook('default')
-    print "+++ Addressbook opened..."
-    self.store = ContactStore(self.addresses)
-    print "+++ Contact store created..."
-    self.updated = []
-    self.unmatched = []
-    self.matched = []
-    contacts = self.addresses.get_all_contacts()
-    contacts.sort(key=lambda obj: obj.get_name())
-    current = 0
-    maximum = len(contacts)
-    for contact in contacts:
-      current += 1
-      self.callback.progress(current, maximum)
-      found = False
-      updated = False
-      
-      # Try match on existing URL...
-      for url in self.store.get_urls(contact):
-        if url in self.friends_by_url:
-          updated = self.update_contact(contact, self.friends_by_url[url], resync)
-          found = True
-          if updated:
-            break
-
-      # Fallback to names...
-      if not found:
-        for name in names.variants(contact.get_name()):
-          if name in self.friends:
-            updated = self.update_contact(contact, self.friends[name], resync)
-            found = True
-            if updated:
-              break
-   
-      # Keep track of updated stuff...
-      if updated:
-        self.updated.append(contact)
-        self.addresses.commit_contact(contact)
-        print "Saved changes to [%s]" % (contact.get_name())
-      
-      if found:
-        self.matched.append(contact)
-      else:
-        self.unmatched.append(contact)
-
-    # -- Create 'empty' contacts with birthdays...
-    #
-    if self.create_empty:
-      for name in self.friends:
-        friend = self.friends[name]
-        if 'contact' in friend or 'birthday_date' not in friend or not friend['birthday_date']:
-          continue
-
-        contact = evolution.ebook.EContact()
-        contact.props.full_name = friend['name']
-        contact.props.given_name = friend['first_name']
-        contact.props.family_name = friend['last_name']
-
-        self.update_contact(contact, friend)
-   
-        self.addresses.add_contact(contact)
-        self.updated.append(contact)
-        self.addresses.commit_contact(contact)
-
-        print "Created [%s]" % (contact.get_name())
-        self.matched.append(contact)
-
-    self.store.close()
-
-
-  # -----------------------------------------------------------------------
-  def update_contact(self, contact, friend, resync = False):
-    """Update the given contact with information from the 'friend'
-       dictionary."""
-
-    updated = False
-    friend['contact'] = contact
-      
-    if 'pic' in friend and friend['pic'] and (resync or contact.get_property('photo') is None):
-      updated = self.store.set_photo(contact, friend['pic']) or updated
+    # -----------------------------------------------------------------------
+    def do_fb_login(self):
+        """Perform authentication against Facebook and store the result in gconf
+             for later use. Uses the 'need_auth' and 'block_for_auth' methods on
+             the callback class. The former allows a message to warn the user
+             about what is about to happen to be shown; the second is to wait
+             for the user to confirm they have logged in."""
+        self.fb.session_key = None
+        self.fb.secret = None
+        self.fb.uid = None
         
-    if 'birthday_date' in friend and friend['birthday_date'] and (resync or contact.get_property('birth-date') is None):
-      date_str = friend['birthday_date'].split('/')
-      date_str.append('0')
-      updated = self.store.set_birthday(contact, int(date_str[1]),
-                                                 int(date_str[0]),
-                                                 int(date_str[2])) or updated
-
-    if 'profile_url' in friend and friend['profile_url']:
-      updated = self.store.add_url(contact, friend['profile_url'], unique='facebook.com') or updated
+        self.callback.need_auth()
+        self.fb.auth.createToken()
+        self.fb.login()
+        self.callback.block_for_auth()
+        session = self.fb.auth.getSession()
+        
+        self.gc.set_string('/apps/maemo/hermes/session_key', session['session_key'])
+        self.gc.set_string('/apps/maemo/hermes/secret_key', session['secret'])
+        self.gc.set_string('/apps/maemo/hermes/uid', str(session['uid']))
+    
+    
+    # -----------------------------------------------------------------------
+    def load_friends(self):
+        """Load information on the authenticated user's friends. If no user is
+           currently authenticated, prompts for a login."""
+        
+        self.friends = {}
+        self.blocked_pictures = []
+        self.callback.progress(0, 0)
+        self.friends_by_url = {}
+        
+        # -- Get a user session and retrieve Facebook friends...
+        #
+        if self.facebook:
+            print "+++ Opening Facebook..."
+            if self.fb.session_key is None:
+                self.fb.session_key = self.gc.get_string('/apps/maemo/hermes/session_key')
+                self.fb.secret = self.gc.get_string('/apps/maemo/hermes/secret_key')
+                self.fb.uid = self.gc.get_string('/apps/maemo/hermes/uid')
             
-    if 'twitter_url' in friend and friend['twitter_url']:
-      updated = self.store.add_url(contact, friend['twitter_url'], unique='twitter.com') or updated
+            # Check the available session is still valid...
+            while True:
+                try:
+                    if self.fb.users.getLoggedInUser() and self.fb.session_key:
+                        break
+                except FacebookError:
+                    pass
+                self.do_fb_login()
             
-    if 'homepage' in friend and friend['homepage']:
-      updated = self.store.add_url(contact, friend['homepage']) or updated
-
-    return updated 
+            # Get the list of friends...
+            attrs = ['uid', 'name', 'pic_big', 'birthday_date', 'profile_url', 'first_name', 'last_name', 'website']
+            for friend in self.fb.users.getInfo(self.fb.friends.get(), attrs):
+                key = unicode(friend['name']).encode('trans')
+                self.friends[key] = friend
+            
+                if 'profile_url' not in friend:
+                    friend['profile_url'] = "http://www.facebook.com/profile.php?id=" + str(friend ['uid'])
+            
+                self.friends_by_url[friend['profile_url']] = friend
+                friend['pic']  = friend[attrs[2]]
+                friend['account'] = 'facebook'
+            
+                if 'website' in friend and friend['website']:
+                    friend['homepage'] = friend['website']
+            
+                if 'pic' not in friend or not friend['pic']:
+                    self.blocked_pictures.append(friend)
+              
+              
+        # -- Retrieve following information from Twitter...
+        #
+        if self.twitter is not None:
+            print "+++ Opening Twitter..."
+            (user, passwd) = self.twitter
+            api = twitter.Api(username=user, password=passwd)
+            users = api.GetFriends()
+            for tweeter in api.GetFriends():
+                key    = unicode(tweeter.name).encode('trans')
+                url    = 'http://twitter.com/%s' % (tweeter.screen_name)
+                friend = {'name':          tweeter.name, 'pic': tweeter.profile_image_url,
+                          'birthday_date': None,         'twitter_url': url,
+                          'homepage':      tweeter.url,  'account': 'twitter'}
+                if friend['pic'].find('/default_profile') > -1:
+                    friend['pic'] = None
+              
+                self.friends[key] = friend
+                self.friends_by_url[url] = friend
+        
+        # TODO What if the user has *no* contacts?
+    
+    
+    # -----------------------------------------------------------------------
+    def sync_contacts(self, resync = False):
+        """Synchronise Facebook profiles to contact database. If resync is false,
+           no existing information will be overwritten."""
+        
+        # -- Find addresses...
+        #
+        print "+++ Syncing contacts..."
+        self.addresses = evolution.ebook.open_addressbook('default')
+        print "+++ Addressbook opened..."
+        self.store = ContactStore(self.addresses)
+        print "+++ Contact store created..."
+        self.updated = []
+        self.unmatched = []
+        self.matched = []
+        contacts = self.addresses.get_all_contacts()
+        contacts.sort(key=lambda obj: obj.get_name())
+        current = 0
+        maximum = len(contacts)
+        for contact in contacts:
+            current += 1
+            self.callback.progress(current, maximum)
+            found = False
+            updated = False
+            
+            # Try match on existing URL...
+            for url in self.store.get_urls(contact):
+                if url in self.friends_by_url:
+                    updated = self.update_contact(contact, self.friends_by_url[url], resync)
+                    found = True
+                    if updated:
+                        break
+            
+            # Fallback to names...
+            if not found:
+                for name in names.variants(contact.get_name()):
+                    if name in self.friends:
+                        updated = self.update_contact(contact, self.friends[name], resync)
+                        found = True
+                        if updated:
+                            break
+            
+            # Keep track of updated stuff...
+            if updated:
+                self.updated.append(contact)
+                self.addresses.commit_contact(contact)
+                print "Saved changes to [%s]" % (contact.get_name())
+            
+            if found:
+                self.matched.append(contact)
+            else:
+                self.unmatched.append(contact)
+        
+        # -- Create 'empty' contacts with birthdays...
+        #
+        if self.create_empty:
+            for name in self.friends:
+                friend = self.friends[name]
+                if 'contact' in friend or 'birthday_date' not in friend or not friend['birthday_date']:
+                    continue
+                
+                contact = evolution.ebook.EContact()
+                contact.props.full_name = friend['name']
+                contact.props.given_name = friend['first_name']
+                contact.props.family_name = friend['last_name']
+                
+                self.update_contact(contact, friend)
+                
+                self.addresses.add_contact(contact)
+                self.updated.append(contact)
+                self.addresses.commit_contact(contact)
+                
+                print "Created [%s]" % (contact.get_name())
+                self.matched.append(contact)
+        
+        self.store.close()
+    
+    
+    # -----------------------------------------------------------------------
+    def update_contact(self, contact, friend, resync = False):
+        """Update the given contact with information from the 'friend'
+           dictionary."""
+        
+        updated = False
+        friend['contact'] = contact
+          
+        if 'pic' in friend and friend['pic'] and (resync or contact.get_property('photo') is None):
+            updated = self.store.set_photo(contact, friend['pic']) or updated
+            
+        if 'birthday_date' in friend and friend['birthday_date'] and (resync or contact.get_property('birth-date') is None):
+            date_str = friend['birthday_date'].split('/')
+            date_str.append('0')
+            updated = self.store.set_birthday(contact, int(date_str[1]),
+                                                       int(date_str[0]),
+                                                       int(date_str[2])) or updated
+        
+        if 'profile_url' in friend and friend['profile_url']:
+            updated = self.store.add_url(contact, friend['profile_url'], unique='facebook.com') or updated
+                
+        if 'twitter_url' in friend and friend['twitter_url']:
+            updated = self.store.add_url(contact, friend['twitter_url'], unique='twitter.com') or updated
+                
+        if 'homepage' in friend and friend['homepage']:
+            updated = self.store.add_url(contact, friend['homepage']) or updated
+        
+        return updated 
 
index a23718b..ca668cd 100644 (file)
@@ -4,73 +4,73 @@ from ctypes import *
 from pygobject import *
 
 class MapContact(hildon.PannableArea):
-  """Widget which shows a list of friends from various feeds and allows
-     the mapping to a particular contact.
+    """Widget which shows a list of friends from various feeds and allows
+       the mapping to a particular contact.
        
-     Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
-     Released under the Artistic Licence."""
+       Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
+       Released under the Artistic Licence."""
 
 
-  # -----------------------------------------------------------------------
-  def __init__(self, friends, contact):
-    """Constructor. Passed a list of `friends' and the contact we're mapping."""
-    
-    hildon.PannableArea.__init__(self)
-    self.friends = friends
-    self.contact = contact
-    self.treestore = gtk.ListStore(gtk.gdk.Pixbuf, str, gtk.gdk.Pixbuf, gobject.TYPE_PYOBJECT)
-    
-    accounts = {}
-    _facebook = gtk.gdk.pixbuf_new_from_file('/opt/hermes/share/account-facebook.png')
-    _twitter  = gtk.gdk.pixbuf_new_from_file('/opt/hermes/share/account-twitter.png')
-    _tick     = gtk.icon_theme_get_default().load_icon('widgets_tickmark_list', 48, 0)
-
-    self.contact_mapped = False
-    mapped_iter = None
-    for key in sorted(self.friends.keys(), cmp = lambda a, b: cmp(a.lower(), b.lower())):
-      friend = self.friends[key]
-      if friend['account'] not in accounts:
-        accounts[friend['account']] = gtk.gdk.pixbuf_new_from_file("/opt/hermes/share/account-%s.png" % (friend['account']))
+    # -----------------------------------------------------------------------
+    def __init__(self, friends, contact):
+        """Constructor. Passed a list of `friends' and the contact we're mapping."""
+        
+        hildon.PannableArea.__init__(self)
+        self.friends = friends
+        self.contact = contact
+        self.treestore = gtk.ListStore(gtk.gdk.Pixbuf, str, gtk.gdk.Pixbuf, gobject.TYPE_PYOBJECT)
+        
+        accounts = {}
+        _facebook = gtk.gdk.pixbuf_new_from_file('/opt/hermes/share/account-facebook.png')
+        _twitter  = gtk.gdk.pixbuf_new_from_file('/opt/hermes/share/account-twitter.png')
+        _tick     = gtk.icon_theme_get_default().load_icon('widgets_tickmark_list', 48, 0)
+        
+        self.contact_mapped = False
+        mapped_iter = None
+        for key in sorted(self.friends.keys(), cmp = lambda a, b: cmp(a.lower(), b.lower())):
+            friend = self.friends[key]
+            if friend['account'] not in accounts:
+                accounts[friend['account']] = gtk.gdk.pixbuf_new_from_file("/opt/hermes/share/account-%s.png" % (friend['account']))
+              
+            photo = friend['pic']
+            pixbuf = None
+            if 'contact' in friend:
+                if friend['contact'] == contact:
+                    pixbuf = _tick
+                    self.contact_mapped = True
+                    mapped_iter = self.treestore.append([accounts[friend['account']], friend['name'], pixbuf, friend])
+                else:
+                    continue
+            else:
+                self.treestore.append([accounts[friend['account']], friend['name'], pixbuf, friend])
+        
+        self.treeview = gtk.TreeView(self.treestore)
+        hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview, gtk.HILDON_UI_MODE_EDIT)
+        
+        self.treeview.append_column(gtk.TreeViewColumn('Account', gtk.CellRendererPixbuf(), pixbuf = 0))
+        self.treeview.append_column(gtk.TreeViewColumn('Name', gtk.CellRendererText(), text = 1))
+        
+        cell = gtk.CellRendererPixbuf()
+        cell.set_property('xalign', 1.0)
+        self.treeview.append_column(gtk.TreeViewColumn('Picture', cell, pixbuf = 2))
+        
+        if mapped_iter:
+            path = self.treestore.get_path(mapped_iter)
+            self.treeview.get_selection().select_path(path)
+            self.treeview.scroll_to_cell(path)
+          
+        self.add(self.treeview)
+        self.set_size_request(600, 320)
       
-      photo = friend['pic']
-      pixbuf = None
-      if 'contact' in friend:
-        if friend['contact'] == contact:
-          pixbuf = _tick
-          self.contact_mapped = True
-          mapped_iter = self.treestore.append([accounts[friend['account']], friend['name'], pixbuf, friend])
-        else:
-          continue
-      else:
-        self.treestore.append([accounts[friend['account']], friend['name'], pixbuf, friend])
-
-    self.treeview = gtk.TreeView(self.treestore)
-    hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview, gtk.HILDON_UI_MODE_EDIT)
-
-    self.treeview.append_column(gtk.TreeViewColumn('Account', gtk.CellRendererPixbuf(), pixbuf = 0))
-    self.treeview.append_column(gtk.TreeViewColumn('Name', gtk.CellRendererText(), text = 1))
-
-    cell = gtk.CellRendererPixbuf()
-    cell.set_property('xalign', 1.0)
-    self.treeview.append_column(gtk.TreeViewColumn('Picture', cell, pixbuf = 2))
-    
-    if mapped_iter:
-      path = self.treestore.get_path(mapped_iter)
-      self.treeview.get_selection().select_path(path)
-      self.treeview.scroll_to_cell(path)
-      
-    self.add(self.treeview)
-    self.set_size_request(600, 320)
+    # -----------------------------------------------------------------------
+    def get_selected_friend(self):
+        """Return the selected friend, or `None' if none."""
     
-  # -----------------------------------------------------------------------
-  def get_selected_friend(self):
-    """Return the selected friend, or `None' if none."""
-
-    (model, iter) = self.treeview.get_selection().get_selected()
-    if not iter:
-      return None
-           
-    return model.get_value(iter, 3)
+        (model, iter) = self.treeview.get_selection().get_selected()
+        if not iter:
+            return None
+             
+        return model.get_value(iter, 3)
 
 
 _account_selected = gobject.signal_new('account-selected', MapContact, gobject.SIGNAL_ACTION, gobject.TYPE_NONE, [gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])
index 0835e8e..59d5a0a 100644 (file)
@@ -1,5 +1,3 @@
-import trans
-
 """Utilities for determing name variants.
 
    Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
@@ -18,11 +16,11 @@ __names__ = [
 
 __map__ = {}
 for row in __names__:
-  for name in row:
-    if (not name in __map__):
-      __map__[name] = set(row)
-    else:
-      __map__[name] = __map__[name].union(row)
+    for name in row:
+        if (not name in __map__):
+            __map__[name] = set(row)
+        else:
+            __map__[name] = __map__[name].union(row)
 
 # -----------------------------------------------------------------------
 def variants(name):
@@ -32,15 +30,15 @@ def variants(name):
 
     result = set()
     if (name is None):
-      return result
+        return result
     
     name = unicode(name).encode('trans')
     result.add(name)
     bits = name.split(' ')
     for bit in bits:
-      if (bit in __map__):
-        for replacement in __map__[bit]:
-          result.add(name.replace(bit, replacement))
+        if (bit in __map__):
+            for replacement in __map__[bit]:
+                result.add(name.replace(bit, replacement))
 
     return result
 
index d095b6f..150deae 100644 (file)
@@ -38,119 +38,119 @@ class PyGObjectCAPI(object):
 
 # -------------------------------------------------------------------------
 class GList(Structure):
-  """GList representation and convenience functions, based on Java's Iterable.
-  
-     Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
-     Released under the Artistic Licence."""
-
-
-  # -----------------------------------------------------------------------
-  @classmethod
-  def new(clazz, ptr = None):
-    """Return a reference to an empty, or valid, GList at the
-       given pointer address."""
-       
-    if ptr:
-      return cast(c_void_p(ptr), POINTER(GList)).contents
-    else:
-      return GList()
-
-
-  # -----------------------------------------------------------------------
-  _fields_ = [('_data', c_void_p),
-              ('_next', c_void_p),
-              ('_prev', c_void_p)]
-              
-  _ptr = None # Initialises to before the list for `while(has_next)...'
-
-  
-  # -----------------------------------------------------------------------
-  def reset(self):
-    """Rewind the iterable to the start of the list."""
+    """GList representation and convenience functions, based on Java's Iterable.
     
-    self._ptr = None
+       Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
+       Released under the Artistic Licence."""
     
-  # -----------------------------------------------------------------------
-  def has_next(self):
-    """Return True if the list has an item on which next can be called."""
     
-    return (not self._ptr and self._data) or (self._ptr and self._ptr._next)
-
-
-  # -----------------------------------------------------------------------
-  def next(self, as_a = None):
-    """Move the pointer on to the next item in the list and return its value, or
-       raise an exception if already on the last."""
-       
-    if self._ptr and not self._ptr._next:
-      raise Exception("IndexOutOfBounds")
-
-    self._ptr = self._ptr and cast(self._ptr._next, POINTER(GList)).contents or self
-    if not self._ptr._data:
-      return None
-    elif as_a:
-      return cast(self._ptr._data, POINTER(as_a)).contents
-    else:
-      return self._ptr._data
-      
-      
-  # -----------------------------------------------------------------------
-  def set(self, value):
-    """Set the data in the current position in the list."""
+    # -----------------------------------------------------------------------
+    @classmethod
+    def new(clazz, ptr = None):
+        """Return a reference to an empty, or valid, GList at the
+           given pointer address."""
+           
+        if ptr:
+            return cast(c_void_p(ptr), POINTER(GList)).contents
+        else:
+            return GList()
     
-    if not self._ptr:
-      self._ptr = self
-      
-    self._ptr._data = cast(value, c_void_p);
     
+    # -----------------------------------------------------------------------
+    _fields_ = [('_data', c_void_p),
+                ('_next', c_void_p),
+                ('_prev', c_void_p)]
+                
+    _ptr = None # Initialises to before the list for `while(has_next)...'
     
     
-  # -----------------------------------------------------------------------
-  def add(self):
-    """Add a new entry on to the end of the list, ready to be "set"."""
+    # -----------------------------------------------------------------------
+    def reset(self):
+        """Rewind the iterable to the start of the list."""
+      
+        self._ptr = None
+      
+    # -----------------------------------------------------------------------
+    def has_next(self):
+        """Return True if the list has an item on which next can be called."""
+      
+        return (not self._ptr and self._data) or (self._ptr and self._ptr._next)
     
-    self.reset()
-    while self.has_next():
-      self.next()
+    
+    # -----------------------------------------------------------------------
+    def next(self, as_a = None):
+        """Move the pointer on to the next item in the list and return its value, or
+           raise an exception if already on the last."""
+           
+        if self._ptr and not self._ptr._next:
+            raise Exception("IndexOutOfBounds")
+        
+        self._ptr = self._ptr and cast(self._ptr._next, POINTER(GList)).contents or self
+        if not self._ptr._data:
+            return None
+        elif as_a:
+            return cast(self._ptr._data, POINTER(as_a)).contents
+        else:
+            return self._ptr._data
+        
+        
+    # -----------------------------------------------------------------------
+    def set(self, value):
+        """Set the data in the current position in the list."""
+      
+        if not self._ptr:
+            self._ptr = self
+        
+        self._ptr._data = cast(value, c_void_p);
+      
+      
       
-    if not self._ptr:
-      self._ptr = self
-    else:      
-      new = GList()
-      new._prev = addressof(self._ptr)
-      self._ptr._next = addressof(new)
-      self._ptr = new
+    # -----------------------------------------------------------------------
+    def add(self):
+        """Add a new entry on to the end of the list, ready to be "set"."""
+        
+        self.reset()
+        while self.has_next():
+            self.next()
+          
+        if not self._ptr:
+            self._ptr = self
+        else:      
+            new = GList()
+            new._prev = addressof(self._ptr)
+            self._ptr._next = addressof(new)
+            self._ptr = new
 
 
 # -------------------------------------------------------------------------
 class EContactPhoto_inlined(Structure):
-  _fields_ = [('mime_type', c_char_p),
-              ('length', c_uint),
-              ('data', c_void_p)]
+    _fields_ = [('mime_type', c_char_p),
+                ('length', c_uint),
+                ('data', c_void_p)]
 
 class EContactPhoto_data(Union):
-  _fields_ = [('inlined', EContactPhoto_inlined),
-              ('uri', c_char_p)]
+    _fields_ = [('inlined', EContactPhoto_inlined),
+                ('uri', c_char_p)]
 
 class EContactPhoto(Structure):
-  _fields_ = [('type', c_int),
-              ('data', EContactPhoto_data)]
+    _fields_ = [('type', c_int),
+                ('data', EContactPhoto_data)]
 
 class EContactDate(Structure):
-  _fields_ = [('year', c_uint),
-              ('month', c_uint),
-              ('day', c_uint)]
+    _fields_ = [('year', c_uint),
+                ('month', c_uint),
+                ('day', c_uint)]
 
 # -------------------------------------------------------------------------
 class EVCardAttribute(Structure):
- _fields_ = [('group', c_char_p),
-            ('name', c_char_p),
-            ('params', POINTER(GList)),
-            ('values', POINTER(GList)),]
+    _fields_ = [('group', c_char_p),
+               ('name', c_char_p),
+               ('params', POINTER(GList)),
+               ('values', POINTER(GList)),]
             
- def value(self):
-   if not self.values:
-     return None
+    def value(self):
+        if not self.values:
+            return None
      
-   return self.values.contents
+        return self.values.contents
 
index cb8d37b..4f602f2 100644 (file)
@@ -12,227 +12,227 @@ import os.path
 # -- Work out environment...
 #
 try:
-  import hildon
-  _have_hildon = True
+    import hildon
+    _have_hildon = True
 except ImportError:
-  _have_hildon = False
+    _have_hildon = False
   
 try:
-  import osso
-  _have_osso = True
+    import osso
+    _have_osso = True
 except ImportError:
-  _have_osso = False
+    _have_osso = False
   
 try:
-  import gnome.gconf
-  _have_gconf = True
+    import gnome.gconf
+    _have_gconf = True
 except ImportError:
-  _have_gconf = False
+    _have_gconf = False
 
 gobject.threads_init()
 
 # -- Main class...
 #
 class WimpWorks:
-  '''A framework for creating easy-to-use graphical user interfaces using
-     GTK+, Python, DBus and more.
-     
-     This is the base class. It should be constructed with a DBus name
-     and a version.
+    '''A framework for creating easy-to-use graphical user interfaces using
+       GTK+, Python, DBus and more.
        
-     Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
-     Released under the Artistic Licence.'''
-
-
-  # -----------------------------------------------------------------------
-  def __init__(self, application, version = '1.0.0', dbus_name = None):
-    '''Constructor. Initialises the gconf connection, DBus, OSSO and more.
+       This is the base class. It should be constructed with a DBus name
+       and a version.
+         
+       Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
+       Released under the Artistic Licence.'''
     
-       @param application User-facing name of the application.
-       @param version Version string of the application.
-       @param dbus_name Name to register with DBus. If unspecified, no
-              DBus registration will be performed.'''
     
-    self.name = application
-    self.dbus_name = dbus_name
-    self.menu = None
-
-    if _have_gconf:
-      self.gconf = gnome.gconf.client_get_default()
+    # -----------------------------------------------------------------------
+    def __init__(self, application, version = '1.0.0', dbus_name = None):
+        '''Constructor. Initialises the gconf connection, DBus, OSSO and more.
+        
+           @param application User-facing name of the application.
+           @param version Version string of the application.
+           @param dbus_name Name to register with DBus. If unspecified, no
+                  DBus registration will be performed.'''
+        
+        self.name = application
+        self.dbus_name = dbus_name
+        self.menu = None
+        
+        if _have_gconf:
+            self.gconf = gnome.gconf.client_get_default()
+        
+        if _have_hildon:
+            self.app = hildon.Program()
+            self.main_window = hildon.Window()
+            gtk.set_application_name(application)
+        else:
+            self.app = None
+            self.main_window = gtk.Window()
+        
+        self.main_window.set_title(application)
+        self.main_window.connect("delete-event", gtk.main_quit)
+        
+        if _have_osso and dbus_name:
+            self.osso_context = osso.Context(dbus_name, version, False)
+          
+        if self.app:
+            self.app.add_window(self.main_window)
+          
+        if _have_hildon:
+            self._expose_hid = self.main_window.connect('expose-event', self._take_screenshot)
     
-    if _have_hildon:
-      self.app = hildon.Program()
-      self.main_window = hildon.Window()
-      gtk.set_application_name(application)
-    else:
-      self.app = None
-      self.main_window = gtk.Window()
-
-    self.main_window.set_title(application)
-    self.main_window.connect("delete-event", gtk.main_quit)
+        
+    # -----------------------------------------------------------------------
+    def set_background(self, file, window = None):
+        '''Set the background of the given (or main) window to that contained in
+           'file'.
+           
+           @param file File name to set. If not an absolute path, typical application
+                       directories will be checked.
+           @param window Window to set background of. If unset, will default to the
+                         main application window.'''
+        
+        # TODO Handle other forms of path
+        file = "/opt/%s/share/%s" % (re.sub('[^a-z0-9_]', '', self.name.lower()), file)
+        if not window:
+            window = self.main_window
+          
+        self._background, mask = gtk.gdk.pixbuf_new_from_file(file).render_pixmap_and_mask()
+        window.realize()
+        window.window.set_back_pixmap(self._background, False)
     
-    if _have_osso and dbus_name:
-      self.osso_context = osso.Context(dbus_name, version, False)
-      
-    if self.app:
-      self.app.add_window(self.main_window)
-      
-    if _have_hildon:
-      self._expose_hid = self.main_window.connect('expose-event', self._take_screenshot)
-
       
-  # -----------------------------------------------------------------------
-  def set_background(self, file, window = None):
-    '''Set the background of the given (or main) window to that contained in
-       'file'.
-       
-       @param file File name to set. If not an absolute path, typical application
-                   directories will be checked.
-       @param window Window to set background of. If unset, will default to the
-                     main application window.'''
+    # -----------------------------------------------------------------------
+    def add_menu_action(self, title, window = None):
+        '''Add a menu action to the given (or main) window. Once add_menu_action()
+           has been called with all the properties, 'self.menu.show_all()' should be
+           called.
+        
+           @param title The label of the action, and used to compute the callback
+                        method. This should be the UN-i18n version: gettext is used
+                        on the value.'''
+        
+        if not window:
+            window = self.main_window
+          
+        if not self.menu:
+            if _have_hildon:
+                self.menu = hildon.AppMenu()
+                window.set_app_menu(self.menu)
+            else:
+                raise Exception("Menu needs to be created, and no Hildon present")
+            
+        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+        button.set_label(_(title))
+        button.connect("clicked", self.callback, title)
+        self.menu.append(button)
+    
+    
+    # -----------------------------------------------------------------------
+    def run(self):
+        '''Once the application has been initialised, this will show the main window
+           and run the mainloop.'''
+         
+        self.main_window.show_all()
+        gtk.main()
+    
+    
+    # -----------------------------------------------------------------------
+    def _take_screenshot(self, event = None, data = None):
+        '''Used to provide a quick-loading screen.
+        
+           @see http://maemo.org/api_refs/5.0/5.0-final/hildon/hildon-Additions-to-GTK+.html#hildon-gtk-window-take-screenshot'''
+        
+        self.main_window.disconnect(self._expose_hid)
+        if not os.path.isfile("/home/user/.cache/launch/%s.pvr" % (self.dbus_name)):
+            gobject.timeout_add(80, hildon.hildon_gtk_window_take_screenshot, self.main_window, True)
     
-    # TODO Handle other forms of path
-    file = "/opt/%s/share/%s" % (re.sub('[^a-z0-9_]', '', self.name.lower()), file)
-    if not window:
-      window = self.main_window
       
-    self._background, mask = gtk.gdk.pixbuf_new_from_file(file).render_pixmap_and_mask()
-    window.realize()
-    window.window.set_back_pixmap(self._background, False)
-
+    # -----------------------------------------------------------------------
+    def callback(self, event, method):
+        '''Call a method on this object, using the given string to derive
+           the name. If no method is found, no action is taken.
+           
+           @param event Event which triggered the callback.
+           @param method String which will be lowercased to form a method
+                  called 'do_method'.'''
+        
+        method = re.sub('[^a-z0-9_]', '', method.lower())
+        getattr(self, "do_%s" % (method))(event.window)
     
-  # -----------------------------------------------------------------------
-  def add_menu_action(self, title, window = None):
-    '''Add a menu action to the given (or main) window. Once add_menu_action()
-       has been called with all the properties, 'self.menu.show_all()' should be
-       called.
-
-       @param title The label of the action, and used to compute the callback
-                    method. This should be the UN-i18n version: gettext is used
-                    on the value.'''
-
-    if not window:
-      window = self.main_window
       
-    if not self.menu:
-      if _have_hildon:
-        self.menu = hildon.AppMenu()
-        window.set_app_menu(self.menu)
-      else:
-        raise Exception("Menu needs to be created, and no Hildon present")
+    # -----------------------------------------------------------------------
+    def new_checkbox(self, label, box = None):
+        '''Create a new checkbox, adding it to the given container.
         
-    button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-    button.set_label(_(title))
-    button.connect("clicked", self.callback, title)
-    self.menu.append(button)
-
-
-  # -----------------------------------------------------------------------
-  def run(self):
-    '''Once the application has been initialised, this will show the main window
-       and run the mainloop.'''
-       
-    self.main_window.show_all()
-    gtk.main()
-
-
-  # -----------------------------------------------------------------------
-  def _take_screenshot(self, event = None, data = None):
-    '''Used to provide a quick-loading screen.
+           @param label Label for the checkbox.
+           @param box Optional container to add the created checkbox to.
+           @return The newly created checkbox.'''
+           
+        checkbox = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
+        checkbox.set_label(label)
+        if box:
+            box.add(checkbox)
+        return checkbox
     
-       @see http://maemo.org/api_refs/5.0/5.0-final/hildon/hildon-Additions-to-GTK+.html#hildon-gtk-window-take-screenshot'''
     
-    self.main_window.disconnect(self._expose_hid)
-    if not os.path.isfile("/home/user/.cache/launch/%s.pvr" % (self.dbus_name)):
-      gobject.timeout_add(80, hildon.hildon_gtk_window_take_screenshot, self.main_window, True)
-
+    # -----------------------------------------------------------------------
+    def new_indent(self, box):
+        '''Create an indent which can be used to show items related to each other.
+        
+           @param box Container to add the indent to.'''
+           
+        outer = gtk.HBox()
+        indent = gtk.VBox()
+        outer.pack_start(indent, padding=48)
+        box.add(outer)
+        return indent
     
-  # -----------------------------------------------------------------------
-  def callback(self, event, method):
-    '''Call a method on this object, using the given string to derive
-       the name. If no method is found, no action is taken.
-       
-       @param event Event which triggered the callback.
-       @param method String which will be lowercased to form a method
-              called 'do_method'.'''
-
-    method = re.sub('[^a-z0-9_]', '', method.lower())
-    getattr(self, "do_%s" % (method))(event.window)
-
     
-  # -----------------------------------------------------------------------
-  def new_checkbox(self, label, box = None):
-    '''Create a new checkbox, adding it to the given container.
+    # -----------------------------------------------------------------------
+    def new_input(self, label, box = None, password = False):
+        '''Create a new input with the given label, optionally adding it to a
+           container.
+           
+           @param label Text describing the purpose of the input field.
+           @param box Optional container to add the input to.
+           @param password Boolean indicating if the input is used for passwords.
+           @return The newly created input.'''
+           
+        input = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
+        input.set_placeholder(label)
+        input.set_property('is-focus', False)
+        
+        if password:
+            input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL | gtk.HILDON_GTK_INPUT_MODE_INVISIBLE)
+        else:
+            input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL)
+          
+        if box:
+            box.add(input)
+        return input
     
-       @param label Label for the checkbox.
-       @param box Optional container to add the created checkbox to.
-       @return The newly created checkbox.'''
-       
-    checkbox = hildon.CheckButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
-    checkbox.set_label(label)
-    if box:
-      box.add(checkbox)
-    return checkbox
-
-
-  # -----------------------------------------------------------------------
-  def new_indent(self, box):
-    '''Create an indent which can be used to show items related to each other.
     
-       @param box Container to add the indent to.'''
-       
-    outer = gtk.HBox()
-    indent = gtk.VBox()
-    outer.pack_start(indent, padding=48)
-    box.add(outer)
-    return indent
-
-  # -----------------------------------------------------------------------
-  def new_input(self, label, box = None, password = False):
-    '''Create a new input with the given label, optionally adding it to a
-       container.
-       
-       @param label Text describing the purpose of the input field.
-       @param box Optional container to add the input to.
-       @param password Boolean indicating if the input is used for passwords.
-       @return The newly created input.'''
-       
-    input = hildon.Entry(gtk.HILDON_SIZE_FINGER_HEIGHT)
-    input.set_placeholder(label)
-    input.set_property('is-focus', False)
+    # -----------------------------------------------------------------------
+    def link_control(self, checkbox, ctrl, box = None):
+        '''Link a checkbox to a control, such that the editability of the
+           control is determined by the checkbox state.
+           
+           @param checkbox Checkbox which will control the state.
+           @param ctrl Control to add.
+           @param box Optional container to add 'ctrl' to.
+           @return The added control.'''
+           
+        if box:
+            box.add(ctrl)
+          
+        self._sync_edit(checkbox, ctrl)
+        checkbox.connect('toggled', self._sync_edit, ctrl)
+        return ctrl
     
-    if password:
-      input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL | gtk.HILDON_GTK_INPUT_MODE_INVISIBLE)
-    else:
-      input.set_property('hildon-input-mode', gtk.HILDON_GTK_INPUT_MODE_FULL)
-      
-    if box:
-      box.add(input)
-    return input
-
-
-  # -----------------------------------------------------------------------
-  def link_control(self, checkbox, ctrl, box = None):
-    '''Link a checkbox to a control, such that the editability of the
-       control is determined by the checkbox state.
-       
-       @param checkbox Checkbox which will control the state.
-       @param ctrl Control to add.
-       @param box Optional container to add 'ctrl' to.
-       @return The added control.'''
-       
-    if box:
-      box.add(ctrl)
       
-    self._sync_edit(checkbox, ctrl)
-    checkbox.connect('toggled', self._sync_edit, ctrl)
-    return ctrl
-  
-    
-  # -----------------------------------------------------------------------
-  def _sync_edit(self, checkbox, edit):
-    edit.set_property('sensitive', checkbox.get_active())
+    # -----------------------------------------------------------------------
+    def _sync_edit(self, checkbox, edit):
+        edit.set_property('sensitive', checkbox.get_active())
     
 
       
@@ -247,38 +247,38 @@ class HildonMainScreenLayout():
 
     # ---------------------------------------------------------------------
     def __init__(self, container, offset = 0.5):
-      '''Create a new layout.
-      
-         @param container Container to add layout to. If unspecified,
-                the application's main window will be used.
-         @param offset The vertical offset for the buttons. If unspecified,
-                they will be centred. Ranges from 0.0 (top) to 1.0 (bottom).'''
-                
-      self._container = container
-      alignment = gtk.Alignment(xalign=0.5, yalign=0.8, xscale=0.8)
-      self._box = gtk.HButtonBox()
-      alignment.add(self._box)
-      container.main_window.add(alignment)
-      self._box.set_property('layout-style', gtk.BUTTONBOX_SPREAD)
+        '''Create a new layout.
+        
+           @param container Container to add layout to. If unspecified,
+                  the application's main window will be used.
+           @param offset The vertical offset for the buttons. If unspecified,
+                  they will be centred. Ranges from 0.0 (top) to 1.0 (bottom).'''
+                  
+        self._container = container
+        alignment = gtk.Alignment(xalign=0.5, yalign=0.8, xscale=0.8)
+        self._box = gtk.HButtonBox()
+        alignment.add(self._box)
+        container.main_window.add(alignment)
+        self._box.set_property('layout-style', gtk.BUTTONBOX_SPREAD)
 
       
     # ---------------------------------------------------------------------
     def add_button(self, title, subtitle = ''):
-      '''Add a button to the layout with the specified title. Upon clicking
-         the button, a method of the name 'do_title' will be invoked on the
-         main class.
-         
-         @param title Value of the button, and used to derive the callback method. This
-                      should be the UN-i18n version: gettext is used on the value.
-         @param subtitle An optional subtitle containing more information.'''
-
-      if _have_hildon:         
-        button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
-                               title = title, value = subtitle)
-      else:
-        button = gtk.Button(label = _(title))
-
-      button.set_property('width-request', 250)
-      button.connect('clicked', self._container.callback, title)
-      self._box.add(button)
+        '''Add a button to the layout with the specified title. Upon clicking
+           the button, a method of the name 'do_title' will be invoked on the
+           main class.
+           
+           @param title Value of the button, and used to derive the callback method. This
+                        should be the UN-i18n version: gettext is used on the value.
+           @param subtitle An optional subtitle containing more information.'''
+        
+        if _have_hildon:         
+            button = hildon.Button(gtk.HILDON_SIZE_THUMB_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL,
+                                 title = title, value = subtitle)
+        else:
+            button = gtk.Button(label = _(title))
+        
+        button.set_property('width-request', 250)
+        button.connect('clicked', self._container.callback, title)
+        self._box.add(button)