a06db0b7cd0556ed0d1a3c58a9ab5ae4081aea33
[pspp] / src / ui / gui / psppire-var-sheet.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
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 #include <config.h>
18
19 #include "ui/gui/psppire-var-sheet.h"
20
21 #include "data/format.h"
22 #include "data/value-labels.h"
23 #include "libpspp/range-set.h"
24 #include "ui/gui/builder-wrapper.h"
25 #include "ui/gui/helper.h"
26 #include "ui/gui/missing-val-dialog.h"
27 #include "ui/gui/pspp-sheet-selection.h"
28 #include "ui/gui/psppire-cell-renderer-button.h"
29 #include "ui/gui/psppire-data-editor.h"
30 #include "ui/gui/psppire-data-window.h"
31 #include "ui/gui/psppire-dialog-action-var-info.h"
32 #include "ui/gui/psppire-dictview.h"
33 #include "ui/gui/psppire-empty-list-store.h"
34 #include "ui/gui/psppire-marshal.h"
35 #include "ui/gui/val-labs-dialog.h"
36 #include "ui/gui/var-type-dialog.h"
37 #include "ui/gui/var-display.h"
38 #include "ui/gui/var-type-dialog.h"
39
40 #include "gl/intprops.h"
41
42 #include <gettext.h>
43 #define _(msgid) gettext (msgid)
44 #define N_(msgid) msgid
45
46 enum vs_column
47   {
48     VS_NAME,
49     VS_TYPE,
50     VS_WIDTH,
51     VS_DECIMALS,
52     VS_LABEL,
53     VS_VALUES,
54     VS_MISSING,
55     VS_COLUMNS,
56     VS_ALIGN,
57     VS_MEASURE,
58     VS_ROLE
59   };
60
61 G_DEFINE_TYPE (PsppireVarSheet, psppire_var_sheet, PSPP_TYPE_SHEET_VIEW);
62
63 static void
64 set_spin_cell (GtkCellRenderer *cell, int value, int min, int max, int step)
65 {
66   char text[INT_BUFSIZE_BOUND (int)];
67   GtkAdjustment *adjust;
68
69   if (max > min)
70     adjust = GTK_ADJUSTMENT (gtk_adjustment_new (value, min, max,
71                                                  step, step, 0.0));
72   else
73     adjust = NULL;
74
75   sprintf (text, "%d", value);
76   g_object_set (cell,
77                 "text", text,
78                 "adjustment", adjust,
79                 "editable", TRUE,
80                 NULL);
81 }
82
83 static void
84 error_dialog (GtkWindow *w, gchar *primary_text, gchar *secondary_text)
85 {
86   GtkWidget *dialog =
87     gtk_message_dialog_new (w,
88                             GTK_DIALOG_DESTROY_WITH_PARENT,
89                             GTK_MESSAGE_ERROR,
90                             GTK_BUTTONS_CLOSE, "%s", primary_text);
91
92   g_object_set (dialog, "icon-name", "psppicon", NULL);
93
94   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
95                                             "%s", secondary_text);
96
97   gtk_dialog_run (GTK_DIALOG (dialog));
98
99   gtk_widget_destroy (dialog);
100 }
101
102 static void
103 on_name_column_editing_started (GtkCellRenderer *cell,
104                                 GtkCellEditable *editable,
105                                 const gchar     *path,
106                                 gpointer         user_data)
107 {
108   PsppireVarSheet *var_sheet = g_object_get_data (G_OBJECT (cell), "var-sheet");
109   PsppireDict *dict = psppire_var_sheet_get_dictionary (var_sheet);
110
111   g_return_if_fail (var_sheet);
112           g_return_if_fail (dict);
113
114   if (GTK_IS_ENTRY (editable))
115     {
116       GtkEntry *entry = GTK_ENTRY (editable);
117       if (gtk_entry_get_text (entry)[0] == '\0')
118         {
119           gchar name[64];
120           if (psppire_dict_generate_name (dict, name, sizeof name))
121             gtk_entry_set_text (entry, name);
122         }
123     }
124 }
125
126 static void
127 scroll_to_bottom (GtkWidget      *widget,
128                   GtkRequisition *requisition,
129                   gpointer        unused UNUSED)
130 {
131   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
132   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
133   GtkAdjustment *vadjust;
134
135   vadjust = pspp_sheet_view_get_vadjustment (sheet_view);
136   gtk_adjustment_set_value (vadjust, gtk_adjustment_get_upper (vadjust));
137
138   if (var_sheet->scroll_to_bottom_signal)
139     {
140       g_signal_handler_disconnect (var_sheet,
141                                    var_sheet->scroll_to_bottom_signal);
142       var_sheet->scroll_to_bottom_signal = 0;
143     }
144 }
145
146 static struct variable *
147 path_string_to_var (PsppireVarSheet *var_sheet, gchar *path_string)
148 {
149   GtkTreePath *path;
150   gint row;
151
152   path = gtk_tree_path_new_from_string (path_string);
153   row = gtk_tree_path_get_indices (path)[0];
154   gtk_tree_path_free (path);
155
156   return psppire_dict_get_variable (var_sheet->dict, row);
157 }
158
159 static void
160 on_var_column_edited (GtkCellRendererText *cell,
161                       gchar               *path_string,
162                       gchar               *new_text,
163                       gpointer             user_data)
164 {
165   PsppireVarSheet *var_sheet = user_data;
166   GtkWindow *window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (var_sheet)));
167   struct dictionary *dict = var_sheet->dict->dict;
168   enum vs_column column_id;
169   struct variable *var;
170   int width, decimals;
171
172   column_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cell),
173                                                   "column-id"));
174
175   var = path_string_to_var (var_sheet, path_string);
176   if (var == NULL)
177     {
178       g_return_if_fail (column_id == VS_NAME);
179
180       if (!dict_id_is_valid (dict, new_text, false))
181         error_dialog (window,
182                       g_strdup (_("Cannot create variable.")),
183                       g_strdup_printf (_("\"%s\" is not a valid variable "
184                                          "name."), new_text));
185       else if (dict_lookup_var (dict, new_text) != NULL)
186         error_dialog (window,
187                       g_strdup (_("Cannot create variable.")),
188                       g_strdup_printf (_("This dictionary already contains "
189                                          "a variable named \"%s\"."),
190                                          new_text));
191       else
192         {
193           dict_create_var (var_sheet->dict->dict, new_text, 0);
194           if (!var_sheet->scroll_to_bottom_signal)
195             {
196               gtk_widget_queue_resize (GTK_WIDGET (var_sheet));
197               var_sheet->scroll_to_bottom_signal =
198                 g_signal_connect (var_sheet, "size-request",
199                                   G_CALLBACK (scroll_to_bottom), NULL);
200             }
201         }
202
203       return;
204     }
205
206   switch (column_id)
207     {
208     case VS_NAME:
209       if (!dict_id_is_valid (dict, new_text, false))
210         error_dialog (window,
211                       g_strdup (_("Cannot rename variable.")),
212                       g_strdup_printf (_("\"%s\" is not a valid variable "
213                                          "name."), new_text));
214       else if (dict_lookup_var (dict, new_text) != NULL
215                && dict_lookup_var (dict, new_text) != var)
216         error_dialog (window,
217                       g_strdup (_("Cannot rename variable.")),
218                       g_strdup_printf (_("This dictionary already contains "
219                                          "a variable named \"%s\"."),
220                                          new_text));
221       else
222         dict_rename_var (dict, var, new_text);
223       break;
224
225     case VS_TYPE:
226       /* Not reachable. */
227       break;
228
229     case VS_WIDTH:
230       width = atoi (new_text);
231       if (width > 0)
232         {
233           struct fmt_spec format;
234
235           format = *var_get_print_format (var);
236           fmt_change_width (&format, width, var_sheet->format_use);
237           var_set_width (var, fmt_var_width (&format));
238           var_set_both_formats (var, &format);
239         }
240       break;
241
242     case VS_DECIMALS:
243       decimals = atoi (new_text);
244       if (decimals >= 0)
245         {
246           struct fmt_spec format;
247
248           format = *var_get_print_format (var);
249           fmt_change_decimals (&format, decimals, var_sheet->format_use);
250           var_set_print_format (var, &format);
251         }
252       break;
253
254     case VS_LABEL:
255       var_set_label (var, new_text);
256       break;
257
258     case VS_VALUES:
259     case VS_MISSING:
260       break;
261
262     case VS_COLUMNS:
263       width = atoi (new_text);
264       if (width > 0 && width < 2 * MAX_STRING)
265         var_set_display_width (var, width);
266       break;
267
268     case VS_ALIGN:
269       if (!strcmp (new_text, alignment_to_string (ALIGN_LEFT)))
270         var_set_alignment (var, ALIGN_LEFT);
271       else if (!strcmp (new_text, alignment_to_string (ALIGN_CENTRE)))
272         var_set_alignment (var, ALIGN_CENTRE);
273       else if (!strcmp (new_text, alignment_to_string (ALIGN_RIGHT)))
274         var_set_alignment (var, ALIGN_RIGHT);
275       break;
276
277     case VS_MEASURE:
278       if (!strcmp (new_text, measure_to_string (MEASURE_NOMINAL)))
279         var_set_measure (var, MEASURE_NOMINAL);
280       else if (!strcmp (new_text, measure_to_string (MEASURE_ORDINAL)))
281         var_set_measure (var, MEASURE_ORDINAL);
282       else if (!strcmp (new_text, measure_to_string (MEASURE_SCALE)))
283         var_set_measure (var, MEASURE_SCALE);
284       break;
285
286     case VS_ROLE:
287       if (!strcmp (new_text, var_role_to_string (ROLE_INPUT)))
288         var_set_role (var, ROLE_INPUT);
289       else if (!strcmp (new_text, var_role_to_string (ROLE_TARGET)))
290         var_set_role (var, ROLE_TARGET);
291       else if (!strcmp (new_text, var_role_to_string (ROLE_BOTH)))
292         var_set_role (var, ROLE_BOTH);
293       else if (!strcmp (new_text, var_role_to_string (ROLE_NONE)))
294         var_set_role (var, ROLE_NONE);
295       else if (!strcmp (new_text, var_role_to_string (ROLE_PARTITION)))
296         var_set_role (var, ROLE_PARTITION);
297       else if (!strcmp (new_text, var_role_to_string (ROLE_SPLIT)))
298         var_set_role (var, ROLE_SPLIT);
299       break;
300     }
301 }
302
303 static void
304 render_popup_cell (PsppSheetViewColumn *tree_column,
305                    GtkCellRenderer *cell,
306                    GtkTreeModel *model,
307                    GtkTreeIter *iter,
308                    void *user_data)
309 {
310   PsppireVarSheet *var_sheet = user_data;
311   gint row;
312
313   row = GPOINTER_TO_INT (iter->user_data);
314   g_object_set (cell,
315                 "editable", row < psppire_dict_get_var_cnt (var_sheet->dict),
316                 NULL);
317 }
318
319 const char *
320 get_var_align_stock_id (enum alignment alignment)
321 {
322   switch (alignment)
323     {
324     case ALIGN_LEFT: return "align-left";
325     case ALIGN_CENTRE: return "align-center";
326     case ALIGN_RIGHT: return "align-right";
327     default:
328       g_return_val_if_reached ("");
329     }
330 }
331
332 const char *
333 get_var_role_stock_id (enum var_role role)
334 {
335   switch (role)
336     {
337     case ROLE_INPUT: return "role-input";
338     case ROLE_TARGET: return "role-target";
339     case ROLE_BOTH: return "role-both";
340     case ROLE_NONE: return "role-none";
341     case ROLE_PARTITION: return "role-partition";
342     case ROLE_SPLIT: return "role-split";
343     default:
344       g_return_val_if_reached ("");
345     }
346 }
347
348 static void
349 render_var_cell (PsppSheetViewColumn *tree_column,
350                  GtkCellRenderer *cell,
351                  GtkTreeModel *model,
352                  GtkTreeIter *iter,
353                  void *user_data)
354 {
355   PsppireVarSheet *var_sheet = user_data;
356   const struct fmt_spec *print;
357   enum vs_column column_id;
358   struct variable *var;
359   gint row;
360
361   column_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
362                                                   "column-number")) - 1;
363   row = GPOINTER_TO_INT (iter->user_data);
364
365   gtk_cell_renderer_set_visible (cell, true);
366   if (row >= psppire_dict_get_var_cnt (var_sheet->dict))
367     {
368       if (GTK_IS_CELL_RENDERER_TEXT (cell))
369         {
370           g_object_set (cell,
371                         "text", "",
372                         "editable", column_id == VS_NAME,
373                         NULL);
374           if (column_id == VS_WIDTH
375               || column_id == VS_DECIMALS
376               || column_id == VS_COLUMNS)
377             g_object_set (cell, "adjustment", NULL, NULL);
378         }
379       else
380         {
381           gtk_cell_renderer_set_visible (cell, false);
382         }
383       return;
384     }
385
386   var = psppire_dict_get_variable (var_sheet->dict, row);
387
388   print = var_get_print_format (var);
389   switch (column_id)
390     {
391     case VS_NAME:
392       g_object_set (cell,
393                     "text", var_get_name (var),
394                     "editable", TRUE,
395                     NULL);
396       break;
397
398     case VS_TYPE:
399       g_object_set (cell,
400                     "text", fmt_gui_name (print->type),
401                     "editable", FALSE,
402                     NULL);
403       break;
404
405     case VS_WIDTH:
406       set_spin_cell (cell, print->w,
407                      fmt_min_width (print->type, var_sheet->format_use),
408                      fmt_max_width (print->type, var_sheet->format_use),
409                      fmt_step_width (print->type));
410       break;
411
412     case VS_DECIMALS:
413       if (fmt_takes_decimals (print->type))
414         {
415           int max_w = fmt_max_width (print->type, var_sheet->format_use);
416           int max_d = fmt_max_decimals (print->type, max_w,
417                                         var_sheet->format_use);
418           set_spin_cell (cell, print->d, 0, max_d, 1);
419         }
420       else
421         g_object_set (cell,
422                       "text", "",
423                       "editable", FALSE,
424                       "adjustment", NULL,
425                       NULL);
426       break;
427
428     case VS_LABEL:
429       g_object_set (cell,
430                     "text", var_has_label (var) ? var_get_label (var) : "",
431                     "editable", TRUE,
432                     NULL);
433       break;
434
435     case VS_VALUES:
436       g_object_set (cell, "editable", FALSE, NULL);
437       if ( ! var_has_value_labels (var))
438         g_object_set (cell, "text", _("None"), NULL);
439       else
440         {
441           const struct val_labs *vls = var_get_value_labels (var);
442           const struct val_lab **labels = val_labs_sorted (vls);
443           const struct val_lab *vl = labels[0];
444           gchar *vstr = value_to_text (vl->value, var);
445           char *text = xasprintf (_("{%s, %s}..."), vstr,
446                                   val_lab_get_escaped_label (vl));
447           free (vstr);
448
449           g_object_set (cell, "text", text, NULL);
450           free (text);
451           free (labels);
452         }
453       break;
454
455     case VS_MISSING:
456       {
457         char *text = missing_values_to_string (var, NULL);
458         g_object_set (cell,
459                       "text", text,
460                       "editable", FALSE,
461                       NULL);
462         free (text);
463       }
464       break;
465
466     case VS_COLUMNS:
467       set_spin_cell (cell, var_get_display_width (var), 1, 2 * MAX_STRING, 1);
468       break;
469
470     case VS_ALIGN:
471       if (GTK_IS_CELL_RENDERER_TEXT (cell))
472         g_object_set (cell,
473                       "text", alignment_to_string (var_get_alignment (var)),
474                       "editable", TRUE,
475                       NULL);
476       else
477         g_object_set (cell, "stock-id",
478                       get_var_align_stock_id (var_get_alignment (var)), NULL);
479       break;
480
481     case VS_MEASURE:
482       if (GTK_IS_CELL_RENDERER_TEXT (cell))
483         g_object_set (cell,
484                       "text", measure_to_string (var_get_measure (var)),
485                       "editable", TRUE,
486                       NULL);
487       else
488         {
489           enum fmt_type type = var_get_print_format (var)->type;
490           enum measure measure = var_get_measure (var);
491
492           g_object_set (cell, "stock-id",
493                         get_var_measurement_stock_id (type, measure),
494                         NULL);
495         }
496       break;
497
498     case VS_ROLE:
499       if (GTK_IS_CELL_RENDERER_TEXT (cell))
500         g_object_set (cell,
501                       "text", var_role_to_string (var_get_role (var)),
502                       "editable", TRUE,
503                       NULL);
504       else
505         g_object_set (cell, "stock-id",
506                       get_var_role_stock_id (var_get_role (var)), NULL);
507       break;
508     }
509 }
510
511 static struct variable *
512 path_string_to_variable (PsppireVarSheet *var_sheet, gchar *path_string)
513 {
514   PsppireDict *dict;
515   GtkTreePath *path;
516   gint row;
517
518   path = gtk_tree_path_new_from_string (path_string);
519   row = gtk_tree_path_get_indices (path)[0];
520   gtk_tree_path_free (path);
521
522   dict = psppire_var_sheet_get_dictionary (var_sheet);
523   g_return_val_if_fail (dict != NULL, NULL);
524
525   return psppire_dict_get_variable (dict, row);
526 }
527
528 static void
529 on_type_click (PsppireCellRendererButton *cell,
530                gchar *path,
531                PsppireVarSheet *var_sheet)
532 {
533   GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (var_sheet));
534   struct fmt_spec format;
535   struct variable *var;
536
537   var = path_string_to_variable (var_sheet, path);
538   g_return_if_fail (var != NULL);
539
540   format = *var_get_print_format (var);
541   psppire_var_type_dialog_run (GTK_WINDOW (toplevel), &format);
542
543   var_set_width_and_formats (var, fmt_var_width (&format), &format, &format);
544 }
545
546 static void
547 on_value_labels_click (PsppireCellRendererButton *cell,
548                        gchar *path,
549                        PsppireVarSheet *var_sheet)
550 {
551   GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (var_sheet));
552   struct val_labs *labels;
553   struct variable *var;
554
555   var = path_string_to_variable (var_sheet, path);
556   g_return_if_fail (var != NULL);
557
558   labels = psppire_val_labs_dialog_run (GTK_WINDOW (toplevel), var);
559   if (labels)
560     {
561       var_set_value_labels (var, labels);
562       val_labs_destroy (labels);
563     }
564 }
565
566 static void
567 on_missing_values_click (PsppireCellRendererButton *cell,
568                          gchar *path,
569                          PsppireVarSheet *var_sheet)
570 {
571   GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (var_sheet));
572   struct missing_values mv;
573   struct variable *var;
574
575   var = path_string_to_variable (var_sheet, path);
576   g_return_if_fail (var != NULL);
577
578   psppire_missing_val_dialog_run (GTK_WINDOW (toplevel), var, &mv);
579   var_set_missing_values (var, &mv);
580   mv_destroy (&mv);
581 }
582
583 static gint
584 get_string_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
585                   const char *string)
586 {
587   gint width;
588   g_object_set (G_OBJECT (renderer),
589                 PSPPIRE_IS_CELL_RENDERER_BUTTON (renderer) ? "label" : "text",
590                 string, (void *) NULL);
591   gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
592                               NULL, NULL, NULL, &width, NULL);
593   return width;
594 }
595
596 static gint
597 get_monospace_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
598                      size_t char_cnt)
599 {
600   struct string s;
601   gint width;
602
603   ds_init_empty (&s);
604   ds_put_byte_multiple (&s, '0', char_cnt);
605   ds_put_byte (&s, ' ');
606   width = get_string_width (treeview, renderer, ds_cstr (&s));
607   ds_destroy (&s);
608
609   return width;
610 }
611
612 static PsppSheetViewColumn *
613 add_var_sheet_column (PsppireVarSheet *var_sheet, GtkCellRenderer *renderer,
614                       enum vs_column column_id,
615                       const char *title, int width)
616 {
617   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
618   int title_width, content_width;
619   PsppSheetViewColumn *column;
620
621   column = pspp_sheet_view_column_new_with_attributes (title, renderer, NULL);
622   g_object_set_data (G_OBJECT (column), "column-number",
623                      GINT_TO_POINTER (column_id) + 1);
624
625   pspp_sheet_view_column_set_cell_data_func (
626     column, renderer, render_var_cell, var_sheet, NULL);
627
628   title_width = get_string_width (sheet_view, renderer, title);
629   content_width = get_monospace_width (sheet_view, renderer, width);
630   g_object_set_data (G_OBJECT (column), "content-width",
631                      GINT_TO_POINTER (content_width));
632
633   pspp_sheet_view_column_set_fixed_width (column,
634                                           MAX (title_width, content_width));
635   pspp_sheet_view_column_set_resizable (column, TRUE);
636
637   pspp_sheet_view_append_column (sheet_view, column);
638
639   g_signal_connect (renderer, "edited",
640                     G_CALLBACK (on_var_column_edited),
641                     var_sheet);
642   g_object_set_data (G_OBJECT (renderer), "column-id",
643                      GINT_TO_POINTER (column_id));
644   g_object_set_data (G_OBJECT (renderer), "var-sheet", var_sheet);
645
646   return column;
647 }
648
649 static PsppSheetViewColumn *
650 add_text_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
651                  const char *title, int width)
652 {
653   return add_var_sheet_column (var_sheet, gtk_cell_renderer_text_new (),
654                                column_id, title, width);
655 }
656
657 static PsppSheetViewColumn *
658 add_spin_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
659                  const char *title, int width)
660 {
661   return add_var_sheet_column (var_sheet, gtk_cell_renderer_spin_new (),
662                                column_id, title, width);
663 }
664
665 static const char *
666 measure_to_stock_id (enum fmt_type type, int measure)
667 {
668   return get_var_measurement_stock_id (type, measure);
669 }
670
671 static const char *
672 alignment_to_stock_id (enum fmt_type type, int alignment)
673 {
674   return get_var_align_stock_id (alignment);
675 }
676
677 static const char *
678 role_to_stock_id (enum fmt_type type, int role)
679 {
680   return get_var_role_stock_id (role);
681 }
682
683 static void
684 render_var_pixbuf (GtkCellLayout *cell_layout,
685                    GtkCellRenderer *cell,
686                    GtkTreeModel *tree_model,
687                    GtkTreeIter *iter,
688                    gpointer data)
689 {
690   const char *(*value_to_stock_id) (enum fmt_type, int value);
691   enum fmt_type type = GPOINTER_TO_INT (data);
692   gint value;
693
694   value_to_stock_id = g_object_get_data (G_OBJECT (cell), "value-to-stock-id");
695
696   gtk_tree_model_get (tree_model, iter, 0, &value, -1);
697   g_object_set (cell, "stock-id", value_to_stock_id (type, value), NULL);
698 }
699
700 static void
701 on_combo_editing_started (GtkCellRenderer *renderer,
702                           GtkCellEditable *editable,
703                           gchar           *path_string,
704                           gpointer         user_data)
705 {
706   PsppireVarSheet *var_sheet = user_data;
707
708   if (GTK_IS_COMBO_BOX (editable))
709     {
710       struct variable *var = path_string_to_variable (var_sheet, path_string);
711       const struct fmt_spec *format = var_get_print_format (var);
712       const char *(*value_to_stock_id) (enum fmt_type, int value);
713       GtkCellRenderer *cell;
714
715       value_to_stock_id = g_object_get_data (G_OBJECT (renderer),
716                                              "value-to-stock-id");
717
718       cell = gtk_cell_renderer_pixbuf_new ();
719       g_object_set (cell, "width", 16, "height", 16, NULL);
720       g_object_set_data (G_OBJECT (cell),
721                          "value-to-stock-id", value_to_stock_id);
722       gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (editable), cell, FALSE);
723       gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (editable), cell,
724                                           render_var_pixbuf,
725                                           GINT_TO_POINTER (format->type),
726                                           NULL);
727     }
728 }
729
730 static void
731 add_combo_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
732                   const char *title, int width,
733                   const char *(*value_to_stock_id) (enum fmt_type, int value),
734                   ...)
735 {
736   PsppSheetViewColumn *column;
737   GtkCellRenderer *cell;
738   GtkListStore *store;
739   const char *name;
740   va_list args;
741
742   store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
743   va_start (args, value_to_stock_id);
744   while ((name = va_arg (args, const char *)) != NULL)
745     {
746       int value = va_arg (args, int);
747       gtk_list_store_insert_with_values (store, NULL, G_MAXINT,
748                                          0, value,
749                                          1, name,
750                                          -1);
751     }
752   va_end (args);
753
754   cell = gtk_cell_renderer_combo_new ();
755   g_object_set (cell,
756                 "has-entry", FALSE,
757                 "model", GTK_TREE_MODEL (store),
758                 "text-column", 1,
759                 NULL);
760   if (value_to_stock_id != NULL)
761     {
762       g_object_set_data (G_OBJECT (cell),
763                          "value-to-stock-id", value_to_stock_id);
764       g_signal_connect (cell, "editing-started",
765                         G_CALLBACK (on_combo_editing_started),
766                         var_sheet);
767     }
768
769   column = add_var_sheet_column (var_sheet, cell, column_id, title, width);
770
771   cell = gtk_cell_renderer_pixbuf_new ();
772   g_object_set (cell, "width", 16, "height", 16, NULL);
773   pspp_sheet_view_column_pack_end (column, cell, FALSE);
774   pspp_sheet_view_column_set_cell_data_func (
775     column, cell, render_var_cell, var_sheet, NULL);
776 }
777
778 static void
779 add_popup_menu (PsppireVarSheet *var_sheet,
780                 PsppSheetViewColumn *column,
781                 void (*on_click) (PsppireCellRendererButton *,
782                                   gchar *path,
783                                   PsppireVarSheet *var_sheet))
784 {
785   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
786   const char *button_label = "...";
787   GtkCellRenderer *button_renderer;
788   gint content_width;
789
790   button_renderer = psppire_cell_renderer_button_new ();
791   g_object_set (button_renderer,
792                 "label", button_label,
793                 "editable", TRUE,
794                 NULL);
795   g_signal_connect (button_renderer, "clicked", G_CALLBACK (on_click),
796                     var_sheet);
797   pspp_sheet_view_column_pack_start (column, button_renderer, FALSE);
798   pspp_sheet_view_column_set_cell_data_func (
799     column, button_renderer, render_popup_cell, var_sheet, NULL);
800
801   content_width = GPOINTER_TO_INT (g_object_get_data (
802                                      G_OBJECT (column), "content-width"));
803   content_width += get_string_width (sheet_view, button_renderer,
804                                      button_label);
805   if (content_width > pspp_sheet_view_column_get_fixed_width (column))
806     pspp_sheet_view_column_set_fixed_width (column, content_width);
807 }
808
809 static gboolean
810 get_tooltip_location (GtkWidget *widget, GtkTooltip *tooltip,
811                       gint wx, gint wy, size_t *row, size_t *column)
812 {
813   PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
814   gint bx, by;
815   GtkTreePath *path;
816   GtkTreeIter iter;
817   PsppSheetViewColumn *tree_column;
818   GtkTreeModel *tree_model;
819   gpointer column_ptr;
820   bool ok;
821
822   /* Check that WIDGET is really visible on the screen before we
823      do anything else.  This is a bug fix for a sticky situation:
824      when text_data_import_assistant() returns, it frees the data
825      necessary to compose the tool tip message, but there may be
826      a tool tip under preparation at that point (even if there is
827      no visible tool tip) that will call back into us a little
828      bit later.  Perhaps the correct solution to this problem is
829      to make the data related to the tool tips part of a GObject
830      that only gets destroyed when all references are released,
831      but this solution appears to be effective too. */
832   if (!gtk_widget_get_mapped (widget))
833     return FALSE;
834
835   pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
836                                                      wx, wy, &bx, &by);
837   if (!pspp_sheet_view_get_path_at_pos (tree_view, bx, by,
838                                       &path, &tree_column, NULL, NULL))
839     return FALSE;
840
841   column_ptr = g_object_get_data (G_OBJECT (tree_column), "column-number");
842   if (column_ptr == NULL)
843     return FALSE;
844   *column = GPOINTER_TO_INT (column_ptr) - 1;
845
846   pspp_sheet_view_set_tooltip_cell (tree_view, tooltip, path, tree_column,
847                                     NULL);
848
849   tree_model = pspp_sheet_view_get_model (tree_view);
850   ok = gtk_tree_model_get_iter (tree_model, &iter, path);
851   gtk_tree_path_free (path);
852   if (!ok)
853     return FALSE;
854
855   *row = GPOINTER_TO_INT (iter.user_data);
856   return TRUE;
857 }
858
859 static gboolean
860 on_query_var_tooltip (GtkWidget *widget, gint wx, gint wy,
861                       gboolean keyboard_mode UNUSED,
862                       GtkTooltip *tooltip, gpointer *user_data UNUSED)
863 {
864   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
865   PsppireDict *dict;
866   struct variable *var;
867   size_t row, column;
868
869   if (!get_tooltip_location (widget, tooltip, wx, wy, &row, &column))
870     return FALSE;
871
872   dict = psppire_var_sheet_get_dictionary (var_sheet);
873   g_return_val_if_fail (dict != NULL, FALSE);
874
875   if (row >= psppire_dict_get_var_cnt (dict))
876     {
877       gtk_tooltip_set_text (tooltip, _("Enter a variable name to add a "
878                                        "new variable."));
879       return TRUE;
880     }
881
882   var = psppire_dict_get_variable (dict, row);
883   g_return_val_if_fail (var != NULL, FALSE);
884
885   switch (column)
886     {
887     case VS_TYPE:
888       {
889         char text[FMT_STRING_LEN_MAX + 1];
890
891         fmt_to_string (var_get_print_format (var), text);
892         gtk_tooltip_set_text (tooltip, text);
893         return TRUE;
894       }
895
896     case VS_VALUES:
897       if (var_has_value_labels (var))
898         {
899           const struct val_labs *vls = var_get_value_labels (var);
900           const struct val_lab **labels = val_labs_sorted (vls);
901           struct string s;
902           size_t i;
903
904           ds_init_empty (&s);
905           for (i = 0; i < val_labs_count (vls); i++)
906             {
907               const struct val_lab *vl = labels[i];
908               gchar *vstr;
909
910               if (i >= 10 || ds_length (&s) > 500)
911                 {
912                   ds_put_cstr (&s, "...");
913                   break;
914                 }
915
916               vstr = value_to_text (vl->value, var);
917               ds_put_format (&s, _("{%s, %s}\n"), vstr,
918                              val_lab_get_escaped_label (vl));
919               free (vstr);
920
921             }
922           ds_chomp_byte (&s, '\n');
923
924           gtk_tooltip_set_text (tooltip, ds_cstr (&s));
925           ds_destroy (&s);
926           free (labels);
927
928           return TRUE;
929         }
930     }
931
932   return FALSE;
933 }
934
935 static void
936 do_popup_menu (GtkWidget *widget, guint button, guint32 time)
937 {
938   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
939   GtkWidget *menu;
940
941   menu = get_widget_assert (var_sheet->builder, "varsheet-variable-popup");
942   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
943 }
944
945 static void
946 on_popup_menu (GtkWidget *widget, gpointer user_data UNUSED)
947 {
948   do_popup_menu (widget, 0, gtk_get_current_event_time ());
949 }
950
951 static gboolean
952 on_button_pressed (GtkWidget *widget, GdkEventButton *event,
953                    gpointer user_data UNUSED)
954 {
955   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
956
957   if (event->type == GDK_BUTTON_PRESS && event->button == 3)
958     {
959       PsppSheetSelection *selection;
960
961       selection = pspp_sheet_view_get_selection (sheet_view);
962       if (pspp_sheet_selection_count_selected_rows (selection) <= 1)
963         {
964           GtkTreePath *path;
965
966           if (pspp_sheet_view_get_path_at_pos (sheet_view, event->x, event->y,
967                                                &path, NULL, NULL, NULL))
968             {
969               pspp_sheet_selection_unselect_all (selection);
970               pspp_sheet_selection_select_path (selection, path);
971               gtk_tree_path_free (path);
972             }
973         }
974
975       do_popup_menu (widget, event->button, event->time);
976       return TRUE;
977     }
978
979   return FALSE;
980 }
981 \f
982 GType
983 psppire_fmt_use_get_type (void)
984 {
985   static GType etype = 0;
986   if (etype == 0)
987     {
988       static const GEnumValue values[] =
989         {
990           { FMT_FOR_INPUT, "FMT_FOR_INPUT", "input" },
991           { FMT_FOR_OUTPUT, "FMT_FOR_OUTPUT", "output" },
992           { 0, NULL, NULL }
993         };
994
995       etype = g_enum_register_static
996         (g_intern_static_string ("PsppireFmtUse"), values);
997     }
998   return etype;
999 }
1000
1001 enum
1002   {
1003     PROP_0,
1004     PROP_DICTIONARY,
1005     PROP_MAY_CREATE_VARS,
1006     PROP_MAY_DELETE_VARS,
1007     PROP_FORMAT_TYPE,
1008     PROP_UI_MANAGER
1009   };
1010
1011 static void
1012 psppire_var_sheet_set_property (GObject      *object,
1013                              guint         prop_id,
1014                              const GValue *value,
1015                              GParamSpec   *pspec)
1016 {
1017   PsppireVarSheet *obj = PSPPIRE_VAR_SHEET (object);
1018
1019   switch (prop_id)
1020     {
1021     case PROP_DICTIONARY:
1022       psppire_var_sheet_set_dictionary (obj,
1023                                         PSPPIRE_DICT (g_value_get_object (
1024                                                         value)));
1025       break;
1026
1027     case PROP_MAY_CREATE_VARS:
1028       psppire_var_sheet_set_may_create_vars (obj,
1029                                              g_value_get_boolean (value));
1030       break;
1031
1032     case PROP_MAY_DELETE_VARS:
1033       psppire_var_sheet_set_may_delete_vars (obj,
1034                                              g_value_get_boolean (value));
1035       break;
1036
1037     case PROP_FORMAT_TYPE:
1038       obj->format_use = g_value_get_enum (value);
1039       break;
1040
1041     case PROP_UI_MANAGER:
1042     default:
1043       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1044       break;
1045     }
1046 }
1047
1048 static void
1049 psppire_var_sheet_get_property (GObject      *object,
1050                                 guint         prop_id,
1051                                 GValue       *value,
1052                                 GParamSpec   *pspec)
1053 {
1054   PsppireVarSheet *obj = PSPPIRE_VAR_SHEET (object);
1055
1056   switch (prop_id)
1057     {
1058     case PROP_DICTIONARY:
1059       g_value_set_object (value,
1060                           G_OBJECT (psppire_var_sheet_get_dictionary (obj)));
1061       break;
1062
1063     case PROP_MAY_CREATE_VARS:
1064       g_value_set_boolean (value, obj->may_create_vars);
1065       break;
1066
1067     case PROP_MAY_DELETE_VARS:
1068       g_value_set_boolean (value, obj->may_delete_vars);
1069       break;
1070
1071     case PROP_FORMAT_TYPE:
1072       g_value_set_enum (value, obj->format_use);
1073       break;
1074
1075     case PROP_UI_MANAGER:
1076       g_value_set_object (value, psppire_var_sheet_get_ui_manager (obj));
1077       break;
1078
1079     default:
1080       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1081       break;
1082     }
1083 }
1084
1085 static void
1086 psppire_var_sheet_dispose (GObject *obj)
1087 {
1088   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (obj);
1089   int i;
1090
1091   if (var_sheet->dispose_has_run)
1092     return;
1093
1094   var_sheet->dispose_has_run = TRUE;
1095
1096   for (i = 0; i < PSPPIRE_VAR_SHEET_N_SIGNALS; i++)
1097     if ( var_sheet->dict_signals[i])
1098       g_signal_handler_disconnect (var_sheet->dict,
1099                                    var_sheet->dict_signals[i]);
1100
1101   if (var_sheet->dict)
1102     g_object_unref (var_sheet->dict);
1103   
1104   if (var_sheet->uim)
1105     g_object_unref (var_sheet->uim);
1106
1107   /* These dialogs are not GObjects (although they should be!)
1108     But for now, unreffing them only causes a GCritical Error
1109     so comment them out for now. (and accept the memory leakage)
1110
1111   g_object_unref (var_sheet->val_labs_dialog);
1112   g_object_unref (var_sheet->missing_val_dialog);
1113   g_object_unref (var_sheet->var_type_dialog);
1114   */
1115
1116   G_OBJECT_CLASS (psppire_var_sheet_parent_class)->dispose (obj);
1117 }
1118
1119 static void
1120 psppire_var_sheet_class_init (PsppireVarSheetClass *class)
1121 {
1122   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
1123   GParamSpec *pspec;
1124
1125   gobject_class->set_property = psppire_var_sheet_set_property;
1126   gobject_class->get_property = psppire_var_sheet_get_property;
1127   gobject_class->dispose = psppire_var_sheet_dispose;
1128
1129   g_signal_new ("var-double-clicked",
1130                 G_OBJECT_CLASS_TYPE (gobject_class),
1131                 G_SIGNAL_RUN_LAST,
1132                 0,
1133                 g_signal_accumulator_true_handled, NULL,
1134                 psppire_marshal_BOOLEAN__INT,
1135                 G_TYPE_BOOLEAN, 1, G_TYPE_INT);
1136
1137   pspec = g_param_spec_object ("dictionary",
1138                                "Dictionary displayed by the sheet",
1139                                "The PsppireDict that the sheet displays "
1140                                "may allow the user to edit",
1141                                PSPPIRE_TYPE_DICT,
1142                                G_PARAM_READWRITE);
1143   g_object_class_install_property (gobject_class, PROP_DICTIONARY, pspec);
1144
1145   pspec = g_param_spec_boolean ("may-create-vars",
1146                                 "May create variables",
1147                                 "Whether the user may create more variables",
1148                                 TRUE,
1149                                 G_PARAM_READWRITE);
1150   g_object_class_install_property (gobject_class, PROP_MAY_CREATE_VARS, pspec);
1151
1152   pspec = g_param_spec_boolean ("may-delete-vars",
1153                                 "May delete variables",
1154                                 "Whether the user may delete variables",
1155                                 TRUE,
1156                                 G_PARAM_READWRITE);
1157   g_object_class_install_property (gobject_class, PROP_MAY_DELETE_VARS, pspec);
1158
1159   pspec = g_param_spec_enum ("format-use",
1160                              "Use of variable format",
1161                              ("Whether variables have input or output "
1162                               "formats"),
1163                              PSPPIRE_TYPE_FMT_USE,
1164                              FMT_FOR_OUTPUT,
1165                              G_PARAM_READWRITE);
1166   g_object_class_install_property (gobject_class, PROP_FORMAT_TYPE, pspec);
1167
1168   pspec = g_param_spec_object ("ui-manager",
1169                                "UI Manager",
1170                                "UI manager for the variable sheet.  The client should merge this UI manager with the active UI manager to obtain variable sheet specific menu items and tool bar items.",
1171                                GTK_TYPE_UI_MANAGER,
1172                                G_PARAM_READABLE);
1173   g_object_class_install_property (gobject_class, PROP_UI_MANAGER, pspec);
1174 }
1175
1176 static void
1177 render_row_number_cell (PsppSheetViewColumn *tree_column,
1178                         GtkCellRenderer *cell,
1179                         GtkTreeModel *model,
1180                         GtkTreeIter *iter,
1181                         gpointer user_data)
1182 {
1183   PsppireVarSheet *var_sheet = user_data;
1184   GValue gvalue = { 0, };
1185   gint row;
1186
1187   row = GPOINTER_TO_INT (iter->user_data);
1188
1189   g_value_init (&gvalue, G_TYPE_INT);
1190   g_value_set_int (&gvalue, row + 1);
1191   g_object_set_property (G_OBJECT (cell), "label", &gvalue);
1192   g_value_unset (&gvalue);
1193
1194   if (!var_sheet->dict || row < psppire_dict_get_var_cnt (var_sheet->dict))
1195     g_object_set (cell, "editable", TRUE, NULL);
1196   else
1197     g_object_set (cell, "editable", FALSE, NULL);
1198 }
1199
1200 static void
1201 psppire_var_sheet_row_number_double_clicked (PsppireCellRendererButton *button,
1202                                              gchar *path_string,
1203                                              PsppireVarSheet *var_sheet)
1204 {
1205   GtkTreePath *path;
1206
1207   g_return_if_fail (var_sheet->dict != NULL);
1208
1209   path = gtk_tree_path_new_from_string (path_string);
1210   if (gtk_tree_path_get_depth (path) == 1)
1211     {
1212       gint *indices = gtk_tree_path_get_indices (path);
1213       if (indices[0] < psppire_dict_get_var_cnt (var_sheet->dict))
1214         {
1215           gboolean handled;
1216           g_signal_emit_by_name (var_sheet, "var-double-clicked",
1217                                  indices[0], &handled);
1218         }
1219     }
1220   gtk_tree_path_free (path);
1221 }
1222
1223 static void
1224 psppire_var_sheet_variables_column_clicked (PsppSheetViewColumn *column,
1225                                             PsppireVarSheet *var_sheet)
1226 {
1227   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1228   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1229
1230   pspp_sheet_selection_select_all (selection);
1231 }
1232
1233 static PsppSheetViewColumn *
1234 make_row_number_column (PsppireVarSheet *var_sheet)
1235 {
1236   PsppSheetViewColumn *column;
1237   GtkCellRenderer *renderer;
1238
1239   renderer = psppire_cell_renderer_button_new ();
1240   g_object_set (renderer, "xalign", 1.0, NULL);
1241   g_signal_connect (renderer, "double-clicked",
1242                     G_CALLBACK (psppire_var_sheet_row_number_double_clicked),
1243                     var_sheet);
1244
1245   column = pspp_sheet_view_column_new_with_attributes (_("Variable"),
1246                                                        renderer, NULL);
1247   pspp_sheet_view_column_set_clickable (column, TRUE);
1248   pspp_sheet_view_column_set_cell_data_func (
1249     column, renderer, render_row_number_cell, var_sheet, NULL);
1250   pspp_sheet_view_column_set_fixed_width (column, 50);
1251   g_signal_connect (column, "clicked",
1252                     G_CALLBACK (psppire_var_sheet_variables_column_clicked),
1253                     var_sheet);
1254
1255   return column;
1256 }
1257
1258 static void
1259 on_edit_clear_variables (GtkAction *action, PsppireVarSheet *var_sheet)
1260 {
1261   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1262   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1263   PsppireDict *dict = var_sheet->dict;
1264   const struct range_set_node *node;
1265   struct range_set *selected;
1266
1267   selected = pspp_sheet_selection_get_range_set (selection);
1268   for (node = range_set_last (selected); node != NULL;
1269        node = range_set_prev (selected, node))
1270     {
1271       int i;
1272
1273       for (i = 1; i <= range_set_node_get_width (node); i++)
1274         {
1275           unsigned long row = range_set_node_get_end (node) - i;
1276           if (row < psppire_dict_get_var_cnt (dict))
1277             psppire_dict_delete_variables (dict, row, 1);
1278         }
1279     }
1280   range_set_destroy (selected);
1281 }
1282
1283 static void
1284 on_selection_changed (PsppSheetSelection *selection,
1285                       gpointer user_data UNUSED)
1286 {
1287   PsppSheetView *sheet_view = pspp_sheet_selection_get_tree_view (selection);
1288   PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (sheet_view);
1289   gint n_selected_rows;
1290   gboolean may_delete;
1291   GtkTreePath *path;
1292   GtkAction *action;
1293
1294   n_selected_rows = pspp_sheet_selection_count_selected_rows (selection);
1295
1296   action = get_action_assert (var_sheet->builder, "edit_insert-variable");
1297   gtk_action_set_sensitive (action, (var_sheet->may_create_vars
1298                                      && n_selected_rows > 0));
1299
1300   switch (n_selected_rows)
1301     {
1302     case 0:
1303       may_delete = FALSE;
1304       break;
1305
1306     case 1:
1307       /* The row used for inserting new variables cannot be deleted. */
1308       path = gtk_tree_path_new_from_indices (
1309         psppire_dict_get_var_cnt (var_sheet->dict), -1);
1310       may_delete = !pspp_sheet_selection_path_is_selected (selection, path);
1311       gtk_tree_path_free (path);
1312       break;
1313
1314     default:
1315       may_delete = TRUE;
1316       break;
1317     }
1318   action = get_action_assert (var_sheet->builder, "edit_clear-variables");
1319   gtk_action_set_sensitive (action, var_sheet->may_delete_vars && may_delete);
1320 }
1321
1322 static void
1323 on_edit_insert_variable (GtkAction *action, PsppireVarSheet *var_sheet)
1324 {
1325   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1326   PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
1327   PsppireDict *dict = var_sheet->dict;
1328   struct range_set *selected;
1329   unsigned long row;
1330
1331   selected = pspp_sheet_selection_get_range_set (selection);
1332   row = range_set_scan (selected, 0);
1333   range_set_destroy (selected);
1334
1335   if (row <= psppire_dict_get_var_cnt (dict))
1336     {
1337       gchar name[64];;
1338       if (psppire_dict_generate_name (dict, name, sizeof name))
1339         psppire_dict_insert_variable (dict, row, name);
1340     }
1341 }
1342
1343 static void
1344 psppire_var_sheet_init (PsppireVarSheet *obj)
1345 {
1346   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (obj);
1347   PsppSheetViewColumn *column;
1348   GtkAction *action;
1349   GList *list;
1350
1351   obj->dict = NULL;
1352   obj->format_use = FMT_FOR_OUTPUT;
1353   obj->may_create_vars = TRUE;
1354   obj->may_delete_vars = TRUE;
1355
1356   obj->scroll_to_bottom_signal = 0;
1357
1358   obj->container = NULL;
1359   obj->dispose_has_run = FALSE;
1360   obj->uim = NULL;
1361
1362   pspp_sheet_view_append_column (sheet_view, make_row_number_column (obj));
1363
1364   column = add_text_column (obj, VS_NAME, _("Name"), 12);
1365   list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
1366   g_signal_connect (list->data, "editing-started",
1367                     G_CALLBACK (on_name_column_editing_started), NULL);
1368   g_list_free (list);
1369
1370   column = add_text_column (obj, VS_TYPE, _("Type"), 8);
1371   add_popup_menu (obj, column, on_type_click);
1372
1373   add_spin_column (obj, VS_WIDTH, _("Width"), 5);
1374
1375   add_spin_column (obj, VS_DECIMALS, _("Decimals"), 2);
1376
1377   add_text_column (obj, VS_LABEL, _("Label"), 20);
1378
1379   column = add_text_column (obj, VS_VALUES, _("Value Labels"), 20);
1380   add_popup_menu (obj, column, on_value_labels_click);
1381
1382   column = add_text_column (obj, VS_MISSING, _("Missing Values"), 20);
1383   add_popup_menu (obj, column, on_missing_values_click);
1384
1385   add_spin_column (obj, VS_COLUMNS, _("Columns"), 3);
1386
1387   add_combo_column (obj, VS_ALIGN, _("Align"), 8, alignment_to_stock_id,
1388                     alignment_to_string (ALIGN_LEFT), ALIGN_LEFT,
1389                     alignment_to_string (ALIGN_CENTRE), ALIGN_CENTRE,
1390                     alignment_to_string (ALIGN_RIGHT), ALIGN_RIGHT,
1391                     NULL);
1392
1393   add_combo_column (obj, VS_MEASURE, _("Measure"), 11, measure_to_stock_id,
1394                     measure_to_string (MEASURE_NOMINAL), MEASURE_NOMINAL,
1395                     measure_to_string (MEASURE_ORDINAL), MEASURE_ORDINAL,
1396                     measure_to_string (MEASURE_SCALE), MEASURE_SCALE,
1397                     NULL);
1398
1399   add_combo_column (obj, VS_ROLE, _("Role"), 11, role_to_stock_id,
1400                     var_role_to_string (ROLE_INPUT), ROLE_INPUT,
1401                     var_role_to_string (ROLE_TARGET), ROLE_TARGET,
1402                     var_role_to_string (ROLE_BOTH), ROLE_BOTH,
1403                     var_role_to_string (ROLE_NONE), ROLE_NONE,
1404                     var_role_to_string (ROLE_PARTITION), ROLE_PARTITION,
1405                     var_role_to_string (ROLE_SPLIT), ROLE_SPLIT,
1406                     NULL);
1407
1408   pspp_sheet_view_set_rubber_banding (sheet_view, TRUE);
1409   pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (sheet_view),
1410                                  PSPP_SHEET_SELECTION_MULTIPLE);
1411
1412   g_object_set (G_OBJECT (obj), "has-tooltip", TRUE, NULL);
1413   g_signal_connect (obj, "query-tooltip",
1414                     G_CALLBACK (on_query_var_tooltip), NULL);
1415   g_signal_connect (obj, "button-press-event",
1416                     G_CALLBACK (on_button_pressed), NULL);
1417   g_signal_connect (obj, "popup-menu", G_CALLBACK (on_popup_menu), NULL);
1418
1419   obj->builder = builder_new ("var-sheet.ui");
1420
1421   action = get_action_assert (obj->builder, "edit_clear-variables");
1422   g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_variables),
1423                     obj);
1424   gtk_action_set_sensitive (action, FALSE);
1425   g_signal_connect (pspp_sheet_view_get_selection (sheet_view),
1426                     "changed", G_CALLBACK (on_selection_changed), NULL);
1427
1428   action = get_action_assert (obj->builder, "edit_insert-variable");
1429   gtk_action_set_sensitive (action, FALSE);
1430   g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_variable),
1431                     obj);
1432 }
1433
1434 GtkWidget *
1435 psppire_var_sheet_new (void)
1436 {
1437   return g_object_new (PSPPIRE_VAR_SHEET_TYPE, NULL);
1438 }
1439
1440 PsppireDict *
1441 psppire_var_sheet_get_dictionary (PsppireVarSheet *var_sheet)
1442 {
1443   return var_sheet->dict;
1444 }
1445
1446 static void
1447 refresh_model (PsppireVarSheet *var_sheet)
1448 {
1449   pspp_sheet_view_set_model (PSPP_SHEET_VIEW (var_sheet), NULL);
1450
1451   if (var_sheet->dict != NULL)
1452     {
1453       PsppireEmptyListStore *store;
1454       int n_rows;
1455
1456       n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1457                 + var_sheet->may_create_vars);
1458       store = psppire_empty_list_store_new (n_rows);
1459       pspp_sheet_view_set_model (PSPP_SHEET_VIEW (var_sheet),
1460                                  GTK_TREE_MODEL (store));
1461       g_object_unref (store);
1462     }
1463 }
1464
1465 static void
1466 on_var_changed (PsppireDict *dict, glong row,
1467                 guint what, const struct variable *oldvar,
1468                 PsppireVarSheet *var_sheet)
1469 {
1470   PsppireEmptyListStore *store;
1471
1472   g_return_if_fail (dict == var_sheet->dict);
1473
1474   store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1475                                       PSPP_SHEET_VIEW (var_sheet)));
1476   g_return_if_fail (store != NULL);
1477
1478   psppire_empty_list_store_row_changed (store, row);
1479 }
1480
1481 static void
1482 on_var_inserted (PsppireDict *dict, glong row, PsppireVarSheet *var_sheet)
1483 {
1484   PsppireEmptyListStore *store;
1485   int n_rows;
1486
1487   g_return_if_fail (dict == var_sheet->dict);
1488
1489   store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1490                                       PSPP_SHEET_VIEW (var_sheet)));
1491   g_return_if_fail (store != NULL);
1492
1493   n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1494             + var_sheet->may_create_vars);
1495   psppire_empty_list_store_set_n_rows (store, n_rows);
1496   psppire_empty_list_store_row_inserted (store, row);
1497 }
1498
1499 static void
1500 on_var_deleted (PsppireDict *dict,
1501                 const struct variable *var, int dict_idx, int case_idx,
1502                 PsppireVarSheet *var_sheet)
1503 {
1504   PsppireEmptyListStore *store;
1505   int n_rows;
1506
1507   g_return_if_fail (dict == var_sheet->dict);
1508
1509   store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1510                                       PSPP_SHEET_VIEW (var_sheet)));
1511   g_return_if_fail (store != NULL);
1512
1513   n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1514             + var_sheet->may_create_vars);
1515   psppire_empty_list_store_set_n_rows (store, n_rows);
1516   psppire_empty_list_store_row_deleted (store, dict_idx);
1517 }
1518
1519 static void
1520 on_backend_changed (PsppireDict *dict, PsppireVarSheet *var_sheet)
1521 {
1522   g_return_if_fail (dict == var_sheet->dict);
1523   refresh_model (var_sheet);
1524 }
1525
1526 void
1527 psppire_var_sheet_set_dictionary (PsppireVarSheet *var_sheet,
1528                                   PsppireDict *dict)
1529 {
1530   if (var_sheet->dict != NULL)
1531     {
1532       int i;
1533       
1534       for (i = 0; i < PSPPIRE_VAR_SHEET_N_SIGNALS; i++)
1535         {
1536           if (var_sheet->dict_signals[i])
1537             g_signal_handler_disconnect (var_sheet->dict,
1538                                          var_sheet->dict_signals[i]);
1539           
1540           var_sheet->dict_signals[i] = 0;
1541         }
1542
1543       g_object_unref (var_sheet->dict);
1544     }
1545
1546   var_sheet->dict = dict;
1547
1548   if (dict != NULL)
1549     {
1550       g_object_ref (dict);
1551
1552       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_BACKEND_CHANGED]
1553         = g_signal_connect (dict, "backend-changed",
1554                             G_CALLBACK (on_backend_changed), var_sheet);
1555
1556       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_VARIABLE_CHANGED]
1557         = g_signal_connect (dict, "variable-changed",
1558                             G_CALLBACK (on_var_changed), var_sheet);
1559
1560       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_VARIABLE_DELETED]
1561         = g_signal_connect (dict, "variable-inserted",
1562                             G_CALLBACK (on_var_inserted), var_sheet);
1563
1564       var_sheet->dict_signals[PSPPIRE_VAR_SHEET_VARIABLE_INSERTED]
1565         = g_signal_connect (dict, "variable-deleted",
1566                             G_CALLBACK (on_var_deleted), var_sheet);
1567     }
1568
1569   refresh_model (var_sheet);
1570 }
1571
1572 gboolean
1573 psppire_var_sheet_get_may_create_vars (PsppireVarSheet *var_sheet)
1574 {
1575   return var_sheet->may_create_vars;
1576 }
1577
1578 void
1579 psppire_var_sheet_set_may_create_vars (PsppireVarSheet *var_sheet,
1580                                        gboolean may_create_vars)
1581 {
1582   if (var_sheet->may_create_vars != may_create_vars)
1583     {
1584       PsppireEmptyListStore *store;
1585       gint n_rows;
1586
1587       var_sheet->may_create_vars = may_create_vars;
1588
1589       store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
1590                                           PSPP_SHEET_VIEW (var_sheet)));
1591       g_return_if_fail (store != NULL);
1592
1593       n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
1594                 + var_sheet->may_create_vars);
1595       psppire_empty_list_store_set_n_rows (store, n_rows);
1596
1597       if (may_create_vars)
1598         psppire_empty_list_store_row_inserted (store, n_rows - 1);
1599       else
1600         psppire_empty_list_store_row_deleted (store, n_rows);
1601
1602       on_selection_changed (pspp_sheet_view_get_selection (
1603                               PSPP_SHEET_VIEW (var_sheet)), NULL);
1604     }
1605 }
1606
1607 gboolean
1608 psppire_var_sheet_get_may_delete_vars (PsppireVarSheet *var_sheet)
1609 {
1610   return var_sheet->may_delete_vars;
1611 }
1612
1613 void
1614 psppire_var_sheet_set_may_delete_vars (PsppireVarSheet *var_sheet,
1615                                        gboolean may_delete_vars)
1616 {
1617   if (var_sheet->may_delete_vars != may_delete_vars)
1618     {
1619       var_sheet->may_delete_vars = may_delete_vars;
1620       on_selection_changed (pspp_sheet_view_get_selection (
1621                               PSPP_SHEET_VIEW (var_sheet)), NULL);
1622     }
1623 }
1624
1625 void
1626 psppire_var_sheet_goto_variable (PsppireVarSheet *var_sheet, int dict_index)
1627 {
1628   PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
1629   GtkTreePath *path;
1630
1631   path = gtk_tree_path_new_from_indices (dict_index, -1);
1632   pspp_sheet_view_scroll_to_cell (sheet_view, path, NULL, FALSE, 0.0, 0.0);
1633   pspp_sheet_view_set_cursor (sheet_view, path, NULL, FALSE);
1634   gtk_tree_path_free (path);
1635 }
1636
1637 GtkUIManager *
1638 psppire_var_sheet_get_ui_manager (PsppireVarSheet *var_sheet)
1639 {
1640   if (var_sheet->uim == NULL)
1641     {
1642       var_sheet->uim = GTK_UI_MANAGER (get_object_assert (var_sheet->builder,
1643                                                           "var_sheet_uim",
1644                                                           GTK_TYPE_UI_MANAGER));
1645       g_object_ref (var_sheet->uim);
1646     }
1647
1648   return var_sheet->uim;
1649 }
1650