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