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