Minor updates being taken in from Dialcentral
[doneit] / src / doneit_glade.py
index 132155c..79b0ad3 100755 (executable)
@@ -1,15 +1,22 @@
 #!/usr/bin/python
 
+"""
+@todo Add logging support to make debugging random user issues a lot easier
+@todo See Tasque for UI ideas http://live.gnome.org/Tasque/Screenshots
+"""
 
-from __future__ import with_statement
 
+from __future__ import with_statement
 
 import sys
 import gc
 import os
 import threading
+import ConfigParser
+import socket
 import warnings
 
+import gobject
 import gtk
 import gtk.glade
 
@@ -21,11 +28,74 @@ except ImportError:
 import gtk_toolbox
 
 
+socket.setdefaulttimeout(10)
+
+
+class PreferencesDialog(object):
+
+       def __init__(self, widgetTree):
+               self._backendList = gtk.ListStore(gobject.TYPE_STRING)
+               self._backendCell = gtk.CellRendererText()
+
+               self._dialog = widgetTree.get_widget("preferencesDialog")
+               self._backendSelector = widgetTree.get_widget("prefsBackendSelector")
+               self._applyButton = widgetTree.get_widget("applyPrefsButton")
+               self._cancelButton = widgetTree.get_widget("cancelPrefsButton")
+
+               self._onApplyId = None
+               self._onCancelId = None
+
+       def enable(self):
+               self._dialog.set_default_size(800, 300)
+               self._onApplyId = self._applyButton.connect("clicked", self._on_apply_clicked)
+               self._onCancelId = self._cancelButton.connect("clicked", self._on_cancel_clicked)
+
+               cell = self._backendCell
+               self._backendSelector.pack_start(cell, True)
+               self._backendSelector.add_attribute(cell, 'text', 0)
+               self._backendSelector.set_model(self._backendList)
+
+       def disable(self):
+               self._applyButton.disconnect(self._onApplyId)
+               self._cancelButton.disconnect(self._onCancelId)
+
+               self._backendList.clear()
+               self._backendSelector.set_model(None)
+
+       def run(self, app, parentWindow = None):
+               if parentWindow is not None:
+                       self._dialog.set_transient_for(parentWindow)
+
+               self._backendList.clear()
+               activeIndex = 0
+               for i, (uiName, ui) in enumerate(app.get_uis()):
+                       self._backendList.append((uiName, ))
+                       if uiName == app.get_default_ui():
+                               activeIndex = i
+               self._backendSelector.set_active(activeIndex)
+
+               try:
+                       response = self._dialog.run()
+                       if response != gtk.RESPONSE_OK:
+                               raise RuntimeError("Edit Cancelled")
+               finally:
+                       self._dialog.hide()
+
+               backendName = self._backendSelector.get_active_text()
+               app.switch_ui(backendName)
+
+       def _on_apply_clicked(self, *args):
+               self._dialog.response(gtk.RESPONSE_OK)
+
+       def _on_cancel_clicked(self, *args):
+               self._dialog.response(gtk.RESPONSE_CANCEL)
+
+
 class DoneIt(object):
 
        __pretty_app_name__ = "DoneIt"
        __app_name__ = "doneit"
-       __version__ = "0.3.0"
+       __version__ = "0.3.1"
        __app_magic__ = 0xdeadbeef
 
        _glade_files = [
@@ -34,15 +104,18 @@ class DoneIt(object):
                os.path.join(os.path.dirname(__file__), "../lib/doneit.glade"),
        ]
 
-       _user_data = os.path.expanduser("~/.%s/" % __app_name__)
+       _user_data = os.path.join(os.path.expanduser("~"), ".%s" % __app_name__)
        _user_settings = "%s/settings.ini" % _user_data
 
        def __init__(self):
-               self._todoUIs = []
+               self._initDone = False
+               self._todoUIs = {}
                self._todoUI = None
                self._osso = None
                self._deviceIsOnline = True
                self._connection = None
+               self._fallbackUIName = ""
+               self._defaultUIName = ""
 
                for path in self._glade_files:
                        if os.path.isfile(path):
@@ -60,17 +133,22 @@ class DoneIt(object):
                self._clipboard = gtk.clipboard_get()
                self.__window = self._widgetTree.get_widget("mainWindow")
                self.__errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
+               self._prefsDialog = PreferencesDialog(self._widgetTree)
 
                self._app = None
                self._isFullScreen = False
                if hildon is not None:
                        self._app = hildon.Program()
+                       oldWindow = self.__window
                        self.__window = hildon.Window()
-                       self._widgetTree.get_widget("mainLayout").reparent(self.__window)
+                       oldWindow.get_child().reparent(self.__window)
                        self._app.add_window(self.__window)
-                       self._widgetTree.get_widget("usernameentry").set_property('hildon-input-mode', 7)
-                       self._widgetTree.get_widget("passwordentry").set_property('hildon-input-mode', 7|(1 << 29))
-                       self._widgetTree.get_widget("projectsCombo").get_child().set_property('hildon-input-mode', (1 << 4))
+
+                       try:
+                               self._widgetTree.get_widget("usernameentry").set_property('hildon-input-mode', 7)
+                               self._widgetTree.get_widget("passwordentry").set_property('hildon-input-mode', 7|(1 << 29))
+                       except TypeError, e:
+                               warnings.warn(e.message)
 
                        gtkMenu = self._widgetTree.get_widget("mainMenubar")
                        menu = gtk.Menu()
@@ -84,18 +162,17 @@ class DoneIt(object):
                else:
                        pass # warnings.warn("No Hildon", UserWarning, 2)
 
+               if hildon is None:
+                       self.__window.set_title("%s" % self.__pretty_app_name__)
+
                callbackMapping = {
                        "on_doneit_quit": self._on_close,
-                       "on_paste": self._on_paste,
                        "on_about": self._on_about_activate,
                }
                self._widgetTree.signal_autoconnect(callbackMapping)
 
-               if self.__window:
-                       if hildon is None:
-                               self.__window.set_title("%s" % self.__pretty_app_name__)
-                       self.__window.connect("destroy", self._on_close)
-                       self.__window.show_all()
+               self.__window.connect("destroy", self._on_close)
+               self.__window.show_all()
 
                backgroundSetup = threading.Thread(target=self._idle_setup)
                backgroundSetup.setDaemon(True)
@@ -103,14 +180,13 @@ class DoneIt(object):
 
        def _idle_setup(self):
                # Barebones UI handlers
-               import gtk_null
-               gtk.gdk.threads_enter()
-               try:
-                       self._todoUIs = [
-                               gtk_null.GtkNull(self._widgetTree),
-                       ]
-               finally:
-                       gtk.gdk.threads_leave()
+               import null_view
+               with gtk_toolbox.gtk_lock():
+                       nullView = null_view.GtkNull(self._widgetTree)
+                       self._todoUIs[nullView.name()] = nullView
+                       self._todoUI = nullView
+                       self._todoUI.enable()
+                       self._fallbackUIName = nullView.name()
 
                # Setup maemo specifics
                try:
@@ -138,18 +214,32 @@ class DoneIt(object):
                        pass # warnings.warn("No Internet Connectivity API ", UserWarning)
 
                # Setup costly backends
-               import gtk_rtmilk
-               gtk.gdk.threads_enter()
-               try:
-                       self._todoUIs.extend([
-                               gtk_rtmilk.GtkRtMilk(self._widgetTree),
-                       ])
-                       self._todoUI = self._todoUIs[1]
-                       self._todoUI.enable()
-               finally:
-                       gtk.gdk.threads_leave()
+               import rtm_view
+               with gtk_toolbox.gtk_lock():
+                       rtmView = rtm_view.RtmView(self._widgetTree, self.__errorDisplay)
+               self._todoUIs[rtmView.name()] = rtmView
+
+               import file_view
+               defaultStoragePath = "%s/data.txt" % self._user_data
+               with gtk_toolbox.gtk_lock():
+                       fileView = file_view.FileView(self._widgetTree, self.__errorDisplay, defaultStoragePath)
+               self._todoUIs[fileView.name()] = fileView
+
+               self._defaultUIName = fileView.name()
+
+               config = ConfigParser.SafeConfigParser()
+               config.read(self._user_settings)
+               with gtk_toolbox.gtk_lock():
+                       self.load_settings(config)
+                       self._widgetTree.get_widget("connectMenuItem").connect("activate", lambda *args: self.switch_ui(self._defaultUIName))
+                       self._widgetTree.get_widget("preferencesMenuItem").connect("activate", self._on_prefs)
+
+               self._initDone = True
 
        def display_error_message(self, msg):
+               """
+               @note UI Thread
+               """
                error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
 
                def close(dialog, response, editor):
@@ -158,6 +248,81 @@ class DoneIt(object):
                error_dialog.connect("response", close, self)
                error_dialog.run()
 
+       def load_settings(self, config):
+               """
+               @note UI Thread
+               """
+               for todoUI in self._todoUIs.itervalues():
+                       try:
+                               todoUI.load_settings(config)
+                       except ConfigParser.NoSectionError, e:
+                               warnings.warn(
+                                       "Settings file %s is missing section %s" % (
+                                               self._user_settings,
+                                               e.section,
+                                       ),
+                                       stacklevel=2
+                               )
+
+               try:
+                       activeUIName = config.get(self.__pretty_app_name__, "active")
+               except ConfigParser.NoSectionError, e:
+                       activeUIName = ""
+                       warnings.warn(
+                               "Settings file %s is missing section %s" % (
+                                       self._user_settings,
+                                       e.section,
+                               ),
+                               stacklevel=2
+                       )
+
+               try:
+                       self.switch_ui(activeUIName)
+               except KeyError, e:
+                       self.switch_ui(self._defaultUIName)
+
+       def save_settings(self, config):
+               """
+               @note Thread Agnostic
+               """
+               config.add_section(self.__pretty_app_name__)
+               config.set(self.__pretty_app_name__, "active", self._todoUI.name())
+
+               for todoUI in self._todoUIs.itervalues():
+                       todoUI.save_settings(config)
+
+       def get_uis(self):
+               return (ui for ui in self._todoUIs.iteritems())
+
+       def get_default_ui(self):
+               return self._defaultUIName
+
+       def switch_ui(self, uiName):
+               """
+               @note UI Thread
+               """
+               newActiveUI = self._todoUIs[uiName]
+               try:
+                       newActiveUI.login()
+               except RuntimeError:
+                       return # User cancelled the operation
+
+               self._todoUI.disable()
+               self._todoUI = newActiveUI
+               self._todoUI.enable()
+
+               if uiName != self._fallbackUIName:
+                       self._defaultUIName = uiName
+
+       def _save_settings(self):
+               """
+               @note Thread Agnostic
+               """
+               config = ConfigParser.SafeConfigParser()
+               self.save_settings(config)
+               with open(self._user_settings, "wb") as configFile:
+                       config.write(configFile)
+
        def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
                """
                For system_inactivity, we have no background tasks to pause
@@ -168,7 +333,7 @@ class DoneIt(object):
                        gc.collect()
 
                if save_unsaved_data or shutdown:
-                       pass
+                       self._save_settings()
 
        def _on_connection_change(self, connection, event, magicIdentifier):
                """
@@ -183,8 +348,12 @@ class DoneIt(object):
 
                if status == conic.STATUS_CONNECTED:
                        self._deviceIsOnline = True
+                       if self._initDone:
+                               self.switch_ui(self._defaultUIName)
                elif status == conic.STATUS_DISCONNECTED:
                        self._deviceIsOnline = False
+                       if self._initDone:
+                               self.switch_ui(self._fallbackUIName)
 
        def _on_window_state_change(self, widget, event, *args):
                """
@@ -196,17 +365,15 @@ class DoneIt(object):
                        self._isFullScreen = False
 
        def _on_close(self, *args, **kwds):
-               if self._osso is not None:
-                       self._osso.close()
-
                try:
-                       pass
+                       if self._osso is not None:
+                               self._osso.close()
+
+                       if self._initDone:
+                               self._save_settings()
                finally:
                        gtk.main_quit()
 
-       def _on_paste(self, *args):
-               pass
-
        def _on_key_press(self, widget, event, *args):
                """
                @note Hildon specific
@@ -217,14 +384,31 @@ class DoneIt(object):
                        else:
                                self.__window.fullscreen()
 
+       def _on_logout(self, *args):
+               if not self._initDone:
+                       return
+
+               self._todoUI.logout()
+               self.switch_ui(self._fallbackUIName)
+
+       def _on_prefs(self, *args):
+               if not self._initDone:
+                       return
+
+               self._prefsDialog.enable()
+               try:
+                       self._prefsDialog.run(self)
+               finally:
+                       self._prefsDialog.disable()
+
        def _on_about_activate(self, *args):
                dlg = gtk.AboutDialog()
                dlg.set_name(self.__pretty_app_name__)
                dlg.set_version(self.__version__)
                dlg.set_copyright("Copyright 2008 - LGPL")
                dlg.set_comments("")
-               dlg.set_website("")
-               dlg.set_authors([""])
+               dlg.set_website("http://doneit.garage.maemo.org")
+               dlg.set_authors(["Ed Page"])
                dlg.run()
                dlg.destroy()