ErorDisplay overkill now in effect
[gc-dialer] / src / dc_glade.py
1 #!/usr/bin/python2.5
2
3 """
4 DialCentral - Front end for Google's Grand Central service.
5 Copyright (C) 2008  Mark Bergman bergman AT merctech DOT com
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public
18 License along with this library; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20 """
21
22
23 from __future__ import with_statement
24
25 import sys
26 import gc
27 import os
28 import threading
29 import base64
30 import ConfigParser
31 import itertools
32 import logging
33
34 import gtk
35 import gtk.glade
36
37 import constants
38 import hildonize
39 import gtk_toolbox
40
41
42 def getmtime_nothrow(path):
43         try:
44                 return os.path.getmtime(path)
45         except Exception:
46                 return 0
47
48
49 def display_error_message(msg):
50         error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
51
52         def close(dialog, response):
53                 dialog.destroy()
54         error_dialog.connect("response", close)
55         error_dialog.run()
56
57
58 class Dialcentral(object):
59
60         _glade_files = [
61                 os.path.join(os.path.dirname(__file__), "dialcentral.glade"),
62                 os.path.join(os.path.dirname(__file__), "../lib/dialcentral.glade"),
63                 '/usr/lib/dialcentral/dialcentral.glade',
64         ]
65
66         KEYPAD_TAB = 0
67         RECENT_TAB = 1
68         MESSAGES_TAB = 2
69         CONTACTS_TAB = 3
70         ACCOUNT_TAB = 4
71
72         NULL_BACKEND = 0
73         GV_BACKEND = 2
74         BACKENDS = (NULL_BACKEND, GV_BACKEND)
75
76         def __init__(self):
77                 self._initDone = False
78                 self._connection = None
79                 self._osso = None
80                 self._clipboard = gtk.clipboard_get()
81
82                 self._credentials = ("", "")
83                 self._selectedBackendId = self.NULL_BACKEND
84                 self._defaultBackendId = self.GV_BACKEND
85                 self._phoneBackends = None
86                 self._dialpads = None
87                 self._accountViews = None
88                 self._messagesViews = None
89                 self._recentViews = None
90                 self._contactsViews = None
91                 self._alarmHandler = None
92                 self._ledHandler = None
93                 self._originalCurrentLabels = []
94
95                 for path in self._glade_files:
96                         if os.path.isfile(path):
97                                 self._widgetTree = gtk.glade.XML(path)
98                                 break
99                 else:
100                         display_error_message("Cannot find dialcentral.glade")
101                         gtk.main_quit()
102                         return
103
104                 self._window = self._widgetTree.get_widget("mainWindow")
105                 self._notebook = self._widgetTree.get_widget("notebook")
106                 self._errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
107                 self._credentialsDialog = gtk_toolbox.LoginWindow(self._widgetTree)
108
109                 self._isFullScreen = False
110                 self._app = hildonize.get_app_class()()
111                 self._window = hildonize.hildonize_window(self._app, self._window)
112                 hildonize.hildonize_text_entry(self._widgetTree.get_widget("usernameentry"))
113                 hildonize.hildonize_password_entry(self._widgetTree.get_widget("passwordentry"))
114                 hildonize.hildonize_combo_entry(self._widgetTree.get_widget("callbackcombo").get_child())
115
116                 for scrollingWidget in (
117                         'recent_scrolledwindow',
118                         'message_scrolledwindow',
119                         'contacts_scrolledwindow',
120                         "phoneSelectionMessage_scrolledwindow",
121                         "phonetypes_scrolledwindow",
122                         "smsMessage_scrolledwindow",
123                         "smsMessage_scrolledEntry",
124                 ):
125                         hildonize.set_thumb_scrollbar(self._widgetTree.get_widget(scrollingWidget))
126
127                 hildonize.hildonize_menu(self._window, self._widgetTree.get_widget("dialpad_menubar"))
128
129                 if hildonize.IS_HILDON:
130                         self._window.connect("key-press-event", self._on_key_press)
131                         self._window.connect("window-state-event", self._on_window_state_change)
132                 else:
133                         logging.warning("No hildonization support")
134
135
136                 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
137
138                 callbackMapping = {
139                         "on_dialpad_quit": self._on_close,
140                 }
141                 self._widgetTree.signal_autoconnect(callbackMapping)
142
143                 self._window.connect("destroy", self._on_close)
144                 self._window.set_default_size(800, 300)
145                 self._window.show_all()
146
147                 self._loginSink = gtk_toolbox.threaded_stage(
148                         gtk_toolbox.comap(
149                                 self.attempt_login,
150                                 gtk_toolbox.null_sink(),
151                         )
152                 )
153
154                 backgroundSetup = threading.Thread(target=self._idle_setup)
155                 backgroundSetup.setDaemon(True)
156                 backgroundSetup.start()
157
158         def _idle_setup(self):
159                 """
160                 If something can be done after the UI loads, push it here so it's not blocking the UI
161                 """
162                 try:
163                         # Barebones UI handlers
164                         import null_backend
165                         import null_views
166
167                         self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()}
168                         with gtk_toolbox.gtk_lock():
169                                 self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)}
170                                 self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)}
171                                 self._recentViews = {self.NULL_BACKEND: null_views.RecentCallsView(self._widgetTree)}
172                                 self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)}
173                                 self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)}
174
175                                 self._dialpads[self._selectedBackendId].enable()
176                                 self._accountViews[self._selectedBackendId].enable()
177                                 self._recentViews[self._selectedBackendId].enable()
178                                 self._messagesViews[self._selectedBackendId].enable()
179                                 self._contactsViews[self._selectedBackendId].enable()
180
181                         # Setup maemo specifics
182                         try:
183                                 import osso
184                         except ImportError:
185                                 osso = None
186                         self._osso = None
187                         if osso is not None:
188                                 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
189                                 device = osso.DeviceState(self._osso)
190                                 device.set_device_state_callback(self._on_device_state_change, 0)
191                         else:
192                                 logging.warning("No device state support")
193
194                         try:
195                                 import alarm_handler
196                                 self._alarmHandler = alarm_handler.AlarmHandler()
197                         except ImportError:
198                                 alarm_handler = None
199                         except Exception:
200                                 with gtk_toolbox.gtk_lock():
201                                         self._errorDisplay.push_exception()
202                                 alarm_handler = None
203                                 logging.warning("No notification support")
204                         if hildonize.IS_HILDON:
205                                 import led_handler
206                                 self._ledHandler = led_handler.LedHandler()
207
208                         # Setup maemo specifics
209                         try:
210                                 import conic
211                         except ImportError:
212                                 conic = None
213                         self._connection = None
214                         if conic is not None:
215                                 self._connection = conic.Connection()
216                                 self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
217                                 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
218                         else:
219                                 logging.warning("No connection support")
220
221                         # Setup costly backends
222                         import gv_backend
223                         import file_backend
224                         import gv_views
225
226                         try:
227                                 os.makedirs(constants._data_path_)
228                         except OSError, e:
229                                 if e.errno != 17:
230                                         raise
231                         gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
232
233                         self._phoneBackends.update({
234                                 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
235                         })
236                         with gtk_toolbox.gtk_lock():
237                                 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
238                                 self._dialpads.update({
239                                         self.GV_BACKEND: unifiedDialpad,
240                                 })
241                                 self._accountViews.update({
242                                         self.GV_BACKEND: gv_views.AccountInfo(
243                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
244                                         ),
245                                 })
246                                 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
247                                 self._recentViews.update({
248                                         self.GV_BACKEND: gv_views.RecentCallsView(
249                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
250                                         ),
251                                 })
252                                 self._messagesViews.update({
253                                         self.GV_BACKEND: gv_views.MessagesView(
254                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
255                                         ),
256                                 })
257                                 self._contactsViews.update({
258                                         self.GV_BACKEND: gv_views.ContactsView(
259                                                 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
260                                         ),
261                                 })
262
263                         fsContactsPath = os.path.join(constants._data_path_, "contacts")
264                         fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath)
265
266                         self._dialpads[self.GV_BACKEND].number_selected = self._select_action
267                         self._recentViews[self.GV_BACKEND].number_selected = self._select_action
268                         self._messagesViews[self.GV_BACKEND].number_selected = self._select_action
269                         self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
270
271                         addressBooks = [
272                                 self._phoneBackends[self.GV_BACKEND],
273                                 fileBackend,
274                         ]
275                         mergedBook = gv_views.MergedAddressBook(addressBooks, gv_views.MergedAddressBook.advanced_lastname_sorter)
276                         self._contactsViews[self.GV_BACKEND].append(mergedBook)
277                         self._contactsViews[self.GV_BACKEND].extend(addressBooks)
278                         self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
279
280                         callbackMapping = {
281                                 "on_paste": self._on_paste,
282                                 "on_refresh": self._on_menu_refresh,
283                                 "on_clearcookies_clicked": self._on_clearcookies_clicked,
284                                 "on_notebook_switch_page": self._on_notebook_switch_page,
285                                 "on_about_activate": self._on_about_activate,
286                         }
287                         self._widgetTree.signal_autoconnect(callbackMapping)
288
289                         with gtk_toolbox.gtk_lock():
290                                 self._originalCurrentLabels = [
291                                         self._notebook.get_tab_label(self._notebook.get_nth_page(pageIndex)).get_text()
292                                         for pageIndex in xrange(self._notebook.get_n_pages())
293                                 ]
294                                 self._notebookTapHandler = gtk_toolbox.TapOrHold(self._notebook)
295                                 self._notebookTapHandler.enable()
296                         self._notebookTapHandler.on_tap = self._reset_tab_refresh
297                         self._notebookTapHandler.on_hold = self._on_tab_refresh
298                         self._notebookTapHandler.on_holding = self._set_tab_refresh
299                         self._notebookTapHandler.on_cancel = self._reset_tab_refresh
300
301                         self._initDone = True
302
303                         config = ConfigParser.SafeConfigParser()
304                         config.read(constants._user_settings_)
305                         with gtk_toolbox.gtk_lock():
306                                 self.load_settings(config)
307
308                         self._spawn_attempt_login(2)
309                 except Exception, e:
310                         with gtk_toolbox.gtk_lock():
311                                 self._errorDisplay.push_exception()
312
313         def attempt_login(self, numOfAttempts = 10, force = False):
314                 """
315                 @todo Handle user notification better like attempting to login and failed login
316
317                 @note This must be run outside of the UI lock
318                 """
319                 try:
320                         assert 0 <= numOfAttempts, "That was pointless having 0 or less login attempts"
321                         assert self._initDone, "Attempting login before app is fully loaded"
322
323                         serviceId = self.NULL_BACKEND
324                         loggedIn = False
325                         if not force:
326                                 try:
327                                         self.refresh_session()
328                                         serviceId = self._defaultBackendId
329                                         loggedIn = True
330                                 except Exception, e:
331                                         logging.exception('Session refresh failed with the following message "%s"' % e.message)
332
333                         if not loggedIn:
334                                 loggedIn, serviceId = self._login_by_user(numOfAttempts)
335
336                         with gtk_toolbox.gtk_lock():
337                                 self._change_loggedin_status(serviceId)
338                 except Exception, e:
339                         with gtk_toolbox.gtk_lock():
340                                 self._errorDisplay.push_exception()
341
342         def _spawn_attempt_login(self, *args):
343                 self._loginSink.send(args)
344
345         def refresh_session(self):
346                 """
347                 @note Thread agnostic
348                 """
349                 assert self._initDone, "Attempting login before app is fully loaded"
350
351                 loggedIn = False
352                 if not loggedIn:
353                         loggedIn = self._login_by_cookie()
354                 if not loggedIn:
355                         loggedIn = self._login_by_settings()
356
357                 if not loggedIn:
358                         raise RuntimeError("Login Failed")
359
360         def _login_by_cookie(self):
361                 """
362                 @note Thread agnostic
363                 """
364                 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
365                 if loggedIn:
366                         logging.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
367                 return loggedIn
368
369         def _login_by_settings(self):
370                 """
371                 @note Thread agnostic
372                 """
373                 username, password = self._credentials
374                 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
375                 if loggedIn:
376                         self._credentials = username, password
377                         logging.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
378                 return loggedIn
379
380         def _login_by_user(self, numOfAttempts):
381                 """
382                 @note This must be run outside of the UI lock
383                 """
384                 loggedIn, (username, password) = False, self._credentials
385                 tmpServiceId = self.GV_BACKEND
386                 for attemptCount in xrange(numOfAttempts):
387                         if loggedIn:
388                                 break
389                         with gtk_toolbox.gtk_lock():
390                                 credentials = self._credentialsDialog.request_credentials(
391                                         defaultCredentials = self._credentials
392                                 )
393                         username, password = credentials
394                         loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
395
396                 if loggedIn:
397                         serviceId = tmpServiceId
398                         self._credentials = username, password
399                         logging.info("Logged into %r through user request" % self._phoneBackends[serviceId])
400                 else:
401                         serviceId = self.NULL_BACKEND
402
403                 return loggedIn, serviceId
404
405         def _select_action(self, action, number, message):
406                 self.refresh_session()
407                 if action == "select":
408                         self._dialpads[self._selectedBackendId].set_number(number)
409                         self._notebook.set_current_page(self.KEYPAD_TAB)
410                 elif action == "dial":
411                         self._on_dial_clicked(number)
412                 elif action == "sms":
413                         self._on_sms_clicked(number, message)
414                 else:
415                         assert False, "Unknown action: %s" % action
416
417         def _change_loggedin_status(self, newStatus):
418                 oldStatus = self._selectedBackendId
419                 if oldStatus == newStatus:
420                         return
421
422                 self._dialpads[oldStatus].disable()
423                 self._accountViews[oldStatus].disable()
424                 self._recentViews[oldStatus].disable()
425                 self._messagesViews[oldStatus].disable()
426                 self._contactsViews[oldStatus].disable()
427
428                 self._dialpads[newStatus].enable()
429                 self._accountViews[newStatus].enable()
430                 self._recentViews[newStatus].enable()
431                 self._messagesViews[newStatus].enable()
432                 self._contactsViews[newStatus].enable()
433
434                 if self._phoneBackends[self._selectedBackendId].get_callback_number() is None:
435                         self._phoneBackends[self._selectedBackendId].set_sane_callback()
436                 self._accountViews[self._selectedBackendId].update()
437
438                 self._selectedBackendId = newStatus
439
440         def load_settings(self, config):
441                 """
442                 @note UI Thread
443                 """
444                 try:
445                         self._defaultBackendId = int(config.get(constants.__pretty_app_name__, "active"))
446                         blobs = (
447                                 config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
448                                 for i in xrange(len(self._credentials))
449                         )
450                         creds = (
451                                 base64.b64decode(blob)
452                                 for blob in blobs
453                         )
454                         self._credentials = tuple(creds)
455
456                         if self._alarmHandler is not None:
457                                 self._alarmHandler.load_settings(config, "alarm")
458                 except ConfigParser.NoOptionError, e:
459                         logging.exception(
460                                 "Settings file %s is missing section %s" % (
461                                         constants._user_settings_,
462                                         e.section,
463                                 ),
464                         )
465                 except ConfigParser.NoSectionError, e:
466                         logging.exception(
467                                 "Settings file %s is missing section %s" % (
468                                         constants._user_settings_,
469                                         e.section,
470                                 ),
471                         )
472
473                 for backendId, view in itertools.chain(
474                         self._dialpads.iteritems(),
475                         self._accountViews.iteritems(),
476                         self._messagesViews.iteritems(),
477                         self._recentViews.iteritems(),
478                         self._contactsViews.iteritems(),
479                 ):
480                         sectionName = "%s - %s" % (backendId, view.name())
481                         try:
482                                 view.load_settings(config, sectionName)
483                         except ConfigParser.NoOptionError, e:
484                                 logging.exception(
485                                         "Settings file %s is missing section %s" % (
486                                                 constants._user_settings_,
487                                                 e.section,
488                                         ),
489                                 )
490                         except ConfigParser.NoSectionError, e:
491                                 logging.exception(
492                                         "Settings file %s is missing section %s" % (
493                                                 constants._user_settings_,
494                                                 e.section,
495                                         ),
496                                 )
497
498         def save_settings(self, config):
499                 """
500                 @note Thread Agnostic
501                 """
502                 config.add_section(constants.__pretty_app_name__)
503                 config.set(constants.__pretty_app_name__, "active", str(self._selectedBackendId))
504                 for i, value in enumerate(self._credentials):
505                         blob = base64.b64encode(value)
506                         config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
507                 config.add_section("alarm")
508                 if self._alarmHandler is not None:
509                         self._alarmHandler.save_settings(config, "alarm")
510
511                 for backendId, view in itertools.chain(
512                         self._dialpads.iteritems(),
513                         self._accountViews.iteritems(),
514                         self._messagesViews.iteritems(),
515                         self._recentViews.iteritems(),
516                         self._contactsViews.iteritems(),
517                 ):
518                         sectionName = "%s - %s" % (backendId, view.name())
519                         config.add_section(sectionName)
520                         view.save_settings(config, sectionName)
521
522         def _save_settings(self):
523                 """
524                 @note Thread Agnostic
525                 """
526                 config = ConfigParser.SafeConfigParser()
527                 self.save_settings(config)
528                 with open(constants._user_settings_, "wb") as configFile:
529                         config.write(configFile)
530
531         def _refresh_active_tab(self):
532                 pageIndex = self._notebook.get_current_page()
533                 if pageIndex == self.CONTACTS_TAB:
534                         self._contactsViews[self._selectedBackendId].update(force=True)
535                 elif pageIndex == self.RECENT_TAB:
536                         self._recentViews[self._selectedBackendId].update(force=True)
537                 elif pageIndex == self.MESSAGES_TAB:
538                         self._messagesViews[self._selectedBackendId].update(force=True)
539
540                 if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB):
541                         if self._ledHandler is not None:
542                                 self._ledHandler.off()
543
544         def _on_close(self, *args, **kwds):
545                 try:
546                         if self._osso is not None:
547                                 self._osso.close()
548
549                         if self._initDone:
550                                 self._save_settings()
551                 finally:
552                         gtk.main_quit()
553
554         def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
555                 """
556                 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
557                 For system_inactivity, we have no background tasks to pause
558
559                 @note Hildon specific
560                 """
561                 try:
562                         if memory_low:
563                                 for backendId in self.BACKENDS:
564                                         self._phoneBackends[backendId].clear_caches()
565                                 self._contactsViews[self._selectedBackendId].clear_caches()
566                                 gc.collect()
567
568                         if save_unsaved_data or shutdown:
569                                 self._save_settings()
570                 except Exception, e:
571                         self._errorDisplay.push_exception()
572
573         def _on_connection_change(self, connection, event, magicIdentifier):
574                 """
575                 @note Hildon specific
576                 """
577                 try:
578                         import conic
579
580                         status = event.get_status()
581                         error = event.get_error()
582                         iap_id = event.get_iap_id()
583                         bearer = event.get_bearer_type()
584
585                         if status == conic.STATUS_CONNECTED:
586                                 if self._initDone:
587                                         self._spawn_attempt_login(2)
588                         elif status == conic.STATUS_DISCONNECTED:
589                                 if self._initDone:
590                                         self._defaultBackendId = self._selectedBackendId
591                                         self._change_loggedin_status(self.NULL_BACKEND)
592                 except Exception, e:
593                         self._errorDisplay.push_exception()
594
595         def _on_window_state_change(self, widget, event, *args):
596                 """
597                 @note Hildon specific
598                 """
599                 try:
600                         if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
601                                 self._isFullScreen = True
602                         else:
603                                 self._isFullScreen = False
604                 except Exception, e:
605                         self._errorDisplay.push_exception()
606
607         def _on_key_press(self, widget, event, *args):
608                 """
609                 @note Hildon specific
610                 """
611                 try:
612                         if event.keyval == gtk.keysyms.F6:
613                                 if self._isFullScreen:
614                                         self._window.unfullscreen()
615                                 else:
616                                         self._window.fullscreen()
617                 except Exception, e:
618                         self._errorDisplay.push_exception()
619
620         def _on_clearcookies_clicked(self, *args):
621                 try:
622                         self._phoneBackends[self._selectedBackendId].logout()
623                         self._accountViews[self._selectedBackendId].clear()
624                         self._recentViews[self._selectedBackendId].clear()
625                         self._messagesViews[self._selectedBackendId].clear()
626                         self._contactsViews[self._selectedBackendId].clear()
627                         self._change_loggedin_status(self.NULL_BACKEND)
628
629                         self._spawn_attempt_login(2, True)
630                 except Exception, e:
631                         self._errorDisplay.push_exception()
632
633         def _on_notebook_switch_page(self, notebook, page, pageIndex):
634                 try:
635                         self._reset_tab_refresh()
636
637                         didRecentUpdate = False
638                         didMessagesUpdate = False
639
640                         if pageIndex == self.RECENT_TAB:
641                                 didRecentUpdate = self._recentViews[self._selectedBackendId].update()
642                         elif pageIndex == self.MESSAGES_TAB:
643                                 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
644                         elif pageIndex == self.CONTACTS_TAB:
645                                 self._contactsViews[self._selectedBackendId].update()
646                         elif pageIndex == self.ACCOUNT_TAB:
647                                 self._accountViews[self._selectedBackendId].update()
648
649                         if didRecentUpdate or didMessagesUpdate:
650                                 if self._ledHandler is not None:
651                                         self._ledHandler.off()
652                 except Exception, e:
653                         self._errorDisplay.push_exception()
654
655         def _set_tab_refresh(self, *args):
656                 try:
657                         pageIndex = self._notebook.get_current_page()
658                         child = self._notebook.get_nth_page(pageIndex)
659                         self._notebook.get_tab_label(child).set_text("Refresh?")
660                 except Exception, e:
661                         self._errorDisplay.push_exception()
662                 return False
663
664         def _reset_tab_refresh(self, *args):
665                 try:
666                         pageIndex = self._notebook.get_current_page()
667                         child = self._notebook.get_nth_page(pageIndex)
668                         self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
669                 except Exception, e:
670                         self._errorDisplay.push_exception()
671                 return False
672
673         def _on_tab_refresh(self, *args):
674                 try:
675                         self._refresh_active_tab()
676                         self._reset_tab_refresh()
677                 except Exception, e:
678                         self._errorDisplay.push_exception()
679                 return False
680
681         def _on_sms_clicked(self, number, message):
682                 try:
683                         assert number, "No number specified"
684                         assert message, "Empty message"
685                         try:
686                                 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
687                         except Exception, e:
688                                 loggedIn = False
689                                 self._errorDisplay.push_exception()
690                                 return
691
692                         if not loggedIn:
693                                 self._errorDisplay.push_message(
694                                         "Backend link with grandcentral is not working, please try again"
695                                 )
696                                 return
697
698                         dialed = False
699                         try:
700                                 self._phoneBackends[self._selectedBackendId].send_sms(number, message)
701                                 dialed = True
702                         except Exception, e:
703                                 self._errorDisplay.push_exception()
704
705                         if dialed:
706                                 self._dialpads[self._selectedBackendId].clear()
707                 except Exception, e:
708                         self._errorDisplay.push_exception()
709
710         def _on_dial_clicked(self, number):
711                 try:
712                         assert number, "No number to call"
713                         try:
714                                 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
715                         except Exception, e:
716                                 loggedIn = False
717                                 self._errorDisplay.push_exception()
718                                 return
719
720                         if not loggedIn:
721                                 self._errorDisplay.push_message(
722                                         "Backend link with grandcentral is not working, please try again"
723                                 )
724                                 return
725
726                         dialed = False
727                         try:
728                                 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
729                                 self._phoneBackends[self._selectedBackendId].dial(number)
730                                 dialed = True
731                         except Exception, e:
732                                 self._errorDisplay.push_exception()
733
734                         if dialed:
735                                 self._dialpads[self._selectedBackendId].clear()
736                 except Exception, e:
737                         self._errorDisplay.push_exception()
738
739         def _on_menu_refresh(self, *args):
740                 try:
741                         self._refresh_active_tab()
742                 except Exception, e:
743                         self._errorDisplay.push_exception()
744
745         def _on_paste(self, *args):
746                 try:
747                         contents = self._clipboard.wait_for_text()
748                         self._dialpads[self._selectedBackendId].set_number(contents)
749                 except Exception, e:
750                         self._errorDisplay.push_exception()
751
752         def _on_about_activate(self, *args):
753                 try:
754                         dlg = gtk.AboutDialog()
755                         dlg.set_name(constants.__pretty_app_name__)
756                         dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
757                         dlg.set_copyright("Copyright 2008 - LGPL")
758                         dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice/Grandcentral account.  This application is not affiliated with Google in any way")
759                         dlg.set_website("http://gc-dialer.garage.maemo.org/")
760                         dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <edpage@byu.net>"])
761                         dlg.run()
762                         dlg.destroy()
763                 except Exception, e:
764                         self._errorDisplay.push_exception()
765
766
767 def run_doctest():
768         import doctest
769
770         failureCount, testCount = doctest.testmod()
771         if not failureCount:
772                 print "Tests Successful"
773                 sys.exit(0)
774         else:
775                 sys.exit(1)
776
777
778 def run_dialpad():
779         _lock_file = os.path.join(constants._data_path_, ".lock")
780         #with gtk_toolbox.flock(_lock_file, 0):
781         gtk.gdk.threads_init()
782
783         if hildonize.IS_HILDON:
784                 gtk.set_application_name(constants.__pretty_app_name__)
785         handle = Dialcentral()
786         gtk.main()
787
788
789 class DummyOptions(object):
790
791         def __init__(self):
792                 self.test = False
793
794
795 if __name__ == "__main__":
796         if hildonize.IS_HILDON:
797                 userLogPath = "%s/dialcentral.log" % constants._data_path_
798                 logging.basicConfig(level=logging.DEBUG, filename=userLogPath)
799         else:
800                 logging.basicConfig(level=logging.DEBUG)
801         try:
802                 if len(sys.argv) > 1:
803                         try:
804                                 import optparse
805                         except ImportError:
806                                 optparse = None
807
808                         if optparse is not None:
809                                 parser = optparse.OptionParser()
810                                 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
811                                 (commandOptions, commandArgs) = parser.parse_args()
812                 else:
813                         commandOptions = DummyOptions()
814                         commandArgs = []
815
816                 if commandOptions.test:
817                         run_doctest()
818                 else:
819                         run_dialpad()
820         finally:
821                 logging.shutdown()