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