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