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