Delete gtkextra.c
[pspp-builds.git] / src / ui / gui / psppire-var-sheet.c
1
2 /* PSPPIRE - a graphical user interface for PSPP.
3    Copyright (C) 2008 Free Software Foundation, Inc.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
17
18 #include <config.h>
19 #include "psppire-var-sheet.h"
20
21 #include <glade/glade.h>
22 #include "helper.h"
23 #include <gtksheet/gsheet-hetero-column.h>
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 static const struct column_parameters column_def[] = {
100   { N_("Name"),    80},
101   { N_("Type"),    100},
102   { N_("Width"),   57},
103   { N_("Decimals"),91},
104   { N_("Label"),   95},
105   { N_("Values"),  103},
106   { N_("Missing"), 95},
107   { N_("Columns"), 80},
108   { N_("Align"),   69},
109   { N_("Measure"), 99},
110 };
111
112
113 #define n_ALIGNMENTS 3
114
115 const gchar *const alignments[n_ALIGNMENTS + 1]={
116   N_("Left"),
117   N_("Right"),
118   N_("Center"),
119   0
120 };
121
122 const gchar *const measures[n_MEASURES + 1]={
123   N_("Nominal"),
124   N_("Ordinal"),
125   N_("Scale"),
126   0
127 };
128
129
130
131 /* Create a list store from an array of strings */
132 static GtkListStore *
133 create_label_list (const gchar *const *labels)
134 {
135   const gchar *s;
136   gint i = 0;
137   GtkTreeIter iter;
138
139   GtkListStore *list_store;
140   list_store = gtk_list_store_new (1, G_TYPE_STRING);
141
142
143   while ( (s = labels[i++]))
144     {
145       gtk_list_store_append (list_store, &iter);
146       gtk_list_store_set (list_store, &iter,
147                           0, gettext (s),
148                           -1);
149     }
150
151   return list_store;
152 }
153
154
155 static void
156 psppire_var_sheet_set_property (GObject      *object,
157                                 guint         property_id,
158                                 const GValue *value,
159                                 GParamSpec   *pspec)
160 {
161   PsppireVarSheet *self = (PsppireVarSheet *) object;
162
163   switch (property_id)
164     {
165     case PSPPIRE_VAR_SHEET_MAY_CREATE_VARS:
166       self->may_create_vars = g_value_get_boolean (value);
167       break;
168
169     default:
170       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
171       break;
172     }
173 }
174
175 static void
176 psppire_var_sheet_get_property (GObject      *object,
177                                 guint         property_id,
178                                 GValue       *value,
179                                 GParamSpec   *pspec)
180 {
181   PsppireVarSheet *self = (PsppireVarSheet *) object;
182
183   switch (property_id)
184     {
185     case PSPPIRE_VAR_SHEET_MAY_CREATE_VARS:
186       g_value_set_boolean (value, self->may_create_vars);
187       break;
188
189     default:
190       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
191       break;
192     }
193 }
194
195
196
197 static void
198 psppire_var_sheet_class_init (PsppireVarSheetClass *klass)
199 {
200   GObjectClass *object_class = G_OBJECT_CLASS (klass);
201   GParamSpec *pspec;
202
203   parent_class = g_type_class_peek_parent (klass);
204
205   object_class->dispose = psppire_var_sheet_dispose;
206   object_class->finalize = psppire_var_sheet_finalize;
207   object_class->set_property = psppire_var_sheet_set_property;
208   object_class->get_property = psppire_var_sheet_get_property;
209
210   pspec = g_param_spec_boolean ("may-create-vars",
211                                 "May create variables",
212                                 "Whether the user may create more variables",
213                                 TRUE,
214                                 G_PARAM_READWRITE);
215   g_object_class_install_property (object_class,
216                                    PSPPIRE_VAR_SHEET_MAY_CREATE_VARS,
217                                    pspec);
218
219   klass->measure_list = create_label_list (measures);
220   klass->alignment_list = create_label_list (alignments);
221 }
222
223
224
225 /* Callback for when the alignment combo box
226    item is selected */
227 static void
228 change_alignment (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_alignment (var, active_item);
236 }
237
238
239
240 /* Callback for when the measure combo box
241    item is selected */
242 static void
243 change_measure (GtkComboBox *cb,
244                 struct variable *var)
245 {
246   gint active_item = gtk_combo_box_get_active (cb);
247
248   if ( active_item < 0 ) return ;
249
250   var_set_measure (var, active_item);
251 }
252
253
254 /* Moves the focus to a new cell.
255    Returns TRUE iff the move should be disallowed */
256 static gboolean
257 traverse_cell_callback (GtkSheet *sheet,
258                         gint row, gint column,
259                         gint *new_row, gint *new_column)
260 {
261   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (sheet);
262   PsppireVarStore *var_store = PSPPIRE_VAR_STORE (gtk_sheet_get_model (sheet));
263
264   gint n_vars = psppire_var_store_get_var_cnt (var_store);
265
266   if (*new_row >= n_vars && !var_sheet->may_create_vars)
267     return TRUE;
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   /* If the destination cell is outside the current  variables, then
284      automatically create variables for the new rows.
285   */
286   if ( ((*new_row > n_vars) ||
287         (*new_row == n_vars && *new_column != PSPPIRE_VAR_STORE_COL_NAME)) )
288     {
289       gint i;
290       for ( i = n_vars ; i <= *new_row; ++i )
291         psppire_dict_insert_variable (var_store->dict, i, NULL);
292     }
293
294   return FALSE;
295 }
296
297
298
299 /*
300    Callback whenever the pointer leaves a cell on the var sheet.
301 */
302 static gboolean
303 var_sheet_cell_entry_leave (GtkSheet * sheet, gint row, gint column,
304                             gpointer data)
305 {
306   gtk_sheet_change_entry (sheet, GTK_TYPE_ENTRY);
307   return TRUE;
308 }
309
310
311 /*
312    Callback whenever the pointer enters a cell on the var sheet.
313 */
314 static gboolean
315 var_sheet_cell_entry_enter (PsppireVarSheet *vs, gint row, gint column,
316                             gpointer data)
317 {
318   GtkSheetCellAttr attributes;
319   PsppireVarStore *var_store ;
320   PsppireVarSheetClass *vs_class =
321     PSPPIRE_VAR_SHEET_CLASS(G_OBJECT_GET_CLASS (vs));
322
323   struct variable *var ;
324   GtkSheet *sheet = GTK_SHEET (vs);
325
326   g_return_val_if_fail (sheet != NULL, FALSE);
327
328   var_store = PSPPIRE_VAR_STORE (gtk_sheet_get_model (sheet));
329
330   g_assert (var_store);
331
332   if ( row >= psppire_var_store_get_var_cnt (var_store))
333     return TRUE;
334
335   gtk_sheet_get_attributes (sheet, row, column, &attributes);
336
337
338   var = psppire_var_store_get_var (var_store, row);
339
340   switch (column)
341     {
342     case PSPPIRE_VAR_STORE_COL_ALIGN:
343       {
344         static GtkListStore *list_store = NULL;
345         GtkComboBoxEntry *cbe;
346         gtk_sheet_change_entry (sheet, GTK_TYPE_COMBO_BOX_ENTRY);
347         cbe =
348           GTK_COMBO_BOX_ENTRY (gtk_sheet_get_entry (sheet)->parent);
349
350
351         if ( ! list_store) list_store = create_label_list (alignments);
352
353         gtk_combo_box_set_model (GTK_COMBO_BOX (cbe),
354                                 GTK_TREE_MODEL (vs_class->alignment_list));
355
356         gtk_combo_box_entry_set_text_column (cbe, 0);
357
358         g_signal_connect (G_OBJECT (cbe),"changed",
359                          G_CALLBACK (change_alignment), var);
360       }
361       break;
362
363     case PSPPIRE_VAR_STORE_COL_MEASURE:
364       {
365         GtkComboBoxEntry *cbe;
366         gtk_sheet_change_entry (sheet, GTK_TYPE_COMBO_BOX_ENTRY);
367         cbe =
368           GTK_COMBO_BOX_ENTRY (gtk_sheet_get_entry (sheet)->parent);
369
370
371
372         gtk_combo_box_set_model (GTK_COMBO_BOX (cbe),
373                                 GTK_TREE_MODEL (vs_class->measure_list));
374
375         gtk_combo_box_entry_set_text_column (cbe, 0);
376
377         g_signal_connect (G_OBJECT (cbe),"changed",
378                           G_CALLBACK (change_measure), var);
379       }
380       break;
381
382     case PSPPIRE_VAR_STORE_COL_VALUES:
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         val_labs_dialog_set_target_variable (vs->val_labs_dialog, var);
397
398         g_signal_connect_swapped (customEntry,
399                                   "clicked",
400                                   G_CALLBACK (val_labs_dialog_show),
401                                   vs->val_labs_dialog);
402       }
403       break;
404
405     case PSPPIRE_VAR_STORE_COL_MISSING:
406       {
407         PsppireCustomEntry *customEntry;
408
409         gtk_sheet_change_entry (sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
410
411         customEntry =
412           PSPPIRE_CUSTOM_ENTRY (gtk_sheet_get_entry (sheet));
413
414         if ( var_is_long_string (var))
415           g_object_set (customEntry,
416                         "editable", FALSE,
417                         NULL);
418
419
420         vs->missing_val_dialog->pv =
421           psppire_var_store_get_var (var_store, row);
422
423         g_signal_connect_swapped (customEntry,
424                                   "clicked",
425                                   G_CALLBACK (missing_val_dialog_show),
426                                   vs->missing_val_dialog);
427       }
428       break;
429
430     case PSPPIRE_VAR_STORE_COL_TYPE:
431       {
432         PsppireCustomEntry *customEntry;
433
434         gtk_sheet_change_entry (sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
435
436         customEntry =
437           PSPPIRE_CUSTOM_ENTRY (gtk_sheet_get_entry (sheet));
438
439
440         /* Popup the Variable Type dialog box */
441         vs->var_type_dialog->pv = var;
442
443         g_signal_connect_swapped (customEntry,
444                                  "clicked",
445                                  G_CALLBACK (var_type_dialog_show),
446                                   vs->var_type_dialog);
447       }
448       break;
449
450     case PSPPIRE_VAR_STORE_COL_WIDTH:
451     case PSPPIRE_VAR_STORE_COL_DECIMALS:
452     case PSPPIRE_VAR_STORE_COL_COLUMNS:
453       {
454         if ( attributes.is_editable)
455           {
456             gint r_min, r_max;
457
458             const gchar *s = gtk_sheet_cell_get_text (sheet, row, column);
459
460             if (s)
461               {
462                 GtkSpinButton *spinButton ;
463                 const gint current_value  = g_strtod (s, NULL);
464                 GtkObject *adj ;
465
466                 const struct fmt_spec *fmt = var_get_write_format (var);
467                 switch (column)
468                   {
469                   case PSPPIRE_VAR_STORE_COL_WIDTH:
470                     r_min = MAX (fmt->d + 1, fmt_min_output_width (fmt->type));
471                     r_max = fmt_max_output_width (fmt->type);
472                     break;
473                   case PSPPIRE_VAR_STORE_COL_DECIMALS:
474                     r_min = 0 ;
475                     r_max = fmt_max_output_decimals (fmt->type, fmt->w);
476                     break;
477                   case PSPPIRE_VAR_STORE_COL_COLUMNS:
478                     r_min = 1;
479                     r_max = 255 ; /* Is this a sensible value ? */
480                     break;
481                   default:
482                     g_assert_not_reached ();
483                   }
484
485                 adj = gtk_adjustment_new (current_value,
486                                          r_min, r_max,
487                                          1.0, 1.0, 1.0 /* steps */
488                                          );
489
490                 gtk_sheet_change_entry (sheet, GTK_TYPE_SPIN_BUTTON);
491
492                 spinButton =
493                   GTK_SPIN_BUTTON (gtk_sheet_get_entry (sheet));
494
495                 gtk_spin_button_set_adjustment (spinButton, GTK_ADJUSTMENT (adj));
496                 gtk_spin_button_set_digits (spinButton, 0);
497               }
498           }
499       }
500       break;
501
502     default:
503       gtk_sheet_change_entry (sheet, GTK_TYPE_ENTRY);
504       break;
505     }
506
507
508   return TRUE;
509 }
510
511
512
513
514 static void
515 psppire_var_sheet_init (PsppireVarSheet *vs)
516 {
517   gint i;
518   GObject *geo = g_sheet_hetero_column_new (75, PSPPIRE_VAR_STORE_n_COLS);
519   GladeXML *xml = XML_NEW ("data-editor.glade");
520
521   vs->val_labs_dialog = val_labs_dialog_create (xml);
522   vs->missing_val_dialog = missing_val_dialog_create (xml);
523   vs->var_type_dialog = var_type_dialog_create (xml);
524
525   g_object_unref (xml);
526
527   vs->dispose_has_run = FALSE;
528   vs->may_create_vars = TRUE;
529
530   for (i = 0 ; i < PSPPIRE_VAR_STORE_n_COLS ; ++i )
531     {
532       g_sheet_hetero_column_set_button_label (G_SHEET_HETERO_COLUMN (geo), i,
533                                               gettext (column_def[i].label));
534
535       g_sheet_hetero_column_set_width (G_SHEET_HETERO_COLUMN (geo), i,
536                                        column_def[i].width);
537     }
538
539   g_object_set (vs, "column-geometry", geo, NULL);
540
541
542   g_signal_connect (vs, "activate",
543                     G_CALLBACK (var_sheet_cell_entry_enter),
544                     NULL);
545
546   g_signal_connect (vs, "deactivate",
547                     G_CALLBACK (var_sheet_cell_entry_leave),
548                     NULL);
549
550   g_signal_connect (vs, "traverse",
551                     G_CALLBACK (traverse_cell_callback), NULL);
552 }
553
554
555 GtkWidget*
556 psppire_var_sheet_new (void)
557 {
558   return GTK_WIDGET (g_object_new (psppire_var_sheet_get_type (), NULL));
559 }