a041fd5a05cae1ef23356a206f563f2a4961fc6e
[pspp-builds.git] / src / ui / gui / psppire-selector.c
1 /*
2    PSPPIRE --- A Graphical User Interface for PSPP
3    Copyright (C) 2007  Free Software Foundation
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, USA. */
19
20 /*
21   This module provides a widget, PsppireSelector derived from
22   GtkButton.
23
24   It contains a GtkArrow, and is used for selecting objects from a
25   GtkTreeView and putting them into a destination widget (often
26   another GtkTreeView).  Typically this is used in psppire for
27   selecting variables, thus:
28
29
30   +----------------------------------------------------------+
31   |                                                          |
32   |     Source Widget                       Dest Widget      |
33   |   +----------------+                 +----------------+  |
34   |   | Variable0      |                 | Variable2      |  |
35   |   | Variable1      |                 |                |  |
36   |   | Variable3      |                 |                |  |
37   |   |                |    Selector     |                |  |
38   |   |                |                 |                |  |
39   |   |                |    +------+     |                |  |
40   |   |                |    | |\   |     |                |  |
41   |   |                |    | | \  |     |                |  |
42   |   |                |    | | /  |     |                |  |
43   |   |                |    | |/   |     |                |  |
44   |   |                |    +------+     |                |  |
45   |   |                |                 |                |  |
46   |   |                |                 |                |  |
47   |   |                |                 |                |  |
48   |   |                |                 |                |  |
49   |   +----------------+                 +----------------+  |
50   |                                                          |
51   +----------------------------------------------------------+
52
53   The Source Widget is always a GtkTreeView.  The Dest Widget may be a
54   GtkTreeView or a GtkEntry (other destination widgets may be
55   supported in the future).
56
57   Widgets may be source to more than one PsppireSelector.
58 */
59
60
61 #include <config.h>
62
63 #include <gtk/gtksignal.h>
64 #include <gtk/gtkbutton.h>
65 #include <gtk/gtkentry.h>
66
67 #include "psppire-selector.h"
68
69 #include <gtk/gtktreeview.h>
70 #include <gtk/gtktreeselection.h>
71 #include <gtk/gtktextview.h>
72 #include <gtk/gtkwidget.h>
73
74 static void psppire_selector_base_finalize (PsppireSelectorClass *, gpointer);
75 static void psppire_selector_base_init     (PsppireSelectorClass *class);
76 static void psppire_selector_class_init    (PsppireSelectorClass *class);
77 static void psppire_selector_init          (PsppireSelector      *selector);
78
79
80 static void set_direction (PsppireSelector *, enum psppire_selector_dir);
81
82
83 enum  {SELECTED,    /* Emitted when an item is inserted into dest */
84        DE_SELECTED, /* Emitted when an item is removed from dest */
85        n_SIGNALS};
86
87 static guint signals [n_SIGNALS];
88
89
90 GType
91 psppire_selector_get_type (void)
92 {
93   static GType psppire_selector_type = 0;
94
95   if (!psppire_selector_type)
96     {
97       static const GTypeInfo psppire_selector_info =
98       {
99         sizeof (PsppireSelectorClass),
100         (GBaseInitFunc) psppire_selector_base_init,
101         (GBaseFinalizeFunc) psppire_selector_base_finalize,
102         (GClassInitFunc)psppire_selector_class_init,
103         (GClassFinalizeFunc) NULL,
104         NULL,
105         sizeof (PsppireSelector),
106         0,
107         (GInstanceInitFunc) psppire_selector_init,
108       };
109
110       psppire_selector_type =
111         g_type_register_static (GTK_TYPE_BUTTON, "PsppireSelector",
112                                 &psppire_selector_info, 0);
113     }
114
115   return psppire_selector_type;
116 }
117
118
119 static void
120 psppire_selector_finalize (GObject *object)
121 {
122 }
123
124 /* Properties */
125 enum
126 {
127   PROP_0,
128   PROP_ORIENTATION
129 };
130
131
132 static void
133 psppire_selector_set_property (GObject         *object,
134                                guint            prop_id,
135                                const GValue    *value,
136                                GParamSpec      *pspec)
137 {
138   PsppireSelector *selector = PSPPIRE_SELECTOR (object);
139
140   switch (prop_id)
141     {
142     case PROP_ORIENTATION:
143       selector->orientation = g_value_get_enum (value);
144       set_direction (selector, selector->direction);
145       break;
146     default:
147       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
148       break;
149     };
150 }
151
152
153 static void
154 psppire_selector_get_property (GObject         *object,
155                                guint            prop_id,
156                                GValue          *value,
157                                GParamSpec      *pspec)
158 {
159   PsppireSelector *selector = PSPPIRE_SELECTOR (object);
160
161   switch (prop_id)
162     {
163     case PROP_ORIENTATION:
164       g_value_set_enum (value, selector->orientation);
165       break;
166     default:
167       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
168       break;
169     };
170 }
171
172
173
174 static void
175 psppire_selector_class_init (PsppireSelectorClass *class)
176 {
177   GObjectClass *object_class = G_OBJECT_CLASS (class);
178   GParamSpec *orientation_spec =
179     g_param_spec_enum ("orientation",
180                        "Orientation",
181                        "Where the selector is relative to its subjects",
182                        G_TYPE_PSPPIRE_SELECTOR_ORIENTATION,
183                        PSPPIRE_SELECT_SOURCE_BEFORE_DEST /* default value */,
184                        G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
185
186
187   object_class->set_property = psppire_selector_set_property;
188   object_class->get_property = psppire_selector_get_property;
189
190   g_object_class_install_property (object_class,
191                                    PROP_ORIENTATION,
192                                    orientation_spec);
193
194   signals [SELECTED] =
195     g_signal_new ("selected",
196                   G_TYPE_FROM_CLASS (class),
197                   G_SIGNAL_RUN_FIRST,
198                   0,
199                   NULL, NULL,
200                   g_cclosure_marshal_VOID__VOID,
201                   G_TYPE_NONE,
202                   0);
203
204   signals [DE_SELECTED] =
205     g_signal_new ("de-selected",
206                   G_TYPE_FROM_CLASS (class),
207                   G_SIGNAL_RUN_FIRST,
208                   0,
209                   NULL, NULL,
210                   g_cclosure_marshal_VOID__VOID,
211                   G_TYPE_NONE,
212                   0);
213 }
214
215
216 static void
217 psppire_selector_base_init (PsppireSelectorClass *class)
218 {
219   GObjectClass *object_class = G_OBJECT_CLASS (class);
220
221   object_class->finalize = psppire_selector_finalize;
222
223   class->source_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
224 }
225
226
227
228 static void
229 psppire_selector_base_finalize(PsppireSelectorClass *class,
230                                 gpointer class_data)
231 {
232   g_hash_table_destroy (class->source_hash);
233 }
234
235
236 static void
237 psppire_selector_init (PsppireSelector *selector)
238 {
239   selector->arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
240   selector->filtered_source = NULL;
241
242   gtk_container_add (GTK_CONTAINER (selector), selector->arrow);
243
244   gtk_widget_show (selector->arrow);
245
246   selector->selecting = FALSE;
247 }
248
249
250 GtkWidget*
251 psppire_selector_new (void)
252 {
253   return GTK_WIDGET (g_object_new (psppire_selector_get_type (), NULL));
254 }
255
256
257 static void
258 set_direction (PsppireSelector *selector, enum psppire_selector_dir d)
259 {
260   selector->direction = d;
261
262   /* FIXME: Need to reverse the arrow direction if an RTL locale is in
263      effect */
264   if ( d == PSPPIRE_SELECTOR_SOURCE_TO_DEST )
265     {
266       switch (selector->orientation)
267         {
268         case   PSPPIRE_SELECT_SOURCE_BEFORE_DEST:
269           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_RIGHT, NULL);
270           break;
271         case   PSPPIRE_SELECT_SOURCE_AFTER_DEST:
272           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_LEFT, NULL);
273           break;
274         case   PSPPIRE_SELECT_SOURCE_ABOVE_DEST:
275           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_DOWN, NULL);
276           break;
277         case   PSPPIRE_SELECT_SOURCE_BELOW_DEST:
278           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_UP, NULL);
279           break;
280         default:
281           g_assert_not_reached ();
282           break;
283         };
284     }
285   else
286     {
287       switch (selector->orientation)
288         {
289         case   PSPPIRE_SELECT_SOURCE_BEFORE_DEST:
290           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_LEFT, NULL);
291           break;
292         case   PSPPIRE_SELECT_SOURCE_AFTER_DEST:
293           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_RIGHT, NULL);
294           break;
295         case   PSPPIRE_SELECT_SOURCE_ABOVE_DEST:
296           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_UP, NULL);
297           break;
298         case   PSPPIRE_SELECT_SOURCE_BELOW_DEST:
299           g_object_set (selector->arrow, "arrow-type", GTK_ARROW_DOWN, NULL);
300           break;
301         default:
302           g_assert_not_reached ();
303           break;
304         };
305
306     }
307 }
308
309 /* Callback for when the source selection changes */
310 static void
311 on_source_select (GtkTreeSelection *treeselection, gpointer data)
312 {
313   PsppireSelector *selector = data;
314
315   set_direction (selector, PSPPIRE_SELECTOR_SOURCE_TO_DEST);
316
317   if ( GTK_IS_ENTRY (selector->dest) )
318     {
319       gtk_widget_set_sensitive (GTK_WIDGET (selector),
320                                 gtk_tree_selection_count_selected_rows
321                                 (treeselection) <= 1 );
322     }
323 }
324
325 /* Callback for when the destination treeview selection changes */
326 static void
327 on_dest_treeview_select (GtkTreeSelection *treeselection, gpointer data)
328 {
329   PsppireSelector *selector = data;
330
331   set_direction (selector, PSPPIRE_SELECTOR_DEST_TO_SOURCE);
332 }
333
334 /* Callback for source deselection, when the dest is GtkEntry */
335 static void
336 de_select_selection_entry (PsppireSelector *selector)
337 {
338   gtk_entry_set_text (GTK_ENTRY (selector->dest), "");
339 }
340
341 /* Callback for source deselection, when the dest is GtkTreeView */
342 static void
343 de_select_selection_tree_view (PsppireSelector *selector)
344 {
345   GList *item;
346
347   GtkTreeSelection* selection =
348     gtk_tree_view_get_selection ( GTK_TREE_VIEW (selector->dest));
349
350   GtkTreeModel *model =
351     gtk_tree_view_get_model (GTK_TREE_VIEW (selector->dest));
352
353   GList *selected_rows =
354     gtk_tree_selection_get_selected_rows (selection, NULL);
355
356   g_return_if_fail (selector->select_items);
357
358   /* Convert paths to RowRefs */
359   for (item = g_list_first (selected_rows);
360        item != NULL;
361        item = g_list_next (item))
362     {
363       GtkTreeRowReference* rowref;
364       GtkTreePath *path  = item->data;
365
366       rowref = gtk_tree_row_reference_new (GTK_TREE_MODEL (model), path);
367
368       item->data = rowref ;
369       gtk_tree_path_free (path);
370     }
371
372   /* Remove each selected row from the dest widget */
373   for (item = g_list_first (selected_rows);
374        item != NULL;
375        item = g_list_next (item))
376     {
377       GtkTreeIter iter;
378       GtkTreeRowReference *rr = item->data;
379
380       GtkTreePath *path = gtk_tree_row_reference_get_path (rr);
381
382       gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
383
384       gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
385
386       gtk_tree_path_free (path);
387     }
388
389   /* Delete list of RowRefs and its contents */
390   g_list_foreach (selected_rows, (GFunc) gtk_tree_row_reference_free, NULL);
391   g_list_free (selected_rows);
392 }
393
394
395 /* Removes something from the DEST widget */
396 static void
397 de_select_selection (PsppireSelector *selector)
398 {
399   selector->selecting = TRUE;
400
401   if ( GTK_IS_TREE_VIEW (selector->dest ) )
402     de_select_selection_tree_view (selector);
403
404   else if ( GTK_IS_ENTRY (selector->dest))
405     de_select_selection_entry (selector);
406
407   else
408     g_assert_not_reached ();
409
410   selector->selecting = FALSE;
411
412   gtk_tree_model_filter_refilter (selector->filtered_source);
413
414   g_signal_emit (selector, signals [DE_SELECTED], 0);
415 }
416
417
418 /* Puts something into the DEST widget */
419 static void
420 select_selection (PsppireSelector *selector)
421 {
422   GList *item ;
423   GtkTreeSelection* selection =
424     gtk_tree_view_get_selection ( GTK_TREE_VIEW (selector->source));
425
426   GList *selected_rows =
427     gtk_tree_selection_get_selected_rows (selection, NULL);
428
429   GtkTreeModel *childmodel  = gtk_tree_model_filter_get_model
430     (selector->filtered_source);
431
432   g_return_if_fail (selector->select_items);
433
434   selector->selecting = TRUE;
435
436   for (item = g_list_first (selected_rows);
437        item != NULL;
438        item = g_list_next (item))
439     {
440       GtkTreeIter child_iter;
441       GtkTreeIter iter;
442       GtkTreePath *path  = item->data;
443
444       gtk_tree_model_get_iter (GTK_TREE_MODEL (selector->filtered_source),
445                                &iter, path);
446
447       gtk_tree_model_filter_convert_iter_to_child_iter
448         (selector->filtered_source,
449          &child_iter,
450          &iter);
451
452       selector->select_items (child_iter,
453                               selector->dest,
454                               childmodel);
455     }
456
457   g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
458   g_list_free (selected_rows);
459
460   gtk_tree_model_filter_refilter (selector->filtered_source);
461
462   g_signal_emit (selector, signals [SELECTED], 0);
463
464   selector->selecting = FALSE;
465 }
466
467 /* Callback for when the source treeview is activated (double clicked) */
468 static void
469 on_row_activate (GtkTreeView       *tree_view,
470                  GtkTreePath       *path,
471                  GtkTreeViewColumn *column,
472                  gpointer           data)
473 {
474   PsppireSelector *selector  = data;
475
476   select_selection (selector);
477 }
478
479 /* Callback for when the selector button is clicked */
480 static void
481 on_click (PsppireSelector *selector, gpointer data)
482 {
483   switch (selector->direction)
484     {
485     case PSPPIRE_SELECTOR_SOURCE_TO_DEST:
486       select_selection (selector);
487       break;
488     case PSPPIRE_SELECTOR_DEST_TO_SOURCE:
489       de_select_selection (selector);
490       break;
491     default:
492       g_assert_not_reached ();
493       break;
494     }
495 }
496
497 /* Default visibility filter for GtkTreeView DEST widget */
498 static gboolean
499 is_item_in_dest (GtkTreeModel *model, GtkTreeIter *iter,
500                  PsppireSelector *selector)
501 {
502   GtkTreeModel *dest_model;
503   GtkTreeIter dest_iter;
504   GtkTreeIter source_iter;
505   gint index;
506   GtkTreePath *path ;
507   GtkTreeModel *source_model;
508
509   if ( GTK_IS_TREE_MODEL_FILTER (model) )
510     {
511       source_model = gtk_tree_model_filter_get_model
512         (GTK_TREE_MODEL_FILTER (model));
513
514       gtk_tree_model_filter_convert_iter_to_child_iter
515         ( GTK_TREE_MODEL_FILTER (model),  &source_iter,  iter  );
516     }
517   else
518     {
519       source_model = model;
520       source_iter = *iter;
521     }
522
523   dest_model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector->dest));
524
525   path = gtk_tree_model_get_path (source_model, &source_iter);
526
527   index = *gtk_tree_path_get_indices (path);
528
529   gtk_tree_path_free (path);
530
531   if ( ! gtk_tree_model_get_iter_first (dest_model, &dest_iter) )
532     return FALSE;
533
534   do
535     {
536       GValue value = {0};
537       gtk_tree_model_get_value (dest_model, &dest_iter, 0, &value);
538
539       if ( g_value_get_int (&value) == index)
540         return TRUE;
541     }
542   while (gtk_tree_model_iter_next (dest_model, &dest_iter));
543
544   return FALSE;
545 }
546
547 /* Visibility function for items in the SOURCE widget.
548    Returns TRUE iff *all* the selectors for which SOURCE is associated
549    are visible */
550 static gboolean
551 is_source_item_visible (GtkTreeModel *childmodel,
552                         GtkTreeIter *iter, gpointer data)
553 {
554   PsppireSelector *selector = data;
555   PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
556
557   GList *list = NULL;
558
559   list = g_hash_table_lookup (class->source_hash, selector->source);
560
561   while (list)
562     {
563       PsppireSelector *selector = list->data;
564
565       if ( selector->filter && selector->filter (childmodel, iter, selector))
566         return FALSE;
567
568       list = list->next;
569     }
570
571
572   return TRUE;
573 }
574
575 /* set the source widget to SOURCE */
576 static void
577 set_tree_view_source (PsppireSelector *selector,
578                       GtkTreeView *source)
579 {
580   GtkTreeSelection* selection ;
581   GList *list = NULL;
582
583   PsppireSelectorClass *class = g_type_class_peek (PSPPIRE_SELECTOR_TYPE);
584
585   if ( ! (list = g_hash_table_lookup (class->source_hash, source)))
586     {
587       selector->filtered_source =
588         GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new
589                                (gtk_tree_view_get_model (source),  NULL));
590
591       gtk_tree_view_set_model (source, NULL);
592
593       gtk_tree_view_set_model (source,
594                                GTK_TREE_MODEL (selector->filtered_source));
595
596       list = g_list_append (list, selector);
597       g_hash_table_insert (class->source_hash, source, list);
598
599
600       gtk_tree_model_filter_set_visible_func (selector->filtered_source,
601                                               is_source_item_visible,
602                                               selector,
603                                               NULL);
604     }
605   else
606     {  /* Append this selector to the list and push the <source,list>
607           pair onto the hash table */
608
609       selector->filtered_source = GTK_TREE_MODEL_FILTER (
610         gtk_tree_view_get_model (source));
611
612       list = g_list_append (list, selector);
613       g_hash_table_replace (class->source_hash, source, list);
614     }
615
616   selection = gtk_tree_view_get_selection (source);
617
618   g_signal_connect (source, "row-activated", G_CALLBACK (on_row_activate),
619                     selector);
620
621   g_signal_connect (selection, "changed", G_CALLBACK (on_source_select),
622                     selector);
623 }
624
625
626 /*
627    Callback for when the destination treeview's data changes
628  */
629 static void
630 on_dest_data_change (GtkTreeModel *tree_model,
631                      GtkTreePath  *path,
632                      GtkTreeIter  *iter,
633                      gpointer      user_data)
634 {
635   PsppireSelector *selector = user_data;
636
637   if ( selector->selecting) return;
638
639   gtk_tree_model_filter_refilter (selector->filtered_source);
640 }
641
642
643 static void
644 on_dest_data_delete (GtkTreeModel *tree_model,
645                      GtkTreePath  *path,
646                      gpointer      user_data)
647 {
648   PsppireSelector *selector = user_data;
649
650   if ( selector->selecting ) return;
651
652   gtk_tree_model_filter_refilter (selector->filtered_source);
653 }
654
655
656
657
658 /* Set the destination widget to DEST */
659 static void
660 set_tree_view_dest (PsppireSelector *selector,
661                     GtkTreeView *dest)
662 {
663   GtkTreeSelection* selection = gtk_tree_view_get_selection (dest);
664   GtkTreeModel *model = gtk_tree_view_get_model (dest);
665
666   gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
667
668   g_signal_connect (selection, "changed", G_CALLBACK (on_dest_treeview_select),
669                     selector);
670
671   g_signal_connect (model, "row-changed", G_CALLBACK (on_dest_data_change),
672                       selector);
673
674   g_signal_connect (model, "row-deleted", G_CALLBACK (on_dest_data_delete),
675                       selector);
676
677 }
678
679 /* Callback for when the DEST GtkEntry is activated (Enter is pressed) */
680 static void
681 on_entry_activate (GtkEntry *w, gpointer data)
682 {
683   PsppireSelector * selector = data;
684
685   gtk_tree_model_filter_refilter (selector->filtered_source);
686 }
687
688 /* Callback for when the DEST GtkEntry is selected (clicked) */
689 static gboolean
690 on_entry_dest_select (GtkWidget *widget, GdkEventFocus *event, gpointer data)
691 {
692   PsppireSelector * selector = data;
693
694   set_direction (selector, PSPPIRE_SELECTOR_DEST_TO_SOURCE);
695
696   return FALSE;
697 }
698
699 /* Set DEST to be the destination GtkEntry widget */
700 static void
701 set_entry_dest (PsppireSelector *selector,
702                 GtkEntry *dest)
703 {
704   g_signal_connect (dest, "activate", G_CALLBACK (on_entry_activate),
705                     selector);
706
707   g_signal_connect (dest, "focus-in-event", G_CALLBACK (on_entry_dest_select),
708                     selector);
709 }
710
711
712 /* Set SOURCE and DEST for this selector, and
713    set SELECT_FUNC and FILTER_FUNC */
714 void
715 psppire_selector_set_subjects (PsppireSelector *selector,
716                                GtkWidget *source,
717                                GtkWidget *dest,
718                                SelectItemsFunc *select_func,
719                                FilterItemsFunc *filter_func )
720 {
721   g_assert(selector);
722
723   selector->filter = filter_func ;
724
725   selector->source = source;
726   selector->dest = dest;
727
728   if ( filter_func == NULL)
729     {
730       if  (GTK_IS_TREE_VIEW (dest))
731         selector->filter = is_item_in_dest;
732     }
733
734   g_signal_connect (selector, "clicked", G_CALLBACK (on_click), NULL);
735
736   if ( GTK_IS_TREE_VIEW (source))
737     set_tree_view_source (selector, GTK_TREE_VIEW (source) );
738   else
739     g_error ("Unsupported source widget: %s", G_OBJECT_TYPE_NAME (source));
740
741   g_assert ( GTK_IS_TREE_MODEL_FILTER (selector->filtered_source));
742
743   if  ( GTK_IS_TREE_VIEW (dest))
744     set_tree_view_dest (selector, GTK_TREE_VIEW (dest));
745
746   else if ( GTK_IS_ENTRY (dest))
747     set_entry_dest (selector, GTK_ENTRY (dest));
748
749   else if (GTK_IS_TEXT_VIEW (dest))
750     {
751       /* Nothing to be done */
752     }
753
754   else
755     g_error ("Unsupported destination widget: %s", G_OBJECT_TYPE_NAME (dest));
756
757   selector->select_items = select_func;
758 }
759
760
761
762 GType
763 psppire_selector_orientation_get_type (void)
764 {
765   static GType etype = 0;
766   if (etype == 0) {
767     static const GEnumValue values[] = {
768       { PSPPIRE_SELECT_SOURCE_BEFORE_DEST, "PSPPIRE_SELECT_SOURCE_BEFORE_DEST", "source before destination" },
769       { PSPPIRE_SELECT_SOURCE_AFTER_DEST, "PSPPIRE_SELECT_SOURCE_AFTER_DEST", "source after destination" },
770       { PSPPIRE_SELECT_SOURCE_ABOVE_DEST, "PSPPIRE_SELECT_SOURCE_ABOVE_DEST", "source above destination" },
771       { PSPPIRE_SELECT_SOURCE_BELOW_DEST, "PSPPIRE_SELECT_SOURCE_BELOW_DEST", "source below destination" },
772       { 0, NULL, NULL }
773     };
774     etype = g_enum_register_static (g_intern_static_string ("PsppireSelectorOrientation"), values);
775   }
776   return etype;
777 }