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