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