Fix possible memory corruption when creating dialogs with selectors.
[pspp] / src / ui / gui / psppire-selector.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2009, 2010, 2012 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 GtkArrow, 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-var-view.h"
63 #include "psppire-dict.h"
64 #include "psppire-select-dest.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_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
443
444
445   gtk_container_add (GTK_CONTAINER (selector), selector->arrow);
446
447   gtk_widget_show (selector->arrow);
448
449   selector->selecting = FALSE;
450
451   selector->source = NULL;
452   selector->dest = NULL;
453   selector->dispose_has_run = FALSE;
454
455
456   selector->row_activate_id = 0;
457   selector->source_select_id  = 0;
458
459   selector->source_litem = NULL;
460 }
461
462
463 GtkWidget*
464 psppire_selector_new (void)
465 {
466   return GTK_WIDGET (g_object_new (psppire_selector_get_type (), NULL));
467 }
468
469
470 static void
471 set_direction (PsppireSelector *selector, enum psppire_selector_dir d)
472 {
473   selector->direction = d;
474
475   /* FIXME: Need to reverse the arrow direction if an RTL locale is in
476      effect */
477   if ( d == PSPPIRE_SELECTOR_SOURCE_TO_DEST )
478     {
479       switch (selector->orientation)
480         {
481         case   PSPPIRE_SELECT_SOURCE_BEFORE_DEST:
482           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_RIGHT, NULL);
483           break;
484         case   PSPPIRE_SELECT_SOURCE_AFTER_DEST:
485           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_LEFT, NULL);
486           break;
487         case   PSPPIRE_SELECT_SOURCE_ABOVE_DEST:
488           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_DOWN, NULL);
489           break;
490         case   PSPPIRE_SELECT_SOURCE_BELOW_DEST:
491           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_UP, NULL);
492           break;
493         default:
494           g_assert_not_reached ();
495           break;
496         };
497     }
498   else
499     {
500       switch (selector->orientation)
501         {
502         case   PSPPIRE_SELECT_SOURCE_BEFORE_DEST:
503           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_LEFT, NULL);
504           break;
505         case   PSPPIRE_SELECT_SOURCE_AFTER_DEST:
506           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_RIGHT, NULL);
507           break;
508         case   PSPPIRE_SELECT_SOURCE_ABOVE_DEST:
509           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_UP, NULL);
510           break;
511         case   PSPPIRE_SELECT_SOURCE_BELOW_DEST:
512           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_DOWN, NULL);
513           break;
514         default:
515           g_assert_not_reached ();
516           break;
517         };
518
519     }
520 }
521
522 /* Callback for when the destination treeview selection changes */
523 static void
524 on_dest_treeview_select (GtkTreeSelection *treeselection, gpointer data)
525 {
526   PsppireSelector *selector = data;
527
528   gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (selector->source)));
529
530   set_direction (selector, PSPPIRE_SELECTOR_DEST_TO_SOURCE);
531 }
532
533 /* Callback for source deselection, when the dest is GtkEntry */
534 static void
535 de_select_selection_entry (PsppireSelector *selector)
536 {
537   gtk_entry_set_text (GTK_ENTRY (selector->dest), "");
538 }
539
540 /* Callback for source deselection, when the dest is GtkTreeView */
541 static void
542 de_select_selection_tree_view (PsppireSelector *selector)
543 {
544   GList *item;
545
546   GtkTreeSelection* selection =
547     gtk_tree_view_get_selection ( GTK_TREE_VIEW (selector->dest));
548
549   GtkTreeModel *model =
550     gtk_tree_view_get_model (GTK_TREE_VIEW (selector->dest));
551
552   GList *selected_rows =
553     gtk_tree_selection_get_selected_rows (selection, NULL);
554
555   g_return_if_fail (selector->select_items);
556
557   /* Convert paths to RowRefs */
558   for (item = g_list_first (selected_rows);
559        item != NULL;
560        item = g_list_next (item))
561     {
562       GtkTreeRowReference* rowref;
563       GtkTreePath *path  = item->data;
564
565       rowref = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), path);
566
567       item->data = rowref ;
568       gtk_tree_path_free (path);
569     }
570
571   /* Remove each selected row from the dest widget */
572   for (item = g_list_first (selected_rows);
573        item != NULL;
574        item = g_list_next (item))
575     {
576       GtkTreeIter iter;
577       GtkTreeRowReference *rr = item->data;
578
579       GtkTreePath *path = gtk_tree_row_reference_get_path (rr);
580
581       gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
582
583       gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
584
585       gtk_tree_path_free (path);
586     }
587
588   /* Delete list of RowRefs and its contents */
589   g_list_foreach (selected_rows, (GFunc) gtk_tree_row_reference_free, NULL);
590   g_list_free (selected_rows);
591 }
592
593
594 /* Callback which causes the filter to be refiltered.
595    Called when the DEST GtkEntry is activated (Enter is pressed), or when it
596    looses focus.
597 */
598 static gboolean
599 refilter (PsppireSelector *selector)
600 {
601   GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->source));
602   gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
603   return FALSE;
604 }
605
606 /* Removes something from the DEST widget */
607 static void
608 de_select_selection (PsppireSelector *selector)
609 {
610   selector->selecting = TRUE;
611
612   if ( GTK_IS_TREE_VIEW (selector->dest ) )
613     de_select_selection_tree_view (selector);
614
615   else if ( GTK_IS_ENTRY (selector->dest))
616     de_select_selection_entry (selector);
617
618   else
619     g_assert_not_reached ();
620
621   selector->selecting = FALSE;
622
623   refilter (selector);
624
625   g_signal_emit (selector, signals [DE_SELECTED], 0);
626 }
627
628
629 /* Puts something into the DEST widget */
630 static void
631 select_selection (PsppireSelector *selector)
632 {
633   GList *item ;
634   GtkTreeSelection* selection =
635     gtk_tree_view_get_selection ( GTK_TREE_VIEW (selector->source));
636
637   GList *selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
638
639   GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->source));
640
641   GtkTreeModel *childmodel = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model));
642
643   g_return_if_fail (selector->select_items);
644
645   if (selector->allow_selection && 
646       ! selector->allow_selection (selector->source, selector->dest))
647     return;
648
649   selector->selecting = TRUE;
650
651   for (item = g_list_first (selected_rows);
652        item != NULL;
653        item = g_list_next (item))
654     {
655       GtkTreeIter child_iter;
656       GtkTreeIter iter;
657       GtkTreePath *path  = item->data;
658
659       g_return_if_fail (model);
660
661       gtk_tree_model_get_iter (model, &iter, path);
662
663       gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model),
664                                                         &child_iter, &iter);
665       selector->select_items (child_iter,
666                               selector->dest,
667                               childmodel,
668                               selector->select_user_data
669                               );
670     }
671
672   g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
673   g_list_free (selected_rows);
674
675   refilter (selector);
676
677   g_signal_emit (selector, signals [SELECTED], 0);
678
679   selector->selecting = FALSE;
680 }
681
682 /* Callback for when the selector button is clicked,
683    or other event which causes the selector's action to occur.
684  */
685 static void
686 on_click (GtkButton *b)
687 {
688   PsppireSelector *selector = PSPPIRE_SELECTOR (b);
689
690   switch (selector->direction)
691     {
692     case PSPPIRE_SELECTOR_SOURCE_TO_DEST:
693       select_selection (selector);
694       break;
695     case PSPPIRE_SELECTOR_DEST_TO_SOURCE:
696       de_select_selection (selector);
697       break;
698     default:
699       g_assert_not_reached ();
700       break;
701     }
702
703   if (GTK_BUTTON_CLASS (parent_class)->clicked)
704     GTK_BUTTON_CLASS (parent_class)->clicked (b);
705 }
706
707 static gboolean
708 is_item_in_dest (GtkTreeModel *model, GtkTreeIter *iter, PsppireSelector *selector)
709 {
710   gboolean result = FALSE;
711   GtkTreeIter source_iter;
712   GtkTreeModel *source_model;
713   GValue value = {0};
714
715   if (GTK_IS_TREE_MODEL_FILTER (model))
716     {
717       source_model = gtk_tree_model_filter_get_model
718         (GTK_TREE_MODEL_FILTER (model));
719
720       gtk_tree_model_filter_convert_iter_to_child_iter
721         (GTK_TREE_MODEL_FILTER (model),  &source_iter, iter);
722     }
723   else
724     {
725       source_model = model;
726       source_iter = *iter;
727     }
728
729   gtk_tree_model_get_value (source_model, &source_iter, DICT_TVM_COL_VAR, &value);
730
731   result = psppire_select_dest_widget_contains_var (PSPPIRE_SELECT_DEST_WIDGET (selector->dest),
732                                                     &value);
733
734   g_value_unset (&value);
735
736   return result;
737 }
738
739
740
741 /* Visibility function for items in the SOURCE widget.
742    Returns TRUE iff *all* the selectors for which SOURCE is associated
743    are visible */
744 static gboolean
745 is_source_item_visible (GtkTreeModel *childmodel,
746                         GtkTreeIter *iter, gpointer data)
747 {
748   PsppireSelector *selector = data;
749   PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
750
751   GList *list = NULL;
752
753   list = g_hash_table_lookup (class->source_hash, selector->source);
754
755   while (list)
756     {
757       PsppireSelector *selector = list->data;
758
759       if ( selector->filter && selector->filter (childmodel, iter, selector))
760         return FALSE;
761
762       list = list->next;
763     }
764
765
766   return TRUE;
767 }
768
769 /* set the source widget to SOURCE */
770 static void
771 set_tree_view_source (PsppireSelector *selector)
772 {
773   GList *list = NULL;
774
775   PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
776   
777   if ( ! (list = g_hash_table_lookup (class->source_hash, selector->source)))
778     {
779       /* Base case:  This widget is currently not the source of 
780          any selector.  Create a hash entry and make this selector
781          the first selector in the list */
782
783       list = g_list_append (list, selector);
784       g_hash_table_insert (class->source_hash, selector->source, list);
785
786       /* Save the list item so that it can be removed later */
787       selector->source_litem = list;
788     }
789   else
790     {  /* Append this selector to the list and push the <source,list>
791           pair onto the hash table */
792
793       if ( NULL == g_list_find (list, selector) )
794         {
795           if ( selector->primary_requested )
796             {
797               list = g_list_prepend (list, selector);
798               selector->source_litem = list;
799             }
800           else
801             {
802               list = g_list_append (list, selector);
803               selector->source_litem = g_list_last (list);
804             }
805           g_hash_table_replace (class->source_hash, selector->source, list);
806         }
807     }
808 }
809
810
811
812 /* This function is a callback which occurs when the
813    SOURCE's model has changed */
814 static void
815 update_model (
816               GtkTreeView *source,
817               GParamSpec *psp,
818               PsppireSelector *selector
819               )
820 {
821   GtkTreeModel *model = gtk_tree_view_get_model (source);
822
823   g_assert (source == GTK_TREE_VIEW (selector->source));
824
825   if (model && (model == g_object_get_data (G_OBJECT (source), "model-copy")))
826     return;
827
828   if (model != NULL) 
829     {      
830       GtkTreeModel *new_model = gtk_tree_model_filter_new (model, NULL); 
831
832       g_object_set_data (G_OBJECT (source), "model-copy", new_model);  
833
834       gtk_tree_view_set_model (source, new_model);
835   
836       gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (new_model),
837                                               is_source_item_visible,
838                                               selector,
839                                               NULL);
840
841       g_signal_connect_swapped (new_model,
842                                 "row-deleted",
843                                 G_CALLBACK (on_row_deleted), selector);
844
845       g_signal_connect_swapped (new_model,
846                                 "row-inserted",
847                                 G_CALLBACK (on_row_inserted), selector);
848
849       g_object_unref (new_model);
850     }
851 }
852
853
854
855 /*
856    Callback for when the destination treeview's data changes
857  */
858 static void
859 on_dest_data_change (GtkTreeModel *tree_model,
860                      GtkTreePath  *path,
861                      GtkTreeIter  *iter,
862                      gpointer      user_data)
863 {
864   PsppireSelector *selector = user_data;
865
866   if ( selector->selecting) return;
867
868   refilter (selector);
869 }
870
871
872 static void
873 on_dest_data_delete (GtkTreeModel *tree_model,
874                      GtkTreePath  *path,
875                      gpointer      user_data)
876 {
877   PsppireSelector *selector = user_data;
878
879   if ( selector->selecting ) return;
880
881   refilter (selector);
882 }
883
884
885 static void
886 on_dest_model_changed (PsppireSelector *selector)
887 {
888   GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->dest));
889
890   if ( model ) 
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 }
899
900 /* Set the destination widget to DEST */
901 static void
902 set_tree_view_dest (PsppireSelector *selector,
903                     GtkTreeView *dest)
904 {
905   GtkTreeSelection* selection = gtk_tree_view_get_selection (dest);
906
907
908   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
909
910   g_signal_connect (selection, "changed", G_CALLBACK (on_dest_treeview_select),
911                     selector);
912
913   on_dest_model_changed (selector);
914   g_signal_connect_swapped (dest, "notify::model",
915                             G_CALLBACK (on_dest_model_changed), selector);
916 }
917
918
919
920
921 /* Callback for when the DEST GtkEntry is selected (clicked) */
922 static gboolean
923 on_entry_dest_select (GtkWidget *widget, GdkEventFocus *event, gpointer data)
924 {
925   PsppireSelector * selector = data;
926
927   gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (selector->source)));
928   set_direction (selector, PSPPIRE_SELECTOR_DEST_TO_SOURCE);
929
930   return FALSE;
931 }
932
933
934
935 /* Set DEST to be the destination GtkEntry widget */
936 static void
937 set_entry_dest (PsppireSelector *selector,
938                 GtkEntry *dest)
939 {
940   g_signal_connect_swapped (dest, "activate", G_CALLBACK (refilter),
941                     selector);
942
943   g_signal_connect_swapped (dest, "changed", G_CALLBACK (refilter),
944                     selector);
945
946   g_signal_connect (dest, "focus-in-event", G_CALLBACK (on_entry_dest_select),
947                     selector);
948
949   g_signal_connect_swapped (dest, "focus-out-event", G_CALLBACK (refilter),
950                     selector);
951
952
953 }
954
955 static void
956 set_default_filter (PsppireSelector *selector)
957 {
958   if ( selector->filter == NULL)
959     {
960       if  (GTK_IS_TREE_VIEW (selector->dest))
961         selector->filter = is_item_in_dest;
962     }
963 }
964
965
966 static void
967 update_subjects (PsppireSelector *selector)
968 {
969   if ( NULL == selector->dest )
970     return;
971
972   set_default_filter (selector);
973
974   if ( NULL == selector->source )
975     return;
976
977   if ( GTK_IS_TREE_VIEW (selector->source))
978     {
979       set_tree_view_source (selector);
980
981       g_signal_connect (selector->source, "notify::model", 
982                               G_CALLBACK (update_model), selector); 
983
984       update_model (GTK_TREE_VIEW (selector->source), 0, selector);
985     }
986   else
987     g_error ("Unsupported source widget: %s", G_OBJECT_TYPE_NAME (selector->source));
988
989   if ( NULL == selector->dest)
990     ;
991   else if  ( GTK_IS_TREE_VIEW (selector->dest))
992     {
993       set_tree_view_dest (selector, GTK_TREE_VIEW (selector->dest));
994     }
995
996   else if ( GTK_IS_ENTRY (selector->dest))
997     set_entry_dest (selector, GTK_ENTRY (selector->dest));
998
999   else if (GTK_IS_TEXT_VIEW (selector->dest))
1000     {
1001       /* Nothing to be done */
1002     }
1003   else
1004     g_error ("Unsupported destination widget: %s", G_OBJECT_TYPE_NAME (selector->dest));
1005
1006
1007   /* FIXME: Remove this dependency */
1008   if ( PSPPIRE_IS_DICT_VIEW (selector->source) )
1009     {
1010       GObjectClass *class = G_OBJECT_GET_CLASS (selector);
1011       GType type = G_OBJECT_TYPE (selector->dest);
1012
1013       SelectItemsFunc *func  = 
1014         g_hash_table_lookup (PSPPIRE_SELECTOR_CLASS (class)->default_selection_funcs, (gpointer) type);
1015
1016       if ( func )
1017         psppire_selector_set_select_func (PSPPIRE_SELECTOR (selector),
1018                                           func, NULL);
1019     }
1020 }
1021
1022
1023 void
1024 psppire_selector_set_default_selection_func (GType type, SelectItemsFunc *func)
1025 {
1026   GObjectClass *class = g_type_class_ref (PSPPIRE_SELECTOR_TYPE);
1027
1028   g_hash_table_insert (PSPPIRE_SELECTOR_CLASS (class)->default_selection_funcs, (gpointer) type, func);
1029
1030   g_type_class_unref (class);
1031 }
1032
1033
1034
1035
1036 /* Set FILTER_FUNC for this selector */
1037 void
1038 psppire_selector_set_filter_func (PsppireSelector *selector,
1039                                   FilterItemsFunc *filter_func)
1040 {
1041   selector->filter = filter_func ;
1042 }
1043
1044
1045 /* Set SELECT_FUNC for this selector */
1046 void
1047 psppire_selector_set_select_func (PsppireSelector *selector,
1048                                SelectItemsFunc *select_func,
1049                                gpointer user_data)
1050 {
1051   selector->select_user_data = user_data;
1052   selector->select_items = select_func;
1053 }
1054
1055
1056
1057 void
1058 psppire_selector_set_allow (PsppireSelector *selector, AllowSelectionFunc *allow)
1059 {
1060   selector->allow_selection = allow;
1061 }
1062
1063
1064 GType
1065 psppire_selector_orientation_get_type (void)
1066 {
1067   static GType etype = 0;
1068   if (etype == 0) {
1069     static const GEnumValue values[] = {
1070       { PSPPIRE_SELECT_SOURCE_BEFORE_DEST, "PSPPIRE_SELECT_SOURCE_BEFORE_DEST", "source before destination" },
1071       { PSPPIRE_SELECT_SOURCE_AFTER_DEST, "PSPPIRE_SELECT_SOURCE_AFTER_DEST", "source after destination" },
1072       { PSPPIRE_SELECT_SOURCE_ABOVE_DEST, "PSPPIRE_SELECT_SOURCE_ABOVE_DEST", "source above destination" },
1073       { PSPPIRE_SELECT_SOURCE_BELOW_DEST, "PSPPIRE_SELECT_SOURCE_BELOW_DEST", "source below destination" },
1074       { 0, NULL, NULL }
1075     };
1076     etype = g_enum_register_static (g_intern_static_string ("PsppireSelectorOrientation"), values);
1077   }
1078   return etype;
1079 }