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