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