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