Adding the ignore file
[gc-dialer] / src / dc_glade.py
index dcdf777..2c1563e 100755 (executable)
@@ -29,6 +29,7 @@ import threading
 import base64
 import ConfigParser
 import itertools
+import shutil
 import logging
 
 import gtk
@@ -39,6 +40,10 @@ import hildonize
 import gtk_toolbox
 
 
+_moduleLogger = logging.getLogger("dc_glade")
+PROFILE_STARTUP = False
+
+
 def getmtime_nothrow(path):
        try:
                return os.path.getmtime(path)
@@ -70,6 +75,7 @@ class Dialcentral(object):
        ACCOUNT_TAB = 4
 
        NULL_BACKEND = 0
+       # 1 Was GrandCentral support so the gap was maintained for compatibility
        GV_BACKEND = 2
        BACKENDS = (NULL_BACKEND, GV_BACKEND)
 
@@ -77,6 +83,7 @@ class Dialcentral(object):
                self._initDone = False
                self._connection = None
                self._osso = None
+               self._deviceState = None
                self._clipboard = gtk.clipboard_get()
 
                self._credentials = ("", "")
@@ -86,11 +93,12 @@ class Dialcentral(object):
                self._dialpads = None
                self._accountViews = None
                self._messagesViews = None
-               self._recentViews = None
+               self._historyViews = None
                self._contactsViews = None
                self._alarmHandler = None
                self._ledHandler = None
                self._originalCurrentLabels = []
+               self._fsContactsPath = os.path.join(constants._data_path_, "contacts")
 
                for path in self._glade_files:
                        if os.path.isfile(path):
@@ -112,31 +120,60 @@ class Dialcentral(object):
                hildonize.hildonize_text_entry(self._widgetTree.get_widget("usernameentry"))
                hildonize.hildonize_password_entry(self._widgetTree.get_widget("passwordentry"))
 
-               for scrollingWidget in (
-                       'recent_scrolledwindow',
+               for scrollingWidgetName in (
+                       'history_scrolledwindow',
                        'message_scrolledwindow',
                        'contacts_scrolledwindow',
-                       "phoneSelectionMessages_scrolledwindow",
                        "smsMessages_scrolledwindow",
                ):
-                       hildonize.hildonize_scrollwindow(self._widgetTree.get_widget(scrollingWidget))
-               for scrollingWidget in (
-                       "phonetypes_scrolledwindow",
+                       scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName)
+                       assert scrollingWidget is not None, scrollingWidgetName
+                       hildonize.hildonize_scrollwindow(scrollingWidget)
+               for scrollingWidgetName in (
                        "smsMessage_scrolledEntry",
                ):
-                       hildonize.hildonize_scrollwindow_with_viewport(self._widgetTree.get_widget(scrollingWidget))
+                       scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName)
+                       assert scrollingWidget is not None, scrollingWidgetName
+                       hildonize.hildonize_scrollwindow_with_viewport(scrollingWidget)
+
+               for buttonName in (
+                       "back",
+                       "addressbookSelectButton",
+                       "sendSmsButton",
+                       "dialButton",
+                       "cancelSmsButton",
+                       "callbackSelectButton",
+                       "minutesEntryButton",
+                       "clearcookies",
+                       "phoneTypeSelection",
+               ):
+                       button = self._widgetTree.get_widget(buttonName)
+                       assert button is not None, buttonName
+                       hildonize.set_button_thumb_selectable(button)
 
-               replacementButtons = [gtk.Button("Test")]
                menu = hildonize.hildonize_menu(
                        self._window,
                        self._widgetTree.get_widget("dialpad_menubar"),
-                       replacementButtons
                )
+               if not hildonize.GTK_MENU_USED:
+                       button = gtk.Button("New Login")
+                       button.connect("clicked", self._on_clearcookies_clicked)
+                       menu.append(button)
+
+                       button = gtk.Button("Import Contacts")
+                       button.connect("clicked", self._on_contact_import)
+                       menu.append(button)
+
+                       button = gtk.Button("Refresh")
+                       button.connect("clicked", self._on_menu_refresh)
+                       menu.append(button)
+
+                       menu.show_all()
 
                self._window.connect("key-press-event", self._on_key_press)
                self._window.connect("window-state-event", self._on_window_state_change)
                if not hildonize.IS_HILDON_SUPPORTED:
-                       logging.warning("No hildonization support")
+                       _moduleLogger.warning("No hildonization support")
 
                hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
 
@@ -151,9 +188,12 @@ class Dialcentral(object):
                        )
                )
 
-               backgroundSetup = threading.Thread(target=self._idle_setup)
-               backgroundSetup.setDaemon(True)
-               backgroundSetup.start()
+               if not PROFILE_STARTUP:
+                       backgroundSetup = threading.Thread(target=self._idle_setup)
+                       backgroundSetup.setDaemon(True)
+                       backgroundSetup.start()
+               else:
+                       self._idle_setup()
 
        def _idle_setup(self):
                """
@@ -161,20 +201,20 @@ class Dialcentral(object):
                """
                # Barebones UI handlers
                try:
-                       import null_backend
+                       from backends import null_backend
                        import null_views
 
                        self._phoneBackends = {self.NULL_BACKEND: null_backend.NullDialer()}
                        with gtk_toolbox.gtk_lock():
                                self._dialpads = {self.NULL_BACKEND: null_views.Dialpad(self._widgetTree)}
                                self._accountViews = {self.NULL_BACKEND: null_views.AccountInfo(self._widgetTree)}
-                               self._recentViews = {self.NULL_BACKEND: null_views.RecentCallsView(self._widgetTree)}
+                               self._historyViews = {self.NULL_BACKEND: null_views.CallHistoryView(self._widgetTree)}
                                self._messagesViews = {self.NULL_BACKEND: null_views.MessagesView(self._widgetTree)}
                                self._contactsViews = {self.NULL_BACKEND: null_views.ContactsView(self._widgetTree)}
 
                                self._dialpads[self._selectedBackendId].enable()
                                self._accountViews[self._selectedBackendId].enable()
-                               self._recentViews[self._selectedBackendId].enable()
+                               self._historyViews[self._selectedBackendId].enable()
                                self._messagesViews[self._selectedBackendId].enable()
                                self._contactsViews[self._selectedBackendId].enable()
                except Exception, e:
@@ -188,29 +228,34 @@ class Dialcentral(object):
                        except (ImportError, OSError):
                                osso = None
                        self._osso = None
+                       self._deviceState = None
                        if osso is not None:
                                self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
-                               device = osso.DeviceState(self._osso)
-                               device.set_device_state_callback(self._on_device_state_change, 0)
+                               self._deviceState = osso.DeviceState(self._osso)
+                               self._deviceState.set_device_state_callback(self._on_device_state_change, 0)
                        else:
-                               logging.warning("No device state support")
+                               _moduleLogger.warning("No device state support")
 
                        try:
                                import alarm_handler
-                               self._alarmHandler = alarm_handler.AlarmHandler()
+                               if alarm_handler.AlarmHandler is not alarm_handler._NoneAlarmHandler:
+                                       self._alarmHandler = alarm_handler.AlarmHandler()
+                               else:
+                                       self._alarmHandler = None
                        except (ImportError, OSError):
                                alarm_handler = None
                        except Exception:
                                with gtk_toolbox.gtk_lock():
                                        self._errorDisplay.push_exception()
                                alarm_handler = None
-                               logging.warning("No notification support")
+                       if alarm_handler is None:
+                               _moduleLogger.warning("No notification support")
                        if hildonize.IS_HILDON_SUPPORTED:
                                try:
                                        import led_handler
                                        self._ledHandler = led_handler.LedHandler()
                                except Exception, e:
-                                       logging.exception('LED Handling failed: "%s"' % str(e))
+                                       _moduleLogger.exception('LED Handling failed: "%s"' % str(e))
                                        self._ledHandler = None
                        else:
                                self._ledHandler = None
@@ -225,16 +270,17 @@ class Dialcentral(object):
                                self._connection.connect("connection-event", self._on_connection_change, constants.__app_magic__)
                                self._connection.request_connection(conic.CONNECT_FLAG_NONE)
                        else:
-                               logging.warning("No connection support")
+                               _moduleLogger.warning("No connection support")
                except Exception, e:
                        with gtk_toolbox.gtk_lock():
                                self._errorDisplay.push_exception()
 
                # Setup costly backends
                try:
-                       import gv_backend
-                       import file_backend
+                       from backends import gv_backend
+                       from backends import file_backend
                        import gv_views
+                       from backends import merge_backend
 
                        try:
                                os.makedirs(constants._data_path_)
@@ -257,8 +303,8 @@ class Dialcentral(object):
                                        ),
                                })
                                self._accountViews[self.GV_BACKEND].save_everything = self._save_settings
-                               self._recentViews.update({
-                                       self.GV_BACKEND: gv_views.RecentCallsView(
+                               self._historyViews.update({
+                                       self.GV_BACKEND: gv_views.CallHistoryView(
                                                self._widgetTree, self._phoneBackends[self.GV_BACKEND], self._errorDisplay
                                        ),
                                })
@@ -273,11 +319,10 @@ class Dialcentral(object):
                                        ),
                                })
 
-                       fsContactsPath = os.path.join(constants._data_path_, "contacts")
-                       fileBackend = file_backend.FilesystemAddressBookFactory(fsContactsPath)
+                       fileBackend = file_backend.FilesystemAddressBookFactory(self._fsContactsPath)
 
                        self._dialpads[self.GV_BACKEND].number_selected = self._select_action
-                       self._recentViews[self.GV_BACKEND].number_selected = self._select_action
+                       self._historyViews[self.GV_BACKEND].number_selected = self._select_action
                        self._messagesViews[self.GV_BACKEND].number_selected = self._select_action
                        self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
 
@@ -285,7 +330,7 @@ class Dialcentral(object):
                                self._phoneBackends[self.GV_BACKEND],
                                fileBackend,
                        ]
-                       mergedBook = gv_views.MergedAddressBook(addressBooks, gv_views.MergedAddressBook.advanced_lastname_sorter)
+                       mergedBook = merge_backend.MergedAddressBook(addressBooks, merge_backend.MergedAddressBook.basic_firtname_sorter)
                        self._contactsViews[self.GV_BACKEND].append(mergedBook)
                        self._contactsViews[self.GV_BACKEND].extend(addressBooks)
                        self._contactsViews[self.GV_BACKEND].open_addressbook(*self._contactsViews[self.GV_BACKEND].get_addressbooks().next()[0][0:2])
@@ -295,6 +340,7 @@ class Dialcentral(object):
                                "on_refresh": self._on_menu_refresh,
                                "on_clearcookies_clicked": self._on_clearcookies_clicked,
                                "on_about_activate": self._on_about_activate,
+                               "on_import": self._on_contact_import,
                        }
                        if hildonize.GTK_MENU_USED:
                                self._widgetTree.signal_autoconnect(callbackMapping)
@@ -313,8 +359,6 @@ class Dialcentral(object):
                        self._notebookTapHandler.on_holding = self._set_tab_refresh
                        self._notebookTapHandler.on_cancel = self._reset_tab_refresh
 
-                       self._initDone = True
-
                        config = ConfigParser.SafeConfigParser()
                        config.read(constants._user_settings_)
                        with gtk_toolbox.gtk_lock():
@@ -323,36 +367,47 @@ class Dialcentral(object):
                        with gtk_toolbox.gtk_lock():
                                self._errorDisplay.push_exception()
                finally:
-                       self._spawn_attempt_login(2)
+                       self._initDone = True
+                       self._spawn_attempt_login()
 
        def _spawn_attempt_login(self, *args):
                self._loginSink.send(args)
 
-       def _attempt_login(self, numOfAttempts = 10, force = False):
+       def _attempt_login(self, force = False):
                """
                @note This must be run outside of the UI lock
                """
                try:
-                       assert 0 <= numOfAttempts, "That was pointless having 0 or less login attempts"
                        assert self._initDone, "Attempting login before app is fully loaded"
 
                        serviceId = self.NULL_BACKEND
                        loggedIn = False
-                       if not force:
+                       if not force and self._defaultBackendId != self.NULL_BACKEND:
+                               with gtk_toolbox.gtk_lock():
+                                       banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
                                try:
                                        self.refresh_session()
                                        serviceId = self._defaultBackendId
                                        loggedIn = True
                                except Exception, e:
-                                       logging.exception('Session refresh failed with the following message "%s"' % str(e))
+                                       _moduleLogger.exception('Session refresh failed with the following message "%s"' % str(e))
+                               finally:
+                                       with gtk_toolbox.gtk_lock():
+                                               hildonize.show_busy_banner_end(banner)
 
                        if not loggedIn:
-                               loggedIn, serviceId = self._login_by_user(numOfAttempts)
+                               loggedIn, serviceId = self._login_by_user()
 
                        with gtk_toolbox.gtk_lock():
                                self._change_loggedin_status(serviceId)
                                if loggedIn:
                                        hildonize.show_information_banner(self._window, "Logged In")
+                               else:
+                                       hildonize.show_information_banner(self._window, "Login Failed")
+                               if not self._phoneBackends[self._defaultBackendId].get_callback_number():
+                                       # subtle reminder to the users to configure things
+                                       self._notebook.set_current_page(self.ACCOUNT_TAB)
+
                except Exception, e:
                        with gtk_toolbox.gtk_lock():
                                self._errorDisplay.push_exception()
@@ -376,60 +431,74 @@ class Dialcentral(object):
                """
                @note Thread agnostic
                """
-               loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
+               loggedIn = False
+
+               isQuickLoginPossible = self._phoneBackends[self._defaultBackendId].is_quick_login_possible()
+               if self._credentials != ("", "") and isQuickLoginPossible:
+                       if not loggedIn:
+                               loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
+
                if loggedIn:
-                       logging.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
+                       _moduleLogger.info("Logged into %r through cookies" % self._phoneBackends[self._defaultBackendId])
+               else:
+                       # If the cookies are bad, scratch them completely
+                       self._phoneBackends[self._defaultBackendId].logout()
+
                return loggedIn
 
        def _login_by_settings(self):
                """
                @note Thread agnostic
                """
+               if self._credentials == ("", ""):
+                       # Don't bother with the settings if they are blank
+                       return False
+
                username, password = self._credentials
                loggedIn = self._phoneBackends[self._defaultBackendId].login(username, password)
                if loggedIn:
                        self._credentials = username, password
-                       logging.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
+                       _moduleLogger.info("Logged into %r through settings" % self._phoneBackends[self._defaultBackendId])
                return loggedIn
 
-       def _login_by_user(self, numOfAttempts):
+       def _login_by_user(self):
                """
                @note This must be run outside of the UI lock
                """
                loggedIn, (username, password) = False, self._credentials
                tmpServiceId = self.GV_BACKEND
-               for attemptCount in xrange(numOfAttempts):
-                       if loggedIn:
-                               break
+               while not loggedIn:
                        with gtk_toolbox.gtk_lock():
                                credentials = self._credentialsDialog.request_credentials(
                                        defaultCredentials = self._credentials
                                )
-                               if not self._phoneBackends[tmpServiceId].get_callback_number():
-                                       # subtle reminder to the users to configure things
-                                       self._notebook.set_current_page(self.ACCOUNT_TAB)
-                       username, password = credentials
-                       loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
+                               banner = hildonize.show_busy_banner_start(self._window, "Logging In...")
+                       try:
+                               username, password = credentials
+                               loggedIn = self._phoneBackends[tmpServiceId].login(username, password)
+                       finally:
+                               with gtk_toolbox.gtk_lock():
+                                       hildonize.show_busy_banner_end(banner)
 
                if loggedIn:
                        serviceId = tmpServiceId
                        self._credentials = username, password
-                       logging.info("Logged into %r through user request" % self._phoneBackends[serviceId])
+                       _moduleLogger.info("Logged into %r through user request" % self._phoneBackends[serviceId])
                else:
+                       # Hint to the user that they are not logged in
                        serviceId = self.NULL_BACKEND
                        self._notebook.set_current_page(self.ACCOUNT_TAB)
 
                return loggedIn, serviceId
 
-       def _select_action(self, action, number, message):
+       def _select_action(self, action, numbers, message):
                self.refresh_session()
-               if action == "select":
-                       self._dialpads[self._selectedBackendId].set_number(number)
-                       self._notebook.set_current_page(self.KEYPAD_TAB)
-               elif action == "dial":
+               if action == "dial":
+                       assert len(numbers) == 1
+                       number = numbers[0]
                        self._on_dial_clicked(number)
                elif action == "sms":
-                       self._on_sms_clicked(number, message)
+                       self._on_sms_clicked(numbers, message)
                else:
                        assert False, "Unknown action: %s" % action
 
@@ -440,19 +509,16 @@ class Dialcentral(object):
 
                self._dialpads[oldStatus].disable()
                self._accountViews[oldStatus].disable()
-               self._recentViews[oldStatus].disable()
+               self._historyViews[oldStatus].disable()
                self._messagesViews[oldStatus].disable()
                self._contactsViews[oldStatus].disable()
 
                self._dialpads[newStatus].enable()
                self._accountViews[newStatus].enable()
-               self._recentViews[newStatus].enable()
+               self._historyViews[newStatus].enable()
                self._messagesViews[newStatus].enable()
                self._contactsViews[newStatus].enable()
 
-               if self._phoneBackends[self._selectedBackendId].get_callback_number() is None:
-                       self._phoneBackends[self._selectedBackendId].set_sane_callback()
-
                self._selectedBackendId = newStatus
 
                self._accountViews[self._selectedBackendId].update()
@@ -463,7 +529,10 @@ class Dialcentral(object):
                @note UI Thread
                """
                try:
-                       self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active")
+                       if not PROFILE_STARTUP:
+                               self._defaultBackendId = config.getint(constants.__pretty_app_name__, "active")
+                       else:
+                               self._defaultBackendId = self.NULL_BACKEND
                        blobs = (
                                config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
                                for i in xrange(len(self._credentials))
@@ -476,15 +545,19 @@ class Dialcentral(object):
 
                        if self._alarmHandler is not None:
                                self._alarmHandler.load_settings(config, "alarm")
+
+                       isFullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen")
+                       if isFullscreen:
+                               self._window.fullscreen()
                except ConfigParser.NoOptionError, e:
-                       logging.exception(
+                       _moduleLogger.exception(
                                "Settings file %s is missing section %s" % (
                                        constants._user_settings_,
                                        e.section,
                                ),
                        )
                except ConfigParser.NoSectionError, e:
-                       logging.exception(
+                       _moduleLogger.exception(
                                "Settings file %s is missing section %s" % (
                                        constants._user_settings_,
                                        e.section,
@@ -495,21 +568,21 @@ class Dialcentral(object):
                        self._dialpads.iteritems(),
                        self._accountViews.iteritems(),
                        self._messagesViews.iteritems(),
-                       self._recentViews.iteritems(),
+                       self._historyViews.iteritems(),
                        self._contactsViews.iteritems(),
                ):
                        sectionName = "%s - %s" % (backendId, view.name())
                        try:
                                view.load_settings(config, sectionName)
                        except ConfigParser.NoOptionError, e:
-                               logging.exception(
+                               _moduleLogger.exception(
                                        "Settings file %s is missing section %s" % (
                                                constants._user_settings_,
                                                e.section,
                                        ),
                                )
                        except ConfigParser.NoSectionError, e:
-                               logging.exception(
+                               _moduleLogger.exception(
                                        "Settings file %s is missing section %s" % (
                                                constants._user_settings_,
                                                e.section,
@@ -523,14 +596,14 @@ class Dialcentral(object):
                        elif previousOrientation == gtk.ORIENTATION_VERTICAL:
                                hildonize.window_to_portrait(self._window)
                except ConfigParser.NoOptionError, e:
-                       logging.exception(
+                       _moduleLogger.exception(
                                "Settings file %s is missing section %s" % (
                                        constants._user_settings_,
                                        e.section,
                                ),
                        )
                except ConfigParser.NoSectionError, e:
-                       logging.exception(
+                       _moduleLogger.exception(
                                "Settings file %s is missing section %s" % (
                                        constants._user_settings_,
                                        e.section,
@@ -541,9 +614,17 @@ class Dialcentral(object):
                """
                @note Thread Agnostic
                """
+               # Because we now only support GVoice, if there are user credentials,
+               # always assume its using the GVoice backend
+               if self._credentials[0] and self._credentials[1]:
+                       backend = self.GV_BACKEND
+               else:
+                       backend = self.NULL_BACKEND
+
                config.add_section(constants.__pretty_app_name__)
-               config.set(constants.__pretty_app_name__, "active", str(self._selectedBackendId))
+               config.set(constants.__pretty_app_name__, "active", str(backend))
                config.set(constants.__pretty_app_name__, "orientation", str(int(gtk_toolbox.get_screen_orientation())))
+               config.set(constants.__pretty_app_name__, "fullscreen", str(self._isFullScreen))
                for i, value in enumerate(self._credentials):
                        blob = base64.b64encode(value)
                        config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
@@ -555,7 +636,7 @@ class Dialcentral(object):
                        self._dialpads.iteritems(),
                        self._accountViews.iteritems(),
                        self._messagesViews.iteritems(),
-                       self._recentViews.iteritems(),
+                       self._historyViews.iteritems(),
                        self._contactsViews.iteritems(),
                ):
                        sectionName = "%s - %s" % (backendId, view.name())
@@ -576,7 +657,7 @@ class Dialcentral(object):
                if pageIndex == self.CONTACTS_TAB:
                        self._contactsViews[self._selectedBackendId].update(force=True)
                elif pageIndex == self.RECENT_TAB:
-                       self._recentViews[self._selectedBackendId].update(force=True)
+                       self._historyViews[self._selectedBackendId].update(force=True)
                elif pageIndex == self.MESSAGES_TAB:
                        self._messagesViews[self._selectedBackendId].update(force=True)
 
@@ -584,13 +665,20 @@ class Dialcentral(object):
                        if self._ledHandler is not None:
                                self._ledHandler.off()
 
+       @gtk_toolbox.log_exception(_moduleLogger)
        def _on_close(self, *args, **kwds):
                try:
-                       if self._osso is not None:
-                               self._osso.close()
-
                        if self._initDone:
                                self._save_settings()
+
+                       try:
+                               self._deviceState.close()
+                       except AttributeError:
+                               pass # Either None or close was removed (in Fremantle)
+                       try:
+                               self._osso.close()
+                       except AttributeError:
+                               pass # Either None or close was removed (in Fremantle)
                finally:
                        gtk.main_quit()
 
@@ -627,7 +715,7 @@ class Dialcentral(object):
 
                        if status == conic.STATUS_CONNECTED:
                                if self._initDone:
-                                       self._spawn_attempt_login(2)
+                                       self._spawn_attempt_login()
                        elif status == conic.STATUS_DISCONNECTED:
                                if self._initDone:
                                        self._defaultBackendId = self._selectedBackendId
@@ -651,15 +739,28 @@ class Dialcentral(object):
                """
                @note Hildon specific
                """
+               RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
                try:
                        if (
                                event.keyval == gtk.keysyms.F6 or
-                               event.keyval == gtk.keysyms.Return and event.get_state() & gtk.gdk.CONTROL_MASK
+                               event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK
                        ):
                                if self._isFullScreen:
                                        self._window.unfullscreen()
                                else:
                                        self._window.fullscreen()
+                       elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
+                               with open(constants._user_logpath_, "r") as f:
+                                       logLines = f.xreadlines()
+                                       log = "".join(logLines)
+                                       self._clipboard.set_text(str(log))
+                       elif (
+                               event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and
+                               event.get_state() & gtk.gdk.CONTROL_MASK
+                       ):
+                               self._window.destroy()
+                       elif event.keyval == gtk.keysyms.r and event.get_state() & gtk.gdk.CONTROL_MASK:
+                               self._refresh_active_tab()
                except Exception, e:
                        self._errorDisplay.push_exception()
 
@@ -667,12 +768,12 @@ class Dialcentral(object):
                try:
                        self._phoneBackends[self._selectedBackendId].logout()
                        self._accountViews[self._selectedBackendId].clear()
-                       self._recentViews[self._selectedBackendId].clear()
+                       self._historyViews[self._selectedBackendId].clear()
                        self._messagesViews[self._selectedBackendId].clear()
                        self._contactsViews[self._selectedBackendId].clear()
                        self._change_loggedin_status(self.NULL_BACKEND)
 
-                       self._spawn_attempt_login(2, True)
+                       self._spawn_attempt_login(True)
                except Exception, e:
                        self._errorDisplay.push_exception()
 
@@ -684,7 +785,7 @@ class Dialcentral(object):
                        didMessagesUpdate = False
 
                        if pageIndex == self.RECENT_TAB:
-                               didRecentUpdate = self._recentViews[self._selectedBackendId].update()
+                               didRecentUpdate = self._historyViews[self._selectedBackendId].update()
                        elif pageIndex == self.MESSAGES_TAB:
                                didMessagesUpdate = self._messagesViews[self._selectedBackendId].update()
                        elif pageIndex == self.CONTACTS_TAB:
@@ -724,9 +825,9 @@ class Dialcentral(object):
                        self._errorDisplay.push_exception()
                return False
 
-       def _on_sms_clicked(self, number, message):
+       def _on_sms_clicked(self, numbers, message):
                try:
-                       assert number, "No number specified"
+                       assert numbers, "No number specified"
                        assert message, "Empty message"
                        try:
                                loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
@@ -743,8 +844,9 @@ class Dialcentral(object):
 
                        dialed = False
                        try:
-                               self._phoneBackends[self._selectedBackendId].send_sms(number, message)
-                               hildonize.show_information_banner(self._window, "Sending to %s" % number)
+                               self._phoneBackends[self._selectedBackendId].send_sms(numbers, message)
+                               hildonize.show_information_banner(self._window, "Sending to %s" % ", ".join(numbers))
+                               _moduleLogger.info("Sending SMS to %r" % numbers)
                                dialed = True
                        except Exception, e:
                                self._errorDisplay.push_exception()
@@ -773,8 +875,9 @@ class Dialcentral(object):
                        dialed = False
                        try:
                                assert self._phoneBackends[self._selectedBackendId].get_callback_number() != "", "No callback number specified"
-                               self._phoneBackends[self._selectedBackendId].dial(number)
+                               self._phoneBackends[self._selectedBackendId].call(number)
                                hildonize.show_information_banner(self._window, "Calling %s" % number)
+                               _moduleLogger.info("Calling %s" % number)
                                dialed = True
                        except Exception, e:
                                self._errorDisplay.push_exception()
@@ -784,6 +887,27 @@ class Dialcentral(object):
                except Exception, e:
                        self._errorDisplay.push_exception()
 
+       def _on_contact_import(self, *args):
+               try:
+                       csvFilter = gtk.FileFilter()
+                       csvFilter.set_name("Contacts")
+                       csvFilter.add_pattern("*.csv")
+                       importFileChooser = gtk.FileChooserDialog(
+                               title="Contacts",
+                               parent=self._window,
+                       )
+                       importFileChooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
+                       importFileChooser.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
+
+                       importFileChooser.set_property("filter", csvFilter)
+                       userResponse = importFileChooser.run()
+                       importFileChooser.hide()
+                       if userResponse == gtk.RESPONSE_OK:
+                               filename = importFileChooser.get_filename()
+                               shutil.copy2(filename, self._fsContactsPath)
+               except Exception, e:
+                       self._errorDisplay.push_exception()
+
        def _on_menu_refresh(self, *args):
                try:
                        self._refresh_active_tab()
@@ -806,7 +930,7 @@ class Dialcentral(object):
                        dlg.set_copyright("Copyright 2008 - LGPL")
                        dlg.set_comments("Dialcentral is a touch screen enhanced interface to your GoogleVoice account.  This application is not affiliated with Google in any way")
                        dlg.set_website("http://gc-dialer.garage.maemo.org/")
-                       dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <edpage@byu.net>"])
+                       dlg.set_authors(["<z2n@merctech.com>", "Eric Warnke <ericew@gmail.com>", "Ed Page <eopage@byu.net>"])
                        dlg.run()
                        dlg.destroy()
                except Exception, e:
@@ -832,7 +956,8 @@ def run_dialpad():
        if hildonize.IS_HILDON_SUPPORTED:
                gtk.set_application_name(constants.__pretty_app_name__)
        handle = Dialcentral()
-       gtk.main()
+       if not PROFILE_STARTUP:
+               gtk.main()
 
 
 class DummyOptions(object):