Update selector widgets on dialog activation.
[pspp] / src / ui / gui / psppire-selector.c
index 753a9dc8c5481fe3c963c570eaff2e9291bb129e..32e6ce126ca6ce621906cb042001b55c03b2bda8 100644 (file)
@@ -1,10 +1,9 @@
-/*
-   PSPPIRE --- A Graphical User Interface for PSPP
-   Copyright (C) 2007  Free Software Foundation
+/* PSPPIRE - a graphical user interface for PSPP.
+   Copyright (C) 2007, 2009, 2010, 2012, 2015 Free Software Foundation
 
-   This program is free software; you can redistribute it and/or modify
+   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
+   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,
    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. */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 /*
   This module provides a widget, PsppireSelector derived from
   GtkButton.
 
-  It contains a GtkArrow, and is used for selecting objects from a
+  It contains a GtkImage (to indicate the arrow), and is used for selecting objects from a
   GtkTreeView and putting them into a destination widget (often
   another GtkTreeView).  Typically this is used in psppire for
   selecting variables, thus:
 
 #include <config.h>
 
-#include <gtk/gtksignal.h>
-#include <gtk/gtkbutton.h>
+#include "psppire-dictview.h"
+#include "psppire-dict.h"
+#include "psppire-select-dest.h"
+#include "psppire-means-layer.h"
 
-#include "psppire-selector.h"
+#include <gtk/gtk.h>
 
-#include <gtk/gtktreeview.h>
-#include <gtk/gtktreeselection.h>
-#include <gtk/gtkwidget.h>
+#include "psppire-selector.h"
 
-static void psppire_selector_base_finalize (PsppireSelectorClass *, gpointer);
-static void psppire_selector_base_init     (PsppireSelectorClass *class);
 static void psppire_selector_class_init    (PsppireSelectorClass *class);
 static void psppire_selector_init          (PsppireSelector      *selector);
 
+
+static void set_direction (PsppireSelector *, enum psppire_selector_dir);
+
+
 enum  {SELECTED,    /* Emitted when an item is inserted into dest */
        DE_SELECTED, /* Emitted when an item is removed from dest */
        n_SIGNALS};
 
 static guint signals [n_SIGNALS];
 
+/* Callback for when an item disappears from the source list.
+   By implication, this means that the item has been inserted into the
+   destination.
+ */
+static void
+on_row_deleted (PsppireSelector *selector)
+{
+  g_signal_emit (selector, signals [SELECTED], 0);
+}
+
+/* Callback for when a new item appears in the source list.
+   By implication, this means that an item has been deleted from the
+   destination.
+ */
+static void
+on_row_inserted (PsppireSelector *selector)
+{
+  g_signal_emit (selector, signals [DE_SELECTED], 0);
+}
+
 
 GType
 psppire_selector_get_type (void)
@@ -91,8 +111,8 @@ psppire_selector_get_type (void)
       static const GTypeInfo psppire_selector_info =
       {
        sizeof (PsppireSelectorClass),
-       (GBaseInitFunc) psppire_selector_base_init,
-        (GBaseFinalizeFunc) psppire_selector_base_finalize,
+       (GBaseInitFunc) NULL, 
+        (GBaseFinalizeFunc) NULL,
        (GClassInitFunc)psppire_selector_class_init,
        (GClassFinalizeFunc) NULL,
        NULL,
@@ -109,17 +129,211 @@ psppire_selector_get_type (void)
   return psppire_selector_type;
 }
 
+static GObjectClass * parent_class = NULL;
+
+
+
+#define SELECTOR_DEBUGGING 0
 
 static void
-psppire_selector_finalize (GObject *object)
+dump_hash_entry (gpointer key, gpointer value, gpointer obj)
 {
+  GList *item = NULL;
+  g_print ("Source %p; ", key);
+
+  for (item = g_list_first (value);
+       item != NULL;
+       item = g_list_next (item))
+    {
+      g_print ("%p(%p) ", item->data, item);
+    }
+  g_print ("\n");
+}
+
+/* This function is for debugging only */
+void 
+psppire_selector_show_map (PsppireSelector *obj)
+{
+  PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
+
+  g_print ("%s %p\n", __FUNCTION__, obj);
+  g_hash_table_foreach (class->source_hash, dump_hash_entry, obj);
 }
 
 
 
+static void
+psppire_selector_dispose (GObject *obj)
+{
+  PsppireSelector *sel = PSPPIRE_SELECTOR (obj);
+  PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
+  GList *list;
+
+  if (sel->dispose_has_run)
+    return;
+
+  /* Make sure dispose does not run twice. */
+  sel->dispose_has_run = TRUE;
+
+  /* Remove ourself from the source map. If we are the only entry for this source
+     widget, that will result in an empty list for the source.  In that case, we
+     remove the entire entry too. */
+  if ((list = g_hash_table_lookup (class->source_hash, sel->source)))
+    {
+      GList *newlist = g_list_remove_link (list, sel->source_litem);
+      g_list_free (sel->source_litem);
+      if (newlist == NULL)
+       g_hash_table_remove (class->source_hash, sel->source);
+      else
+       g_hash_table_replace (class->source_hash, sel->source, newlist);
+
+      sel->source_litem = NULL;
+    }
+  
+  g_object_unref (sel->dest);
+  g_object_unref (sel->source);
+
+  /* Chain up to the parent class */
+  G_OBJECT_CLASS (parent_class)->dispose (obj);
+}
+
+
+/* Properties */
+enum
+{
+  PROP_0,
+  PROP_ORIENTATION,
+  PROP_PRIMARY,
+  PROP_SOURCE_WIDGET,
+  PROP_DEST_WIDGET
+};
+
+
+static void on_click (GtkButton *b);
+static void on_realize (GtkWidget *selector);
+
+
+static void
+psppire_selector_set_property (GObject         *object,
+                              guint            prop_id,
+                              const GValue    *value,
+                              GParamSpec      *pspec)
+{
+  PsppireSelector *selector = PSPPIRE_SELECTOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_ORIENTATION:
+      selector->orientation = g_value_get_enum (value);
+      set_direction (selector, selector->direction);
+      break;
+    case PROP_PRIMARY:
+      selector->primary_requested = TRUE;
+      psppire_selector_update_subjects (selector);
+      break;
+    case PROP_SOURCE_WIDGET:
+      selector->source = g_value_dup_object (value);
+      psppire_selector_update_subjects (selector);
+      break;
+    case PROP_DEST_WIDGET:
+      selector->dest = g_value_dup_object (value);
+      psppire_selector_update_subjects (selector);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    };
+}
+
+
+static void
+psppire_selector_get_property (GObject         *object,
+                              guint            prop_id,
+                              GValue          *value,
+                              GParamSpec      *pspec)
+{
+  PsppireSelector *selector = PSPPIRE_SELECTOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_ORIENTATION:
+      g_value_set_enum (value, selector->orientation);
+      break;
+    case PROP_SOURCE_WIDGET:
+      g_value_take_object (value, selector->source);
+      break;
+    case PROP_DEST_WIDGET:
+      g_value_take_object (value, selector->dest);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    };
+}
+
 static void
 psppire_selector_class_init (PsppireSelectorClass *class)
 {
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  GtkButtonClass *button_class = GTK_BUTTON_CLASS (class);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+  GParamSpec *orientation_spec =
+    g_param_spec_enum ("orientation",
+                      "Orientation",
+                      "Where the selector is relative to its subjects",
+                      PSPPIRE_TYPE_SELECTOR_ORIENTATION,
+                      PSPPIRE_SELECT_SOURCE_BEFORE_DEST /* default value */,
+                      G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
+
+
+ /* Meaningfull only if more than one selector shares this selectors source */
+  GParamSpec *primary_spec =
+    g_param_spec_boolean ("primary",
+                         "Primary",
+                         "Whether this selector should be the primary selector for the source",
+                         FALSE,
+                         G_PARAM_READWRITE);
+
+  GParamSpec *source_widget_spec = 
+    g_param_spec_object ("source-widget",
+                        "Source Widget",
+                        "The widget to be used as the source for this selector",
+                        GTK_TYPE_WIDGET,
+                        G_PARAM_READWRITE);
+
+  GParamSpec *dest_widget_spec = 
+    g_param_spec_object ("dest-widget",
+                        "Destination Widget",
+                        "The widget to be used as the destination for this selector",
+                        GTK_TYPE_WIDGET,
+                        G_PARAM_READWRITE);
+
+
+  button_class->clicked = on_click;
+  widget_class->realize = on_realize;
+
+
+  object_class->set_property = psppire_selector_set_property;
+  object_class->get_property = psppire_selector_get_property;
+
+  g_object_class_install_property (object_class,
+                                   PROP_ORIENTATION,
+                                   orientation_spec);
+
+  g_object_class_install_property (object_class,
+                                   PROP_PRIMARY,
+                                   primary_spec);
+
+  g_object_class_install_property (object_class,
+                                   PROP_SOURCE_WIDGET,
+                                   source_widget_spec);
+
+  g_object_class_install_property (object_class,
+                                   PROP_DEST_WIDGET,
+                                   dest_widget_spec);
+
+  parent_class = g_type_class_peek_parent (class);
+
   signals [SELECTED] =
     g_signal_new ("selected",
                  G_TYPE_FROM_CLASS (class),
@@ -140,43 +354,105 @@ psppire_selector_class_init (PsppireSelectorClass *class)
                  G_TYPE_NONE,
                  0);
 
+  object_class->dispose = psppire_selector_dispose;
 
+  class->source_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
+  class->default_selection_funcs = g_hash_table_new (g_direct_hash, g_direct_equal);
 }
 
 
+/* Callback for when the source treeview is activated (double clicked) */
 static void
-psppire_selector_base_init (PsppireSelectorClass *class)
+on_row_activate (GtkTreeView       *tree_view,
+                GtkTreePath       *path,
+                GtkTreeViewColumn *column,
+                gpointer           data)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  on_click (GTK_BUTTON (data));
+}
 
-  object_class->finalize = psppire_selector_finalize;
+/* Callback for when the source selection changes */
+static void
+on_source_select (GtkTreeSelection *treeselection, gpointer data)
+{
+  PsppireSelector *selector = data;
 
-  class->source_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
-}
+  set_direction (selector, PSPPIRE_SELECTOR_SOURCE_TO_DEST);
 
+  if ( selector->allow_selection )
+    {
+      gtk_widget_set_sensitive (GTK_WIDGET (selector),
+                               selector->allow_selection (selector->source, selector->dest));
+    }
+  else if ( GTK_IS_ENTRY (selector->dest) )
+    {
+      gtk_widget_set_sensitive (GTK_WIDGET (selector),
+                               gtk_tree_selection_count_selected_rows
+                               (treeselection) <= 1 );
+    }
+}
 
 
 static void
-psppire_selector_base_finalize(PsppireSelectorClass *class,
-                               gpointer class_data)
+on_realize (GtkWidget *w)
 {
-  g_hash_table_destroy (class->source_hash);
+  PsppireSelector *selector = PSPPIRE_SELECTOR (w);
+  PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
+  GtkTreeSelection* selection ;
+
+  GList *list = g_hash_table_lookup (class->source_hash, selector->source);
+
+  if (GTK_WIDGET_CLASS (parent_class)->realize)
+    GTK_WIDGET_CLASS (parent_class)->realize (w);
+
+  if ( NULL == list)
+    return;
+
+  if ( g_list_first (list)->data == selector)
+    {
+      if ( selector->row_activate_id )
+       g_signal_handler_disconnect (selector->source, selector->row_activate_id);
+
+      selector->row_activate_id =  
+       g_signal_connect (selector->source, "row-activated", G_CALLBACK (on_row_activate), selector);
+    }
+
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector->source));
+
+  if ( selector->source_select_id )
+    g_signal_handler_disconnect (selection, selector->source_select_id);
+
+  selector->source_select_id = 
+    g_signal_connect (selection, "changed", G_CALLBACK (on_source_select), selector);
 }
 
 
 static void
 psppire_selector_init (PsppireSelector *selector)
 {
-  selector->arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
-  selector->filtered_source = NULL;
+  selector->primary_requested = FALSE;
+  selector->select_user_data = NULL;
+  selector->select_items = NULL;
+  selector->allow_selection = NULL;
+  selector->filter = NULL;
+
+  selector->arrow = gtk_image_new_from_icon_name ("pan-start-symbolic", GTK_ICON_SIZE_BUTTON);
 
   gtk_container_add (GTK_CONTAINER (selector), selector->arrow);
 
   gtk_widget_show (selector->arrow);
 
-  /* FIXME: This shouldn't be necessary, but Glade interfaces seem to
-     need it. */
-  gtk_widget_show (GTK_WIDGET (selector));
+  selector->selecting = FALSE;
+
+  selector->source = NULL;
+  selector->dest = NULL;
+  selector->dispose_has_run = FALSE;
+
+
+  selector->row_activate_id = 0;
+  selector->source_select_id  = 0;
+
+  selector->source_litem = NULL;
 }
 
 
@@ -195,24 +471,46 @@ set_direction (PsppireSelector *selector, enum psppire_selector_dir d)
   /* FIXME: Need to reverse the arrow direction if an RTL locale is in
      effect */
   if ( d == PSPPIRE_SELECTOR_SOURCE_TO_DEST )
-    g_object_set (selector->arrow, "arrow-type", GTK_ARROW_RIGHT, NULL);
+    {
+      switch (selector->orientation)
+       {
+       case   PSPPIRE_SELECT_SOURCE_BEFORE_DEST:
+         g_object_set (selector->arrow, "icon-name", "pan-end-symbolic", NULL);
+         break;
+       case   PSPPIRE_SELECT_SOURCE_AFTER_DEST:
+         g_object_set (selector->arrow, "icon-name", "pan-start-symbolic", NULL);
+         break;
+       case   PSPPIRE_SELECT_SOURCE_ABOVE_DEST:
+         g_object_set (selector->arrow, "icon-name", "pan-down-symbolic", NULL);
+         break;
+       case   PSPPIRE_SELECT_SOURCE_BELOW_DEST:
+         g_object_set (selector->arrow, "icon-name", "pan-up-symbolic", NULL);
+         break;
+       default:
+         g_assert_not_reached ();
+         break;
+       };
+    }
   else
-    g_object_set (selector->arrow, "arrow-type", GTK_ARROW_LEFT, NULL);
-}
-
-/* Callback for when the source selection changes */
-static void
-on_source_select (GtkTreeSelection *treeselection, gpointer data)
-{
-  PsppireSelector *selector = data;
-
-  set_direction (selector, PSPPIRE_SELECTOR_SOURCE_TO_DEST);
-
-  if ( GTK_IS_ENTRY (selector->dest) )
     {
-      gtk_widget_set_sensitive (GTK_WIDGET (selector),
-                               gtk_tree_selection_count_selected_rows
-                               (treeselection) <= 1 );
+      switch (selector->orientation)
+       {
+       case   PSPPIRE_SELECT_SOURCE_BEFORE_DEST:
+         g_object_set (selector->arrow, "icon-name", "pan-start-symbolic", NULL);
+         break;
+       case   PSPPIRE_SELECT_SOURCE_AFTER_DEST:
+         g_object_set (selector->arrow, "icon-name", "pan-end-symbolic", NULL);
+         break;
+       case   PSPPIRE_SELECT_SOURCE_ABOVE_DEST:
+         g_object_set (selector->arrow, "icon-name", "pan-up-symbolic", NULL);
+         break;
+       case   PSPPIRE_SELECT_SOURCE_BELOW_DEST:
+         g_object_set (selector->arrow, "icon-name", "pan-down-symbolic", NULL);
+         break;
+       default:
+         g_assert_not_reached ();
+         break;
+       };
     }
 }
 
@@ -222,9 +520,12 @@ on_dest_treeview_select (GtkTreeSelection *treeselection, gpointer data)
 {
   PsppireSelector *selector = data;
 
+  gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (selector->source)));
+
   set_direction (selector, PSPPIRE_SELECTOR_DEST_TO_SOURCE);
 }
 
+
 /* Callback for source deselection, when the dest is GtkEntry */
 static void
 de_select_selection_entry (PsppireSelector *selector)
@@ -232,23 +533,47 @@ de_select_selection_entry (PsppireSelector *selector)
   gtk_entry_set_text (GTK_ENTRY (selector->dest), "");
 }
 
+
+static void  de_select_tree_model (GtkTreeSelection *selection, GtkTreeModel *model);
+
+/* Callback for source deselection, when the dest is PsppireMeansLayer */
+static void
+de_select_selection_means_layer (PsppireSelector *selector)
+{
+  PsppireMeansLayer *ml = PSPPIRE_MEANS_LAYER (selector->dest);
+  GtkTreeView *tv = GTK_TREE_VIEW (ml->var_view);
+  GtkTreeSelection *selection = gtk_tree_view_get_selection (tv);
+
+  GtkTreeModel *model = psppire_means_layer_get_model (ml);
+
+  g_return_if_fail (selector->select_items);
+
+  de_select_tree_model (selection, model);
+}
+
 /* Callback for source deselection, when the dest is GtkTreeView */
 static void
 de_select_selection_tree_view (PsppireSelector *selector)
 {
-  GList *item;
-
   GtkTreeSelection* selection =
     gtk_tree_view_get_selection ( GTK_TREE_VIEW (selector->dest));
 
   GtkTreeModel *model =
     gtk_tree_view_get_model (GTK_TREE_VIEW (selector->dest));
 
+  g_return_if_fail (selector->select_items);
+
+  de_select_tree_model (selection, model);
+}
+
+static void 
+de_select_tree_model (GtkTreeSelection *selection, GtkTreeModel *model)
+{
+  GList *item;
+
   GList *selected_rows =
     gtk_tree_selection_get_selected_rows (selection, NULL);
 
-  g_return_if_fail (selector->select_items);
-
   /* Convert paths to RowRefs */
   for (item = g_list_first (selected_rows);
        item != NULL;
@@ -286,20 +611,40 @@ de_select_selection_tree_view (PsppireSelector *selector)
 }
 
 
+/* Callback which causes the filter to be refiltered.
+   Called when the DEST GtkEntry is activated (Enter is pressed), or when it
+   looses focus.
+*/
+static gboolean
+refilter (PsppireSelector *selector)
+{
+  GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->source));
+  if (GTK_IS_TREE_MODEL_FILTER (model))
+    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
+  return FALSE;
+}
+
 /* Removes something from the DEST widget */
 static void
 de_select_selection (PsppireSelector *selector)
 {
+  selector->selecting = TRUE;
+
   if ( GTK_IS_TREE_VIEW (selector->dest ) )
     de_select_selection_tree_view (selector);
 
   else if ( GTK_IS_ENTRY (selector->dest))
     de_select_selection_entry (selector);
 
+  else if ( PSPPIRE_IS_MEANS_LAYER (selector->dest))
+    de_select_selection_means_layer (selector);
+
   else
     g_assert_not_reached ();
 
-  gtk_tree_model_filter_refilter (selector->filtered_source);
+  selector->selecting = FALSE;
+
+  refilter (selector);
 
   g_signal_emit (selector, signals [DE_SELECTED], 0);
 }
@@ -313,14 +658,19 @@ select_selection (PsppireSelector *selector)
   GtkTreeSelection* selection =
     gtk_tree_view_get_selection ( GTK_TREE_VIEW (selector->source));
 
-  GList *selected_rows =
-    gtk_tree_selection_get_selected_rows (selection, NULL);
+  GList *selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
 
-  GtkTreeModel *childmodel  = gtk_tree_model_filter_get_model
-    (selector->filtered_source);
+  GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->source));
+
+  GtkTreeModel *childmodel = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model));
 
   g_return_if_fail (selector->select_items);
 
+  if (selector->allow_selection && 
+      ! selector->allow_selection (selector->source, selector->dest))
+    return;
+
+  selector->selecting = TRUE;
 
   for (item = g_list_first (selected_rows);
        item != NULL;
@@ -330,43 +680,37 @@ select_selection (PsppireSelector *selector)
       GtkTreeIter iter;
       GtkTreePath *path  = item->data;
 
-      gtk_tree_model_get_iter (GTK_TREE_MODEL (selector->filtered_source),
-                              &iter, path);
+      g_return_if_fail (model);
 
-      gtk_tree_model_filter_convert_iter_to_child_iter
-       (selector->filtered_source,
-        &child_iter,
-        &iter);
+      gtk_tree_model_get_iter (model, &iter, path);
 
+      gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model),
+                                                       &child_iter, &iter);
       selector->select_items (child_iter,
                              selector->dest,
-                             childmodel);
+                             childmodel,
+                             selector->select_user_data
+                             );
     }
 
   g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
   g_list_free (selected_rows);
 
-  gtk_tree_model_filter_refilter (selector->filtered_source);
+  refilter (selector);
 
   g_signal_emit (selector, signals [SELECTED], 0);
-}
-
-/* Callback fro then the source treeview is activated (double clicked) */
-static void
-on_row_activate (GtkTreeView       *tree_view,
-                GtkTreePath       *path,
-                GtkTreeViewColumn *column,
-                gpointer           data)
-{
-  PsppireSelector *selector  = data;
 
-  select_selection (selector);
+  selector->selecting = FALSE;
 }
 
-/* Callback for when the selector button is clicked */
+/* Callback for when the selector button is clicked,
+   or other event which causes the selector's action to occur.
+ */
 static void
-on_click (PsppireSelector *selector, gpointer data)
+on_click (GtkButton *b)
 {
+  PsppireSelector *selector = PSPPIRE_SELECTOR (b);
+
   switch (selector->direction)
     {
     case PSPPIRE_SELECTOR_SOURCE_TO_DEST:
@@ -379,27 +723,26 @@ on_click (PsppireSelector *selector, gpointer data)
       g_assert_not_reached ();
       break;
     }
+
+  if (GTK_BUTTON_CLASS (parent_class)->clicked)
+    GTK_BUTTON_CLASS (parent_class)->clicked (b);
 }
 
-/* Default visibility filter for GtkTreeView DEST widget */
 static gboolean
-is_item_in_dest (GtkTreeModel *model, GtkTreeIter *iter,
-                PsppireSelector *selector)
+is_item_in_dest (GtkTreeModel *model, GtkTreeIter *iter, PsppireSelector *selector)
 {
-  GtkTreeModel *dest_model;
-  GtkTreeIter dest_iter;
+  gboolean result = FALSE;
   GtkTreeIter source_iter;
-  gint index;
-  GtkTreePath *path ;
   GtkTreeModel *source_model;
+  GValue value = {0};
 
-  if ( GTK_IS_TREE_MODEL_FILTER (model) )
+  if (GTK_IS_TREE_MODEL_FILTER (model))
     {
       source_model = gtk_tree_model_filter_get_model
        (GTK_TREE_MODEL_FILTER (model));
 
       gtk_tree_model_filter_convert_iter_to_child_iter
-       ( GTK_TREE_MODEL_FILTER (model),  &source_iter,  iter  );
+       (GTK_TREE_MODEL_FILTER (model),  &source_iter, iter);
     }
   else
     {
@@ -407,29 +750,17 @@ is_item_in_dest (GtkTreeModel *model, GtkTreeIter *iter,
       source_iter = *iter;
     }
 
-  dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->dest));
-
-  path = gtk_tree_model_get_path (source_model, &source_iter);
-
-  index = *gtk_tree_path_get_indices (path);
+  gtk_tree_model_get_value (source_model, &source_iter, DICT_TVM_COL_VAR, &value);
 
-  gtk_tree_path_free (path);
+  result = psppire_select_dest_widget_contains_var (PSPPIRE_SELECT_DEST_WIDGET (selector->dest),
+                                                   &value);
 
-  if ( ! gtk_tree_model_get_iter_first (dest_model, &dest_iter) )
-    return FALSE;
+  g_value_unset (&value);
 
-  do
-    {
-      GValue value = {0};
-      gtk_tree_model_get_value (dest_model, &dest_iter, 0, &value);
+  return result;
+}
 
-      if ( g_value_get_int (&value) == index)
-       return TRUE;
-    }
-  while (gtk_tree_model_iter_next (dest_model, &dest_iter));
 
-  return FALSE;
-}
 
 /* Visibility function for items in the SOURCE widget.
    Returns TRUE iff *all* the selectors for which SOURCE is associated
@@ -449,8 +780,8 @@ is_source_item_visible (GtkTreeModel *childmodel,
     {
       PsppireSelector *selector = list->data;
 
-      if ( selector->filter (childmodel, iter, selector))
-         return FALSE;
+      if ( selector->filter && selector->filter (childmodel, iter, selector))
+       return FALSE;
 
       list = list->next;
     }
@@ -461,55 +792,147 @@ is_source_item_visible (GtkTreeModel *childmodel,
 
 /* set the source widget to SOURCE */
 static void
-set_tree_view_source (PsppireSelector *selector,
-                     GtkTreeView *source)
+set_tree_view_source (PsppireSelector *selector)
 {
-  GtkTreeSelection* selection ;
   GList *list = NULL;
 
   PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
-
-  if ( ! (list = g_hash_table_lookup (class->source_hash, source)))
+  
+  if ( ! (list = g_hash_table_lookup (class->source_hash, selector->source)))
     {
-      selector->filtered_source =
-       GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new
-                              (gtk_tree_view_get_model (source),  NULL));
-
-      gtk_tree_view_set_model (source, NULL);
-
-      gtk_tree_view_set_model (source,
-                              GTK_TREE_MODEL (selector->filtered_source));
+      /* Base case:  This widget is currently not the source of 
+        any selector.  Create a hash entry and make this selector
+        the first selector in the list */
 
       list = g_list_append (list, selector);
-      g_hash_table_insert (class->source_hash, source, list);
-
+      g_hash_table_insert (class->source_hash, selector->source, list);
 
-      gtk_tree_model_filter_set_visible_func (selector->filtered_source,
-                                             is_source_item_visible,
-                                             selector,
-                                             NULL);
+      /* Save the list item so that it can be removed later */
+      selector->source_litem = list;
     }
   else
     {  /* Append this selector to the list and push the <source,list>
          pair onto the hash table */
 
-      selector->filtered_source = GTK_TREE_MODEL_FILTER (
-       gtk_tree_view_get_model (source));
+      if ( NULL == g_list_find (list, selector) )
+       {
+         if ( selector->primary_requested )
+           {
+             list = g_list_prepend (list, selector);
+             selector->source_litem = list;
+           }
+         else
+           {
+             list = g_list_append (list, selector);
+             selector->source_litem = g_list_last (list);
+           }
+         g_hash_table_replace (class->source_hash, selector->source, list);
+       }
+    }
+}
 
-      list = g_list_append (list, selector);
-      g_hash_table_replace (class->source_hash, source, list);
+
+
+/* This function is a callback which occurs when the
+   SOURCE's model has changed */
+static void
+update_model (
+              GtkTreeView *source,
+              GParamSpec *psp,
+              PsppireSelector *selector
+              )
+{
+  GtkTreeModel *model = gtk_tree_view_get_model (source);
+
+  g_assert (source == GTK_TREE_VIEW (selector->source));
+
+  if (model && (model == g_object_get_data (G_OBJECT (source), "model-copy")))
+    return;
+
+  if (model != NULL) 
+    {      
+      GtkTreeModel *new_model = gtk_tree_model_filter_new (model, NULL); 
+
+      g_object_set_data (G_OBJECT (source), "model-copy", new_model);  
+
+      gtk_tree_view_set_model (source, new_model);
+  
+      gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_model),
+                                              is_source_item_visible,
+                                              selector,
+                                              NULL);
+
+      g_signal_connect_swapped (new_model,
+                               "row-deleted",
+                               G_CALLBACK (on_row_deleted), selector);
+
+      g_signal_connect_swapped (new_model,
+                               "row-inserted",
+                               G_CALLBACK (on_row_inserted), selector);
+
+      g_object_unref (new_model);
     }
+}
 
-  selection = gtk_tree_view_get_selection (source);
 
-  g_signal_connect (source, "row-activated", G_CALLBACK (on_row_activate),
-                   selector);
 
-  g_signal_connect (selection, "changed", G_CALLBACK (on_source_select),
-                   selector);
+/*
+   Callback for when the destination treeview's data changes
+ */
+static void
+on_dest_data_change (GtkTreeModel *tree_model,
+                    GtkTreePath  *path,
+                    GtkTreeIter  *iter,
+                    gpointer      user_data)
+{
+  PsppireSelector *selector = user_data;
+
+  if ( selector->selecting) return;
+
+  refilter (selector);
+}
+
+
+static void
+on_dest_data_delete (GtkTreeModel *tree_model,
+                    GtkTreePath  *path,
+                    gpointer      user_data)
+{
+  PsppireSelector *selector = user_data;
+
+  if ( selector->selecting ) return;
+
+  refilter (selector);
 }
 
 
+static void
+remove_selector_handlers (PsppireSelector *selector, GObject *sel)
+{
+  g_signal_handlers_disconnect_by_data (sel, selector);
+}
+
+static void
+on_dest_model_changed (PsppireSelector *selector)
+{
+  GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->dest));
+
+  if (model == NULL) 
+    return;
+
+  g_signal_connect (model, "row-changed", G_CALLBACK (on_dest_data_change),
+                   selector);
+  
+  g_signal_connect (model, "row-deleted", G_CALLBACK (on_dest_data_delete),
+                   selector);
+
+  g_signal_connect (selector, "destroy", G_CALLBACK (remove_selector_handlers), model);
+  
+  if ( selector->selecting ) return;
+  
+  refilter (selector);
+}
+
 /* Set the destination widget to DEST */
 static void
 set_tree_view_dest (PsppireSelector *selector,
@@ -517,82 +940,193 @@ set_tree_view_dest (PsppireSelector *selector,
 {
   GtkTreeSelection* selection = gtk_tree_view_get_selection (dest);
 
+
   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
 
   g_signal_connect (selection, "changed", G_CALLBACK (on_dest_treeview_select),
                    selector);
+
+  on_dest_model_changed (selector);
+  g_signal_connect_swapped (dest, "notify::model",
+                           G_CALLBACK (on_dest_model_changed), selector);
 }
 
-/* Callback for when the DEST GtkEntry is activated (Enter is pressed) */
 static void
-on_entry_activate (GtkEntry *w, gpointer data)
+set_layer_dest (PsppireSelector *selector,
+                   PsppireMeansLayer *dest)
 {
-  PsppireSelector * selector = data;
+  GtkTreeSelection* selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dest->var_view));
+
+  gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
 
-  gtk_tree_model_filter_refilter (selector->filtered_source);
+
+  g_signal_connect (selection, "changed", G_CALLBACK (on_dest_treeview_select),
+                   selector);
 }
 
+
 /* Callback for when the DEST GtkEntry is selected (clicked) */
 static gboolean
 on_entry_dest_select (GtkWidget *widget, GdkEventFocus *event, gpointer data)
 {
   PsppireSelector * selector = data;
 
+  gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (selector->source)));
   set_direction (selector, PSPPIRE_SELECTOR_DEST_TO_SOURCE);
 
   return FALSE;
 }
 
+
+
 /* Set DEST to be the destination GtkEntry widget */
 static void
 set_entry_dest (PsppireSelector *selector,
                GtkEntry *dest)
 {
-  g_signal_connect (dest, "activate", G_CALLBACK (on_entry_activate),
+  g_signal_connect_swapped (dest, "activate", G_CALLBACK (refilter),
+                   selector);
+
+  g_signal_connect_swapped (dest, "changed", G_CALLBACK (refilter),
                    selector);
 
   g_signal_connect (dest, "focus-in-event", G_CALLBACK (on_entry_dest_select),
                    selector);
+
+  g_signal_connect_swapped (dest, "focus-out-event", G_CALLBACK (refilter),
+                   selector);
+
+
+}
+
+static void
+set_default_filter (PsppireSelector *selector)
+{
+  if ( selector->filter == NULL)
+    {
+      if  (GTK_IS_TREE_VIEW (selector->dest))
+       selector->filter = is_item_in_dest;
+    }
 }
 
 
-/* Set SOURCE and DEST for this selector, and
-   set SELECT_FUNC and FILTER_FUNC */
 void
-psppire_selector_set_subjects (PsppireSelector *selector,
-                              GtkWidget *source,
-                              GtkWidget *dest,
-                              SelectItemsFunc *select_func,
-                              FilterItemsFunc *filter_func )
+psppire_selector_update_subjects (PsppireSelector *selector)
 {
-  selector->filter = filter_func ;
+  if ( NULL == selector->dest )
+    return;
+
+  set_default_filter (selector);
 
-  selector->source = source;
-  selector->dest = dest;
+  if ( NULL == selector->source )
+    return;
 
-  if ( filter_func == NULL)
+  if ( GTK_IS_TREE_VIEW (selector->source))
     {
-      if  (GTK_IS_TREE_VIEW (dest))
-       selector->filter = is_item_in_dest;
-    }
+      set_tree_view_source (selector);
+
+      g_signal_connect (selector->source, "notify::model", 
+                              G_CALLBACK (update_model), selector); 
 
-  g_signal_connect (selector, "clicked", G_CALLBACK (on_click), NULL);
+      update_model (GTK_TREE_VIEW (selector->source), 0, selector);
+    }
+  else
+    g_error ("Unsupported source widget: %s", G_OBJECT_TYPE_NAME (selector->source));
 
-  if ( GTK_IS_TREE_VIEW (source))
-    set_tree_view_source (selector, GTK_TREE_VIEW (source) );
+  if ( NULL == selector->dest)
+    ;
+  else if  ( GTK_IS_TREE_VIEW (selector->dest))
+    {
+      set_tree_view_dest (selector, GTK_TREE_VIEW (selector->dest));
+    }
+  else if ( GTK_IS_ENTRY (selector->dest))
+    {
+      set_entry_dest (selector, GTK_ENTRY (selector->dest));
+    }
+  else if (PSPPIRE_IS_MEANS_LAYER (selector->dest))
+    {
+      set_layer_dest (selector, PSPPIRE_MEANS_LAYER (selector->dest));
+    }
+  else if (GTK_IS_TEXT_VIEW (selector->dest))
+    {
+      /* Nothing to be done */
+    }
   else
-    g_error ("Unsupported source widget: %s", G_OBJECT_TYPE_NAME (source));
+    g_error ("Unsupported destination widget: %s", G_OBJECT_TYPE_NAME (selector->dest));
+
 
-  g_assert ( GTK_IS_TREE_MODEL_FILTER (selector->filtered_source));
+  /* FIXME: Remove this dependency */
+  if ( PSPPIRE_IS_DICT_VIEW (selector->source) )
+    {
+      GObjectClass *class = G_OBJECT_GET_CLASS (selector);
+      GType type = G_OBJECT_TYPE (selector->dest);
 
-  if  ( GTK_IS_TREE_VIEW (dest))
-    set_tree_view_dest (selector, GTK_TREE_VIEW (dest));
+      SelectItemsFunc *func  = 
+       g_hash_table_lookup (PSPPIRE_SELECTOR_CLASS (class)->default_selection_funcs, (gpointer) type);
 
-  else if ( GTK_IS_ENTRY (dest))
-    set_entry_dest (selector, GTK_ENTRY (dest));
+      if ( func )
+       psppire_selector_set_select_func (PSPPIRE_SELECTOR (selector),
+                                         func, NULL);
+    }
+}
 
-  else
-    g_error ("Unsupported destination widget: %s", G_OBJECT_TYPE_NAME (dest));
 
+void
+psppire_selector_set_default_selection_func (GType type, SelectItemsFunc *func)
+{
+  GObjectClass *class = g_type_class_ref (PSPPIRE_SELECTOR_TYPE);
+
+  g_hash_table_insert (PSPPIRE_SELECTOR_CLASS (class)->default_selection_funcs, (gpointer) type, func);
+
+  g_type_class_unref (class);
+}
+
+
+
+
+/* Set FILTER_FUNC for this selector */
+void
+psppire_selector_set_filter_func (PsppireSelector *selector,
+                                 FilterItemsFunc *filter_func)
+{
+  selector->filter = filter_func ;
+}
+
+
+/* Set SELECT_FUNC for this selector */
+void
+psppire_selector_set_select_func (PsppireSelector *selector,
+                              SelectItemsFunc *select_func,
+                              gpointer user_data)
+{
+  selector->select_user_data = user_data;
   selector->select_items = select_func;
 }
+
+
+
+void
+psppire_selector_set_allow (PsppireSelector *selector, AllowSelectionFunc *allow)
+{
+  selector->allow_selection = allow;
+}
+
+
+GType
+psppire_selector_orientation_get_type (void)
+{
+  static GType etype = 0;
+  if (etype == 0)
+    {
+      static const GEnumValue values[] =
+       {
+         { PSPPIRE_SELECT_SOURCE_BEFORE_DEST, "PSPPIRE_SELECT_SOURCE_BEFORE_DEST", "source before destination" },
+         { PSPPIRE_SELECT_SOURCE_AFTER_DEST, "PSPPIRE_SELECT_SOURCE_AFTER_DEST", "source after destination" },
+         { PSPPIRE_SELECT_SOURCE_ABOVE_DEST, "PSPPIRE_SELECT_SOURCE_ABOVE_DEST", "source above destination" },
+         { PSPPIRE_SELECT_SOURCE_BELOW_DEST, "PSPPIRE_SELECT_SOURCE_BELOW_DEST", "source below destination" },
+         { 0, NULL, NULL }
+       };
+      etype = g_enum_register_static (g_intern_static_string ("PsppireSelectorOrientation"), values);
+    }
+  return etype;
+}