a5ac034c888e8cb36a75fcadbdbbc160e59c6b90
[pspp] / 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   if (selector->allow_selection && 
588       ! selector->allow_selection (selector->source, selector->dest))
589     return;
590
591   selector->selecting = TRUE;
592
593   for (item = g_list_first (selected_rows);
594        item != NULL;
595        item = g_list_next (item))
596     {
597       GtkTreeIter child_iter;
598       GtkTreeIter iter;
599       GtkTreePath *path  = item->data;
600
601       gtk_tree_model_get_iter (GTK_TREE_MODEL (selector->filtered_source),
602                                &iter, path);
603
604       gtk_tree_model_filter_convert_iter_to_child_iter
605         (selector->filtered_source,
606          &child_iter,
607          &iter);
608
609       selector->select_items (child_iter,
610                               selector->dest,
611                               childmodel,
612                               selector->select_user_data
613                               );
614     }
615
616   g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
617   g_list_free (selected_rows);
618
619   gtk_tree_model_filter_refilter (selector->filtered_source);
620
621   g_signal_emit (selector, signals [SELECTED], 0);
622
623   selector->selecting = FALSE;
624 }
625
626 /* Callback for when the selector button is clicked,
627    or other event which causes the selector's action to occur.
628  */
629 static void
630 on_click (GtkButton *b)
631 {
632   PsppireSelector *selector = PSPPIRE_SELECTOR (b);
633
634   switch (selector->direction)
635     {
636     case PSPPIRE_SELECTOR_SOURCE_TO_DEST:
637       select_selection (selector);
638       break;
639     case PSPPIRE_SELECTOR_DEST_TO_SOURCE:
640       de_select_selection (selector);
641       break;
642     default:
643       g_assert_not_reached ();
644       break;
645     }
646
647   if (GTK_BUTTON_CLASS (parent_class)->clicked)
648     GTK_BUTTON_CLASS (parent_class)->clicked (b);
649
650 }
651
652 static gboolean
653 is_item_in_dest (GtkTreeModel *model, GtkTreeIter *iter, PsppireSelector *selector)
654 {
655   gboolean result = FALSE;
656   GtkTreeIter source_iter;
657   GtkTreeModel *source_model;
658   GValue value = {0};
659
660   if (GTK_IS_TREE_MODEL_FILTER (model))
661     {
662       source_model = gtk_tree_model_filter_get_model
663         (GTK_TREE_MODEL_FILTER (model));
664
665       gtk_tree_model_filter_convert_iter_to_child_iter
666         (GTK_TREE_MODEL_FILTER (model),  &source_iter, iter);
667     }
668   else
669     {
670       source_model = model;
671       source_iter = *iter;
672     }
673
674   gtk_tree_model_get_value (source_model, &source_iter, DICT_TVM_COL_VAR, &value);
675
676   result = psppire_select_dest_widget_contains_var (PSPPIRE_SELECT_DEST_WIDGET (selector->dest),
677                                                     &value);
678
679   g_value_unset (&value);
680
681   return result;
682 }
683
684
685
686 /* Visibility function for items in the SOURCE widget.
687    Returns TRUE iff *all* the selectors for which SOURCE is associated
688    are visible */
689 static gboolean
690 is_source_item_visible (GtkTreeModel *childmodel,
691                         GtkTreeIter *iter, gpointer data)
692 {
693   PsppireSelector *selector = data;
694   PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
695
696   GList *list = NULL;
697
698   list = g_hash_table_lookup (class->source_hash, selector->source);
699
700   while (list)
701     {
702       PsppireSelector *selector = list->data;
703
704       if ( selector->filter && selector->filter (childmodel, iter, selector))
705         return FALSE;
706
707       list = list->next;
708     }
709
710
711   return TRUE;
712 }
713
714 /* set the source widget to SOURCE */
715 static void
716 set_tree_view_source (PsppireSelector *selector,
717                       GtkTreeView *source)
718 {
719   GList *list = NULL;
720
721   PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
722   
723   GtkTreeModel *model = gtk_tree_view_get_model (source);
724
725   if ( ! (list = g_hash_table_lookup (class->source_hash, source)))
726     {
727       selector->filtered_source =
728         GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (model, NULL));
729
730       gtk_tree_view_set_model (source,
731                                GTK_TREE_MODEL (selector->filtered_source));
732
733       list = g_list_append (list, selector);
734       g_hash_table_insert (class->source_hash, source, list);
735
736
737       gtk_tree_model_filter_set_visible_func (selector->filtered_source,
738                                               is_source_item_visible,
739                                               selector,
740                                               NULL);
741     }
742   else
743     {  /* Append this selector to the list and push the <source,list>
744           pair onto the hash table */
745
746       selector->filtered_source = GTK_TREE_MODEL_FILTER (model);
747
748       if ( NULL == g_list_find (list, selector) )
749         {
750           if ( selector->primary_requested )
751             list = g_list_prepend (list, selector);
752           else
753             list = g_list_append (list, selector);
754           g_hash_table_replace (class->source_hash, source, list);
755         }
756     }
757 }
758
759
760 /*
761    Callback for when the destination treeview's data changes
762  */
763 static void
764 on_dest_data_change (GtkTreeModel *tree_model,
765                      GtkTreePath  *path,
766                      GtkTreeIter  *iter,
767                      gpointer      user_data)
768 {
769   PsppireSelector *selector = user_data;
770
771   if ( selector->selecting) return;
772
773   gtk_tree_model_filter_refilter (selector->filtered_source);
774 }
775
776
777 static void
778 on_dest_data_delete (GtkTreeModel *tree_model,
779                      GtkTreePath  *path,
780                      gpointer      user_data)
781 {
782   PsppireSelector *selector = user_data;
783
784   if ( selector->selecting ) return;
785
786   gtk_tree_model_filter_refilter (selector->filtered_source);
787 }
788
789
790 static void
791 on_dest_model_changed (PsppireSelector *selector)
792 {
793   GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->dest));
794
795   g_signal_connect (model, "row-changed", G_CALLBACK (on_dest_data_change),
796                     selector);
797
798   g_signal_connect (model, "row-deleted", G_CALLBACK (on_dest_data_delete),
799                     selector);
800 }
801
802 /* Set the destination widget to DEST */
803 static void
804 set_tree_view_dest (PsppireSelector *selector,
805                     GtkTreeView *dest)
806 {
807   GtkTreeSelection* selection = gtk_tree_view_get_selection (dest);
808
809
810   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
811
812   g_signal_connect (selection, "changed", G_CALLBACK (on_dest_treeview_select),
813                     selector);
814
815   on_dest_model_changed (selector);
816   g_signal_connect_swapped (dest, "notify::model",
817                             G_CALLBACK (on_dest_model_changed), selector);
818 }
819
820
821 /* Callback which causes the filter to be refiltered.
822    Called when the DEST GtkEntry is activated (Enter is pressed), or when it
823    looses focus.
824 */
825 static gboolean
826 refilter (PsppireSelector *selector)
827 {
828   gtk_tree_model_filter_refilter (selector->filtered_source);
829   return FALSE;
830 }
831
832
833 /* Callback for when the DEST GtkEntry is selected (clicked) */
834 static gboolean
835 on_entry_dest_select (GtkWidget *widget, GdkEventFocus *event, gpointer data)
836 {
837   PsppireSelector * selector = data;
838
839   gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (selector->source)));
840   set_direction (selector, PSPPIRE_SELECTOR_DEST_TO_SOURCE);
841
842   return FALSE;
843 }
844
845
846 /* Callback for when an item disappears from the source list.
847    By implication, this means that the item has been inserted into the
848    destination.
849  */
850 static void
851 on_row_deleted (PsppireSelector *selector)
852 {
853   g_signal_emit (selector, signals [SELECTED], 0);
854 }
855
856 /* Callback for when a new item appears in the source list.
857    By implication, this means that an item has been deleted from the
858    destination.
859  */
860 static void
861 on_row_inserted (PsppireSelector *selector)
862 {
863   g_signal_emit (selector, signals [DE_SELECTED], 0);
864 }
865
866
867
868 /* Set DEST to be the destination GtkEntry widget */
869 static void
870 set_entry_dest (PsppireSelector *selector,
871                 GtkEntry *dest)
872 {
873   g_signal_connect_swapped (dest, "activate", G_CALLBACK (refilter),
874                     selector);
875
876   g_signal_connect_swapped (dest, "changed", G_CALLBACK (refilter),
877                     selector);
878
879   g_signal_connect (dest, "focus-in-event", G_CALLBACK (on_entry_dest_select),
880                     selector);
881
882   g_signal_connect_swapped (dest, "focus-out-event", G_CALLBACK (refilter),
883                     selector);
884
885
886   g_signal_connect_swapped (selector->filtered_source, "row-deleted",
887                     G_CALLBACK (on_row_deleted), selector);
888
889   g_signal_connect_swapped (selector->filtered_source, "row-inserted",
890                     G_CALLBACK (on_row_inserted), selector);
891 }
892
893 static void
894 set_default_filter (PsppireSelector *selector)
895 {
896   if ( selector->filter == NULL)
897     {
898       if  (GTK_IS_TREE_VIEW (selector->dest))
899         selector->filter = is_item_in_dest;
900     }
901 }
902
903 static void
904 update_subjects (PsppireSelector *selector)
905 {
906   GtkTreeModel *model = NULL;
907
908   if ( NULL == selector->dest )
909     return;
910
911   set_default_filter (selector);
912
913   if ( NULL == selector->source )
914     return;
915
916   g_signal_connect_swapped (selector->source, "notify::model",
917                             G_CALLBACK (update_subjects), selector);
918
919   model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->source));
920
921   if ( NULL == model)
922     return;
923
924
925   if ( GTK_IS_TREE_VIEW (selector->source))
926     set_tree_view_source (selector, GTK_TREE_VIEW (selector->source) );
927   else
928     g_error ("Unsupported source widget: %s", G_OBJECT_TYPE_NAME (selector->source));
929
930   if ( NULL == selector->dest)
931     ;
932   else if  ( GTK_IS_TREE_VIEW (selector->dest))
933     {
934       set_tree_view_dest (selector, GTK_TREE_VIEW (selector->dest));
935     }
936
937   else if ( GTK_IS_ENTRY (selector->dest))
938     set_entry_dest (selector, GTK_ENTRY (selector->dest));
939
940   else if (GTK_IS_TEXT_VIEW (selector->dest))
941     {
942       /* Nothing to be done */
943     }
944   else
945     g_error ("Unsupported destination widget: %s", G_OBJECT_TYPE_NAME (selector->dest));
946
947
948   /* FIXME: Remove this dependency */
949   if ( PSPPIRE_IS_DICT_VIEW (selector->source) )
950     {
951       GObjectClass *class = G_OBJECT_GET_CLASS (selector);
952       GType type = G_OBJECT_TYPE (selector->dest);
953
954       SelectItemsFunc *func  = 
955         g_hash_table_lookup (PSPPIRE_SELECTOR_CLASS (class)->default_selection_funcs, (gpointer) type);
956
957       if ( func )
958         psppire_selector_set_select_func (PSPPIRE_SELECTOR (selector),
959                                           func, NULL);
960     }
961 }
962
963
964 void
965 psppire_selector_set_default_selection_func (GType type, SelectItemsFunc *func)
966 {
967   GObjectClass *class = g_type_class_ref (PSPPIRE_SELECTOR_TYPE);
968
969   g_hash_table_insert (PSPPIRE_SELECTOR_CLASS (class)->default_selection_funcs, (gpointer) type, func);
970
971   g_type_class_unref (class);
972 }
973
974
975
976
977 /* Set FILTER_FUNC for this selector */
978 void
979 psppire_selector_set_filter_func (PsppireSelector *selector,
980                                   FilterItemsFunc *filter_func)
981 {
982   selector->filter = filter_func ;
983 }
984
985
986 /* Set SELECT_FUNC for this selector */
987 void
988 psppire_selector_set_select_func (PsppireSelector *selector,
989                                SelectItemsFunc *select_func,
990                                gpointer user_data)
991 {
992   selector->select_user_data = user_data;
993   selector->select_items = select_func;
994 }
995
996
997
998 void
999 psppire_selector_set_allow (PsppireSelector *selector, AllowSelectionFunc *allow)
1000 {
1001   selector->allow_selection = allow;
1002 }
1003
1004
1005 GType
1006 psppire_selector_orientation_get_type (void)
1007 {
1008   static GType etype = 0;
1009   if (etype == 0) {
1010     static const GEnumValue values[] = {
1011       { PSPPIRE_SELECT_SOURCE_BEFORE_DEST, "PSPPIRE_SELECT_SOURCE_BEFORE_DEST", "source before destination" },
1012       { PSPPIRE_SELECT_SOURCE_AFTER_DEST, "PSPPIRE_SELECT_SOURCE_AFTER_DEST", "source after destination" },
1013       { PSPPIRE_SELECT_SOURCE_ABOVE_DEST, "PSPPIRE_SELECT_SOURCE_ABOVE_DEST", "source above destination" },
1014       { PSPPIRE_SELECT_SOURCE_BELOW_DEST, "PSPPIRE_SELECT_SOURCE_BELOW_DEST", "source below destination" },
1015       { 0, NULL, NULL }
1016     };
1017     etype = g_enum_register_static (g_intern_static_string ("PsppireSelectorOrientation"), values);
1018   }
1019   return etype;
1020 }