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