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