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