Very minor cleanups
[gc-dialer] / src / dialcentral / gc_dialer.py
index a47a467..fc8c85a 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/python2.5
 
 #!/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
 # 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
 """
 
 import sys
@@ -38,13 +38,6 @@ try:
 except ImportError:
        hildon = None
 
 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):
 
 
 def make_ugly(prettynumber):
@@ -123,7 +116,7 @@ def make_idler(func):
                except StopIteration:
                        del a[:]
                        return False
                except StopIteration:
                        del a[:]
                        return False
-       
+
        decorated_func.__name__ = func.__name__
        decorated_func.__doc__ = func.__doc__
        decorated_func.__dict__.update(func.__dict__)
        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"
                @returns Iterable of (Address Book Factory, Book Id, Book Name)
                """
                yield self, "", "None"
-       
+
        def open_addressbook(self, bookId):
                return self
 
        @staticmethod
        def open_addressbook(self, bookId):
                return self
 
        @staticmethod
-       def factory_short_name():
+       def contact_source_short_name(contactId):
                return ""
 
        @staticmethod
                return ""
 
        @staticmethod
@@ -168,6 +161,65 @@ class DummyAddressBook(object):
                return []
 
 
                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):
 class PhoneTypeSelector(object):
 
        def __init__(self, widgetTree, gcBackend):
@@ -216,7 +268,7 @@ class PhoneTypeSelector(object):
                self._typeviewselection.unselect_all()
                self._dialog.hide()
                return phoneNumber
                self._typeviewselection.unselect_all()
                self._dialog.hide()
                return phoneNumber
-       
+
        def _on_phonetype_select(self, *args):
                self._dialog.response(gtk.RESPONSE_OK)
 
        def _on_phonetype_select(self, *args):
                self._dialog.response(gtk.RESPONSE_OK)
 
@@ -232,9 +284,9 @@ class Dialpad(object):
        __app_magic__ = 0xdeadbeef
 
        _glade_files = [
        __app_magic__ = 0xdeadbeef
 
        _glade_files = [
-               './gc_dialer.glade',
-               '../lib/gc_dialer.glade',
                '/usr/lib/dialcentral/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):
        ]
 
        def __init__(self):
@@ -256,7 +308,7 @@ class Dialpad(object):
                self._contactsviewselection = None
 
                self._clearall_id = None
                self._contactsviewselection = None
 
                self._clearall_id = None
-               
+
                for path in Dialpad._glade_files:
                        if os.path.isfile(path):
                                self._widgetTree = gtk.glade.XML(path)
                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))
                        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()
 
                        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
                """
                """
                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
 
                try:
                        import osso
@@ -379,21 +435,24 @@ class Dialpad(object):
                        warnings.warn("No Internet Connectivity API ", UserWarning, 2)
 
 
                        warnings.warn("No Internet Connectivity API ", UserWarning, 2)
 
 
-               self._addressBookFactories = [
+               addressBooks = [
                        self._gcBackend,
                        self._gcBackend,
+                       evo_backend.EvolutionAddressBook(),
                        DummyAddressBook(),
                        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])
                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:
                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:
                        elif factoryName:
                                entryName = factoryName
                        elif bookName:
@@ -455,6 +514,13 @@ class Dialpad(object):
                # Add the column to the treeview
                column = gtk.TreeViewColumn("Contact")
 
                # 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)
                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):
                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():
                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._recentmodel.append(item)
                        gtk.gdk.threads_leave()
 
-               self._recenttime = time.time()
                return False
 
                return False
 
-       @make_idler
        def _idly_populate_contactsview(self):
        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
                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)
 
                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) + ("",))
                        self._contactsmodel.append(contactType + (contactName, "", contactId) + ("",))
-                       yield
 
                # restart the treeview data rendering
                contactsview.set_model(self._contactsmodel)
                contactsview.thaw_child_notify()
 
                # 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):
                """
 
        def attempt_login(self, numOfAttempts = 1):
                """
@@ -543,7 +609,7 @@ class Dialpad(object):
 
                if self._gcBackend.is_authed():
                        return True
 
                if self._gcBackend.is_authed():
                        return True
-               
+
                for x in xrange(numOfAttempts):
                        gtk.gdk.threads_enter()
 
                for x in xrange(numOfAttempts):
                        gtk.gdk.threads_enter()
 
@@ -567,6 +633,8 @@ class Dialpad(object):
                                gtk.gdk.threads_leave()
                                return True
 
                                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)
 
        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)
                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
        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):
                """
 
        def set_number(self, number):
                """
@@ -645,7 +713,7 @@ class Dialpad(object):
                        self._isFullScreen = True
                else:
                        self._isFullScreen = False
                        self._isFullScreen = True
                else:
                        self._isFullScreen = False
-       
+
        def _on_key_press(self, widget, event, *args):
                """
                @note Hildon specific
        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):
                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:
 
                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("")
 
        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])
 
        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()
                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
 
 def run_doctest():
        import doctest