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