Very minor cleanups
[gc-dialer] / src / dialcentral / gc_dialer.py
index a47a467..fc8c85a 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/python2.5
 
-# GC Dialer - Front end for Google's Grand Central service.
+# DialCentral - Front end for Google's Grand Central service.
 # Copyright (C) 2008  Mark Bergman bergman AT merctech DOT com
 # 
 # This library is free software; you can redistribute it and/or
@@ -19,7 +19,7 @@
 
 
 """
-Grandcentral Dialer
+DialCentral: A phone dialer using GrandCentral
 """
 
 import sys
@@ -38,13 +38,6 @@ try:
 except ImportError:
        hildon = None
 
-import socket
-
-
-gtk.gdk.threads_init()
-#This changes the default, system wide, socket timeout so that a hung server will not completly
-#hork the application
-socket.setdefaulttimeout(5)
 
 
 def make_ugly(prettynumber):
@@ -123,7 +116,7 @@ def make_idler(func):
                except StopIteration:
                        del a[:]
                        return False
-       
+
        decorated_func.__name__ = func.__name__
        decorated_func.__doc__ = func.__doc__
        decorated_func.__dict__.update(func.__dict__)
@@ -141,12 +134,12 @@ class DummyAddressBook(object):
                @returns Iterable of (Address Book Factory, Book Id, Book Name)
                """
                yield self, "", "None"
-       
+
        def open_addressbook(self, bookId):
                return self
 
        @staticmethod
-       def factory_short_name():
+       def contact_source_short_name(contactId):
                return ""
 
        @staticmethod
@@ -168,6 +161,65 @@ class DummyAddressBook(object):
                return []
 
 
+class MergedAddressBook(object):
+       """
+       Merger of all addressbooks
+       """
+
+       def __init__(self, addressbooks, sorter = None):
+               self.__addressbooks = addressbooks
+               self.__sort_contacts = sorter if sorter is not None else self.null_sorter
+
+       def get_addressbooks(self):
+               """
+               @returns Iterable of (Address Book Factory, Book Id, Book Name)
+               """
+               yield self, "", ""
+
+       def open_addressbook(self, bookId):
+               return self
+
+       def contact_source_short_name(self, contactId):
+               bookIndex, originalId = contactId.split("-", 1)
+               return self.__addressbooks[int(bookIndex)].contact_source_short_name(originalId)
+
+       @staticmethod
+       def factory_name():
+               return "All Contacts"
+
+       def get_contacts(self):
+               """
+               @returns Iterable of (contact id, contact name)
+               """
+               contacts = (
+                       ("-".join([str(bookIndex), contactId]), contactName)
+                               for (bookIndex, addressbook) in enumerate(self.__addressbooks)
+                                       for (contactId, contactName) in addressbook.get_contacts()
+               )
+               sortedContacts = self.__sort_contacts(contacts)
+               return sortedContacts
+
+       def get_contact_details(self, contactId):
+               """
+               @returns Iterable of (Phone Type, Phone Number)
+               """
+               bookIndex, originalId = contactId.split("-", 1)
+               return self.__addressbooks[int(bookIndex)].get_contact_details(originalId)
+
+       @staticmethod
+       def null_sorter(contacts):
+               return contacts
+
+       @staticmethod
+       def basic_lastname_sorter(contacts):
+               contactsWithKey = [
+                       (contactName.rsplit(" ", 1)[-1], (contactId, contactName))
+                               for (contactId, contactName) in contacts
+               ]
+               contactsWithKey.sort()
+               return (contactData for (lastName, contactData) in contactsWithKey)
+
+
 class PhoneTypeSelector(object):
 
        def __init__(self, widgetTree, gcBackend):
@@ -216,7 +268,7 @@ class PhoneTypeSelector(object):
                self._typeviewselection.unselect_all()
                self._dialog.hide()
                return phoneNumber
-       
+
        def _on_phonetype_select(self, *args):
                self._dialog.response(gtk.RESPONSE_OK)
 
@@ -232,9 +284,9 @@ class Dialpad(object):
        __app_magic__ = 0xdeadbeef
 
        _glade_files = [
-               './gc_dialer.glade',
-               '../lib/gc_dialer.glade',
                '/usr/lib/dialcentral/gc_dialer.glade',
+               os.path.join(os.path.dirname(__file__), "gc_dialer.glade"),
+               os.path.join(os.path.dirname(__file__), "../lib/gc_dialer.glade"),
        ]
 
        def __init__(self):
@@ -256,7 +308,7 @@ class Dialpad(object):
                self._contactsviewselection = None
 
                self._clearall_id = None
-               
+
                for path in Dialpad._glade_files:
                        if os.path.isfile(path):
                                self._widgetTree = gtk.glade.XML(path)
@@ -287,6 +339,8 @@ class Dialpad(object):
                        self._widgetTree.get_widget("callbackcombo").get_child().set_property('hildon-input-mode', (1 << 4))
                        self._widgetTree.get_widget("usernameentry").set_property('hildon-input-mode', 7)
                        self._widgetTree.get_widget("passwordentry").set_property('hildon-input-mode', 7|(1 << 29))
+                       hildon.hildon_helper_set_thumb_scrollbar(self._widgetTree.get_widget('contacts_scrolledwindow'), True)
+                       hildon.hildon_helper_set_thumb_scrollbar(self._widgetTree.get_widget('recent_scrolledwindow'), True)
 
                        gtkMenu = self._widgetTree.get_widget("dialpad_menubar")
                        menu = gtk.Menu()
@@ -345,11 +399,13 @@ class Dialpad(object):
                """
                If something can be done after the UI loads, push it here so it's not blocking the UI
                """
-               
-               from gc_backend import GCDialer
-               from evo_backend import EvolutionAddressBook
 
-               self._gcBackend = GCDialer()
+               import gc_backend
+               import evo_backend
+               import gmail_backend
+               import maemo_backend
+
+               self._gcBackend = gc_backend.GCDialer()
 
                try:
                        import osso
@@ -379,21 +435,24 @@ class Dialpad(object):
                        warnings.warn("No Internet Connectivity API ", UserWarning, 2)
 
 
-               self._addressBookFactories = [
+               addressBooks = [
                        self._gcBackend,
+                       evo_backend.EvolutionAddressBook(),
                        DummyAddressBook(),
-                       EvolutionAddressBook(),
                ]
+               mergedBook = MergedAddressBook(addressBooks, MergedAddressBook.basic_lastname_sorter)
+               self._addressBookFactories = list(addressBooks)
+               self._addressBookFactories.insert(0, mergedBook)
                self._addressBook = None
                self.open_addressbook(*self.get_addressbooks().next()[0][0:2])
-       
+
                gtk.gdk.threads_enter()
                self._booksList = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
                gtk.gdk.threads_leave()
 
                for (factoryId, bookId), (factoryName, bookName) in self.get_addressbooks():
                        if factoryName and bookName:
-                               entryName = "%s: %s" % (factoryName, bookName) 
+                               entryName = "%s: %s" % (factoryName, bookName)
                        elif factoryName:
                                entryName = factoryName
                        elif bookName:
@@ -455,6 +514,13 @@ class Dialpad(object):
                # Add the column to the treeview
                column = gtk.TreeViewColumn("Contact")
 
+               #displayContactSource = False
+               displayContactSource = True
+               if displayContactSource:
+                       textrenderer = gtk.CellRendererText()
+                       column.pack_start(textrenderer, expand=False)
+                       column.add_attribute(textrenderer, 'text', 0)
+
                textrenderer = gtk.CellRendererText()
                column.pack_start(textrenderer, expand=True)
                column.add_attribute(textrenderer, 'text', 1)
@@ -499,6 +565,7 @@ class Dialpad(object):
                combobox.get_child().set_text(make_pretty(self._gcBackend.get_callback_number()))
 
        def _idly_populate_recentview(self):
+               self._recenttime = time.time()
                self._recentmodel.clear()
 
                for personsName, phoneNumber, date, action in self._gcBackend.get_recent():
@@ -508,11 +575,11 @@ class Dialpad(object):
                        self._recentmodel.append(item)
                        gtk.gdk.threads_leave()
 
-               self._recenttime = time.time()
                return False
 
-       @make_idler
        def _idly_populate_contactsview(self):
+               #@todo Add a lock so only one code path can be in here at a time
+               self._contactstime = time.time()
                self._contactsmodel.clear()
 
                # completely disable updating the treeview while we populate the data
@@ -520,16 +587,15 @@ class Dialpad(object):
                contactsview.freeze_child_notify()
                contactsview.set_model(None)
 
-               contactType = (self._addressBook.factory_short_name(),)
-               for contactId, contactName in self._addressBook.get_contacts():
+               addressBook = self._addressBook
+               for contactId, contactName in addressBook.get_contacts():
+                       contactType = (addressBook.contact_source_short_name(contactId),)
                        self._contactsmodel.append(contactType + (contactName, "", contactId) + ("",))
-                       yield
 
                # restart the treeview data rendering
                contactsview.set_model(self._contactsmodel)
                contactsview.thaw_child_notify()
-
-               self._contactstime = time.time()
+               return False
 
        def attempt_login(self, numOfAttempts = 1):
                """
@@ -543,7 +609,7 @@ class Dialpad(object):
 
                if self._gcBackend.is_authed():
                        return True
-               
+
                for x in xrange(numOfAttempts):
                        gtk.gdk.threads_enter()
 
@@ -567,6 +633,8 @@ class Dialpad(object):
                                gtk.gdk.threads_leave()
                                return True
 
+               return False
+
        def display_error_message(self, msg):
                error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
 
@@ -583,11 +651,11 @@ class Dialpad(object):
                for i, factory in enumerate(self._addressBookFactories):
                        for bookFactory, bookId, bookName in factory.get_addressbooks():
                                yield (i, bookId), (factory.factory_name(), bookName)
-       
+
        def open_addressbook(self, bookFactoryId, bookId):
                self._addressBook = self._addressBookFactories[bookFactoryId].open_addressbook(bookId)
                self._contactstime = 0
-               gobject.idle_add(self._idly_populate_contactsview)
+               threading.Thread(target=self._idly_populate_contactsview).start()
 
        def set_number(self, number):
                """
@@ -645,7 +713,7 @@ class Dialpad(object):
                        self._isFullScreen = True
                else:
                        self._isFullScreen = False
-       
+
        def _on_key_press(self, widget, event, *args):
                """
                @note Hildon specific
@@ -727,12 +795,15 @@ class Dialpad(object):
                self._contactsviewselection.unselect_all()
 
        def _on_notebook_switch_page(self, notebook, page, page_num):
-               if page_num == 1 and 300 < (time.time() - self._contactstime):
-                       threading.Thread(target=self._idly_populate_contactsview).start()
-               elif page_num == 2 and 300 < (time.time() - self._recenttime):
-                       threading.Thread(target=self._idly_populate_recentview).start()
-               #elif page_num == 3 and self._callbackNeedsSetup:
-               #       gobject.idle_add(self._idly_populate_callback_combo)
+               if page_num == 1:
+                       if 300 < (time.time() - self._contactstime):
+                               threading.Thread(target=self._idly_populate_contactsview).start()
+               elif page_num == 3:
+                       if 300 < (time.time() - self._recenttime):
+                               threading.Thread(target=self._idly_populate_recentview).start()
+               #elif page_num == 2:
+               #       self._callbackNeedsSetup::
+               #               gobject.idle_add(self._idly_populate_callback_combo)
 
                tabTitle = self._notebook.get_tab_label(self._notebook.get_nth_page(page_num)).get_text()
                if hildon is not None:
@@ -777,7 +848,7 @@ class Dialpad(object):
                self.set_number("")
 
        def _on_digit_clicked(self, widget):
-               self.set_number(self._phonenumber + widget.get_name()[5])
+               self.set_number(self._phonenumber + widget.get_name()[-1])
 
        def _on_backspace(self, widget):
                self.set_number(self._phonenumber[:-1])
@@ -804,7 +875,7 @@ class Dialpad(object):
                dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <edpage@byu.net>"])
                dlg.run()
                dlg.destroy()
-       
+
 
 def run_doctest():
        import doctest