Separated psppire_selector_set_subjects into two individual functions
[pspp-builds.git] / src / ui / gui / psppire-selector.c
index 453fee8ca006d8aba5d1db4b928da0cef973d46a..914c870174907e22d2b250797a3d20ba231d3e40 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
-   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,
@@ -13,9 +12,7 @@
    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
@@ -68,6 +65,7 @@
 
 #include <gtk/gtktreeview.h>
 #include <gtk/gtktreeselection.h>
+#include <gtk/gtktextview.h>
 #include <gtk/gtkwidget.h>
 
 static void psppire_selector_base_finalize (PsppireSelectorClass *, gpointer);
@@ -75,6 +73,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 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};
@@ -110,10 +112,102 @@ psppire_selector_get_type (void)
   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_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_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
-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 +215,47 @@ psppire_selector_finalize (GObject *object)
 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);
+
+  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_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,8 +275,6 @@ psppire_selector_class_init (PsppireSelectorClass *class)
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE,
                  0);
-
-
 }
 
 
@@ -151,6 +284,7 @@ psppire_selector_base_init (PsppireSelectorClass *class)
   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);
 }
@@ -165,19 +299,33 @@ psppire_selector_base_finalize(PsppireSelectorClass *class,
 }
 
 
+
 static void
 psppire_selector_init (PsppireSelector *selector)
 {
+  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->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);
 
-  /* 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;
 }
 
 
@@ -196,9 +344,48 @@ 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, "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
-    g_object_set (selector->arrow, "arrow-type", GTK_ARROW_LEFT, NULL);
+    {
+      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;
+       };
+
+    }
 }
 
 /* Callback for when the source selection changes */
@@ -209,9 +396,14 @@ on_source_select (GtkTreeSelection *treeselection, gpointer data)
 
   set_direction (selector, PSPPIRE_SELECTOR_SOURCE_TO_DEST);
 
-  if ( GTK_IS_ENTRY (selector->dest) )
+  if ( selector->allow_selection )
     {
-      gtk_widget_set_sensitive (GTK_WIDGET (selector),
+      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 );
     }
@@ -223,6 +415,8 @@ 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);
 }
 
@@ -291,6 +485,8 @@ de_select_selection_tree_view (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);
 
@@ -300,6 +496,8 @@ de_select_selection (PsppireSelector *selector)
   else
     g_assert_not_reached ();
 
+  selector->selecting = FALSE;
+
   gtk_tree_model_filter_refilter (selector->filtered_source);
 
   g_signal_emit (selector, signals [DE_SELECTED], 0);
@@ -322,6 +520,7 @@ select_selection (PsppireSelector *selector)
 
   g_return_if_fail (selector->select_items);
 
+  selector->selecting = TRUE;
 
   for (item = g_list_first (selected_rows);
        item != NULL;
@@ -341,7 +540,9 @@ select_selection (PsppireSelector *selector)
 
       selector->select_items (child_iter,
                              selector->dest,
-                             childmodel);
+                             childmodel,
+                             selector->select_user_data
+                             );
     }
 
   g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
@@ -350,9 +551,11 @@ select_selection (PsppireSelector *selector)
   gtk_tree_model_filter_refilter (selector->filtered_source);
 
   g_signal_emit (selector, signals [SELECTED], 0);
+
+  selector->selecting = FALSE;
 }
 
-/* Callback fro then the source treeview is activated (double clicked) */
+/* Callback for when the source treeview is activated (double clicked) */
 static void
 on_row_activate (GtkTreeView       *tree_view,
                 GtkTreePath       *path,
@@ -361,12 +564,14 @@ on_row_activate (GtkTreeView       *tree_view,
 {
   PsppireSelector *selector  = data;
 
-  select_selection (selector);
+  gtk_action_activate (selector->action);
 }
 
-/* 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_activate (PsppireSelector *selector, gpointer data)
 {
   switch (selector->direction)
     {
@@ -421,10 +626,21 @@ is_item_in_dest (GtkTreeModel *model, GtkTreeIter *iter,
 
   do
     {
+      int x;
       GValue value = {0};
+      GValue int_value = {0};
       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));
@@ -450,8 +666,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;
     }
@@ -511,6 +727,48 @@ set_tree_view_source (PsppireSelector *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;
+
+  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);
+
+  g_signal_connect (model, "row-deleted", G_CALLBACK (on_dest_data_delete),
+                   selector);
+}
+
 /* Set the destination widget to DEST */
 static void
 set_tree_view_dest (PsppireSelector *selector,
@@ -518,19 +776,27 @@ 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);
+
+
+  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);
+  return FALSE;
 }
 
 /* Callback for when the DEST GtkEntry is selected (clicked) */
@@ -539,61 +805,160 @@ 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;
 }
 
+
+
+/* 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)
 {
-  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);
 
 
-/* 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 )
+  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
+update_subjects (PsppireSelector *selector)
 {
-  selector->filter = filter_func ;
+  GtkTreeModel *model = NULL;
 
-  selector->source = source;
-  selector->dest = dest;
+  if ( NULL == selector->dest )
+    return;
 
-  if ( filter_func == NULL)
+  if ( selector->filter == NULL)
     {
-      if  (GTK_IS_TREE_VIEW (dest))
+      if  (GTK_IS_TREE_VIEW (selector->dest))
        selector->filter = is_item_in_dest;
     }
 
-  g_signal_connect (selector, "clicked", G_CALLBACK (on_click), NULL);
+  if ( NULL == selector->source )
+    return;
 
-  if ( GTK_IS_TREE_VIEW (source))
-    set_tree_view_source (selector, GTK_TREE_VIEW (source) );
-  else
-    g_error ("Unsupported source widget: %s", G_OBJECT_TYPE_NAME (source));
+  g_signal_connect_swapped (selector->source, "notify::model",
+                           G_CALLBACK (update_subjects), selector);
 
-  g_assert ( GTK_IS_TREE_MODEL_FILTER (selector->filtered_source));
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->source));
 
-  if  ( GTK_IS_TREE_VIEW (dest))
-    set_tree_view_dest (selector, GTK_TREE_VIEW (dest));
+  if ( NULL == model)
+    return;
 
-  else if ( GTK_IS_ENTRY (dest))
-    set_entry_dest (selector, GTK_ENTRY (dest));
 
+  if ( GTK_IS_TREE_VIEW (selector->source))
+    set_tree_view_source (selector, GTK_TREE_VIEW (selector->source) );
   else
-    g_error ("Unsupported destination widget: %s", G_OBJECT_TYPE_NAME (dest));
+    g_error ("Unsupported source widget: %s", G_OBJECT_TYPE_NAME (selector->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 (GTK_IS_TEXT_VIEW (selector->dest))
+    {
+      /* Nothing to be done */
+    }
+  else
+    g_error ("Unsupported destination widget: %s", G_OBJECT_TYPE_NAME (selector->dest));
+
+}
+
+
+/* Set FILTER_FUNC for this selector */
+void
+psppire_selector_set_filter_func (PsppireSelector *selector,
+                                 FilterItemsFunc *filter_func)
+{
+  selector->filter = filter_func ;
+
+  if ( selector->filter == NULL)
+    {
+      if  (GTK_IS_TREE_VIEW (selector->dest))
+       selector->filter = is_item_in_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;
 }
+
+
+
+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;
+}