New module psppire-var-view
[pspp-builds.git] / src / ui / gui / psppire-selector.c
index 453fee8ca006d8aba5d1db4b928da0cef973d46a..367502b6ea219dca40b1aaeb07bb65a806996de4 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 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
    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,
    (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
@@ -13,9 +12,8 @@
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    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
 
 /*
   This module provides a widget, PsppireSelector derived from
 
 #include <config.h>
 
 
 #include <config.h>
 
+#include "psppire-dictview.h"
+#include "psppire-var-view.h"
+
+
+
 #include <gtk/gtksignal.h>
 #include <gtk/gtkbutton.h>
 #include <gtk/gtkentry.h>
 #include <gtk/gtksignal.h>
 #include <gtk/gtkbutton.h>
 #include <gtk/gtkentry.h>
@@ -68,6 +71,7 @@
 
 #include <gtk/gtktreeview.h>
 #include <gtk/gtktreeselection.h>
 
 #include <gtk/gtktreeview.h>
 #include <gtk/gtktreeselection.h>
+#include <gtk/gtktextview.h>
 #include <gtk/gtkwidget.h>
 
 static void psppire_selector_base_finalize (PsppireSelectorClass *, gpointer);
 #include <gtk/gtkwidget.h>
 
 static void psppire_selector_base_finalize (PsppireSelectorClass *, gpointer);
@@ -75,6 +79,10 @@ 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 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};
 enum  {SELECTED,    /* Emitted when an item is inserted into dest */
        DE_SELECTED, /* Emitted when an item is removed from dest */
        n_SIGNALS};
@@ -110,10 +118,107 @@ psppire_selector_get_type (void)
   return psppire_selector_type;
 }
 
   return psppire_selector_type;
 }
 
+static GObjectClass * parent_class = NULL;
+
+static void
+psppire_selector_finalize (GObject *obj)
+{
+   /* Chain up to the parent class */
+   G_OBJECT_CLASS (parent_class)->finalize (obj);
+}
+
+
+static void
+psppire_selector_dispose (GObject *obj)
+{
+  PsppireSelector *sel = PSPPIRE_SELECTOR (obj);
+
+  if (sel->dispose_has_run)
+    return;
+
+  /* Make sure dispose does not run twice. */
+  sel->dispose_has_run = TRUE;
+
+  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_activate (PsppireSelector *selector, gpointer data);
+
+static void update_subjects (PsppireSelector *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;
+      update_subjects (selector);
+      break;
+    case PROP_SOURCE_WIDGET:
+      selector->source = g_value_dup_object (value);
+      update_subjects (selector);
+      break;
+    case PROP_DEST_WIDGET:
+      selector->dest = g_value_dup_object (value);
+      update_subjects (selector);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    };
+}
+
 
 static void
 
 static void
-psppire_selector_finalize (GObject *object)
+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;
+    };
 }
 
 
 }
 
 
@@ -121,6 +226,60 @@ psppire_selector_finalize (GObject *object)
 static void
 psppire_selector_class_init (PsppireSelectorClass *class)
 {
 static void
 psppire_selector_class_init (PsppireSelectorClass *class)
 {
+  GObjectClass *object_class = G_OBJECT_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);
+
+
+  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),
   signals [SELECTED] =
     g_signal_new ("selected",
                  G_TYPE_FROM_CLASS (class),
@@ -140,8 +299,6 @@ psppire_selector_class_init (PsppireSelectorClass *class)
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE,
                  0);
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE,
                  0);
-
-
 }
 
 
 }
 
 
@@ -151,6 +308,7 @@ psppire_selector_base_init (PsppireSelectorClass *class)
   GObjectClass *object_class = G_OBJECT_CLASS (class);
 
   object_class->finalize = psppire_selector_finalize;
   GObjectClass *object_class = G_OBJECT_CLASS (class);
 
   object_class->finalize = psppire_selector_finalize;
+  object_class->dispose = psppire_selector_dispose;
 
   class->source_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
 }
 
   class->source_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
 }
@@ -164,20 +322,102 @@ psppire_selector_base_finalize(PsppireSelectorClass *class,
   g_hash_table_destroy (class->source_hash);
 }
 
   g_hash_table_destroy (class->source_hash);
 }
 
+/* Callback for when the source treeview is activated (double clicked) */
+static void
+on_row_activate (GtkTreeView       *tree_view,
+                GtkTreePath       *path,
+                GtkTreeViewColumn *column,
+                gpointer           data)
+{
+  PsppireSelector *selector  = data;
+
+  gtk_action_activate (selector->action);
+}
+
+/* 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 ( selector->allow_selection )
+    {
+      gtk_action_set_sensitive (selector->action,
+                               selector->allow_selection (selector->source, selector->dest));
+    }
+  else if ( GTK_IS_ENTRY (selector->dest) )
+    {
+      gtk_action_set_sensitive (selector->action,
+                               gtk_tree_selection_count_selected_rows
+                               (treeselection) <= 1 );
+    }
+}
+
+
+static void
+on_realize (PsppireSelector *selector)
+{
+  PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
+  GtkTreeSelection* selection ;
+
+  GList *list = g_hash_table_lookup (class->source_hash, selector->source);
+
+  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)
 {
 
 static void
 psppire_selector_init (PsppireSelector *selector)
 {
+  selector->primary_requested = FALSE;
+  selector->select_user_data = NULL;
+  selector->select_items = NULL;
+  selector->allow_selection = NULL;
+  selector->filter = NULL;
+
   selector->arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
   selector->filtered_source = NULL;
 
   selector->arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
   selector->filtered_source = NULL;
 
+  selector->action = gtk_action_new ("select", NULL, NULL, "pspp-stock-select");
+
+  gtk_action_connect_proxy (selector->action, GTK_WIDGET (selector));
+
   gtk_container_add (GTK_CONTAINER (selector), selector->arrow);
 
   gtk_widget_show (selector->arrow);
 
   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));
+  g_signal_connect_swapped (selector->action, "activate", G_CALLBACK (on_activate), 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;
+
+  g_signal_connect (selector, "realize",
+                   G_CALLBACK (on_realize), NULL);
+
 }
 
 
 }
 
 
@@ -196,24 +436,47 @@ 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 )
   /* 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, "arrow-type", GTK_ARROW_RIGHT, NULL);
+         break;
+       case   PSPPIRE_SELECT_SOURCE_AFTER_DEST:
+         g_object_set (selector->arrow, "arrow-type", GTK_ARROW_LEFT, NULL);
+         break;
+       case   PSPPIRE_SELECT_SOURCE_ABOVE_DEST:
+         g_object_set (selector->arrow, "arrow-type", GTK_ARROW_DOWN, NULL);
+         break;
+       case   PSPPIRE_SELECT_SOURCE_BELOW_DEST:
+         g_object_set (selector->arrow, "arrow-type", GTK_ARROW_UP, NULL);
+         break;
+       default:
+         g_assert_not_reached ();
+         break;
+       };
+    }
   else
   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, "arrow-type", GTK_ARROW_LEFT, NULL);
+         break;
+       case   PSPPIRE_SELECT_SOURCE_AFTER_DEST:
+         g_object_set (selector->arrow, "arrow-type", GTK_ARROW_RIGHT, NULL);
+         break;
+       case   PSPPIRE_SELECT_SOURCE_ABOVE_DEST:
+         g_object_set (selector->arrow, "arrow-type", GTK_ARROW_UP, NULL);
+         break;
+       case   PSPPIRE_SELECT_SOURCE_BELOW_DEST:
+         g_object_set (selector->arrow, "arrow-type", GTK_ARROW_DOWN, NULL);
+         break;
+       default:
+         g_assert_not_reached ();
+         break;
+       };
+
     }
 }
 
     }
 }
 
@@ -223,6 +486,8 @@ on_dest_treeview_select (GtkTreeSelection *treeselection, gpointer data)
 {
   PsppireSelector *selector = 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);
 }
 
   set_direction (selector, PSPPIRE_SELECTOR_DEST_TO_SOURCE);
 }
 
@@ -291,6 +556,8 @@ de_select_selection_tree_view (PsppireSelector *selector)
 static void
 de_select_selection (PsppireSelector *selector)
 {
 static void
 de_select_selection (PsppireSelector *selector)
 {
+  selector->selecting = TRUE;
+
   if ( GTK_IS_TREE_VIEW (selector->dest ) )
     de_select_selection_tree_view (selector);
 
   if ( GTK_IS_TREE_VIEW (selector->dest ) )
     de_select_selection_tree_view (selector);
 
@@ -300,6 +567,8 @@ de_select_selection (PsppireSelector *selector)
   else
     g_assert_not_reached ();
 
   else
     g_assert_not_reached ();
 
+  selector->selecting = FALSE;
+
   gtk_tree_model_filter_refilter (selector->filtered_source);
 
   g_signal_emit (selector, signals [DE_SELECTED], 0);
   gtk_tree_model_filter_refilter (selector->filtered_source);
 
   g_signal_emit (selector, signals [DE_SELECTED], 0);
@@ -322,6 +591,7 @@ select_selection (PsppireSelector *selector)
 
   g_return_if_fail (selector->select_items);
 
 
   g_return_if_fail (selector->select_items);
 
+  selector->selecting = TRUE;
 
   for (item = g_list_first (selected_rows);
        item != NULL;
 
   for (item = g_list_first (selected_rows);
        item != NULL;
@@ -341,7 +611,9 @@ select_selection (PsppireSelector *selector)
 
       selector->select_items (child_iter,
                              selector->dest,
 
       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_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
@@ -350,23 +622,15 @@ select_selection (PsppireSelector *selector)
   gtk_tree_model_filter_refilter (selector->filtered_source);
 
   g_signal_emit (selector, signals [SELECTED], 0);
   gtk_tree_model_filter_refilter (selector->filtered_source);
 
   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
 static void
-on_click (PsppireSelector *selector, gpointer data)
+on_activate (PsppireSelector *selector, gpointer data)
 {
   switch (selector->direction)
     {
 {
   switch (selector->direction)
     {
@@ -382,6 +646,13 @@ on_click (PsppireSelector *selector, gpointer data)
     }
 }
 
     }
 }
 
+static gboolean
+permissive_filter (GtkTreeModel *model, GtkTreeIter *iter,
+                PsppireSelector *selector)
+{
+  return FALSE;
+}
+
 /* Default visibility filter for GtkTreeView DEST widget */
 static gboolean
 is_item_in_dest (GtkTreeModel *model, GtkTreeIter *iter,
 /* Default visibility filter for GtkTreeView DEST widget */
 static gboolean
 is_item_in_dest (GtkTreeModel *model, GtkTreeIter *iter,
@@ -421,10 +692,21 @@ is_item_in_dest (GtkTreeModel *model, GtkTreeIter *iter,
 
   do
     {
 
   do
     {
+      int x;
       GValue value = {0};
       GValue value = {0};
+      GValue int_value = {0};
       gtk_tree_model_get_value (dest_model, &dest_iter, 0, &value);
 
       gtk_tree_model_get_value (dest_model, &dest_iter, 0, &value);
 
-      if ( g_value_get_int (&value) == index)
+      g_value_init (&int_value, G_TYPE_INT);
+
+      g_value_transform (&value, &int_value);
+
+      x = g_value_get_int (&int_value);
+
+      g_value_unset (&int_value);
+      g_value_unset (&value);
+
+      if ( x == index )
        return TRUE;
     }
   while (gtk_tree_model_iter_next (dest_model, &dest_iter));
        return TRUE;
     }
   while (gtk_tree_model_iter_next (dest_model, &dest_iter));
@@ -450,8 +732,8 @@ is_source_item_visible (GtkTreeModel *childmodel,
     {
       PsppireSelector *selector = list->data;
 
     {
       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;
     }
 
       list = list->next;
     }
@@ -465,18 +747,17 @@ static void
 set_tree_view_source (PsppireSelector *selector,
                      GtkTreeView *source)
 {
 set_tree_view_source (PsppireSelector *selector,
                      GtkTreeView *source)
 {
-  GtkTreeSelection* selection ;
+
   GList *list = NULL;
 
   PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
   GList *list = NULL;
 
   PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
+  
+  GtkTreeModel *model = gtk_tree_view_get_model (source);
 
   if ( ! (list = g_hash_table_lookup (class->source_hash, source)))
     {
       selector->filtered_source =
 
   if ( ! (list = g_hash_table_lookup (class->source_hash, 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_MODEL_FILTER (gtk_tree_model_filter_new (model, NULL));
 
       gtk_tree_view_set_model (source,
                               GTK_TREE_MODEL (selector->filtered_source));
 
       gtk_tree_view_set_model (source,
                               GTK_TREE_MODEL (selector->filtered_source));
@@ -494,23 +775,64 @@ set_tree_view_source (PsppireSelector *selector,
     {  /* Append this selector to the list and push the <source,list>
          pair onto the hash table */
 
     {  /* 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));
+      selector->filtered_source = GTK_TREE_MODEL_FILTER (model);
 
 
-      list = g_list_append (list, selector);
-      g_hash_table_replace (class->source_hash, source, list);
+      if ( NULL == g_list_find (list, selector) )
+       {
+         if ( selector->primary_requested )
+           list = g_list_prepend (list, selector);
+         else
+           list = g_list_append (list, selector);
+         g_hash_table_replace (class->source_hash, source, list);
+       }
     }
 
     }
 
-  selection = gtk_tree_view_get_selection (source);
 
 
-  g_signal_connect (source, "row-activated", G_CALLBACK (on_row_activate),
+}
+
+
+/*
+   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;
+
+  gtk_tree_model_filter_refilter (selector->filtered_source);
+}
+
+
+static void
+on_dest_data_delete (GtkTreeModel *tree_model,
+                    GtkTreePath  *path,
+                    gpointer      user_data)
+{
+  PsppireSelector *selector = user_data;
+
+  if ( selector->selecting ) return;
+
+  gtk_tree_model_filter_refilter (selector->filtered_source);
+}
+
+
+static void
+xxx (PsppireSelector *selector)
+{
+  GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->dest));
+
+  g_signal_connect (model, "row-changed", G_CALLBACK (on_dest_data_change),
                    selector);
 
                    selector);
 
-  g_signal_connect (selection, "changed", G_CALLBACK (on_source_select),
+  g_signal_connect (model, "row-deleted", G_CALLBACK (on_dest_data_delete),
                    selector);
 }
 
                    selector);
 }
 
-
 /* Set the destination widget to DEST */
 static void
 set_tree_view_dest (PsppireSelector *selector,
 /* Set the destination widget to DEST */
 static void
 set_tree_view_dest (PsppireSelector *selector,
@@ -518,19 +840,27 @@ set_tree_view_dest (PsppireSelector *selector,
 {
   GtkTreeSelection* selection = gtk_tree_view_get_selection (dest);
 
 {
   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);
   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
 
   g_signal_connect (selection, "changed", G_CALLBACK (on_dest_treeview_select),
                    selector);
+
+
+  g_signal_connect_swapped (dest, "notify::model",
+                           G_CALLBACK (xxx), selector);
+
 }
 
 }
 
-/* Callback for when the DEST GtkEntry is activated (Enter is pressed) */
-static void
-on_entry_activate (GtkEntry *w, gpointer data)
+/* 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)
 {
 {
-  PsppireSelector * selector = data;
-
   gtk_tree_model_filter_refilter (selector->filtered_source);
   gtk_tree_model_filter_refilter (selector->filtered_source);
+  return FALSE;
 }
 
 /* Callback for when the DEST GtkEntry is selected (clicked) */
 }
 
 /* Callback for when the DEST GtkEntry is selected (clicked) */
@@ -539,61 +869,173 @@ on_entry_dest_select (GtkWidget *widget, GdkEventFocus *event, gpointer data)
 {
   PsppireSelector * selector = 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_direction (selector, PSPPIRE_SELECTOR_DEST_TO_SOURCE);
 
   return FALSE;
 }
 
+
+
+/* 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);
+}
+
+
+
 /* Set DEST to be the destination GtkEntry widget */
 static void
 set_entry_dest (PsppireSelector *selector,
                GtkEntry *dest)
 {
 /* 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);
                    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);
+
+
+  g_signal_connect_swapped (selector->filtered_source, "row-deleted",
+                   G_CALLBACK (on_row_deleted), selector);
+
+  g_signal_connect_swapped (selector->filtered_source, "row-inserted",
+                   G_CALLBACK (on_row_inserted), selector);
 }
 
 }
 
+static void
+set_default_filter (PsppireSelector *selector)
+{
+  if ( selector->filter == NULL)
+    {
+      if  (GTK_IS_TREE_VIEW (selector->dest))
+       selector->filter = permissive_filter;
+    }
+}
 
 
-/* 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 )
+static void
+update_subjects (PsppireSelector *selector)
 {
 {
-  selector->filter = filter_func ;
+  GtkTreeModel *model = NULL;
+
+  if ( NULL == selector->dest )
+    return;
+
+  set_default_filter (selector);
+
+  if ( NULL == selector->source )
+    return;
 
 
-  selector->source = source;
-  selector->dest = dest;
+  g_signal_connect_swapped (selector->source, "notify::model",
+                           G_CALLBACK (update_subjects), selector);
 
 
-  if ( filter_func == NULL)
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->source));
+
+  if ( NULL == model)
+    return;
+
+
+  if ( GTK_IS_TREE_VIEW (selector->source))
+    set_tree_view_source (selector, GTK_TREE_VIEW (selector->source) );
+  else
+    g_error ("Unsupported source widget: %s", G_OBJECT_TYPE_NAME (selector->source));
+
+  if ( NULL == selector->dest)
+    ;
+  else if  ( GTK_IS_TREE_VIEW (selector->dest))
     {
     {
-      if  (GTK_IS_TREE_VIEW (dest))
-       selector->filter = is_item_in_dest;
+      set_tree_view_dest (selector, GTK_TREE_VIEW (selector->dest));
     }
 
     }
 
-  g_signal_connect (selector, "clicked", G_CALLBACK (on_click), NULL);
+  else if ( GTK_IS_ENTRY (selector->dest))
+    set_entry_dest (selector, GTK_ENTRY (selector->dest));
 
 
-  if ( GTK_IS_TREE_VIEW (source))
-    set_tree_view_source (selector, GTK_TREE_VIEW (source) );
+  else if (GTK_IS_TEXT_VIEW (selector->dest))
+    {
+      /* Nothing to be done */
+    }
   else
   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) )
+    {
+      if ( PSPPIRE_IS_VAR_VIEW (selector->dest))
+       psppire_selector_set_select_func (PSPPIRE_SELECTOR (selector),
+                                         insert_source_row_into_tree_view,
+                                         NULL);
+      else if (GTK_IS_ENTRY (selector->dest))
+       psppire_selector_set_select_func (PSPPIRE_SELECTOR (selector),
+                                         insert_source_row_into_entry,
+                                         NULL);
+    }
 
 
-  if  ( GTK_IS_TREE_VIEW (dest))
-    set_tree_view_dest (selector, GTK_TREE_VIEW (dest));
+}
 
 
-  else if ( GTK_IS_ENTRY (dest))
-    set_entry_dest (selector, GTK_ENTRY (dest));
+/* Set FILTER_FUNC for this selector */
+void
+psppire_selector_set_filter_func (PsppireSelector *selector,
+                                 FilterItemsFunc *filter_func)
+{
+  selector->filter = filter_func ;
+  
+  set_default_filter (selector);
+}
 
 
-  else
-    g_error ("Unsupported destination widget: %s", G_OBJECT_TYPE_NAME (dest));
 
 
+/* 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;
 }
   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;
+}