Make the missing value code do more work, so that its callers can do
[pspp-builds.git] / src / ui / gui / var-sheet.c
1 /* 
2    PSPPIRE --- A Graphical User Interface for PSPP
3    Copyright (C) 2004, 2005, 2006  Free Software Foundation
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 2 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, write to the Free Software
17    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18    02110-1301, USA. */
19
20
21 /* This module creates the Variable Sheet used for inputing the
22    variables in the  dictonary */
23
24 #include <config.h>
25 #include <gettext.h>
26 #define _(msgid) gettext (msgid)
27 #define N_(msgid) msgid
28
29 #include <data/value-labels.h>
30
31 #include <glade/glade.h>
32 #include <gtk/gtk.h>
33
34 #include <stdlib.h>
35 #include <string.h>
36 #include <langinfo.h>
37
38 #include <data/value.h>
39
40 #include <gtksheet/gtksheet.h>
41 #include <gtksheet/gsheet-hetero-column.h>
42 #include <gtksheet/gsheet-uniform-row.h>
43
44 #include "psppire-var-store.h"
45 #include "helper.h"
46 #include "menu-actions.h"
47 #include "psppire-dict.h"
48 #include "var-type-dialog.h"
49 #include "var-sheet.h"
50 #include "customentry.h"
51
52 #include "val-labs-dialog.h"
53 #include "missing-val-dialog.h"
54
55
56
57 static const gint n_initial_rows = 40;
58
59
60 extern GladeXML *xml;
61
62 struct column_parameters
63 {
64   gchar label[20];  
65   gint width ;
66 };
67
68 static const struct column_parameters column_def[] = {
69   { N_("Name"),    80},
70   { N_("Type"),    100},
71   { N_("Width"),   57}, 
72   { N_("Decimals"),91}, 
73   { N_("Label"),   95}, 
74   { N_("Values"),  103},
75   { N_("Missing"), 95}, 
76   { N_("Columns"), 80}, 
77   { N_("Align"),   69}, 
78   { N_("Measure"), 99}, 
79 };
80
81
82 static gboolean
83 click2row(GtkWidget *w, gint row, gpointer data)
84 {
85   gint current_row, current_column;
86   GtkWidget *data_sheet  = get_widget_assert(xml, "data_sheet");
87
88   select_sheet(PAGE_DATA_SHEET);
89
90   gtk_sheet_get_active_cell(GTK_SHEET(data_sheet), 
91                             &current_row, &current_column);
92
93   gtk_sheet_set_active_cell(GTK_SHEET(data_sheet), current_row, row);
94
95   return FALSE;
96 }
97
98
99
100 const gchar *alignments[n_ALIGNMENTS + 1]={
101   N_("Left"),
102   N_("Right"),
103   N_("Centre"),
104   0
105 };
106
107 const gchar *measures[n_MEASURES + 1]={
108   N_("Nominal"),
109   N_("Ordinal"),
110   N_("Scale"),
111   0
112 };
113
114 static GtkListStore *
115 create_label_list(const gchar **labels)
116 {
117   const gchar *s;
118   gint i = 0;
119   GtkTreeIter iter;
120
121   GtkListStore *list_store;
122   list_store = gtk_list_store_new (1, G_TYPE_STRING);
123
124
125   while ( (s = labels[i++]))
126     {
127       gtk_list_store_append (list_store, &iter);
128       gtk_list_store_set (list_store, &iter,
129                           0, gettext(s),
130                           -1);
131     }
132         
133   return list_store;
134 }
135
136 /* Callback for when the alignment combo box 
137    item is selected */
138 static void        
139 change_alignment(GtkComboBox *cb,
140     gpointer user_data)
141 {
142   struct variable *pv = user_data;
143   gint active_item = gtk_combo_box_get_active(cb);
144
145   if ( active_item < 0 ) return ;
146
147   var_set_alignment (pv, active_item);
148 }
149
150
151
152 /* Callback for when the measure combo box 
153    item is selected */
154 static void
155 change_measure(GtkComboBox *cb,
156     gpointer user_data)
157 {
158   struct variable *pv = user_data;
159   gint active_item = gtk_combo_box_get_active(cb);
160
161   if ( active_item < 0 ) return ;
162
163   var_set_measure (pv, active_item);
164 }
165
166
167
168 static gboolean 
169 traverse_cell_callback (GtkSheet * sheet, 
170                         gint row, gint column, 
171                         gint *new_row, gint *new_column
172                         )
173 {
174   PsppireVarStore *var_store = PSPPIRE_VAR_STORE(gtk_sheet_get_model(sheet));
175
176   gint n_vars = psppire_var_store_get_var_cnt(var_store);
177
178   if ( row == n_vars && *new_row >= n_vars)
179     {
180       GtkEntry *entry = GTK_ENTRY(gtk_sheet_get_entry(sheet));
181
182       const gchar *name = gtk_entry_get_text(entry);
183
184       if (! psppire_dict_check_name(var_store->dict, name, TRUE))
185         return FALSE;
186       
187       psppire_dict_insert_variable(var_store->dict, row, name);
188
189       return TRUE;
190     }
191
192   /* If the destination cell is outside the current  variables, then
193      automatically create variables for the new rows.
194   */
195   if ( (*new_row > n_vars) || 
196        (*new_row == n_vars && *new_column != COL_NAME) ) 
197     {
198       gint i;
199       for ( i = n_vars ; i <= *new_row; ++i )
200         psppire_dict_insert_variable(var_store->dict, i, NULL);
201     }
202
203   return TRUE;
204 }
205
206
207 /* Callback whenever the cell on the var sheet is entered or left.
208    It sets the entry box type appropriately.
209 */
210 static gboolean 
211 var_sheet_cell_change_entry (GtkSheet * sheet, gint row, gint column, 
212                              gpointer leaving)
213 {
214   GtkSheetCellAttr attributes;
215   PsppireVarStore *var_store ;
216   struct variable *pv ;
217
218   g_return_val_if_fail(sheet != NULL, FALSE);
219
220   var_store = PSPPIRE_VAR_STORE(gtk_sheet_get_model(sheet));
221
222   if ( row >= psppire_var_store_get_var_cnt(var_store))
223     return TRUE;
224
225   if ( leaving ) 
226     {
227       gtk_sheet_change_entry(sheet, GTK_TYPE_ENTRY);
228       return TRUE;
229     }
230
231
232   gtk_sheet_get_attributes(sheet, row, column, &attributes);
233
234   pv = psppire_var_store_get_var (var_store, row);
235
236   switch (column)
237     {
238     case COL_ALIGN:
239       {
240         static GtkListStore *list_store = 0;
241         GtkComboBoxEntry *cbe;
242         gtk_sheet_change_entry(sheet, GTK_TYPE_COMBO_BOX_ENTRY);
243         cbe = 
244           GTK_COMBO_BOX_ENTRY(gtk_sheet_get_entry(sheet)->parent);
245
246
247         if ( ! list_store) list_store = create_label_list(alignments);
248
249         gtk_combo_box_set_model(GTK_COMBO_BOX(cbe), 
250                                 GTK_TREE_MODEL(list_store));
251
252         gtk_combo_box_entry_set_text_column (cbe, 0);
253
254
255         g_signal_connect(G_OBJECT(cbe),"changed", 
256                          G_CALLBACK(change_alignment), pv);
257       }
258       break;
259     case COL_MEASURE:
260       {
261         static GtkListStore *list_store = 0;
262         GtkComboBoxEntry *cbe;
263         gtk_sheet_change_entry(sheet, GTK_TYPE_COMBO_BOX_ENTRY);
264         cbe = 
265           GTK_COMBO_BOX_ENTRY(gtk_sheet_get_entry(sheet)->parent);
266
267
268         if ( ! list_store) list_store = create_label_list (measures);
269
270         gtk_combo_box_set_model(GTK_COMBO_BOX(cbe), 
271                                 GTK_TREE_MODEL(list_store));
272
273         gtk_combo_box_entry_set_text_column (cbe, 0);
274
275         g_signal_connect (G_OBJECT(cbe),"changed",
276                           G_CALLBACK (change_measure), pv);
277       }
278       break;
279
280     case COL_VALUES:
281       {
282         static struct val_labs_dialog *val_labs_dialog = 0;
283
284         PsppireCustomEntry *customEntry;
285
286         gtk_sheet_change_entry(sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
287
288         customEntry = 
289           PSPPIRE_CUSTOM_ENTRY(gtk_sheet_get_entry(sheet));
290
291
292         if (!val_labs_dialog ) 
293             val_labs_dialog = val_labs_dialog_create(xml);
294
295         val_labs_dialog->pv = pv;
296
297         g_signal_connect_swapped(GTK_OBJECT(customEntry),
298                                  "clicked",
299                                  GTK_SIGNAL_FUNC(val_labs_dialog_show),
300                                  val_labs_dialog);
301       }
302       break;
303     case COL_MISSING:
304       {
305         static struct missing_val_dialog *missing_val_dialog = 0;
306         PsppireCustomEntry *customEntry;
307         
308         gtk_sheet_change_entry(sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
309
310         customEntry = 
311           PSPPIRE_CUSTOM_ENTRY(gtk_sheet_get_entry(sheet));
312
313         if (!missing_val_dialog ) 
314             missing_val_dialog = missing_val_dialog_create(xml);
315
316         missing_val_dialog->pv = psppire_var_store_get_var (var_store, row);
317
318         g_signal_connect_swapped(GTK_OBJECT(customEntry),
319                                  "clicked",
320                                  GTK_SIGNAL_FUNC(missing_val_dialog_show),
321                                  missing_val_dialog);
322       }
323       break;
324
325     case COL_TYPE:
326       {
327         static struct var_type_dialog *var_type_dialog = 0;
328
329         PsppireCustomEntry *customEntry;
330
331         gtk_sheet_change_entry(sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
332
333         customEntry = 
334           PSPPIRE_CUSTOM_ENTRY(gtk_sheet_get_entry(sheet));
335
336
337         /* Popup the Variable Type dialog box */
338         if (!var_type_dialog ) 
339             var_type_dialog = var_type_dialog_create(xml);
340
341
342         var_type_dialog->pv = pv;
343
344         g_signal_connect_swapped(GTK_OBJECT(customEntry),
345                                  "clicked",
346                                  GTK_SIGNAL_FUNC(var_type_dialog_show),
347                                  var_type_dialog);
348       }
349       break;
350     case COL_WIDTH:
351     case COL_DECIMALS:
352     case COL_COLUMNS:
353       {
354         if ( attributes.is_editable) 
355           {
356             gint r_min, r_max;
357
358             const gchar *s = gtk_sheet_cell_get_text(sheet, row, column);
359
360             if (!s) 
361               return FALSE;
362
363             {
364               GtkSpinButton *spinButton ;
365               const gint current_value  = atoi(s);
366               GtkObject *adj ;
367
368               const struct fmt_spec *fmt = var_get_write_format (pv);
369               switch (column) 
370                 {
371                 case COL_WIDTH:
372                   r_min = MAX (fmt->d + 1, fmt_min_output_width (fmt->type));
373                   r_max = fmt_max_output_width (fmt->type);
374                   break;
375                 case COL_DECIMALS:
376                   r_min = 0 ; 
377                   r_max = fmt_max_output_decimals (fmt->type, fmt->w);
378                   break;
379                 case COL_COLUMNS:
380                   r_min = 1;
381                   r_max = 255 ; /* Is this a sensible value ? */
382                   break;
383                 default:
384                   g_assert_not_reached();
385                 }
386
387               adj = gtk_adjustment_new(current_value,
388                                        r_min, r_max,
389                                        1.0, 1.0, 1.0 /* steps */
390                                        );
391
392               gtk_sheet_change_entry(sheet, GTK_TYPE_SPIN_BUTTON);
393
394               spinButton = 
395                 GTK_SPIN_BUTTON(gtk_sheet_get_entry(sheet));
396
397               gtk_spin_button_set_adjustment(spinButton, GTK_ADJUSTMENT(adj));
398               gtk_spin_button_set_digits(spinButton, 0);
399             }
400           }
401       }
402       break; 
403
404     default:
405       gtk_sheet_change_entry(sheet, GTK_TYPE_ENTRY);
406       break;
407     }
408
409   return TRUE;
410 }
411
412
413 extern PsppireVarStore *var_store;
414
415
416 /* Create the var sheet */
417 GtkWidget*
418 psppire_variable_sheet_create (gchar *widget_name, 
419                                gchar *string1, 
420                                gchar *string2,
421                                gint int1, gint int2)
422 {
423   gchar *codeset;
424   gint i;
425   GtkWidget *sheet;
426
427   GObject *geo = g_sheet_hetero_column_new(75, n_COLS);
428
429   sheet = gtk_sheet_new(G_SHEET_ROW(var_store),
430                         G_SHEET_COLUMN(geo), 
431                         "variable sheet", 0); 
432
433   g_signal_connect (GTK_OBJECT (sheet), "activate",
434                     GTK_SIGNAL_FUNC (var_sheet_cell_change_entry),
435                     0);
436
437   g_signal_connect (GTK_OBJECT (sheet), "deactivate",
438                     GTK_SIGNAL_FUNC (var_sheet_cell_change_entry),
439                     (void *) 1);
440
441   g_signal_connect (GTK_OBJECT (sheet), "traverse",
442                     GTK_SIGNAL_FUNC (traverse_cell_callback), 0);
443
444
445   g_signal_connect (GTK_OBJECT (sheet), "double-click-row",
446                     GTK_SIGNAL_FUNC (click2row),
447                     sheet);
448
449   /* Since this happens inside glade_xml_new, we must prevent strings from 
450    * being re-encoded twice */
451   codeset = bind_textdomain_codeset(PACKAGE, 0);
452   bind_textdomain_codeset(PACKAGE, nl_langinfo(CODESET));
453   for (i = 0 ; i < n_COLS ; ++i ) 
454     {
455       g_sheet_hetero_column_set_button_label(G_SHEET_HETERO_COLUMN(geo), i, 
456                         gettext(column_def[i].label));
457       
458       g_sheet_hetero_column_set_width(G_SHEET_HETERO_COLUMN(geo), i, 
459                                                column_def[i].width);
460     }
461   bind_textdomain_codeset(PACKAGE, codeset);
462
463   gtk_widget_show(sheet);
464
465   return sheet;
466 }
467
468