Ensure that only one set of callbacks exist.
[pspp-builds.git] / src / ui / gui / psppire-selector.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2009 Free Software Foundation
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 /*
18   This module provides a widget, PsppireSelector derived from
19   GtkButton.
20
21   It contains a GtkArrow, and is used for selecting objects from a
22   GtkTreeView and putting them into a destination widget (often
23   another GtkTreeView).  Typically this is used in psppire for
24   selecting variables, thus:
25
26
27   +----------------------------------------------------------+
28   |                                                          |
29   |     Source Widget                       Dest Widget      |
30   |   +----------------+                 +----------------+  |
31   |   | Variable0      |                 | Variable2      |  |
32   |   | Variable1      |                 |                |  |
33   |   | Variable3      |                 |                |  |
34   |   |                |    Selector     |                |  |
35   |   |                |                 |                |  |
36   |   |                |    +------+     |                |  |
37   |   |                |    | |\   |     |                |  |
38   |   |                |    | | \  |     |                |  |
39   |   |                |    | | /  |     |                |  |
40   |   |                |    | |/   |     |                |  |
41   |   |                |    +------+     |                |  |
42   |   |                |                 |                |  |
43   |   |                |                 |                |  |
44   |   |                |                 |                |  |
45   |   |                |                 |                |  |
46   |   +----------------+                 +----------------+  |
47   |                                                          |
48   +----------------------------------------------------------+
49
50   The Source Widget is always a GtkTreeView.  The Dest Widget may be a
51   GtkTreeView or a GtkEntry (other destination widgets may be
52   supported in the future).
53
54   Widgets may be source to more than one PsppireSelector.
55 */
56
57
58 #include <config.h>
59
60 #include <gtk/gtksignal.h>
61 #include <gtk/gtkbutton.h>
62 #include <gtk/gtkentry.h>
63
64 #include "psppire-selector.h"
65
66 #include <gtk/gtktreeview.h>
67 #include <gtk/gtktreeselection.h>
68 #include <gtk/gtktextview.h>
69 #include <gtk/gtkwidget.h>
70
71 static void psppire_selector_base_finalize (PsppireSelectorClass *, gpointer);
72 static void psppire_selector_base_init     (PsppireSelectorClass *class);
73 static void psppire_selector_class_init    (PsppireSelectorClass *class);
74 static void psppire_selector_init          (PsppireSelector      *selector);
75
76
77 static void set_direction (PsppireSelector *, enum psppire_selector_dir);
78
79
80 enum  {SELECTED,    /* Emitted when an item is inserted into dest */
81        DE_SELECTED, /* Emitted when an item is removed from dest */
82        n_SIGNALS};
83
84 static guint signals [n_SIGNALS];
85
86
87 GType
88 psppire_selector_get_type (void)
89 {
90   static GType psppire_selector_type = 0;
91
92   if (!psppire_selector_type)
93     {
94       static const GTypeInfo psppire_selector_info =
95       {
96         sizeof (PsppireSelectorClass),
97         (GBaseInitFunc) psppire_selector_base_init,
98         (GBaseFinalizeFunc) psppire_selector_base_finalize,
99         (GClassInitFunc)psppire_selector_class_init,
100         (GClassFinalizeFunc) NULL,
101         NULL,
102         sizeof (PsppireSelector),
103         0,
104         (GInstanceInitFunc) psppire_selector_init,
105       };
106
107       psppire_selector_type =
108         g_type_register_static (GTK_TYPE_BUTTON, "PsppireSelector",
109                                 &psppire_selector_info, 0);
110     }
111
112   return psppire_selector_type;
113 }
114
115 static GObjectClass * parent_class = NULL;
116
117 static void
118 psppire_selector_finalize (GObject *obj)
119 {
120    /* Chain up to the parent class */
121    G_OBJECT_CLASS (parent_class)->finalize (obj);
122 }
123
124
125 static void
126 psppire_selector_dispose (GObject *obj)
127 {
128   PsppireSelector *sel = PSPPIRE_SELECTOR (obj);
129
130   if (sel->dispose_has_run)
131     return;
132
133   /* Make sure dispose does not run twice. */
134   sel->dispose_has_run = TRUE;
135
136   g_object_unref (sel->dest);
137   g_object_unref (sel->source);
138
139   /* Chain up to the parent class */
140   G_OBJECT_CLASS (parent_class)->dispose (obj);
141 }
142
143
144 /* Properties */
145 enum
146 {
147   PROP_0,
148   PROP_ORIENTATION,
149   PROP_SOURCE_WIDGET,
150   PROP_DEST_WIDGET
151 };
152
153
154 static void on_activate (PsppireSelector *selector, gpointer data);
155
156 static void update_subjects (PsppireSelector *selector);
157
158
159 static void
160 psppire_selector_set_property (GObject         *object,
161                                guint            prop_id,
162                                const GValue    *value,
163                                GParamSpec      *pspec)
164 {
165   PsppireSelector *selector = PSPPIRE_SELECTOR (object);
166
167   switch (prop_id)
168     {
169     case PROP_ORIENTATION:
170       selector->orientation = g_value_get_enum (value);
171       set_direction (selector, selector->direction);
172       break;
173     case PROP_SOURCE_WIDGET:
174       selector->source = g_value_dup_object (value);
175       update_subjects (selector);
176       break;
177     case PROP_DEST_WIDGET:
178       selector->dest = g_value_dup_object (value);
179       update_subjects (selector);
180       break;
181     default:
182       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
183       break;
184     };
185 }
186
187
188 static void
189 psppire_selector_get_property (GObject         *object,
190                                guint            prop_id,
191                                GValue          *value,
192                                GParamSpec      *pspec)
193 {
194   PsppireSelector *selector = PSPPIRE_SELECTOR (object);
195
196   switch (prop_id)
197     {
198     case PROP_ORIENTATION:
199       g_value_set_enum (value, selector->orientation);
200       break;
201     case PROP_SOURCE_WIDGET:
202       g_value_take_object (value, selector->source);
203       break;
204     case PROP_DEST_WIDGET:
205       g_value_take_object (value, selector->dest);
206       break;
207     default:
208       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
209       break;
210     };
211 }
212
213
214
215 static void
216 psppire_selector_class_init (PsppireSelectorClass *class)
217 {
218   GObjectClass *object_class = G_OBJECT_CLASS (class);
219   GParamSpec *orientation_spec =
220     g_param_spec_enum ("orientation",
221                        "Orientation",
222                        "Where the selector is relative to its subjects",
223                        PSPPIRE_TYPE_SELECTOR_ORIENTATION,
224                        PSPPIRE_SELECT_SOURCE_BEFORE_DEST /* default value */,
225                        G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
226
227   GParamSpec *source_widget_spec = 
228     g_param_spec_object ("source-widget",
229                          "Source Widget",
230                          "The widget to be used as the source for this selector",
231                          GTK_TYPE_WIDGET,
232                          G_PARAM_READWRITE);
233
234   GParamSpec *dest_widget_spec = 
235     g_param_spec_object ("dest-widget",
236                          "Destination Widget",
237                          "The widget to be used as the destination for this selector",
238                          GTK_TYPE_WIDGET,
239                          G_PARAM_READWRITE);
240
241
242   object_class->set_property = psppire_selector_set_property;
243   object_class->get_property = psppire_selector_get_property;
244
245   g_object_class_install_property (object_class,
246                                    PROP_ORIENTATION,
247                                    orientation_spec);
248
249   g_object_class_install_property (object_class,
250                                    PROP_SOURCE_WIDGET,
251                                    source_widget_spec);
252
253   g_object_class_install_property (object_class,
254                                    PROP_DEST_WIDGET,
255                                    dest_widget_spec);
256
257   parent_class = g_type_class_peek_parent (class);
258
259   signals [SELECTED] =
260     g_signal_new ("selected",
261                   G_TYPE_FROM_CLASS (class),
262                   G_SIGNAL_RUN_FIRST,
263                   0,
264                   NULL, NULL,
265                   g_cclosure_marshal_VOID__VOID,
266                   G_TYPE_NONE,
267                   0);
268
269   signals [DE_SELECTED] =
270     g_signal_new ("de-selected",
271                   G_TYPE_FROM_CLASS (class),
272                   G_SIGNAL_RUN_FIRST,
273                   0,
274                   NULL, NULL,
275                   g_cclosure_marshal_VOID__VOID,
276                   G_TYPE_NONE,
277                   0);
278 }
279
280
281 static void
282 psppire_selector_base_init (PsppireSelectorClass *class)
283 {
284   GObjectClass *object_class = G_OBJECT_CLASS (class);
285
286   object_class->finalize = psppire_selector_finalize;
287   object_class->dispose = psppire_selector_dispose;
288
289   class->source_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
290 }
291
292
293
294 static void
295 psppire_selector_base_finalize(PsppireSelectorClass *class,
296                                 gpointer class_data)
297 {
298   g_hash_table_destroy (class->source_hash);
299 }
300
301
302
303 static void
304 psppire_selector_init (PsppireSelector *selector)
305 {
306   selector->select_user_data = NULL;
307   selector->select_items = NULL;
308   selector->allow_selection = NULL;
309   selector->filter = NULL;
310
311   selector->arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
312   selector->filtered_source = NULL;
313
314   selector->action = gtk_action_new ("select", NULL, NULL, "pspp-stock-select");
315
316   gtk_action_connect_proxy (selector->action, GTK_WIDGET (selector));
317
318   gtk_container_add (GTK_CONTAINER (selector), selector->arrow);
319
320   gtk_widget_show (selector->arrow);
321
322   g_signal_connect_swapped (selector->action, "activate", G_CALLBACK (on_activate), selector);
323
324   selector->selecting = FALSE;
325
326   selector->source = NULL;
327   selector->dest = NULL;
328   selector->dispose_has_run = FALSE;
329
330
331   selector->row_activate_id = 0;
332   selector->source_select_id  = 0;
333 }
334
335
336 GtkWidget*
337 psppire_selector_new (void)
338 {
339   return GTK_WIDGET (g_object_new (psppire_selector_get_type (), NULL));
340 }
341
342
343 static void
344 set_direction (PsppireSelector *selector, enum psppire_selector_dir d)
345 {
346   selector->direction = d;
347
348   /* FIXME: Need to reverse the arrow direction if an RTL locale is in
349      effect */
350   if ( d == PSPPIRE_SELECTOR_SOURCE_TO_DEST )
351     {
352       switch (selector->orientation)
353         {
354         case   PSPPIRE_SELECT_SOURCE_BEFORE_DEST:
355           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_RIGHT, NULL);
356           break;
357         case   PSPPIRE_SELECT_SOURCE_AFTER_DEST:
358           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_LEFT, NULL);
359           break;
360         case   PSPPIRE_SELECT_SOURCE_ABOVE_DEST:
361           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_DOWN, NULL);
362           break;
363         case   PSPPIRE_SELECT_SOURCE_BELOW_DEST:
364           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_UP, NULL);
365           break;
366         default:
367           g_assert_not_reached ();
368           break;
369         };
370     }
371   else
372     {
373       switch (selector->orientation)
374         {
375         case   PSPPIRE_SELECT_SOURCE_BEFORE_DEST:
376           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_LEFT, NULL);
377           break;
378         case   PSPPIRE_SELECT_SOURCE_AFTER_DEST:
379           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_RIGHT, NULL);
380           break;
381         case   PSPPIRE_SELECT_SOURCE_ABOVE_DEST:
382           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_UP, NULL);
383           break;
384         case   PSPPIRE_SELECT_SOURCE_BELOW_DEST:
385           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_DOWN, NULL);
386           break;
387         default:
388           g_assert_not_reached ();
389           break;
390         };
391
392     }
393 }
394
395 /* Callback for when the source selection changes */
396 static void
397 on_source_select (GtkTreeSelection *treeselection, gpointer data)
398 {
399   PsppireSelector *selector = data;
400
401   set_direction (selector, PSPPIRE_SELECTOR_SOURCE_TO_DEST);
402
403   if ( selector->allow_selection )
404     {
405       gtk_action_set_sensitive (selector->action,
406                                 selector->allow_selection (selector->source, selector->dest));
407     }
408   else if ( GTK_IS_ENTRY (selector->dest) )
409     {
410       gtk_action_set_sensitive (selector->action,
411                                 gtk_tree_selection_count_selected_rows
412                                 (treeselection) <= 1 );
413     }
414 }
415
416 /* Callback for when the destination treeview selection changes */
417 static void
418 on_dest_treeview_select (GtkTreeSelection *treeselection, gpointer data)
419 {
420   PsppireSelector *selector = data;
421
422   gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (selector->source)));
423
424   set_direction (selector, PSPPIRE_SELECTOR_DEST_TO_SOURCE);
425 }
426
427 /* Callback for source deselection, when the dest is GtkEntry */
428 static void
429 de_select_selection_entry (PsppireSelector *selector)
430 {
431   gtk_entry_set_text (GTK_ENTRY (selector->dest), "");
432 }
433
434 /* Callback for source deselection, when the dest is GtkTreeView */
435 static void
436 de_select_selection_tree_view (PsppireSelector *selector)
437 {
438   GList *item;
439
440   GtkTreeSelection* selection =
441     gtk_tree_view_get_selection ( GTK_TREE_VIEW (selector->dest));
442
443   GtkTreeModel *model =
444     gtk_tree_view_get_model (GTK_TREE_VIEW (selector->dest));
445
446   GList *selected_rows =
447     gtk_tree_selection_get_selected_rows (selection, NULL);
448
449   g_return_if_fail (selector->select_items);
450
451   /* Convert paths to RowRefs */
452   for (item = g_list_first (selected_rows);
453        item != NULL;
454        item = g_list_next (item))
455     {
456       GtkTreeRowReference* rowref;
457       GtkTreePath *path  = item->data;
458
459       rowref = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), path);
460
461       item->data = rowref ;
462       gtk_tree_path_free (path);
463     }
464
465   /* Remove each selected row from the dest widget */
466   for (item = g_list_first (selected_rows);
467        item != NULL;
468        item = g_list_next (item))
469     {
470       GtkTreeIter iter;
471       GtkTreeRowReference *rr = item->data;
472
473       GtkTreePath *path = gtk_tree_row_reference_get_path (rr);
474
475       gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
476
477       gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
478
479       gtk_tree_path_free (path);
480     }
481
482   /* Delete list of RowRefs and its contents */
483   g_list_foreach (selected_rows, (GFunc) gtk_tree_row_reference_free, NULL);
484   g_list_free (selected_rows);
485 }
486
487
488 /* Removes something from the DEST widget */
489 static void
490 de_select_selection (PsppireSelector *selector)
491 {
492   selector->selecting = TRUE;
493
494   if ( GTK_IS_TREE_VIEW (selector->dest ) )
495     de_select_selection_tree_view (selector);
496
497   else if ( GTK_IS_ENTRY (selector->dest))
498     de_select_selection_entry (selector);
499
500   else
501     g_assert_not_reached ();
502
503   selector->selecting = FALSE;
504
505   gtk_tree_model_filter_refilter (selector->filtered_source);
506
507   g_signal_emit (selector, signals [DE_SELECTED], 0);
508 }
509
510
511 /* Puts something into the DEST widget */
512 static void
513 select_selection (PsppireSelector *selector)
514 {
515   GList *item ;
516   GtkTreeSelection* selection =
517     gtk_tree_view_get_selection ( GTK_TREE_VIEW (selector->source));
518
519   GList *selected_rows =
520     gtk_tree_selection_get_selected_rows (selection, NULL);
521
522   GtkTreeModel *childmodel  = gtk_tree_model_filter_get_model
523     (selector->filtered_source);
524
525   g_return_if_fail (selector->select_items);
526
527   selector->selecting = TRUE;
528
529   for (item = g_list_first (selected_rows);
530        item != NULL;
531        item = g_list_next (item))
532     {
533       GtkTreeIter child_iter;
534       GtkTreeIter iter;
535       GtkTreePath *path  = item->data;
536
537       gtk_tree_model_get_iter (GTK_TREE_MODEL (selector->filtered_source),
538                                &iter, path);
539
540       gtk_tree_model_filter_convert_iter_to_child_iter
541         (selector->filtered_source,
542          &child_iter,
543          &iter);
544
545       selector->select_items (child_iter,
546                               selector->dest,
547                               childmodel,
548                               selector->select_user_data
549                               );
550     }
551
552   g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
553   g_list_free (selected_rows);
554
555   gtk_tree_model_filter_refilter (selector->filtered_source);
556
557   g_signal_emit (selector, signals [SELECTED], 0);
558
559   selector->selecting = FALSE;
560 }
561
562 /* Callback for when the source treeview is activated (double clicked) */
563 static void
564 on_row_activate (GtkTreeView       *tree_view,
565                  GtkTreePath       *path,
566                  GtkTreeViewColumn *column,
567                  gpointer           data)
568 {
569   PsppireSelector *selector  = data;
570
571   gtk_action_activate (selector->action);
572 }
573
574 /* Callback for when the selector button is clicked,
575    or other event which causes the selector's action to occur.
576  */
577 static void
578 on_activate (PsppireSelector *selector, gpointer data)
579 {
580   switch (selector->direction)
581     {
582     case PSPPIRE_SELECTOR_SOURCE_TO_DEST:
583       select_selection (selector);
584       break;
585     case PSPPIRE_SELECTOR_DEST_TO_SOURCE:
586       de_select_selection (selector);
587       break;
588     default:
589       g_assert_not_reached ();
590       break;
591     }
592 }
593
594 /* Default visibility filter for GtkTreeView DEST widget */
595 static gboolean
596 is_item_in_dest (GtkTreeModel *model, GtkTreeIter *iter,
597                  PsppireSelector *selector)
598 {
599   GtkTreeModel *dest_model;
600   GtkTreeIter dest_iter;
601   GtkTreeIter source_iter;
602   gint index;
603   GtkTreePath *path ;
604   GtkTreeModel *source_model;
605
606   if ( GTK_IS_TREE_MODEL_FILTER (model) )
607     {
608       source_model = gtk_tree_model_filter_get_model
609         (GTK_TREE_MODEL_FILTER (model));
610
611       gtk_tree_model_filter_convert_iter_to_child_iter
612         ( GTK_TREE_MODEL_FILTER (model),  &source_iter,  iter  );
613     }
614   else
615     {
616       source_model = model;
617       source_iter = *iter;
618     }
619
620   dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->dest));
621
622   path = gtk_tree_model_get_path (source_model, &source_iter);
623
624   index = *gtk_tree_path_get_indices (path);
625
626   gtk_tree_path_free (path);
627
628   if ( ! gtk_tree_model_get_iter_first (dest_model, &dest_iter) )
629     return FALSE;
630
631   do
632     {
633       int x;
634       GValue value = {0};
635       GValue int_value = {0};
636       gtk_tree_model_get_value (dest_model, &dest_iter, 0, &value);
637
638       g_value_init (&int_value, G_TYPE_INT);
639
640       g_value_transform (&value, &int_value);
641
642       x = g_value_get_int (&int_value);
643
644       g_value_unset (&int_value);
645       g_value_unset (&value);
646
647       if ( x == index )
648         return TRUE;
649     }
650   while (gtk_tree_model_iter_next (dest_model, &dest_iter));
651
652   return FALSE;
653 }
654
655 /* Visibility function for items in the SOURCE widget.
656    Returns TRUE iff *all* the selectors for which SOURCE is associated
657    are visible */
658 static gboolean
659 is_source_item_visible (GtkTreeModel *childmodel,
660                         GtkTreeIter *iter, gpointer data)
661 {
662   PsppireSelector *selector = data;
663   PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
664
665   GList *list = NULL;
666
667   list = g_hash_table_lookup (class->source_hash, selector->source);
668
669   while (list)
670     {
671       PsppireSelector *selector = list->data;
672
673       if ( selector->filter && selector->filter (childmodel, iter, selector))
674         return FALSE;
675
676       list = list->next;
677     }
678
679
680   return TRUE;
681 }
682
683 /* set the source widget to SOURCE */
684 static void
685 set_tree_view_source (PsppireSelector *selector,
686                       GtkTreeView *source)
687 {
688   GtkTreeSelection* selection ;
689   GList *list = NULL;
690
691   PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
692   
693   GtkTreeModel *model = gtk_tree_view_get_model (source);
694
695   if ( ! (list = g_hash_table_lookup (class->source_hash, source)))
696     {
697       selector->filtered_source =
698         GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (model, NULL));
699
700       gtk_tree_view_set_model (source,
701                                GTK_TREE_MODEL (selector->filtered_source));
702
703       list = g_list_append (list, selector);
704       g_hash_table_insert (class->source_hash, source, list);
705
706
707       gtk_tree_model_filter_set_visible_func (selector->filtered_source,
708                                               is_source_item_visible,
709                                               selector,
710                                               NULL);
711     }
712   else
713     {  /* Append this selector to the list and push the <source,list>
714           pair onto the hash table */
715
716       selector->filtered_source = GTK_TREE_MODEL_FILTER (model);
717
718       list = g_list_append (list, selector);
719       g_hash_table_replace (class->source_hash, source, list);
720     }
721
722   selection = gtk_tree_view_get_selection (source);
723
724   if ( selector->row_activate_id )
725     g_signal_handler_disconnect (source, selector->row_activate_id);
726
727   selector->row_activate_id =  
728     g_signal_connect (source, "row-activated", G_CALLBACK (on_row_activate), selector);
729
730   if ( selector->source_select_id )
731     g_signal_handler_disconnect (selection, selector->source_select_id);
732
733   selector->source_select_id = 
734     g_signal_connect (selection, "changed", G_CALLBACK (on_source_select), selector);
735 }
736
737
738 /*
739    Callback for when the destination treeview's data changes
740  */
741 static void
742 on_dest_data_change (GtkTreeModel *tree_model,
743                      GtkTreePath  *path,
744                      GtkTreeIter  *iter,
745                      gpointer      user_data)
746 {
747   PsppireSelector *selector = user_data;
748
749   if ( selector->selecting) return;
750
751   gtk_tree_model_filter_refilter (selector->filtered_source);
752 }
753
754
755 static void
756 on_dest_data_delete (GtkTreeModel *tree_model,
757                      GtkTreePath  *path,
758                      gpointer      user_data)
759 {
760   PsppireSelector *selector = user_data;
761
762   if ( selector->selecting ) return;
763
764   gtk_tree_model_filter_refilter (selector->filtered_source);
765 }
766
767
768 static void
769 xxx (PsppireSelector *selector)
770 {
771   GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->dest));
772
773   g_signal_connect (model, "row-changed", G_CALLBACK (on_dest_data_change),
774                     selector);
775
776   g_signal_connect (model, "row-deleted", G_CALLBACK (on_dest_data_delete),
777                     selector);
778 }
779
780 /* Set the destination widget to DEST */
781 static void
782 set_tree_view_dest (PsppireSelector *selector,
783                     GtkTreeView *dest)
784 {
785   GtkTreeSelection* selection = gtk_tree_view_get_selection (dest);
786
787
788   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
789
790   g_signal_connect (selection, "changed", G_CALLBACK (on_dest_treeview_select),
791                     selector);
792
793
794   g_signal_connect_swapped (dest, "notify::model",
795                             G_CALLBACK (xxx), selector);
796
797 }
798
799 /* Callback which causes the filter to be refiltered.
800    Called when the DEST GtkEntry is activated (Enter is pressed), or when it
801    looses focus.
802 */
803 static gboolean
804 refilter (PsppireSelector *selector)
805 {
806   gtk_tree_model_filter_refilter (selector->filtered_source);
807   return FALSE;
808 }
809
810 /* Callback for when the DEST GtkEntry is selected (clicked) */
811 static gboolean
812 on_entry_dest_select (GtkWidget *widget, GdkEventFocus *event, gpointer data)
813 {
814   PsppireSelector * selector = data;
815
816   gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (selector->source)));
817   set_direction (selector, PSPPIRE_SELECTOR_DEST_TO_SOURCE);
818
819   return FALSE;
820 }
821
822
823
824 /* Callback for when an item disappears from the source list.
825    By implication, this means that the item has been inserted into the
826    destination.
827  */
828 static void
829 on_row_deleted (PsppireSelector *selector)
830 {
831   g_signal_emit (selector, signals [SELECTED], 0);
832 }
833
834 /* Callback for when a new item appears in the source list.
835    By implication, this means that an item has been deleted from the
836    destination.
837  */
838 static void
839 on_row_inserted (PsppireSelector *selector)
840 {
841   g_signal_emit (selector, signals [DE_SELECTED], 0);
842 }
843
844
845
846 /* Set DEST to be the destination GtkEntry widget */
847 static void
848 set_entry_dest (PsppireSelector *selector,
849                 GtkEntry *dest)
850 {
851   g_signal_connect_swapped (dest, "activate", G_CALLBACK (refilter),
852                     selector);
853
854   g_signal_connect_swapped (dest, "changed", G_CALLBACK (refilter),
855                     selector);
856
857   g_signal_connect (dest, "focus-in-event", G_CALLBACK (on_entry_dest_select),
858                     selector);
859
860   g_signal_connect_swapped (dest, "focus-out-event", G_CALLBACK (refilter),
861                     selector);
862
863
864   g_signal_connect_swapped (selector->filtered_source, "row-deleted",
865                     G_CALLBACK (on_row_deleted), selector);
866
867   g_signal_connect_swapped (selector->filtered_source, "row-inserted",
868                     G_CALLBACK (on_row_inserted), selector);
869 }
870
871 static void
872 update_subjects (PsppireSelector *selector)
873 {
874   GtkTreeModel *model = NULL;
875
876   if ( NULL == selector->dest )
877     return;
878
879   if ( selector->filter == NULL)
880     {
881       if  (GTK_IS_TREE_VIEW (selector->dest))
882         selector->filter = is_item_in_dest;
883     }
884
885   if ( NULL == selector->source )
886     return;
887
888   g_signal_connect_swapped (selector->source, "notify::model",
889                             G_CALLBACK (update_subjects), selector);
890
891   model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->source));
892
893   if ( NULL == model)
894     return;
895
896
897   if ( GTK_IS_TREE_VIEW (selector->source))
898     set_tree_view_source (selector, GTK_TREE_VIEW (selector->source) );
899   else
900     g_error ("Unsupported source widget: %s", G_OBJECT_TYPE_NAME (selector->source));
901
902   if ( NULL == selector->dest)
903     ;
904   else if  ( GTK_IS_TREE_VIEW (selector->dest))
905     {
906       set_tree_view_dest (selector, GTK_TREE_VIEW (selector->dest));
907     }
908
909   else if ( GTK_IS_ENTRY (selector->dest))
910     set_entry_dest (selector, GTK_ENTRY (selector->dest));
911
912   else if (GTK_IS_TEXT_VIEW (selector->dest))
913     {
914       /* Nothing to be done */
915     }
916   else
917     g_error ("Unsupported destination widget: %s", G_OBJECT_TYPE_NAME (selector->dest));
918
919 }
920
921
922 /* Set FILTER_FUNC for this selector */
923 void
924 psppire_selector_set_filter_func (PsppireSelector *selector,
925                                   FilterItemsFunc *filter_func)
926 {
927   selector->filter = filter_func ;
928
929   if ( selector->filter == NULL)
930     {
931       if  (GTK_IS_TREE_VIEW (selector->dest))
932         selector->filter = is_item_in_dest;
933     }
934 }
935
936
937 /* Set SELECT_FUNC for this selector */
938 void
939 psppire_selector_set_select_func (PsppireSelector *selector,
940                                SelectItemsFunc *select_func,
941                                gpointer user_data)
942 {
943   selector->select_user_data = user_data;
944   selector->select_items = select_func;
945 }
946
947
948
949 void
950 psppire_selector_set_allow (PsppireSelector *selector, AllowSelectionFunc *allow)
951 {
952   selector->allow_selection = allow;
953 }
954
955
956
957 GType
958 psppire_selector_orientation_get_type (void)
959 {
960   static GType etype = 0;
961   if (etype == 0) {
962     static const GEnumValue values[] = {
963       { PSPPIRE_SELECT_SOURCE_BEFORE_DEST, "PSPPIRE_SELECT_SOURCE_BEFORE_DEST", "source before destination" },
964       { PSPPIRE_SELECT_SOURCE_AFTER_DEST, "PSPPIRE_SELECT_SOURCE_AFTER_DEST", "source after destination" },
965       { PSPPIRE_SELECT_SOURCE_ABOVE_DEST, "PSPPIRE_SELECT_SOURCE_ABOVE_DEST", "source above destination" },
966       { PSPPIRE_SELECT_SOURCE_BELOW_DEST, "PSPPIRE_SELECT_SOURCE_BELOW_DEST", "source below destination" },
967       { 0, NULL, NULL }
968     };
969     etype = g_enum_register_static (g_intern_static_string ("PsppireSelectorOrientation"), values);
970   }
971   return etype;
972 }