Value Label Dialog: Do not attempt to read selection if there is none.
[pspp] / src / ui / gui / val-labs-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2005, 2009, 2010, 2011, 2012  Free Software Foundation
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17
18 /*  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
52 psppire_val_labs_dialog_set_property (GObject      *object,
53                                       guint         prop_id,
54                                       const GValue *value,
55                                       GParamSpec   *pspec)
56 {
57   PsppireValLabsDialog *obj = PSPPIRE_VAL_LABS_DIALOG (object);
58
59   switch (prop_id)
60     {
61     case PROP_VARIABLE:
62       psppire_val_labs_dialog_set_variable (obj, g_value_get_pointer (value));
63       break;
64     case PROP_VALUE_LABELS:
65     default:
66       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
67       break;
68     }
69 }
70
71 static void
72 psppire_val_labs_dialog_get_property (GObject      *object,
73                                       guint         prop_id,
74                                       GValue       *value,
75                                       GParamSpec   *pspec)
76 {
77   PsppireValLabsDialog *obj = PSPPIRE_VAL_LABS_DIALOG (object);
78
79   switch (prop_id)
80     {
81     case PROP_VALUE_LABELS:
82       g_value_set_pointer (value, obj->labs);
83       break;
84     case PROP_VARIABLE:
85     default:
86       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
87       break;
88     }
89 }
90
91 static void
92 psppire_val_labs_dialog_class_init (PsppireValLabsDialogClass *class)
93 {
94   GObjectClass *gobject_class;
95   gobject_class = G_OBJECT_CLASS (class);
96
97   gobject_class->constructor = psppire_val_labs_dialog_constructor;
98   gobject_class->finalize = psppire_val_labs_dialog_finalize;
99   gobject_class->set_property = psppire_val_labs_dialog_set_property;
100   gobject_class->get_property = psppire_val_labs_dialog_get_property;
101
102   g_object_class_install_property (
103     gobject_class, PROP_VARIABLE,
104     g_param_spec_pointer ("variable",
105                           "Variable",
106                           "Variable whose value labels are to be edited.  The "
107                           "variable's print format and encoding are also used "
108                           "for editing.",
109                           G_PARAM_WRITABLE));
110
111   g_object_class_install_property (
112     gobject_class, PROP_VALUE_LABELS,
113     g_param_spec_pointer ("value-labels",
114                           "Value Labels",
115                           "Edited value labels.",
116                           G_PARAM_READABLE));
117 }
118
119 static void
120 psppire_val_labs_dialog_init (PsppireValLabsDialog *obj)
121 {
122   /* We do all of our work on widgets in the constructor function, because that
123      runs after the construction properties have been set.  Otherwise
124      PsppireDialog's "orientation" property hasn't been set and therefore we
125      have no box to populate. */
126   obj->labs = val_labs_create (0);
127 }
128
129 static void
130 psppire_val_labs_dialog_finalize (GObject *obj)
131 {
132   PsppireValLabsDialog *dialog = PSPPIRE_VAL_LABS_DIALOG (obj);
133
134   val_labs_destroy (dialog->labs);
135   g_free (dialog->encoding);
136
137   G_OBJECT_CLASS (psppire_val_labs_dialog_parent_class)->finalize (obj);
138 }
139
140 PsppireValLabsDialog *
141 psppire_val_labs_dialog_new (const struct variable *var)
142 {
143   return PSPPIRE_VAL_LABS_DIALOG (
144     g_object_new (PSPPIRE_TYPE_VAL_LABS_DIALOG,
145                   "orientation", PSPPIRE_HORIZONTAL,
146                   "variable", var,
147                   NULL));
148 }
149
150 struct val_labs *
151 psppire_val_labs_dialog_run (GtkWindow *parent_window,
152                              const struct variable *var)
153 {
154   PsppireValLabsDialog *dialog;
155   struct val_labs *labs;
156
157   dialog = psppire_val_labs_dialog_new (var);
158   gtk_window_set_transient_for (GTK_WINDOW (dialog), parent_window);
159   gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
160   gtk_widget_show (GTK_WIDGET (dialog));
161
162   labs = (psppire_dialog_run (PSPPIRE_DIALOG (dialog)) == GTK_RESPONSE_OK
163           ? val_labs_clone (psppire_val_labs_dialog_get_value_labels (dialog))
164           : NULL);
165
166   gtk_widget_destroy (GTK_WIDGET (dialog));
167
168   return labs;
169 }
170
171 /* This callback occurs when the text in the label entry box
172    is changed */
173 static void
174 on_label_entry_change (GtkEntry *entry, gpointer data)
175 {
176   union value v;
177   const gchar *text ;
178   PsppireValLabsDialog *dialog = data;
179   g_assert (dialog->labs);
180
181   text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
182
183   text_to_value__ (text, &dialog->format, dialog->encoding, &v);
184
185   if (val_labs_find (dialog->labs, &v))
186     {
187       gtk_widget_set_sensitive (dialog->change_button, TRUE);
188       gtk_widget_set_sensitive (dialog->add_button, FALSE);
189     }
190   else
191     {
192       gtk_widget_set_sensitive (dialog->change_button, FALSE);
193       gtk_widget_set_sensitive (dialog->add_button, TRUE);
194     }
195
196   value_destroy (&v, val_labs_get_width (dialog->labs));
197 }
198
199
200 /* Set the TREEVIEW list cursor to the item which has the value VAL */
201 static void
202 select_treeview_from_value (GtkTreeView *treeview, union value *val)
203 {
204   GtkTreePath *path ;
205
206   /*
207     We do this with a linear search through the model --- hardly
208     efficient, but the list is short ... */
209   GtkTreeIter iter;
210
211   GtkTreeModel * model  = gtk_tree_view_get_model (treeview);
212
213   gboolean success;
214   for (success = gtk_tree_model_get_iter_first (model, &iter);
215        success;
216        success = gtk_tree_model_iter_next (model, &iter))
217     {
218       union value v;
219       GValue gvalue = {0};
220
221       gtk_tree_model_get_value (model, &iter, 1, &gvalue);
222
223       v.f = g_value_get_double (&gvalue);
224
225       if ( 0 == memcmp (&v, val, sizeof (union value)))
226         {
227           break;
228         }
229     }
230
231   path = gtk_tree_model_get_path (model, &iter);
232   if ( path )
233     {
234       gtk_tree_view_set_cursor (treeview, path, 0, 0);
235       gtk_tree_path_free (path);
236     }
237
238 }
239
240
241 /* This callback occurs when the text in the value entry box is
242    changed */
243 static void
244 on_value_entry_change (GtkEntry *entry, gpointer data)
245 {
246   const char *s;
247
248   PsppireValLabsDialog *dialog = data;
249
250   const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
251
252   union value v;
253   text_to_value__ (text, &dialog->format, dialog->encoding, &v);
254
255   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
256                          dialog->change_handler_id);
257
258   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),"");
259
260
261   if ( (s = val_labs_find (dialog->labs, &v)) )
262     {
263       gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), s);
264       gtk_widget_set_sensitive (dialog->add_button, FALSE);
265       gtk_widget_set_sensitive (dialog->remove_button, TRUE);
266       select_treeview_from_value (GTK_TREE_VIEW (dialog->treeview), &v);
267     }
268   else
269     {
270       gtk_widget_set_sensitive (dialog->remove_button, FALSE);
271       gtk_widget_set_sensitive (dialog->add_button, TRUE);
272     }
273
274   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
275                          dialog->change_handler_id);
276
277   value_destroy (&v, val_labs_get_width (dialog->labs));
278 }
279
280
281 /* Return the value-label pair currently selected in the dialog box  */
282 static gboolean
283 get_selected_tuple (PsppireValLabsDialog *dialog,
284                     union value *valuep, const char **label)
285 {
286   GtkTreeView *treeview = GTK_TREE_VIEW (dialog->treeview);
287
288   GtkTreeIter iter ;
289   GValue the_value = {0};
290   union value value;
291
292   GtkTreeSelection* sel =  gtk_tree_view_get_selection (treeview);
293
294   GtkTreeModel * model  = gtk_tree_view_get_model (treeview);
295
296   if (! gtk_tree_selection_get_selected (sel, &model, &iter))
297     return FALSE;
298
299   gtk_tree_model_get_value (model, &iter, 1, &the_value);
300
301   value.f = g_value_get_double (&the_value);
302   g_value_unset (&the_value);
303
304   if (valuep != NULL)
305     *valuep = value;
306   if (label != NULL)
307     {
308       struct val_lab *vl = val_labs_lookup (dialog->labs, &value);
309       if (vl != NULL)
310         *label = val_lab_get_escaped_label (vl);
311     }
312   
313   return TRUE;
314 }
315
316
317 static void repopulate_dialog (PsppireValLabsDialog *dialog);
318
319 /* Callback which occurs when the "Change" button is clicked */
320 static void
321 on_change (GtkWidget *w, gpointer data)
322 {
323   PsppireValLabsDialog *dialog = data;
324
325   const gchar *val_text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
326
327   union value v;
328
329   text_to_value__ (val_text, &dialog->format, dialog->encoding, &v);
330
331   val_labs_replace (dialog->labs, &v,
332                     gtk_entry_get_text (GTK_ENTRY (dialog->label_entry)));
333
334   gtk_widget_set_sensitive (dialog->change_button, FALSE);
335
336   repopulate_dialog (dialog);
337   gtk_widget_grab_focus (dialog->value_entry);
338
339   value_destroy (&v, val_labs_get_width (dialog->labs));
340 }
341
342 /* Callback which occurs when the "Add" button is clicked */
343 static void
344 on_add (GtkWidget *w, gpointer data)
345 {
346   PsppireValLabsDialog *dialog = data;
347
348   union value v;
349
350   const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
351
352   text_to_value__ (text, &dialog->format, dialog->encoding, &v);
353
354   if (val_labs_add (dialog->labs, &v,
355                     gtk_entry_get_text
356                     ( GTK_ENTRY (dialog->label_entry)) ) )
357     {
358       gtk_widget_set_sensitive (dialog->add_button, FALSE);
359
360       repopulate_dialog (dialog);
361       gtk_widget_grab_focus (dialog->value_entry);
362     }
363
364   value_destroy (&v, val_labs_get_width (dialog->labs));
365 }
366
367 /* Callback which occurs when the "Remove" button is clicked */
368 static void
369 on_remove (GtkWidget *w, gpointer data)
370 {
371   PsppireValLabsDialog *dialog = data;
372
373   union value value;
374   struct val_lab *vl;
375
376   if (! get_selected_tuple (dialog, &value, NULL))
377     return;
378   
379   vl = val_labs_lookup (dialog->labs, &value);
380   if (vl != NULL)
381     val_labs_remove (dialog->labs, vl);
382
383   repopulate_dialog (dialog);
384   gtk_widget_grab_focus (dialog->value_entry);
385
386   gtk_widget_set_sensitive (dialog->remove_button, FALSE);
387 }
388
389
390
391 /* Callback which occurs when a line item is selected in the list of
392    value--label pairs.*/
393 static void
394 on_select_row (GtkTreeView *treeview, gpointer data)
395 {
396   PsppireValLabsDialog *dialog = data;
397
398   union value value;
399   const char *label = NULL;
400
401   gchar *text;
402
403   if (! get_selected_tuple (dialog, &value, &label))
404     return;
405   
406   text = value_to_text__ (value, &dialog->format, dialog->encoding);
407
408   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
409                          dialog->value_handler_id);
410
411   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), text);
412
413   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
414                          dialog->value_handler_id);
415   g_free (text);
416
417   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
418                          dialog->change_handler_id);
419
420
421   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),
422                       label);
423
424   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
425                          dialog->change_handler_id);
426
427   gtk_widget_set_sensitive (dialog->remove_button, TRUE);
428   gtk_widget_set_sensitive (dialog->change_button, FALSE);
429 }
430
431
432 /* Create a new dialog box
433    (there should  normally be only one)*/
434 static GObject *
435 psppire_val_labs_dialog_constructor (GType                  type,
436                                      guint                  n_properties,
437                                      GObjectConstructParam *properties)
438 {
439   PsppireValLabsDialog *dialog;
440   GtkTreeViewColumn *column;
441
442   GtkCellRenderer *renderer ;
443
444   GtkBuilder *xml = builder_new ("val-labs-dialog.ui");
445
446   GtkContainer *content_area;
447   GObject *obj;
448
449   obj = G_OBJECT_CLASS (psppire_val_labs_dialog_parent_class)->constructor (
450     type, n_properties, properties);
451   dialog = PSPPIRE_VAL_LABS_DIALOG (obj);
452
453   content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog)->box);
454   gtk_container_add (GTK_CONTAINER (content_area),
455                      get_widget_assert (xml, "val-labs-dialog"));
456
457   dialog->value_entry = get_widget_assert (xml,"value_entry");
458   dialog->label_entry = get_widget_assert (xml,"label_entry");
459
460   dialog->add_button = get_widget_assert (xml, "val_labs_add");
461   dialog->remove_button = get_widget_assert (xml, "val_labs_remove");
462   dialog->change_button = get_widget_assert (xml, "val_labs_change");
463
464   dialog->treeview = get_widget_assert (xml,"treeview1");
465
466   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (dialog->treeview), FALSE);
467
468   renderer = gtk_cell_renderer_text_new ();
469
470   column = gtk_tree_view_column_new_with_attributes ("Title",
471                                                      renderer,
472                                                      "text",
473                                                      0,
474                                                      NULL);
475
476   gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->treeview), column);
477
478   dialog->change_handler_id =
479     g_signal_connect (dialog->label_entry,
480                      "changed",
481                      G_CALLBACK (on_label_entry_change), dialog);
482
483   dialog->value_handler_id  =
484     g_signal_connect (dialog->value_entry,
485                      "changed",
486                      G_CALLBACK (on_value_entry_change), dialog);
487
488   g_signal_connect (dialog->change_button,
489                    "clicked",
490                    G_CALLBACK (on_change), dialog);
491
492
493   g_signal_connect (dialog->treeview, "cursor-changed",
494                    G_CALLBACK (on_select_row), dialog);
495
496   g_signal_connect (dialog->remove_button, "clicked",
497                    G_CALLBACK (on_remove), dialog);
498
499   g_signal_connect (dialog->add_button, "clicked",
500                    G_CALLBACK (on_add), dialog);
501
502   dialog->labs = NULL;
503
504   g_object_unref (xml);
505
506   return obj;
507 }
508
509
510 /* Populate the components of the dialog box, from the 'labs' member
511    variable */
512 static void
513 repopulate_dialog (PsppireValLabsDialog *dialog)
514 {
515   const struct val_lab **labels;
516   size_t n_labels;
517   size_t i;
518
519   GtkTreeIter iter;
520
521   GtkListStore *list_store = gtk_list_store_new (2,
522                                                  G_TYPE_STRING,
523                                                  G_TYPE_DOUBLE);
524
525   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
526                          dialog->change_handler_id);
527   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
528                          dialog->value_handler_id);
529
530   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), "");
531   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), "");
532
533   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
534                          dialog->value_handler_id);
535   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
536                            dialog->change_handler_id);
537
538   labels = val_labs_sorted (dialog->labs);
539   n_labels = val_labs_count (dialog->labs);
540   for (i = 0; i < n_labels; i++)
541     {
542       const struct val_lab *vl = labels[i];
543
544       gchar *const vstr  =
545         value_to_text__ (vl->value, &dialog->format, dialog->encoding);
546
547       gchar *const text = g_strdup_printf (_("%s = `%s'"), vstr,
548                                            val_lab_get_escaped_label (vl));
549
550       gtk_list_store_append (list_store, &iter);
551       gtk_list_store_set (list_store, &iter,
552                           0, text,
553                           1, vl->value.f,
554                           -1);
555
556       g_free (text);
557       g_free (vstr);
558     }
559   free (labels);
560
561   gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->treeview),
562                           GTK_TREE_MODEL (list_store));
563
564   g_object_unref (list_store);
565
566 }
567
568 void
569 psppire_val_labs_dialog_set_variable (PsppireValLabsDialog *dialog,
570                                       const struct variable *var)
571 {
572   val_labs_destroy (dialog->labs);
573   dialog->labs = NULL;
574
575   g_free (dialog->encoding);
576   dialog->encoding = NULL;
577
578   if (var != NULL)
579     {
580       dialog->labs = val_labs_clone (var_get_value_labels (var));
581       dialog->encoding = g_strdup (var_get_encoding (var));
582       dialog->format = *var_get_print_format (var);
583     }
584   else
585     dialog->format = F_8_0;
586
587   if (dialog->labs == NULL)
588     dialog->labs = val_labs_create (var_get_width (var));
589
590   repopulate_dialog (dialog);
591 }
592
593 const struct val_labs *
594 psppire_val_labs_dialog_get_value_labels (const PsppireValLabsDialog *dialog)
595 {
596   return dialog->labs;
597 }