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