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