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