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