fixed goto case dialog
[pspp] / src / ui / gui / val-labs-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2005, 2009, 2010, 2011, 2012, 2015, 2016  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 describes the behaviour of the Value Labels dialog box,
19     used for input of the value labels in the variable sheet */
20
21 #include <config.h>
22
23 #include "ui/gui/val-labs-dialog.h"
24
25 #include <string.h>
26
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"
32
33 #include <gettext.h>
34 #define _(msgid) gettext (msgid)
35 #define N_(msgid) msgid
36
37 static GObject *psppire_val_labs_dialog_constructor (GType type, guint,
38                                                      GObjectConstructParam *);
39 static void psppire_val_labs_dialog_finalize (GObject *);
40
41 G_DEFINE_TYPE (PsppireValLabsDialog,
42                psppire_val_labs_dialog,
43                PSPPIRE_TYPE_DIALOG);
44 enum
45   {
46     PROP_0,
47     PROP_VARIABLE,
48     PROP_VALUE_LABELS
49   };
50
51 static void do_change (PsppireValLabsDialog *);
52
53 static void
54 psppire_val_labs_dialog_set_property (GObject      *object,
55                                       guint         prop_id,
56                                       const GValue *value,
57                                       GParamSpec   *pspec)
58 {
59   PsppireValLabsDialog *obj = PSPPIRE_VAL_LABS_DIALOG (object);
60
61   switch (prop_id)
62     {
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;
70     }
71 }
72
73 static void
74 psppire_val_labs_dialog_get_property (GObject      *object,
75                                       guint         prop_id,
76                                       GValue       *value,
77                                       GParamSpec   *pspec)
78 {
79   PsppireValLabsDialog *obj = PSPPIRE_VAL_LABS_DIALOG (object);
80
81   switch (prop_id)
82     {
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;
90     }
91 }
92
93 static void
94 psppire_val_labs_dialog_class_init (PsppireValLabsDialogClass *class)
95 {
96   GObjectClass *gobject_class;
97   gobject_class = G_OBJECT_CLASS (class);
98
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;
103
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));
112
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));
119 }
120
121 static void
122 psppire_val_labs_dialog_init (PsppireValLabsDialog *obj)
123 {
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);
129 }
130
131 static void
132 psppire_val_labs_dialog_finalize (GObject *obj)
133 {
134   PsppireValLabsDialog *dialog = PSPPIRE_VAL_LABS_DIALOG (obj);
135
136   val_labs_destroy (dialog->labs);
137   g_free (dialog->encoding);
138
139   G_OBJECT_CLASS (psppire_val_labs_dialog_parent_class)->finalize (obj);
140 }
141
142 PsppireValLabsDialog *
143 psppire_val_labs_dialog_new (const struct variable *var)
144 {
145   return PSPPIRE_VAL_LABS_DIALOG (
146     g_object_new (PSPPIRE_TYPE_VAL_LABS_DIALOG,
147                   "variable", var,
148                   NULL));
149 }
150
151 struct val_labs *
152 psppire_val_labs_dialog_run (GtkWindow *parent_window,
153                              const struct variable *var)
154 {
155   PsppireValLabsDialog *dialog;
156   struct val_labs *labs;
157
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));
162
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);
166
167   gtk_widget_destroy (GTK_WIDGET (dialog));
168
169   return labs;
170 }
171
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)
176 {
177   union value v;
178   const gchar *text ;
179   PsppireValLabsDialog *dialog = data;
180   g_assert (dialog->labs);
181
182   text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
183
184   text_to_value__ (text, &dialog->format, dialog->encoding, &v);
185
186   if (val_labs_find (dialog->labs, &v))
187     {
188       gtk_widget_set_sensitive (dialog->change_button, TRUE);
189       gtk_widget_set_sensitive (dialog->add_button, FALSE);
190     }
191   else
192     {
193       gtk_widget_set_sensitive (dialog->change_button, FALSE);
194       gtk_widget_set_sensitive (dialog->add_button, TRUE);
195     }
196
197   value_destroy (&v, val_labs_get_width (dialog->labs));
198 }
199
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)
203 {
204   PsppireValLabsDialog *dialog = data;
205   do_change (dialog);
206 }
207
208 /* Return the value-label pair currently selected in the dialog box  */
209
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)
213 {
214   GtkTreePath *path ;
215
216   /*
217     We do this with a linear search through the model --- hardly
218     efficient, but the list is short ... */
219   GtkTreeIter iter;
220
221   GtkTreeModel * model  = gtk_tree_view_get_model (treeview);
222
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))
227     {
228       union value v;
229       GValue gvalue = {0};
230
231       gtk_tree_model_get_value (model, &iter, 1, &gvalue);
232
233       v.f = g_value_get_double (&gvalue);
234
235       if ( 0 == memcmp (&v, val, sizeof (union value)))
236         {
237           break;
238         }
239     }
240
241   path = gtk_tree_model_get_path (model, &iter);
242   if ( path )
243     {
244       gtk_tree_view_set_cursor (treeview, path, 0, 0);
245       gtk_tree_path_free (path);
246     }
247
248 }
249
250
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)
255 {
256   const char *s;
257
258   PsppireValLabsDialog *dialog = data;
259
260   const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
261
262   union value v;
263   text_to_value__ (text, &dialog->format, dialog->encoding, &v);
264
265   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
266                          dialog->change_handler_id);
267
268   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),"");
269
270
271   if ( (s = val_labs_find (dialog->labs, &v)) )
272     {
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);
277     }
278   else
279     {
280       gtk_widget_set_sensitive (dialog->remove_button, FALSE);
281       gtk_widget_set_sensitive (dialog->add_button, TRUE);
282     }
283
284   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
285                          dialog->change_handler_id);
286
287   value_destroy (&v, val_labs_get_width (dialog->labs));
288 }
289
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)
293 {
294   PsppireValLabsDialog *dialog = data;
295
296   gtk_widget_grab_focus (dialog->label_entry);
297 }
298
299 static gboolean
300 get_selected_tuple (PsppireValLabsDialog *dialog,
301                     union value *valuep, const char **label)
302 {
303   GtkTreeView *treeview = GTK_TREE_VIEW (dialog->treeview);
304
305   GtkTreeIter iter ;
306   GValue the_value = {0};
307   union value value;
308
309   GtkTreeSelection* sel =  gtk_tree_view_get_selection (treeview);
310
311   GtkTreeModel * model  = gtk_tree_view_get_model (treeview);
312
313   if (! gtk_tree_selection_get_selected (sel, &model, &iter))
314     return FALSE;
315
316   gtk_tree_model_get_value (model, &iter, 1, &the_value);
317
318   value.f = g_value_get_double (&the_value);
319   g_value_unset (&the_value);
320
321   if (valuep != NULL)
322     *valuep = value;
323   if (label != NULL)
324     {
325       struct val_lab *vl = val_labs_lookup (dialog->labs, &value);
326       if (vl != NULL)
327         *label = val_lab_get_escaped_label (vl);
328     }
329
330   return TRUE;
331 }
332
333
334 static void repopulate_dialog (PsppireValLabsDialog *dialog);
335
336 /* Callback which occurs when the "Change" button is clicked */
337 static void
338 on_change (GtkWidget *w, gpointer data)
339 {
340   PsppireValLabsDialog *dialog = data;
341   do_change (dialog);
342 }
343
344 static void
345 do_change (PsppireValLabsDialog *dialog)
346 {
347   const gchar *val_text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
348
349   union value v;
350
351   if (text_to_value__ (val_text, &dialog->format, dialog->encoding, &v))
352     {
353       val_labs_replace (dialog->labs, &v,
354                         gtk_entry_get_text (GTK_ENTRY (dialog->label_entry)));
355
356       gtk_widget_set_sensitive (dialog->change_button, FALSE);
357
358       repopulate_dialog (dialog);
359       gtk_widget_grab_focus (dialog->value_entry);
360
361       value_destroy (&v, val_labs_get_width (dialog->labs));
362     }
363 }
364
365 /* Callback which occurs when the "Add" button is clicked */
366 static void
367 on_add (GtkWidget *w, gpointer data)
368 {
369   PsppireValLabsDialog *dialog = data;
370
371   union value v;
372
373   const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
374
375   if (text_to_value__ (text, &dialog->format, dialog->encoding, &v))
376     {
377       if (val_labs_add (dialog->labs, &v,
378                         gtk_entry_get_text
379                         ( GTK_ENTRY (dialog->label_entry)) ) )
380         {
381           gtk_widget_set_sensitive (dialog->add_button, FALSE);
382
383           repopulate_dialog (dialog);
384           gtk_widget_grab_focus (dialog->value_entry);
385         }
386
387       value_destroy (&v, val_labs_get_width (dialog->labs));
388     }
389 }
390
391 /* Callback which occurs when the "Remove" button is clicked */
392 static void
393 on_remove (GtkWidget *w, gpointer data)
394 {
395   PsppireValLabsDialog *dialog = data;
396
397   union value value;
398   struct val_lab *vl;
399
400   if (! get_selected_tuple (dialog, &value, NULL))
401     return;
402
403   vl = val_labs_lookup (dialog->labs, &value);
404   if (vl != NULL)
405     val_labs_remove (dialog->labs, vl);
406
407   repopulate_dialog (dialog);
408   gtk_widget_grab_focus (dialog->value_entry);
409
410   gtk_widget_set_sensitive (dialog->remove_button, FALSE);
411 }
412
413
414
415 /* Callback which occurs when a line item is selected in the list of
416    value--label pairs.*/
417 static void
418 on_select_row (GtkTreeView *treeview, gpointer data)
419 {
420   PsppireValLabsDialog *dialog = data;
421
422   union value value;
423   const char *label = NULL;
424
425   gchar *text;
426
427   if (! get_selected_tuple (dialog, &value, &label))
428     return;
429
430   text = value_to_text__ (value, &dialog->format, dialog->encoding);
431
432   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
433                          dialog->value_handler_id);
434
435   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), text);
436
437   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
438                          dialog->value_handler_id);
439   g_free (text);
440
441   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
442                          dialog->change_handler_id);
443
444
445   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),
446                       label);
447
448   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
449                          dialog->change_handler_id);
450
451   gtk_widget_set_sensitive (dialog->remove_button, TRUE);
452   gtk_widget_set_sensitive (dialog->change_button, FALSE);
453 }
454
455
456 /* Create a new dialog box
457    (there should  normally be only one)*/
458 static GObject *
459 psppire_val_labs_dialog_constructor (GType                  type,
460                                      guint                  n_properties,
461                                      GObjectConstructParam *properties)
462 {
463   PsppireValLabsDialog *dialog;
464   GtkTreeViewColumn *column;
465
466   GtkCellRenderer *renderer ;
467
468   GtkBuilder *xml = builder_new ("val-labs-dialog.ui");
469
470   GtkContainer *content_area;
471   GObject *obj;
472
473   obj = G_OBJECT_CLASS (psppire_val_labs_dialog_parent_class)->constructor (
474     type, n_properties, properties);
475   dialog = PSPPIRE_VAL_LABS_DIALOG (obj);
476
477   content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
478   gtk_container_add (GTK_CONTAINER (content_area),
479                      get_widget_assert (xml, "val-labs-dialog"));
480
481   dialog->value_entry = get_widget_assert (xml,"value_entry");
482   dialog->label_entry = get_widget_assert (xml,"label_entry");
483
484   dialog->add_button = get_widget_assert (xml, "val_labs_add");
485   dialog->remove_button = get_widget_assert (xml, "val_labs_remove");
486   dialog->change_button = get_widget_assert (xml, "val_labs_change");
487
488   dialog->treeview = get_widget_assert (xml,"treeview1");
489
490   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (dialog->treeview), FALSE);
491
492   renderer = gtk_cell_renderer_text_new ();
493
494   column = gtk_tree_view_column_new_with_attributes ("Title",
495                                                      renderer,
496                                                      "text",
497                                                      0,
498                                                      NULL);
499
500   gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->treeview), column);
501
502   dialog->change_handler_id =
503     g_signal_connect (dialog->label_entry,
504                      "changed",
505                      G_CALLBACK (on_label_entry_change), dialog);
506   g_signal_connect (dialog->label_entry, "activate",
507                     G_CALLBACK (on_label_entry_activate), dialog);
508
509   dialog->value_handler_id  =
510     g_signal_connect (dialog->value_entry,
511                      "changed",
512                      G_CALLBACK (on_value_entry_change), dialog);
513   g_signal_connect (dialog->value_entry, "activate",
514                     G_CALLBACK (on_value_entry_activate), dialog);
515
516   g_signal_connect (dialog->change_button,
517                    "clicked",
518                    G_CALLBACK (on_change), dialog);
519
520
521   g_signal_connect (dialog->treeview, "cursor-changed",
522                    G_CALLBACK (on_select_row), dialog);
523
524   g_signal_connect (dialog->remove_button, "clicked",
525                    G_CALLBACK (on_remove), dialog);
526
527   g_signal_connect (dialog->add_button, "clicked",
528                    G_CALLBACK (on_add), dialog);
529
530   dialog->labs = NULL;
531
532   g_object_unref (xml);
533
534   return obj;
535 }
536
537
538 /* Populate the components of the dialog box, from the 'labs' member
539    variable */
540 static void
541 repopulate_dialog (PsppireValLabsDialog *dialog)
542 {
543   const struct val_lab **labels;
544   size_t n_labels;
545   size_t i;
546
547   GtkTreeIter iter;
548
549   GtkListStore *list_store = gtk_list_store_new (2,
550                                                  G_TYPE_STRING,
551                                                  G_TYPE_DOUBLE);
552
553   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
554                          dialog->change_handler_id);
555   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
556                          dialog->value_handler_id);
557
558   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), "");
559   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), "");
560
561   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
562                          dialog->value_handler_id);
563   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
564                            dialog->change_handler_id);
565
566   labels = val_labs_sorted (dialog->labs);
567   n_labels = val_labs_count (dialog->labs);
568   for (i = 0; i < n_labels; i++)
569     {
570       const struct val_lab *vl = labels[i];
571
572       gchar *const vstr  =
573         value_to_text__ (vl->value, &dialog->format, dialog->encoding);
574
575       gchar *const text = g_strdup_printf (_("%s = `%s'"), vstr,
576                                            val_lab_get_escaped_label (vl));
577
578       gtk_list_store_append (list_store, &iter);
579       gtk_list_store_set (list_store, &iter,
580                           0, text,
581                           1, vl->value.f,
582                           -1);
583
584       g_free (text);
585       g_free (vstr);
586     }
587   free (labels);
588
589   gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->treeview),
590                           GTK_TREE_MODEL (list_store));
591
592   g_object_unref (list_store);
593
594 }
595
596 void
597 psppire_val_labs_dialog_set_variable (PsppireValLabsDialog *dialog,
598                                       const struct variable *var)
599 {
600   val_labs_destroy (dialog->labs);
601   dialog->labs = NULL;
602
603   g_free (dialog->encoding);
604   dialog->encoding = NULL;
605
606   if (var != NULL)
607     {
608       dialog->labs = val_labs_clone (var_get_value_labels (var));
609       dialog->encoding = g_strdup (var_get_encoding (var));
610       dialog->format = *var_get_print_format (var);
611     }
612   else
613     dialog->format = F_8_0;
614
615   if (dialog->labs == NULL)
616     dialog->labs = val_labs_create (var_get_width (var));
617
618   repopulate_dialog (dialog);
619 }
620
621 const struct val_labs *
622 psppire_val_labs_dialog_get_value_labels (const PsppireValLabsDialog *dialog)
623 {
624   return dialog->labs;
625 }