Removing some code that is no longer needed
[gc-dialer] / src / gc_dialer.py
1 #!/usr/bin/python
2
3 # GC Dialer - Front end for Google's Grand Central service.
4 # Copyright (C) 2008  Mark Bergman bergman AT merctech DOT com
5
6 # This library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2.1 of the License, or (at your option) any later version.
10
11 # This library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # Lesser General Public License for more details.
15
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with this library; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19
20
21 """
22 Grandcentral Dialer
23 """
24
25
26 import sys
27 import gc
28 import os
29 import threading
30 import time
31 import re
32 import warnings
33
34 import gobject
35 import gtk
36 import gtk.glade
37
38 try:
39         import hildon
40 except ImportError:
41         hildon = None
42
43 try:
44         import osso
45 except ImportError:
46         osso = None
47
48 try:
49         import conic
50 except ImportError:
51         conic = None
52
53 try:
54         import doctest
55         import optparse
56 except ImportError:
57         doctest = None
58         optparse = None
59
60 from gc_backend import GCDialer
61 from evo_backend import EvolutionAddressBook
62
63 import socket
64
65
66 socket.setdefaulttimeout(5)
67
68
69 def make_ugly(prettynumber):
70         """
71         function to take a phone number and strip out all non-numeric
72         characters
73
74         >>> make_ugly("+012-(345)-678-90")
75         '01234567890'
76         """
77         uglynumber = re.sub('\D', '', prettynumber)
78         return uglynumber
79
80
81 def make_pretty(phonenumber):
82         """
83         Function to take a phone number and return the pretty version
84         pretty numbers:
85                 if phonenumber begins with 0:
86                         ...-(...)-...-....
87                 if phonenumber begins with 1: ( for gizmo callback numbers )
88                         1 (...)-...-....
89                 if phonenumber is 13 digits:
90                         (...)-...-....
91                 if phonenumber is 10 digits:
92                         ...-....
93         >>> make_pretty("12")
94         '12'
95         >>> make_pretty("1234567")
96         '123-4567'
97         >>> make_pretty("2345678901")
98         '(234)-567-8901'
99         >>> make_pretty("12345678901")
100         '1 (234)-567-8901'
101         >>> make_pretty("01234567890")
102         '+012-(345)-678-90'
103         """
104         if phonenumber is None:
105                 return ""
106
107         if len(phonenumber) < 3:
108                 return phonenumber
109
110         if phonenumber[0] == "0":
111                 prettynumber = ""
112                 prettynumber += "+%s" % phonenumber[0:3]
113                 if 3 < len(phonenumber):
114                         prettynumber += "-(%s)" % phonenumber[3:6]
115                         if 6 < len(phonenumber):
116                                 prettynumber += "-%s" % phonenumber[6:9]
117                                 if 9 < len(phonenumber):
118                                         prettynumber += "-%s" % phonenumber[9:]
119                 return prettynumber
120         elif len(phonenumber) <= 7:
121                 prettynumber = "%s-%s" % (phonenumber[0:3], phonenumber[3:])
122         elif len(phonenumber) > 8 and phonenumber[0] == "1":
123                 prettynumber = "1 (%s)-%s-%s" % (phonenumber[1:4], phonenumber[4:7], phonenumber[7:])
124         elif len(phonenumber) > 7:
125                 prettynumber = "(%s)-%s-%s" % (phonenumber[0:3], phonenumber[3:6], phonenumber[6:])
126         return prettynumber
127
128
129 def make_idler(func):
130         """
131         Decorator that makes a generator-function into a function that will continue execution on next call
132         """
133         a = []
134
135         def decorated_func(*args, **kwds):
136                 if not a:
137                         a.append(func(*args, **kwds))
138                 try:
139                         a[0].next()
140                         return True
141                 except StopIteration:
142                         del a[:]
143                         return False
144         
145         decorated_func.__name__ = func.__name__
146         decorated_func.__doc__ = func.__doc__
147         decorated_func.__dict__.update(func.__dict__)
148
149         return decorated_func
150
151
152 class DummyAddressBook(object):
153
154         def get_addressbooks(self):
155                 """
156                 @returns Iterable of (Address Book Factory, Book Id, Book Name)
157                 """
158                 yield self, "", "None"
159         
160         def open_addressbook(self, bookId):
161                 return self
162
163         def factory_name(self):
164                 return ""
165
166         def get_contacts(self):
167                 """
168                 @returns Iterable of (contact id, contact name)
169                 """
170                 return []
171
172         def get_contact_details(self, contactId):
173                 """
174                 @returns Iterable of (Phone Type, Phone Number)
175                 """
176                 return []
177
178
179 class SettingsDialog(object):
180
181         def __init__(self, widgetTree, gcDialer):
182                 self._gcDialer = gcDialer
183                 self._widgetTree = widgetTree
184                 self._dialog = self._widgetTree.get_widget("settings_dialog")
185
186                 self._applyButton = self._widgetTree.get_widget("apply_settings")
187                 self._applyButton.connect("clicked", self.custom_button_response(gtk.RESPONSE_OK))
188
189                 self._cancelButton = self._widgetTree.get_widget("cancel_settings")
190                 self._cancelButton.connect("clicked", self.custom_button_response(gtk.RESPONSE_CANCEL))
191
192                 self._booksList = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
193                 for (factoryId, bookId), (factoryName, bookName) in self._gcDialer.get_addressbooks():
194                         row = (str(factoryId), bookId, factoryName, bookName)
195                         self._booksList.append(row)
196
197                 self._booksView = self._widgetTree.get_widget("books_view")
198                 self._booksView.set_model(self._booksList)
199
200                 # Add the column to the treeview
201                 column = gtk.TreeViewColumn("Addressbook")
202
203                 textrenderer = gtk.CellRendererText()
204                 column.pack_start(textrenderer, expand=False)
205                 column.add_attribute(textrenderer, 'text', 2)
206
207                 textrenderer = gtk.CellRendererText()
208                 column.pack_start(textrenderer, expand=True)
209                 column.add_attribute(textrenderer, 'text', 3)
210
211                 column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
212                 column.set_sort_column_id(2)
213                 column.set_visible(True)
214                 self._booksView.append_column(column)
215
216                 self._booksViewSelection = self._booksView.get_selection()
217                 self._booksViewSelection.set_mode(gtk.SELECTION_SINGLE)
218                 self.reset()
219         
220         def reset(self):
221                 pass
222         
223         def custom_button_response(self, response):
224
225                 def button_handler(*args, **kwds):
226                         self._dialog.response(response)
227
228                 return button_handler
229
230         def run(self):
231                 userResponse = self._dialog.run()
232
233                 if userResponse == gtk.RESPONSE_OK:
234                         model, itr = self._booksViewSelection.get_selected()
235                         if itr:
236                                 factoryId = int(self._booksList.get_value(itr, 0))
237                                 bookId = self._booksList.get_value(itr, 1)
238                                 self._gcDialer.open_addressbook(factoryId, bookId)
239                                 self._booksViewSelection.unselect_all()
240                 else:
241                         self.reset()
242
243                 self._dialog.hide()
244
245
246 class PhoneTypeSelector(object):
247
248         def __init__(self, widgetTree, gcBackend):
249                 self._gcBackend = gcBackend
250                 self._widgetTree = widgetTree
251                 self._dialog = self._widgetTree.get_widget("phonetype_dialog")
252
253                 self._selectButton = self._widgetTree.get_widget("select_button")
254                 self._selectButton.connect("clicked", self._on_phonetype_select)
255
256                 self._cancelButton = self._widgetTree.get_widget("cancel_button")
257                 self._cancelButton.connect("clicked", self._on_phonetype_cancel)
258
259                 self._typemodel = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
260                 self._typeviewselection = None
261
262                 typeview = self._widgetTree.get_widget("phonetypes")
263                 typeview.connect("row-activated", self._on_phonetype_select)
264                 typeview.set_model(self._typemodel)
265                 textrenderer = gtk.CellRendererText()
266
267                 # Add the column to the treeview
268                 column = gtk.TreeViewColumn("Phone Numbers", textrenderer, text=1)
269                 column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
270
271                 typeview.append_column(column)
272
273                 self._typeviewselection = typeview.get_selection()
274                 self._typeviewselection.set_mode(gtk.SELECTION_SINGLE)
275
276         def run(self, contactDetails):
277                 self._typemodel.clear()
278
279                 for phoneType, phoneNumber in contactDetails:
280                         self._typemodel.append((phoneNumber, "%s - %s" % (make_pretty(phoneNumber), phoneType)))
281
282                 userResponse = self._dialog.run()
283
284                 if userResponse == gtk.RESPONSE_OK:
285                         model, itr = self._typeviewselection.get_selected()
286                         if itr:
287                                 phoneNumber = self._typemodel.get_value(itr, 0)
288                 else:
289                         phoneNumber = ""
290
291                 self._typeviewselection.unselect_all()
292                 self._dialog.hide()
293                 return phoneNumber
294         
295         def _on_phonetype_select(self, *args):
296                 self._dialog.response(gtk.RESPONSE_OK)
297
298         def _on_phonetype_cancel(self, *args):
299                 self._dialog.response(gtk.RESPONSE_CANCEL)
300
301
302 class Dialpad(object):
303
304         __pretty_app_name__ = "Dialer"
305         __app_name__ = "gc_dialer"
306         __version__ = "0.8.0"
307         __app_magic__ = 0xdeadbeef
308
309         _glade_files = [
310                 './gc_dialer.glade',
311                 '../lib/gc_dialer.glade',
312                 '/usr/local/lib/gc_dialer.glade',
313         ]
314
315         def __init__(self):
316                 self._phonenumber = ""
317                 self._prettynumber = ""
318                 self._areacode = "518"
319
320                 self._clipboard = gtk.clipboard_get()
321
322                 self._deviceIsOnline = True
323                 self.callbacklist = None
324                 self._callbackNeedsSetup = True
325
326                 self._recenttime = 0.0
327                 self._recentmodel = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
328                 self._recentviewselection = None
329
330                 self._gcContactText = "GC"
331                 try:
332                         self._gcContactIcon = gtk.gdk.pixbuf_new_from_file_at_size('gc_contact.png', 16, 16)
333                 except gobject.GError:
334                         self._gcContactIcon = None
335                 self._contactstime = 0.0
336                 if self._gcContactIcon is not None:
337                         self._contactsmodel = gtk.ListStore(
338                                 gtk.gdk.Pixbuf,
339                                 gobject.TYPE_STRING,
340                                 gobject.TYPE_STRING,
341                                 gobject.TYPE_STRING,
342                                 gobject.TYPE_STRING
343                         )
344                 else:
345                         self._contactsmodel = gtk.ListStore(
346                                 gobject.TYPE_STRING,
347                                 gobject.TYPE_STRING,
348                                 gobject.TYPE_STRING,
349                                 gobject.TYPE_STRING,
350                                 gobject.TYPE_STRING
351                         )
352                 self._contactsviewselection = None
353
354                 for path in Dialpad._glade_files:
355                         if os.path.isfile(path):
356                                 self._widgetTree = gtk.glade.XML(path)
357                                 break
358                 else:
359                         self.display_error_message("Cannot find gc_dialer.glade")
360                         gtk.main_quit()
361                         return
362
363                 aboutHeader = self._widgetTree.get_widget("about_title")
364                 aboutHeader.set_label("%s\nVersion %s" % (aboutHeader.get_label(), Dialpad.__version__))
365
366                 #Get the buffer associated with the number display
367                 self._numberdisplay = self._widgetTree.get_widget("numberdisplay")
368                 self.set_number("")
369                 self._notebook = self._widgetTree.get_widget("notebook")
370
371                 self._window = self._widgetTree.get_widget("Dialpad")
372
373                 global hildon
374                 self._app = None
375                 self._isFullScreen = False
376                 if hildon is not None and self._window is gtk.Window:
377                         warnings.warn("Hildon installed but glade file not updated to work with hildon", UserWarning, 2)
378                         hildon = None
379                 elif hildon is not None:
380                         self._app = hildon.Program()
381                         self._app.add_window(self._window)
382                         self._widgetTree.get_widget("callbackcombo").get_child().set_property('hildon-input-mode', (1 << 4))
383                         self._widgetTree.get_widget("usernameentry").set_property('hildon-input-mode', 7)
384                         self._widgetTree.get_widget("passwordentry").set_property('hildon-input-mode', 7|(1 << 29))
385
386                         gtkMenu = self._widgetTree.get_widget("menubar1")
387                         menu = gtk.Menu()
388                         for child in gtkMenu.get_children():
389                                 child.reparent(menu)
390                         self._window.set_menu(menu)
391                         gtkMenu.destroy()
392
393                         self._window.connect("key-press-event", self._on_key_press)
394                         self._window.connect("window-state-event", self._on_window_state_change)
395                 else:
396                         warnings.warn("No Hildon", UserWarning, 2)
397
398                 if hildon is not None:
399                         self._window.set_title("Keypad")
400                 else:
401                         self._window.set_title("%s - Keypad" % self.__pretty_app_name__)
402
403                 self._osso = None
404                 self._ebook = None
405                 if osso is not None:
406                         self._osso = osso.Context(Dialpad.__app_name__, Dialpad.__version__, False)
407                         device = osso.DeviceState(self._osso)
408                         device.set_device_state_callback(self._on_device_state_change, 0)
409                 else:
410                         warnings.warn("No OSSO", UserWarning, 2)
411
412                 self._connection = None
413                 if conic is not None:
414                         self._connection = conic.Connection()
415                         self._connection.connect("connection-event", self._on_connection_change, Dialpad.__app_magic__)
416                         self._connection.request_connection(conic.CONNECT_FLAG_NONE)
417                 else:
418                         warnings.warn("No Internet Connectivity API ", UserWarning, 2)
419
420                 callbackMapping = {
421                         # Process signals from buttons
422                         "on_loginbutton_clicked": self._on_loginbutton_clicked,
423                         "on_loginclose_clicked": self._on_loginclose_clicked,
424
425                         "on_dialpad_quit": self._on_close,
426                         "on_paste": self._on_paste,
427                         "on_clear_number": self._on_clear_number,
428                         "on_settings": self._on_settings,
429
430                         "on_clearcookies_clicked": self._on_clearcookies_clicked,
431                         "on_notebook_switch_page": self._on_notebook_switch_page,
432                         "on_recentview_row_activated": self._on_recentview_row_activated,
433                         "on_contactsview_row_activated" : self._on_contactsview_row_activated,
434
435                         "on_digit_clicked": self._on_digit_clicked,
436                         "on_back_clicked": self._on_backspace,
437                         "on_dial_clicked": self._on_dial_clicked,
438                 }
439                 self._widgetTree.signal_autoconnect(callbackMapping)
440                 self._widgetTree.get_widget("callbackcombo").get_child().connect("changed", self._on_callbackentry_changed)
441
442                 if self._window:
443                         self._window.connect("destroy", gtk.main_quit)
444                         self._window.show_all()
445
446                 self._gcBackend = GCDialer()
447
448                 self._addressBookFactories = [
449                         DummyAddressBook(),
450                         EvolutionAddressBook(),
451                         self._gcBackend,
452                 ]
453                 self._addressBook = None
454                 self.open_addressbook(*self.get_addressbooks().next()[0][0:2])
455
456                 self._phoneTypeSelector = PhoneTypeSelector(self._widgetTree, self._gcBackend)
457                 self._settingsDialog = SettingsDialog(self._widgetTree, self)
458
459                 if not self._gcBackend.is_authed():
460                         self.attempt_login(2)
461                 else:
462                         self.set_account_number()
463                 gobject.idle_add(self._idly_init_recent_view)
464                 gobject.idle_add(self._idly_init_contacts_view)
465
466         def _idly_init_recent_view(self):
467                 """
468                 Deferred initalization of the recent view treeview
469                 """
470
471                 recentview = self._widgetTree.get_widget("recentview")
472                 recentview.set_model(self._recentmodel)
473                 textrenderer = gtk.CellRendererText()
474
475                 # Add the column to the treeview
476                 column = gtk.TreeViewColumn("Calls", textrenderer, text=1)
477                 column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
478
479                 recentview.append_column(column)
480
481                 self._recentviewselection = recentview.get_selection()
482                 self._recentviewselection.set_mode(gtk.SELECTION_SINGLE)
483
484                 return False
485
486         def _idly_init_contacts_view(self):
487                 """ deferred initalization of the contacts view treeview """
488
489                 contactsview = self._widgetTree.get_widget("contactsview")
490                 contactsview.set_model(self._contactsmodel)
491
492                 # Add the column to the treeview
493                 column = gtk.TreeViewColumn("Contact")
494
495                 if self._gcContactIcon is not None:
496                         iconrenderer = gtk.CellRendererPixbuf()
497                         column.pack_start(iconrenderer, expand=False)
498                         column.add_attribute(iconrenderer, 'pixbuf', 0)
499                 else:
500                         warnings.warn("Contact icon unavailable", UserWarning, 1)
501                         textrenderer = gtk.CellRendererText()
502                         column.pack_start(textrenderer, expand=False)
503                         column.add_attribute(textrenderer, 'text', 0)
504
505                 textrenderer = gtk.CellRendererText()
506                 column.pack_start(textrenderer, expand=True)
507                 column.add_attribute(textrenderer, 'text', 1)
508
509                 textrenderer = gtk.CellRendererText()
510                 column.pack_start(textrenderer, expand=True)
511                 column.add_attribute(textrenderer, 'text', 4)
512
513                 column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
514                 column.set_sort_column_id(1)
515                 column.set_visible(True)
516                 contactsview.append_column(column)
517
518                 #textrenderer = gtk.CellRendererText()
519                 #column = gtk.TreeViewColumn("Location", textrenderer, text=2)
520                 #column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
521                 #column.set_sort_column_id(2)
522                 #column.set_visible(True)
523                 #contactsview.append_column(column)
524
525                 #textrenderer = gtk.CellRendererText()
526                 #column = gtk.TreeViewColumn("Phone", textrenderer, text=3)
527                 #column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
528                 #column.set_sort_column_id(3)
529                 #column.set_visible(True)
530                 #contactsview.append_column(column)
531
532                 self._contactsviewselection = contactsview.get_selection()
533                 self._contactsviewselection.set_mode(gtk.SELECTION_SINGLE)
534
535                 return False
536
537         def _idly_setup_callback_combo(self):
538                 combobox = self._widgetTree.get_widget("callbackcombo")
539                 self.callbacklist = gtk.ListStore(gobject.TYPE_STRING)
540                 combobox.set_model(self.callbacklist)
541                 combobox.set_text_column(0)
542                 for number, description in self._gcBackend.get_callback_numbers().iteritems():
543                         self.callbacklist.append([make_pretty(number)])
544
545                 combobox.get_child().set_text(make_pretty(self._gcBackend.get_callback_number()))
546                 self._callbackNeedsSetup = False
547
548         def _idly_populate_recentview(self):
549                 self._recentmodel.clear()
550
551                 for personsName, phoneNumber, date, action in self._gcBackend.get_recent():
552                         description = "%s on %s from/to %s - %s" % (action.capitalize(), date, personsName, phoneNumber)
553                         item = (phoneNumber, description)
554                         self._recentmodel.append(item)
555
556                 self._recenttime = time.time()
557                 return False
558
559         @make_idler
560         def _idly_populate_contactsview(self):
561                 self._contactsmodel.clear()
562
563                 # completely disable updating the treeview while we populate the data
564                 contactsview = self._widgetTree.get_widget("contactsview")
565                 contactsview.freeze_child_notify()
566                 contactsview.set_model(None)
567
568                 # get gc icon
569                 if self._gcContactIcon is not None:
570                         contactType = (self._gcContactIcon,)
571                 else:
572                         contactType = (self._gcContactText,)
573                 for contactId, contactName in self._addressBook.get_contacts():
574                         self._contactsmodel.append(contactType + (contactName, "", contactId) + ("",))
575                         yield
576
577                 # restart the treeview data rendering
578                 contactsview.set_model(self._contactsmodel)
579                 contactsview.thaw_child_notify()
580
581                 self._contactstime = time.time()
582
583         def attempt_login(self, numOfAttempts = 1):
584                 """
585                 @note Assumes that you are already logged in
586                 """
587                 assert 0 < numOfAttempts, "That was pointless having 0 or less login attempts"
588                 dialog = self._widgetTree.get_widget("login_dialog")
589
590                 for i in range(numOfAttempts):
591                         dialog.run()
592
593                         username = self._widgetTree.get_widget("usernameentry").get_text()
594                         password = self._widgetTree.get_widget("passwordentry").get_text()
595                         self._widgetTree.get_widget("passwordentry").set_text("")
596
597                         loggedIn = self._gcBackend.login(username, password)
598                         dialog.hide()
599                         if loggedIn:
600                                 if self._gcBackend.get_callback_number() is None:
601                                         self._gcBackend.set_sane_callback()
602                                 self.set_account_number()
603                                 return True
604
605                 return False
606
607         def display_error_message(self, msg):
608                 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
609
610                 def close(dialog, response, editor):
611                         editor.about_dialog = None
612                         dialog.destroy()
613                 error_dialog.connect("response", close, self)
614                 error_dialog.run()
615
616         def get_addressbooks(self):
617                 """
618                 @returns Iterable of ((Factory Id, Book Id), (Factory Name, Book Name))
619                 """
620                 for i, factory in enumerate(self._addressBookFactories):
621                         for bookFactory, bookId, bookName in factory.get_addressbooks():
622                                 yield (i, bookId), (factory.factory_name(), bookName)
623         
624         def open_addressbook(self, bookFactoryId, bookId):
625                 self._addressBook = self._addressBookFactories[bookFactoryId].open_addressbook(bookId)
626                 self._contactstime = 0
627                 gobject.idle_add(self._idly_populate_contactsview)
628
629         def set_number(self, number):
630                 """
631                 Set the callback phonenumber
632                 """
633                 self._phonenumber = make_ugly(number)
634                 self._prettynumber = make_pretty(self._phonenumber)
635                 self._numberdisplay.set_label("<span size='30000' weight='bold'>%s</span>" % (self._prettynumber))
636
637         def set_account_number(self):
638                 """
639                 Displays current account number
640                 """
641                 accountnumber = self._gcBackend.get_account_number()
642                 self._widgetTree.get_widget("gcnumberlabel").set_label("<span size='23000' weight='bold'>%s</span>" % (accountnumber))
643
644         def _on_close(self, *args):
645                 gtk.main_quit()
646
647         def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
648                 """
649                 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
650                 For system_inactivity, we have no background tasks to pause
651
652                 @note Hildon specific
653                 """
654                 if memory_low:
655                         self._gcBackend.clear_caches()
656                         re.purge()
657                         gc.collect()
658
659         def _on_connection_change(self, connection, event, magicIdentifier):
660                 """
661                 @note Hildon specific
662                 """
663                 status = event.get_status()
664                 error = event.get_error()
665                 iap_id = event.get_iap_id()
666                 bearer = event.get_bearer_type()
667
668                 if status == conic.STATUS_CONNECTED:
669                         self._window.set_sensitive(True)
670                         self._deviceIsOnline = True
671                 elif status == conic.STATUS_DISCONNECTED:
672                         self._window.set_sensitive(False)
673                         self._deviceIsOnline = False
674
675         def _on_window_state_change(self, widget, event, *args):
676                 """
677                 @note Hildon specific
678                 """
679                 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
680                         self._isFullScreen = True
681                 else:
682                         self._isFullScreen = False
683         
684         def _on_settings(self, *args, **kwds):
685                 self._settingsDialog.run()
686
687         def _on_key_press(self, widget, event, *args):
688                 """
689                 @note Hildon specific
690                 """
691                 if event.keyval == gtk.keysyms.F6:
692                         if self._isFullScreen:
693                                 self._window.unfullscreen()
694                         else:
695                                 self._window.fullscreen()
696
697         def _on_loginbutton_clicked(self, *args):
698                 self._widgetTree.get_widget("login_dialog").response(gtk.RESPONSE_OK)
699
700         def _on_loginclose_clicked(self, *args):
701                 self._on_close()
702                 sys.exit(0)
703
704         def _on_clearcookies_clicked(self, *args):
705                 self._gcBackend.logout()
706                 self._callbackNeedsSetup = True
707                 self._recenttime = 0.0
708                 self._contactstime = 0.0
709                 self._recentmodel.clear()
710                 self._widgetTree.get_widget("callbackcombo").get_child().set_text("")
711
712                 # re-run the inital grandcentral setup
713                 self.attempt_login(2)
714                 gobject.idle_add(self._idly_setup_callback_combo)
715
716         def _on_callbackentry_changed(self, *args):
717                 """
718                 @todo Potential blocking on web access, maybe we should defer this or put up a dialog?
719                 """
720                 text = make_ugly(self._widgetTree.get_widget("callbackcombo").get_child().get_text())
721                 if not self._gcBackend.is_valid_syntax(text):
722                         warnings.warn("%s is not a valid callback number" % text, UserWarning, 2)
723                 elif text != self._gcBackend.get_callback_number():
724                         self._gcBackend.set_callback_number(text)
725                 else:
726                         warnings.warn("Callback number already is %s" % self._gcBackend.get_callback_number(), UserWarning, 2)
727
728         def _on_recentview_row_activated(self, treeview, path, view_column):
729                 model, itr = self._recentviewselection.get_selected()
730                 if not itr:
731                         return
732
733                 self.set_number(self._recentmodel.get_value(itr, 0))
734                 self._notebook.set_current_page(0)
735                 self._recentviewselection.unselect_all()
736
737         def _on_contactsview_row_activated(self, treeview, path, view_column):
738                 model, itr = self._contactsviewselection.get_selected()
739                 if not itr:
740                         return
741
742                 contactId = self._contactsmodel.get_value(itr, 3)
743                 contactDetails = self._addressBook.get_contact_details(contactId)
744                 contactDetails = [phoneNumber for phoneNumber in contactDetails]
745
746                 if len(contactDetails) == 0:
747                         phoneNumber = ""
748                 elif len(contactDetails) == 1:
749                         phoneNumber = contactDetails[0][1]
750                 else:
751                         phoneNumber = self._phoneTypeSelector.run(contactDetails)
752
753                 if 0 < len(phoneNumber):
754                         self.set_number(phoneNumber)
755                         self._notebook.set_current_page(0)
756
757                 self._contactsviewselection.unselect_all()
758
759         def _on_notebook_switch_page(self, notebook, page, page_num):
760                 if page_num == 1 and 300 < (time.time() - self._contactstime):
761                         gobject.idle_add(self._idly_populate_contactsview)
762                 elif page_num == 2 and 300 < (time.time() - self._recenttime):
763                         gobject.idle_add(self._idly_populate_recentview)
764                 elif page_num == 3 and self._callbackNeedsSetup:
765                         gobject.idle_add(self._idly_setup_callback_combo)
766
767                 tabTitle = self._notebook.get_tab_label(self._notebook.get_nth_page(page_num)).get_text()
768                 if hildon is not None:
769                         self._window.set_title(tabTitle)
770                 else:
771                         self._window.set_title("%s - %s" % (self.__pretty_app_name__, tabTitle))
772
773         def _on_dial_clicked(self, widget):
774                 """
775                 @todo Potential blocking on web access, maybe we should defer parts of this or put up a dialog?
776                 """
777                 loggedIn = self._gcBackend.is_authed()
778                 if not loggedIn:
779                         loggedIn = self.attempt_login(2)
780
781                 if not loggedIn or not self._gcBackend.is_authed() or self._gcBackend.get_callback_number() == "":
782                         self.display_error_message("Backend link with grandcentral is not working, please try again")
783                         warnings.warn("Backend Status: Logged in? %s, Authenticated? %s, Callback=%s" % (loggedIn, self._gcBackend.is_authed(), self._gcBackend.get_callback_number()), UserWarning, 2)
784                         return
785
786                 try:
787                         callSuccess = self._gcBackend.dial(self._phonenumber)
788                 except ValueError, e:
789                         self._gcBackend._msg = e.message
790                         callSuccess = False
791
792                 if not callSuccess:
793                         self.display_error_message(self._gcBackend._msg)
794                 else:
795                         self.set_number("")
796
797                 self._recentmodel.clear()
798                 self._recenttime = 0.0
799
800         def _on_paste(self, *args):
801                 contents = self._clipboard.wait_for_text()
802                 phoneNumber = re.sub('\D', '', contents)
803                 self.set_number(phoneNumber)
804
805         def _on_clear_number(self, *args):
806                 self.set_number("")
807
808         def _on_digit_clicked(self, widget):
809                 self.set_number(self._phonenumber + widget.get_name()[5])
810
811         def _on_backspace(self, widget):
812                 self.set_number(self._phonenumber[:-1])
813
814
815 def run_doctest():
816         failureCount, testCount = doctest.testmod()
817         if not failureCount:
818                 print "Tests Successful"
819                 sys.exit(0)
820         else:
821                 sys.exit(1)
822
823
824 def run_dialpad():
825         gtk.gdk.threads_init()
826         title = 'Dialpad'
827         handle = Dialpad()
828         gtk.main()
829
830
831 class DummyOptions(object):
832
833         def __init__(self):
834                 self.test = False
835
836
837 if __name__ == "__main__":
838         if hildon is not None:
839                 gtk.set_application_name(Dialpad.__pretty_app_name__)
840
841         if optparse is not None:
842                 parser = optparse.OptionParser()
843                 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
844                 (commandOptions, commandArgs) = parser.parse_args()
845         else:
846                 commandOptions = DummyOptions()
847                 commandArgs = []
848
849         if commandOptions.test:
850                 run_doctest()
851         else:
852                 run_dialpad()