Some minor re-arranging and renaming
[gc-dialer] / gc_dialer / gc_dialer.py
index 1c5589e..8a25e9c 100755 (executable)
-#!/usr/bin/python 
+#!/usr/bin/python2.5
+
+
+"""
+Grandcentral Dialer
+Python front-end to a wget script to use grandcentral.com to place outbound VOIP calls.
+(C) 2008 Mark Bergman
+bergman@merctech.com
+"""
 
-# Grandcentral Dialer
-# Python front-end to a wget script to use grandcentral.com to place outbound VOIP calls.
-# (C) 2008 Mark Bergman
-# bergman@merctech.com
 
 import sys
+import gc
 import os
+import threading
 import time
 import re
-try:
-       import pygtk
-       pygtk.require("2.0")
-except:
-       pass
-try:
-       import gtk
-       import gtk.glade
-except:
-       sys.exit(1)
+import warnings
 
-histfile=os.path.expanduser("~")
-histfile=os.path.join(histfile,".gcdialerhist")        # Use the native OS file separator
-liststore = gtk.ListStore(str)
+import gobject
+import gtk
+import gtk.glade
 
-class GCDialer:
-       _wgetOKstrRe    = re.compile("This may take a few seconds", re.M)       # string from Grandcentral.com on successful dial 
-       _validateRe     = re.compile("^[0-9]{7,}$")
+try:
+       import hildon
+except ImportError:
+       hildon = None
 
-       _wgetoutput     = "/tmp/gc_dialer.output"       # results from wget command
-       _cookiefile     = os.path.join(os.path.expanduser("~"),".mozilla/microb/cookies.txt")   # file with browser cookies
-       _wgetcmd        = "wget -nv -O %s --load-cookie=\"%s\" --referer=http://www.grandcentral.com/mobile/messages http://www.grandcentral.com/mobile/calls/click_to_call?destno=%s"
+try:
+       import osso
+       try:
+               import abook
+               import evolution.ebook as evobook
+       except ImportError:
+               abook = None
+               evobook = None
+except ImportError:
+       osso = None
 
-       def __init__(self):
-               self._msg = ""
-               if ( os.path.isfile(GCDialer._cookiefile) == False ) :
-                       self._msg = 'Error: Failed to locate a file with saved browser cookies at \"' + cookiefile + '\".\n\tPlease use the web browser on your tablet to connect to www.grandcentral.com and then re-run Grandcentral Dialer.'
+try:
+       import conic
+except ImportError:
+       conic = None
 
-       def validate(self,number):
-               return GCDialer._validateRe.match(number) != None
+try:
+       import doctest
+       import optparse
+except ImportError:
+       doctest = None
+       optparse = None
 
-       def dial(self,number):
-               self._msg = ""
-               if self.validate(number) == False:
-                       self._msg = "Invalid number format %s" % (number)
-                       return False
+from gcbackend import GCDialer
 
-               # Remove any existing output file...
-               if os.path.isfile(GCDialer._wgetoutput) :
-                       os.unlink(GCDialer._wgetoutput)
-               child_stdout, child_stdin, child_stderr = os.popen3(GCDialer._wgetcmd % (GCDialer._wgetoutput, GCDialer._cookiefile, number))
-               stderr=child_stderr.read()
+import socket
 
-               child_stdout.close()
-               child_stderr.close()
-               child_stdin.close()
 
-               try:
-                       wgetresults = open(GCDialer._wgetoutput, 'r' )
-               except IOError:
-                       self._msg = 'IOError: No /tmp/gc_dialer.output file...dial attempt failed\n\tThis probably means that there is no active internet connection, or that\nthe site www.grandcentral.com is inacessible.'
-                       return False
-               
-               data = wgetresults.read()
-               wgetresults.close()
+socket.setdefaulttimeout(5)
 
-               if GCDialer._wgetOKstrRe.search(data) != None:
-                       return True
-               else:
-                       self._msg = 'Error: Failed to login to www.grandcentral.com.\n\tThis probably means that there is no saved cookie for that site.\n\tPlease use the web browser on your tablet to connect to www.grandcentral.com and then re-run Grandcentral Dialer.'
-                       return False
-
-
-def load_history_list(histfile,liststore):
-       # read the history list, load it into the liststore variable for later
-       # assignment to the combobox menu
-
-       # clear out existing entries
-       dialhist = []
-       liststore.clear()
-       if os.path.isfile(histfile) :
-               histFH = open(histfile,"r")
-               for line in histFH.readlines() :
-                       fields = line.split()   # split the input lines on whitespace
-                       number=fields[0]                #...save only the first field (the phone number)
-                       search4num=re.compile('^' + number + '$')
-                       newnumber=True  # set a flag that the current phone number is not on the history menu
-                       for num in dialhist :
-                               if re.match(search4num,num):
-                                       # the number is already in the drop-down menu list...set the
-                                       # flag and bail out
-                                       newnumber = False
-                                       break
-                       if newnumber == True :
-                               dialhist.append(number) # append the number to the history list
-       
-               histlen=len(dialhist)
-               if histlen > 10 :
-                       dialhist=dialhist[histlen - 10:histlen]         # keep only the last 10 entries
-               dialhist.reverse()      # reverse the list, so that the most recent entry is now first
-       
-               # Now, load the liststore with the entries, for later assignment to the Gtk.combobox menu
-               for entry in dialhist :
-                       entry=makepretty(entry)
-                       liststore.append([entry])
-       # else :
-       #        print "The history file " + histfile + " does not exist"
 
 def makeugly(prettynumber):
-       # function to take a phone number and strip out all non-numeric
-       # characters
-       uglynumber=re.sub('\D','',prettynumber)
+       """
+       function to take a phone number and strip out all non-numeric
+       characters
+
+       >>> makeugly("+012-(345)-678-90")
+       '01234567890'
+       """
+       uglynumber = re.sub('\D', '', prettynumber)
        return uglynumber
 
+
 def makepretty(phonenumber):
-       # Function to take a phone number and return the pretty version
-       # pretty numbers:
-       #       if phonenumber begins with 0:
-       #               ...-(...)-...-....
-       #       else
-       #               if phonenumber is 13 digits:
-       #                       (...)-...-....
-       #               else if phonenumber is 10 digits:
-       #                       ...-....
-       if len(phonenumber) < 3 :
+       """
+       Function to take a phone number and return the pretty version
+        pretty numbers:
+               if phonenumber begins with 0:
+                       ...-(...)-...-....
+               if phonenumber begins with 1: ( for gizmo callback numbers )
+                       1 (...)-...-....
+               if phonenumber is 13 digits:
+                       (...)-...-....
+               if phonenumber is 10 digits:
+                       ...-....
+       >>> makepretty("12")
+       '12'
+       >>> makepretty("1234567")
+       '123-4567'
+       >>> makepretty("2345678901")
+       '(234)-567-8901'
+       >>> makepretty("12345678901")    
+       '1 (234)-567-8901'
+       >>> makepretty("01234567890")
+       '+012-(345)-678-90'
+       """
+       if phonenumber is None:
+               return ""
+
+       if len(phonenumber) < 3:
                return phonenumber
 
-       if  phonenumber[0] == "0" :
-                       if len(phonenumber) <=3:
-                               prettynumber = "+" + phonenumber[0:3] 
-                       elif len(phonenumber) <=6:
-                               prettynumber = "+" + phonenumber[0:3] + "-(" + phonenumber[3:6] + ")"
-                       elif len(phonenumber) <=9:
-                               prettynumber = "+" + phonenumber[0:3] + "-(" + phonenumber[3:6] + ")-" + phonenumber[6:9]
-                       else:
-                               prettynumber = "+" + phonenumber[0:3] + "-(" + phonenumber[3:6] + ")-" + phonenumber[6:9] + "-" + phonenumber[9:]
-                       return prettynumber
-       elif len(phonenumber) <= 7 :
-                       prettynumber = phonenumber[0:3] + "-" + phonenumber[3:] 
-       elif len(phonenumber) > 7 :
-                       prettynumber = "(" + phonenumber[0:3] + ")-" + phonenumber[3:6] + "-" + phonenumber[6:]
+       if phonenumber[0] == "0":
+               prettynumber = ""
+               prettynumber += "+%s" % phonenumber[0:3]
+               if 3 < len(phonenumber):
+                       prettynumber += "-(%s)" % phonenumber[3:6]
+                       if 6 < len(phonenumber):
+                               prettynumber += "-%s" % phonenumber[6:9]
+                               if 9 < len(phonenumber):
+                                       prettynumber += "-%s" % phonenumber[9:]
+               return prettynumber
+       elif len(phonenumber) <= 7:
+               prettynumber = "%s-%s" % (phonenumber[0:3], phonenumber[3:])
+       elif len(phonenumber) > 8 and phonenumber[0] == "1":
+               prettynumber = "1 (%s)-%s-%s" %(phonenumber[1:4], phonenumber[4:7], phonenumber[7:]) 
+       elif len(phonenumber) > 7:
+               prettynumber = "(%s)-%s-%s" % (phonenumber[0:3], phonenumber[3:6], phonenumber[6:])
        return prettynumber
 
-class Dialpad:
 
-       phonenumber = ""
+class Dialpad(object):
+
+       __app_name__ = "gc_dialer"
+       __version__ = "0.7.0"
+       __app_magic__ = 0xdeadbeef
+
+       _glade_files = [
+               './gc_dialer.glade',
+               '../lib/gc_dialer.glade',
+               '/usr/local/lib/gc_dialer.glade',
+       ]
 
        def __init__(self):
-               if os.path.isfile("/usr/local/lib/gc_dialer.glade") :
-                       self.gladefile = "/usr/local/lib/gc_dialer.glade"  
-               elif os.path.isfile("./gc_dialer.glade") :
-                       self.gladefile = "./gc_dialer.glade"
+               self.phonenumber = ""
+               self.prettynumber = ""
+               self.areacode = "518"
+               self.clipboard = gtk.clipboard_get()
+               self.recentmodel = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
+               self.recentviewselection = None
+               self.callbackNeedsSetup = True
+               self.recenttime = 0.0
+
+               for path in Dialpad._glade_files:
+                       if os.path.isfile(path):
+                               self.wTree = gtk.glade.XML(path)
+                               break
+               else:
+                       self.ErrPopUp("Cannot find gc_dialer.glade")
+                       gtk.main_quit()
+                       return
 
-               self.gcd = GCDialer()
-               if self.gcd._msg != "":
-                       self.ErrPopUp(self.gcd._msg)
-                       sys.exit(1)
+               self.wTree.get_widget("about_title").set_label(self.wTree.get_widget("about_title").get_label()+"\nVersion "+Dialpad.__version__)
 
-               self.wTree = gtk.glade.XML(self.gladefile)
-               self.window = self.wTree.get_widget("Dialpad")
-               if (self.window):
-                       self.window.connect("destroy", gtk.main_quit)
                #Get the buffer associated with the number display
                self.numberdisplay = self.wTree.get_widget("numberdisplay")
-               self.dialer_history = self.wTree.get_widget("dialer_history")
+               self.setNumber("")
+               self.notebook = self.wTree.get_widget("notebook")
 
-               # Load the liststore array with the numbers from the history file
-               load_history_list(histfile,liststore)
-               # load the dropdown menu with the numbers from the dial history
-               self.dialer_history.set_model(liststore)
-               cell = gtk.CellRendererText()
-               self.dialer_history.pack_start(cell, True)
-               self.dialer_history.set_active(-1)
-               self.dialer_history.set_attributes(cell, text=0)
+               self.window = self.wTree.get_widget("Dialpad")
+
+               global hildon
+               self.app = None
+               if hildon is not None and isinstance(self.window, gtk.Window):
+                       warnings.warn("Hildon installed but glade file not updated to work with hildon", UserWarning, 2)
+                       hildon = None
+               elif hildon is not None:
+                       self.app = hildon.Program()
+                       self.window.set_title("Keypad")
+                       self.app.add_window(self.window)
+                       self.wTree.get_widget("callbackcombo").get_child().set_property('hildon-input-mode', (1 << 4))
+                       self.wTree.get_widget("usernameentry").set_property('hildon-input-mode', 7)
+                       self.wTree.get_widget("passwordentry").set_property('hildon-input-mode', 7|(1 << 29))
+               else:
+                       warnings.warn("No Hildon", UserWarning, 2)
+
+               self.osso = None
+               self.ebook = None
+               if osso is not None:
+                       self.osso = osso.Context(__name__, Dialpad.__version__, False)
+                       device = osso.DeviceState(self.osso)
+                       device.set_device_state_callback(self.on_device_state_change, 0)
+                       if abook is not None and evobook is not None:
+                               abook.init_with_name(__name__, self.osso)
+                               self.ebook = evo.open_addressbook("default")
+                       else:
+                               warnings.warn("No abook and No evolution address book support", UserWarning, 2)
+               else:
+                       warnings.warn("No OSSO", UserWarning, 2)
 
-               self.about_dialog = None
-               self.error_dialog = None
+               self.connection = None
+               if conic is not None:
+                       self.connection = conic.Connection()
+                       self.connection.connect("connection-event", on_connection_change, Dialpad.__app_magic__)
+                       self.connection.request_connection(conic.CONNECT_FLAG_NONE)
 
-               dic = {
-                       # Routine for processing signal from the combobox (ie., when the
-                       # user selects an entry from the dropdown history
-                       "on_dialer_history_changed" : self.on_dialer_history_changed,
+               if self.window:
+                       self.window.connect("destroy", gtk.main_quit)
+                       self.window.show_all()
 
+               callbackMapping = {
                        # Process signals from buttons
-                       "on_number_clicked"  : self.on_number_clicked,
-                       "on_Clear_clicked"   : self.on_Clear_clicked,
-                       "on_Dial_clicked"    : self.on_Dial_clicked,
-                       "on_Backspace_clicked" : self.Backspace,
-                       "on_Cancel_clicked"  : self.on_Cancel_clicked,
-                       "on_About_clicked"   : self.on_About_clicked}
-               self.wTree.signal_autoconnect(dic)
-
-       def ErrPopUp(self,msg):
-               error_dialog = gtk.MessageDialog(None,0,gtk.MESSAGE_ERROR,gtk.BUTTONS_CLOSE,msg)
+                       "on_digit_clicked"  : self.on_digit_clicked,
+                       "on_dial_clicked"    : self.on_dial_clicked,
+                       "on_loginbutton_clicked" : self.on_loginbutton_clicked,
+                       "on_loginclose_clicked" : self.on_loginclose_clicked,
+                       "on_clearcookies_clicked" : self.on_clearcookies_clicked,
+                       "on_notebook_switch_page" : self.on_notebook_switch_page,
+                       "on_recentview_row_activated" : self.on_recentview_row_activated,
+                       "on_back_clicked" : self.on_backspace
+               }
+               self.wTree.signal_autoconnect(callbackMapping)
+               self.wTree.get_widget("callbackcombo").get_child().connect("changed", self.on_callbackentry_changed)
+
+               # Defer initalization of recent view
+               self.gcd = GCDialer()
+
+               self.attemptLogin(2)
+               gobject.idle_add(self._init_grandcentral)
+               gobject.idle_add(self._init_recent_view)
+
+       def _init_grandcentral(self):
+               """ deferred initalization of the grandcentral info """
+               
+               try:
+                       if self.gcd.isAuthed():
+                               if self.gcd.getCallbackNumber() is None:
+                                       self.gcd.setSaneCallback()
+               except:
+                       pass
+               
+               self.setAccountNumber()
+               print "exit init_gc"
+               return False
+
+       def _init_recent_view(self):
+               """ deferred initalization of the recent view treeview """
+
+               recentview = self.wTree.get_widget("recentview")
+               recentview.set_model(self.recentmodel)
+               textrenderer = gtk.CellRendererText()
+
+               # Add the column to the treeview
+               column = gtk.TreeViewColumn("Calls", textrenderer, text=1)
+               column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
+
+               recentview.append_column(column)
+
+               self.recentviewselection = recentview.get_selection()
+               self.recentviewselection.set_mode(gtk.SELECTION_SINGLE)
+
+               return False
+
+       def _setupCallbackCombo(self):
+               combobox = self.wTree.get_widget("callbackcombo")
+               self.callbacklist = gtk.ListStore(gobject.TYPE_STRING)
+               combobox.set_model(self.callbacklist)
+               combobox.set_text_column(0)
+               for number, description in self.gcd.getCallbackNumbers().iteritems():
+                       self.callbacklist.append([makepretty(number)] )
+
+               self.wTree.get_widget("callbackcombo").get_child().set_text(makepretty(self.gcd.getCallbackNumber()))
+               self.callbackNeedsSetup = False
+
+       def populate_recentview(self):
+               print "Populating"
+               self.recentmodel.clear()
+               for item in self.gcd.get_recent():
+                       self.recentmodel.append(item)
+               self.recenttime = time.time()
+
+               return False
+
+       def attemptLogin(self, times = 1):
+               dialog = self.wTree.get_widget("login_dialog")
+
+               while (0 < times) and not self.gcd.isAuthed():
+                       if dialog.run() != gtk.RESPONSE_OK:
+                               times = 0
+                               continue
+
+                       username = self.wTree.get_widget("usernameentry").get_text()
+                       password = self.wTree.get_widget("passwordentry").get_text()
+                       self.wTree.get_widget("passwordentry").set_text("")
+                       print "Attempting login"
+                       self.gcd.login(username, password)
+                       print "hiding dialog"
+                       dialog.hide()
+                       times = times - 1
+
+               return False
+
+       def ErrPopUp(self, msg):
+               error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
+
                def close(dialog, response, editor):
                        editor.about_dialog = None
                        dialog.destroy()
                error_dialog.connect("response", close, self)
-               # error_dialog.connect("delete-event", delete_event, self)
-               self.error_dialog = error_dialog
                error_dialog.run()
 
-       def on_About_clicked(self, menuitem, data=None):
-               if self.about_dialog: 
-                       self.about_dialog.present()
+       def setNumber(self, number):
+               self.phonenumber = makeugly(number)
+               self.prettynumber = makepretty(self.phonenumber)
+               self.numberdisplay.set_label("<span size='30000' weight='bold'>%s</span>" % ( self.prettynumber ) )
+
+       def setAccountNumber(self):
+               accountnumber = self.gcd.getAccountNumber()
+               self.wTree.get_widget("gcnumberlabel").set_label("<span size='23000' weight='bold'>%s</span>" % (accountnumber))
+
+       def on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
+               """
+               For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
+               For system_inactivity, we have no background tasks to pause
+
+               @note Hildon specific
+               """
+               if memory_low:
+                       self.gcd.clear_caches()
+                       re.purge()
+                       gc.collect()
+
+       def on_connection_change(self, connection, event, magicIdentifier):
+               """
+               @note Hildon specific
+               """
+               status = event.get_status()
+               error = event.get_error()
+               iap_id = event.get_iap_id()
+               bearer = event.get_bearer_type()
+
+               if status == conic.STATUS_CONNECTED:
+                       self.window.set_sensitive(True)
+               elif status == conic.STATUS_DISCONNECTED:
+                       self.window.set_sensitive(False)
+
+       def on_loginbutton_clicked(self, data=None):
+               self.wTree.get_widget("login_dialog").response(gtk.RESPONSE_OK)
+
+       def on_loginclose_clicked(self, data=None):
+               sys.exit(0)
+
+       def on_clearcookies_clicked(self, data=None):
+               self.gcd.reset()
+               self.callbackNeedsSetup = True
+               self.recenttime = 0.0
+               self.recentmodel.clear()
+               self.wTree.get_widget("callbackcombo").get_child().set_text("")
+       
+               # re-run the inital grandcentral setup
+               self.attemptLogin(2)
+               gobject.idle_add(self._init_grandcentral)
+
+       def on_callbackentry_changed(self, data=None):
+               text = makeugly(self.wTree.get_widget("callbackcombo").get_child().get_text())
+               if self.gcd.validate(text) and text != self.gcd.getCallbackNumber():
+                       self.gcd.setCallbackNumber(text)
+
+       def on_recentview_row_activated(self, treeview, path, view_column):
+               model, itr = self.recentviewselection.get_selected()
+               if not itr:
                        return
 
-               authors = [ "Mark Bergman <bergman@merctech.com>",
-                               "Eric Warnke <ericew@gmail.com>" ]
+               self.setNumber(self.recentmodel.get_value(itr, 0))
+               self.notebook.set_current_page(0)
+               self.recentviewselection.unselect_all()
+
+       def on_notebook_switch_page(self, notebook, page, page_num):
+               if page_num == 1 and (time.time() - self.recenttime) > 300:
+                       gobject.idle_add(self.populate_recentview)
+               elif page_num ==2 and self.callbackNeedsSetup:
+                       gobject.idle_add(self._setupCallbackCombo)
+               if hildon:
+                       try:
+                               self.window.set_title(self.notebook.get_tab_label(self.notebook.get_nth_page(page_num)).get_text())
+                       except:
+                               self.window.set_title("")
+
+       def on_dial_clicked(self, widget):
+               self.attemptLogin(3)
+
+               if not self.gcd.isAuthed() or self.gcd.getCallbackNumber() == "":
+                       self.ErrPopUp("Backend link with grandcentral is not working, please try again")
+                       return
 
-               about_dialog = gtk.AboutDialog()
-               about_dialog.set_transient_for(None)
-               about_dialog.set_destroy_with_parent(True)
-               about_dialog.set_name("Grandcentral Dialer")
-               about_dialog.set_version("0.5")
-               about_dialog.set_copyright("Copyright \xc2\xa9 2008 Mark Bergman")
-               about_dialog.set_comments("GUI front-end to initiate outbound call from Grandcentral.com, typically with Grancentral configured to connect the outbound call to a VOIP number accessible via Gizmo on the Internet Tablet.\n\nRequires an existing browser cookie from a previous login session to http://www.grandcentral.com/mobile/messages and the program 'wget'.")
-               about_dialog.set_authors            (authors)
-               about_dialog.set_logo_icon_name     (gtk.STOCK_EDIT)
+               try:
+                       callSuccess = self.gcd.dial(self.phonenumber)
+               except ValueError, e:
+                       self.gcd._msg = e.message
+                       callSuccess = False
 
-               # callbacks for destroying the dialog
-               def close(dialog, response, editor):
-                       editor.about_dialog = None
-                       dialog.destroy()
+               if not callSuccess:
+                       self.ErrPopUp(self.gcd._msg)
+               else:
+                       self.setNumber("")
 
-               def delete_event(dialog, event, editor):
-                       editor.about_dialog = None
-                       return True
-
-               about_dialog.connect("response", close, self)
-               about_dialog.connect("delete-event", delete_event, self)
-               self.about_dialog = about_dialog
-               about_dialog.show()
-
-       def on_Dial_clicked(self, widget):
-               # Strip the leading "1" before the area code, if present
-               if len(Dialpad.phonenumber) == 11 and Dialpad.phonenumber[0] == "1" :
-                               Dialpad.phonenumber = Dialpad.phonenumber[1:]
-               prettynumber = makepretty(Dialpad.phonenumber)
-               if len(Dialpad.phonenumber) < 7 :
-                       # It's too short to be a phone number
-                       msg = 'Phone number "%s" is too short' % ( prettynumber )
-                       self.ErrPopUp(msg)
-               else :
-                       timestamp=time.asctime(time.localtime())
-                       
-                       if self.gcd.dial(Dialpad.phonenumber) == True : 
-                               histFH = open(histfile,"a")
-                               histFH.write("%s dialed at %s\n" % ( Dialpad.phonenumber, timestamp ) )
-                               histFH.close()
-
-                               # Re-load the updated history of dialed numbers
-                               load_history_list(histfile,liststore)
-                               self.dialer_history.set_active(-1)
-                               self.on_Clear_clicked(widget)
-                       else:
-                               self.ErrPopUp(self.gcd._msg)
+               self.recentmodel.clear()
+               self.recenttime = 0.0
+       
+       def on_paste(self, data=None):
+               contents = self.clipboard.wait_for_text()
+               phoneNumber = re.sub('\D', '', contents)
+               self.setNumber(phoneNumber)
+       
+       def on_digit_clicked(self, widget):
+               self.setNumber(self.phonenumber + widget.get_name()[5])
 
-       def on_Cancel_clicked(self, widget):
-               sys.exit(1)
+       def on_backspace(self, widget):
+               self.setNumber(self.phonenumber[:-1])
 
-       def Backspace(self, widget):
-               Dialpad.phonenumber = Dialpad.phonenumber[:-1]
-               prettynumber = makepretty(Dialpad.phonenumber)
-               self.numberdisplay.set_text(prettynumber)
 
-       def on_Clear_clicked(self, widget):
-               Dialpad.phonenumber = ""
-               self.numberdisplay.set_text(Dialpad.phonenumber)
+def run_doctest():
+       failureCount, testCount = doctest.testmod()
+       if not failureCount:
+               print "Tests Successful"
+               sys.exit(0)
+       else:
+               sys.exit(1)
 
-       def on_dialer_history_changed(self,widget):
-               # Set the displayed number to the number chosen from the history list
-               history_list = self.dialer_history.get_model()
-               history_index = self.dialer_history.get_active()
-               prettynumber = history_list[history_index][0]
-               Dialpad.phonenumber = makeugly(prettynumber)
-               self.numberdisplay.set_text(prettynumber)
 
-       def on_number_clicked(self, widget):
-               Dialpad.phonenumber = Dialpad.phonenumber + re.sub('\D','',widget.get_label())
-               prettynumber = makepretty(Dialpad.phonenumber)
-               self.numberdisplay.set_text(prettynumber)
+def run_dialpad():
+       gtk.gdk.threads_init()
+       title = 'Dialpad'
+       handle = Dialpad()
+       gtk.main()
+       sys.exit(0)
 
 
+class DummyOptions(object):
+       def __init__(self):
+               self.test = False
+
 
 if __name__ == "__main__":
-       title = 'Dialpad'
-       handle = Dialpad()
-       gtk.main()
-       sys.exit(1)
+       if hildon:
+               gtk.set_application_name("Dialer")
+
+       try:
+               parser = optparse.OptionParser()
+               parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
+               (options, args) = parser.parse_args()
+       except:
+               args = []
+               options = DummyOptions()
+
+       if options.test:
+               run_doctest()
+       else:
+               run_dialpad()