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