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._app = hildonize.get_app_class()()
123 self._window = hildonize.hildonize_window(self._app, self._window)
124 hildonize.hildonize_text_entry(self._widgetTree.get_widget("usernameentry"))
125 hildonize.hildonize_password_entry(self._widgetTree.get_widget("passwordentry"))
127 for scrollingWidgetName in (
128 'history_scrolledwindow',
129 'message_scrolledwindow',
130 'contacts_scrolledwindow',
131 "smsMessages_scrolledwindow",
133 scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName)
134 assert scrollingWidget is not None, scrollingWidgetName
135 hildonize.hildonize_scrollwindow(scrollingWidget)
136 for scrollingWidgetName in (
137 "smsMessage_scrolledEntry",
139 scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName)
140 assert scrollingWidget is not None, scrollingWidgetName
141 hildonize.hildonize_scrollwindow_with_viewport(scrollingWidget)
145 "addressbookSelectButton",
148 "callbackSelectButton",
149 "minutesEntryButton",
151 "phoneTypeSelection",
153 button = self._widgetTree.get_widget(buttonName)
154 assert button is not None, buttonName
155 hildonize.set_button_thumb_selectable(button)
157 menu = hildonize.hildonize_menu(
159 self._widgetTree.get_widget("dialpad_menubar"),
161 if not hildonize.GTK_MENU_USED:
162 button = gtk.Button("New Login")
163 button.connect("clicked", self._on_clearcookies_clicked)
166 button = gtk.Button("Refresh")
167 button.connect("clicked", self._on_menu_refresh)
172 self._window.connect("key-press-event", self._on_key_press)
173 self._window.connect("window-state-event", self._on_window_state_change)
174 if not hildonize.IS_HILDON_SUPPORTED:
175 _moduleLogger.warning("No hildonization support")
177 hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
179 self._window.connect("destroy", self._on_close)
180 self._window.set_default_size(800, 300)
181 self._window.show_all()
183 self._loginSink = gtk_toolbox.threaded_stage(
186 gtk_toolbox.null_sink(),
190 if not PROFILE_STARTUP:
191 backgroundSetup = threading.Thread(target=self._idle_setup)
192 backgroundSetup.setDaemon(True)
193 backgroundSetup.start()
197 def _idle_setup(self):
199 If something can be done after the UI loads, push it here so it's not blocking the UI
201 # Barebones UI handlers
203 from backends import null_backend
206 self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()}
207 with gtk_toolbox.gtk_lock():
208 self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)}
209 self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)}
210 self._historyViews = {self.NULL_BACKEND: null_views.CallHistoryView(self._widgetTree)}
211 self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)}
212 self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)}
214 self._dialpads[self._selectedBackendId].enable()
215 self._accountViews[self._selectedBackendId].enable()
216 self._historyViews[self._selectedBackendId].enable()
217 self._messagesViews[self._selectedBackendId].enable()
218 self._contactsViews[self._selectedBackendId].enable()
220 with gtk_toolbox.gtk_lock():
221 self._errorDisplay.push_exception()
223 # Setup maemo specifics
227 except (ImportError, OSError):
230 self._deviceState = None
232 self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
233 self._deviceState = osso.DeviceState(self._osso)
234 self._deviceState.set_device_state_callback(self._on_device_state_change, 0)
236 _moduleLogger.warning("No device state support")
240 if alarm_handler.AlarmHandler is not alarm_handler._NoneAlarmHandler:
241 self._alarmHandler = alarm_handler.AlarmHandler()
243 self._alarmHandler = None
244 except (ImportError, OSError):
247 with gtk_toolbox.gtk_lock():
248 self._errorDisplay.push_exception()
250 if alarm_handler is None:
251 _moduleLogger.warning("No notification support")
252 if hildonize.IS_HILDON_SUPPORTED:
255 self._ledHandler = led_handler.LedHandler()
257 _moduleLogger.exception('LED Handling failed: "%s"' % str(e))
258 self._ledHandler = None
260 self._ledHandler = None
264 except (ImportError, OSError):
266 self._connection = None
267 if conic is not None:
268 self._connection = conic.Connection()
269 self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
270 self._connection.request_connection(conic.CONNECT_FLAG_NONE)
272 _moduleLogger.warning("No connection support")
274 with gtk_toolbox.gtk_lock():
275 self._errorDisplay.push_exception()
277 # Setup costly backends
279 from backends import gv_backend
280 from backends import file_backend
282 from backends import merge_backend
284 with gtk_toolbox.gtk_lock():
285 self._smsEntryWindow = gv_views.SmsEntryWindow(self._widgetTree, self._window, self._app)
287 os.makedirs(constants._data_path_)
291 gvCookiePath = os.path.join(constants._data_path_, "gv_cookies.txt")
293 self._phoneBackends.update({
294 self.GV_BACKEND: gv_backend.GVDialer(gvCookiePath),
296 with gtk_toolbox.gtk_lock():
297 unifiedDialpad = gv_views.Dialpad(self._widgetTree, self._errorDisplay)
298 self._dialpads.update({
299 self.GV_BACKEND: unifiedDialpad,
301 self._accountViews.update({
302 self.GV_BACKEND: gv_views.AccountInfo(
303 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._alarmHandler, self._errorDisplay
306 self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
307 self._historyViews.update({
308 self.GV_BACKEND: gv_views.CallHistoryView(
309 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
312 self._messagesViews.update({
313 self.GV_BACKEND: gv_views.MessagesView(
314 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
317 self._contactsViews.update({
318 self.GV_BACKEND: gv_views.ContactsView(
319 self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
323 fileBackend = file_backend.FilesystemAddressBookFactory(self._fsContactsPath)
325 self._smsEntryWindow.send_sms = self._on_sms_clicked
326 self._smsEntryWindow.dial = self._on_dial_clicked
327 self._dialpads[self.GV_BACKEND].add_contact = self._add_contact
328 self._historyViews[self.GV_BACKEND].add_contact = self._add_contact
329 self._messagesViews[self.GV_BACKEND].add_contact = self._add_contact
330 self._contactsViews[self.GV_BACKEND].add_contact = self._add_contact
333 self._phoneBackends[self.GV_BACKEND],
336 mergedBook = merge_backend.MergedAddressBook(addressBooks, merge_backend.MergedAddressBook.basic_firtname_sorter)
337 self._contactsViews[self.GV_BACKEND].append(mergedBook)
338 self._contactsViews[self.GV_BACKEND].extend(addressBooks)
339 self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
342 "on_paste": self._on_paste,
343 "on_refresh": self._on_menu_refresh,
344 "on_clearcookies_clicked": self._on_clearcookies_clicked,
345 "on_about_activate": self._on_about_activate,
347 if hildonize.GTK_MENU_USED:
348 self._widgetTree.signal_autoconnect(callbackMapping)
349 self._notebook.connect("switch-page", self._on_notebook_switch_page)
350 self._widgetTree.get_widget("clearcookies").connect("clicked", self._on_clearcookies_clicked)
352 with gtk_toolbox.gtk_lock():
353 self._originalCurrentLabels = [
354 self._notebook.get_tab_label(self._notebook.get_nth_page(pageIndex)).get_text()
355 for pageIndex in xrange(self._notebook.get_n_pages())
357 self._notebookTapHandler = gtk_toolbox.TapOrHold(self._notebook)
358 self._notebookTapHandler.enable()
359 self._notebookTapHandler.on_tap = self._reset_tab_refresh
360 self._notebookTapHandler.on_hold = self._on_tab_refresh
361 self._notebookTapHandler.on_holding = self._set_tab_refresh
362 self._notebookTapHandler.on_cancel = self._reset_tab_refresh
364 config = ConfigParser.SafeConfigParser()
365 config.read(constants._user_settings_)
366 with gtk_toolbox.gtk_lock():
367 self.load_settings(config)
369 with gtk_toolbox.gtk_lock():
370 self._errorDisplay.push_exception()
372 self._initDone = True
373 self._spawn_attempt_login()
375 def _spawn_attempt_login(self, *args):
376 self._loginSink.send(args)
378 def _attempt_login(self, force = False):
380 @note This must be run outside of the UI lock
383 assert self._initDone, "Attempting login before app is fully loaded"
385 serviceId = self.NULL_BACKEND
387 if not force and self._defaultBackendId != self.NULL_BACKEND:
388 with gtk_toolbox.gtk_lock():
389 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
391 self.refresh_session()
392 serviceId = self._defaultBackendId
395 _moduleLogger.exception('Session refresh failed with the following message "%s"' % str(e))
397 with gtk_toolbox.gtk_lock():
398 hildonize.show_busy_banner_end(banner)
401 loggedIn, serviceId = self._login_by_user()
403 with gtk_toolbox.gtk_lock():
404 self._change_loggedin_status(serviceId)
406 hildonize.show_information_banner(self._window, "Logged In")
408 hildonize.show_information_banner(self._window, "Login Failed")
409 if not self._phoneBackends[self._defaultBackendId].get_callback_number():
410 # subtle reminder to the users to configure things
411 self._notebook.set_current_page(self.ACCOUNT_TAB)
414 with gtk_toolbox.gtk_lock():
415 self._errorDisplay.push_exception()
417 def refresh_session(self):
419 @note Thread agnostic
421 assert self._initDone, "Attempting login before app is fully loaded"
425 loggedIn = self._login_by_cookie()
427 loggedIn = self._login_by_settings()
430 raise RuntimeError("Login Failed")
432 def _login_by_cookie(self):
434 @note Thread agnostic
438 isQuickLoginPossible = self._phoneBackends[self._defaultBackendId].is_quick_login_possible()
439 if self._credentials != ("", "") and isQuickLoginPossible:
441 loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
444 _moduleLogger.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
446 # If the cookies are bad, scratch them completely
447 self._phoneBackends[self._defaultBackendId].logout()
451 def _login_by_settings(self):
453 @note Thread agnostic
455 if self._credentials == ("", ""):
456 # Don't bother with the settings if they are blank
459 username, password = self._credentials
460 loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
462 self._credentials = username, password
463 _moduleLogger.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
466 def _login_by_user(self):
468 @note This must be run outside of the UI lock
470 loggedIn, (username, password) = False, self._credentials
471 tmpServiceId = self.GV_BACKEND
473 with gtk_toolbox.gtk_lock():
474 credentials = self._credentialsDialog.request_credentials(
475 defaultCredentials = self._credentials
477 banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
479 username, password = credentials
480 loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
482 with gtk_toolbox.gtk_lock():
483 hildonize.show_busy_banner_end(banner)
486 serviceId = tmpServiceId
487 self._credentials = username, password
488 _moduleLogger.info("Logged into %r through user request" % self._phoneBackends[serviceId])
490 # Hint to the user that they are not logged in
491 serviceId = self.NULL_BACKEND
492 self._notebook.set_current_page(self.ACCOUNT_TAB)
494 return loggedIn, serviceId
496 def _add_contact(self, *args, **kwds):
497 self._smsEntryWindow.add_contact(*args, **kwds)
499 def _change_loggedin_status(self, newStatus):
500 oldStatus = self._selectedBackendId
501 if oldStatus == newStatus:
504 self._dialpads[oldStatus].disable()
505 self._accountViews[oldStatus].disable()
506 self._historyViews[oldStatus].disable()
507 self._messagesViews[oldStatus].disable()
508 self._contactsViews[oldStatus].disable()
510 self._dialpads[newStatus].enable()
511 self._accountViews[newStatus].enable()
512 self._historyViews[newStatus].enable()
513 self._messagesViews[newStatus].enable()
514 self._contactsViews[newStatus].enable()
516 self._selectedBackendId = newStatus
518 self._accountViews[self._selectedBackendId].update()
519 self._refresh_active_tab()
521 def load_settings(self, config):
526 if not PROFILE_STARTUP:
527 self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active")
529 self._defaultBackendId = self.NULL_BACKEND
531 config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
532 for i in xrange(len(self._credentials))
535 base64.b64decode(blob)
538 self._credentials = tuple(creds)
540 if self._alarmHandler is not None:
541 self._alarmHandler.load_settings(config, "alarm")
543 isFullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen")
545 self._window.fullscreen()
546 except ConfigParser.NoOptionError, e:
547 _moduleLogger.exception(
548 "Settings file %s is missing section %s" % (
549 constants._user_settings_,
553 except ConfigParser.NoSectionError, e:
554 _moduleLogger.exception(
555 "Settings file %s is missing section %s" % (
556 constants._user_settings_,
561 for backendId, view in itertools.chain(
562 self._dialpads.iteritems(),
563 self._accountViews.iteritems(),
564 self._messagesViews.iteritems(),
565 self._historyViews.iteritems(),
566 self._contactsViews.iteritems(),
568 sectionName = "%s - %s" % (backendId, view.name())
570 view.load_settings(config, sectionName)
571 except ConfigParser.NoOptionError, e:
572 _moduleLogger.exception(
573 "Settings file %s is missing section %s" % (
574 constants._user_settings_,
578 except ConfigParser.NoSectionError, e:
579 _moduleLogger.exception(
580 "Settings file %s is missing section %s" % (
581 constants._user_settings_,
587 previousOrientation = config.getint(constants.__pretty_app_name__, "orientation")
588 if previousOrientation == gtk.ORIENTATION_HORIZONTAL:
589 hildonize.window_to_landscape(self._window)
590 elif previousOrientation == gtk.ORIENTATION_VERTICAL:
591 hildonize.window_to_portrait(self._window)
592 except ConfigParser.NoOptionError, e:
593 _moduleLogger.exception(
594 "Settings file %s is missing section %s" % (
595 constants._user_settings_,
599 except ConfigParser.NoSectionError, e:
600 _moduleLogger.exception(
601 "Settings file %s is missing section %s" % (
602 constants._user_settings_,
607 def save_settings(self, config):
609 @note Thread Agnostic
611 # Because we now only support GVoice, if there are user credentials,
612 # always assume its using the GVoice backend
613 if self._credentials[0] and self._credentials[1]:
614 backend = self.GV_BACKEND
616 backend = self.NULL_BACKEND
618 config.add_section(constants.__pretty_app_name__)
619 config.set(constants.__pretty_app_name__, "active", str(backend))
620 config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation())))
621 config.set(constants.__pretty_app_name__, "fullscreen", str(self._isFullScreen))
622 for i, value in enumerate(self._credentials):
623 blob = base64.b64encode(value)
624 config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
625 config.add_section("alarm")
626 if self._alarmHandler is not None:
627 self._alarmHandler.save_settings(config, "alarm")
629 for backendId, view in itertools.chain(
630 self._dialpads.iteritems(),
631 self._accountViews.iteritems(),
632 self._messagesViews.iteritems(),
633 self._historyViews.iteritems(),
634 self._contactsViews.iteritems(),
636 sectionName = "%s - %s" % (backendId, view.name())
637 config.add_section(sectionName)
638 view.save_settings(config, sectionName)
640 def _save_settings(self):
642 @note Thread Agnostic
644 config = ConfigParser.SafeConfigParser()
645 self.save_settings(config)
646 with open(constants._user_settings_, "wb") as configFile:
647 config.write(configFile)
649 def _refresh_active_tab(self):
650 pageIndex = self._notebook.get_current_page()
651 if pageIndex == self.CONTACTS_TAB:
652 self._contactsViews[self._selectedBackendId].update(force=True)
653 elif pageIndex == self.RECENT_TAB:
654 self._historyViews[self._selectedBackendId].update(force=True)
655 elif pageIndex == self.MESSAGES_TAB:
656 self._messagesViews[self._selectedBackendId].update(force=True)
658 if pageIndex in (self.RECENT_TAB, self.MESSAGES_TAB):
659 if self._ledHandler is not None:
660 self._ledHandler.off()
662 @gtk_toolbox.log_exception(_moduleLogger)
663 def _on_close(self, *args, **kwds):
666 self._save_settings()
669 self._deviceState.close()
670 except AttributeError:
671 pass # Either None or close was removed (in Fremantle)
674 except AttributeError:
675 pass # Either None or close was removed (in Fremantle)
679 def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
681 For shutdown or save_unsaved_data, our only state is cookies and I think the cookie manager handles that for us.
682 For system_inactivity, we have no background tasks to pause
684 @note Hildon specific
688 for backendId in self.BACKENDS:
689 self._phoneBackends[backendId].clear_caches()
690 self._contactsViews[self._selectedBackendId].clear_caches()
693 if save_unsaved_data or shutdown:
694 self._save_settings()
696 self._errorDisplay.push_exception()
698 def _on_connection_change(self, connection, event, magicIdentifier):
700 @note Hildon specific
705 status = event.get_status()
706 error = event.get_error()
707 iap_id = event.get_iap_id()
708 bearer = event.get_bearer_type()
710 if status == conic.STATUS_CONNECTED:
712 self._spawn_attempt_login()
713 elif status == conic.STATUS_DISCONNECTED:
715 self._defaultBackendId = self._selectedBackendId
716 self._change_loggedin_status(self.NULL_BACKEND)
718 self._errorDisplay.push_exception()
720 def _on_window_state_change(self, widget, event, *args):
722 @note Hildon specific
725 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
726 self._isFullScreen = True
728 self._isFullScreen = False
730 self._errorDisplay.push_exception()
732 def _on_key_press(self, widget, event, *args):
734 @note Hildon specific
736 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
739 event.keyval == gtk.keysyms.F6 or
740 event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK
742 if self._isFullScreen:
743 self._window.unfullscreen()
745 self._window.fullscreen()
746 elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
747 with open(constants._user_logpath_, "r") as f:
748 logLines = f.xreadlines()
749 log = "".join(logLines)
750 self._clipboard.set_text(str(log))
752 event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and
753 event.get_state() & gtk.gdk.CONTROL_MASK
755 self._window.destroy()
756 elif event.keyval == gtk.keysyms.r and event.get_state() & gtk.gdk.CONTROL_MASK:
757 self._refresh_active_tab()
758 elif event.keyval == gtk.keysyms.i and event.get_state() & gtk.gdk.CONTROL_MASK:
759 self._import_contacts()
761 self._errorDisplay.push_exception()
763 def _on_clearcookies_clicked(self, *args):
765 self._phoneBackends[self._selectedBackendId].logout()
766 self._accountViews[self._selectedBackendId].clear()
767 self._historyViews[self._selectedBackendId].clear()
768 self._messagesViews[self._selectedBackendId].clear()
769 self._contactsViews[self._selectedBackendId].clear()
770 self._change_loggedin_status(self.NULL_BACKEND)
772 self._spawn_attempt_login(True)
774 self._errorDisplay.push_exception()
776 def _on_notebook_switch_page(self, notebook, page, pageIndex):
778 self._reset_tab_refresh()
780 didRecentUpdate = False
781 didMessagesUpdate = False
783 if pageIndex == self.RECENT_TAB:
784 didRecentUpdate = self._historyViews[self._selectedBackendId].update()
785 elif pageIndex == self.MESSAGES_TAB:
786 didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
787 elif pageIndex == self.CONTACTS_TAB:
788 self._contactsViews[self._selectedBackendId].update()
789 elif pageIndex == self.ACCOUNT_TAB:
790 self._accountViews[self._selectedBackendId].update()
792 if didRecentUpdate or didMessagesUpdate:
793 if self._ledHandler is not None:
794 self._ledHandler.off()
796 self._errorDisplay.push_exception()
798 def _set_tab_refresh(self, *args):
800 pageIndex = self._notebook.get_current_page()
801 child = self._notebook.get_nth_page(pageIndex)
802 self._notebook.get_tab_label(child).set_text("Refresh?")
804 self._errorDisplay.push_exception()
807 def _reset_tab_refresh(self, *args):
809 pageIndex = self._notebook.get_current_page()
810 child = self._notebook.get_nth_page(pageIndex)
811 self._notebook.get_tab_label(child).set_text(self._originalCurrentLabels[pageIndex])
813 self._errorDisplay.push_exception()
816 def _on_tab_refresh(self, *args):
818 self._refresh_active_tab()
819 self._reset_tab_refresh()
821 self._errorDisplay.push_exception()
824 def _on_sms_clicked(self, numbers, message):
826 assert numbers, "No number specified"
827 assert message, "Empty message"
828 self.refresh_session()
830 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
833 self._errorDisplay.push_exception()
837 self._errorDisplay.push_message(
838 "Backend link with GoogleVoice is not working, please try again"
844 self._phoneBackends[self._selectedBackendId].send_sms(numbers, message)
845 hildonize.show_information_banner(self._window, "Sending to %s" % ", ".join(numbers))
846 _moduleLogger.info("Sending SMS to %r" % numbers)
849 self._errorDisplay.push_exception()
852 self._dialpads[self._selectedBackendId].clear()
854 self._errorDisplay.push_exception()
856 def _on_dial_clicked(self, number):
858 assert number, "No number to call"
859 self.refresh_session()
861 loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
864 self._errorDisplay.push_exception()
868 self._errorDisplay.push_message(
869 "Backend link with GoogleVoice is not working, please try again"
875 assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
876 self._phoneBackends[self._selectedBackendId].call(number)
877 hildonize.show_information_banner(self._window, "Calling %s" % number)
878 _moduleLogger.info("Calling %s" % number)
881 self._errorDisplay.push_exception()
884 self._dialpads[self._selectedBackendId].clear()
886 self._errorDisplay.push_exception()
888 def _import_contacts(self):
889 csvFilter = gtk.FileFilter()
890 csvFilter.set_name("Contacts")
891 csvFilter.add_pattern("*.csv")
892 importFileChooser = gtk.FileChooserDialog(
896 importFileChooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
897 importFileChooser.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
899 importFileChooser.set_property("filter", csvFilter)
900 userResponse = importFileChooser.run()
901 importFileChooser.hide()
902 if userResponse == gtk.RESPONSE_OK:
903 filename = importFileChooser.get_filename()
904 shutil.copy2(filename, self._fsContactsPath)
906 def _on_menu_refresh(self, *args):
908 self._refresh_active_tab()
910 self._errorDisplay.push_exception()
912 def _on_paste(self, *args):
914 contents = self._clipboard.wait_for_text()
915 if contents is not None:
916 self._dialpads[self._selectedBackendId].set_number(contents)
918 self._errorDisplay.push_exception()
920 def _on_about_activate(self, *args):
922 dlg = gtk.AboutDialog()
923 dlg.set_name(constants.__pretty_app_name__)
924 dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
925 dlg.set_copyright("Copyright 2008 - LGPL")
926 dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice account. This application is not affiliated with Google in any way")
927 dlg.set_website("http://gc-dialer.garage.maemo.org/")
928 dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <eopage@byu.net>"])
932 self._errorDisplay.push_exception()
938 failureCount, testCount = doctest.testmod()
940 print "Tests Successful"
947 _lock_file = os.path.join(constants._data_path_, ".lock")
948 #with gtk_toolbox.flock(_lock_file, 0):
949 gtk.gdk.threads_init()
951 if hildonize.IS_HILDON_SUPPORTED:
952 gtk.set_application_name(constants.__pretty_app_name__)
953 handle = Dialcentral()
954 if not PROFILE_STARTUP:
958 class DummyOptions(object):
964 if __name__ == "__main__":
965 logging.basicConfig(level=logging.DEBUG)
967 if len(sys.argv) > 1:
973 if optparse is not None:
974 parser = optparse.OptionParser()
975 parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
976 (commandOptions, commandArgs) = parser.parse_args()
978 commandOptions = DummyOptions()
981 if commandOptions.test: