Applying some cleanups from TOR
[gc-dialer] / src / dc_glade.py
index d0c7111..aa32707 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/python2.5
+#!/usr/bin/env python
 
 """
 DialCentral - Front end for Google's GoogleVoice service.
@@ -29,6 +29,7 @@ import threading
 import base64
 import ConfigParser
 import itertools
+import shutil
 import logging
 
 import gtk
@@ -82,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 = ("", "")
@@ -91,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):
@@ -108,17 +111,22 @@ class Dialcentral(object):
 
                self._window = self._widgetTree.get_widget("mainWindow")
                self._notebook = self._widgetTree.get_widget("notebook")
-               self._errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
+               errorBox = self._widgetTree.get_widget("errorEventBox")
+               errorDescription = self._widgetTree.get_widget("errorDescription")
+               errorClose = self._widgetTree.get_widget("errorClose")
+               self._errorDisplay = gtk_toolbox.ErrorDisplay(errorBox, errorDescription, errorClose)
                self._credentialsDialog = gtk_toolbox.LoginWindow(self._widgetTree)
+               self._smsEntryWindow = None
 
                self._isFullScreen = False
+               self.__isPortrait = False
                self._app = hildonize.get_app_class()()
                self._window = hildonize.hildonize_window(self._app, self._window)
                hildonize.hildonize_text_entry(self._widgetTree.get_widget("usernameentry"))
                hildonize.hildonize_password_entry(self._widgetTree.get_widget("passwordentry"))
 
                for scrollingWidgetName in (
-                       'recent_scrolledwindow',
+                       'history_scrolledwindow',
                        'message_scrolledwindow',
                        'contacts_scrolledwindow',
                        "smsMessages_scrolledwindow",
@@ -138,7 +146,6 @@ class Dialcentral(object):
                        "addressbookSelectButton",
                        "sendSmsButton",
                        "dialButton",
-                       "cancelSmsButton",
                        "callbackSelectButton",
                        "minutesEntryButton",
                        "clearcookies",
@@ -148,12 +155,20 @@ class Dialcentral(object):
                        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("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)
@@ -186,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:
@@ -213,10 +228,11 @@ 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:
                                _moduleLogger.warning("No device state support")
 
@@ -261,10 +277,13 @@ class Dialcentral(object):
 
                # 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
 
+                       with gtk_toolbox.gtk_lock():
+                               self._smsEntryWindow = gv_views.SmsEntryWindow(self._widgetTree, self._window, self._app)
                        try:
                                os.makedirs(constants._data_path_)
                        except OSError, e:
@@ -286,8 +305,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
                                        ),
                                })
@@ -302,19 +321,21 @@ 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._messagesViews[self.GV_BACKEND].number_selected = self._select_action
-                       self._contactsViews[self.GV_BACKEND].number_selected = self._select_action
+                       self._smsEntryWindow.send_sms = self._on_sms_clicked
+                       self._smsEntryWindow.dial = self._on_dial_clicked
+                       self._dialpads[self.GV_BACKEND].add_contact = self._add_contact
+                       self._dialpads[self.GV_BACKEND].dial = self._on_dial_clicked
+                       self._historyViews[self.GV_BACKEND].add_contact = self._add_contact
+                       self._messagesViews[self.GV_BACKEND].add_contact = self._add_contact
+                       self._contactsViews[self.GV_BACKEND].add_contact = self._add_contact
 
                        addressBooks = [
                                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])
@@ -342,8 +363,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():
@@ -352,6 +371,7 @@ class Dialcentral(object):
                        with gtk_toolbox.gtk_lock():
                                self._errorDisplay.push_exception()
                finally:
+                       self._initDone = True
                        self._spawn_attempt_login()
 
        def _spawn_attempt_login(self, *args):
@@ -415,13 +435,19 @@ class Dialcentral(object):
                """
                @note Thread agnostic
                """
-               if self._credentials == ("", ""):
-                       # Disallow logging in by cookie alone, without credentials
-                       return False
+               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()
 
-               loggedIn = self._phoneBackends[self._defaultBackendId].is_authed()
                if loggedIn:
                        _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):
@@ -469,29 +495,24 @@ class Dialcentral(object):
 
                return loggedIn, serviceId
 
-       def _select_action(self, action, number, message):
-               self.refresh_session()
-               if action == "dial":
-                       self._on_dial_clicked(number)
-               elif action == "sms":
-                       self._on_sms_clicked(number, message)
-               else:
-                       assert False, "Unknown action: %s" % action
+       def _add_contact(self, *args, **kwds):
+               self._smsEntryWindow.add_contact(*args, **kwds)
 
        def _change_loggedin_status(self, newStatus):
                oldStatus = self._selectedBackendId
                if oldStatus == newStatus:
                        return
 
+               _moduleLogger.debug("Changing from %s to %s" % (oldStatus, newStatus))
                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()
 
@@ -499,6 +520,7 @@ class Dialcentral(object):
 
                self._accountViews[self._selectedBackendId].update()
                self._refresh_active_tab()
+               self._refresh_orientation()
 
        def load_settings(self, config):
                """
@@ -521,6 +543,18 @@ 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()
+
+                       isPortrait = config.getboolean(constants.__pretty_app_name__, "portrait")
+                       if isPortrait ^ self.__isPortrait:
+                               if isPortrait:
+                                       orientation = gtk.ORIENTATION_VERTICAL
+                               else:
+                                       orientation = gtk.ORIENTATION_HORIZONTAL
+                               self.set_orientation(orientation)
                except ConfigParser.NoOptionError, e:
                        _moduleLogger.exception(
                                "Settings file %s is missing section %s" % (
@@ -540,7 +574,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())
@@ -561,27 +595,6 @@ class Dialcentral(object):
                                        ),
                                )
 
-               try:
-                       previousOrientation = config.getint(constants.__pretty_app_name__, "orientation")
-                       if previousOrientation == gtk.ORIENTATION_HORIZONTAL:
-                               hildonize.window_to_landscape(self._window)
-                       elif previousOrientation == gtk.ORIENTATION_VERTICAL:
-                               hildonize.window_to_portrait(self._window)
-               except ConfigParser.NoOptionError, e:
-                       _moduleLogger.exception(
-                               "Settings file %s is missing section %s" % (
-                                       constants._user_settings_,
-                                       e.section,
-                               ),
-                       )
-               except ConfigParser.NoSectionError, e:
-                       _moduleLogger.exception(
-                               "Settings file %s is missing section %s" % (
-                                       constants._user_settings_,
-                                       e.section,
-                               ),
-                       )
-
        def save_settings(self, config):
                """
                @note Thread Agnostic
@@ -595,7 +608,8 @@ class Dialcentral(object):
 
                config.add_section(constants.__pretty_app_name__)
                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__, "portrait", str(self.__isPortrait))
+               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)
@@ -607,7 +621,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())
@@ -628,7 +642,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)
 
@@ -636,13 +650,50 @@ class Dialcentral(object):
                        if self._ledHandler is not None:
                                self._ledHandler.off()
 
+       def set_orientation(self, orientation):
+               if orientation == gtk.ORIENTATION_VERTICAL:
+                       hildonize.window_to_portrait(self._window)
+                       self._notebook.set_property("tab-pos", gtk.POS_BOTTOM)
+                       self.__isPortrait = True
+               elif orientation == gtk.ORIENTATION_HORIZONTAL:
+                       hildonize.window_to_landscape(self._window)
+                       self._notebook.set_property("tab-pos", gtk.POS_LEFT)
+                       self.__isPortrait = False
+               else:
+                       raise NotImplementedError(orientation)
+
+       def get_orientation(self):
+               return gtk.ORIENTATION_VERTICAL if self.__isPortrait else gtk.ORIENTATION_HORIZONTAL
+
+       def _toggle_rotate(self):
+               if self.__isPortrait:
+                       self.set_orientation(gtk.ORIENTATION_HORIZONTAL)
+               else:
+                       self.set_orientation(gtk.ORIENTATION_VERTICAL)
+
+       def _refresh_orientation(self):
+               """
+               Mostly meant to be used when switching backends
+               """
+               if self.__isPortrait:
+                       self.set_orientation(gtk.ORIENTATION_VERTICAL)
+               else:
+                       self.set_orientation(gtk.ORIENTATION_HORIZONTAL)
+
+       @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()
 
@@ -713,6 +764,23 @@ class Dialcentral(object):
                                        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.o and event.get_state() & gtk.gdk.CONTROL_MASK:
+                               self._toggle_rotate()
+                               return True
+                       elif event.keyval == gtk.keysyms.r and event.get_state() & gtk.gdk.CONTROL_MASK:
+                               self._refresh_active_tab()
+                       elif event.keyval == gtk.keysyms.i and event.get_state() & gtk.gdk.CONTROL_MASK:
+                               self._import_contacts()
                except Exception, e:
                        self._errorDisplay.push_exception()
 
@@ -720,7 +788,7 @@ 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)
@@ -737,7 +805,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:
@@ -777,10 +845,11 @@ 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"
+                       self.refresh_session()
                        try:
                                loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
                        except Exception, e:
@@ -796,8 +865,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()
@@ -810,6 +880,7 @@ class Dialcentral(object):
        def _on_dial_clicked(self, number):
                try:
                        assert number, "No number to call"
+                       self.refresh_session()
                        try:
                                loggedIn = self._phoneBackends[self._selectedBackendId].is_authed()
                        except Exception, e:
@@ -826,8 +897,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()
@@ -837,6 +909,24 @@ class Dialcentral(object):
                except Exception, e:
                        self._errorDisplay.push_exception()
 
+       def _import_contacts(self):
+               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)
+
        def _on_menu_refresh(self, *args):
                try:
                        self._refresh_active_tab()
@@ -859,7 +949,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:
@@ -878,8 +968,6 @@ def run_doctest():
 
 
 def run_dialpad():
-       _lock_file = os.path.join(constants._data_path_, ".lock")
-       #with gtk_toolbox.flock(_lock_file, 0):
        gtk.gdk.threads_init()
 
        if hildonize.IS_HILDON_SUPPORTED: