56399aa271f3b3b6ee4739c33b3640a008c6472b
[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  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                   "variable", var,
146                   NULL));
147 }
148
149 struct val_labs *
150 psppire_val_labs_dialog_run (GtkWindow *parent_window,
151                              const struct variable *var)
152 {
153   PsppireValLabsDialog *dialog;
154   struct val_labs *labs;
155
156   dialog = psppire_val_labs_dialog_new (var);
157   gtk_window_set_transient_for (GTK_WINDOW (dialog), parent_window);
158   gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
159   gtk_widget_show (GTK_WIDGET (dialog));
160
161   labs = (psppire_dialog_run (PSPPIRE_DIALOG (dialog)) == GTK_RESPONSE_OK
162           ? val_labs_clone (psppire_val_labs_dialog_get_value_labels (dialog))
163           : NULL);
164
165   gtk_widget_destroy (GTK_WIDGET (dialog));
166
167   return labs;
168 }
169
170 /* This callback occurs when the text in the label entry box
171    is changed */
172 static void
173 on_label_entry_change (GtkEntry *entry, gpointer data)
174 {
175   union value v;
176   const gchar *text ;
177   PsppireValLabsDialog *dialog = data;
178   g_assert (dialog->labs);
179
180   text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
181
182   text_to_value__ (text, &dialog->format, dialog->encoding, &v);
183
184   if (val_labs_find (dialog->labs, &v))
185     {
186       gtk_widget_set_sensitive (dialog->change_button, TRUE);
187       gtk_widget_set_sensitive (dialog->add_button, FALSE);
188     }
189   else
190     {
191       gtk_widget_set_sensitive (dialog->change_button, FALSE);
192       gtk_widget_set_sensitive (dialog->add_button, TRUE);
193     }
194
195   value_destroy (&v, val_labs_get_width (dialog->labs));
196 }
197
198
199 /* Set the TREEVIEW list cursor to the item which has the value VAL */
200 static void
201 select_treeview_from_value (GtkTreeView *treeview, union value *val)
202 {
203   GtkTreePath *path ;
204
205   /*
206     We do this with a linear search through the model --- hardly
207     efficient, but the list is short ... */
208   GtkTreeIter iter;
209
210   GtkTreeModel * model  = gtk_tree_view_get_model (treeview);
211
212   gboolean success;
213   for (success = gtk_tree_model_get_iter_first (model, &iter);
214        success;
215        success = gtk_tree_model_iter_next (model, &iter))
216     {
217       union value v;
218       GValue gvalue = {0};
219
220       gtk_tree_model_get_value (model, &iter, 1, &gvalue);
221
222       v.f = g_value_get_double (&gvalue);
223
224       if ( 0 == memcmp (&v, val, sizeof (union value)))
225         {
226           break;
227         }
228     }
229
230   path = gtk_tree_model_get_path (model, &iter);
231   if ( path )
232     {
233       gtk_tree_view_set_cursor (treeview, path, 0, 0);
234       gtk_tree_path_free (path);
235     }
236
237 }
238
239
240 /* This callback occurs when the text in the value entry box is
241    changed */
242 static void
243 on_value_entry_change (GtkEntry *entry, gpointer data)
244 {
245   const char *s;
246
247   PsppireValLabsDialog *dialog = data;
248
249   const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
250
251   union value v;
252   text_to_value__ (text, &dialog->format, dialog->encoding, &v);
253
254   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
255                          dialog->change_handler_id);
256
257   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),"");
258
259
260   if ( (s = val_labs_find (dialog->labs, &v)) )
261     {
262       gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), s);
263       gtk_widget_set_sensitive (dialog->add_button, FALSE);
264       gtk_widget_set_sensitive (dialog->remove_button, TRUE);
265       select_treeview_from_value (GTK_TREE_VIEW (dialog->treeview), &v);
266     }
267   else
268     {
269       gtk_widget_set_sensitive (dialog->remove_button, FALSE);
270       gtk_widget_set_sensitive (dialog->add_button, TRUE);
271     }
272
273   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
274                          dialog->change_handler_id);
275
276   value_destroy (&v, val_labs_get_width (dialog->labs));
277 }
278
279
280 /* Return the value-label pair currently selected in the dialog box  */
281 static gboolean
282 get_selected_tuple (PsppireValLabsDialog *dialog,
283                     union value *valuep, const char **label)
284 {
285   GtkTreeView *treeview = GTK_TREE_VIEW (dialog->treeview);
286
287   GtkTreeIter iter ;
288   GValue the_value = {0};
289   union value value;
290
291   GtkTreeSelection* sel =  gtk_tree_view_get_selection (treeview);
292
293   GtkTreeModel * model  = gtk_tree_view_get_model (treeview);
294
295   if (! gtk_tree_selection_get_selected (sel, &model, &iter))
296     return FALSE;
297
298   gtk_tree_model_get_value (model, &iter, 1, &the_value);
299
300   value.f = g_value_get_double (&the_value);
301   g_value_unset (&the_value);
302
303   if (valuep != NULL)
304     *valuep = value;
305   if (label != NULL)
306     {
307       struct val_lab *vl = val_labs_lookup (dialog->labs, &value);
308       if (vl != NULL)
309         *label = val_lab_get_escaped_label (vl);
310     }
311   
312   return TRUE;
313 }
314
315
316 static void repopulate_dialog (PsppireValLabsDialog *dialog);
317
318 /* Callback which occurs when the "Change" button is clicked */
319 static void
320 on_change (GtkWidget *w, gpointer data)
321 {
322   PsppireValLabsDialog *dialog = data;
323
324   const gchar *val_text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
325
326   union value v;
327
328   text_to_value__ (val_text, &dialog->format, dialog->encoding, &v);
329
330   val_labs_replace (dialog->labs, &v,
331                     gtk_entry_get_text (GTK_ENTRY (dialog->label_entry)));
332
333   gtk_widget_set_sensitive (dialog->change_button, FALSE);
334
335   repopulate_dialog (dialog);
336   gtk_widget_grab_focus (dialog->value_entry);
337
338   value_destroy (&v, val_labs_get_width (dialog->labs));
339 }
340
341 /* Callback which occurs when the "Add" button is clicked */
342 static void
343 on_add (GtkWidget *w, gpointer data)
344 {
345   PsppireValLabsDialog *dialog = data;
346
347   union value v;
348
349   const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
350
351   text_to_value__ (text, &dialog->format, dialog->encoding, &v);
352
353   if (val_labs_add (dialog->labs, &v,
354                     gtk_entry_get_text
355                     ( GTK_ENTRY (dialog->label_entry)) ) )
356     {
357       gtk_widget_set_sensitive (dialog->add_button, FALSE);
358
359       repopulate_dialog (dialog);
360       gtk_widget_grab_focus (dialog->value_entry);
361     }
362
363   value_destroy (&v, val_labs_get_width (dialog->labs));
364 }
365
366 /* Callback which occurs when the "Remove" button is clicked */
367 static void
368 on_remove (GtkWidget *w, gpointer data)
369 {
370   PsppireValLabsDialog *dialog = data;
371
372   union value value;
373   struct val_lab *vl;
374
375   if (! get_selected_tuple (dialog, &value, NULL))
376     return;
377   
378   vl = val_labs_lookup (dialog->labs, &value);
379   if (vl != NULL)
380     val_labs_remove (dialog->labs, vl);
381
382   repopulate_dialog (dialog);
383   gtk_widget_grab_focus (dialog->value_entry);
384
385   gtk_widget_set_sensitive (dialog->remove_button, FALSE);
386 }
387
388
389
390 /* Callback which occurs when a line item is selected in the list of
391    value--label pairs.*/
392 static void
393 on_select_row (GtkTreeView *treeview, gpointer data)
394 {
395   PsppireValLabsDialog *dialog = data;
396
397   union value value;
398   const char *label = NULL;
399
400   gchar *text;
401
402   if (! get_selected_tuple (dialog, &value, &label))
403     return;
404   
405   text = value_to_text__ (value, &dialog->format, dialog->encoding);
406
407   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
408                          dialog->value_handler_id);
409
410   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), text);
411
412   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
413                          dialog->value_handler_id);
414   g_free (text);
415
416   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
417                          dialog->change_handler_id);
418
419
420   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),
421                       label);
422
423   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
424                          dialog->change_handler_id);
425
426   gtk_widget_set_sensitive (dialog->remove_button, TRUE);
427   gtk_widget_set_sensitive (dialog->change_button, FALSE);
428 }
429
430
431 /* Create a new dialog box
432    (there should  normally be only one)*/
433 static GObject *
434 psppire_val_labs_dialog_constructor (GType                  type,
435                                      guint                  n_properties,
436                                      GObjectConstructParam *properties)
437 {
438   PsppireValLabsDialog *dialog;
439   GtkTreeViewColumn *column;
440
441   GtkCellRenderer *renderer ;
442
443   GtkBuilder *xml = builder_new ("val-labs-dialog.ui");
444
445   GtkContainer *content_area;
446   GObject *obj;
447
448   obj = G_OBJECT_CLASS (psppire_val_labs_dialog_parent_class)->constructor (
449     type, n_properties, properties);
450   dialog = PSPPIRE_VAL_LABS_DIALOG (obj);
451
452   content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
453   gtk_container_add (GTK_CONTAINER (content_area),
454                      get_widget_assert (xml, "val-labs-dialog"));
455
456   dialog->value_entry = get_widget_assert (xml,"value_entry");
457   dialog->label_entry = get_widget_assert (xml,"label_entry");
458
459   dialog->add_button = get_widget_assert (xml, "val_labs_add");
460   dialog->remove_button = get_widget_assert (xml, "val_labs_remove");
461   dialog->change_button = get_widget_assert (xml, "val_labs_change");
462
463   dialog->treeview = get_widget_assert (xml,"treeview1");
464
465   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (dialog->treeview), FALSE);
466
467   renderer = gtk_cell_renderer_text_new ();
468
469   column = gtk_tree_view_column_new_with_attributes ("Title",
470                                                      renderer,
471                                                      "text",
472                                                      0,
473                                                      NULL);
474
475   gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->treeview), column);
476
477   dialog->change_handler_id =
478     g_signal_connect (dialog->label_entry,
479                      "changed",
480                      G_CALLBACK (on_label_entry_change), dialog);
481
482   dialog->value_handler_id  =
483     g_signal_connect (dialog->value_entry,
484                      "changed",
485                      G_CALLBACK (on_value_entry_change), dialog);
486
487   g_signal_connect (dialog->change_button,
488                    "clicked",
489                    G_CALLBACK (on_change), dialog);
490
491
492   g_signal_connect (dialog->treeview, "cursor-changed",
493                    G_CALLBACK (on_select_row), dialog);
494
495   g_signal_connect (dialog->remove_button, "clicked",
496                    G_CALLBACK (on_remove), dialog);
497
498   g_signal_connect (dialog->add_button, "clicked",
499                    G_CALLBACK (on_add), dialog);
500
501   dialog->labs = NULL;
502
503   g_object_unref (xml);
504
505   return obj;
506 }
507
508
509 /* Populate the components of the dialog box, from the 'labs' member
510    variable */
511 static void
512 repopulate_dialog (PsppireValLabsDialog *dialog)
513 {
514   const struct val_lab **labels;
515   size_t n_labels;
516   size_t i;
517
518   GtkTreeIter iter;
519
520   GtkListStore *list_store = gtk_list_store_new (2,
521                                                  G_TYPE_STRING,
522                                                  G_TYPE_DOUBLE);
523
524   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
525                          dialog->change_handler_id);
526   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
527                          dialog->value_handler_id);
528
529   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), "");
530   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), "");
531
532   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
533                          dialog->value_handler_id);
534   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
535                            dialog->change_handler_id);
536
537   labels = val_labs_sorted (dialog->labs);
538   n_labels = val_labs_count (dialog->labs);
539   for (i = 0; i < n_labels; i++)
540     {
541       const struct val_lab *vl = labels[i];
542
543       gchar *const vstr  =
544         value_to_text__ (vl->value, &dialog->format, dialog->encoding);
545
546       gchar *const text = g_strdup_printf (_("%s = `%s'"), vstr,
547                                            val_lab_get_escaped_label (vl));
548
549       gtk_list_store_append (list_store, &iter);
550       gtk_list_store_set (list_store, &iter,
551                           0, text,
552                           1, vl->value.f,
553                           -1);
554
555       g_free (text);
556       g_free (vstr);
557     }
558   free (labels);
559
560   gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->treeview),
561                           GTK_TREE_MODEL (list_store));
562
563   g_object_unref (list_store);
564
565 }
566
567 void
568 psppire_val_labs_dialog_set_variable (PsppireValLabsDialog *dialog,
569                                       const struct variable *var)
570 {
571   val_labs_destroy (dialog->labs);
572   dialog->labs = NULL;
573
574   g_free (dialog->encoding);
575   dialog->encoding = NULL;
576
577   if (var != NULL)
578     {
579       dialog->labs = val_labs_clone (var_get_value_labels (var));
580       dialog->encoding = g_strdup (var_get_encoding (var));
581       dialog->format = *var_get_print_format (var);
582     }
583   else
584     dialog->format = F_8_0;
585
586   if (dialog->labs == NULL)
587     dialog->labs = val_labs_create (var_get_width (var));
588
589   repopulate_dialog (dialog);
590 }
591
592 const struct val_labs *
593 psppire_val_labs_dialog_get_value_labels (const PsppireValLabsDialog *dialog)
594 {
595   return dialog->labs;
596 }