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