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