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 self._errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
115 self._credentialsDialog = gtk_toolbox.LoginWindow(self._widgetTree)
117 self._isFullScreen = False
118 self._app = hildonize.get_app_class()()
119 self._window = hildonize.hildonize_window(self._app, self._window)
120 hildonize.hildonize_text_entry(self._widgetTree.get_widget("usernameentry"))
121 hildonize.hildonize_password_entry(self._widgetTree.get_widget("passwordentry"))
123 for scrollingWidgetName in (
124 'history_scrolledwindow',
125 'message_scrolledwindow',
126 'contacts_scrolledwindow',
127 "smsMessages_scrolledwindow",
129 scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName)
130 assert scrollingWidget is not None, scrollingWidgetName
131 hildonize.hildonize_scrollwindow(scrollingWidget)
132 for scrollingWidgetName in (
133 "smsMessage_scrolledEntry",
135 scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName)
136 assert scrollingWidget is not None, scrollingWidgetName
137 hildonize.hildonize_scrollwindow_with_viewport(scrollingWidget)
141 "addressbookSelectButton",
145 "callbackSelectButton",
146 "minutesEntryButton",
148 "phoneTypeSelection",
150 button = self._widgetTree.get_widget(buttonName)
151 assert button is not None, buttonName
152 hildonize.set_button_thumb_selectable(button)
154 menu = hildonize.hildonize_menu(
156 self._widgetTree.get_widget("dialpad_menubar"),
158 if not hildonize.GTK_MENU_USED:
159 button = gtk.Button("New Login")
160 button.connect("clicked", self._on_clearcookies_clicked)
163 button = gtk.Button("Import Contacts")
164 button.connect("clicked", self._on_contact_import)
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_title(self._window, "%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
286 os.makedirs(constants._data_path_)
290 gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
292 self._phoneBackends.update({
293 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
295 with gtk_toolbox.gtk_lock():
296 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
297 self._dialpads.update({
298 self.GV_BACKEND: unifiedDialpad,
300 self._accountViews.update({
301 self.GV_BACKEND: gv_views.AccountInfo(
302 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
305 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
306 self._historyViews.update({
307 self.GV_BACKEND: gv_views.CallHistoryView(
308 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
311 self._messagesViews.update({
312 self.GV_BACKEND: gv_views.MessagesView(
313 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
316 self._contactsViews.update({
317 self.GV_BACKEND: gv_views.ContactsView(
318 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
322 fileBackend = file_backend.FilesystemAddressBookFactory(self._fsContactsPath)
324 self._dialpads[self.GV_BACKEND].number_selected = self._select_action
325 self._historyViews[self.GV_BACKEND].number_selected = self._select_action
326 self._messagesViews[self.GV_BACKEND].number_selected = self._select_action
327 self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
330 self._phoneBackends[self.GV_BACKEND],
333 mergedBook = merge_backend.MergedAddressBook(addressBooks, merge_backend.MergedAddressBook.basic_firtname_sorter)
334 self._contactsViews[self.GV_BACKEND].append(mergedBook)
335 self._contactsViews[self.GV_BACKEND].extend(addressBooks)
336 self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
339 "on_paste": self._on_paste,
340 "on_refresh": self._on_menu_refresh,
341 "on_clearcookies_clicked": self._on_clearcookies_clicked,
342 "on_about_activate": self._on_about_activate,
343 "on_import": self._on_contact_import,
345 if hildonize.GTK_MENU_USED:
346 self._widgetTree.signal_autoconnect(callbackMapping)
347 self._notebook.connect("switch-page", self._on_notebook_switch_page)
348 self._widgetTree.get_widget("clearcookies").connect("clicked", self._on_clearcookies_clicked)
350 with gtk_toolbox.gtk_lock():
351 self._originalCurrentLabels = [
352 self._notebook.get_tab_label(self._notebook.get_nth_page(pageIndex)).get_text()
353 for pageIndex in xrange(self._notebook.get_n_pages())
355 self._notebookTapHandler = gtk_toolbox.TapOrHold(self._notebook)
356 self._notebookTapHandler.enable()
357 self._notebookTapHandler.on_tap = self._reset_tab_refresh
358 self._notebookTapHandler.on_hold = self._on_tab_refresh
359 self._notebookTapHandler.on_holding = self._set_tab_refresh
360 self._notebookTapHandler.on_cancel = self._reset_tab_refresh
362 config = ConfigParser.SafeConfigParser()
363 config.read(constants._user_settings_)
364 with gtk_toolbox.gtk_lock():
365 self.load_settings(config)
367 with gtk_toolbox.gtk_lock():
368 self._errorDisplay.push_exception()
370 self._initDone = True
371 self._spawn_attempt_login()
373 def _spawn_attempt_login(self, *args):
374 self._loginSink.send(args)
376 def _attempt_login(self, force = False):
378 @note This must be run outside of the UI lock
381 assert self._initDone, "Attempting login before app is fully loaded"
383 serviceId = self.NULL_BACKEND
385 if not force and self._defaultBackendId != self.NULL_BACKEND:
386 with gtk_toolbox.gtk_lock():
387 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
389 self.refresh_session()
390 serviceId = self._defaultBackendId
393 _moduleLogger.exception('Session refresh failed with the following message "%s"' % str(e))
395 with gtk_toolbox.gtk_lock():
396 hildonize.show_busy_banner_end(banner)
399 loggedIn, serviceId = self._login_by_user()
401 with gtk_toolbox.gtk_lock():
402 self._change_loggedin_status(serviceId)
404 hildonize.show_information_banner(self._window, "Logged In")
406 hildonize.show_information_banner(self._window, "Login Failed")
407 if not self._phoneBackends[self._defaultBackendId].get_callback_number():
408 # subtle reminder to the users to configure things
409 self._notebook.set_current_page(self.ACCOUNT_TAB)
412 with gtk_toolbox.gtk_lock():
413 self._errorDisplay.push_exception()
415 def refresh_session(self):
417 @note Thread agnostic
419 assert self._initDone, "Attempting login before app is fully loaded"
423 loggedIn = self._login_by_cookie()
425 loggedIn = self._login_by_settings()
428 raise RuntimeError("Login Failed")
430 def _login_by_cookie(self):
432 @note Thread agnostic
436 isQuickLoginPossible = self._phoneBackends[self._defaultBackendId].is_quick_login_possible()
437 if self._credentials != ("", "") and isQuickLoginPossible:
439 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
442 _moduleLogger.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
444 # If the cookies are bad, scratch them completely
445 self._phoneBackends[self._defaultBackendId].logout()
449 def _login_by_settings(self):
451 @note Thread agnostic
453 if self._credentials == ("", ""):
454 # Don't bother with the settings if they are blank
457 username, password = self._credentials
458 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
460 self._credentials = username, password
461 _moduleLogger.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
464 def _login_by_user(self):
466 @note This must be run outside of the UI lock
468 loggedIn, (username, password) = False, self._credentials
469 tmpServiceId = self.GV_BACKEND
471 with gtk_toolbox.gtk_lock():
472 credentials = self._credentialsDialog.request_credentials(
473 defaultCredentials = self._credentials
475 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
477 username, password = credentials
478 loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
480 with gtk_toolbox.gtk_lock():
481 hildonize.show_busy_banner_end(banner)
484 serviceId = tmpServiceId
485 self._credentials = username, password
486 _moduleLogger.info("Logged into %r through user request" % self._phoneBackends[serviceId])
488 # Hint to the user that they are not logged in
489 serviceId = self.NULL_BACKEND
490 self._notebook.set_current_page(self.ACCOUNT_TAB)
492 return loggedIn, serviceId
494 def _select_action(self, action, numbers, message):
495 self.refresh_session()
497 assert len(numbers) == 1
499 self._on_dial_clicked(number)
500 elif action == "sms":
501 self._on_sms_clicked(numbers, message)
503 assert False, "Unknown action: %s" % action
505 def _change_loggedin_status(self, newStatus):
506 oldStatus = self._selectedBackendId
507 if oldStatus == newStatus:
510 self._dialpads[oldStatus].disable()
511 self._accountViews[oldStatus].disable()
512 self._historyViews[oldStatus].disable()
513 self._messagesViews[oldStatus].disable()
514 self._contactsViews[oldStatus].disable()
516 self._dialpads[newStatus].enable()
517 self._accountViews[newStatus].enable()
518 self._historyViews[newStatus].enable()
519 self._messagesViews[newStatus].enable()
520 self._contactsViews[newStatus].enable()
522 self._selectedBackendId = newStatus
524 self._accountViews[self._selectedBackendId].update()
525 self._refresh_active_tab()
527 def load_settings(self, config):
532 if not PROFILE_STARTUP:
533 self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active")
535 self._defaultBackendId = self.NULL_BACKEND
537 config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
538 for i in xrange(len(self._credentials))
541 base64.b64decode(blob)
544 self._credentials = tuple(creds)
546 if self._alarmHandler is not None:
547 self._alarmHandler.load_settings(config, "alarm")
549 isFullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen")
551 self._window.fullscreen()
552 except ConfigParser.NoOptionError, e:
553 _moduleLogger.exception(
554 "Settings file %s is missing section %s" % (
555 constants._user_settings_,
559 except ConfigParser.NoSectionError, e:
560 _moduleLogger.exception(
561 "Settings file %s is missing section %s" % (
562 constants._user_settings_,
567 for backendId, view in itertools.chain(
568 self._dialpads.iteritems(),
569 self._accountViews.iteritems(),
570 self._messagesViews.iteritems(),
571 self._historyViews.iteritems(),
572 self._contactsViews.iteritems(),
574 sectionName = "%s - %s" % (backendId, view.name())
576 view.load_settings(config, sectionName)
577 except ConfigParser.NoOptionError, e:
578 _moduleLogger.exception(
579 "Settings file %s is missing section %s" % (
580 constants._user_settings_,
584 except ConfigParser.NoSectionError, e:
585 _moduleLogger.exception(
586 "Settings file %s is missing section %s" % (
587 constants._user_settings_,
593 previousOrientation = config.getint(constants.__pretty_app_name__, "orientation")
594 if previousOrientation == gtk.ORIENTATION_HORIZONTAL:
595 hildonize.window_to_landscape(self._window)
596 elif previousOrientation == gtk.ORIENTATION_VERTICAL:
597 hildonize.window_to_portrait(self._window)
598 except ConfigParser.NoOptionError, e:
599 _moduleLogger.exception(
600 "Settings file %s is missing section %s" % (
601 constants._user_settings_,
605 except ConfigParser.NoSectionError, e:
606 _moduleLogger.exception(
607 "Settings file %s is missing section %s" % (
608 constants._user_settings_,
613 def save_settings(self, config):
615 @note Thread Agnostic
617 # Because we now only support GVoice, if there are user credentials,
618 # always assume its using the GVoice backend
619 if self._credentials[0] and self._credentials[1]:
620 backend = self.GV_BACKEND
622 backend = self.NULL_BACKEND
624 config.add_section(constants.__pretty_app_name__)
625 config.set(constants.__pretty_app_name__, "active", str(backend))
626 config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation())))
627 config.set(constants.__pretty_app_name__, "fullscreen", str(self._isFullScreen))
628 for i, value in enumerate(self._credentials):
629 blob = base64.b64encode(value)
630 config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
631 config.add_section("alarm")
632 if self._alarmHandler is not None:
633 self._alarmHandler.save_settings(config, "alarm")
635 for backendId, view in itertools.chain(
636 self._dialpads.iteritems(),
637 self._accountViews.iteritems(),
638 self._messagesViews.iteritems(),
639 self._historyViews.iteritems(),
640 self._contactsViews.iteritems(),
642 sectionName = "%s - %s" % (backendId, view.name())
643 config.add_section(sectionName)
644 view.save_settings(config, sectionName)
646 def _save_settings(self):
648 @note Thread Agnostic
650 config = ConfigParser.SafeConfigParser()
651 self.save_settings(config)
652 with open(constants._user_settings_, "wb") as configFile:
653 config.write(configFile)
655 def _refresh_active_tab(self):
656 pageIndex = self._notebook.get_current_page()
657 if pageIndex == self.CONTACTS_TAB:
658 self._contactsViews[self._selectedBackendId].update(force=True)
659 elif pageIndex == self.RECENT_TAB:
660 self._historyViews[self._selectedBackendId].update(force=True)
661 elif pageIndex == self.MESSAGES_TAB:
662 self._messagesViews[self._selectedBackendId].update(force=True)
664 if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB):
665 if self._ledHandler is not None:
666 self._ledHandler.off()
668 @gtk_toolbox.log_exception(_moduleLogger)
669 def _on_close(self, *args, **kwds):
672 self._save_settings()
675 self._deviceState.close()
676 except AttributeError:
677 pass # Either None or close was removed (in Fremantle)
680 except AttributeError:
681 pass # Either None or close was removed (in Fremantle)
685 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
687 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
688 For system_inactivity, we have no background tasks to pause
690 @note Hildon specific
694 for backendId in self.BACKENDS:
695 self._phoneBackends[backendId].clear_caches()
696 self._contactsViews[self._selectedBackendId].clear_caches()
699 if save_unsaved_data or shutdown:
700 self._save_settings()
702 self._errorDisplay.push_exception()
704 def _on_connection_change(self, connection, event, magicIdentifier):
706 @note Hildon specific
711 status = event.get_status()
712 error = event.get_error()
713 iap_id = event.get_iap_id()
714 bearer = event.get_bearer_type()
716 if status == conic.STATUS_CONNECTED:
718 self._spawn_attempt_login()
719 elif status == conic.STATUS_DISCONNECTED:
721 self._defaultBackendId = self._selectedBackendId
722 self._change_loggedin_status(self.NULL_BACKEND)
724 self._errorDisplay.push_exception()
726 def _on_window_state_change(self, widget, event, *args):
728 @note Hildon specific
731 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
732 self._isFullScreen = True
734 self._isFullScreen = False
736 self._errorDisplay.push_exception()
738 def _on_key_press(self, widget, event, *args):
740 @note Hildon specific
742 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
745 event.keyval == gtk.keysyms.F6 or
746 event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK
748 if self._isFullScreen:
749 self._window.unfullscreen()
751 self._window.fullscreen()
752 elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
753 with open(constants._user_logpath_, "r") as f:
754 logLines = f.xreadlines()
755 log = "".join(logLines)
756 self._clipboard.set_text(str(log))
758 event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and
759 event.get_state() & gtk.gdk.CONTROL_MASK
761 self._window.destroy()
762 elif event.keyval == gtk.keysyms.r and event.get_state() & gtk.gdk.CONTROL_MASK:
763 self._refresh_active_tab()
765 self._errorDisplay.push_exception()
767 def _on_clearcookies_clicked(self, *args):
769 self._phoneBackends[self._selectedBackendId].logout()
770 self._accountViews[self._selectedBackendId].clear()
771 self._historyViews[self._selectedBackendId].clear()
772 self._messagesViews[self._selectedBackendId].clear()
773 self._contactsViews[self._selectedBackendId].clear()
774 self._change_loggedin_status(self.NULL_BACKEND)
776 self._spawn_attempt_login(True)
778 self._errorDisplay.push_exception()
780 def _on_notebook_switch_page(self, notebook, page, pageIndex):
782 self._reset_tab_refresh()
784 didRecentUpdate = False
785 didMessagesUpdate = False
787 if pageIndex == self.RECENT_TAB:
788 didRecentUpdate = self._historyViews[self._selectedBackendId].update()
789 elif pageIndex == self.MESSAGES_TAB:
790 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
791 elif pageIndex == self.CONTACTS_TAB:
792 self._contactsViews[self._selectedBackendId].update()
793 elif pageIndex == self.ACCOUNT_TAB:
794 self._accountViews[self._selectedBackendId].update()
796 if didRecentUpdate or didMessagesUpdate:
797 if self._ledHandler is not None:
798 self._ledHandler.off()
800 self._errorDisplay.push_exception()
802 def _set_tab_refresh(self, *args):
804 pageIndex = self._notebook.get_current_page()
805 child = self._notebook.get_nth_page(pageIndex)
806 self._notebook.get_tab_label(child).set_text("Refresh?")
808 self._errorDisplay.push_exception()
811 def _reset_tab_refresh(self, *args):
813 pageIndex = self._notebook.get_current_page()
814 child = self._notebook.get_nth_page(pageIndex)
815 self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
817 self._errorDisplay.push_exception()
820 def _on_tab_refresh(self, *args):
822 self._refresh_active_tab()
823 self._reset_tab_refresh()
825 self._errorDisplay.push_exception()
828 def _on_sms_clicked(self, numbers, message):
830 assert numbers, "No number specified"
831 assert message, "Empty message"
833 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
836 self._errorDisplay.push_exception()
840 self._errorDisplay.push_message(
841 "Backend link with GoogleVoice is not working, please try again"
847 self._phoneBackends[self._selectedBackendId].send_sms(numbers, message)
848 hildonize.show_information_banner(self._window, "Sending to %s" % ", ".join(numbers))
849 _moduleLogger.info("Sending SMS to %r" % numbers)
852 self._errorDisplay.push_exception()
855 self._dialpads[self._selectedBackendId].clear()
857 self._errorDisplay.push_exception()
859 def _on_dial_clicked(self, number):
861 assert number, "No number to call"
863 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
866 self._errorDisplay.push_exception()
870 self._errorDisplay.push_message(
871 "Backend link with GoogleVoice is not working, please try again"
877 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
878 self._phoneBackends[self._selectedBackendId].call(number)
879 hildonize.show_information_banner(self._window, "Calling %s" % number)
880 _moduleLogger.info("Calling %s" % number)
883 self._errorDisplay.push_exception()
886 self._dialpads[self._selectedBackendId].clear()
888 self._errorDisplay.push_exception()
890 def _on_contact_import(self, *args):
892 csvFilter = gtk.FileFilter()
893 csvFilter.set_name("Contacts")
894 csvFilter.add_pattern("*.csv")
895 importFileChooser = gtk.FileChooserDialog(
899 importFileChooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
900 importFileChooser.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
902 importFileChooser.set_property("filter", csvFilter)
903 userResponse = importFileChooser.run()
904 importFileChooser.hide()
905 if userResponse == gtk.RESPONSE_OK:
906 filename = importFileChooser.get_filename()
907 shutil.copy2(filename, self._fsContactsPath)
909 self._errorDisplay.push_exception()
911 def _on_menu_refresh(self, *args):
913 self._refresh_active_tab()
915 self._errorDisplay.push_exception()
917 def _on_paste(self, *args):
919 contents = self._clipboard.wait_for_text()
920 if contents is not None:
921 self._dialpads[self._selectedBackendId].set_number(contents)
923 self._errorDisplay.push_exception()
925 def _on_about_activate(self, *args):
927 dlg = gtk.AboutDialog()
928 dlg.set_name(constants.__pretty_app_name__)
929 dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
930 dlg.set_copyright("Copyright 2008 - LGPL")
931 dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice account. This application is not affiliated with Google in any way")
932 dlg.set_website("http://gc-dialer.garage.maemo.org/")
933 dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <eopage@byu.net>"])
937 self._errorDisplay.push_exception()
943 failureCount, testCount = doctest.testmod()
945 print "Tests Successful"
952 _lock_file = os.path.join(constants._data_path_, ".lock")
953 #with gtk_toolbox.flock(_lock_file, 0):
954 gtk.gdk.threads_init()
956 if hildonize.IS_HILDON_SUPPORTED:
957 gtk.set_application_name(constants.__pretty_app_name__)
958 handle = Dialcentral()
959 if not PROFILE_STARTUP:
963 class DummyOptions(object):
969 if __name__ == "__main__":
970 logging.basicConfig(level=logging.DEBUG)
972 if len(sys.argv) > 1:
978 if optparse is not None:
979 parser = optparse.OptionParser()
980 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
981 (commandOptions, commandArgs) = parser.parse_args()
983 commandOptions = DummyOptions()
986 if commandOptions.test: