← Back to team overview

gtg team mailing list archive

[Merge] lp:~gtg-user/gtg/backends-window into lp:gtg

 

Luca Invernizzi has proposed merging lp:~gtg-user/gtg/backends-window into lp:gtg.

Requested reviews:
  Gtg developers (gtg)


This merge contains all the code relative to the window used to add, remove and edit backends.
All the functions should be documented.


-- 
https://code.launchpad.net/~gtg-user/gtg/backends-window/+merge/32647
Your team Gtg developers is requested to review the proposed merge of lp:~gtg-user/gtg/backends-window into lp:gtg.
=== modified file 'CHANGELOG'
--- CHANGELOG	2010-08-04 00:30:22 +0000
+++ CHANGELOG	2010-08-13 23:45:20 +0000
@@ -4,6 +4,7 @@
     * Fixed bug with data consistency #579189, by Marko Kevac
     * Added samba bugzilla to the bugzilla plugin, by Jelmer Vernoij
     * Fixed bug #532392, a start date is later than a due date, by Volodymyr Floreskul
+    * Added a window to add/delete/edit backends by Luca Invernizzi
 
 2010-03-01 Getting Things GNOME! 0.2.2
     * Autostart on login, by Luca Invernizzi

=== added file 'GTG/backends/backendsignals.py'
--- GTG/backends/backendsignals.py	1970-01-01 00:00:00 +0000
+++ GTG/backends/backendsignals.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import gobject
+
+from GTG.tools.borg import Borg
+
+
+
+class BackendSignals(Borg):
+    '''
+    This class handles the signals that involve backends. 
+    In particular, it's a wrapper Borg class around a _BackendSignalsGObject
+    class, and all method of the wrapped class can be used as if they were part
+    of this class
+    '''
+    
+    #error codes to send along with the BACKEND_FAILED signal
+    ERRNO_AUTHENTICATION = "authentication failed"
+    ERRNO_NETWORK = "network is down"
+    ERRNO_DBUS = "Dbus interface cannot be connected"
+
+    def __init__(self):
+        '''Checks that this is the only instance, and instantiates the
+        gobject'''
+        super(BackendSignals, self).__init__()
+        if  hasattr(self, "_gobject"):
+            return
+        self._gobject = _BackendSignalsGObject()
+
+    def __getattr__(self, attr):
+        '''
+        From outside the class, there should be no difference between self's
+        attributes and self._gobject's attributes.
+        '''
+        if attr == "_gobject" and not "_gobject" in self.__dict__:
+            raise AttributeError
+        return getattr(self._gobject, attr)
+
+
+def signal_type_factory(*args):
+    '''
+    Simply returns a gobject signal type
+
+    @returns tuple
+    '''
+    return (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, args)
+
+
+
+class _BackendSignalsGObject(gobject.GObject):
+
+    #signal name constants
+    BACKEND_STATE_TOGGLED = 'backend-state-toggled' #emitted when a
+                                                    #backend is
+                                                    #enabled or disabled
+    BACKEND_RENAMED = 'backend-renamed' #emitted when a backend is renamed
+    BACKEND_ADDED = 'backend-added'
+    BACKEND_REMOVED = 'backend-added' #when a backend is deleted
+    DEFAULT_BACKEND_LOADED = 'default-backend-loaded' #emitted after all
+                                                     # tasks have been
+                                                     # loaded from the
+                                                     # default backend
+    BACKEND_FAILED = 'backend-failed' #something went wrong with a backend
+    BACKEND_SYNC_STARTED = 'backend-sync-started'
+    BACKEND_SYNC_ENDED = 'backend-sync-ended'
+    INTERACTION_REQUESTED = 'user-interaction-requested'
+
+    INTERACTION_CONFIRM = 'confirm'
+    INTERACTION_TEXT = 'text'
+
+    __gsignals__ = {BACKEND_STATE_TOGGLED : signal_type_factory(str), \
+                    BACKEND_RENAMED       : signal_type_factory(str), \
+                    BACKEND_ADDED         : signal_type_factory(str), \
+                    BACKEND_REMOVED       : signal_type_factory(str), \
+                    BACKEND_SYNC_STARTED  : signal_type_factory(str), \
+                    BACKEND_SYNC_ENDED    : signal_type_factory(str), \
+                    DEFAULT_BACKEND_LOADED: signal_type_factory(), \
+                    BACKEND_FAILED        : signal_type_factory(str, str), \
+                    INTERACTION_REQUESTED : signal_type_factory(str, str, \
+                                                                str,  str)}
+    
+    def __init__(self):
+        super(_BackendSignalsGObject, self).__init__()
+        self.backends_currently_syncing = []
+
+    ############# Signals #########
+    #connecting to signals is fine, but keep an eye if you should emit them.
+    #As a general rule, signals should only be emitted in the GenericBackend
+    #class
+    
+    def _emit_signal(self, signal, backend_id):
+        gobject.idle_add(self.emit, signal, backend_id)
+
+    def backend_state_changed(self, backend_id):
+        self._emit_signal(self.BACKEND_STATE_TOGGLED, backend_id)
+
+    def backend_renamed(self, backend_id):
+        self._emit_signal(self.BACKEND_RENAMED, backend_id)
+
+    def backend_added(self, backend_id):
+        self._emit_signal(self.BACKEND_ADDED, backend_id)
+
+    def backend_removed(self, backend_id):
+        self._emit_signal(self.BACKEND_REMOVED, backend_id)
+
+    def default_backend_loaded(self):
+        gobject.idle_add(self.emit, self.DEFAULT_BACKEND_LOADED)
+
+    def backend_failed(self, backend_id, error_code):
+        gobject.idle_add(self.emit, self.BACKEND_FAILED, backend_id, \
+                         error_code)
+
+    def interaction_requested(self, backend_id, description, \
+                              interaction_type, callback_str):
+        gobject.idle_add(self.emit, self.INTERACTION_REQUESTED, \
+                         backend_id, description, interaction_type, callback_str)
+
+    def backend_sync_started(self, backend_id):
+        self._emit_signal(self.BACKEND_SYNC_STARTED, backend_id)
+        self.backends_currently_syncing.append(backend_id)
+
+    def backend_sync_ended(self, backend_id):
+        self._emit_signal(self.BACKEND_SYNC_ENDED, backend_id)
+        try:
+            self.backends_currently_syncing.remove(backend_id)
+        except:
+            pass
+    
+    def is_backend_syncing(self, backend_id):
+        return backend_id in self.backends_currently_syncing
+

=== removed file 'GTG/backends/backendsignals.py'
--- GTG/backends/backendsignals.py	2010-06-23 12:49:28 +0000
+++ GTG/backends/backendsignals.py	1970-01-01 00:00:00 +0000
@@ -1,127 +0,0 @@
-# -*- coding: utf-8 -*-
-# -----------------------------------------------------------------------------
-# Getting Things Gnome! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
-#
-# This program is free software: you can redistribute it and/or modify it under
-# the terms of the GNU General Public License as published by the Free Software
-# Foundation, either version 3 of the License, or (at your option) any later
-# version.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-# details.
-#
-# You should have received a copy of the GNU General Public License along with
-# this program.  If not, see <http://www.gnu.org/licenses/>.
-# -----------------------------------------------------------------------------
-
-import gobject
-
-from GTG.tools.borg import Borg
-
-
-
-class BackendSignals(Borg):
-    '''
-    This class handles the signals that involve backends. 
-    In particular, it's a wrapper Borg class around a _BackendSignalsGObject
-    class, and all method of the wrapped class can be used as if they were part
-    of this class
-    '''
-    
-    #error codes to send along with the BACKEND_FAILED signal
-    ERRNO_AUTHENTICATION = "authentication failed"
-    ERRNO_NETWORK = "network is down"
-    ERRNO_DBUS = "Dbus interface cannot be connected"
-
-    def __init__(self):
-        super(BackendSignals, self).__init__()
-        if  hasattr(self, "_gobject"):
-            return
-        self._gobject = _BackendSignalsGObject()
-
-    def __getattr__(self, attr):
-        if attr == "_gobject" and not "_gobject" in self.__dict__:
-            raise AttributeError
-        return getattr(self._gobject, attr)
-
-
-class _BackendSignalsGObject(gobject.GObject):
-
-    #signal name constants
-    BACKEND_STATE_TOGGLED = 'backend-state-toggled' #emitted when a
-                                                    #backend is
-                                                    #enabled or disabled
-    BACKEND_RENAMED = 'backend-renamed' #emitted when a backend is renamed
-    BACKEND_ADDED = 'backend-added'
-    BACKEND_REMOVED = 'backend-added' #when a backend is deleted
-    DEFAULT_BACKEND_LOADED = 'default-backend-loaded' #emitted after all
-                                                     # tasks have been
-                                                     # loaded from the
-                                                     # default backend
-    BACKEND_FAILED = 'backend-failed' #something went wrong with a backend
-    BACKEND_SYNC_STARTED = 'backend-sync-started'
-    BACKEND_SYNC_ENDED = 'backend-sync-ended'
-
-    __string_signal__ = (gobject.SIGNAL_RUN_FIRST, \
-                     gobject.TYPE_NONE, (str, ))
-    __none_signal__   = (gobject.SIGNAL_RUN_FIRST, \
-                     gobject.TYPE_NONE, ( ))
-    __string_string_signal__ = (gobject.SIGNAL_RUN_FIRST, \
-                    gobject.TYPE_NONE, (str, str, ))
-
-    __gsignals__ = {BACKEND_STATE_TOGGLED : __string_signal__, \
-                    BACKEND_RENAMED       : __string_signal__, \
-                    BACKEND_ADDED         : __string_signal__, \
-                    BACKEND_REMOVED       : __string_signal__, \
-                    BACKEND_SYNC_STARTED  : __string_signal__, \
-                    BACKEND_SYNC_ENDED    : __string_signal__, \
-                    DEFAULT_BACKEND_LOADED: __none_signal__, \
-                    BACKEND_FAILED        : __string_string_signal__}
-
-    def __init__(self):
-        super(_BackendSignalsGObject, self).__init__()
-        self.backends_currently_syncing = []
-
-    ############# Signals #########
-    #connecting to signals is fine, but keep an eye if you should emit them.
-    #As a general rule, signals should only be emitted in the GenericBackend
-    #class
-    
-    def _emit_signal(self, signal, backend_id):
-        gobject.idle_add(self.emit, signal, backend_id)
-
-    def backend_state_changed(self, backend_id):
-        self._emit_signal(self.BACKEND_STATE_TOGGLED, backend_id)
-
-    def backend_renamed(self, backend_id):
-        self._emit_signal(self.BACKEND_RENAMED, backend_id)
-
-    def backend_added(self, backend_id):
-        self._emit_signal(self.BACKEND_ADDED, backend_id)
-
-    def backend_removed(self, backend_id):
-        self._emit_signal(self.BACKEND_REMOVED, backend_id)
-
-    def default_backend_loaded(self):
-        gobject.idle_add(self.emit, self.DEFAULT_BACKEND_LOADED)
-
-    def backend_failed(self, backend_id, error_code):
-        gobject.idle_add(self.emit, self.BACKEND_FAILED, backend_id, \
-                         error_code)
-
-    def backend_sync_started(self, backend_id):
-        self._emit_signal(self.BACKEND_SYNC_STARTED, backend_id)
-        self.backends_currently_syncing.append(backend_id)
-
-    def backend_sync_ended(self, backend_id):
-        self._emit_signal(self.BACKEND_SYNC_ENDED, backend_id)
-        try:
-            self.backends_currently_syncing.remove(backend_id)
-        except:
-            pass
-    
-    def is_backend_syncing(self, backend_id):
-        return backend_id in self.backends_currently_syncing

=== modified file 'GTG/core/requester.py'
--- GTG/core/requester.py	2010-06-22 19:55:15 +0000
+++ GTG/core/requester.py	2010-08-13 23:45:20 +0000
@@ -284,3 +284,6 @@
 
     def backend_change_attached_tags(self, backend_id, tags):
         return self.ds.backend_change_attached_tags(backend_id, tags)
+
+    def save_datastore(self):
+        return self.ds.save()

=== modified file 'GTG/gtk/__init__.py'
--- GTG/gtk/__init__.py	2010-06-02 18:12:23 +0000
+++ GTG/gtk/__init__.py	2010-08-13 23:45:20 +0000
@@ -28,7 +28,9 @@
 
 
 class ViewConfig:
+
+
     current_rep = os.path.dirname(os.path.abspath(__file__))
     DELETE_GLADE_FILE  = os.path.join(current_rep, "deletion.glade")
     PREFERENCES_GLADE_FILE = os.path.join(current_rep, "preferences.glade")
-
+    BACKENDS_GLADE_FILE = os.path.join(current_rep, "backends_dialog.glade")

=== added directory 'GTG/gtk/backends_dialog'
=== added file 'GTG/gtk/backends_dialog.glade'
--- GTG/gtk/backends_dialog.glade	1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog.glade	2010-08-13 23:45:20 +0000
@@ -0,0 +1,166 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkWindow" id="backends_dialog">
+    <property name="window_position">mouse</property>
+    <signal name="delete_event" handler="on_BackendsDialog_delete_event"/>
+    <child>
+      <object class="GtkAlignment" id="alignment1">
+        <property name="visible">True</property>
+        <property name="top_padding">10</property>
+        <property name="bottom_padding">10</property>
+        <property name="left_padding">10</property>
+        <property name="right_padding">10</property>
+        <child>
+          <object class="GtkVBox" id="vbox1">
+            <property name="visible">True</property>
+            <property name="spacing">10</property>
+            <child>
+              <object class="GtkHBox" id="big_central_hbox">
+                <property name="visible">True</property>
+                <property name="spacing">10</property>
+                <child>
+                  <object class="GtkVBox" id="vbox2">
+                    <property name="visible">True</property>
+                    <child>
+                      <object class="GtkAlignment" id="treeview_window">
+                        <property name="height_request">400</property>
+                        <property name="visible">True</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkAlignment" id="alignment2">
+                        <property name="height_request">30</property>
+                        <property name="visible">True</property>
+                        <property name="yalign">1</property>
+                        <property name="top_padding">20</property>
+                        <property name="bottom_padding">10</property>
+                        <property name="left_padding">10</property>
+                        <property name="right_padding">10</property>
+                        <child>
+                          <object class="GtkHButtonBox" id="hbuttonbox3">
+                            <property name="visible">True</property>
+                            <property name="spacing">10</property>
+                            <property name="homogeneous">True</property>
+                            <child>
+                              <object class="GtkButton" id="add_button">
+                                <property name="label">gtk-add</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">True</property>
+                                <property name="use_stock">True</property>
+                                <signal name="clicked" handler="on_add_button_clicked"/>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">False</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkButton" id="remove_button">
+                                <property name="label">gtk-remove</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">True</property>
+                                <property name="use_stock">True</property>
+                                <signal name="clicked" handler="on_remove_button_clicked"/>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">False</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkScrolledWindow" id="central_pane_window">
+                    <property name="width_request">450</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="vadjustment">adjustment1</property>
+                    <property name="hscrollbar_policy">automatic</property>
+                    <property name="vscrollbar_policy">automatic</property>
+                    <child>
+                      <object class="GtkViewport" id="central_pane1">
+                        <property name="visible">True</property>
+                        <property name="resize_mode">queue</property>
+                        <child>
+                          <object class="GtkAlignment" id="central_pane">
+                            <property name="visible">True</property>
+                            <property name="left_padding">10</property>
+                            <property name="right_padding">10</property>
+                            <child>
+                              <placeholder/>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkHButtonBox" id="hbuttonbox2">
+                <property name="visible">True</property>
+                <property name="layout_style">end</property>
+                <child>
+                  <object class="GtkButton" id="quit_button">
+                    <property name="label">gtk-quit</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="use_stock">True</property>
+                    <signal name="clicked" handler="on_quit_button_clicked"/>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkAdjustment" id="adjustment1">
+    <property name="upper">100</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+    <property name="page_size">10</property>
+  </object>
+</interface>

=== added file 'GTG/gtk/backends_dialog/__init__.py'
--- GTG/gtk/backends_dialog/__init__.py	1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/__init__.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,294 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+'''
+This file contains BackendsDialog, a class that manages the window that
+lets you add and configure backends.
+This window is divided in two:
+    - a treeview of the currently loaded backends (the ones added by the user)
+    - a big space, that can be filled by the configuration panel or the add
+    panel (these are called also "views" in this class)
+'''
+
+import gtk
+
+from GTG.gtk                                import ViewConfig
+from GTG.core                               import CoreConfig
+from GTG.gtk.backends_dialog.backendstree   import BackendsTree
+from GTG.gtk.backends_dialog.addpanel       import AddPanel
+from GTG.gtk.backends_dialog.configurepanel import ConfigurePanel
+from GTG.backends                           import BackendFactory
+from GTG.tools.logger                       import Log
+from GTG                                    import _
+from GTG.backends.genericbackend            import GenericBackend
+
+
+
+class BackendsDialog(object):
+    '''
+    BackendsDialog manages a window that lets you manage and configure backends.
+    It can display two "views", or "panels":
+        - the backend configuration view
+        - the backend adding view
+    '''
+
+
+    def __init__(self, req):
+        '''
+        Initializes the gtk objects and signals.
+        @param req: a Requester object
+        '''
+        self.req = req
+        self._configure_icon_theme()
+        builder = gtk.Builder() 
+        self._load_widgets_from_glade(builder)
+        self._create_widgets_for_add_panel()
+        self._create_widgets_for_configure_panel()
+        self._setup_signal_connections(builder)
+        self._create_widgets_for_backends_tree()
+
+########################################
+### INTERFACE WITH THE VIEWMANAGER #####
+########################################
+
+    def activate(self):
+        '''Shows this window, refreshing the current view'''
+        self.config_panel.set_hidden(False)
+        self.dialog.show_all()
+        self.backends_tv.refresh()
+        self.backends_tv.select_backend()
+        self.dialog.present()
+
+    def on_close(self, widget, data = None):
+        '''
+        Hides this window, saving the backends configuration.
+        
+        @param widget: not used, here only for using this as signal callback
+        @param data: same as widget, disregard the content
+        '''
+        self.dialog.hide()
+        self.config_panel.set_hidden(True)
+        self.req.save_datastore()
+
+########################################
+### HELPER FUNCTIONS ###################
+########################################
+
+    def get_requester(self):
+        '''
+        Helper function: returns the requester.
+        It's used by the "views" displayed by this class (backend editing and
+        adding views) to access the requester
+        '''
+        return self.req
+
+    def get_pixbuf_from_icon_name(self, name, height, width):
+        '''
+        Helper function: returns a pixbuf of an icon given its name in the
+        loaded icon theme
+
+        @param name: the name of the icon
+        @param height: the height of the returned pixbuf
+        @param width:  the width of the returned pixbuf
+
+        @returns gtk.gdk.Pixbuf: a pixbuf containing the wanted icon, or None
+        (if the icon is not present)
+        '''
+        #NOTE: loading icons directly from the theme and scaling them results in
+        #      blurry icons. So, instead of doing that, I'm loading them
+        #      directly from file. 
+        icon_info = self.icon_theme.lookup_icon(name, gtk.ICON_SIZE_MENU, 0)
+        if icon_info == None:
+            return None
+        pixbuf =  gtk.gdk.pixbuf_new_from_file(icon_info.get_filename())
+        return pixbuf.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR)
+
+    def _show_panel(self, panel_name):
+        '''
+        Helper function to switch between panels.
+
+        @param panel_name: the name of the wanted panel. Choose between 
+                        "configuration" or "add"
+        '''
+        if panel_name == "configuration":
+            panel_to_remove = self.add_panel
+            panel_to_add = self.config_panel
+            side_is_enabled = True
+        elif panel_name == "add":
+            panel_to_remove = self.config_panel
+            panel_to_add = self.add_panel
+            side_is_enabled = False
+        else:
+            Log.error("panel name unknown")
+            return
+        ##Central pane
+        #NOTE: self.central_pane is the gtk.Container in which we load panels
+        if panel_to_remove in self.central_pane:
+            self.central_pane.remove(panel_to_remove)
+        if not panel_to_add in self.central_pane:
+            self.central_pane.add(panel_to_add)
+        self.central_pane.show_all()
+        #Side treeview
+        # disabled if we're adding a new backend
+        try:
+            #when this is called upon initialization of this class, the
+            # backends_tv object has not been created yet.
+            self.add_button.set_sensitive(side_is_enabled)
+            self.remove_button.set_sensitive(side_is_enabled)
+            self.backends_tv.set_sensitive(side_is_enabled)
+        except AttributeError:
+            pass
+
+########################################
+### WIDGETS AND SIGNALS ################
+########################################
+
+    def _load_widgets_from_glade(self, builder):
+        '''
+        Loads widgets from the glade file
+
+        @param builder: a gtk.Builder
+        '''
+        builder.add_from_file(ViewConfig.BACKENDS_GLADE_FILE)
+        widgets = {
+          'dialog'        : 'backends_dialog',
+          'treeview_window'   : 'treeview_window',
+          'central_pane'  : 'central_pane',
+          'add_button'    : 'add_button',
+          'remove_button' : 'remove_button',
+          }
+        for attr, widget in widgets.iteritems():
+            setattr(self, attr, builder.get_object(widget))
+
+    def _setup_signal_connections(self, builder):
+        '''
+        Creates some GTK signals connections
+
+        @param builder: a gtk.Builder
+        '''
+        signals = {
+         'on_add_button_clicked': self.on_add_button,
+         'on_BackendsDialog_delete_event': self.on_close,
+         'on_quit_button_clicked': self.on_close,
+         'on_remove_button_clicked': self.on_remove_button,
+        }
+        builder.connect_signals(signals)
+
+    def _configure_icon_theme(self):
+        '''
+        Inform gtk on the location of the backends icons (which is in
+        the GTG directory tree, and not in the default location for icons
+        '''
+        self.icon_theme = gtk.icon_theme_get_default()
+        for directory in CoreConfig().get_icons_directories():
+            self.icon_theme.prepend_search_path(directory)
+
+    def _create_widgets_for_backends_tree(self):
+        '''
+        Creates the widgets for the lateral treeview displaying the 
+        backends the user has added
+        '''
+        self.backends_tv = BackendsTree(self)
+        self.treeview_window.add(self.backends_tv)
+
+    def _create_widgets_for_configure_panel(self):
+        '''simply creates the panel to configure backends'''
+        self.config_panel = ConfigurePanel(self)
+
+    def _create_widgets_for_add_panel(self):
+        '''simply creates the panel to add backends'''
+        self.add_panel = AddPanel(self)
+
+########################################
+### EVENT HANDLING #####################
+########################################
+
+    def on_backend_selected(self, backend_id):
+        '''
+        When a backend in the treeview gets selected, show
+        its configuration pane
+
+        @param backend_id: the id of the selected backend
+        '''
+        if backend_id:
+            self._show_panel("configuration")
+            self.config_panel.set_backend(backend_id)
+            backend = self.req.get_backend(backend_id)
+            self.remove_button.set_sensitive(not backend.is_default())
+
+    def on_add_button(self, widget = None, data = None):
+        '''
+        When the add button is pressed, the add panel is shown
+
+        @param widget: not used, here only for using this as signal callback
+        @param data: same as widget, disregard the content
+        '''
+        self._show_panel("add")
+        self.add_panel.refresh_backends()
+
+    def on_backend_added(self, backend_name):
+        '''
+        When a backend is added, it is created and registered in the Datastore.
+        Also, the configuration panel is shown.
+
+        @param backend_name: the name of the type of the backend to add
+                             (identified as BACKEND_NAME in the Backend class)
+        '''
+        backend_id = None
+        #Create Backend
+        backend_dic = BackendFactory().get_new_backend_dict(backend_name)
+        if backend_dic:
+            backend_id = backend_dic["backend"].get_id()
+            backend_dic[GenericBackend.KEY_ENABLED] = False
+            self.req.register_backend(backend_dic)
+        #Restore UI
+        self._show_panel("configuration")
+
+    def show_config_for_backend(self, backend_id):
+        '''
+        Selects a backend in the lateral treeview
+
+        @param backend_id: the id of the backend that must be selected
+        '''
+        self.backends_tv.select_backend(backend_id)
+
+    def on_remove_button(self, widget = None, data = None):
+        '''
+        When the remove button is pressed, a confirmation dialog is shown, 
+        and if the answer is positive, the backend is deleted.
+        '''
+        backend_id = self.backends_tv.get_selected_backend_id()
+        if backend_id == None:
+            #no backend selected
+            return
+        backend = self.req.get_backend(backend_id)
+        dialog = gtk.MessageDialog( \
+                    parent = self.dialog,
+                    flags = gtk.DIALOG_DESTROY_WITH_PARENT,
+                    type = gtk.MESSAGE_QUESTION,
+                    buttons = gtk.BUTTONS_YES_NO,
+                    message_format = \
+                     _("Do you really want to remove the backend '%s'?") % \
+                            backend.get_human_name())
+        response = dialog.run() 
+        dialog.destroy()
+        if response == gtk.RESPONSE_YES:
+            #delete the backend and remove it from the lateral treeview
+            self.req.remove_backend(backend_id)
+            self.backends_tv.remove_backend(backend_id)

=== added file 'GTG/gtk/backends_dialog/addpanel.py'
--- GTG/gtk/backends_dialog/addpanel.py	1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/addpanel.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,214 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import gtk
+
+from GTG.gtk.backends_dialog.backendscombo import BackendsCombo
+from GTG.backends                          import BackendFactory
+from GTG                                   import _, ngettext
+
+#The code for showing the required modules has been disabled since it
+# seems that backends will be packaged separately (as plugins). I'm
+# leaving this here in case we change that decision (invernizzi).
+#from GTG.tools.moduletopackage             import ModuleToPackage
+
+
+
+class AddPanel(gtk.VBox):
+    ''' 
+    A VBox filled with gtk widgets to let the user choose a new backend.
+    '''
+
+
+    def __init__(self, backends_dialog):
+        '''
+        Constructor, just initializes the gtk widgets
+
+        @param backends_dialog: a reference to the dialog in which this is
+        loaded
+        '''
+        super(AddPanel, self).__init__()
+        self.dialog = backends_dialog
+        self._create_widgets()
+
+    def _create_widgets(self):
+        '''
+        gtk widgets initialization
+        '''
+        #Division of the available space in three segments:
+        # top, middle and bottom.
+        top = gtk.HBox()
+        middle = gtk.HBox()
+        bottom = gtk.HBox()
+        self._fill_top_hbox(top)
+        self._fill_middle_hbox(middle)
+        self._fill_bottom_hbox(bottom)
+        self.pack_start(top, False)
+        self.pack_start(middle, True)
+        self.pack_start(bottom, True)
+
+    def _fill_top_hbox(self, hbox):
+        '''
+        Helper function to fill and hbox with a combobox that lists the
+        available backends and a gtk.Label.
+
+        @param hbox: the gtk.HBox to fill
+        '''
+        label = gtk.Label("Select a backend")
+        label.set_size_request(-1, 30)
+        self.combo_types = BackendsCombo(self.dialog)
+        self.combo_types.child.connect('changed', self.on_combo_changed)
+        hbox.pack_start(label, True, True)
+        hbox.pack_start(self.combo_types, False, True)
+
+    def _fill_middle_hbox(self, hbox):
+        '''
+        Helper function to fill an hbox with a label describing the backend
+        and a gtk.Image (that loads the backend image)
+
+        @param hbox: the gtk.HBox to fill
+        '''
+        self.label_name = gtk.Label("name")
+        self.label_name.set_alignment(xalign = 0.5, yalign = 1)
+        self.label_description = gtk.Label()
+        self.label_description.set_justify(gtk.JUSTIFY_FILL)
+        self.label_description.set_line_wrap(True)
+        self.label_description.set_size_request(300, -1)
+        self.label_description.set_alignment(xalign = 0, yalign = 0.5)
+        self.label_author = gtk.Label("")
+        self.label_author.set_line_wrap(True)
+        self.label_author.set_alignment(xalign = 0, yalign = 0)
+        self.label_modules = gtk.Label("")
+        self.label_modules.set_line_wrap(True)
+        self.label_modules.set_alignment(xalign = 0, yalign = 0)
+        self.image_icon = gtk.Image()
+        self.image_icon.set_size_request(100, 100)
+        align_image = gtk.Alignment(xalign = 1, yalign = 0)
+        align_image.add(self.image_icon)
+        labels_vbox = gtk.VBox()
+        labels_vbox.pack_start(self.label_description, True, True)
+        labels_vbox.pack_start(self.label_author, True, True)
+        labels_vbox.pack_start(self.label_modules, True, True)
+        low_hbox = gtk.HBox()
+        low_hbox.pack_start(labels_vbox, True, True)
+        low_hbox.pack_start(align_image, True, True)
+        vbox = gtk.VBox()
+        vbox.pack_start(self.label_name, True, True)
+        vbox.pack_start(low_hbox, True, True)
+        hbox.pack_start(vbox, True, True)
+
+    def _fill_bottom_hbox(self, hbox):
+        '''
+        Helper function to fill and hbox with a buttonbox, featuring 
+        and ok and cancel buttons.
+
+        @param hbox: the gtk.HBox to fill
+        '''
+        cancel_button = gtk.Button(stock = gtk.STOCK_CANCEL)
+        cancel_button.connect('clicked', self.on_cancel)
+        self.ok_button = gtk.Button(stock = gtk.STOCK_OK)
+        self.ok_button.connect('clicked', self.on_confirm)
+        align =gtk.Alignment(xalign = 0.5, \
+                             yalign = 1, \
+                             xscale = 1)
+        align.set_padding(0, 10, 0, 0)
+        buttonbox = gtk.HButtonBox()
+        buttonbox.set_layout(gtk.BUTTONBOX_EDGE)
+        buttonbox.add(cancel_button)
+        buttonbox.set_child_secondary(cancel_button, False)
+        buttonbox.add(self.ok_button)
+        align.add(buttonbox)
+        hbox.pack_start(align, True, True)
+
+    def refresh_backends(self):
+        '''Populates the combo box containing the available backends'''
+        self.combo_types.refresh()
+
+    def on_confirm(self, widget = None):
+        '''
+        Notifies the dialog holding this VBox that a backend has been
+        chosen
+
+        @param widget: just to make this function usable as a signal callback.
+                       Not used.
+        '''
+        backend_name = self.combo_types.get_selected()
+        self.dialog.on_backend_added(backend_name)
+
+    def on_cancel(self, widget = None):
+        '''
+        Aborts the addition of a new backend. Shows the configuration panel
+        previously loaded.
+
+        @param widget: just to make this function usable as a signal callback.
+                       Not used.
+        '''
+        self.dialog.show_config_for_backend(None)
+
+    def on_combo_changed(self, widget = None):
+        '''
+        Updates the backend description and icon.
+
+        @param widget: just to make this function usable as a signal callback.
+                       Not used.
+        '''
+        backend_name = self.combo_types.get_selected()
+        if backend_name == None:
+            return
+        backend = BackendFactory().get_backend(backend_name)
+        self.label_description.set_markup(backend.Backend.get_description())
+
+        label = _('Syncing is <span color="red">disabled</span>')
+        markup = '<big><big><big><b>%s</b></big></big></big>' % \
+                                backend.Backend.get_human_default_name()
+        self.label_name.set_markup(markup)
+        authors = backend.Backend.get_authors()
+        author_txt = '<b>%s</b>:\n   - %s' % \
+                (ngettext("Author", "Authors", len(authors)),
+                 reduce(lambda a, b: a + "\n" + "   - " + b, authors))
+        self.label_author.set_markup(author_txt)
+        #The code for showing the required modules has been disabled since it
+        # seems that backends will be packaged separately (as plugins). I'm
+        # leaving this here in case we change that decision (invernizzi).
+        #self._build_module_list(backend.Backend)
+        pixbuf = self.dialog.get_pixbuf_from_icon_name(backend_name, 100, 100)
+        self.image_icon.set_from_pixbuf(pixbuf)
+        self.show_all()
+
+        #The code for showing the required modules has been disabled since it
+        # seems that backends will be packaged separately (as plugins). I'm
+        # leaving this here in case we change that decision (invernizzi).
+#    def _build_module_list(self, backend):
+#        missing_modules = []
+#        for module in backend.get_required_modules():
+#            try:
+#                __import__(module)
+#            except ImportError:
+#                missing_modules.append(module)
+#        if missing_modules:
+#            text = "<b> Missing modules:</b>\n - "
+#            module2package = ModuleToPackage()
+#            missing_modules = map(lambda a: \
+#                    "<span color='red'>" + \
+#                    module2package.lookup(a) +\
+#                    "</span>", missing_modules)
+#            text += reduce(lambda a, b: a + "\n - " + b, missing_modules)
+#            self.label_modules.set_markup(text)
+#        self.ok_button.set_sensitive(missing_modules == [])
+

=== added file 'GTG/gtk/backends_dialog/backendscombo.py'
--- GTG/gtk/backends_dialog/backendscombo.py	1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/backendscombo.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import gtk
+
+from GTG.backends import BackendFactory
+
+
+
+class BackendsCombo(gtk.ComboBoxEntry):
+    '''
+    A combobox listing all the available backends types
+    '''
+    
+
+    COLUMN_NAME = 0         #unique name for the backend type. It's never
+                            # displayed, it's used to find which backend has
+                            # been selected
+    COLUMN_HUMAN_NAME = 1   #human friendly name (which is localized).
+    COLUMN_ICON = 2 
+
+    def __init__(self, backends_dialog):
+        '''
+        Constructor, itializes gtk widgets.
+        @param backends_dialog: reference to the dialog in which this combo is
+                                loaded.
+        '''
+        super(BackendsCombo, self).__init__()
+        self.dialog = backends_dialog
+        self._liststore_init()
+        self._renderers_init()
+        self.set_size_request(-1, 30)
+        self.show_all()
+
+    def _liststore_init(self):
+        '''Setup the gtk.ListStore'''
+        self.liststore = gtk.ListStore(str, str, gtk.gdk.Pixbuf)
+        self.set_model(self.liststore)
+
+    def _renderers_init(self):
+        '''Configure the cell renderers'''
+        #Text renderer
+        text_cell = gtk.CellRendererText()
+        self.pack_start(text_cell, False)
+        self.set_text_column(self.COLUMN_HUMAN_NAME)
+        #Icon renderer
+        pixbuf_cell = gtk.CellRendererPixbuf()
+        self.pack_start(pixbuf_cell, False)
+        self.add_attribute(pixbuf_cell, "pixbuf", self.COLUMN_ICON)
+
+    def refresh(self):
+        '''
+        Populates the combo box with the available backends
+        '''
+        self.liststore.clear()
+        backend_types = BackendFactory().get_all_backends()
+        for name, module in backend_types.iteritems():
+            pixbuf = self.dialog.get_pixbuf_from_icon_name(name, 16, 16)
+            self.liststore.append((name, \
+                                   module.Backend.get_human_default_name(), \
+                                   pixbuf))
+        if backend_types:
+            #triggers a "changed" signal, which is used in the AddPanel to
+            #refresh the backend description and icon
+            self.set_active(0)
+
+    def get_selected(self):
+        '''
+        Returns the name of the selected backend, or None
+        '''
+        selected_iter = self.get_active_iter()
+        if selected_iter:
+            return self.liststore.get_value(selected_iter, \
+                                        BackendsCombo.COLUMN_NAME)
+        else:
+            return None

=== added file 'GTG/gtk/backends_dialog/backendstree.py'
--- GTG/gtk/backends_dialog/backendstree.py	1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/backendstree.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,252 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import gtk
+
+from GTG.gtk.colors              import get_colored_tags_markup
+from GTG.backends.genericbackend import GenericBackend
+from GTG.backends.backendsignals import BackendSignals
+
+
+
+class BackendsTree(gtk.TreeView):
+    '''
+    gtk.TreeView that shows the currently loaded backends.
+    '''
+
+
+    COLUMN_BACKEND_ID = 0 #never shown, used for internal lookup.
+    COLUMN_ICON = 1
+    COLUMN_TEXT = 2 # holds the backend "human-readable" name
+    COLUMN_TAGS = 3
+
+    def __init__(self, backendsdialog):
+        '''
+        Constructor, just initializes the gtk widgets
+
+        @param backends_dialog: a reference to the dialog in which this is
+        loaded
+        '''
+        super(BackendsTree,self).__init__()
+        self.dialog = backendsdialog
+        self.req = backendsdialog.get_requester()
+        self._init_liststore()
+        self._init_renderers()
+        self._init_signals()
+        self.refresh()
+
+    def refresh(self):
+        '''refreshes the gtk.Liststore'''
+        self.backendid_to_iter = {}
+        self.liststore.clear()
+        for backend in self.req.get_all_backends(disabled = True):
+            self.add_backend(backend)
+            self.on_backend_state_changed(None, backend.get_id())
+
+    def on_backend_added(self, sender, backend_id):
+        '''
+        Signal callback executed when a new backend is loaded
+
+        @param sender: not used, only here to let this function be used as a
+                       callback
+        @param backend_id: the id of the backend to add
+        '''
+        #Add
+        backend = self.req.get_backend(backend_id)
+        if not backend:
+            return
+        self.add_backend(backend)
+        #Select
+        self.select_backend(backend_id)
+        #Update it's enabled state
+        self.on_backend_state_changed(None, backend.get_id())
+
+    def add_backend(self, backend):
+        '''
+        Adds a new backend to the list
+
+        @param backend_id: the id of the backend to add
+        '''
+        if backend:
+            backend_iter = self.liststore.append([ \
+                backend.get_id(), \
+                self.dialog.get_pixbuf_from_icon_name(backend.get_name(), \
+                                                      16, 16), \
+                backend.get_human_name(), \
+                self._get_markup_for_tags(backend.get_attached_tags()), \
+                ])
+            self.backendid_to_iter[backend.get_id()] = backend_iter
+
+
+    def on_backend_state_changed(self, sender, backend_id):
+        '''
+        Signal callback executed when a backend is enabled/disabled.
+
+        @param sender: not used, only here to let this function be used as a
+                       callback
+        @param backend_id: the id of the backend to add
+        '''
+        if backend_id in self.backendid_to_iter:
+            style = self.get_style()
+            b_iter = self.backendid_to_iter[backend_id]
+            b_path = self.liststore.get_path(b_iter)
+            backend = self.req.get_backend(backend_id)
+            backend_name = backend.get_human_name()
+            if backend.is_enabled():
+                text = backend_name
+            else:
+                color = str(style.text[gtk.STATE_INSENSITIVE])
+                text = "<span color='%s'>%s</span>" % \
+                           (color, backend_name)
+            self.liststore[b_path][self.COLUMN_TEXT] = text
+
+    def _get_markup_for_tags(self, tag_names):
+        '''Given a list of tags names, generates the pango markup to render that
+        list with the tag colors used in GTG
+
+        @param tag_names: the list of the tags (strings)
+        @return str: the pango markup string
+        '''
+        if GenericBackend.ALLTASKS_TAG in tag_names:
+            tags_txt = ""
+        else:
+            tags_txt = get_colored_tags_markup(self.req, tag_names)
+        return "<small>" + tags_txt + "</small>"
+
+
+    def remove_backend(self, backend_id):
+        ''' Removes a backend from the treeview, and selects the first (to show
+        something in the configuration panel
+
+        @param backend_id: the id of the backend to remove
+        '''
+        if backend_id in self.backendid_to_iter:
+            self.liststore.remove(self.backendid_to_iter[backend_id])
+            del self.backendid_to_iter[backend_id]
+            self.select_backend()
+
+    def _init_liststore(self):
+        '''Creates the liststore'''
+        self.liststore = gtk.ListStore(object, gtk.gdk.Pixbuf, str, str)
+        self.set_model(self.liststore)
+
+    def _init_renderers(self):
+        '''Initializes the cell renderers'''
+        # We hide the columns headers
+        self.set_headers_visible(False)
+        # For the backend icon
+        pixbuf_cell = gtk.CellRendererPixbuf()
+        tvcolumn_pixbuf = gtk.TreeViewColumn('Icon', pixbuf_cell)
+        tvcolumn_pixbuf.add_attribute(pixbuf_cell, 'pixbuf', self.COLUMN_ICON)
+        self.append_column(tvcolumn_pixbuf)
+        # For the backend name
+        text_cell = gtk.CellRendererText()
+        tvcolumn_text = gtk.TreeViewColumn('Name', text_cell)
+        tvcolumn_text.add_attribute(text_cell, 'markup', self.COLUMN_TEXT)
+        self.append_column(tvcolumn_text)
+        text_cell.connect('edited', self.cell_edited_callback)
+        text_cell.set_property('editable', True)
+        # For the backend tags
+        tags_cell = gtk.CellRendererText()
+        tvcolumn_tags = gtk.TreeViewColumn('Tags', tags_cell)
+        tvcolumn_tags.add_attribute(tags_cell, 'markup', self.COLUMN_TAGS)
+        self.append_column(tvcolumn_tags)
+
+    def cell_edited_callback(self, text_cell, path, new_text):
+        '''If a backend name is changed, it saves the changes in the Backend
+
+        @param text_cell: not used. The gtk.CellRendererText that emitted the
+                          signal. Only here because it's passed by the signal
+        @param path: the gtk.TreePath of the edited cell
+        @param new_text: the new name of the backend
+        '''
+        #we strip everything not permitted in backend names
+        new_text = ''.join(c for c in new_text if (c.isalnum() or\
+                                    c in [" ", "-", "_"]))
+        selected_iter = self.liststore.get_iter(path)
+        # update the backend name
+        backend_id = self.liststore.get_value(selected_iter, \
+                                              self.COLUMN_BACKEND_ID)
+        backend = self.dialog.get_requester().get_backend(backend_id)
+        if backend:
+            backend.set_human_name(new_text)
+            # update the text in the liststore
+            self.liststore.set(selected_iter, self.COLUMN_TEXT, new_text)
+
+    def _init_signals(self):
+        '''Initializes the backends and gtk signals '''
+        self.connect("cursor-changed", self.on_select_row)
+        _signals = BackendSignals()
+        _signals.connect(_signals.BACKEND_ADDED, self.on_backend_added)
+        _signals.connect(_signals.BACKEND_STATE_TOGGLED,
+                         self.on_backend_state_changed)
+
+    def on_select_row(self, treeview = None):
+        '''When a row is selected, displays the corresponding editing panel
+
+        @treeview: not used
+        '''
+        self.dialog.on_backend_selected(self.get_selected_backend_id())
+
+    def _get_selected_path(self):
+        '''
+        Helper function to get the selected path
+
+        @return gtk.TreePath : returns exactly one path for the selected object or
+                               None
+        '''
+        selection = self.get_selection()
+        if selection:
+            model, selected_paths = self.get_selection().get_selected_rows()
+            if selected_paths:
+                return selected_paths[0]
+        return None
+
+    def select_backend(self, backend_id = None):
+        '''
+        Selects the backend corresponding to backend_id.
+        If backend_id is none, refreshes the current configuration panel.
+
+        @param backend_id: the id of the backend to select
+        '''
+        if backend_id in self.backendid_to_iter:
+            backend_iter = self.backendid_to_iter[backend_id]
+            selection = self.get_selection()
+            if selection:
+                selection.select_iter(backend_iter)
+        else:
+            if self._get_selected_path():
+                #We just reselect the currently selected entry
+                self.on_select_row()
+            else:
+                #If nothing is selected, we select the first entry
+                self.get_selection().select_path("0")
+        self.dialog.on_backend_selected(self.get_selected_backend_id())
+
+    def get_selected_backend_id(self):
+        '''
+        returns the selected backend id, or none
+
+        @return string: the selected backend id (or None)
+        '''
+        selected_path = self._get_selected_path()
+        if not selected_path:
+            return None
+        selected_iter = self.liststore.get_iter(selected_path)
+        return self.liststore.get_value(selected_iter, self.COLUMN_BACKEND_ID)

=== added file 'GTG/gtk/backends_dialog/configurepanel.py'
--- GTG/gtk/backends_dialog/configurepanel.py	1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/configurepanel.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,298 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import gtk
+
+from GTG.gtk.colors                        import get_colored_tags_markup
+from GTG                                   import _, ngettext
+from GTG.backends.genericbackend           import GenericBackend
+from GTG.gtk.backends_dialog.parameters_ui import ParametersUI
+from GTG.backends.backendsignals           import BackendSignals
+
+
+class ConfigurePanel(gtk.VBox):
+    ''' 
+    A VBox that lets you configure a backend
+    '''
+
+
+    def __init__(self, backends_dialog):
+        '''
+        Constructor, creating all the gtk widgets
+
+        @param backends_dialog: a reference to the dialog in which this is
+        loaded
+        '''
+        super(ConfigurePanel, self).__init__()
+        self.dialog = backends_dialog
+        self.should_spinner_be_shown = False
+        self.task_deleted_handle = None
+        self.task_added_handle = None
+        self.req = backends_dialog.get_requester()
+        self._create_widgets()
+        self._connect_signals()
+
+    def _connect_signals(self):
+        ''' Connects the backends generated signals '''
+        _signals = BackendSignals()
+        _signals.connect(_signals.BACKEND_RENAMED, self.refresh_title)
+        _signals.connect(_signals.BACKEND_STATE_TOGGLED, \
+                         self.refresh_sync_status)
+        _signals.connect(_signals.BACKEND_SYNC_STARTED, self.on_sync_started)
+        _signals.connect(_signals.BACKEND_SYNC_ENDED, self.on_sync_ended)
+    
+    def _create_widgets(self):
+        '''
+        This function fills this Vbox with widgets
+        '''
+        #Division of the available space in three segments:
+        # top, middle and bottom
+        top = gtk.HBox()
+        middle = gtk.HBox()
+        self._fill_top_hbox(top)
+        self._fill_middle_hbox(middle)
+        self.pack_start(top, False)
+        self.pack_start(middle, False)
+        align = gtk.Alignment(xalign = 0, yalign = 0, xscale = 1)
+        align.set_padding(10, 0, 0, 0)
+        self.parameters_ui = ParametersUI(self.req)
+        align.add(self.parameters_ui)
+        self.pack_start(align, False)
+
+    def _fill_top_hbox(self, hbox):
+        '''
+        Helper function to fill an hbox with an image, a spinner and 
+        three labels
+
+        @param hbox: the gtk.HBox to fill
+        '''
+        hbox.set_spacing(10)
+        self.image_icon = gtk.Image()
+        self.image_icon.set_size_request(100, 100)
+        vbox = gtk.VBox()
+        hbox_top = gtk.HBox()
+        self.human_name_label = gtk.Label()
+        self.human_name_label.set_alignment(xalign = 0, yalign = 0.5)
+        self.spinner = gtk.Spinner()
+        self.spinner.set_size_request(32, 32)
+        self.spinner.connect("show", self.on_spinner_show)
+        align_spin = gtk.Alignment(xalign = 1, yalign = 0)
+        align_spin.add(self.spinner)
+        hbox_top.pack_start(self.human_name_label, True)
+        hbox_top.pack_start(align_spin, False)
+        self.sync_desc_label = gtk.Label()
+        self.sync_desc_label.set_alignment(xalign = 0, yalign = 1)
+        self.sync_desc_label.set_line_wrap(True)
+        vbox.pack_start(hbox_top, True)
+        vbox.pack_start(self.sync_desc_label, True)
+        hbox.pack_start(self.image_icon, False)
+        align_vbox = gtk.Alignment(xalign = 0, yalign = 0, xscale = 1)
+        align_vbox.set_padding(10, 0, 20, 0)
+        align_vbox.add(vbox)
+        hbox.pack_start(align_vbox, True)
+
+    def _fill_middle_hbox(self, hbox):
+        '''
+        Helper function to fill an hbox with a label and a button
+
+        @param hbox: the gtk.HBox to fill
+        '''
+        self.sync_status_label = gtk.Label()
+        self.sync_status_label.set_alignment(xalign = 0.8, yalign = 0.5)
+        self.sync_button = gtk.Button()
+        self.sync_button.connect("clicked", self.on_sync_button_clicked)
+        hbox.pack_start(self.sync_status_label, True)
+        hbox.pack_start(self.sync_button, True)
+
+    def set_backend(self, backend_id):
+        '''Changes the backend to configure, refreshing this view.
+        
+        @param backend_id: the id of the backend to configure
+        '''
+        self.backend = self.dialog.get_requester().get_backend(backend_id)
+        self.refresh_title()
+        self.refresh_sync_status()
+        self.parameters_ui.refresh(self.backend)
+        self.image_icon.set_from_pixbuf(self.dialog.get_pixbuf_from_icon_name(\
+                                        self.backend.get_name(), 80, 80))
+
+    def refresh_title(self, sender = None, data = None):
+        '''
+        Callback for the signal that notifies backends name changes. It changes
+        the title of this view
+
+        @param sender: not used, here only for signal callback compatibility
+        @param data: not used, here only for signal callback compatibility
+        '''
+        markup = "<big><big><big><b>%s</b></big></big></big>" % \
+                                        self.backend.get_human_name()
+        self.human_name_label.set_markup(markup)
+    
+    def refresh_number_of_tasks(self):
+        '''refreshes the number of synced tasks by this backend'''
+        #FIXME: disabled for now. I'm not sure that this is nice because the
+        # count is correct only after the backend has synced all the pending
+        # tasks, and this is quite misleading (invernizzi)
+        return
+        #This will have to be changed for import/export..
+        tags = self.backend.get_attached_tags()
+        tasks_number = self.backend.get_number_of_tasks()
+        if GenericBackend.ALLTASKS_TAG in tags:
+            if tasks_number == 0:
+                markup = _("Ready to start syncing")
+            else:
+                markup = ngettext("Syncing your only task", \
+                    "Syncing all %d tasks" % tasks_number, tasks_number)
+        else:
+            tags_txt = get_colored_tags_markup(self.req, tags)
+            if tasks_number == 0:
+                markup = _("There's no task tagged %s") % tags_txt
+            else:
+                markup = ngettext("Syncing a task tagged %s" % tags_txt, \
+                    "Syncing %d tasks tagged %s" % (tasks_number, tags_txt), \
+                              tasks_number) 
+        self.sync_desc_label.set_markup(markup)
+
+    def refresh_sync_button(self):
+        '''
+        Refreshes the state of the button that enables the backend
+        '''
+        self.sync_button.set_sensitive(not self.backend.is_default())
+        if self.backend.is_enabled():
+            label = _("Disable syncing")
+        else:
+            label = _("Enable syncing")
+        self.sync_button.set_label(label)
+
+    def refresh_sync_status_label(self):
+        '''
+        Refreshes the gtk.Label that shows the current state of this backend
+        '''
+        if self.backend.is_default():
+            label = _("This is the default backend")
+        else:
+            if self.backend.is_enabled():
+                label = _("Syncing is enabled")
+            else:
+                label = _('Syncing is <span color="red">disabled</span>')
+        self.sync_status_label.set_markup(label)
+
+    def refresh_sync_status(self, sender = False, data = False):
+        '''Signal callback function, called when a backend state
+        (enabled/disabled) changes. Refreshes this view.
+
+        @param sender: not used, here only for signal callback compatibility
+        @param data: not used, here only for signal callback compatibility
+        '''
+        self.refresh_number_of_tasks()
+        self.refresh_sync_button()
+        self.refresh_sync_status_label()
+    
+    def set_hidden(self, is_hidden):
+        '''
+        Notifies this pane if it's hidden or not. We disconnect signals when
+        hidden, since there is no need to keep the UI updated.
+        Hopefully, this should make GTG faster :)
+
+        @param is_hidden: boolean, True if the window is not visible
+        '''
+        #These is only needed to refresh the number of synced tasks.
+        #since that is disabled for now, there is no need for this
+
+#        if is_hidden:
+#            if self.task_added_handle:
+#                self.req.disconnect(self.task_added_handle)
+#                self.task_added_handle = None
+#            if self.task_deleted_handle:
+#                self.req.disconnect(self.task_deleted_handle)
+#                self.task_deleted_handle = None
+#        else:
+#            self.task_added_handle = self.req.connect("task-added", \
+#                                            self.__on_task_changed)
+#            self.task_added_handle = self.req.connect("task-modified", \
+#                                            self.__on_task_changed)
+#            self.task_deleted_handle = self.req.connect("task-deleted",  \
+#                                            self.__on_task_changed)
+#
+#    def __on_task_changed(self, sender, task_id):
+#        '''
+#        If tasks are added, modified or removed, updates the number of
+#        tasks of the current backend
+#        '''
+#        self.refresh_sync_status()
+
+    def on_sync_button_clicked(self, sender):
+        '''
+        Signal callback when a backend is enabled/disabled via the UI button
+
+        @param sender: not used, here only for signal callback compatibility
+        '''
+        self.parameters_ui.commit_changes()
+        self.req.set_backend_enabled(self.backend.get_id(), \
+                                     not self.backend.is_enabled())
+
+    def on_sync_started(self, sender, backend_id):
+        '''
+        If the backend has started syncing tasks, update the state of the
+        gtk.Spinner
+
+        @param sender: not used, here only for signal callback compatibility
+        @param backend_id: the id of the backend that emitted this signal
+        '''
+        if backend_id == self.backend.get_id():
+            self.spinner_set_active(True)
+
+    def on_sync_ended(self, sender, backend_id):
+        '''
+        If the backend has stopped syncing tasks, update the state of the
+        gtk.Spinner
+
+        @param sender: not used, here only for signal callback compatibility
+        @param backend_id: the id of the backend that emitted this signal
+        '''
+
+        if backend_id == self.backend.get_id():
+            self.spinner_set_active(False)
+
+    def on_spinner_show(self, sender):
+        '''This signal callback hides the spinner if it's not supposed to be
+        seen. It's a workaround to let us call show_all on the whole window
+        while keeping this hidden (it's the only widget that requires special
+        attention)
+
+        @param sender: not used, here only for signal callback compatibility
+        '''
+        if self.should_spinner_be_shown == False:
+            self.spinner.hide()
+
+    def spinner_set_active(self, active):
+        '''
+        Enables/disables the gtk.Spinner, while showing/hiding it at the same
+        time
+        
+        @param active: True if the spinner should spin
+        '''
+        self.should_spinner_be_shown = active
+        if active:
+            self.spinner.start()
+            self.spinner.show()
+        else:
+            self.spinner.hide()
+            self.spinner.stop()
+

=== added directory 'GTG/gtk/backends_dialog/parameters_ui'
=== added file 'GTG/gtk/backends_dialog/parameters_ui/__init__.py'
--- GTG/gtk/backends_dialog/parameters_ui/__init__.py	1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/__init__.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+'''
+This modules reads a bakcn configuration and generates a series of widgets to
+let the user see the configuration and modify it.
+In this manner, backends do not need to know anything about their UI since it's
+built for them: it should play along the lines of the separation between GTG
+server and client
+'''
+
+#FIXME: all the parameters have one function in common (2 lines total).
+#       Evaluate if there is a clean way to avoid duplication of this code,
+#       without becoming too difficult to understand.
+#       (invernizzi)
+
+import gtk
+import functools
+
+from GTG import _
+from GTG.backends.genericbackend                        import GenericBackend
+from GTG.gtk.backends_dialog.parameters_ui.importtagsui import ImportTagsUI
+from GTG.gtk.backends_dialog.parameters_ui.textui       import TextUI
+from GTG.gtk.backends_dialog.parameters_ui.passwordui   import PasswordUI
+from GTG.gtk.backends_dialog.parameters_ui.periodui     import PeriodUI
+from GTG.gtk.backends_dialog.parameters_ui.checkboxui   import CheckBoxUI
+from GTG.gtk.backends_dialog.parameters_ui.pathui       import PathUI
+
+
+
+class ParametersUI(gtk.VBox):
+    '''
+    Given a bakcend, this gtk.VBox populates itself with all the necessary
+    widgets to view and edit a backend configuration
+    '''
+
+
+    COMMON_WIDTH = 150
+
+    def __init__(self, requester):
+        '''Constructs the list of the possible widgets.
+
+        @param requester: a GTG.core.requester.Requester object
+        '''
+        super(ParametersUI, self).__init__(False)
+        self.req = requester 
+        self.set_spacing(10)
+
+        #builds a list of widget generators. More precisely, it's a
+        # list of tuples: (backend_parameter_name, widget_generator)
+        self.parameter_widgets = ( \
+               ("import-tags", self.UI_generator(ImportTagsUI, \
+                            {"title": _("Import tags"), \
+                             "anybox_text": _("All tags"), \
+                             "somebox_text": _("Just these tags"), \
+                             "parameter_name": "import-tags"}) \
+               ),\
+               ("attached-tags", self.UI_generator(ImportTagsUI, \
+                            {"title": _("Tags to sync"), \
+                             "anybox_text": _("All tasks"), \
+                             "somebox_text": _("Tasks with these tags"), \
+                             "parameter_name": "attached-tags"}) \
+               ),\
+               ("path", self.UI_generator(PathUI)), \
+               ("username", self.UI_generator(TextUI, \
+                            {"description": _("Username"),
+                             "parameter_name": "username"})
+               ), \
+               ("password"     , self.UI_generator(PasswordUI)), \
+               ("period"       , self.UI_generator(PeriodUI)), \
+               ("import-from-replies", self.UI_generator(CheckBoxUI, \
+                            {"text": _("Import tasks from @ replies " + \
+                                                         "directed to you"), \
+                             "parameter": "import-from-replies"}) \
+               ),\
+               ("import-from-direct-messages", self.UI_generator(CheckBoxUI,  \
+                            {"text": _("Import tasks from direct messages"), \
+                             "parameter": "import-from-direct-messages"}) \
+               ),\
+               ("import-from-my-tweets", self.UI_generator(CheckBoxUI, \
+                            {"text": _("Import tasks from your tweets"), \
+                             "parameter": "import-from-my-tweets"}) \
+               ),\
+               ("import-bug-tags", self.UI_generator(CheckBoxUI, \
+                            {"text": _("Tag your tasks with the bug tags"), \
+                             "parameter": "import-bug-tags"}) \
+               ),\
+            ) 
+    def UI_generator(self, param_type, special_arguments = {}):
+        '''A helper function to build a widget type from a template.
+        It passes to the created widget generator a series of common parameters,
+        plus the ones needed to specialize the given template
+
+        @param param_type: the template to specialize
+        @param special_arguments: the arguments used for this particular widget
+                                  generator.
+
+        @return function: return a widget generator, not a widget. the widget can
+                          be obtained by calling widget_generator(backend)
+        '''
+        return lambda backend: param_type(req = self.req, \
+                                          backend = backend, \
+                                          width = self.COMMON_WIDTH, \
+                                          **special_arguments)
+
+    def refresh(self, backend):
+        '''Builds the widgets necessary to configure the backend. If it doesn't
+        know how to render a widget, it simply skips it.
+
+        @param backend: the backend that is being configured
+        '''
+        #remove the old parameters UIs
+        def _remove_child(self, child):
+            self.remove(child)
+        self.foreach(functools.partial(_remove_child, self))
+        #add new widgets
+        backend_parameters = backend.get_parameters()
+        if backend_parameters[GenericBackend.KEY_DEFAULT_BACKEND]:
+            #if it's the default backend, the user should not mess with it
+            return
+        for parameter_name, widget in self.parameter_widgets:
+            if parameter_name in backend_parameters:
+                self.pack_start(widget(backend), True)
+        self.show_all()
+
+    def commit_changes(self):
+        '''
+        Saves all the parameters at their current state (the user may have
+        modified them)
+        '''
+        def _commit_changes(child):
+            child.commit_changes()
+        self.foreach(_commit_changes)
+

=== added file 'GTG/gtk/backends_dialog/parameters_ui/checkboxui.py'
--- GTG/gtk/backends_dialog/parameters_ui/checkboxui.py	1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/checkboxui.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import gtk
+
+
+
+class CheckBoxUI(gtk.HBox):
+    '''
+    It's a widget displaying a simple checkbox, with some text to explain its
+    meaning
+    '''
+    
+
+    def __init__(self, req, backend, width, text, parameter):
+        '''
+        Creates the checkbox and the related label.
+
+        @param req: a Requester
+        @param backend: a backend object
+        @param width: the width of the gtk.Label object
+        @param parameter: the backend parameter this checkbox should display and
+                           modify
+        '''
+        super(CheckBoxUI, self).__init__()
+        self.backend = backend
+        self.req = req
+        self.text = text
+        self.parameter = parameter
+        self._populate_gtk(width)
+
+    def _populate_gtk(self, width):
+        '''Creates the checkbox and the related label
+        
+        @param width: the width of the gtk.Label object
+        '''
+        self.checkbutton =gtk.CheckButton(label = self.text)
+        self.checkbutton.set_active(self.backend.get_parameters()[self.parameter])
+        self.checkbutton.connect("toggled", self.on_modified)
+        self.pack_start(self.checkbutton, False)
+
+    def commit_changes(self):
+        '''Saves the changes to the backend parameter'''
+        self.backend.set_parameter(self.parameter,\
+                        self.checkbutton.get_active())
+
+    def on_modified(self, sender = None):
+        ''' Signal callback, executed when the user clicks on the checkbox.
+        Disables the backend. The user will re-enable it to confirm the changes
+        (s)he made.
+
+        @param sender: not used, only here for signal compatibility
+        '''
+        if self.backend.is_enabled() and not self.backend.is_default():
+            self.req.set_backend_enabled(self.backend.get_id(), False)
+

=== added file 'GTG/gtk/backends_dialog/parameters_ui/importtagsui.py'
--- GTG/gtk/backends_dialog/parameters_ui/importtagsui.py	1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/importtagsui.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,131 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import gtk
+
+from GTG.backends.genericbackend import GenericBackend
+
+
+
+class ImportTagsUI(gtk.VBox):
+    '''
+    It's a widget displaying a couple of radio buttons, a label and a textbox
+    to let the user change the attached tags (or imported)
+    '''
+    
+
+    def __init__(self, req, backend, width, title, anybox_text, somebox_text, \
+                 parameter_name):
+        '''Populates the widgets and refresh the tags to display
+
+        @param req: a requester
+        @param backend: the backend to configure
+        @param width: the length of the radio buttons
+        @param title: the text for the label describing what this collection
+                      of gtk widgets is used for
+        @param anybox_text: the text for the "Any tag matches" radio button
+        @param somebox_text: the text for the "only this set of tags matches"
+                             radio button
+        @param parameter_name: the backend parameter this widget should modify
+        '''
+        super(ImportTagsUI, self).__init__()
+        self.backend = backend
+        self.req = req
+        self.title = title
+        self.anybox_text = anybox_text
+        self.somebox_text = somebox_text
+        self.parameter_name = parameter_name
+        self._populate_gtk(width)
+        self._refresh_tags()
+        self._connect_signals()
+
+    def _populate_gtk(self, width):
+        '''
+        Populates the widgets
+
+        @param width: the length of the radio buttons
+        '''
+        title_label = gtk.Label()
+        title_label.set_alignment(xalign = 0, yalign = 0)
+        title_label.set_markup("<big><b>%s</b></big>" % self.title)
+        self.pack_start(title_label, True)
+        align = gtk.Alignment(xalign = 0, yalign = 0, xscale = 1)
+        align.set_padding(0, 0, 10, 0)
+        self.pack_start(align, True)
+        vbox = gtk.VBox()
+        align.add(vbox)
+        self.all_tags_radio = gtk.RadioButton(group = None, \
+                                              label = self.anybox_text)
+        vbox.pack_start(self.all_tags_radio, True)
+        self.some_tags_radio = gtk.RadioButton(group = self.all_tags_radio,
+                                               label = self.somebox_text)
+        self.some_tags_radio.set_size_request(width = width, height = -1)
+        hbox = gtk.HBox()
+        vbox.pack_start(hbox, True)
+        hbox.pack_start(self.some_tags_radio, False)
+        self.tags_entry = gtk.Entry()
+        hbox.pack_start(self.tags_entry, True)
+
+    def on_changed(self, radio, data = None):
+        ''' Signal callback, executed when the user modifies something.
+        Disables the backend. The user will re-enable it to confirm the changes
+        (s)he made.
+
+        @param sender: not used, only here for signal compatibility
+        @param data: not used, only here for signal compatibility
+        '''
+        #every change in the config disables the backend
+        self.req.set_backend_enabled(self.backend.get_id(), False)
+        self._refresh_textbox_state()
+
+    def commit_changes(self):
+        '''Saves the changes to the backend parameter'''
+        if self.all_tags_radio.get_active():
+            tags = [GenericBackend.ALLTASKS_TAG]
+        else:
+            tags = self.tags_entry.get_text().split(",")
+            tags = filter(lambda t: t, tags)
+        self.backend.set_parameter(self.parameter_name, tags)
+
+    def _refresh_textbox_state(self):
+        '''Refreshes the content of the textbox'''
+        self.tags_entry.set_sensitive(self.some_tags_radio.get_active())
+
+    def _refresh_tags(self):
+        '''
+        Refreshes the list of tags to display in the textbox, and selects
+        the correct radio button
+        '''
+        tags_list = self.backend.get_parameters()[self.parameter_name]
+        has_all_tasks = GenericBackend.ALLTASKS_TAG in tags_list
+        self.all_tags_radio.set_active(has_all_tasks)
+        self.some_tags_radio.set_active(not has_all_tasks)
+        self._refresh_textbox_state()
+        if not has_all_tasks:
+            tags_text = ""
+            if tags_list:
+                tags_text = reduce(lambda a, b: a + ", " + b, tags_list)
+            self.tags_entry.set_text(tags_text)
+
+    def _connect_signals(self):
+        '''Connects the gtk signals'''
+        self.some_tags_radio.connect("toggled", self.on_changed)
+        self.all_tags_radio.connect("toggled", self.on_changed)
+        self.tags_entry.connect("changed", self.on_changed)
+

=== added file 'GTG/gtk/backends_dialog/parameters_ui/passwordui.py'
--- GTG/gtk/backends_dialog/parameters_ui/passwordui.py	1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/passwordui.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import gtk
+
+from GTG import _
+
+
+
+class PasswordUI(gtk.HBox):
+    '''Widget displaying a gtk.Label and a textbox to input a password'''
+    
+
+    def __init__(self, req, backend, width):
+        '''Creates the gtk widgets and loads the current password in the text
+        field
+
+        @param req: a Requester
+        @param backend: a backend object
+        @param width: the width of the gtk.Label object
+        '''
+        super(PasswordUI, self).__init__()
+        self.backend = backend
+        self.req = req
+        self._populate_gtk(width)
+        self._load_password()
+        self._connect_signals()
+
+    def _populate_gtk(self, width):
+        '''Creates the text box and the related label
+        
+        @param width: the width of the gtk.Label object
+        '''
+        password_label = gtk.Label(_("Password:"))
+        password_label.set_alignment(xalign = 0, yalign = 0.5)
+        password_label.set_size_request(width = width, height = -1)
+        self.pack_start(password_label, False)
+        align = gtk.Alignment(xalign = 0, yalign = 0.5, xscale = 1)
+        align.set_padding(0, 0, 10, 0)
+        self.pack_start(align, True)
+        self.password_textbox = gtk.Entry()
+        align.add(self.password_textbox)
+
+    def _load_password(self):
+        '''Loads the password from the backend'''
+        password = self.backend.get_parameters()['password']
+        self.password_textbox.set_invisible_char('*')
+        self.password_textbox.set_visibility(False)
+        self.password_textbox.set_text(password)
+
+    def _connect_signals(self):
+        '''Connects the gtk signals'''
+        self.password_textbox.connect('changed', self.on_password_modified)
+
+    def commit_changes(self):
+        '''Saves the changes to the backend parameter ('password')'''
+        self.backend.set_parameter('password', self.password_textbox.get_text())
+
+    def on_password_modified(self, sender):
+        ''' Signal callback, executed when the user edits the password.
+        Disables the backend. The user will re-enable it to confirm the changes
+        (s)he made.
+
+        @param sender: not used, only here for signal compatibility
+        '''
+        if self.backend.is_enabled() and not self.backend.is_default():
+            self.req.set_backend_enabled(self.backend.get_id(), False)
+

=== added file 'GTG/gtk/backends_dialog/parameters_ui/pathui.py'
--- GTG/gtk/backends_dialog/parameters_ui/pathui.py	1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/pathui.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import gtk
+import os.path
+
+from GTG import _
+
+
+
+
+class PathUI(gtk.HBox):
+    '''Gtk widgets to show a path in a textbox, and a button to bring up a
+    filesystem explorer to modify that path (also, a label to describe those)
+    '''
+    
+
+    def __init__(self, req, backend, width):
+        '''
+        Creates the textbox, the button and loads the current path.
+
+        @param req: a Requester
+        @param backend: a backend object
+        @param width: the width of the gtk.Label object
+        '''
+        super(PathUI, self).__init__()
+        self.backend = backend
+        self.req = req
+        self._populate_gtk(width)
+
+    def _populate_gtk(self, width):
+        '''Creates the gtk.Label, the textbox and the button
+        
+        @param width: the width of the gtk.Label object
+        '''
+        label = gtk.Label(_("Filename:"))
+        label.set_alignment(xalign = 0, yalign = 0.5)
+        label.set_size_request(width = width, height = -1)
+        self.pack_start(label, False)
+        align = gtk.Alignment(xalign = 0, yalign = 0.5, xscale = 1)
+        align.set_padding(0, 0, 10, 0)
+        self.pack_start(align, True)
+        self.textbox = gtk.Entry()
+        self.textbox.set_text(self.backend.get_parameters()['path'])
+        self.textbox.connect('changed', self.on_path_modified)
+        align.add(self.textbox)
+        self.button = gtk.Button(stock = gtk.STOCK_EDIT)
+        self.button.connect('clicked', self.on_button_clicked)
+        self.pack_start(self.button, False)
+
+    def commit_changes(self):
+        '''Saves the changes to the backend parameter'''
+        self.backend.set_parameter('path', self.textbox.get_text())
+
+    def on_path_modified(self, sender):
+        ''' Signal callback, executed when the user edits the path.
+        Disables the backend. The user will re-enable it to confirm the changes
+        (s)he made.
+
+        @param sender: not used, only here for signal compatibility
+        '''
+        if self.backend.is_enabled() and not self.backend.is_default():
+            self.req.set_backend_enabled(self.backend.get_id(), False)
+    
+    def on_button_clicked(self, sender):
+        '''Shows the filesystem explorer to choose a new file
+
+        @param sender: not used, only here for signal compatibility
+        '''
+        self.chooser = gtk.FileChooserDialog( \
+                    title=None,
+                    action=gtk.FILE_CHOOSER_ACTION_SAVE,
+                    buttons=(gtk.STOCK_CANCEL,
+                             gtk.RESPONSE_CANCEL, \
+                             gtk.STOCK_OK, \
+                             gtk.RESPONSE_OK))
+        self.chooser.set_default_response(gtk.RESPONSE_OK)
+        #set default file as the current self.path
+        self.chooser.set_current_name(os.path.basename(self.textbox.get_text()))
+        self.chooser.set_current_folder(os.path.dirname(self.textbox.get_text()))
+        
+        #filter files
+        afilter = gtk.FileFilter()
+        afilter.set_name("All files")
+        afilter.add_pattern("*")
+        self.chooser.add_filter(afilter)
+        afilter = gtk.FileFilter()
+        afilter.set_name("XML files")
+        afilter.add_mime_type("text/plain")
+        afilter.add_pattern("*.xml")
+        self.chooser.add_filter(afilter)
+        response = self.chooser.run()
+        if response == gtk.RESPONSE_OK:
+            self.textbox.set_text(self.chooser.get_filename())
+        self.chooser.destroy()

=== added file 'GTG/gtk/backends_dialog/parameters_ui/periodui.py'
--- GTG/gtk/backends_dialog/parameters_ui/periodui.py	1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/periodui.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import gtk
+
+from GTG import _
+
+
+
+class PeriodUI(gtk.HBox):
+    '''A widget to change the frequency of a backend synchronization
+    '''
+    
+
+    def __init__(self, req, backend, width):
+        '''
+        Creates the gtk.Adjustment and the related label. Loads the current
+        period.
+
+        @param req: a Requester
+        @param backend: a backend object
+        @param width: the width of the gtk.Label object
+        '''
+        super(PeriodUI, self).__init__()
+        self.backend = backend
+        self.req = req
+        self._populate_gtk(width)
+        self._connect_signals()
+
+    def _populate_gtk(self, width):
+        '''Creates the gtk widgets
+        
+        @param width: the width of the gtk.Label object
+        '''
+        period_label = gtk.Label(_("Period:"))
+        period_label.set_alignment(xalign = 0, yalign = 0.5)
+        period_label.set_size_request(width = width, height = -1)
+        self.pack_start(period_label, False)
+        align = gtk.Alignment(xalign = 0, yalign = 0.5, xscale = 1)
+        align.set_padding(0, 0, 10, 0)
+        self.pack_start(align, False)
+        period = self.backend.get_parameters()['period']
+        self.adjustment = gtk.Adjustment(value = period,
+                                         lower = 1,
+                                         upper = 120,
+                                         step_incr = 1,
+                                         page_incr = 0,
+                                         page_size = 0)
+        self.period_spin = gtk.SpinButton(adjustment = self.adjustment,
+                                          climb_rate = 0.3,
+                                          digits = 0)
+        align.add(self.period_spin)
+        self.show_all()
+
+    def _connect_signals(self):
+        '''Connects the gtk signals'''
+        self.period_spin.connect('changed', self.on_spin_changed)
+
+    def commit_changes(self):
+        '''Saves the changes to the backend parameter'''
+        self.backend.set_parameter('period', int(self.adjustment.get_value()))
+
+    def on_spin_changed(self, sender):
+        ''' Signal callback, executed when the user changes the period.
+        Disables the backend. The user will re-enable it to confirm the changes
+        (s)he made.
+
+        @param sender: not used, only here for signal compatibility
+        '''
+        if self.backend.is_enabled() and not self.backend.is_default():
+            self.req.set_backend_enabled(self.backend.get_id(), False)
+

=== added file 'GTG/gtk/backends_dialog/parameters_ui/textui.py'
--- GTG/gtk/backends_dialog/parameters_ui/textui.py	1970-01-01 00:00:00 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/textui.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import gtk
+
+
+
+class TextUI(gtk.HBox):
+    '''A widget to display a simple textbox and a label to describe its content
+    '''
+    
+
+    def __init__(self, req, backend, width, description, parameter_name):
+        '''
+        Creates the textbox and the related label. Loads the current
+        content.
+
+        @param req: a Requester
+        @param backend: a backend object
+        @param width: the width of the gtk.Label object
+        '''
+        super(TextUI, self).__init__()
+        self.backend = backend
+        self.req = req
+        self.parameter_name = parameter_name
+        self.description = description
+        self._populate_gtk(width)
+
+    def _populate_gtk(self, width):
+        '''Creates the gtk widgets
+        
+        @param width: the width of the gtk.Label object
+        '''
+        label = gtk.Label("%s:" % self.description)
+        label.set_alignment(xalign = 0, yalign = 0.5)
+        label.set_size_request(width = width, height = -1)
+        self.pack_start(label, False)
+        align = gtk.Alignment(xalign = 0, yalign = 0.5, xscale = 1)
+        align.set_padding(0, 0, 10, 0)
+        self.pack_start(align, True)
+        self.textbox = gtk.Entry()
+        self.textbox.set_text(\
+                        self.backend.get_parameters()[self.parameter_name])
+        self.textbox.connect('changed', self.on_text_modified)
+        align.add(self.textbox)
+
+    def commit_changes(self):
+        '''Saves the changes to the backend parameter'''
+        self.backend.set_parameter(self.parameter_name,\
+                        self.textbox.get_text())
+
+    def on_text_modified(self, sender):
+        ''' Signal callback, executed when the user changes the text.
+        Disables the backend. The user will re-enable it to confirm the changes
+        (s)he made.
+
+        @param sender: not used, only here for signal compatibility
+        '''
+        if self.backend.is_enabled() and not self.backend.is_default():
+            self.req.set_backend_enabled(self.backend.get_id(), False)
+

=== modified file 'GTG/gtk/browser/browser.py'
--- GTG/gtk/browser/browser.py	2010-08-10 17:30:24 +0000
+++ GTG/gtk/browser/browser.py	2010-08-13 23:45:20 +0000
@@ -34,7 +34,9 @@
 
 #our own imports
 import GTG
-from GTG.core                    import CoreConfig
+from GTG.backends.backendsignals import BackendSignals
+from GTG.gtk.browser.custominfobar import CustomInfoBar
+from GTG.core                       import CoreConfig
 from GTG                         import _, info, ngettext
 from GTG.core.task               import Task
 from GTG.gtk.browser             import GnomeConfig, tasktree, tagtree
@@ -206,6 +208,7 @@
         self.sidebar_notebook   = self.builder.get_object("sidebar_notebook")
         self.main_notebook      = self.builder.get_object("main_notebook")
         self.accessory_notebook = self.builder.get_object("accessory_notebook")
+        self.vbox_toolbars      = self.builder.get_object("vbox_toolbars")
         
         self.closed_pane        = None
 
@@ -313,6 +316,8 @@
                 self.on_nonworkviewtag_toggled,
             "on_preferences_activate":
                 self.open_preferences,
+            "on_edit_backends_activate":
+                self.open_edit_backends,
         }
         self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC)
 
@@ -346,6 +351,16 @@
         # Connect requester signals to TreeModels
         self.req.connect("task-added", self.on_task_added) 
         self.req.connect("task-deleted", self.on_task_deleted)
+        #this causes changed be shouwn only on save
+        #tree = self.task_tree_model.get_tree()
+        #tree.connect("task-added-inview", self.on_task_added) 
+        #tree.connect("task-deleted-inview", self.on_task_deleted)
+        b_signals = BackendSignals()
+        b_signals.connect(b_signals.BACKEND_FAILED, self.on_backend_failed)
+        b_signals.connect(b_signals.BACKEND_STATE_TOGGLED, \
+                          self.remove_backend_infobar)
+        b_signals.connect(b_signals.INTERACTION_REQUESTED, \
+                          self.on_backend_needing_interaction)
         
         # Connect signals from models
         self.task_modelsort.connect("row-has-child-toggled",\
@@ -425,9 +440,12 @@
 
 ### HELPER FUNCTIONS ########################################################
 
-    def open_preferences(self,widget):
+    def open_preferences(self, widget):
         self.vmanager.open_preferences(self.priv)
         
+    def open_edit_backends(self, widget):
+        self.vmanager.open_edit_backends()
+
     def quit(self,widget=None):
         self.vmanager.close_browser()
         
@@ -522,7 +540,7 @@
                     col_id,\
                     self.priv["tasklist"]["sort_order"])
             except:
-                print "Invalid configuration for sorting columns"
+                Log.error("Invalid configuration for sorting columns")
 
         if "view" in self.config["browser"]:
             view = self.config["browser"]["view"]
@@ -953,7 +971,9 @@
                     text = \
                         text.replace("%s%s:%s" % (spaces, attribute, args), "")
             # Create the new task
-            task = self.req.new_task(tags=[t.get_name() for t in tags], newtask=True)
+            task = self.req.new_task( newtask=True)
+            for tag in tags:
+                task.add_tag(tag.get_name())
             if text != "":
                 task.set_title(text.strip())
                 task.set_to_keep()
@@ -1516,3 +1536,82 @@
         """ Returns true if window is the currently active window """
         return self.window.get_property("is-active")
 
+## BACKENDS RELATED METHODS ##################################################
+
+    def on_backend_failed(self, sender, backend_id, error_code):
+        '''
+        Signal callback.
+        When a backend fails to work, loads a gtk.Infobar to alert the user
+
+        @param sender: not used, only here for signal compatibility
+        @param backend_id: the id of the failing backend 
+        @param error_code: a backend error code, as specified in BackendsSignals
+        '''
+        infobar = self._new_infobar(backend_id)
+        infobar.set_error_code(error_code)
+
+    def on_backend_needing_interaction(self, sender, backend_id, description, \
+                                       interaction_type, callback):
+        '''
+        Signal callback.
+        When a backend needs some kind of feedback from the user,
+        loads a gtk.Infobar to alert the user.
+        This is used, for example, to request confirmation after authenticating
+        via OAuth.
+
+        @param sender: not used, only here for signal compatibility
+        @param backend_id: the id of the failing backend 
+        @param description: a string describing the interaction needed
+        @param interaction_type: a string describing the type of interaction
+                                 (yes/no, only confirm, ok/cancel...)
+        @param callback: the function to call when the user provides the
+                         feedback
+        '''
+        infobar = self._new_infobar(backend_id)
+        infobar.set_interaction_request(description, interaction_type, callback)
+
+
+    def __remove_backend_infobar(self, child, backend_id):
+        '''
+        Helper function to remove an gtk.Infobar related to a backend
+
+        @param child: a gtk.Infobar
+        @param backend_id: the id of the backend which gtk.Infobar should be
+                            removed.
+        '''
+        if isinstance(child, CustomInfoBar) and\
+            child.get_backend_id() == backend_id:
+            if self.vbox_toolbars:
+                self.vbox_toolbars.remove(child)
+
+    def remove_backend_infobar(self, sender, backend_id):
+        '''
+        Signal callback.
+        Deletes the gtk.Infobars related to a backend
+
+        @param sender: not used, only here for signal compatibility
+        @param backend_id: the id of the backend which gtk.Infobar should be
+                            removed.
+        '''
+        backend = self.req.get_backend(backend_id)
+        if not backend or (backend and backend.is_enabled()):
+            #remove old infobar related to backend_id, if any
+            if self.vbox_toolbars:
+                self.vbox_toolbars.foreach(self.__remove_backend_infobar, \
+                                       backend_id)
+
+    def _new_infobar(self, backend_id):
+        '''
+        Helper function to create a new infobar for a backend
+        
+        @param backend_id: the backend for which we're creating the infobar
+        @returns gtk.Infobar: the created infobar
+        '''
+        #remove old infobar related to backend_id, if any
+        if not self.vbox_toolbars:
+            return
+        self.vbox_toolbars.foreach(self.__remove_backend_infobar, backend_id)
+        #add a new one
+        infobar = CustomInfoBar(self.req, self, self.vmanager, backend_id)
+        self.vbox_toolbars.pack_start(infobar, True)
+        return infobar

=== added file 'GTG/gtk/browser/custominfobar.py'
--- GTG/gtk/browser/custominfobar.py	1970-01-01 00:00:00 +0000
+++ GTG/gtk/browser/custominfobar.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import gtk
+import threading
+
+from GTG                         import _
+from GTG.backends.backendsignals import BackendSignals
+from GTG.tools.networkmanager    import is_connection_up
+
+
+
+class CustomInfoBar(gtk.InfoBar):
+    '''
+    A gtk.InfoBar specialized for displaying errors and requests for
+    interaction coming from the backends
+    '''
+
+
+    AUTHENTICATION_MESSAGE = _("The <b>%s</b> backend cannot login with the "
+                               "supplied authentication data and has been"
+                               " disabled. To retry the login, re-enable the backend.")
+
+    NETWORK_MESSAGE = _("Due to a network problem, I cannot contact "
+                        "the <b>%s</b> backend.")
+
+    DBUS_MESSAGE = _("Cannot connect to DBUS, I've disabled "
+                        "the <b>%s</b> backend.")
+
+    def __init__(self, req, browser, vmanager, backend_id):
+        '''
+        Constructor, Prepares the infobar.
+
+        @param req: a Requester object
+        @param browser: a TaskBrowser object
+        @param vmanager: a ViewManager object
+        @param backend_id: the id of the backend linked to the infobar
+        '''
+        super(CustomInfoBar, self).__init__()
+        self.req = req
+        self.browser = browser
+        self.vmanager = vmanager
+        self.backend_id = backend_id
+        self.backend = self.req.get_backend(backend_id)
+
+    def get_backend_id(self):
+        '''
+        Getter function to return the id of the backend for which this
+        gtk.InfoBar was created
+        '''
+        return self.backend_id
+
+    def _populate(self):
+        '''Setting up gtk widgets'''
+        content_hbox = self.get_content_area()
+        content_hbox.set_homogeneous(False)
+        self.label = gtk.Label()
+        self.label.set_line_wrap(True)
+        self.label.set_alignment(0.5, 0.5)
+        self.label.set_justify(gtk.JUSTIFY_FILL)
+        content_hbox.pack_start(self.label, True, True)
+
+    def _on_error_response(self, widget, event):
+        '''
+        Signal callback executed when the user acknowledges the error displayed
+        in the infobar
+
+        @param widget: not used, here for compatibility with signals callbacks
+        @param event: the code of the gtk response
+        '''
+        self.hide()
+        if event == gtk.RESPONSE_ACCEPT:
+            self.vmanager.configure_backend(backend_id = self.backend_id)
+
+    def set_error_code(self, error_code):
+        '''
+        Sets this infobar to show an error to the user
+
+        @param error_code: the code of the error to show. Error codes are listed
+                           in BackendSignals
+        '''
+        self._populate()
+        self.connect("response", self._on_error_response)
+        backend_name = self.backend.get_human_name()
+
+        if error_code == BackendSignals.ERRNO_AUTHENTICATION:
+            self.set_message_type(gtk.MESSAGE_ERROR)
+            self.label.set_markup(self.AUTHENTICATION_MESSAGE % backend_name)
+            self.add_button(_('Configure backend'), gtk.RESPONSE_ACCEPT)
+            self.add_button(_('Ignore'), gtk.RESPONSE_CLOSE)
+
+        elif error_code == BackendSignals.ERRNO_NETWORK:
+            if not is_connection_up():
+                return
+            self.set_message_type(gtk.MESSAGE_WARNING)
+            self.label.set_markup(self.NETWORK_MESSAGE % backend_name)
+            #FIXME: use gtk stock button instead
+            self.add_button(_('Ok'), gtk.RESPONSE_CLOSE)
+        
+        elif error_code == BackendSignals.ERRNO_DBUS:
+            self.set_message_type(gtk.MESSAGE_WARNING)
+            self.label.set_markup(self.DBUS_MESSAGE % backend_name)
+            self.add_button(_('Ok'), gtk.RESPONSE_CLOSE)
+
+        self.show_all()
+
+    def set_interaction_request(self, description, interaction_type, callback):
+        '''
+        Sets this infobar to request an interaction from the user
+
+        @param description: a string describing the interaction needed
+        @param interaction_type: a string describing the type of interaction
+                                 (yes/no, only confirm, ok/cancel...)
+        @param callback: the function to call when the user provides the
+                         feedback
+        '''
+        self._populate()
+        self.callback = callback
+        self.set_message_type(gtk.MESSAGE_INFO)
+        self.label.set_markup(description)
+        self.connect("response", self._on_interaction_response)
+        self.interaction_type = interaction_type
+        if interaction_type == BackendSignals().INTERACTION_CONFIRM:
+            self.add_button(_('Confirm'), gtk.RESPONSE_ACCEPT)
+        elif interaction_type == BackendSignals().INTERACTION_TEXT:
+            self.add_button(_('Continue'), gtk.RESPONSE_ACCEPT)
+        self.show_all()
+
+    def _on_interaction_response(self, widget, event):
+        '''
+        Signal callback executed when the user gives the feedback for a
+        requested interaction
+
+        @param widget: not used, here for compatibility with signals callbacks
+        @param event: the code of the gtk response
+        '''
+        if event == gtk.RESPONSE_ACCEPT:
+            if self.interaction_type == BackendSignals().INTERACTION_TEXT:
+                self._prepare_textual_interaction()
+                print "done"
+            elif self.interaction_type == BackendSignals().INTERACTION_CONFIRM:
+                self.hide()
+                threading.Thread(target = getattr(self.backend,
+                                            self.callback)).start()
+
+    def _prepare_textual_interaction(self):
+        '''
+        Helper function. gtk calls to populate the infobar in the case of
+        interaction request
+        '''
+        title, description\
+                = getattr(self.backend, self.callback)("get_title")
+        self.dialog = gtk.Window()#type = gtk.WINDOW_POPUP)
+        self.dialog.set_title(title)
+        self.dialog.set_transient_for(self.browser.window)
+        self.dialog.set_destroy_with_parent(True)
+        self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
+        self.dialog.set_modal(True)
+        #        self.dialog.set_size_request(300,170)
+        vbox = gtk.VBox()
+        self.dialog.add(vbox)
+        description_label = gtk.Label()
+        description_label.set_justify(gtk.JUSTIFY_FILL)
+        description_label.set_line_wrap(True)
+        description_label.set_markup(description)
+        align = gtk.Alignment(0.5, 0.5, 1, 1)
+        align.set_padding(10, 0, 20, 20)
+        align.add(description_label)
+        vbox.pack_start(align)
+        self.text_box = gtk.Entry()
+        self.text_box.set_size_request(-1, 40)
+        align = gtk.Alignment(0.5, 0.5, 1, 1)
+        align.set_padding(20, 20, 20, 20)
+        align.add(self.text_box)
+        vbox.pack_start(align)
+        button = gtk.Button(stock = gtk.STOCK_OK)
+        button.connect("clicked", self._on_text_confirmed)
+        button.set_size_request(-1, 40)
+        vbox.pack_start(button, False)
+        self.dialog.show_all()
+        self.hide()
+
+    def _on_text_confirmed(self, widget):
+        '''
+        Signal callback, used when the interaction needs a textual input to be
+        completed (e.g, the twitter OAuth, requesting a pin)
+
+        @param widget: not used, here for signal callback compatibility
+        '''
+        text = self.text_box.get_text()
+        self.dialog.destroy()
+        threading.Thread(target = getattr(self.backend, self.callback), 
+                          args = ("set_text", text)).start()
+

=== modified file 'GTG/gtk/browser/taskbrowser.glade'
--- GTG/gtk/browser/taskbrowser.glade	2010-05-22 22:41:44 +0000
+++ GTG/gtk/browser/taskbrowser.glade	2010-08-13 23:45:20 +0000
@@ -154,6 +154,17 @@
                         <signal name="activate" handler="on_preferences_activate"/>
                       </object>
                     </child>
+                    <child>
+                      <object class="GtkImageMenuItem" id="backends_mi">
+                        <property name="label">_Backends</property>
+                        <property name="visible">True</property>
+                        <property name="use_underline">True</property>
+                        <property name="image">image4</property>
+                        <property name="use_stock">False</property>
+                        <property name="accel_group">accelgroup1</property>
+                        <signal name="activate" handler="on_edit_backends_activate"/>
+                      </object>
+                    </child>
                   </object>
                 </child>
               </object>

=== modified file 'GTG/gtk/colors.py'
--- GTG/gtk/colors.py	2010-06-07 21:14:45 +0000
+++ GTG/gtk/colors.py	2010-08-13 23:45:20 +0000
@@ -20,7 +20,7 @@
 
 #Take list of Tags and give the background color that should be applied
 #The returned color might be None (in which case, the default is used)
-def background_color(tags, bgcolor=None):
+def background_color(tags, bgcolor = None):
     if not bgcolor:
         bgcolor = gtk.gdk.color_parse("#FFFFFF")
     # Compute color
@@ -52,3 +52,29 @@
         my_color = gtk.gdk.Color(red, green, blue).to_string()
     return my_color
 
+def get_colored_tag_markup(req, tag_name):
+    '''
+    Given a tag name, returns a string containing the markup to color the
+    tag name
+    '''
+    tag = req.get_tag(tag_name)
+    if tag is None:
+        #no task loaded with that tag, color cannot be taken
+        return tag_name
+    else:
+        tag_color = tag.get_attribute("color")
+        if tag_color:
+            return '<span color="%s">%s</span>' % (tag_color, tag_name)
+        else:
+            return tag_name
+
+def get_colored_tags_markup(req, tag_names):
+    '''
+    Calls get_colored_tag_markup for each tag_name in tag_names
+    '''
+    tag_markups = map(lambda t: get_colored_tag_markup(req, t), tag_names)
+    tags_txt = ""
+    if tag_markups:
+        #reduce crashes if applied to an empty list
+        tags_txt = reduce(lambda a, b: a + ", " + b, tag_markups)
+    return tags_txt

=== modified file 'GTG/gtk/manager.py'
--- GTG/gtk/manager.py	2010-08-03 17:07:31 +0000
+++ GTG/gtk/manager.py	2010-08-13 23:45:20 +0000
@@ -39,10 +39,11 @@
 from GTG.core.plugins.engine import PluginEngine
 from GTG.core.plugins.api    import PluginAPI
 from GTG.tools.logger        import Log
-
-
-
-class Manager:
+from GTG.gtk.backends_dialog import BackendsDialog
+
+
+
+class Manager(object):
     
 
     ############## init #####################################################
@@ -80,6 +81,7 @@
         #Preferences and Backends windows
         # Initialize  dialogs
         self.preferences_dialog = None
+        self.edit_backends_dialog = None
         
         #DBus
         DBusTaskWrapper(self.req, self)
@@ -196,6 +198,16 @@
             
 ################ Others dialog ############################################
 
+    def open_edit_backends(self, sender = None, backend_id = None):
+        if not self.edit_backends_dialog:
+            self.edit_backends_dialog = BackendsDialog(self.req)
+        self.edit_backends_dialog.activate()
+        if backend_id != None:
+            self.edit_backends_dialog.show_config_for_backend(backend_id)
+
+    def configure_backend(self, backend_id):
+        self.open_edit_backends(None, backend_id)
+
     def open_preferences(self, config_priv, sender=None):
         if not hasattr(self, "preferences"):
             self.preferences = PreferencesDialog(self.pengine, self.p_apis, \
@@ -211,7 +223,8 @@
                     self.close_task(t)
             
 ### MAIN ###################################################################
-    def main(self, once_thru=False):
+
+    def main(self, once_thru = False):
         gobject.threads_init()
         if once_thru:
             gtk.main_iteration()
@@ -219,7 +232,6 @@
             gtk.main()
         return 0
         
-        
     def quit(self,sender=None):
         gtk.main_quit()
         #save opened tasks and their positions.

=== added file 'GTG/tests/test_interruptible.py'
--- GTG/tests/test_interruptible.py	1970-01-01 00:00:00 +0000
+++ GTG/tests/test_interruptible.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Gettings Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+'''
+Tests for interrupting cooperative threads
+'''
+
+import unittest
+import time
+from threading import Thread, Event
+
+from GTG.tools.interruptible import interruptible, _cancellation_point
+
+
+class TestInterruptible(unittest.TestCase):
+    '''
+    Tests for interrupting cooperative threads
+    '''
+
+    def test_interruptible_decorator(self):
+        self.quit_condition = False
+        cancellation_point = lambda: _cancellation_point(\
+                                        lambda: self.quit_condition)
+        self.thread_started = Event()
+        @interruptible
+        def never_ending(cancellation_point):
+            self.thread_started.set()
+            while True:
+                time.sleep(0.1)
+                cancellation_point()
+        thread = Thread(target = never_ending, args = (cancellation_point, ))
+        thread.start()
+        self.thread_started.wait()
+        self.quit_condition = True
+        countdown = 10
+        while thread.is_alive() and countdown > 0:
+            time.sleep(0.1)
+            countdown -= 1
+        self.assertFalse(thread.is_alive())
+
+
+
+
+
+
+
+
+
+    
+
+def test_suite():
+    return unittest.TestLoader().loadTestsFromTestCase(TestInterruptible)
+

=== added file 'GTG/tools/networkmanager.py'
--- GTG/tools/networkmanager.py	1970-01-01 00:00:00 +0000
+++ GTG/tools/networkmanager.py	2010-08-13 23:45:20 +0000
@@ -0,0 +1,57 @@
+#!/bin/env python
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright (C) 2010 Red Hat, Inc.
+#
+
+import dbus
+
+
+def is_connection_up():
+    '''
+    Returns True if network-manager reports that at least one connection is up
+
+    @returns bool
+    '''
+    state = False
+    bus = dbus.SystemBus()
+
+    proxy = bus.get_object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
+    manager = dbus.Interface(proxy, "org.freedesktop.NetworkManager")
+
+    manager_prop_iface = dbus.Interface(proxy, "org.freedesktop.DBus.Properties")
+    active = manager_prop_iface.Get("org.freedesktop.NetworkManager", "ActiveConnections")
+    for a in active:
+        ac_proxy = bus.get_object("org.freedesktop.NetworkManager", a)
+        prop_iface = dbus.Interface(ac_proxy, "org.freedesktop.DBus.Properties")
+        state = prop_iface.Get("org.freedesktop.NetworkManager.ActiveConnection", "State")
+
+        # Connections in NM are a collection of settings that describe everything
+        # needed to connect to a specific network.  Lets get those details so we
+        # can find the user-readable name of the connection.
+        con_path = prop_iface.Get("org.freedesktop.NetworkManager.ActiveConnection", "Connection")
+        con_service = prop_iface.Get("org.freedesktop.NetworkManager.ActiveConnection", "ServiceName")
+
+        # ask the provider of the connection for its details
+        service_proxy = bus.get_object(con_service, con_path)
+        con_iface = dbus.Interface(service_proxy, "org.freedesktop.NetworkManagerSettings.Connection")
+        con_details = con_iface.GetSettings()
+        con_name = con_details['connection']['id']
+
+        if state == 2:   # activated
+            state = True
+    return state
+

=== added file 'data/icons/hicolor/scalable/apps/backend_localfile.png'
Binary files data/icons/hicolor/scalable/apps/backend_localfile.png	1970-01-01 00:00:00 +0000 and data/icons/hicolor/scalable/apps/backend_localfile.png	2010-08-13 23:45:20 +0000 differ

Follow ups