gui: Redo var sheet, data sheet, text import with PsppSheetView.
[pspp] / src / ui / gui / psppire-var-sheet.c
index 648d9fd786ee149bdeafd7e972c382d4893dc66a..037af7b93ec90a0f11d0ed1b28b1addbeb780a2d 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (C) 2008, 2009, 2011 Free Software Foundation, Inc.
+   Copyright (C) 2011, 2012 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <config.h>
-#include "psppire-var-sheet.h"
-#include <ui/gui/sheet/psppire-axis.h>
 
-#include "builder-wrapper.h"
-#include "helper.h"
-
-#include "customentry.h"
-#include <data/variable.h>
-#include "psppire-var-store.h"
+#include "ui/gui/psppire-var-sheet.h"
+
+#include "data/format.h"
+#include "data/value-labels.h"
+#include "libpspp/range-set.h"
+#include "ui/gui/builder-wrapper.h"
+#include "ui/gui/helper.h"
+#include "ui/gui/missing-val-dialog.h"
+#include "ui/gui/pspp-sheet-selection.h"
+#include "ui/gui/psppire-cell-renderer-button.h"
+#include "ui/gui/psppire-data-editor.h"
+#include "ui/gui/psppire-data-window.h"
+#include "ui/gui/psppire-dialog-action-var-info.h"
+#include "ui/gui/psppire-empty-list-store.h"
+#include "ui/gui/psppire-marshal.h"
+#include "ui/gui/psppire-var-store.h"
+#include "ui/gui/val-labs-dialog.h"
+#include "ui/gui/var-display.h"
+#include "ui/gui/var-type-dialog.h"
+
+#include "gl/intprops.h"
 
 #include <gettext.h>
 #define _(msgid) gettext (msgid)
 #define N_(msgid) msgid
 
+enum vs_column
+  {
+    VS_NAME,
+    VS_TYPE,
+    VS_WIDTH,
+    VS_DECIMALS,
+    VS_LABEL,
+    VS_VALUES,
+    VS_MISSING,
+    VS_COLUMNS,
+    VS_ALIGN,
+    VS_MEASURE
+  };
 
-static void psppire_var_sheet_class_init  (PsppireVarSheetClass *klass);
-static void psppire_var_sheet_init        (PsppireVarSheet      *vs);
-static void psppire_var_sheet_realize     (GtkWidget *w);
-static void psppire_var_sheet_unrealize   (GtkWidget *w);
+G_DEFINE_TYPE (PsppireVarSheet, psppire_var_sheet, PSPP_TYPE_SHEET_VIEW);
 
+static void
+set_spin_cell (GtkCellRenderer *cell, int value, int min, int max, int step)
+{
+  char text[INT_BUFSIZE_BOUND (int)];
+  GtkAdjustment *adjust;
+
+  if (max > min)
+    adjust = GTK_ADJUSTMENT (gtk_adjustment_new (value, min, max,
+                                                 step, step, 0.0));
+  else
+    adjust = NULL;
+
+  sprintf (text, "%d", value);
+  g_object_set (cell,
+                "text", text,
+                "adjustment", adjust,
+                "editable", TRUE,
+                NULL);
+}
 
-enum
-  {
-    PSPPIRE_VAR_SHEET_MAY_CREATE_VARS = 1
-  };
+static void
+error_dialog (GtkWindow *w, gchar *primary_text, gchar *secondary_text)
+{
+  GtkWidget *dialog =
+    gtk_message_dialog_new (w,
+                           GTK_DIALOG_DESTROY_WITH_PARENT,
+                           GTK_MESSAGE_ERROR,
+                           GTK_BUTTONS_CLOSE, "%s", primary_text);
 
-GType
-psppire_var_sheet_get_type (void)
+  g_object_set (dialog, "icon-name", "psppicon", NULL);
+
+  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+                                           "%s", secondary_text);
+
+  gtk_dialog_run (GTK_DIALOG (dialog));
+
+  gtk_widget_destroy (dialog);
+}
+
+static void
+on_name_column_editing_started (GtkCellRenderer *cell,
+                                GtkCellEditable *editable,
+                                const gchar     *path,
+                                gpointer         user_data)
 {
-  static GType vs_type = 0;
+  PsppireVarSheet *var_sheet = g_object_get_data (G_OBJECT (cell), "var-sheet");
+  PsppireDict *dict = psppire_var_sheet_get_dictionary (var_sheet);
 
-  if (!vs_type)
+  g_return_if_fail (var_sheet);
+          g_return_if_fail (dict);
+
+  if (GTK_IS_ENTRY (editable))
     {
-      static const GTypeInfo vs_info =
-      {
-       sizeof (PsppireVarSheetClass),
-       NULL, /* base_init */
-        NULL, /* base_finalize */
-       (GClassInitFunc) psppire_var_sheet_class_init,
-        NULL, /* class_finalize */
-       NULL, /* class_data */
-        sizeof (PsppireVarSheet),
-       0,
-       (GInstanceInitFunc) psppire_var_sheet_init,
-      };
-
-      vs_type = g_type_register_static (PSPPIRE_TYPE_SHEET, "PsppireVarSheet",
-                                       &vs_info, 0);
+      GtkEntry *entry = GTK_ENTRY (editable);
+      if (gtk_entry_get_text (entry)[0] == '\0')
+        {
+          gchar name[64];
+          if (psppire_dict_generate_name (dict, name, sizeof name))
+            gtk_entry_set_text (entry, name);
+        }
     }
+}
+
+static void
+scroll_to_bottom (GtkWidget      *widget,
+                  GtkRequisition *requisition,
+                  gpointer        unused UNUSED)
+{
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
+  GtkAdjustment *vadjust;
+
+  vadjust = pspp_sheet_view_get_vadjustment (sheet_view);
+  gtk_adjustment_set_value (vadjust, gtk_adjustment_get_upper (vadjust));
 
-  return vs_type;
+  if (var_sheet->scroll_to_bottom_signal)
+    {
+      g_signal_handler_disconnect (var_sheet,
+                                   var_sheet->scroll_to_bottom_signal);
+      var_sheet->scroll_to_bottom_signal = 0;
+    }
 }
 
-static GObjectClass * parent_class = NULL;
+static void
+on_var_column_edited (GtkCellRendererText *cell,
+                      gchar               *path_string,
+                      gchar               *new_text,
+                      gpointer             user_data)
+{
+  PsppireVarSheet *var_sheet = user_data;
+  GtkWindow *window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (var_sheet)));
+  struct dictionary *dict = var_sheet->dict->dict;
+  enum vs_column column_id;
+  struct variable *var;
+  int width, decimals;
+  GtkTreePath *path;
+  gint row;
+
+  path = gtk_tree_path_new_from_string (path_string);
+  row = gtk_tree_path_get_indices (path)[0];
+  gtk_tree_path_free (path);
+
+  column_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cell),
+                                                  "column-id"));
+
+  var = psppire_dict_get_variable (var_sheet->dict, row);
+  if (var == NULL)
+    {
+      g_return_if_fail (column_id == VS_NAME);
+
+      if (!dict_id_is_valid (dict, new_text, false))
+        error_dialog (window,
+                      g_strdup (_("Cannot create variable.")),
+                      g_strdup_printf (_("\"%s\" is not a valid variable "
+                                         "name."), new_text));
+      else if (dict_lookup_var (dict, new_text) != NULL)
+        error_dialog (window,
+                      g_strdup (_("Cannot create variable.")),
+                      g_strdup_printf (_("This dictionary already contains "
+                                         "a variable named \"%s\"."),
+                                         new_text));
+      else
+        {
+          dict_create_var (var_sheet->dict->dict, new_text, 0);
+          if (!var_sheet->scroll_to_bottom_signal)
+            {
+              gtk_widget_queue_resize (GTK_WIDGET (var_sheet));
+              var_sheet->scroll_to_bottom_signal =
+                g_signal_connect (var_sheet, "size-request",
+                                  G_CALLBACK (scroll_to_bottom), NULL);
+            }
+        }
+
+      return;
+    }
+
+  switch (column_id)
+    {
+    case VS_NAME:
+      if (!dict_id_is_valid (dict, new_text, false))
+        error_dialog (window,
+                      g_strdup (_("Cannot rename variable.")),
+                      g_strdup_printf (_("\"%s\" is not a valid variable "
+                                         "name."), new_text));
+      else if (dict_lookup_var (dict, new_text) != NULL
+               && dict_lookup_var (dict, new_text) != var)
+        error_dialog (window,
+                      g_strdup (_("Cannot rename variable.")),
+                      g_strdup_printf (_("This dictionary already contains "
+                                         "a variable named \"%s\"."),
+                                         new_text));
+      else
+        dict_rename_var (dict, var, new_text);
+      break;
+
+    case VS_TYPE:
+      /* Not reachable. */
+      break;
+
+    case VS_WIDTH:
+      width = atoi (new_text);
+      if (width > 0)
+        {
+          struct fmt_spec format;
+
+          format = *var_get_print_format (var);
+          fmt_change_width (&format, width, var_sheet->format_use);
+          var_set_print_format (var, &format);
+          var_set_width (var, fmt_var_width (&format));
+        }
+      break;
+
+    case VS_DECIMALS:
+      decimals = atoi (new_text);
+      if (decimals >= 0)
+        {
+          struct fmt_spec format;
+
+          format = *var_get_print_format (var);
+          fmt_change_decimals (&format, decimals, var_sheet->format_use);
+          var_set_print_format (var, &format);
+        }
+      break;
+
+    case VS_LABEL:
+      var_set_label (var, new_text, false);
+      break;
+
+    case VS_VALUES:
+    case VS_MISSING:
+      break;
+
+    case VS_COLUMNS:
+      width = atoi (new_text);
+      if (width > 0 && width < 2 * MAX_STRING)
+        var_set_display_width (var, width);
+      break;
+
+    case VS_ALIGN:
+      if (!strcmp (new_text, alignment_to_string (ALIGN_LEFT)))
+        var_set_alignment (var, ALIGN_LEFT);
+      else if (!strcmp (new_text, alignment_to_string (ALIGN_CENTRE)))
+        var_set_alignment (var, ALIGN_CENTRE);
+      else if (!strcmp (new_text, alignment_to_string (ALIGN_RIGHT)))
+        var_set_alignment (var, ALIGN_RIGHT);
+      break;
+
+    case VS_MEASURE:
+      if (!strcmp (new_text, measure_to_string (MEASURE_NOMINAL)))
+        var_set_measure (var, MEASURE_NOMINAL);
+      else if (!strcmp (new_text, measure_to_string (MEASURE_ORDINAL)))
+        var_set_measure (var, MEASURE_ORDINAL);
+      else if (!strcmp (new_text, measure_to_string (MEASURE_SCALE)))
+        var_set_measure (var, MEASURE_SCALE);
+      break;
+    }
+}
+
+static void
+render_popup_cell (PsppSheetViewColumn *tree_column,
+                   GtkCellRenderer *cell,
+                   GtkTreeModel *model,
+                   GtkTreeIter *iter,
+                   void *user_data)
+{
+  PsppireVarSheet *var_sheet = user_data;
+  gint row;
+
+  row = GPOINTER_TO_INT (iter->user_data);
+  g_object_set (cell,
+                "editable", row < psppire_dict_get_var_cnt (var_sheet->dict),
+                NULL);
+}
 
 static void
-psppire_var_sheet_dispose (GObject *obj)
+render_var_cell (PsppSheetViewColumn *tree_column,
+                 GtkCellRenderer *cell,
+                 GtkTreeModel *model,
+                 GtkTreeIter *iter,
+                 void *user_data)
+{
+  PsppireVarSheet *var_sheet = user_data;
+  const struct fmt_spec *print;
+  enum vs_column column_id;
+  struct variable *var;
+  gint row;
+
+  column_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
+                                                  "column-number")) - 1;
+  row = GPOINTER_TO_INT (iter->user_data);
+
+  if (row >= psppire_dict_get_var_cnt (var_sheet->dict))
+    {
+      g_object_set (cell,
+                    "text", "",
+                    "editable", column_id == VS_NAME,
+                    NULL);
+      if (column_id == VS_WIDTH
+          || column_id == VS_DECIMALS
+          || column_id == VS_COLUMNS)
+        g_object_set (cell, "adjustment", NULL, NULL);
+      return;
+    }
+
+  var = psppire_dict_get_variable (var_sheet->dict, row);
+
+  print = var_get_print_format (var);
+  switch (column_id)
+    {
+    case VS_NAME:
+      g_object_set (cell,
+                    "text", var_get_name (var),
+                    "editable", TRUE,
+                    NULL);
+      break;
+
+    case VS_TYPE:
+      g_object_set (cell,
+                    "text", fmt_gui_name (print->type),
+                    "editable", FALSE,
+                    NULL);
+      break;
+
+    case VS_WIDTH:
+      {
+        int step = fmt_step_width (print->type);
+        if (var_is_numeric (var))
+          set_spin_cell (cell, print->w,
+                         fmt_min_width (print->type, var_sheet->format_use),
+                         fmt_max_width (print->type, var_sheet->format_use),
+                         step);
+        else
+          set_spin_cell (cell, print->w, 0, 0, step);
+      }
+      break;
+
+    case VS_DECIMALS:
+      if (fmt_takes_decimals (print->type))
+        {
+          int max_w = fmt_max_width (print->type, var_sheet->format_use);
+          int max_d = fmt_max_decimals (print->type, max_w,
+                                        var_sheet->format_use);
+          set_spin_cell (cell, print->d, 0, max_d, 1);
+        }
+      else
+        g_object_set (cell,
+                      "text", "",
+                      "editable", FALSE,
+                      "adjustment", NULL,
+                      NULL);
+      break;
+
+    case VS_LABEL:
+      g_object_set (cell,
+                    "text", var_has_label (var) ? var_get_label (var) : "",
+                    "editable", TRUE,
+                    NULL);
+      break;
+
+    case VS_VALUES:
+      g_object_set (cell, "editable", FALSE, NULL);
+      if ( ! var_has_value_labels (var))
+        g_object_set (cell, "text", _("None"), NULL);
+      else
+        {
+          const struct val_labs *vls = var_get_value_labels (var);
+          const struct val_lab **labels = val_labs_sorted (vls);
+          const struct val_lab *vl = labels[0];
+          gchar *vstr = value_to_text (vl->value, var);
+          char *text = xasprintf (_("{%s, %s}..."), vstr,
+                                  val_lab_get_escaped_label (vl));
+          free (vstr);
+
+          g_object_set (cell, "text", text, NULL);
+          free (labels);
+        }
+      break;
+
+    case VS_MISSING:
+      {
+        char *text = missing_values_to_string (var_sheet->dict, var, NULL);
+        g_object_set (cell,
+                      "text", text,
+                      "editable", FALSE,
+                      NULL);
+        free (text);
+      }
+      break;
+
+    case VS_COLUMNS:
+      set_spin_cell (cell, var_get_display_width (var), 1, 2 * MAX_STRING, 1);
+      break;
+
+    case VS_ALIGN:
+      g_object_set (cell,
+                    "text", alignment_to_string (var_get_alignment (var)),
+                    "editable", TRUE,
+                    NULL);
+      break;
+
+    case VS_MEASURE:
+      g_object_set (cell,
+                    "text", measure_to_string (var_get_measure (var)),
+                    "editable", TRUE,
+                    NULL);
+      break;
+    }
+}
+
+static struct variable *
+path_string_to_variable (PsppireVarSheet *var_sheet, gchar *path_string)
 {
-  PsppireVarSheet *vs = (PsppireVarSheet *)obj;
+  PsppireDict *dict;
+  GtkTreePath *path;
+  gint row;
+
+  path = gtk_tree_path_new_from_string (path_string);
+  row = gtk_tree_path_get_indices (path)[0];
+  gtk_tree_path_free (path);
+
+  dict = psppire_var_sheet_get_dictionary (var_sheet);
+  g_return_val_if_fail (dict != NULL, NULL);
 
-  if (vs->dispose_has_run)
-    return;
+  return psppire_dict_get_variable (dict, row);
+}
 
-  /* Make sure dispose does not run twice. */
-  vs->dispose_has_run = TRUE;
+static void
+on_type_click (PsppireCellRendererButton *cell,
+               gchar *path,
+               PsppireVarSheet *var_sheet)
+{
+  var_sheet->var_type_dialog->pv = path_string_to_variable (var_sheet, path);
+  var_type_dialog_show (var_sheet->var_type_dialog);
+}
 
-  /* Chain up to the parent class */
-  G_OBJECT_CLASS (parent_class)->dispose (obj);
+static void
+on_value_labels_click (PsppireCellRendererButton *cell,
+                       gchar *path,
+                       PsppireVarSheet *var_sheet)
+{
+  struct variable *var = path_string_to_variable (var_sheet, path);
+  val_labs_dialog_set_target_variable (var_sheet->val_labs_dialog, var);
+  val_labs_dialog_show (var_sheet->val_labs_dialog);
 }
 
 static void
-psppire_var_sheet_finalize (GObject *obj)
+on_missing_values_click (PsppireCellRendererButton *cell,
+                         gchar *path,
+                         PsppireVarSheet *var_sheet)
 {
-   /* Chain up to the parent class */
-   G_OBJECT_CLASS (parent_class)->finalize (obj);
+  var_sheet->missing_val_dialog->pv = path_string_to_variable (var_sheet,
+                                                               path);
+  missing_val_dialog_show (var_sheet->missing_val_dialog);
 }
 
+static gint
+get_string_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
+                  const char *string)
+{
+  gint width;
+  g_object_set (G_OBJECT (renderer),
+                PSPPIRE_IS_CELL_RENDERER_BUTTON (renderer) ? "label" : "text",
+                string, (void *) NULL);
+  gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
+                              NULL, NULL, NULL, &width, NULL);
+  return width;
+}
+
+static gint
+get_monospace_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
+                     size_t char_cnt)
+{
+  struct string s;
+  gint width;
+
+  ds_init_empty (&s);
+  ds_put_byte_multiple (&s, '0', char_cnt);
+  ds_put_byte (&s, ' ');
+  width = get_string_width (treeview, renderer, ds_cstr (&s));
+  ds_destroy (&s);
+
+  return width;
+}
 
-struct column_parameters
+static PsppSheetViewColumn *
+add_var_sheet_column (PsppireVarSheet *var_sheet, GtkCellRenderer *renderer,
+                      enum vs_column column_id,
+                      const char *title, int width)
 {
-  gchar label[20];
-  gint width ;
-};
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
+  int title_width, content_width;
+  PsppSheetViewColumn *column;
+
+  column = pspp_sheet_view_column_new_with_attributes (title, renderer, NULL);
+  g_object_set_data (G_OBJECT (column), "column-number",
+                     GINT_TO_POINTER (column_id) + 1);
+
+  pspp_sheet_view_column_set_cell_data_func (
+    column, renderer, render_var_cell, var_sheet, NULL);
 
-#define n_ALIGNMENTS 3
+  title_width = get_string_width (sheet_view, renderer, title);
+  content_width = get_monospace_width (sheet_view, renderer, width);
+  g_object_set_data (G_OBJECT (column), "content-width",
+                     GINT_TO_POINTER (content_width));
 
-const gchar *const alignments[n_ALIGNMENTS + 1]={
-  N_("Left"),
-  N_("Right"),
-  N_("Center"),
-  0
-};
+  pspp_sheet_view_column_set_fixed_width (column,
+                                          MAX (title_width, content_width));
+  pspp_sheet_view_column_set_resizable (column, TRUE);
 
-const gchar *const measures[n_MEASURES + 1]={
-  N_("Nominal"),
-  N_("Ordinal"),
-  N_("Scale"),
-  0
-};
+  pspp_sheet_view_append_column (sheet_view, column);
 
+  g_signal_connect (renderer, "edited",
+                    G_CALLBACK (on_var_column_edited),
+                    var_sheet);
+  g_object_set_data (G_OBJECT (renderer), "column-id",
+                     GINT_TO_POINTER (column_id));
+  g_object_set_data (G_OBJECT (renderer), "var-sheet", var_sheet);
 
+  return column;
+}
+
+static PsppSheetViewColumn *
+add_text_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
+                 const char *title, int width)
+{
+  return add_var_sheet_column (var_sheet, gtk_cell_renderer_text_new (),
+                               column_id, title, width);
+}
+
+static PsppSheetViewColumn *
+add_spin_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
+                 const char *title, int width)
+{
+  return add_var_sheet_column (var_sheet, gtk_cell_renderer_spin_new (),
+                               column_id, title, width);
+}
 
-/* Create a list store from an array of strings */
-static GtkListStore *
-create_label_list (const gchar *const *labels)
+static PsppSheetViewColumn *
+add_combo_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
+                  const char *title, int width,
+                  ...)
 {
-  const gchar *s;
-  gint i = 0;
+  GtkCellRenderer *cell;
+  GtkListStore *store;
+  const char *name;
+  va_list args;
+
+  store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
+  va_start (args, width);
+  while ((name = va_arg (args, const char *)) != NULL)
+    {
+      int value = va_arg (args, int);
+      gtk_list_store_insert_with_values (store, NULL, G_MAXINT,
+                                         0, value,
+                                         1, name,
+                                         -1);
+    }
+  va_end (args);
+
+  cell = gtk_cell_renderer_combo_new ();
+  g_object_set (cell,
+                "has-entry", FALSE,
+                "model", GTK_TREE_MODEL (store),
+                "text-column", 1,
+                NULL);
+
+  return add_var_sheet_column (var_sheet, cell, column_id, title, width);
+
+}
+
+static void
+add_popup_menu (PsppireVarSheet *var_sheet,
+                PsppSheetViewColumn *column,
+                void (*on_click) (PsppireCellRendererButton *,
+                                  gchar *path,
+                                  PsppireVarSheet *var_sheet))
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
+  const char *button_label = "...";
+  GtkCellRenderer *button_renderer;
+  gint content_width;
+
+  button_renderer = psppire_cell_renderer_button_new ();
+  g_object_set (button_renderer,
+                "label", button_label,
+                "editable", TRUE,
+                NULL);
+  g_signal_connect (button_renderer, "clicked", G_CALLBACK (on_click),
+                    var_sheet);
+  pspp_sheet_view_column_pack_start (column, button_renderer, FALSE);
+  pspp_sheet_view_column_set_cell_data_func (
+    column, button_renderer, render_popup_cell, var_sheet, NULL);
+
+  content_width = GPOINTER_TO_INT (g_object_get_data (
+                                     G_OBJECT (column), "content-width"));
+  content_width += get_string_width (sheet_view, button_renderer,
+                                     button_label);
+  if (content_width > pspp_sheet_view_column_get_fixed_width (column))
+    pspp_sheet_view_column_set_fixed_width (column, content_width);
+}
+
+static gboolean
+get_tooltip_location (GtkWidget *widget, GtkTooltip *tooltip,
+                      gint wx, gint wy, size_t *row, size_t *column)
+{
+  PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
+  gint bx, by;
+  GtkTreePath *path;
   GtkTreeIter iter;
+  PsppSheetViewColumn *tree_column;
+  GtkTreeModel *tree_model;
+  gpointer column_ptr;
+  bool ok;
+
+  /* Check that WIDGET is really visible on the screen before we
+     do anything else.  This is a bug fix for a sticky situation:
+     when text_data_import_assistant() returns, it frees the data
+     necessary to compose the tool tip message, but there may be
+     a tool tip under preparation at that point (even if there is
+     no visible tool tip) that will call back into us a little
+     bit later.  Perhaps the correct solution to this problem is
+     to make the data related to the tool tips part of a GObject
+     that only gets destroyed when all references are released,
+     but this solution appears to be effective too. */
+  if (!gtk_widget_get_mapped (widget))
+    return FALSE;
+
+  pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
+                                                     wx, wy, &bx, &by);
+  if (!pspp_sheet_view_get_path_at_pos (tree_view, bx, by,
+                                      &path, &tree_column, NULL, NULL))
+    return FALSE;
+
+  column_ptr = g_object_get_data (G_OBJECT (tree_column), "column-number");
+  if (column_ptr == NULL)
+    return FALSE;
+  *column = GPOINTER_TO_INT (column_ptr) - 1;
+
+  pspp_sheet_view_set_tooltip_cell (tree_view, tooltip, path, tree_column,
+                                    NULL);
+
+  tree_model = pspp_sheet_view_get_model (tree_view);
+  ok = gtk_tree_model_get_iter (tree_model, &iter, path);
+  gtk_tree_path_free (path);
+  if (!ok)
+    return FALSE;
+
+  *row = GPOINTER_TO_INT (iter.user_data);
+  return TRUE;
+}
+
+static gboolean
+on_query_var_tooltip (GtkWidget *widget, gint wx, gint wy,
+                      gboolean keyboard_mode UNUSED,
+                      GtkTooltip *tooltip, gpointer *user_data UNUSED)
+{
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
+  PsppireDict *dict;
+  struct variable *var;
+  size_t row, column;
+
+  if (!get_tooltip_location (widget, tooltip, wx, wy, &row, &column))
+    return FALSE;
 
-  GtkListStore *list_store = gtk_list_store_new (1, G_TYPE_STRING);
+  dict = psppire_var_sheet_get_dictionary (var_sheet);
+  g_return_val_if_fail (dict != NULL, FALSE);
 
-  while ( (s = labels[i++]))
+  if (row >= psppire_dict_get_var_cnt (dict))
     {
-      gtk_list_store_append (list_store, &iter);
-      gtk_list_store_set (list_store, &iter,
-                         0, gettext (s),
-                         -1);
+      gtk_tooltip_set_text (tooltip, _("Enter a variable name to add a "
+                                       "new variable."));
+      return TRUE;
     }
 
-  return list_store;
+  var = psppire_dict_get_variable (dict, row);
+  g_return_val_if_fail (var != NULL, FALSE);
+
+  switch (column)
+    {
+    case VS_TYPE:
+      {
+        char text[FMT_STRING_LEN_MAX + 1];
+
+        fmt_to_string (var_get_print_format (var), text);
+        gtk_tooltip_set_text (tooltip, text);
+        return TRUE;
+      }
+
+    case VS_VALUES:
+      if (var_has_value_labels (var))
+        {
+          const struct val_labs *vls = var_get_value_labels (var);
+          const struct val_lab **labels = val_labs_sorted (vls);
+          struct string s;
+          size_t i;
+
+          ds_init_empty (&s);
+          for (i = 0; i < val_labs_count (vls); i++)
+            {
+              const struct val_lab *vl = labels[i];
+              gchar *vstr;
+
+              if (i >= 10 || ds_length (&s) > 500)
+                {
+                  ds_put_cstr (&s, "...");
+                  break;
+                }
+
+              vstr = value_to_text (vl->value, var);
+              ds_put_format (&s, _("{%s, %s}\n"), vstr,
+                             val_lab_get_escaped_label (vl));
+              free (vstr);
+
+            }
+          ds_chomp_byte (&s, '\n');
+
+          gtk_tooltip_set_text (tooltip, ds_cstr (&s));
+          ds_destroy (&s);
+
+          return TRUE;
+        }
+    }
+
+  return FALSE;
 }
 
+static void
+do_popup_menu (GtkWidget *widget, guint button, guint32 time)
+{
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
+  GtkWidget *menu;
+
+  menu = get_widget_assert (var_sheet->builder, "varsheet-variable-popup");
+  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
+}
+
+static void
+on_popup_menu (GtkWidget *widget, gpointer user_data UNUSED)
+{
+  do_popup_menu (widget, 0, gtk_get_current_event_time ());
+}
+
+static gboolean
+on_button_pressed (GtkWidget *widget, GdkEventButton *event,
+                   gpointer user_data UNUSED)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
+
+  if (event->type == GDK_BUTTON_PRESS && event->button == 3)
+    {
+      PsppSheetSelection *selection;
+
+      selection = pspp_sheet_view_get_selection (sheet_view);
+      if (pspp_sheet_selection_count_selected_rows (selection) <= 1)
+        {
+          GtkTreePath *path;
+
+          if (pspp_sheet_view_get_path_at_pos (sheet_view, event->x, event->y,
+                                               &path, NULL, NULL, NULL))
+            {
+              pspp_sheet_selection_unselect_all (selection);
+              pspp_sheet_selection_select_path (selection, path);
+              gtk_tree_path_free (path);
+            }
+        }
+
+      do_popup_menu (widget, event->button, event->time);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+\f
+GType
+psppire_fmt_use_get_type (void)
+{
+  static GType etype = 0;
+  if (etype == 0)
+    {
+      static const GEnumValue values[] =
+       {
+         { FMT_FOR_INPUT, "FMT_FOR_INPUT", "input" },
+         { FMT_FOR_OUTPUT, "FMT_FOR_OUTPUT", "output" },
+         { 0, NULL, NULL }
+       };
+
+      etype = g_enum_register_static
+       (g_intern_static_string ("PsppireFmtUse"), values);
+    }
+  return etype;
+}
+
+enum
+  {
+    PROP_0,
+    PROP_DICTIONARY,
+    PROP_MAY_CREATE_VARS,
+    PROP_MAY_DELETE_VARS,
+    PROP_FORMAT_TYPE,
+    PROP_UI_MANAGER
+  };
 
 static void
 psppire_var_sheet_set_property (GObject      *object,
-                                guint         property_id,
-                                const GValue *value,
-                                GParamSpec   *pspec)
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
 {
-  PsppireVarSheet *self = (PsppireVarSheet *) object;
+  PsppireVarSheet *obj = PSPPIRE_VAR_SHEET (object);
 
-  switch (property_id)
+  switch (prop_id)
     {
-    case PSPPIRE_VAR_SHEET_MAY_CREATE_VARS:
-      self->may_create_vars = g_value_get_boolean (value);
+    case PROP_DICTIONARY:
+      psppire_var_sheet_set_dictionary (obj,
+                                        PSPPIRE_DICT (g_value_get_object (
+                                                        value)));
+      break;
+
+    case PROP_MAY_CREATE_VARS:
+      psppire_var_sheet_set_may_create_vars (obj,
+                                             g_value_get_boolean (value));
       break;
 
+    case PROP_MAY_DELETE_VARS:
+      psppire_var_sheet_set_may_delete_vars (obj,
+                                             g_value_get_boolean (value));
+      break;
+
+    case PROP_FORMAT_TYPE:
+      obj->format_use = g_value_get_enum (value);
+      break;
+
+    case PROP_UI_MANAGER:
     default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
     }
 }
 
 static void
 psppire_var_sheet_get_property (GObject      *object,
-                                guint         property_id,
+                                guint         prop_id,
                                 GValue       *value,
                                 GParamSpec   *pspec)
 {
-  PsppireVarSheet *self = (PsppireVarSheet *) object;
+  PsppireVarSheet *obj = PSPPIRE_VAR_SHEET (object);
 
-  switch (property_id)
+  switch (prop_id)
     {
-    case PSPPIRE_VAR_SHEET_MAY_CREATE_VARS:
-      g_value_set_boolean (value, self->may_create_vars);
+    case PROP_DICTIONARY:
+      g_value_set_object (value,
+                          G_OBJECT (psppire_var_sheet_get_dictionary (obj)));
+      break;
+
+    case PROP_MAY_CREATE_VARS:
+      g_value_set_boolean (value, obj->may_create_vars);
+      break;
+
+    case PROP_MAY_DELETE_VARS:
+      g_value_set_boolean (value, obj->may_delete_vars);
+      break;
+
+    case PROP_FORMAT_TYPE:
+      g_value_set_enum (value, obj->format_use);
+      break;
+
+    case PROP_UI_MANAGER:
+      g_value_set_object (value, psppire_var_sheet_get_ui_manager (obj));
       break;
 
     default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
     }
 }
 
+static void
+psppire_var_sheet_realize (GtkWidget *w)
+{
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (w);
+  GtkWindow *toplevel;
+
+  GTK_WIDGET_CLASS (psppire_var_sheet_parent_class)->realize (w);
+
+  toplevel = GTK_WINDOW (gtk_widget_get_toplevel (w));
+  var_sheet->val_labs_dialog = val_labs_dialog_create (toplevel);
+  var_sheet->missing_val_dialog = missing_val_dialog_create (toplevel);
+  var_sheet->var_type_dialog = var_type_dialog_create (toplevel);
+}
 
 static void
-psppire_var_sheet_class_init (PsppireVarSheetClass *klass)
+psppire_var_sheet_destroy (GtkObject *obj)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (obj);
+
+  GTK_OBJECT_CLASS (psppire_var_sheet_parent_class)->destroy (obj);
+
+  psppire_var_sheet_set_dictionary (var_sheet, NULL);
+
+  if (var_sheet->val_labs_dialog)
+    {
+      g_object_unref (var_sheet->val_labs_dialog);
+      var_sheet->val_labs_dialog = NULL;
+    }
+
+  if (var_sheet->missing_val_dialog)
+    {
+      g_object_unref (var_sheet->missing_val_dialog);
+      var_sheet->missing_val_dialog = NULL;
+    }
+
+  if (var_sheet->var_type_dialog)
+    {
+      g_object_unref (var_sheet->var_type_dialog);
+      var_sheet->var_type_dialog = NULL;
+    }
+}
+
+static void
+psppire_var_sheet_class_init (PsppireVarSheetClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+  GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (class);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
   GParamSpec *pspec;
 
-  parent_class = g_type_class_peek_parent (klass);
+  gobject_class->set_property = psppire_var_sheet_set_property;
+  gobject_class->get_property = psppire_var_sheet_get_property;
 
-  object_class->dispose = psppire_var_sheet_dispose;
-  object_class->finalize = psppire_var_sheet_finalize;
   widget_class->realize = psppire_var_sheet_realize;
-  widget_class->unrealize = psppire_var_sheet_unrealize;
-  object_class->set_property = psppire_var_sheet_set_property;
-  object_class->get_property = psppire_var_sheet_get_property;
+
+  gtk_object_class->destroy = psppire_var_sheet_destroy;
+
+  g_signal_new ("var-double-clicked",
+                G_OBJECT_CLASS_TYPE (gobject_class),
+                G_SIGNAL_RUN_LAST,
+                0,
+                g_signal_accumulator_true_handled, NULL,
+                psppire_marshal_BOOLEAN__INT,
+                G_TYPE_BOOLEAN, 1, G_TYPE_INT);
+
+  pspec = g_param_spec_object ("dictionary",
+                               "Dictionary displayed by the sheet",
+                               "The PsppireDict that the sheet displays "
+                               "may allow the user to edit",
+                               PSPPIRE_TYPE_DICT,
+                               G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_DICTIONARY, pspec);
 
   pspec = g_param_spec_boolean ("may-create-vars",
                                 "May create variables",
                                 "Whether the user may create more variables",
                                 TRUE,
                                 G_PARAM_READWRITE);
-  g_object_class_install_property (object_class,
-                                   PSPPIRE_VAR_SHEET_MAY_CREATE_VARS,
-                                   pspec);
+  g_object_class_install_property (gobject_class, PROP_MAY_CREATE_VARS, pspec);
 
-  klass->measure_list = create_label_list (measures);
-  klass->alignment_list = create_label_list (alignments);
+  pspec = g_param_spec_boolean ("may-delete-vars",
+                                "May delete variables",
+                                "Whether the user may delete variables",
+                                TRUE,
+                                G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_MAY_DELETE_VARS, pspec);
+
+  pspec = g_param_spec_enum ("format-use",
+                             "Use of variable format",
+                             ("Whether variables have input or output "
+                              "formats"),
+                             PSPPIRE_TYPE_FMT_USE,
+                             FMT_FOR_OUTPUT,
+                             G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_FORMAT_TYPE, pspec);
+
+  pspec = g_param_spec_object ("ui-manager",
+                               "UI Manager",
+                               "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.",
+                               GTK_TYPE_UI_MANAGER,
+                               G_PARAM_READABLE);
+  g_object_class_install_property (gobject_class, PROP_UI_MANAGER, pspec);
 }
 
-
-
-/* Callback for when the alignment combo box
-   item is selected */
 static void
-change_alignment (GtkComboBox *cb,
-                 struct variable *var)
+render_row_number_cell (PsppSheetViewColumn *tree_column,
+                        GtkCellRenderer *cell,
+                        GtkTreeModel *model,
+                        GtkTreeIter *iter,
+                        gpointer user_data)
 {
-  gint active_item = gtk_combo_box_get_active (cb);
+  PsppireVarSheet *var_sheet = user_data;
+  GValue gvalue = { 0, };
+  gint row;
 
-  if ( active_item < 0 ) return ;
-
-  var_set_alignment (var, active_item);
-}
+  row = GPOINTER_TO_INT (iter->user_data);
 
+  g_value_init (&gvalue, G_TYPE_INT);
+  g_value_set_int (&gvalue, row + 1);
+  g_object_set_property (G_OBJECT (cell), "label", &gvalue);
+  g_value_unset (&gvalue);
 
+  if (!var_sheet->dict || row < psppire_dict_get_var_cnt (var_sheet->dict))
+    g_object_set (cell, "editable", TRUE, NULL);
+  else
+    g_object_set (cell, "editable", FALSE, NULL);
+}
 
-/* Callback for when the measure combo box
-   item is selected */
 static void
-change_measure (GtkComboBox *cb,
-               struct variable *var)
+psppire_var_sheet_row_number_double_clicked (PsppireCellRendererButton *button,
+                                             gchar *path_string,
+                                             PsppireVarSheet *var_sheet)
 {
-  gint active_item = gtk_combo_box_get_active (cb);
+  GtkTreePath *path;
 
-  if ( active_item < 0 ) return ;
+  g_return_if_fail (var_sheet->dict != NULL);
 
-  var_set_measure (var, active_item);
+  path = gtk_tree_path_new_from_string (path_string);
+  if (gtk_tree_path_get_depth (path) == 1)
+    {
+      gint *indices = gtk_tree_path_get_indices (path);
+      if (indices[0] < psppire_dict_get_var_cnt (var_sheet->dict))
+        {
+          gboolean handled;
+          g_signal_emit_by_name (var_sheet, "var-double-clicked",
+                                 indices[0], &handled);
+        }
+    }
+  gtk_tree_path_free (path);
 }
 
+static PsppSheetViewColumn *
+make_row_number_column (PsppireVarSheet *var_sheet)
+{
+  PsppSheetViewColumn *column;
+  GtkCellRenderer *renderer;
+
+  renderer = psppire_cell_renderer_button_new ();
+  g_object_set (renderer, "xalign", 1.0, NULL);
+  g_signal_connect (renderer, "double-clicked",
+                    G_CALLBACK (psppire_var_sheet_row_number_double_clicked),
+                    var_sheet);
+
+  column = pspp_sheet_view_column_new_with_attributes (_("Variable"),
+                                                       renderer, NULL);
+  pspp_sheet_view_column_set_cell_data_func (
+    column, renderer, render_row_number_cell, var_sheet, NULL);
+  pspp_sheet_view_column_set_fixed_width (column, 50);
+  return column;
+}
 
-/* Moves the focus to a new cell.
-   Returns TRUE iff the move should be disallowed */
-static gboolean
-traverse_cell_callback (PsppireSheet *sheet,
-                       const PsppireSheetCell *existing_cell,
-                       PsppireSheetCell *new_cell)
+static void
+on_edit_clear_variables (GtkAction *action, PsppireVarSheet *var_sheet)
 {
-  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (sheet);
-  PsppireVarStore *var_store = PSPPIRE_VAR_STORE (psppire_sheet_get_model (sheet));
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
+  PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
+  PsppireDict *dict = var_sheet->dict;
+  const struct range_set_node *node;
+  struct range_set *selected;
+
+  selected = pspp_sheet_selection_get_range_set (selection);
+  for (node = range_set_last (selected); node != NULL;
+       node = range_set_prev (selected, node))
+    {
+      int i;
+
+      for (i = 1; i <= range_set_node_get_width (node); i++)
+        {
+          unsigned long row = range_set_node_get_end (node) - i;
+          if (row >= 0 && row < psppire_dict_get_var_cnt (dict))
+            psppire_dict_delete_variables (dict, row, 1);
+        }
+    }
+  range_set_destroy (selected);
+}
 
-  gint n_vars = psppire_var_store_get_var_cnt (var_store);
+static void
+on_selection_changed (PsppSheetSelection *selection,
+                      gpointer user_data UNUSED)
+{
+  PsppSheetView *sheet_view = pspp_sheet_selection_get_tree_view (selection);
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (sheet_view);
+  gint n_selected_rows;
+  gboolean may_delete;
+  GtkTreePath *path;
+  GtkAction *action;
 
-  if (new_cell->col >=  PSPPIRE_VAR_STORE_n_COLS)
-    return TRUE;
+  n_selected_rows = pspp_sheet_selection_count_selected_rows (selection);
 
-  if (new_cell->row >= n_vars && !var_sheet->may_create_vars)
-    return TRUE;
+  action = get_action_assert (var_sheet->builder, "edit_insert-variable");
+  gtk_action_set_sensitive (action, (var_sheet->may_create_vars
+                                     && n_selected_rows > 0));
 
-  if ( existing_cell->row == n_vars && new_cell->row >= n_vars)
+  switch (n_selected_rows)
     {
-      GtkEntry *entry = psppire_sheet_get_entry (sheet);
-
-      const gchar *name = gtk_entry_get_text (entry);
-
-      if (! psppire_dict_check_name (var_store->dictionary, name, TRUE))
-       return TRUE;
+    case 0:
+      may_delete = FALSE;
+      break;
 
-      psppire_dict_insert_variable (var_store->dictionary, existing_cell->row, name);
+    case 1:
+      /* The row used for inserting new variables cannot be deleted. */
+      path = gtk_tree_path_new_from_indices (
+        psppire_dict_get_var_cnt (var_sheet->dict), -1);
+      may_delete = !pspp_sheet_selection_path_is_selected (selection, path);
+      gtk_tree_path_free (path);
+      break;
 
-      return FALSE;
+    default:
+      may_delete = TRUE;
+      break;
     }
+  action = get_action_assert (var_sheet->builder, "edit_clear-variables");
+  gtk_action_set_sensitive (action, var_sheet->may_delete_vars && may_delete);
+}
+
+static void
+on_edit_insert_variable (GtkAction *action, PsppireVarSheet *var_sheet)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
+  PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
+  PsppireDict *dict = var_sheet->dict;
+  struct range_set *selected;
+  unsigned long row;
 
+  selected = pspp_sheet_selection_get_range_set (selection);
+  row = range_set_scan (selected, 0);
+  range_set_destroy (selected);
 
-  /* If the destination cell is outside the current  variables, then
-     automatically create variables for the new rows.
-  */
-  if ( ((new_cell->row > n_vars) ||
-        (new_cell->row == n_vars &&
-        new_cell->col != PSPPIRE_VAR_STORE_COL_NAME)) )
+  if (row <= psppire_dict_get_var_cnt (dict))
     {
-      gint i;
-      for ( i = n_vars ; i <= new_cell->row; ++i )
-       psppire_dict_insert_variable (var_store->dictionary, i, NULL);
+      gchar name[64];;
+      if (psppire_dict_generate_name (dict, name, sizeof name))
+        psppire_dict_insert_variable (dict, row, name);
     }
-
-  return FALSE;
 }
 
-
-
-/*
-   Callback whenever the active cell changes on the var sheet.
-*/
 static void
-var_sheet_change_active_cell (PsppireVarSheet *vs,
-                             gint row, gint column,
-                             gint oldrow, gint oldcolumn,
-                             gpointer data)
+psppire_var_sheet_init (PsppireVarSheet *obj)
 {
-  PsppireVarStore *var_store;
-  PsppireVarSheetClass *vs_class =
-    PSPPIRE_VAR_SHEET_CLASS(G_OBJECT_GET_CLASS (vs));
-
-  struct variable *var ;
-  PsppireSheet *sheet = PSPPIRE_SHEET (vs);
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (obj);
+  PsppSheetViewColumn *column;
+  GtkAction *action;
+  GList *list;
 
-  g_return_if_fail (sheet != NULL);
+  obj->dict = NULL;
+  obj->format_use = PSPPIRE_TYPE_FMT_USE;
+  obj->may_create_vars = TRUE;
+  obj->may_delete_vars = TRUE;
 
-  var_store = PSPPIRE_VAR_STORE (psppire_sheet_get_model (sheet));
+  obj->scroll_to_bottom_signal = 0;
 
-  g_assert (var_store);
+  obj->container = NULL;
 
-  g_return_if_fail (oldcolumn == PSPPIRE_VAR_STORE_COL_NAME ||
-                   row < psppire_var_store_get_var_cnt (var_store));
+  pspp_sheet_view_append_column (sheet_view, make_row_number_column (obj));
 
-  var = psppire_var_store_get_var (var_store, row);
+  column = add_text_column (obj, VS_NAME, _("Name"), 12);
+  list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
+  g_signal_connect (list->data, "editing-started",
+                    G_CALLBACK (on_name_column_editing_started), NULL);
+  g_list_free (list);
 
-  switch (column)
-    {
-    case PSPPIRE_VAR_STORE_COL_ALIGN:
-      {
-       GtkEntry *entry;
-       static GtkListStore *list_store = NULL;
-       GtkComboBoxEntry *cbe;
-       psppire_sheet_change_entry (sheet, GTK_TYPE_COMBO_BOX_ENTRY);
-       entry = psppire_sheet_get_entry (sheet);
-       cbe = GTK_COMBO_BOX_ENTRY (GTK_WIDGET (entry)->parent);
+  column = add_text_column (obj, VS_TYPE, _("Type"), 8);
+  add_popup_menu (obj, column, on_type_click);
 
-       if ( ! list_store) list_store = create_label_list (alignments);
+  add_spin_column (obj, VS_WIDTH, _("Width"), 5);
 
-       gtk_combo_box_set_model (GTK_COMBO_BOX (cbe),
-                               GTK_TREE_MODEL (vs_class->alignment_list));
+  add_spin_column (obj, VS_DECIMALS, _("Decimals"), 2);
 
-       gtk_combo_box_entry_set_text_column (cbe, 0);
-
-       g_signal_connect (cbe, "changed",
-                        G_CALLBACK (change_alignment), var);
-      }
-      break;
+  add_text_column (obj, VS_LABEL, _("Label"), 20);
 
-    case PSPPIRE_VAR_STORE_COL_MEASURE:
-      {
-       GtkEntry *entry;
-       GtkComboBoxEntry *cbe;
-       psppire_sheet_change_entry (sheet, GTK_TYPE_COMBO_BOX_ENTRY);
-       entry = psppire_sheet_get_entry (sheet);
-       cbe = GTK_COMBO_BOX_ENTRY (GTK_WIDGET (entry)->parent);
+  column = add_text_column (obj, VS_VALUES, _("Value Labels"), 20);
+  add_popup_menu (obj, column, on_value_labels_click);
 
-       gtk_combo_box_set_model (GTK_COMBO_BOX (cbe),
-                               GTK_TREE_MODEL (vs_class->measure_list));
+  column = add_text_column (obj, VS_MISSING, _("Missing Values"), 20);
+  add_popup_menu (obj, column, on_missing_values_click);
 
-       gtk_combo_box_entry_set_text_column (cbe, 0);
+  add_spin_column (obj, VS_COLUMNS, _("Columns"), 3);
 
-       g_signal_connect (cbe, "changed",
-                         G_CALLBACK (change_measure), var);
-      }
-      break;
+  add_combo_column (obj, VS_ALIGN, _("Align"), 6,
+                    alignment_to_string (ALIGN_LEFT), ALIGN_LEFT,
+                    alignment_to_string (ALIGN_CENTRE), ALIGN_CENTRE,
+                    alignment_to_string (ALIGN_RIGHT), ALIGN_RIGHT,
+                    NULL);
 
-    case PSPPIRE_VAR_STORE_COL_VALUES:
-      {
-       PsppireCustomEntry *customEntry;
+  add_combo_column (obj, VS_MEASURE, _("Measure"), 10,
+                    measure_to_string (MEASURE_NOMINAL), MEASURE_NOMINAL,
+                    measure_to_string (MEASURE_ORDINAL), MEASURE_ORDINAL,
+                    measure_to_string (MEASURE_SCALE), MEASURE_SCALE,
+                    NULL);
 
-       psppire_sheet_change_entry (sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
+  pspp_sheet_view_set_rubber_banding (sheet_view, TRUE);
+  pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (sheet_view),
+                                 PSPP_SHEET_SELECTION_MULTIPLE);
 
-       customEntry =
-         PSPPIRE_CUSTOM_ENTRY (psppire_sheet_get_entry (sheet));
+  g_object_set (G_OBJECT (obj), "has-tooltip", TRUE, NULL);
+  g_signal_connect (obj, "query-tooltip",
+                    G_CALLBACK (on_query_var_tooltip), NULL);
+  g_signal_connect (obj, "button-press-event",
+                    G_CALLBACK (on_button_pressed), NULL);
+  g_signal_connect (obj, "popup-menu", G_CALLBACK (on_popup_menu), NULL);
 
-       val_labs_dialog_set_target_variable (vs->val_labs_dialog, var);
+  obj->builder = builder_new ("var-sheet.ui");
 
-       g_signal_connect_swapped (customEntry,
-                                 "clicked",
-                                 G_CALLBACK (val_labs_dialog_show),
-                                 vs->val_labs_dialog);
-      }
-      break;
+  action = get_action_assert (obj->builder, "edit_clear-variables");
+  g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_variables),
+                    obj);
+  gtk_action_set_sensitive (action, FALSE);
+  g_signal_connect (pspp_sheet_view_get_selection (sheet_view),
+                    "changed", G_CALLBACK (on_selection_changed), NULL);
 
-    case PSPPIRE_VAR_STORE_COL_MISSING:
-      {
-       PsppireCustomEntry *customEntry;
+  action = get_action_assert (obj->builder, "edit_insert-variable");
+  gtk_action_set_sensitive (action, FALSE);
+  g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_variable),
+                    obj);
+}
 
-       psppire_sheet_change_entry (sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
+GtkWidget *
+psppire_var_sheet_new (void)
+{
+  return g_object_new (PSPPIRE_VAR_SHEET_TYPE, NULL);
+}
 
-       customEntry =
-         PSPPIRE_CUSTOM_ENTRY (psppire_sheet_get_entry (sheet));
+PsppireDict *
+psppire_var_sheet_get_dictionary (PsppireVarSheet *var_sheet)
+{
+  return var_sheet->dict;
+}
 
-       vs->missing_val_dialog->pv =
-         psppire_var_store_get_var (var_store, row);
+static void
+refresh_model (PsppireVarSheet *var_sheet)
+{
+  pspp_sheet_view_set_model (PSPP_SHEET_VIEW (var_sheet), NULL);
 
-       g_signal_connect_swapped (customEntry,
-                                 "clicked",
-                                 G_CALLBACK (missing_val_dialog_show),
-                                 vs->missing_val_dialog);
-      }
-      break;
+  if (var_sheet->dict != NULL)
+    {
+      PsppireEmptyListStore *store;
+      int n_rows;
+
+      n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
+                + var_sheet->may_create_vars);
+      store = psppire_empty_list_store_new (n_rows);
+      pspp_sheet_view_set_model (PSPP_SHEET_VIEW (var_sheet),
+                                 GTK_TREE_MODEL (store));
+      g_object_unref (store);
+    }
+}
 
-    case PSPPIRE_VAR_STORE_COL_TYPE:
-      {
-       PsppireCustomEntry *customEntry;
+static void
+on_var_inserted (PsppireDict *dict, glong row, PsppireVarSheet *var_sheet)
+{
+  PsppireEmptyListStore *store;
+  int n_rows;
 
-       psppire_sheet_change_entry (sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
+  g_return_if_fail (dict == var_sheet->dict);
 
-       customEntry =
-         PSPPIRE_CUSTOM_ENTRY (psppire_sheet_get_entry (sheet));
+  store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
+                                      PSPP_SHEET_VIEW (var_sheet)));
+  g_return_if_fail (store != NULL);
 
+  n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
+            + var_sheet->may_create_vars);
+  psppire_empty_list_store_set_n_rows (store, n_rows);
+  psppire_empty_list_store_row_inserted (store, row);
+}
 
-       /* Popup the Variable Type dialog box */
-       vs->var_type_dialog->pv = var;
+static void
+on_var_deleted (PsppireDict *dict,
+                const struct variable *var, int dict_idx, int case_idx,
+                PsppireVarSheet *var_sheet)
+{
+  PsppireEmptyListStore *store;
+  int n_rows;
 
-       g_signal_connect_swapped (customEntry,
-                                "clicked",
-                                G_CALLBACK (var_type_dialog_show),
-                                 vs->var_type_dialog);
-      }
-      break;
+  g_return_if_fail (dict == var_sheet->dict);
 
-    case PSPPIRE_VAR_STORE_COL_WIDTH:
-    case PSPPIRE_VAR_STORE_COL_DECIMALS:
-    case PSPPIRE_VAR_STORE_COL_COLUMNS:
-      {
-       if ( psppire_sheet_model_is_editable (PSPPIRE_SHEET_MODEL(var_store),
-                                             row, column))
-         {
-           gint r_min, r_max;
-
-           const gchar *s = psppire_sheet_cell_get_text (sheet, row, column);
-
-           if (s)
-             {
-               GtkSpinButton *spinButton ;
-               const gint current_value  = g_strtod (s, NULL);
-               GtkObject *adj ;
-
-               const struct fmt_spec *fmt = var_get_print_format (var);
-               switch (column)
-                 {
-                 case PSPPIRE_VAR_STORE_COL_WIDTH:
-                   r_min = MAX (fmt->d + 1, fmt_min_output_width (fmt->type));
-                   r_max = fmt_max_output_width (fmt->type);
-                   break;
-                 case PSPPIRE_VAR_STORE_COL_DECIMALS:
-                   r_min = 0 ;
-                   r_max = fmt_max_output_decimals (fmt->type, fmt->w);
-                   break;
-                 case PSPPIRE_VAR_STORE_COL_COLUMNS:
-                   r_min = 1;
-                   r_max = 255 ; /* Is this a sensible value ? */
-                   break;
-                 default:
-                   g_assert_not_reached ();
-                 }
-
-               adj = gtk_adjustment_new (current_value,
-                                         r_min, r_max,
-                                         1.0, 1.0, /* steps */
-                                         0);
-
-               psppire_sheet_change_entry (sheet, GTK_TYPE_SPIN_BUTTON);
-
-               spinButton =
-                 GTK_SPIN_BUTTON (psppire_sheet_get_entry (sheet));
-
-               gtk_spin_button_set_adjustment (spinButton, GTK_ADJUSTMENT (adj));
-               gtk_spin_button_set_digits (spinButton, 0);
-             }
-         }
-      }
-      break;
+  store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
+                                      PSPP_SHEET_VIEW (var_sheet)));
+  g_return_if_fail (store != NULL);
 
-    default:
-      psppire_sheet_change_entry (sheet, GTK_TYPE_ENTRY);
-      break;
-    }
+  n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
+            + var_sheet->may_create_vars);
+  psppire_empty_list_store_set_n_rows (store, n_rows);
+  psppire_empty_list_store_row_deleted (store, dict_idx);
 }
 
-
 static void
-psppire_var_sheet_realize (GtkWidget *w)
+on_backend_changed (PsppireDict *dict, PsppireVarSheet *var_sheet)
+{
+  g_return_if_fail (dict == var_sheet->dict);
+  refresh_model (var_sheet);
+}
+
+void
+psppire_var_sheet_set_dictionary (PsppireVarSheet *var_sheet,
+                                  PsppireDict *dict)
 {
-  PsppireVarSheet *vs = PSPPIRE_VAR_SHEET (w);
+  enum {
+    BACKEND_CHANGED,
+    VARIABLE_INSERTED,
+    VARIABLE_DELETED,
+    N_SIGNALS
+  };
 
-  GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (vs));
+  if (var_sheet->dict != NULL)
+    {
+      if (var_sheet->dict_signals)
+        {
+          int i;
+
+          for (i = 0; i < N_SIGNALS; i++)
+            g_signal_handler_disconnect (var_sheet->dict,
+                                         var_sheet->dict_signals[i]);
+
+          g_free (var_sheet->dict_signals);
+          var_sheet->dict_signals = NULL;
+        }
+      g_object_unref (var_sheet->dict);
+    }
 
-  vs->val_labs_dialog = val_labs_dialog_create (GTK_WINDOW (toplevel));
+  var_sheet->dict = dict;
 
-  vs->missing_val_dialog = missing_val_dialog_create (GTK_WINDOW (toplevel));
-  vs->var_type_dialog = var_type_dialog_create (GTK_WINDOW (toplevel));
+  if (dict != NULL)
+    {
+      g_object_ref (dict);
 
-  /* Chain up to the parent class */
-  GTK_WIDGET_CLASS (parent_class)->realize (w);
-}
+      var_sheet->dict_signals = g_malloc0 (
+        N_SIGNALS * sizeof *var_sheet->dict_signals);
 
-static void
-psppire_var_sheet_unrealize (GtkWidget *w)
-{
-  PsppireVarSheet *vs = PSPPIRE_VAR_SHEET (w);
+      var_sheet->dict_signals[BACKEND_CHANGED]
+        = g_signal_connect (dict, "backend-changed",
+                            G_CALLBACK (on_backend_changed), var_sheet);
 
-  g_free (vs->val_labs_dialog);
-  g_free (vs->missing_val_dialog);
-  g_free (vs->var_type_dialog);
+      var_sheet->dict_signals[VARIABLE_DELETED]
+        = g_signal_connect (dict, "variable-inserted",
+                            G_CALLBACK (on_var_inserted), var_sheet);
 
-  /* Chain up to the parent class */
-  GTK_WIDGET_CLASS (parent_class)->unrealize (w);
+      var_sheet->dict_signals[VARIABLE_INSERTED]
+        = g_signal_connect (dict, "variable-deleted",
+                            G_CALLBACK (on_var_deleted), var_sheet);
+    }
+  refresh_model (var_sheet);
 }
 
+gboolean
+psppire_var_sheet_get_may_create_vars (PsppireVarSheet *var_sheet)
+{
+  return var_sheet->may_create_vars;
+}
 
-
-static void
-psppire_var_sheet_init (PsppireVarSheet *vs)
+void
+psppire_var_sheet_set_may_create_vars (PsppireVarSheet *var_sheet,
+                                       gboolean may_create_vars)
 {
-  GtkBuilder *builder = builder_new ("data-editor.ui");
+  if (var_sheet->may_create_vars != may_create_vars)
+    {
+      PsppireEmptyListStore *store;
+      gint n_rows;
 
-  connect_help (builder);
+      var_sheet->may_create_vars = may_create_vars;
 
-  g_object_unref (builder);
+      store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
+                                          PSPP_SHEET_VIEW (var_sheet)));
+      g_return_if_fail (store != NULL);
 
-  vs->dispose_has_run = FALSE;
-  vs->may_create_vars = TRUE;
+      n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
+                + var_sheet->may_create_vars);
+      psppire_empty_list_store_set_n_rows (store, n_rows);
 
-  g_signal_connect (vs, "activate",
-                   G_CALLBACK (var_sheet_change_active_cell),
-                   NULL);
+      if (may_create_vars)
+        psppire_empty_list_store_row_inserted (store, n_rows - 1);
+      else
+        psppire_empty_list_store_row_deleted (store, n_rows);
 
-  g_signal_connect (vs, "traverse",
-                   G_CALLBACK (traverse_cell_callback), NULL);
+      on_selection_changed (pspp_sheet_view_get_selection (
+                              PSPP_SHEET_VIEW (var_sheet)), NULL);
+    }
 }
 
-
-static const struct column_parameters column_def[] = {
-  { N_("Name"),    80},
-  { N_("Type"),    100},
-  { N_("Width"),   57},
-  { N_("Decimals"),91},
-  { N_("Label"),   95},
-  { N_("Values"),  103},
-  { N_("Missing"), 95},
-  { N_("Columns"), 80},
-  { N_("Align"),   69},
-  { N_("Measure"), 99},
-};
-
-GtkWidget*
-psppire_var_sheet_new (void)
+gboolean
+psppire_var_sheet_get_may_delete_vars (PsppireVarSheet *var_sheet)
 {
-  gint i;
-  PsppireAxis *ha = psppire_axis_new ();
-  PsppireAxis *va = psppire_axis_new ();
-
-  GtkWidget *w = g_object_new (psppire_var_sheet_get_type (), NULL);
-
-  for (i = 0 ; i < 10 ; ++i)
-    psppire_axis_append (ha, column_def[i].width);
+  return var_sheet->may_delete_vars;
+}
 
-  g_object_set (va,
-               "default-size", 25,
-               NULL);
+void
+psppire_var_sheet_set_may_delete_vars (PsppireVarSheet *var_sheet,
+                                       gboolean may_delete_vars)
+{
+  if (var_sheet->may_delete_vars != may_delete_vars)
+    {
+      var_sheet->may_delete_vars = may_delete_vars;
+      on_selection_changed (pspp_sheet_view_get_selection (
+                              PSPP_SHEET_VIEW (var_sheet)), NULL);
+    }
+}
 
-  g_object_set (ha, "minimum-extent", 0,
-               NULL);
+void
+psppire_var_sheet_goto_variable (PsppireVarSheet *var_sheet, int dict_index)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
+  GtkTreePath *path;
 
-  g_object_set (w,
-               "horizontal-axis", ha,
-               "vertical-axis", va,
-               NULL);
+  path = gtk_tree_path_new_from_indices (dict_index, -1);
+  pspp_sheet_view_scroll_to_cell (sheet_view, path, NULL, FALSE, 0.0, 0.0);
+  pspp_sheet_view_set_cursor (sheet_view, path, NULL, FALSE);
+  gtk_tree_path_free (path);
+}
 
-  return w;
+GtkUIManager *
+psppire_var_sheet_get_ui_manager (PsppireVarSheet *var_sheet)
+{
+  return GTK_UI_MANAGER (get_object_assert (var_sheet->builder,
+                                            "var_sheet_uim",
+                                            GTK_TYPE_UI_MANAGER));
 }
+