--- /dev/null
+<?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>
--- /dev/null
+<?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>
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
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
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])
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()
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
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])
-import trans
-
"""Utilities for determing name variants.
Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
__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):
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
# -------------------------------------------------------------------------
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
# -- 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())
# ---------------------------------------------------------------------
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)