val-labs-dialog: Improve keyboard interface.
[pspp.git] / src / ui / gui / val-labs-dialog.c
blobcc1b65494c9fbee387b4c7a934b011c7f8014128
1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2005, 2009, 2010, 2011, 2012, 2015, 2016 Free Software Foundation
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.
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.
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/>. */
18 /* This module describes the behaviour of the Value Labels dialog box,
19 used for input of the value labels in the variable sheet */
21 #include <config.h>
23 #include "ui/gui/val-labs-dialog.h"
25 #include <string.h>
27 #include "data/value-labels.h"
28 #include "data/format.h"
29 #include "libpspp/i18n.h"
30 #include "ui/gui/builder-wrapper.h"
31 #include "ui/gui/helper.h"
33 #include <gettext.h>
34 #define _(msgid) gettext (msgid)
35 #define N_(msgid) msgid
37 static GObject *psppire_val_labs_dialog_constructor (GType type, guint,
38 GObjectConstructParam *);
39 static void psppire_val_labs_dialog_finalize (GObject *);
41 G_DEFINE_TYPE (PsppireValLabsDialog,
42 psppire_val_labs_dialog,
43 PSPPIRE_TYPE_DIALOG);
44 enum
46 PROP_0,
47 PROP_VARIABLE,
48 PROP_VALUE_LABELS
51 static void do_change (PsppireValLabsDialog *);
53 static void
54 psppire_val_labs_dialog_set_property (GObject *object,
55 guint prop_id,
56 const GValue *value,
57 GParamSpec *pspec)
59 PsppireValLabsDialog *obj = PSPPIRE_VAL_LABS_DIALOG (object);
61 switch (prop_id)
63 case PROP_VARIABLE:
64 psppire_val_labs_dialog_set_variable (obj, g_value_get_pointer (value));
65 break;
66 case PROP_VALUE_LABELS:
67 default:
68 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
69 break;
73 static void
74 psppire_val_labs_dialog_get_property (GObject *object,
75 guint prop_id,
76 GValue *value,
77 GParamSpec *pspec)
79 PsppireValLabsDialog *obj = PSPPIRE_VAL_LABS_DIALOG (object);
81 switch (prop_id)
83 case PROP_VALUE_LABELS:
84 g_value_set_pointer (value, obj->labs);
85 break;
86 case PROP_VARIABLE:
87 default:
88 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
89 break;
93 static void
94 psppire_val_labs_dialog_class_init (PsppireValLabsDialogClass *class)
96 GObjectClass *gobject_class;
97 gobject_class = G_OBJECT_CLASS (class);
99 gobject_class->constructor = psppire_val_labs_dialog_constructor;
100 gobject_class->finalize = psppire_val_labs_dialog_finalize;
101 gobject_class->set_property = psppire_val_labs_dialog_set_property;
102 gobject_class->get_property = psppire_val_labs_dialog_get_property;
104 g_object_class_install_property (
105 gobject_class, PROP_VARIABLE,
106 g_param_spec_pointer ("variable",
107 "Variable",
108 "Variable whose value labels are to be edited. The "
109 "variable's print format and encoding are also used "
110 "for editing.",
111 G_PARAM_WRITABLE));
113 g_object_class_install_property (
114 gobject_class, PROP_VALUE_LABELS,
115 g_param_spec_pointer ("value-labels",
116 "Value Labels",
117 "Edited value labels.",
118 G_PARAM_READABLE));
121 static void
122 psppire_val_labs_dialog_init (PsppireValLabsDialog *obj)
124 /* We do all of our work on widgets in the constructor function, because that
125 runs after the construction properties have been set. Otherwise
126 PsppireDialog's "orientation" property hasn't been set and therefore we
127 have no box to populate. */
128 obj->labs = val_labs_create (0);
131 static void
132 psppire_val_labs_dialog_finalize (GObject *obj)
134 PsppireValLabsDialog *dialog = PSPPIRE_VAL_LABS_DIALOG (obj);
136 val_labs_destroy (dialog->labs);
137 g_free (dialog->encoding);
139 G_OBJECT_CLASS (psppire_val_labs_dialog_parent_class)->finalize (obj);
142 PsppireValLabsDialog *
143 psppire_val_labs_dialog_new (const struct variable *var)
145 return PSPPIRE_VAL_LABS_DIALOG (
146 g_object_new (PSPPIRE_TYPE_VAL_LABS_DIALOG,
147 "variable", var,
148 NULL));
151 struct val_labs *
152 psppire_val_labs_dialog_run (GtkWindow *parent_window,
153 const struct variable *var)
155 PsppireValLabsDialog *dialog;
156 struct val_labs *labs;
158 dialog = psppire_val_labs_dialog_new (var);
159 gtk_window_set_transient_for (GTK_WINDOW (dialog), parent_window);
160 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
161 gtk_widget_show (GTK_WIDGET (dialog));
163 labs = (psppire_dialog_run (PSPPIRE_DIALOG (dialog)) == GTK_RESPONSE_OK
164 ? val_labs_clone (psppire_val_labs_dialog_get_value_labels (dialog))
165 : NULL);
167 gtk_widget_destroy (GTK_WIDGET (dialog));
169 return labs;
172 /* This callback occurs when the text in the label entry box
173 is changed */
174 static void
175 on_label_entry_change (GtkEntry *entry, gpointer data)
177 union value v;
178 const gchar *text ;
179 PsppireValLabsDialog *dialog = data;
180 g_assert (dialog->labs);
182 text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
184 text_to_value__ (text, &dialog->format, dialog->encoding, &v);
186 if (val_labs_find (dialog->labs, &v))
188 gtk_widget_set_sensitive (dialog->change_button, TRUE);
189 gtk_widget_set_sensitive (dialog->add_button, FALSE);
191 else
193 gtk_widget_set_sensitive (dialog->change_button, FALSE);
194 gtk_widget_set_sensitive (dialog->add_button, TRUE);
197 value_destroy (&v, val_labs_get_width (dialog->labs));
200 /* This callback occurs when Enter is pressed in the label entry box. */
201 static void
202 on_label_entry_activate (GtkEntry *entry, gpointer data)
204 PsppireValLabsDialog *dialog = data;
205 do_change (dialog);
208 /* Return the value-label pair currently selected in the dialog box */
210 /* Set the TREEVIEW list cursor to the item which has the value VAL */
211 static void
212 select_treeview_from_value (GtkTreeView *treeview, union value *val)
214 GtkTreePath *path ;
217 We do this with a linear search through the model --- hardly
218 efficient, but the list is short ... */
219 GtkTreeIter iter;
221 GtkTreeModel * model = gtk_tree_view_get_model (treeview);
223 gboolean success;
224 for (success = gtk_tree_model_get_iter_first (model, &iter);
225 success;
226 success = gtk_tree_model_iter_next (model, &iter))
228 union value v;
229 GValue gvalue = {0};
231 gtk_tree_model_get_value (model, &iter, 1, &gvalue);
233 v.f = g_value_get_double (&gvalue);
235 if ( 0 == memcmp (&v, val, sizeof (union value)))
237 break;
241 path = gtk_tree_model_get_path (model, &iter);
242 if ( path )
244 gtk_tree_view_set_cursor (treeview, path, 0, 0);
245 gtk_tree_path_free (path);
251 /* This callback occurs when the text in the value entry box is
252 changed */
253 static void
254 on_value_entry_change (GtkEntry *entry, gpointer data)
256 const char *s;
258 PsppireValLabsDialog *dialog = data;
260 const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
262 union value v;
263 text_to_value__ (text, &dialog->format, dialog->encoding, &v);
265 g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
266 dialog->change_handler_id);
268 gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),"");
271 if ( (s = val_labs_find (dialog->labs, &v)) )
273 gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), s);
274 gtk_widget_set_sensitive (dialog->add_button, FALSE);
275 gtk_widget_set_sensitive (dialog->remove_button, TRUE);
276 select_treeview_from_value (GTK_TREE_VIEW (dialog->treeview), &v);
278 else
280 gtk_widget_set_sensitive (dialog->remove_button, FALSE);
281 gtk_widget_set_sensitive (dialog->add_button, TRUE);
284 g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
285 dialog->change_handler_id);
287 value_destroy (&v, val_labs_get_width (dialog->labs));
290 /* This callback occurs when Enter is pressed in the value entry box. */
291 static void
292 on_value_entry_activate (GtkEntry *entry, gpointer data)
294 PsppireValLabsDialog *dialog = data;
296 gtk_widget_grab_focus (dialog->label_entry);
299 static gboolean
300 get_selected_tuple (PsppireValLabsDialog *dialog,
301 union value *valuep, const char **label)
303 GtkTreeView *treeview = GTK_TREE_VIEW (dialog->treeview);
305 GtkTreeIter iter ;
306 GValue the_value = {0};
307 union value value;
309 GtkTreeSelection* sel = gtk_tree_view_get_selection (treeview);
311 GtkTreeModel * model = gtk_tree_view_get_model (treeview);
313 if (! gtk_tree_selection_get_selected (sel, &model, &iter))
314 return FALSE;
316 gtk_tree_model_get_value (model, &iter, 1, &the_value);
318 value.f = g_value_get_double (&the_value);
319 g_value_unset (&the_value);
321 if (valuep != NULL)
322 *valuep = value;
323 if (label != NULL)
325 struct val_lab *vl = val_labs_lookup (dialog->labs, &value);
326 if (vl != NULL)
327 *label = val_lab_get_escaped_label (vl);
330 return TRUE;
334 static void repopulate_dialog (PsppireValLabsDialog *dialog);
336 /* Callback which occurs when the "Change" button is clicked */
337 static void
338 on_change (GtkWidget *w, gpointer data)
340 PsppireValLabsDialog *dialog = data;
341 do_change (dialog);
344 static void
345 do_change (PsppireValLabsDialog *dialog)
347 const gchar *val_text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
349 union value v;
351 text_to_value__ (val_text, &dialog->format, dialog->encoding, &v);
353 val_labs_replace (dialog->labs, &v,
354 gtk_entry_get_text (GTK_ENTRY (dialog->label_entry)));
356 gtk_widget_set_sensitive (dialog->change_button, FALSE);
358 repopulate_dialog (dialog);
359 gtk_widget_grab_focus (dialog->value_entry);
361 value_destroy (&v, val_labs_get_width (dialog->labs));
364 /* Callback which occurs when the "Add" button is clicked */
365 static void
366 on_add (GtkWidget *w, gpointer data)
368 PsppireValLabsDialog *dialog = data;
370 union value v;
372 const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
374 text_to_value__ (text, &dialog->format, dialog->encoding, &v);
376 if (val_labs_add (dialog->labs, &v,
377 gtk_entry_get_text
378 ( GTK_ENTRY (dialog->label_entry)) ) )
380 gtk_widget_set_sensitive (dialog->add_button, FALSE);
382 repopulate_dialog (dialog);
383 gtk_widget_grab_focus (dialog->value_entry);
386 value_destroy (&v, val_labs_get_width (dialog->labs));
389 /* Callback which occurs when the "Remove" button is clicked */
390 static void
391 on_remove (GtkWidget *w, gpointer data)
393 PsppireValLabsDialog *dialog = data;
395 union value value;
396 struct val_lab *vl;
398 if (! get_selected_tuple (dialog, &value, NULL))
399 return;
401 vl = val_labs_lookup (dialog->labs, &value);
402 if (vl != NULL)
403 val_labs_remove (dialog->labs, vl);
405 repopulate_dialog (dialog);
406 gtk_widget_grab_focus (dialog->value_entry);
408 gtk_widget_set_sensitive (dialog->remove_button, FALSE);
413 /* Callback which occurs when a line item is selected in the list of
414 value--label pairs.*/
415 static void
416 on_select_row (GtkTreeView *treeview, gpointer data)
418 PsppireValLabsDialog *dialog = data;
420 union value value;
421 const char *label = NULL;
423 gchar *text;
425 if (! get_selected_tuple (dialog, &value, &label))
426 return;
428 text = value_to_text__ (value, &dialog->format, dialog->encoding);
430 g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
431 dialog->value_handler_id);
433 gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), text);
435 g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
436 dialog->value_handler_id);
437 g_free (text);
439 g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
440 dialog->change_handler_id);
443 gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),
444 label);
446 g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
447 dialog->change_handler_id);
449 gtk_widget_set_sensitive (dialog->remove_button, TRUE);
450 gtk_widget_set_sensitive (dialog->change_button, FALSE);
454 /* Create a new dialog box
455 (there should normally be only one)*/
456 static GObject *
457 psppire_val_labs_dialog_constructor (GType type,
458 guint n_properties,
459 GObjectConstructParam *properties)
461 PsppireValLabsDialog *dialog;
462 GtkTreeViewColumn *column;
464 GtkCellRenderer *renderer ;
466 GtkBuilder *xml = builder_new ("val-labs-dialog.ui");
468 GtkContainer *content_area;
469 GObject *obj;
471 obj = G_OBJECT_CLASS (psppire_val_labs_dialog_parent_class)->constructor (
472 type, n_properties, properties);
473 dialog = PSPPIRE_VAL_LABS_DIALOG (obj);
475 content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
476 gtk_container_add (GTK_CONTAINER (content_area),
477 get_widget_assert (xml, "val-labs-dialog"));
479 dialog->value_entry = get_widget_assert (xml,"value_entry");
480 dialog->label_entry = get_widget_assert (xml,"label_entry");
482 dialog->add_button = get_widget_assert (xml, "val_labs_add");
483 dialog->remove_button = get_widget_assert (xml, "val_labs_remove");
484 dialog->change_button = get_widget_assert (xml, "val_labs_change");
486 dialog->treeview = get_widget_assert (xml,"treeview1");
488 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (dialog->treeview), FALSE);
490 renderer = gtk_cell_renderer_text_new ();
492 column = gtk_tree_view_column_new_with_attributes ("Title",
493 renderer,
494 "text",
496 NULL);
498 gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->treeview), column);
500 dialog->change_handler_id =
501 g_signal_connect (dialog->label_entry,
502 "changed",
503 G_CALLBACK (on_label_entry_change), dialog);
504 g_signal_connect (dialog->label_entry, "activate",
505 G_CALLBACK (on_label_entry_activate), dialog);
507 dialog->value_handler_id =
508 g_signal_connect (dialog->value_entry,
509 "changed",
510 G_CALLBACK (on_value_entry_change), dialog);
511 g_signal_connect (dialog->value_entry, "activate",
512 G_CALLBACK (on_value_entry_activate), dialog);
514 g_signal_connect (dialog->change_button,
515 "clicked",
516 G_CALLBACK (on_change), dialog);
519 g_signal_connect (dialog->treeview, "cursor-changed",
520 G_CALLBACK (on_select_row), dialog);
522 g_signal_connect (dialog->remove_button, "clicked",
523 G_CALLBACK (on_remove), dialog);
525 g_signal_connect (dialog->add_button, "clicked",
526 G_CALLBACK (on_add), dialog);
528 dialog->labs = NULL;
530 g_object_unref (xml);
532 return obj;
536 /* Populate the components of the dialog box, from the 'labs' member
537 variable */
538 static void
539 repopulate_dialog (PsppireValLabsDialog *dialog)
541 const struct val_lab **labels;
542 size_t n_labels;
543 size_t i;
545 GtkTreeIter iter;
547 GtkListStore *list_store = gtk_list_store_new (2,
548 G_TYPE_STRING,
549 G_TYPE_DOUBLE);
551 g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
552 dialog->change_handler_id);
553 g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
554 dialog->value_handler_id);
556 gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), "");
557 gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), "");
559 g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
560 dialog->value_handler_id);
561 g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
562 dialog->change_handler_id);
564 labels = val_labs_sorted (dialog->labs);
565 n_labels = val_labs_count (dialog->labs);
566 for (i = 0; i < n_labels; i++)
568 const struct val_lab *vl = labels[i];
570 gchar *const vstr =
571 value_to_text__ (vl->value, &dialog->format, dialog->encoding);
573 gchar *const text = g_strdup_printf (_("%s = `%s'"), vstr,
574 val_lab_get_escaped_label (vl));
576 gtk_list_store_append (list_store, &iter);
577 gtk_list_store_set (list_store, &iter,
578 0, text,
579 1, vl->value.f,
580 -1);
582 g_free (text);
583 g_free (vstr);
585 free (labels);
587 gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->treeview),
588 GTK_TREE_MODEL (list_store));
590 g_object_unref (list_store);
594 void
595 psppire_val_labs_dialog_set_variable (PsppireValLabsDialog *dialog,
596 const struct variable *var)
598 val_labs_destroy (dialog->labs);
599 dialog->labs = NULL;
601 g_free (dialog->encoding);
602 dialog->encoding = NULL;
604 if (var != NULL)
606 dialog->labs = val_labs_clone (var_get_value_labels (var));
607 dialog->encoding = g_strdup (var_get_encoding (var));
608 dialog->format = *var_get_print_format (var);
610 else
611 dialog->format = F_8_0;
613 if (dialog->labs == NULL)
614 dialog->labs = val_labs_create (var_get_width (var));
616 repopulate_dialog (dialog);
619 const struct val_labs *
620 psppire_val_labs_dialog_get_value_labels (const PsppireValLabsDialog *dialog)
622 return dialog->labs;