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