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