4 DialCentral - Front end for Google's GoogleVoice service.
5 Copyright (C) 2008 Mark Bergman bergman AT merctech DOT com
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.
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.
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
23 from __future__ import with_statement
43 _moduleLogger = logging.getLogger("dc_glade")
44 PROFILE_STARTUP = False
47 def getmtime_nothrow(path):
49 return os.path.getmtime(path)
54 def display_error_message(msg):
55 error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
57 def close(dialog, response):
59 error_dialog.connect("response", close)
63 class Dialcentral(object):
66 os.path.join(os.path.dirname(__file__), "dialcentral.glade"),
67 os.path.join(os.path.dirname(__file__), "../lib/dialcentral.glade"),
68 '/usr/lib/dialcentral/dialcentral.glade',
78 # 1 Was GrandCentral support so the gap was maintained for compatibility
80 BACKENDS = (NULL_BACKEND, GV_BACKEND)
83 self._initDone = False
84 self._connection = None
86 self._deviceState = None
87 self._clipboard = gtk.clipboard_get()
89 self._credentials = ("", "")
90 self._selectedBackendId = self.NULL_BACKEND
91 self._defaultBackendId = self.GV_BACKEND
92 self._phoneBackends = None
94 self._accountViews = None
95 self._messagesViews = None
96 self._historyViews = None
97 self._contactsViews = None
98 self._alarmHandler = None
99 self._ledHandler = None
100 self._originalCurrentLabels = []
101 self._fsContactsPath = os.path.join(constants._data_path_, "contacts")
103 for path in self._glade_files:
104 if os.path.isfile(path):
105 self._widgetTree = gtk.glade.XML(path)
108 display_error_message("Cannot find dialcentral.glade")
112 self._window = self._widgetTree.get_widget("mainWindow")
113 self._notebook = self._widgetTree.get_widget("notebook")
114 errorBox = self._widgetTree.get_widget("errorEventBox")
115 errorDescription = self._widgetTree.get_widget("errorDescription")
116 errorClose = self._widgetTree.get_widget("errorClose")
117 self._errorDisplay = gtk_toolbox.ErrorDisplay(errorBox, errorDescription, errorClose)
118 self._credentialsDialog = gtk_toolbox.LoginWindow(self._widgetTree)
119 self._smsEntryWindow = None
121 self._isFullScreen = False
122 self.__isPortrait = False
123 self._app = hildonize.get_app_class()()
124 self._window = hildonize.hildonize_window(self._app, self._window)
125 hildonize.hildonize_text_entry(self._widgetTree.get_widget("usernameentry"))
126 hildonize.hildonize_password_entry(self._widgetTree.get_widget("passwordentry"))
128 for scrollingWidgetName in (
129 'history_scrolledwindow',
130 'message_scrolledwindow',
131 'contacts_scrolledwindow',
132 "smsMessages_scrolledwindow",
134 scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName)
135 assert scrollingWidget is not None, scrollingWidgetName
136 hildonize.hildonize_scrollwindow(scrollingWidget)
137 for scrollingWidgetName in (
138 "smsMessage_scrolledEntry",
140 scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName)
141 assert scrollingWidget is not None, scrollingWidgetName
142 hildonize.hildonize_scrollwindow_with_viewport(scrollingWidget)
146 "addressbookSelectButton",
149 "callbackSelectButton",
150 "minutesEntryButton",
152 "phoneTypeSelection",
154 button = self._widgetTree.get_widget(buttonName)
155 assert button is not None, buttonName
156 hildonize.set_button_thumb_selectable(button)
158 menu = hildonize.hildonize_menu(
160 self._widgetTree.get_widget("dialpad_menubar"),
162 if not hildonize.GTK_MENU_USED:
163 button = gtk.Button("New Login")
164 button.connect("clicked", self._on_clearcookies_clicked)
167 button = gtk.Button("Refresh")
168 button.connect("clicked", self._on_menu_refresh)
173 self._window.connect("key-press-event", self._on_key_press)
174 self._window.connect("window-state-event", self._on_window_state_change)
175 if not hildonize.IS_HILDON_SUPPORTED:
176 _moduleLogger.warning("No hildonization support")
178 hildonize.set_application_name("%s" % constants.__pretty_app_name__)
180 self._window.connect("destroy", self._on_close)
181 self._window.set_default_size(800, 300)
182 self._window.show_all()
184 self._loginSink = gtk_toolbox.threaded_stage(
187 gtk_toolbox.null_sink(),
191 if not PROFILE_STARTUP:
192 backgroundSetup = threading.Thread(target=self._idle_setup)
193 backgroundSetup.setDaemon(True)
194 backgroundSetup.start()
198 def _idle_setup(self):
200 If something can be done after the UI loads, push it here so it's not blocking the UI
202 # Barebones UI handlers
204 from backends import null_backend
207 self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()}
208 with gtk_toolbox.gtk_lock():
209 self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)}
210 self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)}
211 self._historyViews = {self.NULL_BACKEND: null_views.CallHistoryView(self._widgetTree)}
212 self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)}
213 self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)}
215 self._dialpads[self._selectedBackendId].enable()
216 self._accountViews[self._selectedBackendId].enable()
217 self._historyViews[self._selectedBackendId].enable()
218 self._messagesViews[self._selectedBackendId].enable()
219 self._contactsViews[self._selectedBackendId].enable()
221 with gtk_toolbox.gtk_lock():
222 self._errorDisplay.push_exception()
224 # Setup maemo specifics
228 except (ImportError, OSError):
231 self._deviceState = None
233 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
234 self._deviceState = osso.DeviceState(self._osso)
235 self._deviceState.set_device_state_callback(self._on_device_state_change, 0)
237 _moduleLogger.warning("No device state support")
241 if alarm_handler.AlarmHandler is not alarm_handler._NoneAlarmHandler:
242 self._alarmHandler = alarm_handler.AlarmHandler()
244 self._alarmHandler = None
245 except (ImportError, OSError):
248 with gtk_toolbox.gtk_lock():
249 self._errorDisplay.push_exception()
251 if alarm_handler is None:
252 _moduleLogger.warning("No notification support")
253 if hildonize.IS_HILDON_SUPPORTED:
256 self._ledHandler = led_handler.LedHandler()
258 _moduleLogger.exception('LED Handling failed: "%s"' % str(e))
259 self._ledHandler = None
261 self._ledHandler = None
265 except (ImportError, OSError):
267 self._connection = None
268 if conic is not None:
269 self._connection = conic.Connection()
270 self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
271 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
273 _moduleLogger.warning("No connection support")
275 with gtk_toolbox.gtk_lock():
276 self._errorDisplay.push_exception()
278 # Setup costly backends
280 from backends import gv_backend
281 from backends import file_backend
283 from backends import merge_backend
285 with gtk_toolbox.gtk_lock():
286 self._smsEntryWindow = gv_views.SmsEntryWindow(self._widgetTree, self._window, self._app)
288 os.makedirs(constants._data_path_)
292 gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
294 self._phoneBackends.update({
295 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
297 with gtk_toolbox.gtk_lock():
298 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
299 self._dialpads.update({
300 self.GV_BACKEND: unifiedDialpad,
302 self._accountViews.update({
303 self.GV_BACKEND: gv_views.AccountInfo(
304 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
307 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
308 self._historyViews.update({
309 self.GV_BACKEND: gv_views.CallHistoryView(
310 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
313 self._messagesViews.update({
314 self.GV_BACKEND: gv_views.MessagesView(
315 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
318 self._contactsViews.update({
319 self.GV_BACKEND: gv_views.ContactsView(
320 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
324 fileBackend = file_backend.FilesystemAddressBookFactory(self._fsContactsPath)
326 self._smsEntryWindow.send_sms = self._on_sms_clicked
327 self._smsEntryWindow.dial = self._on_dial_clicked
328 self._dialpads[self.GV_BACKEND].add_contact = self._add_contact
329 self._dialpads[self.GV_BACKEND].dial = self._on_dial_clicked
330 self._historyViews[self.GV_BACKEND].add_contact = self._add_contact
331 self._messagesViews[self.GV_BACKEND].add_contact = self._add_contact
332 self._contactsViews[self.GV_BACKEND].add_contact = self._add_contact
335 self._phoneBackends[self.GV_BACKEND],
338 mergedBook = merge_backend.MergedAddressBook(addressBooks, merge_backend.MergedAddressBook.basic_firtname_sorter)
339 self._contactsViews[self.GV_BACKEND].append(mergedBook)
340 self._contactsViews[self.GV_BACKEND].extend(addressBooks)
341 self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
344 "on_paste": self._on_paste,
345 "on_refresh": self._on_menu_refresh,
346 "on_clearcookies_clicked": self._on_clearcookies_clicked,
347 "on_about_activate": self._on_about_activate,
349 if hildonize.GTK_MENU_USED:
350 self._widgetTree.signal_autoconnect(callbackMapping)
351 self._notebook.connect("switch-page", self._on_notebook_switch_page)
352 self._widgetTree.get_widget("clearcookies").connect("clicked", self._on_clearcookies_clicked)
354 with gtk_toolbox.gtk_lock():
355 self._originalCurrentLabels = [
356 self._notebook.get_tab_label(self._notebook.get_nth_page(pageIndex)).get_text()
357 for pageIndex in xrange(self._notebook.get_n_pages())
359 self._notebookTapHandler = gtk_toolbox.TapOrHold(self._notebook)
360 self._notebookTapHandler.enable()
361 self._notebookTapHandler.on_tap = self._reset_tab_refresh
362 self._notebookTapHandler.on_hold = self._on_tab_refresh
363 self._notebookTapHandler.on_holding = self._set_tab_refresh
364 self._notebookTapHandler.on_cancel = self._reset_tab_refresh
366 config = ConfigParser.SafeConfigParser()
367 config.read(constants._user_settings_)
368 with gtk_toolbox.gtk_lock():
369 self.load_settings(config)
371 with gtk_toolbox.gtk_lock():
372 self._errorDisplay.push_exception()
374 self._initDone = True
375 self._spawn_attempt_login()
377 def _spawn_attempt_login(self, *args):
378 self._loginSink.send(args)
380 def _attempt_login(self, force = False):
382 @note This must be run outside of the UI lock
385 assert self._initDone, "Attempting login before app is fully loaded"
387 serviceId = self.NULL_BACKEND
389 if not force and self._defaultBackendId != self.NULL_BACKEND:
390 with gtk_toolbox.gtk_lock():
391 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
393 self.refresh_session()
394 serviceId = self._defaultBackendId
397 _moduleLogger.exception('Session refresh failed with the following message "%s"' % str(e))
399 with gtk_toolbox.gtk_lock():
400 hildonize.show_busy_banner_end(banner)
403 loggedIn, serviceId = self._login_by_user()
405 with gtk_toolbox.gtk_lock():
406 self._change_loggedin_status(serviceId)
408 hildonize.show_information_banner(self._window, "Logged In")
410 hildonize.show_information_banner(self._window, "Login Failed")
411 if not self._phoneBackends[self._defaultBackendId].get_callback_number():
412 # subtle reminder to the users to configure things
413 self._notebook.set_current_page(self.ACCOUNT_TAB)
416 with gtk_toolbox.gtk_lock():
417 self._errorDisplay.push_exception()
419 def refresh_session(self):
421 @note Thread agnostic
423 assert self._initDone, "Attempting login before app is fully loaded"
427 loggedIn = self._login_by_cookie()
429 loggedIn = self._login_by_settings()
432 raise RuntimeError("Login Failed")
434 def _login_by_cookie(self):
436 @note Thread agnostic
440 isQuickLoginPossible = self._phoneBackends[self._defaultBackendId].is_quick_login_possible()
441 if self._credentials != ("", "") and isQuickLoginPossible:
443 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
446 _moduleLogger.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
448 # If the cookies are bad, scratch them completely
449 self._phoneBackends[self._defaultBackendId].logout()
453 def _login_by_settings(self):
455 @note Thread agnostic
457 if self._credentials == ("", ""):
458 # Don't bother with the settings if they are blank
461 username, password = self._credentials
462 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
464 self._credentials = username, password
465 _moduleLogger.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
468 def _login_by_user(self):
470 @note This must be run outside of the UI lock
472 loggedIn, (username, password) = False, self._credentials
473 tmpServiceId = self.GV_BACKEND
475 with gtk_toolbox.gtk_lock():
476 credentials = self._credentialsDialog.request_credentials(
477 defaultCredentials = self._credentials
479 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
481 username, password = credentials
482 loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
484 with gtk_toolbox.gtk_lock():
485 hildonize.show_busy_banner_end(banner)
488 serviceId = tmpServiceId
489 self._credentials = username, password
490 _moduleLogger.info("Logged into %r through user request" % self._phoneBackends[serviceId])
492 # Hint to the user that they are not logged in
493 serviceId = self.NULL_BACKEND
494 self._notebook.set_current_page(self.ACCOUNT_TAB)
496 return loggedIn, serviceId
498 def _add_contact(self, *args, **kwds):
499 self._smsEntryWindow.add_contact(*args, **kwds)
501 def _change_loggedin_status(self, newStatus):
502 oldStatus = self._selectedBackendId
503 if oldStatus == newStatus:
506 _moduleLogger.debug("Changing from %s to %s" % (oldStatus, newStatus))
507 self._dialpads[oldStatus].disable()
508 self._accountViews[oldStatus].disable()
509 self._historyViews[oldStatus].disable()
510 self._messagesViews[oldStatus].disable()
511 self._contactsViews[oldStatus].disable()
513 self._dialpads[newStatus].enable()
514 self._accountViews[newStatus].enable()
515 self._historyViews[newStatus].enable()
516 self._messagesViews[newStatus].enable()
517 self._contactsViews[newStatus].enable()
519 self._selectedBackendId = newStatus
521 self._accountViews[self._selectedBackendId].update()
522 self._refresh_active_tab()
523 self._refresh_orientation()
525 def load_settings(self, config):
530 if not PROFILE_STARTUP:
531 self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active")
533 self._defaultBackendId = self.NULL_BACKEND
535 config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
536 for i in xrange(len(self._credentials))
539 base64.b64decode(blob)
542 self._credentials = tuple(creds)
544 if self._alarmHandler is not None:
545 self._alarmHandler.load_settings(config, "alarm")
547 isFullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen")
549 self._window.fullscreen()
551 isPortrait = config.getboolean(constants.__pretty_app_name__, "portrait")
552 if isPortrait ^ self.__isPortrait:
554 orientation = gtk.ORIENTATION_VERTICAL
556 orientation = gtk.ORIENTATION_HORIZONTAL
557 self.set_orientation(orientation)
558 except ConfigParser.NoOptionError, e:
559 _moduleLogger.exception(
560 "Settings file %s is missing section %s" % (
561 constants._user_settings_,
565 except ConfigParser.NoSectionError, e:
566 _moduleLogger.exception(
567 "Settings file %s is missing section %s" % (
568 constants._user_settings_,
573 for backendId, view in itertools.chain(
574 self._dialpads.iteritems(),
575 self._accountViews.iteritems(),
576 self._messagesViews.iteritems(),
577 self._historyViews.iteritems(),
578 self._contactsViews.iteritems(),
580 sectionName = "%s - %s" % (backendId, view.name())
582 view.load_settings(config, sectionName)
583 except ConfigParser.NoOptionError, e:
584 _moduleLogger.exception(
585 "Settings file %s is missing section %s" % (
586 constants._user_settings_,
590 except ConfigParser.NoSectionError, e:
591 _moduleLogger.exception(
592 "Settings file %s is missing section %s" % (
593 constants._user_settings_,
598 def save_settings(self, config):
600 @note Thread Agnostic
602 # Because we now only support GVoice, if there are user credentials,
603 # always assume its using the GVoice backend
604 if self._credentials[0] and self._credentials[1]:
605 backend = self.GV_BACKEND
607 backend = self.NULL_BACKEND
609 config.add_section(constants.__pretty_app_name__)
610 config.set(constants.__pretty_app_name__, "active", str(backend))
611 config.set(constants.__pretty_app_name__, "portrait", str(self.__isPortrait))
612 config.set(constants.__pretty_app_name__, "fullscreen", str(self._isFullScreen))
613 for i, value in enumerate(self._credentials):
614 blob = base64.b64encode(value)
615 config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
616 config.add_section("alarm")
617 if self._alarmHandler is not None:
618 self._alarmHandler.save_settings(config, "alarm")
620 for backendId, view in itertools.chain(
621 self._dialpads.iteritems(),
622 self._accountViews.iteritems(),
623 self._messagesViews.iteritems(),
624 self._historyViews.iteritems(),
625 self._contactsViews.iteritems(),
627 sectionName = "%s - %s" % (backendId, view.name())
628 config.add_section(sectionName)
629 view.save_settings(config, sectionName)
631 def _save_settings(self):
633 @note Thread Agnostic
635 config = ConfigParser.SafeConfigParser()
636 self.save_settings(config)
637 with open(constants._user_settings_, "wb") as configFile:
638 config.write(configFile)
640 def _refresh_active_tab(self):
641 pageIndex = self._notebook.get_current_page()
642 if pageIndex == self.CONTACTS_TAB:
643 self._contactsViews[self._selectedBackendId].update(force=True)
644 elif pageIndex == self.RECENT_TAB:
645 self._historyViews[self._selectedBackendId].update(force=True)
646 elif pageIndex == self.MESSAGES_TAB:
647 self._messagesViews[self._selectedBackendId].update(force=True)
649 if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB):
650 if self._ledHandler is not None:
651 self._ledHandler.off()
653 def set_orientation(self, orientation):
654 if orientation == gtk.ORIENTATION_VERTICAL:
655 hildonize.window_to_portrait(self._window)
656 self._notebook.set_property("tab-pos", gtk.POS_BOTTOM)
657 self.__isPortrait = True
658 elif orientation == gtk.ORIENTATION_HORIZONTAL:
659 hildonize.window_to_landscape(self._window)
660 self._notebook.set_property("tab-pos", gtk.POS_LEFT)
661 self.__isPortrait = False
663 raise NotImplementedError(orientation)
665 def get_orientation(self):
666 return gtk.ORIENTATION_VERTICAL if self.__isPortrait else gtk.ORIENTATION_HORIZONTAL
668 def _toggle_rotate(self):
669 if self.__isPortrait:
670 self.set_orientation(gtk.ORIENTATION_HORIZONTAL)
672 self.set_orientation(gtk.ORIENTATION_VERTICAL)
674 def _refresh_orientation(self):
676 Mostly meant to be used when switching backends
678 if self.__isPortrait:
679 self.set_orientation(gtk.ORIENTATION_VERTICAL)
681 self.set_orientation(gtk.ORIENTATION_HORIZONTAL)
683 @gtk_toolbox.log_exception(_moduleLogger)
684 def _on_close(self, *args, **kwds):
687 self._save_settings()
690 self._deviceState.close()
691 except AttributeError:
692 pass # Either None or close was removed (in Fremantle)
695 except AttributeError:
696 pass # Either None or close was removed (in Fremantle)
700 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
702 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
703 For system_inactivity, we have no background tasks to pause
705 @note Hildon specific
709 for backendId in self.BACKENDS:
710 self._phoneBackends[backendId].clear_caches()
711 self._contactsViews[self._selectedBackendId].clear_caches()
714 if save_unsaved_data or shutdown:
715 self._save_settings()
717 self._errorDisplay.push_exception()
719 def _on_connection_change(self, connection, event, magicIdentifier):
721 @note Hildon specific
726 status = event.get_status()
727 error = event.get_error()
728 iap_id = event.get_iap_id()
729 bearer = event.get_bearer_type()
731 if status == conic.STATUS_CONNECTED:
733 self._spawn_attempt_login()
734 elif status == conic.STATUS_DISCONNECTED:
736 self._defaultBackendId = self._selectedBackendId
737 self._change_loggedin_status(self.NULL_BACKEND)
739 self._errorDisplay.push_exception()
741 def _on_window_state_change(self, widget, event, *args):
743 @note Hildon specific
746 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
747 self._isFullScreen = True
749 self._isFullScreen = False
751 self._errorDisplay.push_exception()
753 def _on_key_press(self, widget, event, *args):
755 @note Hildon specific
757 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
760 event.keyval == gtk.keysyms.F6 or
761 event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK
763 if self._isFullScreen:
764 self._window.unfullscreen()
766 self._window.fullscreen()
767 elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
768 with open(constants._user_logpath_, "r") as f:
769 logLines = f.xreadlines()
770 log = "".join(logLines)
771 self._clipboard.set_text(str(log))
773 event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and
774 event.get_state() & gtk.gdk.CONTROL_MASK
776 self._window.destroy()
777 elif event.keyval == gtk.keysyms.o and event.get_state() & gtk.gdk.CONTROL_MASK:
778 self._toggle_rotate()
780 elif event.keyval == gtk.keysyms.r and event.get_state() & gtk.gdk.CONTROL_MASK:
781 self._refresh_active_tab()
782 elif event.keyval == gtk.keysyms.i and event.get_state() & gtk.gdk.CONTROL_MASK:
783 self._import_contacts()
785 self._errorDisplay.push_exception()
787 def _on_clearcookies_clicked(self, *args):
789 self._phoneBackends[self._selectedBackendId].logout()
790 self._accountViews[self._selectedBackendId].clear()
791 self._historyViews[self._selectedBackendId].clear()
792 self._messagesViews[self._selectedBackendId].clear()
793 self._contactsViews[self._selectedBackendId].clear()
794 self._change_loggedin_status(self.NULL_BACKEND)
796 self._spawn_attempt_login(True)
798 self._errorDisplay.push_exception()
800 def _on_notebook_switch_page(self, notebook, page, pageIndex):
802 self._reset_tab_refresh()
804 didRecentUpdate = False
805 didMessagesUpdate = False
807 if pageIndex == self.RECENT_TAB:
808 didRecentUpdate = self._historyViews[self._selectedBackendId].update()
809 elif pageIndex == self.MESSAGES_TAB:
810 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
811 elif pageIndex == self.CONTACTS_TAB:
812 self._contactsViews[self._selectedBackendId].update()
813 elif pageIndex == self.ACCOUNT_TAB:
814 self._accountViews[self._selectedBackendId].update()
816 if didRecentUpdate or didMessagesUpdate:
817 if self._ledHandler is not None:
818 self._ledHandler.off()
820 self._errorDisplay.push_exception()
822 def _set_tab_refresh(self, *args):
824 pageIndex = self._notebook.get_current_page()
825 child = self._notebook.get_nth_page(pageIndex)
826 self._notebook.get_tab_label(child).set_text("Refresh?")
828 self._errorDisplay.push_exception()
831 def _reset_tab_refresh(self, *args):
833 pageIndex = self._notebook.get_current_page()
834 child = self._notebook.get_nth_page(pageIndex)
835 self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
837 self._errorDisplay.push_exception()
840 def _on_tab_refresh(self, *args):
842 self._refresh_active_tab()
843 self._reset_tab_refresh()
845 self._errorDisplay.push_exception()
848 def _on_sms_clicked(self, numbers, message):
850 assert numbers, "No number specified"
851 assert message, "Empty message"
852 self.refresh_session()
854 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
857 self._errorDisplay.push_exception()
861 self._errorDisplay.push_message(
862 "Backend link with GoogleVoice is not working, please try again"
868 self._phoneBackends[self._selectedBackendId].send_sms(numbers, message)
869 hildonize.show_information_banner(self._window, "Sending to %s" % ", ".join(numbers))
870 _moduleLogger.info("Sending SMS to %r" % numbers)
873 self._errorDisplay.push_exception()
876 self._dialpads[self._selectedBackendId].clear()
878 self._errorDisplay.push_exception()
880 def _on_dial_clicked(self, number):
882 assert number, "No number to call"
883 self.refresh_session()
885 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
888 self._errorDisplay.push_exception()
892 self._errorDisplay.push_message(
893 "Backend link with GoogleVoice is not working, please try again"
899 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
900 self._phoneBackends[self._selectedBackendId].call(number)
901 hildonize.show_information_banner(self._window, "Calling %s" % number)
902 _moduleLogger.info("Calling %s" % number)
905 self._errorDisplay.push_exception()
908 self._dialpads[self._selectedBackendId].clear()
910 self._errorDisplay.push_exception()
912 def _import_contacts(self):
913 csvFilter = gtk.FileFilter()
914 csvFilter.set_name("Contacts")
915 csvFilter.add_pattern("*.csv")
916 importFileChooser = gtk.FileChooserDialog(
920 importFileChooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
921 importFileChooser.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
923 importFileChooser.set_property("filter", csvFilter)
924 userResponse = importFileChooser.run()
925 importFileChooser.hide()
926 if userResponse == gtk.RESPONSE_OK:
927 filename = importFileChooser.get_filename()
928 shutil.copy2(filename, self._fsContactsPath)
930 def _on_menu_refresh(self, *args):
932 self._refresh_active_tab()
934 self._errorDisplay.push_exception()
936 def _on_paste(self, *args):
938 contents = self._clipboard.wait_for_text()
939 if contents is not None:
940 self._dialpads[self._selectedBackendId].set_number(contents)
942 self._errorDisplay.push_exception()
944 def _on_about_activate(self, *args):
946 dlg = gtk.AboutDialog()
947 dlg.set_name(constants.__pretty_app_name__)
948 dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
949 dlg.set_copyright("Copyright 2008 - LGPL")
950 dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice account. This application is not affiliated with Google in any way")
951 dlg.set_website("http://gc-dialer.garage.maemo.org/")
952 dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <eopage@byu.net>"])
956 self._errorDisplay.push_exception()
962 failureCount, testCount = doctest.testmod()
964 print "Tests Successful"
971 gtk.gdk.threads_init()
973 handle = Dialcentral()
974 if not PROFILE_STARTUP:
978 class DummyOptions(object):
984 if __name__ == "__main__":
985 logging.basicConfig(level=logging.DEBUG)
987 if len(sys.argv) > 1:
993 if optparse is not None:
994 parser = optparse.OptionParser()
995 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
996 (commandOptions, commandArgs) = parser.parse_args()
998 commandOptions = DummyOptions()
1001 if commandOptions.test: