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