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