Change traverse signal to take GtkSheetCell instead of gint,gint
[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                         const GtkSheetCell *existing_cell,
258                         GtkSheetCell *new_cell)
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_cell->row >= n_vars && !var_sheet->may_create_vars)
266     return TRUE;
267
268   if ( existing_cell->row == n_vars && new_cell->row >= n_vars)
269     {
270       GtkEntry *entry = GTK_ENTRY (gtk_sheet_get_entry (sheet));
271
272       const gchar *name = gtk_entry_get_text (entry);
273
274       if (! psppire_dict_check_name (var_store->dict, name, TRUE))
275         return TRUE;
276
277       psppire_dict_insert_variable (var_store->dict, existing_cell->row, name);
278
279       return FALSE;
280     }
281
282
283   /* If the destination cell is outside the current  variables, then
284      automatically create variables for the new rows.
285   */
286   if ( ((new_cell->row > n_vars) ||
287         (new_cell->row == n_vars &&
288          new_cell->col != PSPPIRE_VAR_STORE_COL_NAME)) )
289     {
290       gint i;
291       for ( i = n_vars ; i <= new_cell->row; ++i )
292         psppire_dict_insert_variable (var_store->dict, i, NULL);
293     }
294
295   return FALSE;
296 }
297
298
299
300
301 /*
302    Callback whenever the active cell changes on the var sheet.
303 */
304 static void
305 var_sheet_change_active_cell (PsppireVarSheet *vs,
306                               gint row, gint column,
307                               gint oldrow, gint oldcolumn,
308                               gpointer data)
309 {
310   GtkSheetCellAttr attributes;
311   PsppireVarStore *var_store;
312   PsppireVarSheetClass *vs_class =
313     PSPPIRE_VAR_SHEET_CLASS(G_OBJECT_GET_CLASS (vs));
314
315   struct variable *var ;
316   GtkSheet *sheet = GTK_SHEET (vs);
317
318   g_return_if_fail (sheet != NULL);
319
320   var_store = PSPPIRE_VAR_STORE (gtk_sheet_get_model (sheet));
321
322   g_assert (var_store);
323
324   g_return_if_fail (oldcolumn == PSPPIRE_VAR_STORE_COL_NAME ||
325                     row < psppire_var_store_get_var_cnt (var_store));
326
327   gtk_sheet_get_attributes (sheet, row, column, &attributes);
328
329   var = psppire_var_store_get_var (var_store, row);
330
331   switch (column)
332     {
333     case PSPPIRE_VAR_STORE_COL_ALIGN:
334       {
335         static GtkListStore *list_store = NULL;
336         GtkComboBoxEntry *cbe;
337         gtk_sheet_change_entry (sheet, GTK_TYPE_COMBO_BOX_ENTRY);
338         cbe =
339           GTK_COMBO_BOX_ENTRY (gtk_sheet_get_entry (sheet)->parent);
340
341
342         if ( ! list_store) list_store = create_label_list (alignments);
343
344         gtk_combo_box_set_model (GTK_COMBO_BOX (cbe),
345                                 GTK_TREE_MODEL (vs_class->alignment_list));
346
347         gtk_combo_box_entry_set_text_column (cbe, 0);
348
349         g_signal_connect (G_OBJECT (cbe),"changed",
350                          G_CALLBACK (change_alignment), var);
351       }
352       break;
353
354     case PSPPIRE_VAR_STORE_COL_MEASURE:
355       {
356         GtkComboBoxEntry *cbe;
357         gtk_sheet_change_entry (sheet, GTK_TYPE_COMBO_BOX_ENTRY);
358         cbe =
359           GTK_COMBO_BOX_ENTRY (gtk_sheet_get_entry (sheet)->parent);
360
361
362
363         gtk_combo_box_set_model (GTK_COMBO_BOX (cbe),
364                                 GTK_TREE_MODEL (vs_class->measure_list));
365
366         gtk_combo_box_entry_set_text_column (cbe, 0);
367
368         g_signal_connect (G_OBJECT (cbe),"changed",
369                           G_CALLBACK (change_measure), var);
370       }
371       break;
372
373     case PSPPIRE_VAR_STORE_COL_VALUES:
374       {
375         PsppireCustomEntry *customEntry;
376
377         gtk_sheet_change_entry (sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
378
379         customEntry =
380           PSPPIRE_CUSTOM_ENTRY (gtk_sheet_get_entry (sheet));
381
382         if ( var_is_long_string (var))
383           g_object_set (customEntry,
384                         "editable", FALSE,
385                         NULL);
386
387         val_labs_dialog_set_target_variable (vs->val_labs_dialog, var);
388
389         g_signal_connect_swapped (customEntry,
390                                   "clicked",
391                                   G_CALLBACK (val_labs_dialog_show),
392                                   vs->val_labs_dialog);
393       }
394       break;
395
396     case PSPPIRE_VAR_STORE_COL_MISSING:
397       {
398         PsppireCustomEntry *customEntry;
399
400         gtk_sheet_change_entry (sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
401
402         customEntry =
403           PSPPIRE_CUSTOM_ENTRY (gtk_sheet_get_entry (sheet));
404
405         if ( var_is_long_string (var))
406           g_object_set (customEntry,
407                         "editable", FALSE,
408                         NULL);
409
410
411         vs->missing_val_dialog->pv =
412           psppire_var_store_get_var (var_store, row);
413
414         g_signal_connect_swapped (customEntry,
415                                   "clicked",
416                                   G_CALLBACK (missing_val_dialog_show),
417                                   vs->missing_val_dialog);
418       }
419       break;
420
421     case PSPPIRE_VAR_STORE_COL_TYPE:
422       {
423         PsppireCustomEntry *customEntry;
424
425         gtk_sheet_change_entry (sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
426
427         customEntry =
428           PSPPIRE_CUSTOM_ENTRY (gtk_sheet_get_entry (sheet));
429
430
431         /* Popup the Variable Type dialog box */
432         vs->var_type_dialog->pv = var;
433
434         g_signal_connect_swapped (customEntry,
435                                  "clicked",
436                                  G_CALLBACK (var_type_dialog_show),
437                                   vs->var_type_dialog);
438       }
439       break;
440
441     case PSPPIRE_VAR_STORE_COL_WIDTH:
442     case PSPPIRE_VAR_STORE_COL_DECIMALS:
443     case PSPPIRE_VAR_STORE_COL_COLUMNS:
444       {
445         if ( attributes.is_editable)
446           {
447             gint r_min, r_max;
448
449             const gchar *s = gtk_sheet_cell_get_text (sheet, row, column);
450
451             if (s)
452               {
453                 GtkSpinButton *spinButton ;
454                 const gint current_value  = g_strtod (s, NULL);
455                 GtkObject *adj ;
456
457                 const struct fmt_spec *fmt = var_get_write_format (var);
458                 switch (column)
459                   {
460                   case PSPPIRE_VAR_STORE_COL_WIDTH:
461                     r_min = MAX (fmt->d + 1, fmt_min_output_width (fmt->type));
462                     r_max = fmt_max_output_width (fmt->type);
463                     break;
464                   case PSPPIRE_VAR_STORE_COL_DECIMALS:
465                     r_min = 0 ;
466                     r_max = fmt_max_output_decimals (fmt->type, fmt->w);
467                     break;
468                   case PSPPIRE_VAR_STORE_COL_COLUMNS:
469                     r_min = 1;
470                     r_max = 255 ; /* Is this a sensible value ? */
471                     break;
472                   default:
473                     g_assert_not_reached ();
474                   }
475
476                 adj = gtk_adjustment_new (current_value,
477                                          r_min, r_max,
478                                          1.0, 1.0, 1.0 /* steps */
479                                          );
480
481                 gtk_sheet_change_entry (sheet, GTK_TYPE_SPIN_BUTTON);
482
483                 spinButton =
484                   GTK_SPIN_BUTTON (gtk_sheet_get_entry (sheet));
485
486                 gtk_spin_button_set_adjustment (spinButton, GTK_ADJUSTMENT (adj));
487                 gtk_spin_button_set_digits (spinButton, 0);
488               }
489           }
490       }
491       break;
492
493     default:
494       gtk_sheet_change_entry (sheet, GTK_TYPE_ENTRY);
495       break;
496     }
497 }
498
499
500
501
502 static void
503 psppire_var_sheet_init (PsppireVarSheet *vs)
504 {
505   gint i;
506   GObject *geo = g_sheet_hetero_column_new (75, PSPPIRE_VAR_STORE_n_COLS);
507   GladeXML *xml = XML_NEW ("data-editor.glade");
508
509   vs->val_labs_dialog = val_labs_dialog_create (xml);
510   vs->missing_val_dialog = missing_val_dialog_create (xml);
511   vs->var_type_dialog = var_type_dialog_create (xml);
512
513   g_object_unref (xml);
514
515   vs->dispose_has_run = FALSE;
516   vs->may_create_vars = TRUE;
517
518   for (i = 0 ; i < PSPPIRE_VAR_STORE_n_COLS ; ++i )
519     {
520       g_sheet_hetero_column_set_button_label (G_SHEET_HETERO_COLUMN (geo), i,
521                                               gettext (column_def[i].label));
522
523       g_sheet_hetero_column_set_width (G_SHEET_HETERO_COLUMN (geo), i,
524                                        column_def[i].width);
525     }
526
527   g_object_set (vs, "column-geometry", geo, NULL);
528
529   g_signal_connect (vs, "activate",
530                     G_CALLBACK (var_sheet_change_active_cell),
531                     NULL);
532
533   g_signal_connect (vs, "traverse",
534                     G_CALLBACK (traverse_cell_callback), NULL);
535 }
536
537
538 GtkWidget*
539 psppire_var_sheet_new (void)
540 {
541   return GTK_WIDGET (g_object_new (psppire_var_sheet_get_type (), NULL));
542 }