Add manual mapping for 0.2.0 release
authorAndrew Flegg <andrew@bleb.org>
Sat, 31 Oct 2009 00:44:31 +0000 (00:44 +0000)
committerAndrew Flegg <andrew@bleb.org>
Sat, 31 Oct 2009 00:44:31 +0000 (00:44 +0000)
package/Makefile
package/debian/changelog
package/share/account-facebook.png [new file with mode: 0644]
package/share/account-twitter.png [new file with mode: 0644]
package/src/contacts.py
package/src/contactview.py
package/src/gui.py
package/src/hermes.py
package/src/mapcontact.py [new file with mode: 0644]
package/src/wimpworks.py

index 52cf85a..25b0549 100644 (file)
@@ -13,6 +13,7 @@ install:
        ln -s ../lib/gui.py ${DESTDIR}/opt/hermes/bin/hermes
        install -D -m 0644 -o root -g root src/*.py* ${DESTDIR}/opt/hermes/lib/
        install -D -m 0644 -o root -g root share/background.png ${DESTDIR}/opt/hermes/share/
+       install -D -m 0644 -o root -g root share/account-*.png ${DESTDIR}/opt/hermes/share/
        install -D -m 0644 -o root -g root share/hermes-64.png ${DESTDIR}/usr/share/icons/hicolor/scalable/hildon/hermes.png
        install -D -m 0644 -o root -g root share/hermes-48.png ${DESTDIR}/usr/share/icons/hicolor/48x48/hildon/hermes.png
        install -D -m 0644 -o root -g root share/hermes.desktop ${DESTDIR}/usr/share/applications/hildon/hermes.desktop
index 650b46a..bce429c 100644 (file)
@@ -1,9 +1,13 @@
-hermes (0.1.1) unstable; urgency=low
+hermes (0.2.0) unstable; urgency=low
 
   * New icons from Tim Samoff.
   * Background image for the main window from Tim Samoff.
+  * Use URLs in profiles as a canonical mapping.
+  * Provide first pass at manual mapping through the GUI.
+  * Revise language on two buttons (inspired by MB#5452, reported by
+    Zach Goldberg).
 
- -- Andrew Flegg <andrew@bleb.org>  Fri, 23 Oct 2009 10:26:28 +0100
+ -- Andrew Flegg <andrew@bleb.org>  Sat, 31 Oct 2009 00:41:04 +0000
 
 hermes (0.1.0) unstable; urgency=low
 
diff --git a/package/share/account-facebook.png b/package/share/account-facebook.png
new file mode 100644 (file)
index 0000000..b4803bc
Binary files /dev/null and b/package/share/account-facebook.png differ
diff --git a/package/share/account-twitter.png b/package/share/account-twitter.png
new file mode 100644 (file)
index 0000000..57bcafc
Binary files /dev/null and b/package/share/account-twitter.png differ
index 38b4624..56ebf6b 100644 (file)
@@ -110,7 +110,7 @@ class ContactStore:
     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 url) or updated
+      updated = self._add_url(contact, url, unique or re.sub('(?:.*://)?(\w+(?:[\w\.])*).*', '\\1', url)) or updated
 
     return updated
 
@@ -124,11 +124,11 @@ class ContactStore:
     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
-        break
       
     if not url_attr:
       ai.add()
index e6db658..67d42d0 100644 (file)
@@ -1,5 +1,7 @@
 import gtk
 import hildon
+import gobject
+import evolution
 from ctypes import *
 from pygobject import *
 
@@ -16,7 +18,7 @@ class ContactView(hildon.PannableArea):
     
     hildon.PannableArea.__init__(self)
     self.contacts = contacts
-    self.treestore = gtk.ListStore(str, gtk.gdk.Pixbuf)
+    self.treestore = gtk.ListStore(str, gtk.gdk.Pixbuf, gobject.TYPE_PYOBJECT)
     for contact in self.contacts:
       if not contact.get_name():
         continue
@@ -37,7 +39,7 @@ class ContactView(hildon.PannableArea):
       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])
+      self.treestore.append(row = [contact.get_name(), pixbuf, contact])
 
     self.treeview = gtk.TreeView(self.treestore)
     tvcolumn = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text = 0)
@@ -47,7 +49,24 @@ class ContactView(hildon.PannableArea):
     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 d1c92cc..257e8e1 100755 (executable)
@@ -1,18 +1,17 @@
 #!/usr/bin/env python
 
 import gtk, gobject
-import gnome.gconf
 import traceback
 import time
 import thread
-import os.path
 import contactview
 import urllib2
+import hildon
+import wimpworks
+import mapcontact
 from wimpworks import WimpWorks
 from hermes import Hermes
 
-gobject.threads_init()
-
 class HermesGUI(WimpWorks):
   """Provides the GUI for Hermes, allowing the syncing of Facebook and
      Twitter friends' information with the Evolution contacts' database.
@@ -23,10 +22,10 @@ class HermesGUI(WimpWorks):
 
   # -----------------------------------------------------------------------
   def __init__(self):
-    WimpWorks.__init__(self, 'Hermes', version = '0.1.1', 'org.maemo.hermes')
+    WimpWorks.__init__(self, 'Hermes', version = '0.2.0', dbus_name = 'org.maemo.hermes')
     self.set_background('background.png')
     
-    layout = wimporks.HildonMainScreenLayout(offset = 0.8, container = self)
+    layout = wimpworks.HildonMainScreenLayout(offset = 0.8, container = self)
     layout.add_button('Retrieve', "Get contacts' missing info")
     layout.add_button('Refresh', "Update contacts' info")
 
@@ -46,7 +45,7 @@ class HermesGUI(WimpWorks):
 
   # -----------------------------------------------------------------------
   def do_accounts(self, widget = None):
-    dialog = gtk.Dialog('Accounts', self.window)
+    dialog = gtk.Dialog('Accounts', self.main_window)
     dialog.add_button('Save', gtk.RESPONSE_OK)
 
     #pa = hildon.PannableArea()
@@ -72,7 +71,7 @@ class HermesGUI(WimpWorks):
     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(indent, use_twitter, self.new_input('Twitter password', password = True), indent)
+    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()
@@ -94,8 +93,8 @@ class HermesGUI(WimpWorks):
         return
 
     if main:
-      self.window.set_property('sensitive', False)
-      thread.start_new_thread(self.doSync, (widget, force, False))
+      self.main_window.set_property('sensitive', False)
+      thread.start_new_thread(self.sync, (widget, force, False))
     else:
       try:
         fb2c = Hermes(self,
@@ -124,26 +123,26 @@ class HermesGUI(WimpWorks):
 
   # -----------------------------------------------------------------------
   def open_summary(self, fb2c):
-    gobject.idle_add(self.window.set_property, 'sensitive', True)
+    gobject.idle_add(self.main_window.set_property, 'sensitive', True)
 
-    dialog = gtk.Dialog('Summary', self.window)
+    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.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.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.unmatched)
+    button.connect('clicked', self.show_contacts, fb2c, fb2c.unmatched)
     button.set_property('sensitive', len(fb2c.unmatched) > 0)
     dialog.vbox.add(button)
     
@@ -153,11 +152,11 @@ class HermesGUI(WimpWorks):
   
 
   # -----------------------------------------------------------------------
-  def show_contacts(self, widget, contacts):
+  def show_contacts(self, widget, fb2c, contacts):
     view = contactview.ContactView(contacts)
 
-    dialog = gtk.Dialog('Contacts', self.window)
-    #view.connect('contact-activated', self.map_contact)
+    dialog = gtk.Dialog('Contacts', self.main_window)
+    view.connect('contact-activated', self.map_contact, fb2c)
     dialog.vbox.add(view)
     dialog.show_all()
     
@@ -166,14 +165,32 @@ class HermesGUI(WimpWorks):
     
     
   # -----------------------------------------------------------------------
-  def map_contact(self, widget, contact):
-    print widget, 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()
+    
+    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:
+          hildon.hildon_banner_show_information(self.main_window, '', "Changing existing mappings is not yet supported")
+        else:
+          if fb2c.update_contact(contact, friend, False):
+            fb2c.addresses.commit_contact(contact)
 
     
   # -----------------------------------------------------------------------
   def need_auth(self, main = False):
     if main:
-      hildon.hildon_banner_show_information(self.window, '', "Need to authenticate with Facebook")
+      hildon.hildon_banner_show_information(self.main_window, '', "Need to authenticate with Facebook")
     else:
       gobject.idle_add(self.need_auth, True)
     
@@ -181,7 +198,7 @@ class HermesGUI(WimpWorks):
   # -----------------------------------------------------------------------
   def block_for_auth(self, main = False, lock = None):
     if main:
-      note = gtk.Dialog('Facebook authorisation', self.window)
+      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"))
 
@@ -204,7 +221,7 @@ class HermesGUI(WimpWorks):
     if main:
       if i == 0:
         self.progressbar = gtk.ProgressBar()
-        self.progressnote = gtk.Dialog("Fetching friends' info", self.window)
+        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)
         
@@ -228,42 +245,42 @@ class HermesGUI(WimpWorks):
   # -----------------------------------------------------------------------
   def report_error(self, e, prefs = False):
     if self.progressnote:
-      self.window.set_property('sensitive', True)
+      self.main_window.set_property('sensitive', True)
       self.progressnote.destroy()
 
-    hildon.hildon_banner_show_information(self.window, '', e)
+    hildon.hildon_banner_show_information(self.main_window, '', e)
     if prefs:
       self.do_accounts()
 
       
   def get_use_facebook(self):
-    return self.gc.get_bool("/apps/maemo/hermes/use_facebook")
+    return self.gconf.get_bool("/apps/maemo/hermes/use_facebook")
 
 
   def set_use_facebook(self, value):
-    self.gc.set_bool("/apps/maemo/hermes/use_facebook", value)
+    self.gconf.set_bool("/apps/maemo/hermes/use_facebook", value)
 
   def get_create_empty(self):
-    return self.gc.get_bool("/apps/maemo/hermes/create_empty")
+    return self.gconf.get_bool("/apps/maemo/hermes/create_empty")
 
 
   def set_create_empty(self, value):
-    self.gc.set_bool("/apps/maemo/hermes/create_empty", value)
+    self.gconf.set_bool("/apps/maemo/hermes/create_empty", value)
 
 
   def get_use_twitter(self):
-    return self.gc.get_bool("/apps/maemo/hermes/use_twitter")
+    return self.gconf.get_bool("/apps/maemo/hermes/use_twitter")
 
 
   def set_use_twitter(self, value, user, password):
-    self.gc.set_bool("/apps/maemo/hermes/use_twitter", value)
-    self.gc.set_string("/apps/maemo/hermes/twitter_user", user)
-    self.gc.set_string("/apps/maemo/hermes/twitter_pwd", 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.gc.get_string("/apps/maemo/hermes/twitter_user") or '',
-            self.gc.get_string("/apps/maemo/hermes/twitter_pwd") or '')
+    return (self.gconf.get_string("/apps/maemo/hermes/twitter_user") or '',
+            self.gconf.get_string("/apps/maemo/hermes/twitter_pwd") or '')
 
 
 # -------------------------------------------------------------------------
index e7ec2fc..3f5afb5 100644 (file)
@@ -102,10 +102,12 @@ class Hermes:
     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')
@@ -125,21 +127,28 @@ class Hermes:
       for friend in self.fb.users.getInfo(self.fb.friends.get(), attrs):
         key = unicode(friend['name']).encode('trans')
         self.friends[key] = friend
+        self.friends_by_url[friend['profile_url']] = friend
         friend['pic']  = friend[attrs[2]]
+        friend['account'] = 'facebook'
         if friend['website']:
           friend['homepage'] = friend['website']
 
         if 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 friend in api.GetFriends():
-        self.friends[friend.name] = {'name': unicode(friend.name).encode('trans'), 'pic': friend.profile_image_url, 'birthday_date': None, 'twitter_url': 'http://twitter.com/%s' % (friend.screen_name), 'homepage' : friend.url}
+        key = unicode(friend.name).encode('trans')
+        url = 'http://twitter.com/%s' % (friend.screen_name)
+        self.friends[key] = {'name': friend.name, 'pic': friend.profile_image_url, 'birthday_date': None, 'twitter_url': url, 'homepage': friend.url, 'account': 'twitter'}
+        self.friends_by_url[url] = self.friends[key]
   
     # TODO What if the user has *no* contacts?
 
@@ -152,14 +161,14 @@ class Hermes:
     # -- Find addresses...
     #
     print "+++ Syncing contacts..."
-    addresses = evolution.ebook.open_addressbook('default')
+    self.addresses = evolution.ebook.open_addressbook('default')
     print "+++ Addressbook opened..."
-    self.store = ContactStore(addresses)
+    self.store = ContactStore(self.addresses)
     print "+++ Contact store created..."
     self.updated = []
     self.unmatched = []
     self.matched = []
-    contacts = addresses.get_all_contacts()
+    contacts = self.addresses.get_all_contacts()
     contacts.sort(key=lambda obj: obj.get_name())
     current = 0
     maximum = len(contacts)
@@ -167,17 +176,30 @@ class Hermes:
       current += 1
       self.callback.progress(current, maximum)
       found = False
-      for name in names.variants(contact.get_name()):
-        if name in self.friends:
-          updated = self.update_contact(contact, self.friends[name], resync)
+      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:
-            self.updated.append(contact)
-            addresses.commit_contact(contact)
-            print "Saved changes to [%s]" % (contact.get_name())
-            
-          break
+            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)
@@ -199,9 +221,9 @@ class Hermes:
 
         self.update_contact(contact, friend)
    
-        addresses.add_contact(contact)
+        self.addresses.add_contact(contact)
         self.updated.append(contact)
-        addresses.commit_contact(contact)
+        self.addresses.commit_contact(contact)
 
         print "Created [%s]" % (contact.get_name())
         self.matched.append(contact)
diff --git a/package/src/mapcontact.py b/package/src/mapcontact.py
new file mode 100644 (file)
index 0000000..a23718b
--- /dev/null
@@ -0,0 +1,77 @@
+import gtk
+import hildon
+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.
+       
+     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']))
+      
+      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."""
+
+    (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 6986ed7..6e887d8 100644 (file)
@@ -4,10 +4,6 @@
 #                                       http://www.bleb.org/
 
 import gtk, gobject
-import gnome.gconf
-import hildon, osso
-import traceback
-import time
 import re
 import thread
 import os.path
@@ -25,12 +21,18 @@ try:
   _have_osso = True
 except ImportError:
   _have_osso = False
+  
+try:
+  import gnome.gconf
+  _have_gconf = True
+except ImportError:
+  _have_gconf = False
 
 gobject.threads_init()
 
 # -- Main class...
 #
-class Wimpworks:
+class WimpWorks:
   '''A framework for creating easy-to-use graphical user interfaces using
      GTK+, Python, DBus and more.
      
@@ -50,20 +52,23 @@ class Wimpworks:
        @param dbus_name Name to register with DBus. If unspecified, no
               DBus registration will be performed.'''
     
-    self.gconf = gnome.gconf.client_get_default()
     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()
-      
-    gtk.set_application_name(application)
-    self.window.connect("delete-event", gtk.main_quit)
+
+    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)
@@ -72,7 +77,7 @@ class Wimpworks:
       self.app.add_window(self.main_window)
       
     if _have_hildon:
-      self._expose_hid = self.window.connect('expose-event', self._take_screenshot)
+      self._expose_hid = self.main_window.connect('expose-event', self._take_screenshot)
 
       
   # -----------------------------------------------------------------------
@@ -85,7 +90,8 @@ class Wimpworks:
        @param window Window to set background of. If unset, will default to the
                      main application window.'''
     
-    file = "/opt/%s/share/%s" % (self.name, file) # TODO Handle other forms of path
+    # 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
       
@@ -101,7 +107,7 @@ class Wimpworks:
        called.'''
 
     if not window:
-      window = self.main_Window
+      window = self.main_window
       
     if not self.menu:
       if _have_hildon:
@@ -121,7 +127,7 @@ class Wimpworks:
     '''Once the application has been initialised, this will show the main window
        and run the mainloop.'''
        
-    self.window.show_all()
+    self.main_window.show_all()
     gtk.main()
 
 
@@ -131,7 +137,7 @@ class Wimpworks:
     
        @see http://maemo.org/api_refs/5.0/5.0-final/hildon/hildon-Additions-to-GTK+.html#hildon-gtk-window-take-screenshot'''
     
-    self.window.disconnect(self._expose_hid)
+    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)
 
@@ -146,7 +152,7 @@ class Wimpworks:
               called 'do_method'.'''
 
     method = re.sub('[^a-z0-9_]', '', method.lower())
-    self.attr("do_%s" % (method))(event.window)
+    getattr(self, "do_%s" % (method))(event.window)
 
     
   # -----------------------------------------------------------------------
@@ -243,12 +249,11 @@ class HildonMainScreenLayout():
          @param offset The vertical offset for the buttons. If unspecified,
                 they will be centred. Ranges from 0.0 (top) to 1.0 (bottom).'''
                 
-      print self
       self._container = container
       alignment = gtk.Alignment(xalign=0.5, yalign=0.8, xscale=0.8)
       self._box = gtk.HButtonBox()
       alignment.add(self._box)
-      container.add(alignment)
+      container.main_window.add(alignment)
       self._box.set_property('layout-style', gtk.BUTTONBOX_SPREAD)
 
       
@@ -261,12 +266,13 @@ class HildonMainScreenLayout():
          @param title Value of the button, and used to derive the callback method.
          @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(title)
+      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)
 
-    button.set_property('width-request', 250)
-    button.connect('clicked', self.wimpworks.callback, title)
-    self._box.add(button)