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