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