val-labs-dialog: Convert to a GObject.
[pspp] / src / ui / gui / psppire-var-sheet.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2011, 2012 Free Software Foundation, Inc.
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 #include <config.h>
18 #include "psppire-var-sheet.h"
19 #include <ui/gui/sheet/psppire-axis.h>
20
21 #include "builder-wrapper.h"
22 #include "helper.h"
23
24 #include "customentry.h"
25 #include <data/variable.h>
26 #include "data/value-labels.h"
27 #include "psppire-var-store.h"
28 #include "ui/gui/val-labs-dialog.h"
29
30 #include <gettext.h>
31 #define _(msgid) gettext (msgid)
32 #define N_(msgid) msgid
33
34
35 static void psppire_var_sheet_class_init  (PsppireVarSheetClass *klass);
36 static void psppire_var_sheet_init        (PsppireVarSheet      *vs);
37 static void psppire_var_sheet_realize     (GtkWidget *w);
38 static void psppire_var_sheet_unrealize   (GtkWidget *w);
39
40
41 enum
42   {
43     PSPPIRE_VAR_SHEET_MAY_CREATE_VARS = 1
44   };
45
46 GType
47 psppire_var_sheet_get_type (void)
48 {
49   static GType vs_type = 0;
50
51   if (!vs_type)
52     {
53       static const GTypeInfo vs_info =
54       {
55         sizeof (PsppireVarSheetClass),
56         NULL, /* base_init */
57         NULL, /* base_finalize */
58         (GClassInitFunc) psppire_var_sheet_class_init,
59         NULL, /* class_finalize */
60         NULL, /* class_data */
61         sizeof (PsppireVarSheet),
62         0,
63         (GInstanceInitFunc) psppire_var_sheet_init,
64       };
65
66       vs_type = g_type_register_static (PSPPIRE_TYPE_SHEET, "PsppireVarSheet",
67                                         &vs_info, 0);
68     }
69
70   return vs_type;
71 }
72
73 static GObjectClass * parent_class = NULL;
74
75 static void
76 psppire_var_sheet_dispose (GObject *obj)
77 {
78   PsppireVarSheet *vs = (PsppireVarSheet *)obj;
79
80   if (vs->dispose_has_run)
81     return;
82
83   /* Make sure dispose does not run twice. */
84   vs->dispose_has_run = TRUE;
85
86   /* Chain up to the parent class */
87   G_OBJECT_CLASS (parent_class)->dispose (obj);
88 }
89
90 static void
91 psppire_var_sheet_finalize (GObject *obj)
92 {
93    /* Chain up to the parent class */
94    G_OBJECT_CLASS (parent_class)->finalize (obj);
95 }
96
97
98 struct column_parameters
99 {
100   gchar label[20];
101   gint width ;
102 };
103
104 #define n_ALIGNMENTS 3
105
106 const gchar *const alignments[n_ALIGNMENTS + 1]={
107   N_("Left"),
108   N_("Right"),
109   N_("Center"),
110   0
111 };
112
113 const gchar *const measures[n_MEASURES + 1]={
114   N_("Nominal"),
115   N_("Ordinal"),
116   N_("Scale"),
117   0
118 };
119
120
121
122 /* Create a list store from an array of strings */
123 static GtkListStore *
124 create_label_list (const gchar *const *labels)
125 {
126   const gchar *s;
127   gint i = 0;
128   GtkTreeIter iter;
129
130   GtkListStore *list_store = gtk_list_store_new (1, G_TYPE_STRING);
131
132   while ( (s = labels[i++]))
133     {
134       gtk_list_store_append (list_store, &iter);
135       gtk_list_store_set (list_store, &iter,
136                           0, gettext (s),
137                           -1);
138     }
139
140   return list_store;
141 }
142
143
144 static void
145 psppire_var_sheet_set_property (GObject      *object,
146                                 guint         property_id,
147                                 const GValue *value,
148                                 GParamSpec   *pspec)
149 {
150   PsppireVarSheet *self = (PsppireVarSheet *) object;
151
152   switch (property_id)
153     {
154     case PSPPIRE_VAR_SHEET_MAY_CREATE_VARS:
155       self->may_create_vars = g_value_get_boolean (value);
156       break;
157
158     default:
159       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
160       break;
161     }
162 }
163
164 static void
165 psppire_var_sheet_get_property (GObject      *object,
166                                 guint         property_id,
167                                 GValue       *value,
168                                 GParamSpec   *pspec)
169 {
170   PsppireVarSheet *self = (PsppireVarSheet *) object;
171
172   switch (property_id)
173     {
174     case PSPPIRE_VAR_SHEET_MAY_CREATE_VARS:
175       g_value_set_boolean (value, self->may_create_vars);
176       break;
177
178     default:
179       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
180       break;
181     }
182 }
183
184
185 static void
186 psppire_var_sheet_class_init (PsppireVarSheetClass *klass)
187 {
188   GObjectClass *object_class = G_OBJECT_CLASS (klass);
189   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
190   GParamSpec *pspec;
191
192   parent_class = g_type_class_peek_parent (klass);
193
194   object_class->dispose = psppire_var_sheet_dispose;
195   object_class->finalize = psppire_var_sheet_finalize;
196   widget_class->realize = psppire_var_sheet_realize;
197   widget_class->unrealize = psppire_var_sheet_unrealize;
198   object_class->set_property = psppire_var_sheet_set_property;
199   object_class->get_property = psppire_var_sheet_get_property;
200
201   pspec = g_param_spec_boolean ("may-create-vars",
202                                 "May create variables",
203                                 "Whether the user may create more variables",
204                                 TRUE,
205                                 G_PARAM_READWRITE);
206   g_object_class_install_property (object_class,
207                                    PSPPIRE_VAR_SHEET_MAY_CREATE_VARS,
208                                    pspec);
209
210   klass->measure_list = create_label_list (measures);
211   klass->alignment_list = create_label_list (alignments);
212 }
213
214
215
216 /* Callback for when the alignment combo box
217    item is selected */
218 static void
219 change_alignment (GtkComboBox *cb,
220                   struct variable *var)
221 {
222   gint active_item = gtk_combo_box_get_active (cb);
223
224   if ( active_item < 0 ) return ;
225
226   var_set_alignment (var, active_item);
227 }
228
229
230
231 /* Callback for when the measure combo box
232    item is selected */
233 static void
234 change_measure (GtkComboBox *cb,
235                 struct variable *var)
236 {
237   gint active_item = gtk_combo_box_get_active (cb);
238
239   if ( active_item < 0 ) return ;
240
241   var_set_measure (var, active_item);
242 }
243
244
245 /* Moves the focus to a new cell.
246    Returns TRUE iff the move should be disallowed */
247 static gboolean
248 traverse_cell_callback (PsppireSheet *sheet,
249                         const PsppireSheetCell *existing_cell,
250                         PsppireSheetCell *new_cell)
251 {
252   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (sheet);
253   PsppireVarStore *var_store = PSPPIRE_VAR_STORE (psppire_sheet_get_model (sheet));
254
255   gint n_vars = psppire_var_store_get_var_cnt (var_store);
256
257   if (new_cell->col >=  PSPPIRE_VAR_STORE_n_COLS)
258     return TRUE;
259
260   if (new_cell->row >= n_vars && !var_sheet->may_create_vars)
261     return TRUE;
262
263   if ( existing_cell->row == n_vars && new_cell->row >= n_vars)
264     {
265       GtkEntry *entry = psppire_sheet_get_entry (sheet);
266
267       const gchar *name = gtk_entry_get_text (entry);
268
269       if (! psppire_dict_check_name (var_store->dictionary, name, TRUE))
270         return TRUE;
271
272       psppire_dict_insert_variable (var_store->dictionary, existing_cell->row, name);
273
274       return FALSE;
275     }
276
277
278   /* If the destination cell is outside the current  variables, then
279      automatically create variables for the new rows.
280   */
281   if ( ((new_cell->row > n_vars) ||
282         (new_cell->row == n_vars &&
283          new_cell->col != PSPPIRE_VAR_STORE_COL_NAME)) )
284     {
285       gint i;
286       for ( i = n_vars ; i <= new_cell->row; ++i )
287         psppire_dict_insert_variable (var_store->dictionary, i, NULL);
288     }
289
290   return FALSE;
291 }
292
293 static void
294 var_sheet_show_var_type_dialog (PsppireVarSheet *vs)
295 {
296   PsppireVarStore *var_store;
297   struct fmt_spec format;
298   struct variable *var;
299   gint row;
300
301   var_store = PSPPIRE_VAR_STORE (psppire_sheet_get_model (PSPPIRE_SHEET (vs)));
302
303   psppire_sheet_get_active_cell (PSPPIRE_SHEET (vs), &row, NULL);
304   var = psppire_var_store_get_var (var_store, row);
305   g_return_if_fail (var != NULL);
306
307   format = *var_get_print_format (var);
308   psppire_var_type_dialog_run (GTK_WINDOW (gtk_widget_get_toplevel (
309                                              GTK_WIDGET (vs))), &format);
310   var_set_width (var, fmt_var_width (&format));
311   var_set_both_formats (var, &format);
312 }
313
314 static void
315 var_sheet_show_val_labs_dialog (PsppireVarSheet *vs)
316 {
317   PsppireVarStore *var_store;
318   struct val_labs *labels;
319   struct variable *var;
320   gint row;
321
322   var_store = PSPPIRE_VAR_STORE (psppire_sheet_get_model (PSPPIRE_SHEET (vs)));
323
324   psppire_sheet_get_active_cell (PSPPIRE_SHEET (vs), &row, NULL);
325   var = psppire_var_store_get_var (var_store, row);
326   g_return_if_fail (var != NULL);
327
328   labels = psppire_val_labs_dialog_run (GTK_WINDOW (gtk_widget_get_toplevel (
329                                                       GTK_WIDGET (vs))), var);
330   if (labels)
331     {
332       var_set_value_labels (var, labels);
333       val_labs_destroy (labels);
334     }
335 }
336
337 /*
338    Callback whenever the active cell changes on the var sheet.
339 */
340 static void
341 var_sheet_change_active_cell (PsppireVarSheet *vs,
342                               gint row, gint column,
343                               gint oldrow, gint oldcolumn,
344                               gpointer data)
345 {
346   PsppireVarStore *var_store;
347   PsppireVarSheetClass *vs_class =
348     PSPPIRE_VAR_SHEET_CLASS(G_OBJECT_GET_CLASS (vs));
349
350   struct variable *var ;
351   PsppireSheet *sheet = PSPPIRE_SHEET (vs);
352
353   g_return_if_fail (sheet != NULL);
354
355   var_store = PSPPIRE_VAR_STORE (psppire_sheet_get_model (sheet));
356
357   g_assert (var_store);
358
359   g_return_if_fail (oldcolumn == PSPPIRE_VAR_STORE_COL_NAME ||
360                     row < psppire_var_store_get_var_cnt (var_store));
361
362   var = psppire_var_store_get_var (var_store, row);
363
364   switch (column)
365     {
366     case PSPPIRE_VAR_STORE_COL_ALIGN:
367       {
368         GtkEntry *entry;
369         static GtkListStore *list_store = NULL;
370         GtkComboBoxEntry *cbe;
371         psppire_sheet_change_entry (sheet, GTK_TYPE_COMBO_BOX_ENTRY);
372         entry = psppire_sheet_get_entry (sheet);
373         cbe = GTK_COMBO_BOX_ENTRY (GTK_WIDGET (entry)->parent);
374
375         if ( ! list_store) list_store = create_label_list (alignments);
376
377         gtk_combo_box_set_model (GTK_COMBO_BOX (cbe),
378                                 GTK_TREE_MODEL (vs_class->alignment_list));
379
380         gtk_combo_box_entry_set_text_column (cbe, 0);
381
382         g_signal_connect (cbe, "changed",
383                          G_CALLBACK (change_alignment), var);
384       }
385       break;
386
387     case PSPPIRE_VAR_STORE_COL_MEASURE:
388       {
389         GtkEntry *entry;
390         GtkComboBoxEntry *cbe;
391         psppire_sheet_change_entry (sheet, GTK_TYPE_COMBO_BOX_ENTRY);
392         entry = psppire_sheet_get_entry (sheet);
393         cbe = GTK_COMBO_BOX_ENTRY (GTK_WIDGET (entry)->parent);
394
395         gtk_combo_box_set_model (GTK_COMBO_BOX (cbe),
396                                 GTK_TREE_MODEL (vs_class->measure_list));
397
398         gtk_combo_box_entry_set_text_column (cbe, 0);
399
400         g_signal_connect (cbe, "changed",
401                           G_CALLBACK (change_measure), var);
402       }
403       break;
404
405     case PSPPIRE_VAR_STORE_COL_VALUES:
406       {
407         PsppireCustomEntry *customEntry;
408
409         psppire_sheet_change_entry (sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
410
411         customEntry =
412           PSPPIRE_CUSTOM_ENTRY (psppire_sheet_get_entry (sheet));
413
414         g_signal_connect_swapped (customEntry,
415                                   "clicked",
416                                   G_CALLBACK (var_sheet_show_val_labs_dialog),
417                                   vs);
418       }
419       break;
420
421     case PSPPIRE_VAR_STORE_COL_MISSING:
422       {
423         PsppireCustomEntry *customEntry;
424
425         psppire_sheet_change_entry (sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
426
427         customEntry =
428           PSPPIRE_CUSTOM_ENTRY (psppire_sheet_get_entry (sheet));
429
430         vs->missing_val_dialog->pv =
431           psppire_var_store_get_var (var_store, row);
432
433         g_signal_connect_swapped (customEntry,
434                                   "clicked",
435                                   G_CALLBACK (missing_val_dialog_show),
436                                   vs->missing_val_dialog);
437       }
438       break;
439
440     case PSPPIRE_VAR_STORE_COL_TYPE:
441       {
442         PsppireCustomEntry *customEntry;
443
444         psppire_sheet_change_entry (sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
445
446         customEntry =
447           PSPPIRE_CUSTOM_ENTRY (psppire_sheet_get_entry (sheet));
448
449         g_signal_connect_swapped (customEntry,
450                                  "clicked",
451                                   G_CALLBACK (var_sheet_show_var_type_dialog),
452                                   vs);
453       }
454       break;
455
456     case PSPPIRE_VAR_STORE_COL_WIDTH:
457     case PSPPIRE_VAR_STORE_COL_DECIMALS:
458     case PSPPIRE_VAR_STORE_COL_COLUMNS:
459       {
460         if ( psppire_sheet_model_is_editable (PSPPIRE_SHEET_MODEL(var_store),
461                                               row, column))
462           {
463             gint r_min, r_max;
464
465             const gchar *s = psppire_sheet_cell_get_text (sheet, row, column);
466
467             if (s)
468               {
469                 GtkSpinButton *spinButton ;
470                 const gint current_value  = g_strtod (s, NULL);
471                 GtkObject *adj ;
472
473                 const struct fmt_spec *fmt = var_get_print_format (var);
474                 switch (column)
475                   {
476                   case PSPPIRE_VAR_STORE_COL_WIDTH:
477                     r_min = MAX (fmt->d + 1, fmt_min_output_width (fmt->type));
478                     r_max = fmt_max_output_width (fmt->type);
479                     break;
480                   case PSPPIRE_VAR_STORE_COL_DECIMALS:
481                     r_min = 0 ;
482                     r_max = fmt_max_output_decimals (fmt->type, fmt->w);
483                     break;
484                   case PSPPIRE_VAR_STORE_COL_COLUMNS:
485                     r_min = 1;
486                     r_max = 255 ; /* Is this a sensible value ? */
487                     break;
488                   default:
489                     g_assert_not_reached ();
490                   }
491
492                 adj = gtk_adjustment_new (current_value,
493                                           r_min, r_max,
494                                           1.0, 1.0, /* steps */
495                                           0);
496
497                 psppire_sheet_change_entry (sheet, GTK_TYPE_SPIN_BUTTON);
498
499                 spinButton =
500                   GTK_SPIN_BUTTON (psppire_sheet_get_entry (sheet));
501
502                 gtk_spin_button_set_adjustment (spinButton, GTK_ADJUSTMENT (adj));
503                 gtk_spin_button_set_digits (spinButton, 0);
504               }
505           }
506       }
507       break;
508
509     default:
510       psppire_sheet_change_entry (sheet, GTK_TYPE_ENTRY);
511       break;
512     }
513 }
514
515
516 static void
517 psppire_var_sheet_realize (GtkWidget *w)
518 {
519   PsppireVarSheet *vs = PSPPIRE_VAR_SHEET (w);
520
521   GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (vs));
522
523   vs->missing_val_dialog = missing_val_dialog_create (GTK_WINDOW (toplevel));
524   
525   /* Chain up to the parent class */
526   GTK_WIDGET_CLASS (parent_class)->realize (w);
527 }
528
529 static void
530 psppire_var_sheet_unrealize (GtkWidget *w)
531 {
532   PsppireVarSheet *vs = PSPPIRE_VAR_SHEET (w);
533
534   g_free (vs->missing_val_dialog);
535
536   /* Chain up to the parent class */
537   GTK_WIDGET_CLASS (parent_class)->unrealize (w);
538 }
539
540
541
542 static void
543 psppire_var_sheet_init (PsppireVarSheet *vs)
544 {
545   GtkBuilder *builder = builder_new ("data-editor.ui");
546
547   connect_help (builder);
548
549   g_object_unref (builder);
550
551   vs->dispose_has_run = FALSE;
552   vs->may_create_vars = TRUE;
553
554   g_signal_connect (vs, "activate",
555                     G_CALLBACK (var_sheet_change_active_cell),
556                     NULL);
557
558   g_signal_connect (vs, "traverse",
559                     G_CALLBACK (traverse_cell_callback), NULL);
560 }
561
562
563 static const struct column_parameters column_def[] = {
564   { N_("Name"),    80},
565   { N_("Type"),    100},
566   { N_("Width"),   57},
567   { N_("Decimals"),91},
568   { N_("Label"),   95},
569   { N_("Values"),  103},
570   { N_("Missing"), 95},
571   { N_("Columns"), 80},
572   { N_("Align"),   69},
573   { N_("Measure"), 99},
574 };
575
576 GtkWidget*
577 psppire_var_sheet_new (void)
578 {
579   gint i;
580   PsppireAxis *ha = psppire_axis_new ();
581   PsppireAxis *va = psppire_axis_new ();
582
583   GtkWidget *w = g_object_new (psppire_var_sheet_get_type (), NULL);
584
585   for (i = 0 ; i < 10 ; ++i)
586     psppire_axis_append (ha, column_def[i].width);
587
588   g_object_set (va,
589                 "default-size", 25,
590                 NULL);
591
592   g_object_set (ha, "minimum-extent", 0,
593                 NULL);
594
595   g_object_set (w,
596                 "horizontal-axis", ha,
597                 "vertical-axis", va,
598                 NULL);
599
600   return w;
601 }