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