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