removed references to psppire-dialog orientation property
[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 void
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   gtk_tree_selection_get_selected (sel, &model, &iter);
296
297   gtk_tree_model_get_value (model, &iter, 1, &the_value);
298
299   value.f = g_value_get_double (&the_value);
300   g_value_unset (&the_value);
301
302   if (valuep != NULL)
303     *valuep = value;
304   if (label != NULL)
305     {
306       struct val_lab *vl = val_labs_lookup (dialog->labs, &value);
307       if (vl != NULL)
308         *label = val_lab_get_escaped_label (vl);
309     }
310 }
311
312
313 static void repopulate_dialog (PsppireValLabsDialog *dialog);
314
315 /* Callback which occurs when the "Change" button is clicked */
316 static void
317 on_change (GtkWidget *w, gpointer data)
318 {
319   PsppireValLabsDialog *dialog = data;
320
321   const gchar *val_text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
322
323   union value v;
324
325   text_to_value__ (val_text, &dialog->format, dialog->encoding, &v);
326
327   val_labs_replace (dialog->labs, &v,
328                     gtk_entry_get_text (GTK_ENTRY (dialog->label_entry)));
329
330   gtk_widget_set_sensitive (dialog->change_button, FALSE);
331
332   repopulate_dialog (dialog);
333   gtk_widget_grab_focus (dialog->value_entry);
334
335   value_destroy (&v, val_labs_get_width (dialog->labs));
336 }
337
338 /* Callback which occurs when the "Add" button is clicked */
339 static void
340 on_add (GtkWidget *w, gpointer data)
341 {
342   PsppireValLabsDialog *dialog = data;
343
344   union value v;
345
346   const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
347
348   text_to_value__ (text, &dialog->format, dialog->encoding, &v);
349
350   if (val_labs_add (dialog->labs, &v,
351                     gtk_entry_get_text
352                     ( GTK_ENTRY (dialog->label_entry)) ) )
353     {
354       gtk_widget_set_sensitive (dialog->add_button, FALSE);
355
356       repopulate_dialog (dialog);
357       gtk_widget_grab_focus (dialog->value_entry);
358     }
359
360   value_destroy (&v, val_labs_get_width (dialog->labs));
361 }
362
363 /* Callback which occurs when the "Remove" button is clicked */
364 static void
365 on_remove (GtkWidget *w, gpointer data)
366 {
367   PsppireValLabsDialog *dialog = data;
368
369   union value value;
370   struct val_lab *vl;
371
372   get_selected_tuple (dialog, &value, NULL);
373   vl = val_labs_lookup (dialog->labs, &value);
374   if (vl != NULL)
375     val_labs_remove (dialog->labs, vl);
376
377   repopulate_dialog (dialog);
378   gtk_widget_grab_focus (dialog->value_entry);
379
380   gtk_widget_set_sensitive (dialog->remove_button, FALSE);
381 }
382
383
384
385 /* Callback which occurs when a line item is selected in the list of
386    value--label pairs.*/
387 static void
388 on_select_row (GtkTreeView *treeview, gpointer data)
389 {
390   PsppireValLabsDialog *dialog = data;
391
392   union value value;
393   const char *label = NULL;
394
395   gchar *text;
396
397   get_selected_tuple (dialog, &value, &label);
398   text = value_to_text__ (value, &dialog->format, dialog->encoding);
399
400   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
401                          dialog->value_handler_id);
402
403   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), text);
404
405   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
406                          dialog->value_handler_id);
407   g_free (text);
408
409   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
410                          dialog->change_handler_id);
411
412
413   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),
414                       label);
415
416   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
417                          dialog->change_handler_id);
418
419   gtk_widget_set_sensitive (dialog->remove_button, TRUE);
420   gtk_widget_set_sensitive (dialog->change_button, FALSE);
421 }
422
423
424 /* Create a new dialog box
425    (there should  normally be only one)*/
426 static GObject *
427 psppire_val_labs_dialog_constructor (GType                  type,
428                                      guint                  n_properties,
429                                      GObjectConstructParam *properties)
430 {
431   PsppireValLabsDialog *dialog;
432   GtkTreeViewColumn *column;
433
434   GtkCellRenderer *renderer ;
435
436   GtkBuilder *xml = builder_new ("val-labs-dialog.ui");
437
438   GtkContainer *content_area;
439   GObject *obj;
440
441   obj = G_OBJECT_CLASS (psppire_val_labs_dialog_parent_class)->constructor (
442     type, n_properties, properties);
443   dialog = PSPPIRE_VAL_LABS_DIALOG (obj);
444
445   content_area = GTK_CONTAINER (PSPPIRE_DIALOG (dialog));
446   gtk_container_add (GTK_CONTAINER (content_area),
447                      get_widget_assert (xml, "val-labs-dialog"));
448
449   dialog->value_entry = get_widget_assert (xml,"value_entry");
450   dialog->label_entry = get_widget_assert (xml,"label_entry");
451
452   dialog->add_button = get_widget_assert (xml, "val_labs_add");
453   dialog->remove_button = get_widget_assert (xml, "val_labs_remove");
454   dialog->change_button = get_widget_assert (xml, "val_labs_change");
455
456   dialog->treeview = get_widget_assert (xml,"treeview1");
457
458   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (dialog->treeview), FALSE);
459
460   renderer = gtk_cell_renderer_text_new ();
461
462   column = gtk_tree_view_column_new_with_attributes ("Title",
463                                                      renderer,
464                                                      "text",
465                                                      0,
466                                                      NULL);
467
468   gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->treeview), column);
469
470   dialog->change_handler_id =
471     g_signal_connect (dialog->label_entry,
472                      "changed",
473                      G_CALLBACK (on_label_entry_change), dialog);
474
475   dialog->value_handler_id  =
476     g_signal_connect (dialog->value_entry,
477                      "changed",
478                      G_CALLBACK (on_value_entry_change), dialog);
479
480   g_signal_connect (dialog->change_button,
481                    "clicked",
482                    G_CALLBACK (on_change), dialog);
483
484
485   g_signal_connect (dialog->treeview, "cursor-changed",
486                    G_CALLBACK (on_select_row), dialog);
487
488   g_signal_connect (dialog->remove_button, "clicked",
489                    G_CALLBACK (on_remove), dialog);
490
491   g_signal_connect (dialog->add_button, "clicked",
492                    G_CALLBACK (on_add), dialog);
493
494   dialog->labs = NULL;
495
496   g_object_unref (xml);
497
498   return obj;
499 }
500
501
502 /* Populate the components of the dialog box, from the 'labs' member
503    variable */
504 static void
505 repopulate_dialog (PsppireValLabsDialog *dialog)
506 {
507   const struct val_lab **labels;
508   size_t n_labels;
509   size_t i;
510
511   GtkTreeIter iter;
512
513   GtkListStore *list_store = gtk_list_store_new (2,
514                                                  G_TYPE_STRING,
515                                                  G_TYPE_DOUBLE);
516
517   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
518                          dialog->change_handler_id);
519   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
520                          dialog->value_handler_id);
521
522   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), "");
523   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), "");
524
525   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
526                          dialog->value_handler_id);
527   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
528                            dialog->change_handler_id);
529
530   labels = val_labs_sorted (dialog->labs);
531   n_labels = val_labs_count (dialog->labs);
532   for (i = 0; i < n_labels; i++)
533     {
534       const struct val_lab *vl = labels[i];
535
536       gchar *const vstr  =
537         value_to_text__ (vl->value, &dialog->format, dialog->encoding);
538
539       gchar *const text = g_strdup_printf (_("%s = `%s'"), vstr,
540                                            val_lab_get_escaped_label (vl));
541
542       gtk_list_store_append (list_store, &iter);
543       gtk_list_store_set (list_store, &iter,
544                           0, text,
545                           1, vl->value.f,
546                           -1);
547
548       g_free (text);
549       g_free (vstr);
550     }
551   free (labels);
552
553   gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->treeview),
554                           GTK_TREE_MODEL (list_store));
555
556   g_object_unref (list_store);
557
558 }
559
560 void
561 psppire_val_labs_dialog_set_variable (PsppireValLabsDialog *dialog,
562                                       const struct variable *var)
563 {
564   val_labs_destroy (dialog->labs);
565   dialog->labs = NULL;
566
567   g_free (dialog->encoding);
568   dialog->encoding = NULL;
569
570   if (var != NULL)
571     {
572       dialog->labs = val_labs_clone (var_get_value_labels (var));
573       dialog->encoding = g_strdup (var_get_encoding (var));
574       dialog->format = *var_get_print_format (var);
575     }
576   else
577     dialog->format = F_8_0;
578
579   if (dialog->labs == NULL)
580     dialog->labs = val_labs_create (var_get_width (var));
581
582   repopulate_dialog (dialog);
583 }
584
585 const struct val_labs *
586 psppire_val_labs_dialog_get_value_labels (const PsppireValLabsDialog *dialog)
587 {
588   return dialog->labs;
589 }