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