conduit r1854 - in trunk: . conduit conduit/dataproviders conduit/gtkui



Author: arosenfeld
Date: Tue Feb 10 04:03:44 2009
New Revision: 1854
URL: http://svn.gnome.org/viewvc/conduit?rev=1854&view=rev

Log:
2009-02-10  Alexandre Rosenfeld  <airmind gmail com>

        New configuration system commit. Only the core classes are 
        commited, no dataprovider is changed yet. These will be
        commited when ready.

        * conduit/Configurator.py:
        Base configuration classes
        * conduit/Module.py:
        Log an error when not able to load a module
        * conduit/dataproviders/DataProvider.py:
        Added update_configuration as an alternative to 
        implementing get/set_configuration in data
        providers.
        Added new configuration system methods
        * conduit/gtkui/Canvas.py:
        Use the new configuration system if availiable in a data
        provider, or logs a critical message if not, and runs
        the old configure method.
        * conduit/gtkui/ConfigContainer.py:
        * conduit/gtkui/ConfigWidgets.py:
        * conduit/gtkui/EmbedConfigurator.py:
        * conduit/gtkui/WindowConfigurator.py:
        New configuration system. Temporarily documented in 
        http://wiki.arosenfeld.net/Conduit



Added:
   trunk/conduit/Configurator.py
   trunk/conduit/gtkui/ConfigContainer.py
   trunk/conduit/gtkui/ConfigWidgets.py
   trunk/conduit/gtkui/EmbedConfigurator.py
   trunk/conduit/gtkui/WindowConfigurator.py
Modified:
   trunk/ChangeLog
   trunk/conduit/Module.py
   trunk/conduit/dataproviders/DataProvider.py
   trunk/conduit/gtkui/Canvas.py

Added: trunk/conduit/Configurator.py
==============================================================================
--- (empty file)
+++ trunk/conduit/Configurator.py	Tue Feb 10 04:03:44 2009
@@ -0,0 +1,87 @@
+"""
+Configuration interfaces
+
+Copyright: Alexandre Rosenfeld, 2009
+License: GPLv2
+"""
+import os.path
+import gobject
+import logging
+log = logging.getLogger("conduit.Configutator")
+
+from gettext import gettext as _ 
+import conduit
+
+class BaseConfigContainer(gobject.GObject):
+    """
+    Base configuration container class.
+    """
+    
+    __gsignals__ = {     
+        # Changed is called when the user modifies anything in the 
+        # configuration dialog. The bool property is True if the values
+        # are in their initial state
+        'changed': (gobject.SIGNAL_RUN_FIRST, None, [bool]),
+        'apply': (gobject.SIGNAL_RUN_FIRST, None, []),
+        'cancel': (gobject.SIGNAL_RUN_FIRST, None, []),
+    }
+    
+    def __init__(self, dataprovider, configurator):
+        gobject.GObject.__init__(self)
+        self.showing = False
+        self.dataprovider = dataprovider
+        self.configurator = configurator
+        self.name = None
+        self.icon = None
+        
+    def get_name(self):
+        '''
+        Returns the name for this container
+        '''
+        #FIXME: This doesnt work, the dataprovider does not have a name, it's
+        # module wrapper does. This is fixed by a hack inside the Canvas, by
+        # assigning the name there, which is not ideal.
+        return self.name or self.dataprovider and self.dataprovider.get_name()
+    
+    def get_icon(self):
+        '''
+        Returns a path to this configurator icon
+        '''
+        #FIXME: This doesnt work, the dataprovider does not have an icon, it's
+        # module wrapper does. This is fixed by a hack inside the Canvas, by
+        # assigning the icon there, which is not ideal.
+        return self.icon or self.dataprovider and self.dataprovider.get_icon()
+    
+    def get_config_widget(self):
+        '''
+        Returns the root configuration widget
+        '''
+        pass
+        
+    def show(self):
+        '''
+        Show the configuration widget
+        '''
+        self.showing = True
+        
+    def hide(self):
+        '''
+        Hide the configuration widget
+        '''
+        self.showing = False
+        
+    def apply_config(self):
+        '''
+        Save the current configuration state to the dataprovider.
+        '''
+        self.emit('apply')
+    
+    def cancel_config(self):
+        '''
+        Cancel the configuration, reverting any changes the user might have done
+        '''
+        self.emit('cancel')
+    
+    def is_modified(self):
+        return False
+

Modified: trunk/conduit/Module.py
==============================================================================
--- trunk/conduit/Module.py	(original)
+++ trunk/conduit/Module.py	Tue Feb 10 04:03:44 2009
@@ -201,7 +201,7 @@
             log.warn("Error loading the file: %s\n%s" % (filename, "".join(traceback.format_exception(e.exc,e.value,e.tb))))
             self.invalidFiles.append(os.path.basename(filename))
         except Exception, e:
-            log.warn("Error loading the file: %s\n%s" % (filename, "".join(traceback.format_exception(e.exc,e.value,e.tb))))
+            log.error("Error loading the file: %s\n%s" % (filename, traceback.format_exc()))
             self.invalidFiles.append(os.path.basename(filename))
 
     def load_all(self, whitelist, blacklist):

Modified: trunk/conduit/dataproviders/DataProvider.py
==============================================================================
--- trunk/conduit/dataproviders/DataProvider.py	(original)
+++ trunk/conduit/dataproviders/DataProvider.py	Tue Feb 10 04:03:44 2009
@@ -57,6 +57,8 @@
         self.pendingChangeDetected = False
         self.icon = None
         self.status = STATUS_NONE
+        self.config_container = None
+        self.configuration = {}
 
     def __emit_status_changed(self):
         """
@@ -168,13 +170,46 @@
         else:
             return False
 
-    def configure(self, window):
+    def get_config_container(self, configurator):
         """
-        Show a configuration box for configuring the dataprovider instance.
-        @param window: The parent gtk.Window (to show a modal dialog)
+        Retrieves the configuration container
+        @param configurator: The configurator object
         """
+        # If the dataprovider is using the old system, returns None (a message
+        # will be thrown in the Canvas module)
+        if hasattr(self, "configure"):
+            return None
+        if not self.config_container:
+            import conduit.gtkui.ConfigContainer as ConfigContainer
+            self.config_container = ConfigContainer.ConfigContainer(self, configurator)
+            self.config_container.connect('apply', self.config_apply)
+            self.config_container.connect('cancel', self.config_cancel)
+            self.config_setup(self.config_container)
+            #FIXME: This is definetely just for debugging (it prints everything
+            # that is changed in the configuration dialog)
+            def print_item(config, item):
+                log.debug("%s: %s = %s" % (item.title, item.config_name, item.get_value()))
+            self.config_container.connect("item-changed", print_item)
+        return self.config_container
+    
+    def config_setup(self, config_container):
+        '''
+        Configures the configuration controller, to show the controls needed
+        '''
         pass
-
+        
+    def config_apply(self, config_container):
+        '''
+        Called when the configuration was applied
+        '''
+        pass
+        
+    def config_cancel(self, config_container):
+        '''
+        Called when the configuration was cancelled
+        '''
+        pass
+        
     def is_configured(self, isSource, isTwoWay):
         """
         Checks if the dp has been configured or not (and if it needs to be)
@@ -191,6 +226,19 @@
         @returns: Dictionary of strings containing application settings
         @rtype: C{dict(string)}
         """
+        if self.configuration:
+            ret = {}
+            for name, default, setter, getter in self._get_configuration_parameters(self.configuration):
+                if getter:
+                    value = getter()
+                elif hasattr(self, name):
+                    value = getattr(self, name)
+                else:
+                    value = default
+                ret[name] = value
+            return ret
+        else:
+            log.critical("No configuration set (probably old-style module)")
         return {}
 
     def get_configuration_xml(self):
@@ -207,7 +255,7 @@
                     vtype = Settings.TYPE_TO_TYPE_NAME[ type(configDict[config]) ]
                     value = Settings.TYPE_TO_STRING[  type(configDict[config]) ](configDict[config])
                 except KeyError:
-                    log.warn("Cannot convert %s to string. Value of %s not saved" % (type(value), config))
+                    log.warn("Cannot convert %s to string. Value of %s not saved" % (type(configDict[config]), config))
                     vtype = Settings.TYPE_TO_TYPE_NAME[str]
                     value = Settings.TYPE_TO_STRING[str](configDict[config])
                 configxml.setAttribute("type", vtype)
@@ -218,22 +266,90 @@
 
         return doc.toxml()
 
+    def _get_configuration_parameters(self, configuration):
+        '''
+        Normalize the configuration dict to 3 params and yields the name plus
+        the parameters. See update_configuration for more information.
+        '''
+        for name, params in configuration.iteritems():
+            if not isinstance(params, tuple):
+                params = (params,)
+            # Normalize to 3 parameters plus name
+            yield (name,) + params + (None,) * (3 - len(params))
+
+    def _set_configuration_values(self, configuration, config = None):
+        '''
+        Set attributes according to configuration. See update_configuration
+        for more information.
+        '''
+        for name, default, setter, getter in self._get_configuration_parameters(configuration):
+            if hasattr(self, name) and callable(getattr(self, name)):
+                continue            
+            if not config or (name in config):                
+                #if not klass:
+                klass = default.__class__
+                if config:
+                    value = klass(config[name])
+                else:
+                    value = default
+                if setter:
+                    if not hasattr(self, name):
+                        setattr(self, name, value)
+                    setter(value)
+                else:
+                    setattr(self, name, value)        
+
+    def update_configuration(self, **kwargs):
+        '''
+        Set the configuration values to be automatically saved and loaded.
+        
+        The keys to kwargs are the attribute names that will be used. The values
+        to kwargs may be a default value or a tuple, containing the default
+        value, a setter and a getter. Not all values must exist in the 
+        tuple. In the case the tuple's length is smaller then 3, the later 
+        properties are defaulted to None.
+        The default value is immediately applied to the attribute if no other 
+        value is set to that attribute. So calling this function on 
+        initialization already initializes all attributes.
+        The getter and setter are functions to get and set the value. They are
+        very simple, getter should return the value to an attribute, so that
+        value can be saved, while setter, which receives a value trough it's 
+        arguments, should propably set an attribute with that value.
+        Notice that if the setter is used, the value is not automatically set
+        as an attribute, unless that attribute does not exist. This allows a 
+        setter to compare the current attribute value to new value to be set,
+        and only set the new value if it wishes so.
+        
+        Note if the dataprovider overrides set_configuration or 
+        get_configuration without calling the implementations in this class, 
+        then the properties defined here have no affect. Either do not override 
+        those functions, or call them like 
+        DataProviderBase.get_configuration(self).
+        '''
+        #FIXME: Rewrite and clarify the documentation above
+
+        self.configuration.update(kwargs)
+        self._set_configuration_values(kwargs)
+
     def set_configuration(self, config):
         """
         Restores applications settings
         @param config: dictionary of dataprovider settings to restore
         """
-        for c in config:
-            #Perform these checks to stop malformed xml from stomping on
-            #unintended variables or posing a security risk by overwriting methods
-            if getattr(self, c, None) != None and callable(getattr(self, c, None)) == False:
-                setattr(self,c,config[c])
-            else:
-                log.warn("Not restoring %s setting: Exists=%s Callable=%s" % (
-                    c,
-                    getattr(self, c, False),
-                    callable(getattr(self, c, None)))
-                    )
+        if self.configuration:
+            self._set_configuration_values(self.configuration, config)
+        else:
+            for c in config:
+                #Perform these checks to stop malformed xml from stomping on
+                #unintended variables or posing a security risk by overwriting methods
+                if getattr(self, c, None) != None and callable(getattr(self, c, None)) == False:
+                    setattr(self,c,config[c])
+                else:
+                    log.warn("Not restoring %s setting: Exists=%s Callable=%s" % (
+                        c,
+                        getattr(self, c, False),
+                        callable(getattr(self, c, None)))
+                        )
 
     def set_configuration_xml(self, xmltext):
         """

Modified: trunk/conduit/gtkui/Canvas.py
==============================================================================
--- trunk/conduit/gtkui/Canvas.py	(original)
+++ trunk/conduit/gtkui/Canvas.py	Tue Feb 10 04:03:44 2009
@@ -22,6 +22,7 @@
 import conduit.gtkui.Tree
 import conduit.gtkui.Util as GtkUtil
 import conduit.dataproviders.DataProvider as DataProvider
+import conduit.gtkui.WindowConfigurator as WindowConfigurator
 
 log.info("Module Information: %s" % Utils.get_module_information(goocanvas, "pygoocanvas_version"))
 
@@ -169,6 +170,8 @@
         self.parentWindow = parentWindow
         self.msg = msg
 
+        self.configurator = WindowConfigurator.WindowConfigurator(self.parentWindow)
+
         self._setup_popup_menus(dataproviderMenu, conduitMenu)
 
         #set up DND from the treeview
@@ -351,12 +354,50 @@
 
         for i in self._get_child_conduit_canvas_items():
             i.set_width(allocation.width)
+    
+    def _update_configuration(self, selectedItem):
+        if not selectedItem:
+            self.configurator.set_containers([])
+            return 
+        
+        dps = []
+        for dp in selectedItem.model.get_all_dataproviders():
+            if not dp.module: 
+                continue
+            container = dp.module.get_config_container(self.configurator)
+            if container:
+                #FIXME: Should be moved inside the container
+                container.icon = dp.get_icon()
+                container.name = dp.get_name()
+                dps.append(container)
+        if dps:
+            self.configurator.set_containers(dps)
+        
+    def _update_selection(self, selected_conduit, selected_dataprovider):
+        changed_conduit = (selected_conduit != self.selectedConduitItem)
+        if changed_conduit:
+            self._update_configuration(selected_conduit)
+
+        self.selectedDataproviderItem = selected_dataprovider
+        self.selectedConduitItem = selected_conduit
+        
+    def get_selected_conduit(self):
+        if self.selectedConduitItem:
+            return self.selectedConduitItem.model
+        else:
+            return None
+    
+    def get_selected_dataprovider(self):
+        if self.selectedDataproviderItem:
+            return self.selectedDataproviderItem.model        
+        else:
+            return None
 
     def _on_conduit_button_press(self, view, target, event):
         """
         Handle button clicks on conduits
         """
-        self.selectedConduitItem = view
+        self._update_selection(view, None)
 
         #right click
         if event.type == gtk.gdk.BUTTON_PRESS:
@@ -394,8 +435,7 @@
         @param user_data_dataprovider_wrapper: The dpw that was clicked
         @type user_data_dataprovider_wrapper: L{conduit.Module.ModuleWrapper}
         """
-        self.selectedDataproviderItem = view
-        self.selectedConduitItem = view.get_parent()
+        self._update_selection(view.get_parent(), view)
 
         #single right click
         if event.type == gtk.gdk.BUTTON_PRESS:
@@ -553,6 +593,11 @@
         conduitCanvasItem.add_dataprovider_canvas_item(item)
         self._remove_overlap()
         
+        #The embed configurator needs notification when a new dataprovider
+        #is added and the currently selected Conduit is being configured
+        if self.selectedConduitItem == conduitCanvasItem:
+            self._update_configuration(self.selectedConduitItem)
+
         self._show_hint(conduitCanvasItem, item, item)
 
         self._check_if_dataprovider_needs_configuration(
@@ -612,7 +657,12 @@
         dp = dpw.module
         conduitCanvasItem = self.selectedDataproviderItem.get_parent()
 
-        dp.configure(self.parentWindow)
+        if hasattr(dp, "configure"):
+            log.critical("%s using old configuration system" % dpw.get_name())
+            dp.configure(self.parentWindow)
+        else:
+            config_container = dp.get_config_container(self.configurator)
+            self.configurator.run(config_container)
         self._check_if_dataprovider_needs_configuration(
                 conduitCanvasItem.model,
                 dpw)

Added: trunk/conduit/gtkui/ConfigContainer.py
==============================================================================
--- (empty file)
+++ trunk/conduit/gtkui/ConfigContainer.py	Tue Feb 10 04:03:44 2009
@@ -0,0 +1,264 @@
+'''Manages configuration items for a dataprovider.
+
+The ConfigContainer should handle all widgets in a configurator, including
+adding, removing and handling such as applying and cancelling the configuration.
+
+Copyright: Alexandre Rosenfeld, 2009
+License: GPLv2
+'''
+
+import sys
+import sets
+import gobject
+import gtk, gtk.glade
+import logging
+log = logging.getLogger("gtkui.ConfigContainer")
+
+from gettext import gettext as _ 
+import conduit
+import conduit.gtkui.ConfigWidgets as ConfigWidgets
+import conduit.Configurator as Configurator
+
+class Error(Exception):
+  """Base exception for all exceptions raised in this module."""
+  pass
+
+class ConfigContainer(Configurator.BaseConfigContainer):
+    """
+    Gtk implementation to the ConfigController.
+    
+    A dataprovider usually does not need to instantiate this class.
+    """
+    
+    __gsignals__ = {
+        'item-changed' : (gobject.SIGNAL_RUN_FIRST, None, (gobject.TYPE_OBJECT,)),
+    }
+    
+    # List of registered items. Call register_item to change it.
+    __config_classes = {}
+    
+    def __init__(self, dataprovider, configurator):
+        super(ConfigContainer, self).__init__(dataprovider, configurator)
+        
+        # Current section
+        self.section = None
+        self.sections = []
+        self.items = []
+        self.built_items = False
+        self.config_values = None
+        
+        self._reset_modified_items()
+        
+        #the child widget to contain the custom settings
+        self.widgetTable = gtk.Table(rows=1, columns=2)        
+        self.widgetTable.set_row_spacings(6)
+        self.widgetTable.set_col_spacings(12)
+        
+        self.config_widget = self.widgetTable
+        
+        self.firstRow = True
+    
+    @classmethod
+    def register_item(cls, name, item_class):
+        cls.__config_classes[name] = item_class
+        
+    def _reset_modified_items(self, empty = True):
+        '''
+        Reset the list of modified items. If empty is true, just create a new
+        empty list of modified items (so that no item is modified).
+        If empty is False, set the list to None, so that it will be recreated
+        next time get_modified_items is called.
+        '''
+        if empty:
+            self.modified_items = sets.Set()
+        else:
+            self.modified_items = None
+        
+    def _item_changed(self, item, initial_state, value):
+        self.emit("item-changed", item)
+        if self.modified_items is None:
+            self.get_modified_items()
+        if not initial_state:
+            self.modified_items.add(item)
+        elif item in self.modified_items:
+            self.modified_items.remove(item)
+        self.emit('changed', self.is_modified())
+        
+    def _rebuild_widgets(self):
+        '''
+        Rebuild widgets if needed
+        '''
+        self.modified_items = None
+        if self.showing:
+            self._build_widgets()
+    
+    def _build_widgets(self):
+        '''
+        Creates all necessary widgets
+        '''
+        table = self.widgetTable
+        if self.showing:
+            table.foreach(lambda widget: table.remove(widget))
+            table.resize(1, 2)
+            rows = 0
+        elif self.firstRow:
+            self.firstRow = False
+            rows = 0
+        else:
+            rows = table.get_property('n-rows')            
+        for section in sorted(self.sections, key = lambda section: section.order):
+            rows = section.attach(table, rows)
+            if rows != 1:
+                table.set_row_spacing(rows - 1, 16)
+        self.widgetTable.show_all()
+
+    def _reset(self):
+        '''
+        Set all items to their initial state
+        '''
+        for item in self.items:
+            item.reset()
+
+    def add_section(self, title = None, order = 0, use_existing = True, **kwargs):
+        '''
+        Add a section. Returns the Section object.
+        '''
+        if (not title and self.section and not self.section.title) or \
+           (use_existing and self.section and title is self.section.title):
+            return self.section
+        found = False
+        if use_existing and title:  
+            for section in self.sections:
+                if section.title == title:
+                    self.section = section
+                    found = True
+                    break
+        if not use_existing or not found:
+            self.section = ConfigWidgets.Section(self, title, order, **kwargs)
+            self.sections.append(self.section)
+        self._rebuild_widgets()
+        return self.section
+    
+    def add_item(self, title, kind, order = 0, **kwargs):
+        '''
+        Add a configuration widget. Returns the Item object.
+        
+        You can pass properties to the configuration item in kwargs.
+        '''
+        if kind not in self.__config_classes:
+            raise Error("Config widget %s not found" % kind)
+        if not self.section:
+            self.add_section()
+        # If we have a saved configuration in the config dict from the dp, 
+        # use it as initial value.
+        if self.config_values is None:
+            if self.dataprovider:
+                self.config_values = self.dataprovider.get_configuration()
+            else:
+                self.config_values = {}        
+        if 'config_name' in kwargs:
+            if kwargs['config_name'] in self.config_values:
+                kwargs['initial_value'] = self.config_values[kwargs['config_name']]
+        if 'enabled' not in kwargs:
+            kwargs['enabled'] = self.section.enabled            
+        item = self.__config_classes[kind](self, title, order, **kwargs)
+        item.connect("value-changed", self._item_changed)
+        self.items.append(item)
+        self.section.add_item(item)
+        self._rebuild_widgets()
+        return item
+        
+    def get_modified_items(self):
+        '''
+        Return a list of items that has been modified
+        '''
+        if self.modified_items is None:
+            self.modified_items = sets.Set([item for item in self.items if not item.is_initial_value()])
+        return self.modified_items
+                
+    def is_modified(self):
+        '''
+        Returns true if any item has been modified
+        '''
+        return len(self.get_modified_items()) != 0        
+        
+    def get_config_values(self, items):
+        '''
+        Returns a dict suitable to set_configuration in a data-provider.
+        '''
+        values = {}
+        for item in items:
+            config_value = item.get_config_value()
+            if config_value:
+                values.update(config_value)
+        return values
+    
+    def get_config_widget(self):
+        '''
+        Returns the root configuration widget and
+        builds the configuration widgets if needed
+        '''
+        if not self.built_items:
+            self._build_widgets()
+            self.built_items = True
+        return self.config_widget
+    
+    def show(self):
+        '''
+        Show the configuration widget
+        '''
+        super(ConfigContainer, self).show()
+        self.config_widget.show_all()
+        
+    #def set_busy(self, busy):
+    #    if busy:
+    #        self.old_cursor = self.widgetTable.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
+    #    else:
+    #        self.widgetTable.set_cursor(self.old_cursor)
+    #    gtk.gdk.flush()
+        
+    def apply_config(self, items = None, sections = None):
+        '''
+        Save the current configuration state to the dataprovider and to each 
+        item, saving any changes the user might have done.
+        If items is None, all items will be applied. If sections or items are
+        supplied, only these items will be applied.
+        '''
+        super(ConfigContainer, self).apply_config()
+        if not items and not sections:
+            items = self.items
+        elif not items and sections:
+            items = []
+        if sections:
+            for section in sections:
+                items.extend([item for item in section.items if item not in items])
+        config_values = self.get_config_values(items)
+        if config_values:
+            #FIXME: Remove this messsage on production
+            log.debug("Applying configuration: %s" % (config_values))
+            self.dataprovider.set_configuration(config_values)
+        for item in items:
+            item.save_state()
+        if not items and not sections:
+            self._reset_modified_items()
+        else:
+            self._reset_modified_items(False)
+    
+    def cancel_config(self):
+        '''
+        Cancel the configuration, reverting any changes the user might have done
+        '''
+        super(ConfigContainer, self).cancel_config()
+        self._reset()
+        self._reset_modified_items()
+
+
+log.debug(dir(ConfigWidgets))
+# Register all item classes in this module
+for klass_name in dir(ConfigWidgets):
+    klass = getattr(ConfigWidgets, klass_name)
+    #log.debug("Class: %s" % getattr(ConfigWidgets, klass_name))    
+    if isinstance(klass, type) and issubclass(klass, ConfigWidgets.ConfigItem) and klass != ConfigWidgets.ConfigItem:
+        #log.debug("-- ConfigItem: %s", klass)
+        ConfigContainer.register_item(klass.__item_name__, klass)
+

Added: trunk/conduit/gtkui/ConfigWidgets.py
==============================================================================
--- (empty file)
+++ trunk/conduit/gtkui/ConfigWidgets.py	Tue Feb 10 04:03:44 2009
@@ -0,0 +1,615 @@
+"""
+Gtk implementation to the configuration controller
+
+Copyright: Alexandre Rosenfeld, 2009
+License: GPLv2
+"""
+import sys
+import os.path
+import gobject
+import pango
+import gtk, gtk.glade
+import logging
+log = logging.getLogger("gtkui.Config")
+
+from gettext import gettext as _ 
+import conduit
+
+class Section(gobject.GObject):
+    def __init__(self, container, title, order, **kwargs):
+        '''
+        A section containing items and a title
+        '''
+        gobject.GObject.__init__(self)
+        self.container = container
+        self.title = title
+        self.order = order
+        self.items = []
+        self.__enabled = kwargs.get('enabled', True)
+    
+    def add_item(self, item):
+        ''' Adds an item to this section (this does not update the dialog) '''
+        self.items.append(item)
+    
+    def attach(self, table, row):        
+        if self.title:
+            row += 1
+            label = gtk.Label("<b>%s</b>" % (self.title))
+            label.set_alignment(0.0, 0.5)
+            label.set_use_markup(True)
+            table.resize(row, 2)
+            table.attach(label, 0, 2, row - 1, row, xoptions = gtk.FILL | gtk.SHRINK, yoptions = 0 )
+        for item in sorted(self.items, key = lambda item: item.order):
+            row = item.attach(table, row, bool(self.title))
+        return row
+    
+    enabled = property(lambda self: self.__enabled, 
+                       lambda self, v: self.set_enabled(v))        
+    
+    def set_enabled(self, enabled):
+        '''
+        When enabled, widgets inside this section can be clicked or modified
+        '''
+        for item in self.items:
+            item.set_enabled(enabled)
+        self.__enabled = enabled
+            
+    def apply(self):
+        self.container.apply_config(sections = [self])
+        
+class ConfigItem(gobject.GObject):
+    '''
+    A config item is basically a wrapper to a widget.
+    
+    It works by exposing a value that should be translated to the underlying
+    widget. The type of the value depends on the item.
+
+    A few properties are supported to customize their behaviours. They
+    should be passed in ``kwargs``:
+        :config_name: Used to save/load the configuration value from the 
+            dataprovider.
+        :config_type: ``function(value)`` that converts the config value into
+            something a dataprovider will accept. This could be something 
+            like int, str, etc., or a custom function.
+        :initial_value: When the item is created or cancel is called,
+            the item returns to this value. Changes to the current value
+            when apply is called.
+        :initial_value_callback: It's a function that should return a value
+            to initial value, called when the item is created or when cancel
+            is called. It is especially useful for items that keep their state
+            somewhere else.
+        :choices: Valid when the user needs to select a value from a list.
+            It has be a tuple with ``(value, label)``.
+        :needs_label: If True, the widget will have a label with title as
+            the text.
+        :needs_space: If ``needs_label`` is False, but the widget still wants 
+            to be aligned to the right in the window, set this to True.
+        :enabled: If the widget can be edited by the user.
+        :save_callback: A ``function(item, value)`` called when apply is 
+    selected and the value must be saved.
+        
+    Signals emitted:
+        :value-changed: Emitted everytime the value changes.   
+    '''
+    
+    __gsignals__ = {
+        'value-changed' : (gobject.SIGNAL_RUN_FIRST, None, [bool, object]),
+        #'reset' : (gobject.SIGNAL_RUN_LAST, None, []),
+        #'initial-state' : (gobject.SIGNAL_RUN_FIRST, None, []),
+    }
+    
+    def __init__(self, container, title, order, config_name=None, 
+            config_type=None, choices=None, needs_label=True, 
+            needs_space=False, initial_value=None, initial_value_callback=None,
+            save_callback=None, fill=False, enabled=True, **kwargs):
+        '''
+        Create a config item.
+        '''
+        gobject.GObject.__init__(self)
+        self.container = container
+        self.__widget = None
+        self.__label = None
+        self.title = title
+        self.order = order
+        self.__choices = choices #kwargs.get('choices', None)
+        self.needs_label = needs_label #kwargs.get('needs_label', True)
+        self.needs_space = needs_space #kwargs.get('needs_space', False)
+        self.config_name = config_name #kwargs.get('config_name', None)
+        self.config_type = config_type #kwargs.get('config_type', None)
+        self.save_callback = save_callback #kwargs.get('save_callback', None)
+        self.initial_value = initial_value #kwargs.get('initial_value', None)
+        self.initial_value_callback = initial_value_callback #kwargs.get('initial_value_callback', None)
+        
+        self.fill = fill #kwargs.get('fill', False)
+        self.enabled = enabled #kwargs.get('enabled', True)
+
+    def _value_changed(self, *args):
+        '''
+        Called everytime the value changes.
+        '''
+        #if self.is_initial_value():
+        #    self.emit('initial-state')
+        self.emit('value-changed', self.is_initial_value(), self.value)
+        
+    def _build_choices(self):
+        '''
+        Implement this when you need to build the choices of a widget.
+        It's usually called on build_widget. If implemented together with 
+        _clear_choices, there is no need to implement set_choices
+        '''
+        pass
+    
+    def _clear_choices(self):
+        '''
+        Implement this to clear the choices on the widget.
+        '''
+        pass
+    
+    choices = property(lambda self: self.__choices, 
+                       lambda self, v: self.set_choices(v))
+
+    def set_choices(self, choices):
+        '''
+        Set choices and automatically rebuild a widget containing a set of choices.
+        '''
+        value = self.get_value()
+        self.__choices = choices
+        self._clear_choices()
+        self._build_choices()
+        self.set_value(value)
+    
+    def is_initial_value(self):
+        '''
+        Returns True if the current value is the initial value
+        '''
+        return self.initial_value == self.value
+        
+    def set_enabled(self, enabled):
+        '''
+        Set the button sensibility.
+        '''
+        self.enabled = enabled
+        if self.widget:
+            self.widget.set_sensitive(enabled)
+        
+    def reset(self):
+        '''
+        Resets the widget to it's initial state
+        '''
+        if self.__widget:
+            #self.emit('reset')
+            self.widget.set_sensitive(self.enabled)            
+            if self.initial_value_callback:
+                self.initial_value = self.initial_value_callback()
+            self.value = self.initial_value
+    
+    def save_state(self):
+        '''
+        Set the current value as the initial value.
+        '''
+        value = self.get_value()
+        self.initial_value = value
+        if self.save_callback:
+            self.save_callback(self, value)
+        
+    def apply(self):
+        '''
+        Set the current value as the initial value and calls the dataprovider
+        to save the current value
+        '''
+        self.save_state()
+        self.container.apply_config([self])
+
+    label = property(lambda self: self.get_label(), 
+                     lambda self, v: self.set_label(v))        
+                
+    def get_label(self):
+        '''
+        Returns the gtk.Label to this item (if needed)
+        '''
+        if self.needs_label and not self.__label:
+            label_text = self.title
+            if label_text and not label_text.rstrip().endswith(':'):
+                label_text += ':'
+            self.__label = gtk.Label(label_text)
+            self.__label.set_alignment(0.0, 0.5)
+        return self.__label
+    
+    def set_label(self, label):
+        '''
+        Sets the label widget
+        '''
+        self.__label = label        
+
+    widget = property(lambda self: self.get_widget(), 
+                      lambda self, v: self.set_widget(v))        
+    
+    def get_widget(self):
+        '''
+        Return the widget, building it as needed.
+        '''
+        if not self.__widget:
+            self.build_widget()
+            self.reset()
+        return self.__widget
+
+    def set_widget(self, widget):
+        '''
+        Sets the widget
+        '''
+        self.__widget = widget
+
+    def build_widget(self):
+        '''
+        Implement this to build the underlying widget as needed. Has to assign
+        the widget to self.widget (no need to return it)
+        '''
+        pass
+    
+    def get_config_value(self):
+        '''
+        Return a value suitable to a dataprovider
+        '''
+        if not self.config_name:
+            return None
+        value = self.get_value()
+        try:
+            if self.config_type:
+                self.config_type(value)
+        except:
+            log.warning("Value %s could not be translated with %s" % (value, self.config_type))
+            #raise TypeError()
+        else:
+            return {self.config_name: value}
+    
+    def _get_value(self):
+        self.get_value()
+    
+    #Set value as a property and make sure get_value and set_value can be 
+    #overriden in descendants without conflicting with this property
+    value = property(lambda self: self.get_value(),
+                     lambda self, v: self.set_value(v))
+        
+    def get_value(self):
+        pass
+    
+    def set_value(self, value):
+        pass
+
+    def attach(self, table, row, in_section):
+        widget = self.get_widget()
+        label = self.get_label()
+        row += 1
+        table.resize(row, 2)
+        align = gtk.Alignment(0.5, 0.5, 1.0, 1.0)
+        if in_section:
+            align.set_padding(0, 0, 12, 0)
+        #if label:
+        #    label.set_ellipsize(pango.ELLIPSIZE_END)
+        right_align = label or self.needs_space
+        if self.fill:
+            yoptions = gtk.FILL | gtk.EXPAND
+        else:
+            yoptions = 0
+        if right_align:
+            if label:
+                align.add(label)
+                table.attach(align, 0, 1, row - 1, row, xoptions = gtk.SHRINK | gtk.FILL, yoptions = 0)
+            table.attach(widget, 1, 2, row - 1, row, xoptions = gtk.FILL | gtk.EXPAND, yoptions = yoptions)
+        else:
+            align.add(widget)
+            table.attach(align, 0, 2, row - 1, row, xoptions = gtk.FILL | gtk.EXPAND, yoptions = yoptions)    
+        return row
+
+#FIXME: Finish the CustomWidget implementation (if we need the custom widget support)
+#class ConfigCustomWidget(ConfigItem):
+#    def get_value(self):
+#        return self.load_callback(self.widget)
+#    
+#    def set_value(self, value):
+#        self.widget_callback(self.widget)
+
+class ConfigLabel(ConfigItem):
+    __item_name__ = 'label'
+    
+    def __init__(self, *args, **kwargs):
+        ConfigItem.__init__(self, *args, **kwargs)
+        self.xalignment = kwargs.get('xalignment', 0.0)
+        self.yalignment = kwargs.get('yalignment', 0.5)
+        self.use_markup = kwargs.get('use_markup', False)
+    
+    def build_widget(self):
+        self.widget = gtk.Label()
+        self.widget.set_alignment(self.xalignment, self.yalignment)
+        self.widget.set_use_markup(self.use_markup)
+    
+    def get_value(self):
+        return self.widget.get_text()
+    
+    def set_value(self, value):
+        if self.use_markup:
+            self.widget.set_markup(str(value))
+        else:
+            self.widget.set_text(str(value))
+            
+class ConfigButton(ConfigItem):
+    __item_name__ = 'button'
+    
+    def __init__(self, *args, **kwargs):
+        ConfigItem.__init__(self, *args, **kwargs)
+        self.callback_id = None
+        self.callback = None
+        self.needs_space = kwargs.get('needs_space', True)
+        self.needs_label = kwargs.get('needs_label', False)
+        if 'action' in kwargs:
+            self.initial_value = kwargs['action']
+    
+    def build_widget(self):
+        self.widget = gtk.Button(self.title)
+        
+    def set_value(self, value):
+        if self.callback_id:
+            self.widget.disconnect(self.callback_id)
+        self.callback_id = None
+        self.callback = None
+        if not callable(value):
+            return None        
+        self.callback = value
+        if self.callback:
+            self.callback_id = self.widget.connect("clicked", value)            
+        
+    def get_value(self):
+        return self.callback
+    
+class ConfigFileButton(ConfigItem):
+    __item_name__ = 'filebutton'
+    
+    def __init__(self, *args, **kwargs):
+        ConfigItem.__init__(self, *args, **kwargs)        
+        self.directory = kwargs.get('directory', False)
+        self._current_filename = None
+    
+    def _selection_changed(self, filechooser):
+        if self._current_filename != filechooser.get_filename():
+            self._current_filename = filechooser.get_filename()
+            self._value_changed()            
+    
+    def build_widget(self):
+        self.widget = gtk.FileChooserButton(self.title)
+        self.widget.connect("selection-changed", self._selection_changed)
+        if self.directory:
+            self.widget.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
+        else:
+            self.widget.set_action(gtk.FILE_CHOOSER_ACTION_OPEN)
+    
+    def set_value(self, value):
+        self.widget.set_filename(str(value))
+    
+    def get_value(self):
+        return self._current_filename
+
+class ConfigRadio(ConfigItem):
+    __item_name__ = 'radio'
+    
+    def __init__(self, container, title, order, **kwargs):
+        ConfigItem.__init__(self, container, title, order, **kwargs)
+        self.needs_label = title is not None
+        self.buttons = {}
+        self._active_button = None
+    
+    def _button_changed(self, button):
+        if button.get_active():
+            self._active_button = button
+            self._value_changed()
+    
+    def _clear_choices(self):
+        for widget in self.widget.get_children():
+            self.widget.remove(widget)
+        self.buttons = {}        
+  
+    def _build_choices(self):
+        last_button = None
+        for value, text in self.choices:
+            last_button = gtk.RadioButton(last_button, text)
+            last_button.connect("toggled", self._button_changed)
+            last_button.show()
+            self.buttons[value] = last_button
+            self.widget.pack_start(last_button)
+    
+    def build_widget(self):
+        self.widget = gtk.VBox()
+        self._build_choices()
+    
+    def get_value(self):
+        for value, button in self.buttons.iteritems():
+            if button == self._active_button:
+                return value
+        return None
+    
+    def set_value(self, new_value):
+        if new_value in self.buttons:
+            self.buttons[new_value].set_active(True)
+        else:
+            log.warn("Value %s could not be applied to config %s" % (repr(self.title), new_value))
+
+class ConfigSpin(ConfigItem):
+    __item_name__ = 'spin'
+    
+    def __init__(self, *args, **kwargs):
+        ConfigItem.__init__(self, *args, **kwargs)
+        self.maximum = kwargs.get('maximum', sys.maxint)
+        self.minimum = kwargs.get('minimum', 0)
+        self.step = kwargs.get('step', 1)
+    
+    def build_widget(self):
+        self.adjust = gtk.Adjustment(lower = self.minimum, upper = self.maximum, step_incr = self.step)
+        self.widget = gtk.SpinButton(self.adjust)
+        self.widget.connect("value-changed", self._value_changed)
+    
+    def get_value(self):
+        return float(self.widget.get_value())
+    
+    def set_value(self, value):
+        try:
+            value = float(value)
+            self.widget.set_value(value)
+        except:
+            log.warn("Value %s could not be applied to config %s" % (repr(self.title), value))        
+
+class ConfigCombo(ConfigItem):
+    '''
+    A box where the user can select one value from several
+    
+    The combo box takes as choices a list of tuples, with a value and a 
+    description. 
+    The value is what is returned by get_value and what should be set with 
+    set_value. The value can have any type.
+    The description is the text shown to the user.
+    '''
+    __item_name__ = 'combo'
+    
+    def _build_choices(self):
+        if self.choices:
+            for value, text in self.choices:
+                self.widget.append_text(text)
+    
+    def _clear_choices(self):
+        self.widget.get_model().clear()
+    
+    def build_widget(self):
+        self.widget = gtk.combo_box_new_text()
+        self._build_choices()
+        self.widget.connect("changed", self._value_changed)
+
+    def get_value(self):
+        active = self.widget.get_active()
+        if len(self.choices) > active and active >= 0:
+            return self.choices[active][0]
+        else:
+            log.warning("No value selected in combo")
+            return None
+    
+    def set_value(self, new_value):
+        for idx, (value, text) in enumerate(self.choices):
+            if value == new_value:
+                self.widget.set_active(idx)
+                return
+        log.warn("%s not found in %s" % (new_value, self.title))
+        
+class ConfigComboText(ConfigCombo):
+    __item_name__ = 'combotext'
+
+    def build_widget(self):
+        self.widget = gtk.combo_box_entry_new_text()
+        self._build_choices()
+        self.widget.connect("changed", self._value_changed)
+    
+    def get_value(self):
+        return self.widget.child.get_text()
+    
+    def set_value(self, value):
+        self.widget.child.set_text(str(value))
+    
+class ConfigText(ConfigItem):
+    __item_name__ = 'text'
+    
+    def __init__(self, *args, **kwargs):
+        ConfigItem.__init__(self, *args, **kwargs)
+        self.password = kwargs.get('password', False)
+    
+    def build_widget(self):
+        self.widget = gtk.Entry()
+        self.widget.connect("notify::text", self._value_changed)
+        self.widget.set_visibility(not self.password)
+    
+    def get_value(self):
+        return self.widget.get_text()
+    
+    def set_value(self, value):
+        self.widget.set_text(str(value))
+            
+class ConfigList(ConfigItem):
+    __item_name__ = 'list'
+    
+    def __init__(self, *args, **kwargs):
+        ConfigItem.__init__(self, *args, **kwargs)
+        self.needs_label = kwargs.get('needs_label', False)
+        self.initial_value = sorted(self.initial_value)
+        self.fill = kwargs.get('fill', True)
+        self._checked_items = None
+        self.model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_BOOLEAN)
+    
+    def cellcheck_cb(self, cell, path, model):
+        model[path][1] = not cell.get_active()
+        self._checked_items = None        
+        self._value_changed()
+    
+    def _build_choices(self):
+        for value, label in self.choices:
+            self.model.append((label, False))
+
+    def _clear_choices(self):
+        self.model.clear()
+        
+    def build_widget(self):
+        self.vbox = gtk.VBox()
+        self.vbox.set_spacing(4)
+        self.scrolled_window = gtk.ScrolledWindow()        
+        self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        self.vbox.pack_start(self.scrolled_window)
+        self.total_label = gtk.Label()
+        self.total_label.set_alignment(0.0, 0.0)
+        self.vbox.pack_start(self.total_label, False, False)
+        self.list = gtk.TreeView()        
+        self.list.set_property('headers-visible', False)
+        self.list.set_property('rules-hint', True)
+        self.list.set_model(self.model)        
+        check_renderer = gtk.CellRendererToggle()
+        check_renderer.set_property('activatable', True)
+        check_renderer.connect( 'toggled', self.cellcheck_cb, self.model )
+        self.list.append_column(gtk.TreeViewColumn(_("Enabled"), check_renderer, active=1))                    
+        self.list.append_column(gtk.TreeViewColumn(_("Text"), gtk.CellRendererText(), text=0))     
+        self._build_choices()
+        self.scrolled_window.add(self.list)
+        self.widget = self.vbox
+        self.widget.set_size_request(-1, 150)
+    
+    def _update_total(self):
+        self.total_label.set_text("Total: %d" % len(self._checked_items))
+    
+    def get_value(self):
+        if not self._checked_items:
+            self._checked_items = sorted([row[0] for row in self.model if row[1]])
+            self._update_total()
+        return self._checked_items
+    
+    def set_value(self, value):
+        self._checked_items = []
+        try:
+            for row in self.model:
+                row[1] = (row[0] in value)
+                if row[1]:
+                    self._checked_items.append(row[0])
+        except:
+            log.warn("Value %s could not be applied to config %s" % (value, repr(self.title)))
+        self._update_total()
+
+class ConfigCheckBox(ConfigItem):
+    __item_name__ = 'check'
+    
+    def __init__(self, *args, **kwargs):
+        ConfigItem.__init__(self, *args, **kwargs)
+        self.needs_label = False
+        
+    def build_widget(self):
+        self.widget = gtk.CheckButton()
+        self.widget.set_label(self.title)
+        self.widget.connect("toggled", self._value_changed)
+    
+    def get_value(self):
+        return self.widget.get_active()
+    
+    def set_value(self, value):
+        self.widget.set_active(bool(value))
+
+

Added: trunk/conduit/gtkui/EmbedConfigurator.py
==============================================================================
--- (empty file)
+++ trunk/conduit/gtkui/EmbedConfigurator.py	Tue Feb 10 04:03:44 2009
@@ -0,0 +1,111 @@
+import os.path
+import gobject
+import gtk, gtk.glade
+import logging
+log = logging.getLogger("gtkui.Config")
+
+from gettext import gettext as _ 
+import conduit
+
+class EmbedConfigurator(gobject.GObject):
+    '''
+    A embed configurator.
+    '''
+
+    def __init__(self, window):
+        gobject.GObject.__init__(self)
+        
+        self.window = window
+        
+        self.showing = False
+        self.configs = []
+        
+        self.vbox = gtk.VBox(spacing = 8)
+        self.vbox.set_border_width(6)
+        self.vbox.hide()
+        
+        self.widget = self.vbox
+        
+        #self.widget = gtk.ScrolledWindow()
+        #self.widget.add_with_viewport(self.vbox)
+        #self.widget.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        
+        self.buttons = gtk.HButtonBox()
+        self.buttons.set_layout(gtk.BUTTONBOX_END)
+        #self.widget.pack_end(self.buttons)
+        #self.widget.pack_end(gtk.HSeparator())
+        
+        self.button_apply = gtk.Button(stock = gtk.STOCK_APPLY)
+        self.button_apply.connect("clicked", self._on_apply_clicked)
+        self.button_cancel = gtk.Button(stock = gtk.STOCK_CANCEL)
+        self.button_cancel.connect("clicked", self._on_cancel_clicked)
+        
+        self.buttons.pack_start(self.button_cancel)
+        self.buttons.pack_start(self.button_apply)
+
+    def _config_changed_cb(self, config, item):
+        self._update_modified()
+    
+    def _update_modified(self):
+        modified = True
+        for config in self.configs:
+            modified = config.is_modified()
+            if modified:
+                break
+        self.button_cancel.set_sensitive(modified)
+        self.button_apply.set_sensitive(modified)
+        
+    def _on_apply_clicked(self, button):
+        for config in self.configs:
+            if config:
+                config.apply_config()
+        self._update_modified()
+        
+    def _on_cancel_clicked(self, button):
+        for config in self.configs:
+            if config:
+                config.cancel_config()
+        self._update_modified()
+        
+    def has_configure_menu(self):
+        return False
+        
+    def run(self):
+        return None
+        
+    def set_controllers(self, config_controllers):
+        self.vbox.hide()
+        self.vbox.foreach(lambda widget: self.vbox.remove(widget))
+        for config in self.configs:
+            if config:
+                config.cancel_config()
+                #self.widget.remove(config.get_config_widget())
+                config.hide()        
+        self.vbox.pack_end(self.buttons, False, False)        
+        #self.widget.show()
+        self.configs = config_controllers
+        for config in self.configs:
+            if config:
+                vbox = gtk.HBox(spacing = 8)
+                name, icon = config.get_name(), config.get_icon()
+                if icon:
+                    vbox.pack_start(gtk.image_new_from_pixbuf(icon), False, False)
+                if name:
+                    lbl = gtk.Label(name)                
+                    vbox.pack_start(lbl, False, False)
+                if name or icon:
+                    self.vbox.pack_start(vbox, False, False)
+                self.vbox.pack_start(config.get_config_widget(), False, False)
+                self.vbox.pack_start(gtk.HSeparator(), False, False)
+                config.show()
+                config.connect("changed", self._config_changed_cb)
+        self.vbox.show_all()
+        self._update_modified()
+        
+    def get_window(self):
+        return self.window
+        
+    def get_widget(self):
+        #for config in self.configs:
+        #    config.show()
+        return self.widget        

Added: trunk/conduit/gtkui/WindowConfigurator.py
==============================================================================
--- (empty file)
+++ trunk/conduit/gtkui/WindowConfigurator.py	Tue Feb 10 04:03:44 2009
@@ -0,0 +1,207 @@
+import os.path
+import gobject
+import gtk, gtk.glade
+import logging
+log = logging.getLogger("gtkui.WindowConfigurator")
+
+from gettext import gettext as _ 
+import conduit
+import conduit.gtkui.ConfigContainer as ConfigContainer
+
+class WindowConfigurator(gobject.GObject):
+    """
+    A window configurator to embed a configuration widget.
+    """
+    
+    CONFIG_WINDOW_TITLE_TEXT = _("Configure")
+    
+    #Show multiple containers or only shows the currently selected dataprovider
+    #This should be set to False until all dataproviders use the new system
+    MULTIPLE_VIEW = False
+    #Use a notebook instead of stacking the containers. Notebooks are very good
+    #for large configuration windows, that end up being larger then the screen
+    #height (happens frequently).
+    #Creates a border when not used with MULTIPLE_VIEW, so it's disabled while 
+    #that is disabled (and should be enabled otherwise)
+    NOTEBOOK = MULTIPLE_VIEW
+    
+    def __init__(self, window):
+        """
+        @param window: Parent window (this dialog is modal)
+        @type window: C{gtk.Window}
+        """
+        gobject.GObject.__init__(self)
+        
+        self.showing = False
+        self.built_configs = False
+        
+        #self.dialogParent = window
+        #The dialog is loaded from a glade file
+        #gladeFile = os.path.join(conduit.SHARED_DATA_DIR, "conduit.glade")
+        #widgets = gtk.glade.XML(gladeFile, "DataProviderConfigDialog")
+
+        self.dialog = gtk.Dialog(self.CONFIG_WINDOW_TITLE_TEXT,
+                          window,
+                          gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+                          (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+                           gtk.STOCK_OK, gtk.RESPONSE_OK,
+                           gtk.STOCK_HELP, gtk.RESPONSE_HELP))
+        #TODO: Unless we actually have a help to show, make the help button 
+        #disabled.
+        #BTW, modules should be able to define their own help
+        self.dialog.set_has_separator(False)
+        self.dialog.set_response_sensitive(gtk.RESPONSE_HELP, False)
+        self.dialog.set_default_size(300, -1)
+        #self.dialog = widgets.get_widget("DataProviderConfigDialog")
+        #self.dialog.set_transient_for(self.dialogParent)
+        #self.dialog.set_title(WindowConfigurator.CONFIG_WINDOW_TITLE_TEXT)
+        #self.dialog.set_border_width(6)
+        
+        #self.configVBox = widgets.get_widget("configVBox")
+        self.dialog_box = self.dialog.vbox
+        
+        self.dialog_box.pack_start(self._make_config_widget())        
+        #self.configVBox.pack_start(self.conduit_config.get_config_widget())
+        self.dialog_box.show_all()        
+        
+        self.container_widgets = {}
+        
+    def _make_config_widget(self):
+        if self.NOTEBOOK:
+            self.tabs = {}
+            self.notebook = gtk.Notebook()
+            self.notebook.set_border_width(8)
+            self.notebook.set_show_tabs(self.MULTIPLE_VIEW)
+            return self.notebook
+        else:
+            self.containers_box = gtk.VBox(spacing = 8)
+            self.containers_box.set_border_width(6)        
+            return self.containers_box
+        
+    def set_containers(self, containers):
+        self._clear_containers()
+        self.built_containers = False
+        
+        self.containers = containers
+        #self.configVBox.hide()
+        #import threading
+        #log.debug("THREADS: %s" % threading.activeCount())
+        #if self.notebook:
+        #    self.configVBox.remove(self.notebook)
+        #self.notebook = gtk.Notebook()
+        #self.configVBox.pack_start(self.notebook)
+        
+        #self.notebook.show_all()
+        #self.configVBox.pack_start(self.config.get_config_widget())
+        
+    def _clear_containers(self):
+        if self.NOTEBOOK:
+            #self.notebook.foreach(lambda widget: self.notebook.remove(widget))
+            while self.notebook.get_n_pages() > 0:
+                self.notebook.remove_page(0)
+            self.tabs = {}
+        else:
+            #self.containers_box.foreach(lambda widget: self.containers_box.remove(widget))
+            for container, widget in self.container_widgets.iteritems():
+                self.containers_box.remove(widget)
+                container_widget = container.get_config_widget()
+                container_widget.get_parent().remove(container_widget)
+        self.container_widgets = {}
+        
+    def _add_container(self, container, container_widget):
+        if self.NOTEBOOK:
+            hbox = gtk.HBox(spacing = 8)       
+            hbox.pack_start(gtk.image_new_from_pixbuf(container.get_icon()), False, False)
+            lbl = gtk.Label(container.get_name())
+            hbox.pack_start(lbl, False, False, 4)
+            hbox.show_all()
+            container_widget.set_border_width(8)
+            widget = container_widget
+            tab_index = self.notebook.append_page(container_widget, hbox)
+            self.tabs[container] = tab_index
+            self.notebook.show_all()
+        else:
+            container_box = gtk.VBox(spacing = 8)
+            widget = container_box
+            if self.MULTIPLE_VIEW:
+                title_box = gtk.HBox(spacing = 8)                        
+                title_box.pack_start(gtk.image_new_from_pixbuf(container.get_icon()), False, False)
+                lbl = gtk.Label(container.get_name())
+                title_box.pack_start(lbl, False, False, 4)
+                title_box.show_all()
+                container_box.pack_start(title_box, False, False)
+            #config_widget = config.get_config_widget()
+            container_box.pack_start(container_widget, True, True)
+            container_box.pack_start(gtk.HSeparator(), False, False)
+            self.containers_box.pack_start(container_box)
+            self.containers_box.show_all()
+        self.container_widgets[container] = widget
+    
+    def build_containers(self):
+        if self.built_containers:
+            return
+        for index, container in enumerate(self.containers):
+            if not container:
+                continue
+            container_widget = container.get_config_widget()
+            #FIXME: The situation below is never reached.
+            #The only way to reach it is if the UI allowed a dataprovider to be
+            #configured even if it shouldnt. Even then, the canvas filters when
+            #there is no configuration container. 
+            if not container_widget:
+                container_widget = gtk.Label("No configuration needed for this dataprovider")
+            self._add_container(container, container_widget)
+        self.built_containers = True 
+    
+    def has_configure_menu(self):
+        return True
+        
+    def get_widget(self):
+        return None
+        
+    def get_window(self):
+        return self.dialog
+        
+    def run(self, config_container):
+        """
+        Runs the dialog, return True if OK is clicked, False otherwise
+        
+        @param config_container: container that should be focused, such as the 
+            currently selected dataprovider (in a notebook, the currently 
+            selected page belongs to this container)
+        """
+        self.build_containers()
+        self.showing = True
+        if config_container and not self.MULTIPLE_VIEW:
+            assert config_container in self.container_widgets
+            for container, container_widget in self.container_widgets.iteritems():
+                if container != config_container:
+                    #log.debug("Hiding: %s" % container)
+                    container_widget.hide()       
+                    
+            #log.debug("Showing: %s" % config_container)         
+            config_container.show()
+            self.container_widgets[config_container].show()
+            containers = [config_container]
+        else:
+            for container, container_widget in self.container_widgets.iteritems():
+                container_widget.show()
+                container.show()
+            containers = self.container_widgets.keys()
+        if self.NOTEBOOK and config_container:
+            self.notebook.set_current_page(self.tabs[config_container])
+        #self.dialog.show()
+        self.dialog.reshow_with_initial_size()
+        resp = self.dialog.run()
+        for container in containers:
+            container.hide()
+        self.dialog.hide()
+        #self.dialog = None
+        self.showing = False
+        if resp == gtk.RESPONSE_OK:
+            for container in containers:
+                container.apply_config()
+        else:
+            for container in containers:
+                container.cancel_config()
+        return (resp == gtk.RESPONSE_OK)



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]