6126afc1dddacd5a6f6e67451c5d4977d813e36d
[pspp-builds.git] / src / ui / gui / val-labs-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2005, 2009, 2010, 2011  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 <string.h>
24
25 #include "helper.h"
26 #include "val-labs-dialog.h"
27 #include <data/value-labels.h>
28 #include <data/format.h>
29 #include "psppire-var-sheet.h"
30 #include "psppire-var-store.h"
31 #include <libpspp/i18n.h>
32
33 #include <gettext.h>
34 #define _(msgid) gettext (msgid)
35 #define N_(msgid) msgid
36
37 struct val_labs_dialog
38 {
39   GtkWidget *window;
40
41   /* The variable to be updated */
42   struct variable *pv;
43
44   /* Local copy of labels */
45   struct val_labs *labs;
46
47   /* Actions */
48   GtkWidget *add_button;
49   GtkWidget *remove_button;
50   GtkWidget *change_button;
51
52   /* Entry Boxes */
53   GtkWidget *value_entry;
54   GtkWidget *label_entry;
55
56   /* Signal handler ids */
57   gint change_handler_id;
58   gint value_handler_id;
59
60   GtkWidget *treeview;
61 };
62
63
64 /* This callback occurs when the text in the label entry box
65    is changed */
66 static void
67 on_label_entry_change (GtkEntry *entry, gpointer data)
68 {
69   union value v;
70   const gchar *text ;
71   struct val_labs_dialog *dialog = data;
72   g_assert (dialog->labs);
73
74   text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
75
76   text_to_value (text,
77                  dialog->pv,
78                  &v);
79
80   if (val_labs_find (dialog->labs, &v))
81     {
82       gtk_widget_set_sensitive (dialog->change_button, TRUE);
83       gtk_widget_set_sensitive (dialog->add_button, FALSE);
84     }
85   else
86     {
87       gtk_widget_set_sensitive (dialog->change_button, FALSE);
88       gtk_widget_set_sensitive (dialog->add_button, TRUE);
89     }
90
91   value_destroy (&v, var_get_width (dialog->pv));
92 }
93
94
95 /* Set the TREEVIEW list cursor to the item which has the value VAL */
96 static void
97 select_treeview_from_value (GtkTreeView *treeview, union value *val)
98 {
99   GtkTreePath *path ;
100
101   /*
102     We do this with a linear search through the model --- hardly
103     efficient, but the list is short ... */
104   GtkTreeIter iter;
105
106   GtkTreeModel * model  = gtk_tree_view_get_model (treeview);
107
108   gboolean success;
109   for (success = gtk_tree_model_get_iter_first (model, &iter);
110        success;
111        success = gtk_tree_model_iter_next (model, &iter))
112     {
113       union value v;
114       GValue gvalue = {0};
115
116       gtk_tree_model_get_value (model, &iter, 1, &gvalue);
117
118       v.f = g_value_get_double (&gvalue);
119
120       if ( 0 == memcmp (&v, val, sizeof (union value)))
121         {
122           break;
123         }
124     }
125
126   path = gtk_tree_model_get_path (model, &iter);
127   if ( path )
128     {
129       gtk_tree_view_set_cursor (treeview, path, 0, 0);
130       gtk_tree_path_free (path);
131     }
132
133 }
134
135
136 /* This callback occurs when the text in the value entry box is
137    changed */
138 static void
139 on_value_entry_change (GtkEntry *entry, gpointer data)
140 {
141   const char *s;
142
143   struct val_labs_dialog *dialog = data;
144
145   const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
146
147   union value v;
148   text_to_value (text,
149                  dialog->pv,
150                  &v);
151
152
153   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
154                          dialog->change_handler_id);
155
156   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),"");
157
158
159   if ( (s = val_labs_find (dialog->labs, &v)) )
160     {
161       gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), s);
162       gtk_widget_set_sensitive (dialog->add_button, FALSE);
163       gtk_widget_set_sensitive (dialog->remove_button, TRUE);
164       select_treeview_from_value (GTK_TREE_VIEW (dialog->treeview), &v);
165     }
166   else
167     {
168       gtk_widget_set_sensitive (dialog->remove_button, FALSE);
169       gtk_widget_set_sensitive (dialog->add_button, TRUE);
170     }
171
172   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
173                          dialog->change_handler_id);
174
175   value_destroy (&v, var_get_width (dialog->pv));
176 }
177
178
179 /* Callback for when the Value Labels dialog is closed using
180    the OK button.*/
181 static gint
182 val_labs_ok (GtkWidget *w, gpointer data)
183 {
184   struct val_labs_dialog *dialog = data;
185
186   var_set_value_labels (dialog->pv, dialog->labs);
187
188   val_labs_destroy (dialog->labs);
189
190   dialog->labs = NULL;
191
192   gtk_widget_hide (dialog->window);
193
194   return FALSE;
195 }
196
197 /* Callback for when the Value Labels dialog is closed using
198    the Cancel button.*/
199 static void
200 val_labs_cancel (struct val_labs_dialog *dialog)
201 {
202   val_labs_destroy (dialog->labs);
203
204   dialog->labs = NULL;
205
206   gtk_widget_hide (dialog->window);
207 }
208
209
210 /* Callback for when the Value Labels dialog is closed using
211    the Cancel button.*/
212 static void
213 on_cancel (GtkWidget *w, gpointer data)
214 {
215   struct val_labs_dialog *dialog = data;
216
217   val_labs_cancel (dialog);
218 }
219
220
221 /* Callback for when the Value Labels dialog is closed using
222    the window delete button.*/
223 static gint
224 on_delete (GtkWidget *w, GdkEvent *e, gpointer data)
225 {
226   struct val_labs_dialog *dialog = data;
227
228   val_labs_cancel (dialog);
229
230   return TRUE;
231 }
232
233
234 /* Return the value-label pair currently selected in the dialog box  */
235 static void
236 get_selected_tuple (struct val_labs_dialog *dialog,
237                     union value *valuep, const char **label)
238 {
239   GtkTreeView *treeview = GTK_TREE_VIEW (dialog->treeview);
240
241   GtkTreeIter iter ;
242   GValue the_value = {0};
243   union value value;
244
245   GtkTreeSelection* sel =  gtk_tree_view_get_selection (treeview);
246
247   GtkTreeModel * model  = gtk_tree_view_get_model (treeview);
248
249   gtk_tree_selection_get_selected (sel, &model, &iter);
250
251   gtk_tree_model_get_value (model, &iter, 1, &the_value);
252
253   value.f = g_value_get_double (&the_value);
254   g_value_unset (&the_value);
255
256   if (valuep != NULL)
257     *valuep = value;
258   if (label != NULL)
259     {
260       struct val_lab *vl = val_labs_lookup (dialog->labs, &value);
261       if (vl != NULL)
262         *label = val_lab_get_escaped_label (vl);
263     }
264 }
265
266
267 static void repopulate_dialog (struct val_labs_dialog *dialog);
268
269 /* Callback which occurs when the "Change" button is clicked */
270 static void
271 on_change (GtkWidget *w, gpointer data)
272 {
273   struct val_labs_dialog *dialog = data;
274
275   const gchar *val_text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
276
277   union value v;
278
279   text_to_value (val_text,
280                  dialog->pv,
281                  &v);
282
283   val_labs_replace (dialog->labs, &v,
284                     gtk_entry_get_text (GTK_ENTRY (dialog->label_entry)));
285
286   gtk_widget_set_sensitive (dialog->change_button, FALSE);
287
288   repopulate_dialog (dialog);
289   gtk_widget_grab_focus (dialog->value_entry);
290
291   value_destroy (&v, var_get_width (dialog->pv));
292 }
293
294 /* Callback which occurs when the "Add" button is clicked */
295 static void
296 on_add (GtkWidget *w, gpointer data)
297 {
298   struct val_labs_dialog *dialog = data;
299
300   union value v;
301
302   const gchar *text = gtk_entry_get_text (GTK_ENTRY (dialog->value_entry));
303
304   text_to_value (text,
305                  dialog->pv,
306                  &v);
307
308   if (val_labs_add (dialog->labs, &v,
309                     gtk_entry_get_text
310                     ( GTK_ENTRY (dialog->label_entry)) ) )
311     {
312       gtk_widget_set_sensitive (dialog->add_button, FALSE);
313
314       repopulate_dialog (dialog);
315       gtk_widget_grab_focus (dialog->value_entry);
316     }
317
318   value_destroy (&v, var_get_width (dialog->pv));
319 }
320
321 /* Callback which occurs when the "Remove" button is clicked */
322 static void
323 on_remove (GtkWidget *w, gpointer data)
324 {
325   struct val_labs_dialog *dialog = data;
326
327   union value value;
328   struct val_lab *vl;
329
330   get_selected_tuple (dialog, &value, NULL);
331   vl = val_labs_lookup (dialog->labs, &value);
332   if (vl != NULL)
333     val_labs_remove (dialog->labs, vl);
334
335   repopulate_dialog (dialog);
336   gtk_widget_grab_focus (dialog->value_entry);
337
338   gtk_widget_set_sensitive (dialog->remove_button, FALSE);
339 }
340
341
342
343 /* Callback which occurs when a line item is selected in the list of
344    value--label pairs.*/
345 static void
346 on_select_row (GtkTreeView *treeview, gpointer data)
347 {
348   struct val_labs_dialog *dialog = data;
349
350   union value value;
351   const char *label = NULL;
352
353   gchar *text;
354
355   get_selected_tuple (dialog, &value, &label);
356   text = value_to_text (value, dialog->pv);
357
358   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
359                          dialog->value_handler_id);
360
361   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), text);
362
363   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
364                          dialog->value_handler_id);
365   g_free (text);
366
367   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
368                          dialog->change_handler_id);
369
370
371   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry),
372                       label);
373
374   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
375                          dialog->change_handler_id);
376
377   gtk_widget_set_sensitive (dialog->remove_button, TRUE);
378   gtk_widget_set_sensitive (dialog->change_button, FALSE);
379 }
380
381
382 /* Create a new dialog box
383    (there should  normally be only one)*/
384 struct val_labs_dialog *
385 val_labs_dialog_create (GtkWindow *toplevel)
386 {
387   GtkTreeViewColumn *column;
388
389   GtkCellRenderer *renderer ;
390
391   GtkBuilder *xml = builder_new ("var-sheet-dialogs.ui");
392
393   struct val_labs_dialog *dialog = g_malloc (sizeof (*dialog));
394
395   dialog->window = get_widget_assert (xml,"val_labs_dialog");
396   dialog->value_entry = get_widget_assert (xml,"value_entry");
397   dialog->label_entry = get_widget_assert (xml,"label_entry");
398
399   gtk_window_set_transient_for
400     (GTK_WINDOW (dialog->window), toplevel);
401
402   dialog->add_button = get_widget_assert (xml, "val_labs_add");
403   dialog->remove_button = get_widget_assert (xml, "val_labs_remove");
404   dialog->change_button = get_widget_assert (xml, "val_labs_change");
405
406   dialog->treeview = get_widget_assert (xml,"treeview1");
407
408   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (dialog->treeview), FALSE);
409
410   renderer = gtk_cell_renderer_text_new ();
411
412   column = gtk_tree_view_column_new_with_attributes ("Title",
413                                                      renderer,
414                                                      "text",
415                                                      0,
416                                                      NULL);
417
418   gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->treeview), column);
419
420   g_signal_connect (get_widget_assert (xml, "val_labs_cancel"),
421                    "clicked",
422                    G_CALLBACK (on_cancel), dialog);
423
424   g_signal_connect (dialog->window, "delete-event",
425                     G_CALLBACK (on_delete), dialog);
426
427   g_signal_connect (get_widget_assert (xml, "val_labs_ok"),
428                    "clicked",
429                    G_CALLBACK (val_labs_ok), dialog);
430
431   dialog->change_handler_id =
432     g_signal_connect (dialog->label_entry,
433                      "changed",
434                      G_CALLBACK (on_label_entry_change), dialog);
435
436   dialog->value_handler_id  =
437     g_signal_connect (dialog->value_entry,
438                      "changed",
439                      G_CALLBACK (on_value_entry_change), dialog);
440
441   g_signal_connect (dialog->change_button,
442                    "clicked",
443                    G_CALLBACK (on_change), dialog);
444
445
446   g_signal_connect (dialog->treeview, "cursor-changed",
447                    G_CALLBACK (on_select_row), dialog);
448
449   g_signal_connect (dialog->remove_button, "clicked",
450                    G_CALLBACK (on_remove), dialog);
451
452   g_signal_connect (dialog->add_button, "clicked",
453                    G_CALLBACK (on_add), dialog);
454
455   dialog->labs = NULL;
456
457   g_object_unref (xml);
458
459   return dialog;
460 }
461
462
463 void
464 val_labs_dialog_set_target_variable (struct val_labs_dialog *dialog,
465                                      struct variable *var)
466 {
467   dialog->pv = var;
468 }
469
470
471
472 /* Populate the components of the dialog box, from the 'labs' member
473    variable */
474 static void
475 repopulate_dialog (struct val_labs_dialog *dialog)
476 {
477   const struct val_lab **labels;
478   size_t n_labels;
479   size_t i;
480
481   GtkTreeIter iter;
482
483   GtkListStore *list_store = gtk_list_store_new (2,
484                                                  G_TYPE_STRING,
485                                                  G_TYPE_DOUBLE);
486
487   g_signal_handler_block (GTK_ENTRY (dialog->label_entry),
488                          dialog->change_handler_id);
489   g_signal_handler_block (GTK_ENTRY (dialog->value_entry),
490                          dialog->value_handler_id);
491
492   gtk_entry_set_text (GTK_ENTRY (dialog->value_entry), "");
493   gtk_entry_set_text (GTK_ENTRY (dialog->label_entry), "");
494
495   g_signal_handler_unblock (GTK_ENTRY (dialog->value_entry),
496                          dialog->value_handler_id);
497   g_signal_handler_unblock (GTK_ENTRY (dialog->label_entry),
498                            dialog->change_handler_id);
499
500   labels = val_labs_sorted (dialog->labs);
501   n_labels = val_labs_count (dialog->labs);
502   for (i = 0; i < n_labels; i++)
503     {
504       const struct val_lab *vl = labels[i];
505
506       gchar *const vstr  =
507         value_to_text (vl->value, dialog->pv);
508
509       gchar *const text = g_strdup_printf (_("%s = `%s'"), vstr,
510                                            val_lab_get_escaped_label (vl));
511
512       gtk_list_store_append (list_store, &iter);
513       gtk_list_store_set (list_store, &iter,
514                           0, text,
515                           1, vl->value.f,
516                           -1);
517
518       g_free (text);
519       g_free (vstr);
520     }
521   free (labels);
522
523   gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->treeview),
524                           GTK_TREE_MODEL (list_store));
525
526   g_object_unref (list_store);
527
528 }
529
530 /* Initialise and display the dialog box */
531 void
532 val_labs_dialog_show (struct val_labs_dialog *dialog)
533 {
534   const struct val_labs *value_labels;
535
536   g_assert (!dialog->labs);
537
538   value_labels = var_get_value_labels (dialog->pv);
539
540   if (value_labels)
541     dialog->labs = val_labs_clone ( value_labels );
542   else
543     dialog->labs = val_labs_create ( var_get_width (dialog->pv));
544
545   gtk_widget_set_sensitive (dialog->remove_button, FALSE);
546   gtk_widget_set_sensitive (dialog->change_button, FALSE);
547   gtk_widget_set_sensitive (dialog->add_button, FALSE);
548
549   gtk_widget_grab_focus (dialog->value_entry);
550
551   repopulate_dialog (dialog);
552   gtk_widget_show (dialog->window);
553 }
554