Logistical Regression Dialog: Automatically mark appropriate variables as categorical
[pspp] / src / ui / gui / psppire-data-editor.c
index 7d2374e0967894b8cb44c3323b4f19b4455a7869..18fd5e8e8d81d294f0557ecb8de1b6cb2b852ed0 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyrigght (C) 2008 Free Software Foundation, Inc.
+   Copyright (C) 2008, 2009, 2010, 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 <gtk/gtksignal.h>
-#include <gtk/gtk.h>
-#include <gtksheet/gtksheet.h>
-#include "psppire-data-editor.h"
-#include "psppire-var-sheet.h"
 
-#include <gtksheet/gsheet-hetero-column.h>
-#include <language/syntax-string-source.h>
-#include "psppire-data-store.h"
-#include "helper.h"
+#include "ui/gui/psppire-data-editor.h"
+
+#include <gtk/gtk.h>
+#include <gtk-contrib/gtkxpaned.h>
+
+#include "data/datasheet.h"
+#include "data/value-labels.h"
+#include "libpspp/range-set.h"
+#include "libpspp/str.h"
+#include "ui/gui/helper.h"
+#include "ui/gui/pspp-sheet-selection.h"
+#include "ui/gui/psppire-data-sheet.h"
+#include "ui/gui/psppire-data-store.h"
+#include "ui/gui/psppire-value-entry.h"
+#include "ui/gui/psppire-var-sheet.h"
+#include "ui/gui/psppire-conf.h"
 
-#include <gtksheet/gtkxpaned.h>
 #include <gettext.h>
 #define _(msgid) gettext (msgid)
-#define N_(msgid) msgid
-
-
-static void psppire_data_editor_remove_split (PsppireDataEditor *de);
-static void psppire_data_editor_set_split (PsppireDataEditor *de);
-
-enum {
-  DATA_SELECTION_CHANGED,
-  DATA_AVAILABLE_CHANGED,
-  CASES_SELECTED,
-  VARIABLES_SELECTED,
-  n_SIGNALS
-};
-
-
-static guint data_editor_signals [n_SIGNALS] = { 0 };
 
-
-static gboolean data_is_selected (PsppireDataEditor *de);
+#define FOR_EACH_DATA_SHEET(DATA_SHEET, IDX, DATA_EDITOR)       \
+  for ((IDX) = 0;                                               \
+       (IDX) < 4                                                \
+         && ((DATA_SHEET) = PSPPIRE_DATA_SHEET (                \
+               (DATA_EDITOR)->data_sheets[IDX])) != NULL;       \
+       (IDX)++)
 
 static void psppire_data_editor_class_init          (PsppireDataEditorClass *klass);
 static void psppire_data_editor_init                (PsppireDataEditor      *de);
 
+static void disconnect_data_sheets (PsppireDataEditor *);
+static void refresh_entry (PsppireDataEditor *);
+static void psppire_data_editor_update_ui_manager (PsppireDataEditor *);
+
 GType
 psppire_data_editor_get_type (void)
 {
@@ -86,107 +84,67 @@ psppire_data_editor_dispose (GObject *obj)
 {
   PsppireDataEditor *de = (PsppireDataEditor *) obj;
 
-  if (de->dispose_has_run)
-    return;
-
-  g_object_unref (de->data_store);
-  g_object_unref (de->var_store);
-
-  /* Make sure dispose does not run twice. */
-  de->dispose_has_run = TRUE;
-
-  /* Chain up to the parent class */
-  G_OBJECT_CLASS (parent_class)->dispose (obj);
-}
-
-static void
-psppire_data_editor_finalize (GObject *obj)
-{
-   /* Chain up to the parent class */
-   G_OBJECT_CLASS (parent_class)->finalize (obj);
-}
-
-
-
-static void popup_variable_menu (GtkSheet *sheet, gint column,
-                                GdkEventButton *event, gpointer data);
-
-static void popup_cases_menu (GtkSheet *sheet, gint row,
-                             GdkEventButton *event, gpointer data);
-
-
-
-
-/* Callback which occurs when the data sheet's column title
-   is double clicked */
-static gboolean
-on_data_column_clicked (PsppireDataEditor *de, gint col, gpointer data)
-{
-
-  gint current_row, current_column;
-
-  gtk_notebook_set_current_page (GTK_NOTEBOOK (de), PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
-
-  gtk_sheet_get_active_cell (GTK_SHEET (de->var_sheet),
-                            &current_row, &current_column);
-
-  gtk_sheet_set_active_cell (GTK_SHEET (de->var_sheet), col, current_column);
-
-  return FALSE;
-}
-
-
-/* Callback which occurs when the var sheet's row title
-   button is double clicked */
-static gboolean
-on_var_row_clicked (PsppireDataEditor *de, gint row, gpointer data)
-{
-  GtkSheetRange visible_range;
-
-  gint current_row, current_column;
-
-  gtk_notebook_set_current_page (GTK_NOTEBOOK(de), PSPPIRE_DATA_EDITOR_DATA_VIEW);
+  disconnect_data_sheets (de);
 
-  gtk_sheet_get_active_cell (GTK_SHEET (de->data_sheet[0]),
-                            &current_row, &current_column);
+  if (de->data_store)
+    {
+      g_object_unref (de->data_store);
+      de->data_store = NULL;
+    }
 
-  gtk_sheet_set_active_cell (GTK_SHEET (de->data_sheet[0]), current_row, row);
+  if (de->dict)
+    {
+      g_object_unref (de->dict);
+      de->dict = NULL;
+    }
 
-  gtk_sheet_get_visible_range (GTK_SHEET (de->data_sheet[0]), &visible_range);
+  if (de->font != NULL)
+    {
+      pango_font_description_free (de->font);
+      de->font = NULL;
+    }
 
-  if ( row < visible_range.col0 || row > visible_range.coli)
+  if (de->ui_manager)
     {
-      gtk_sheet_moveto (GTK_SHEET (de->data_sheet[0]),
-                       current_row, row, 0, 0);
+      g_object_unref (de->ui_manager);
+      de->ui_manager = NULL;
     }
 
-  return FALSE;
+  /* Chain up to the parent class */
+  G_OBJECT_CLASS (parent_class)->dispose (obj);
 }
 
-
-
 enum
   {
     PROP_0,
     PROP_DATA_STORE,
-    PROP_VAR_STORE,
-    PROP_COLUMN_MENU,
-    PROP_ROW_MENU,
+    PROP_DICTIONARY,
     PROP_VALUE_LABELS,
-    PROP_CURRENT_CASE,
-    PROP_CURRENT_VAR,
-    PROP_DATA_SELECTED,
-    PROP_SPLIT_WINDOW
+    PROP_SPLIT_WINDOW,
+    PROP_UI_MANAGER
   };
 
+static void
+psppire_data_editor_refresh_model (PsppireDataEditor *de)
+{
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (de->var_sheet);
+  PsppireDataSheet *data_sheet;
+  int i;
+
+  FOR_EACH_DATA_SHEET (data_sheet, i, de)
+    psppire_data_sheet_set_data_store (data_sheet, de->data_store);
+  psppire_var_sheet_set_dictionary (var_sheet, de->dict);
+}
+
 static void
 psppire_data_editor_set_property (GObject         *object,
                                  guint            prop_id,
                                  const GValue    *value,
                                  GParamSpec      *pspec)
 {
-  int i;
   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
+  PsppireDataSheet *data_sheet;
+  int i;
 
   switch (prop_id)
     {
@@ -194,80 +152,37 @@ psppire_data_editor_set_property (GObject         *object,
       psppire_data_editor_split_window (de, g_value_get_boolean (value));
       break;
     case PROP_DATA_STORE:
-      if ( de->data_store) g_object_unref (de->data_store);
+      if ( de->data_store)
+        {
+          g_signal_handlers_disconnect_by_func (de->data_store,
+                                                G_CALLBACK (refresh_entry),
+                                                de);
+          g_object_unref (de->data_store);
+        }
+
       de->data_store = g_value_get_pointer (value);
       g_object_ref (de->data_store);
+      psppire_data_editor_refresh_model (de);
 
-      for (i = 0 ; i < 4 ; ++i )
-       g_object_set (de->data_sheet[i],
-                     "row-geometry", de->data_store,
-                     "column-geometry", de->data_store,
-                     "model", de->data_store,
-                     NULL);
-      break;
-    case PROP_VAR_STORE:
-      if ( de->var_store) g_object_unref (de->var_store);
-      de->var_store = g_value_get_pointer (value);
-      g_object_ref (de->var_store);
-
-      g_object_set (de->var_sheet,
-                   "row-geometry", de->var_store,
-                   "model", de->var_store,
-                   NULL);
-      break;
-    case PROP_COLUMN_MENU:
-      {
-       GObject *menu = g_value_get_object (value);
-
-       g_signal_connect (de->data_sheet[0], "button-event-column",
-                         G_CALLBACK (popup_variable_menu), menu);
-      }
-      break;
-    case PROP_ROW_MENU:
-      {
-       GObject *menu = g_value_get_object (value);
+      g_signal_connect_swapped (de->data_store, "case-changed",
+                                G_CALLBACK (refresh_entry), de);
 
-       g_signal_connect (de->data_sheet[0], "button-event-row",
-                         G_CALLBACK (popup_cases_menu), menu);
-      }
       break;
-    case PROP_CURRENT_VAR:
-      {
-       gint row, col;
-       gint var = g_value_get_long (value);
-       switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (object)))
-         {
-         case PSPPIRE_DATA_EDITOR_DATA_VIEW:
-           gtk_sheet_get_active_cell (GTK_SHEET (de->data_sheet[0]), &row, &col);
-           gtk_sheet_set_active_cell (GTK_SHEET (de->data_sheet[0]), row, var);
-           gtk_sheet_moveto (GTK_SHEET (de->data_sheet[0]), row, var, 0.5, 0.5);
-           break;
-         case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
-           gtk_sheet_get_active_cell (GTK_SHEET (de->var_sheet), &row, &col);
-           gtk_sheet_set_active_cell (GTK_SHEET (de->var_sheet), var, col);
-           gtk_sheet_moveto (GTK_SHEET (de->var_sheet), var, col,  0.5, 0.5);
-           break;
-         default:
-           g_assert_not_reached ();
-           break;
-         };
-      }
-      break;
-    case PROP_CURRENT_CASE:
-      {
-       gint row, col;
-       gint case_num = g_value_get_long (value);
-       gtk_sheet_get_active_cell (GTK_SHEET (de->data_sheet[0]), &row, &col);
-       gtk_sheet_set_active_cell (GTK_SHEET (de->data_sheet[0]), case_num, col);
-       gtk_sheet_moveto (GTK_SHEET (de->data_sheet[0]), case_num, col, 0.5, 0.5);
-      }
+    case PROP_DICTIONARY:
+      if (de->dict)
+        g_object_unref (de->dict);
+      de->dict = g_value_get_pointer (value);
+      g_object_ref (de->dict);
+
+      psppire_var_sheet_set_dictionary (PSPPIRE_VAR_SHEET (de->var_sheet),
+                                        de->dict);
       break;
     case PROP_VALUE_LABELS:
-      {
-       psppire_data_store_show_labels (de->data_store,
-                                       g_value_get_boolean (value));
-      }
+      FOR_EACH_DATA_SHEET (data_sheet, i, de)
+        psppire_data_sheet_set_value_labels (data_sheet,
+                                          g_value_get_boolean (value));
       break;
+    case PROP_UI_MANAGER:
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -290,55 +205,62 @@ psppire_data_editor_get_property (GObject         *object,
     case PROP_DATA_STORE:
       g_value_set_pointer (value, de->data_store);
       break;
-    case PROP_VAR_STORE:
-      g_value_set_pointer (value, de->var_store);
+    case PROP_DICTIONARY:
+      g_value_set_pointer (value, de->dict);
       break;
-    case PROP_CURRENT_CASE:
-      {
-       gint row, column;
-       gtk_sheet_get_active_cell (GTK_SHEET (de->data_sheet[0]), &row, &column);
-       g_value_set_long (value, row);
-      }
-      break;
-    case PROP_CURRENT_VAR:
-      {
-       gint row, column;
-       gtk_sheet_get_active_cell (GTK_SHEET (de->data_sheet[0]), &row, &column);
-       g_value_set_long (value, column);
-      }
+    case PROP_VALUE_LABELS:
+      g_value_set_boolean (value,
+                           psppire_data_sheet_get_value_labels (
+                             PSPPIRE_DATA_SHEET (de->data_sheets[0])));
       break;
-    case PROP_DATA_SELECTED:
-      g_value_set_boolean (value, data_is_selected (de));
+    case PROP_UI_MANAGER:
+      g_value_set_object (value, psppire_data_editor_get_ui_manager (de));
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
-    };
+    }
 }
 
+static void
+psppire_data_editor_switch_page (GtkNotebook     *notebook,
+                                GtkWidget *w,
+                                 guint            page_num)
+{
+  GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
+  psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (notebook));
+}
+
+static void
+psppire_data_editor_set_focus_child (GtkContainer *container,
+                                     GtkWidget    *widget)
+{
+  GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
+  psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (container));
+}
 
 static void
 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
 {
   GParamSpec *data_store_spec ;
-  GParamSpec *var_store_spec ;
-  GParamSpec *column_menu_spec;
-  GParamSpec *row_menu_spec;
+  GParamSpec *dict_spec ;
   GParamSpec *value_labels_spec;
-  GParamSpec *current_case_spec;
-  GParamSpec *current_var_spec;
-  GParamSpec *data_selected_spec;
   GParamSpec *split_window_spec;
+  GParamSpec *ui_manager_spec;
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+  GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
 
   parent_class = g_type_class_peek_parent (klass);
 
   object_class->dispose = psppire_data_editor_dispose;
-  object_class->finalize = psppire_data_editor_finalize;
-
   object_class->set_property = psppire_data_editor_set_property;
   object_class->get_property = psppire_data_editor_get_property;
 
+  container_class->set_focus_child = psppire_data_editor_set_focus_child;
+
+  notebook_class->switch_page = psppire_data_editor_switch_page;
+
   data_store_spec =
     g_param_spec_pointer ("data-store",
                          "Data Store",
@@ -349,38 +271,15 @@ psppire_data_editor_class_init (PsppireDataEditorClass *klass)
                                    PROP_DATA_STORE,
                                    data_store_spec);
 
-  var_store_spec =
-    g_param_spec_pointer ("var-store",
-                         "Variable Store",
-                         "A pointer to the variable store associated with this editor",
+  dict_spec =
+    g_param_spec_pointer ("dictionary",
+                         "Dictionary",
+                         "A pointer to the dictionary associated with this editor",
                          G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
 
   g_object_class_install_property (object_class,
-                                   PROP_VAR_STORE,
-                                   var_store_spec);
-
-  column_menu_spec =
-    g_param_spec_object ("column-menu",
-                        "Column Menu",
-                        "A menu to be displayed when button 3 is pressed in the column title buttons",
-                        GTK_TYPE_MENU,
-                        G_PARAM_WRITABLE);
-
-  g_object_class_install_property (object_class,
-                                   PROP_COLUMN_MENU,
-                                   column_menu_spec);
-
-
-  row_menu_spec =
-    g_param_spec_object ("row-menu",
-                        "Row Menu",
-                        "A menu to be displayed when button 3 is pressed in the row title buttons",
-                        GTK_TYPE_MENU,
-                        G_PARAM_WRITABLE);
-
-  g_object_class_install_property (object_class,
-                                   PROP_ROW_MENU,
-                                   row_menu_spec);
+                                   PROP_DICTIONARY,
+                                   dict_spec);
 
   value_labels_spec =
     g_param_spec_boolean ("value-labels",
@@ -394,45 +293,6 @@ psppire_data_editor_class_init (PsppireDataEditorClass *klass)
                                    value_labels_spec);
 
 
-  current_case_spec =
-    g_param_spec_long ("current-case",
-                      "Current Case",
-                      "Zero based number of the selected case",
-                      0, CASENUMBER_MAX,
-                      0,
-                      G_PARAM_WRITABLE | G_PARAM_READABLE);
-
-  g_object_class_install_property (object_class,
-                                   PROP_CURRENT_CASE,
-                                   current_case_spec);
-
-
-  current_var_spec =
-    g_param_spec_long ("current-variable",
-                      "Current Variable",
-                      "Zero based number of the selected variable",
-                      0, G_MAXINT,
-                      0,
-                      G_PARAM_WRITABLE | G_PARAM_READABLE);
-
-  g_object_class_install_property (object_class,
-                                   PROP_CURRENT_VAR,
-                                   current_var_spec);
-
-
-  data_selected_spec =
-    g_param_spec_boolean ("data-selected",
-                         "Data Selected",
-                         "True iff the data view is active and  one or more cells of data have been selected.",
-                         FALSE,
-                         G_PARAM_READABLE);
-
-  g_object_class_install_property (object_class,
-                                   PROP_DATA_SELECTED,
-                                   data_selected_spec);
-
-
-
   split_window_spec =
     g_param_spec_boolean ("split",
                          "Split Window",
@@ -444,1169 +304,676 @@ psppire_data_editor_class_init (PsppireDataEditorClass *klass)
                                    PROP_SPLIT_WINDOW,
                                    split_window_spec);
 
-  data_editor_signals [DATA_SELECTION_CHANGED] =
-    g_signal_new ("data-selection-changed",
-                 G_TYPE_FROM_CLASS (klass),
-                 G_SIGNAL_RUN_FIRST,
-                 0,
-                 NULL, NULL,
-                 g_cclosure_marshal_VOID__BOOLEAN,
-                 G_TYPE_NONE,
-                 1,
-                 G_TYPE_BOOLEAN);
-
-  data_editor_signals [CASES_SELECTED] =
-    g_signal_new ("cases-selected",
-                 G_TYPE_FROM_CLASS (klass),
-                 G_SIGNAL_RUN_FIRST,
-                 0,
-                 NULL, NULL,
-                 g_cclosure_marshal_VOID__INT,
-                 G_TYPE_NONE,
-                 1,
-                 G_TYPE_INT);
-
-
-  data_editor_signals [VARIABLES_SELECTED] =
-    g_signal_new ("variables-selected",
-                 G_TYPE_FROM_CLASS (klass),
-                 G_SIGNAL_RUN_FIRST,
-                 0,
-                 NULL, NULL,
-                 g_cclosure_marshal_VOID__INT,
-                 G_TYPE_NONE,
-                 1,
-                 G_TYPE_INT);
-
-
-  data_editor_signals [DATA_AVAILABLE_CHANGED] =
-    g_signal_new ("data-available-changed",
-                 G_TYPE_FROM_CLASS (klass),
-                 G_SIGNAL_RUN_FIRST,
-                 0,
-                 NULL, NULL,
-                 g_cclosure_marshal_VOID__BOOLEAN,
-                 G_TYPE_NONE,
-                 1,
-                 G_TYPE_BOOLEAN);
+  ui_manager_spec =
+    g_param_spec_object ("ui-manager",
+                         "UI Manager",
+                         "UI manager for the active notebook tab.  The client should merge this UI manager with the active UI manager to obtain menu items and tool bar items specific to the active notebook tab.",
+                         GTK_TYPE_UI_MANAGER,
+                         G_PARAM_READABLE);
+  g_object_class_install_property (object_class,
+                                   PROP_UI_MANAGER,
+                                   ui_manager_spec);
 }
 
-/* Update the data_ref_entry with the reference of the active cell */
-static gint
-update_data_ref_entry (const GtkSheet *sheet, gint row, gint col, gpointer data)
+static gboolean
+on_data_sheet_var_double_clicked (PsppireDataSheet *data_sheet,
+                                  gint dict_index,
+                                  PsppireDataEditor *de)
 {
-  PsppireDataEditor *de = data;
-
-  PsppireDataStore *data_store =
-    PSPPIRE_DATA_STORE (gtk_sheet_get_model (sheet));
-
-  if (data_store)
-    {
-      const struct variable *var =
-       psppire_dict_get_variable (data_store->dict, col);
-
-      if ( var )
-       {
-         gchar *text = g_strdup_printf ("%d: %s", row + FIRST_CASE_NUMBER,
-                                        var_get_name (var));
-
-         gchar *s = pspp_locale_to_utf8 (text, -1, 0);
-
-         g_free (text);
-
-         gtk_entry_set_text (GTK_ENTRY (de->cell_ref_entry), s);
-
-         g_free (s);
-       }
-      else
-       goto blank_entry;
-
-      if ( var )
-       {
-         gchar *text =
-           psppire_data_store_get_string (data_store, row,
-                                          var_get_dict_index(var));
-
-         if ( ! text )
-           goto blank_entry;
+  gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
+                                 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
 
-         g_strchug (text);
+  psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
+                                   dict_index);
 
-         gtk_entry_set_text (GTK_ENTRY (de->datum_entry), text);
-
-         g_free (text);
-       }
-      else
-       goto blank_entry;
+  return TRUE;
+}
 
-    }
+static gboolean
+on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
+                                 PsppireDataEditor *de)
+{
+  PsppireDataSheet *data_sheet;
 
-  return FALSE;
+  gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
+                                 PSPPIRE_DATA_EDITOR_DATA_VIEW);
 
- blank_entry:
-  gtk_entry_set_text (GTK_ENTRY (de->datum_entry), "");
+  data_sheet = psppire_data_editor_get_active_data_sheet (de);
+  psppire_data_sheet_goto_variable (data_sheet, dict_index);
 
-  return FALSE;
+  return TRUE;
 }
 
-
+/* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
+   active cell or cells. */
 static void
-datum_entry_activate (GtkEntry *entry, gpointer data)
-{
-  gint row, column;
-  PsppireDataEditor *de = data;
+refresh_entry (PsppireDataEditor *de)
+{
+  PsppireDataSheet *data_sheet = psppire_data_editor_get_active_data_sheet (de);
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
+
+  gchar *ref_cell_text;
+  GList *selected_columns, *iter;
+  struct variable *var;
+  gint n_cases;
+  gint n_vars;
+
+  selected_columns = pspp_sheet_selection_get_selected_columns (selection);
+  n_vars = 0;
+  var = NULL;
+  for (iter = selected_columns; iter != NULL; iter = iter->next)
+    {
+      PsppSheetViewColumn *column = iter->data;
+      struct variable *v = g_object_get_data (G_OBJECT (column), "variable");
+      if (v != NULL)
+        {
+          var = v;
+          n_vars++;
+        }
+    }
+  g_list_free (selected_columns);
 
-  const gchar *text = gtk_entry_get_text (entry);
+  n_cases = pspp_sheet_selection_count_selected_rows (selection);
+  if (n_cases > 0)
+    {
+      /* The final row is selectable but it isn't a case (it's just used to add
+         more cases), so don't count it. */
+      GtkTreePath *path;
+      gint case_count;
+
+      case_count = psppire_data_store_get_case_count (de->data_store);
+      path = gtk_tree_path_new_from_indices (case_count, -1);
+      if (pspp_sheet_selection_path_is_selected (selection, path))
+        n_cases--;
+      gtk_tree_path_free (path);
+    }
 
-  gtk_sheet_get_active_cell (GTK_SHEET (de->data_sheet[0]), &row, &column);
+  ref_cell_text = NULL;
+  if (n_cases == 1 && n_vars == 1)
+    {
+      PsppireValueEntry *value_entry = PSPPIRE_VALUE_ENTRY (de->datum_entry);
+      struct range_set *selected_rows;
+      gboolean show_value_labels;
+      union value value;
+      int width;
+      gint row;
 
-  if ( row == -1 || column == -1)
-    return;
+      selected_rows = pspp_sheet_selection_get_range_set (selection);
+      row = range_set_scan (selected_rows, 0);
+      range_set_destroy (selected_rows);
 
-  psppire_data_store_set_string (de->data_store, text, row, column);
-}
+      ref_cell_text = g_strdup_printf ("%d : %s", row + 1, var_get_name (var));
 
-static void on_activate (PsppireDataEditor *de);
-static void on_deactivate (PsppireDataEditor *de);
-static gboolean on_switch_page (PsppireDataEditor *de, GtkNotebookPage *p, gint pagenum, gpointer data);
-static void on_select_range (PsppireDataEditor *de);
+      show_value_labels = psppire_data_sheet_get_value_labels (data_sheet);
 
-static void on_select_row (GtkSheet *, gint, PsppireDataEditor *);
-static void on_select_variable (GtkSheet *, gint, PsppireDataEditor *);
+      psppire_value_entry_set_variable (value_entry, var);
+      psppire_value_entry_set_show_value_label (value_entry,
+                                                show_value_labels);
 
+      width = var_get_width (var);
+      value_init (&value, width);
+      datasheet_get_value (de->data_store->datasheet,
+                           row, var_get_case_index (var), &value);
+      psppire_value_entry_set_value (value_entry, &value, width);
+      value_destroy (&value, width);
 
-static void on_owner_change (GtkClipboard *,
-                            GdkEventOwnerChange *, gpointer);
+      gtk_widget_set_sensitive (de->datum_entry, TRUE);
+    }
+  else
+    {
+      if (n_cases == 0 || n_vars == 0)
+        {
+          ref_cell_text = NULL;
+        }
+      else
+        {
+          struct string s;
+
+          /* The glib string library does not understand the ' printf modifier
+             on all platforms, but the "struct string" library does (because
+             Gnulib fixes that problem), so use the latter.  */
+          ds_init_empty (&s);
+          ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
+                         n_cases);
+          ds_put_byte (&s, ' ');
+          ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
+          ds_put_byte (&s, ' ');
+          ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
+                                       n_vars),
+                         n_vars);
+          ref_cell_text = ds_steal_cstr (&s);
+        }
 
-static void
-on_map (GtkWidget *w)
-{
-  GtkClipboard *clip = gtk_widget_get_clipboard (w, GDK_SELECTION_CLIPBOARD);
+      psppire_value_entry_set_variable (PSPPIRE_VALUE_ENTRY (de->datum_entry),
+                                        NULL);
+      gtk_entry_set_text (
+        GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))), "");
+      gtk_widget_set_sensitive (de->datum_entry, FALSE);
+    }
 
-  g_signal_connect (clip, "owner-change", G_CALLBACK (on_owner_change), w);
+  gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
+                       ref_cell_text ? ref_cell_text : "");
+  g_free (ref_cell_text);
 }
 
-
 static void
-init_sheet (PsppireDataEditor *de, int i,
-           GtkAdjustment *hadj, GtkAdjustment *vadj)
+on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
 {
-  de->sheet_bin[i] = gtk_scrolled_window_new (hadj, vadj);
-
-  de->data_sheet[i] = gtk_sheet_new (NULL, NULL, NULL);
-
-  g_object_set (de->sheet_bin[i],
-               "border-width", 3,
-               "shadow-type",  GTK_SHADOW_ETCHED_IN,
-               NULL);
-
-  gtk_container_add (GTK_CONTAINER (de->sheet_bin[i]), de->data_sheet[i]);
-
-  gtk_widget_show (de->sheet_bin[i]);
-}
+  PsppireDataSheet *data_sheet = psppire_data_editor_get_active_data_sheet (de);
+  struct variable *var;
+  union value value;
+  int width;
+  gint row;
 
+  row = psppire_data_sheet_get_current_case (data_sheet);
+  var = psppire_data_sheet_get_current_variable (data_sheet);
+  if (row < 0 || !var)
+    return;
 
-static void
-init_data_sheet (PsppireDataEditor *de)
-{
-  GtkAdjustment *va0, *ha0;
-  GtkAdjustment *va1, *ha1;
-  GtkWidget *sheet ;
-
-  de->split = TRUE;
-  de->paned = gtk_xpaned_new ();
-
-  init_sheet (de, 0, NULL, NULL);
-  gtk_widget_show (de->sheet_bin[0]);
-  va0 = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (de->sheet_bin[0]));
-  ha0 = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (de->sheet_bin[0]));
-
-  g_object_set (de->sheet_bin[0], "vscrollbar-policy", GTK_POLICY_NEVER, NULL);
-  g_object_set (de->sheet_bin[0], "hscrollbar-policy", GTK_POLICY_NEVER, NULL);
-
-  init_sheet (de, 1, NULL, va0);
-  gtk_widget_show (de->sheet_bin[1]);
-  sheet = gtk_bin_get_child (GTK_BIN (de->sheet_bin[1]));
-  gtk_sheet_hide_row_titles (GTK_SHEET (sheet));
-  ha1 = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (de->sheet_bin[1]));
-  g_object_set (de->sheet_bin[1], "vscrollbar-policy", GTK_POLICY_ALWAYS, NULL);
-  g_object_set (de->sheet_bin[1], "hscrollbar-policy", GTK_POLICY_NEVER, NULL);
-
-  init_sheet (de, 2, ha0, NULL);
-  gtk_widget_show (de->sheet_bin[2]);
-  sheet = gtk_bin_get_child (GTK_BIN (de->sheet_bin[2]));
-  gtk_sheet_hide_column_titles (GTK_SHEET (sheet));
-  g_object_set (de->sheet_bin[2], "vscrollbar-policy", GTK_POLICY_NEVER, NULL);
-  g_object_set (de->sheet_bin[2], "hscrollbar-policy", GTK_POLICY_ALWAYS, NULL);
-  va1 = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (de->sheet_bin[2]));
-
-  init_sheet (de, 3, ha1, va1);
-  gtk_widget_show (de->sheet_bin[3]);
-  sheet = gtk_bin_get_child (GTK_BIN (de->sheet_bin[3]));
-  gtk_sheet_hide_column_titles (GTK_SHEET (sheet));
-  gtk_sheet_hide_row_titles (GTK_SHEET (sheet));
-  g_object_set (de->sheet_bin[3], "vscrollbar-policy", GTK_POLICY_ALWAYS, NULL);
-  g_object_set (de->sheet_bin[3], "hscrollbar-policy", GTK_POLICY_ALWAYS, NULL);
-
-  gtk_xpaned_pack_top_left (GTK_XPANED (de->paned), de->sheet_bin[0], TRUE, TRUE);
-  gtk_xpaned_pack_top_right (GTK_XPANED (de->paned), de->sheet_bin[1], TRUE, TRUE);
-  gtk_xpaned_pack_bottom_left (GTK_XPANED (de->paned), de->sheet_bin[2], TRUE, TRUE);
-  gtk_xpaned_pack_bottom_right (GTK_XPANED (de->paned), de->sheet_bin[3], TRUE, TRUE);
-
-  gtk_xpaned_set_position_y (GTK_XPANED (de->paned), 150);
-  gtk_xpaned_set_position_x (GTK_XPANED (de->paned), 350);
+  width = var_get_width (var);
+  value_init (&value, width);
+  if (psppire_value_entry_get_value (PSPPIRE_VALUE_ENTRY (de->datum_entry),
+                                     &value, width))
+    psppire_data_store_set_value (de->data_store, row, var, &value);
+  value_destroy (&value, width);
 }
 
-
 static void
-psppire_data_editor_init (PsppireDataEditor *de)
-{
-  GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
-  GtkWidget *sw_vs = gtk_scrolled_window_new (NULL, NULL);
-
-  init_data_sheet (de);
-
-  de->data_vbox = gtk_vbox_new (FALSE, 0);
-  de->var_sheet = psppire_var_sheet_new ();
-
-  g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
-
-  de->datum_entry = gtk_entry_new ();
-  de->cell_ref_entry = gtk_entry_new ();
-
-  g_object_set (de->cell_ref_entry,
-               "sensitive", FALSE,
-               "editable",  FALSE,
-               "width_chars", 25,
-               NULL);
-
-  gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_entry, FALSE, FALSE, 0);
-  gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
-
-
-  gtk_container_add (GTK_CONTAINER (sw_vs), de->var_sheet);
-  gtk_widget_show_all (sw_vs);
-
-
-  gtk_box_pack_start (GTK_BOX (de->data_vbox), hbox, FALSE, FALSE, 0);
-  gtk_box_pack_start (GTK_BOX (de->data_vbox), de->paned, TRUE, TRUE, 0);
-
-
-  psppire_data_editor_remove_split (de);
-
-  gtk_widget_show_all (de->data_vbox);
-
-  gtk_notebook_append_page (GTK_NOTEBOOK (de), de->data_vbox,
-                           gtk_label_new_with_mnemonic (_("Data View")));
-
-  gtk_notebook_append_page (GTK_NOTEBOOK (de), sw_vs,
-                           gtk_label_new_with_mnemonic (_("Variable View")));
-
-  g_signal_connect (de->data_sheet[0], "activate",
-                   G_CALLBACK (update_data_ref_entry),
-                   de);
-
-  g_signal_connect (de->datum_entry, "activate",
-                   G_CALLBACK (datum_entry_activate),
-                   de);
-
-
-  g_signal_connect_swapped (de->data_sheet[0],
-                   "double-click-column",
-                   G_CALLBACK (on_data_column_clicked),
-                   de);
-
-  g_signal_connect_swapped (de->var_sheet,
-                   "double-click-row",
-                   G_CALLBACK (on_var_row_clicked),
-                   de);
-
-  g_signal_connect_swapped (de->data_sheet[0], "activate",
-                           G_CALLBACK (on_activate),
-                           de);
-
-  g_signal_connect_swapped (de->data_sheet[0], "deactivate",
-                           G_CALLBACK (on_deactivate),
-                           de);
-
-  g_signal_connect_swapped (de->data_sheet[0], "select-range",
-                           G_CALLBACK (on_select_range),
-                           de);
-
-  g_signal_connect (de->data_sheet[0], "select-row",
-                   G_CALLBACK (on_select_row), de);
-
-  g_signal_connect (de->data_sheet[0], "select-column",
-                   G_CALLBACK (on_select_variable), de);
-
-
-  g_signal_connect (de->var_sheet, "select-row",
-                   G_CALLBACK (on_select_variable), de);
-
-
-  g_signal_connect_after (de, "switch-page",
-                   G_CALLBACK (on_switch_page),
-                   NULL);
-
-
-  g_signal_connect (de, "map", G_CALLBACK (on_map), NULL);
-
-
+on_data_sheet_selection_changed (PsppSheetSelection *selection,
+                                 PsppireDataEditor *de)
+{
+  /* In a split view, ensure that only a single data sheet has a nonempty
+     selection.  */
+  if (de->split
+      && pspp_sheet_selection_count_selected_rows (selection)
+      && pspp_sheet_selection_count_selected_columns (selection))
+    {
+      PsppireDataSheet *ds;
+      int i;
 
-  //     gtk_sheet_hide_column_titles (de->var_sheet);
-  //  gtk_sheet_hide_row_titles (de->data_sheet);
+      FOR_EACH_DATA_SHEET (ds, i, de)
+        {
+          PsppSheetSelection *s;
 
+          s = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
+          if (s != selection)
+            pspp_sheet_selection_unselect_all (s);
+        }
+    }
 
-  de->dispose_has_run = FALSE;
+  refresh_entry (de);
 }
 
-
-GtkWidget*
-psppire_data_editor_new (PsppireVarStore *var_store,
-                        PsppireDataStore *data_store)
+/* Ensures that rows in the right-hand panes in the split view have the same
+   row height as the left-hand panes.  Otherwise, the rows in the right-hand
+   pane tend to be smaller, because the right-hand pane doesn't have buttons
+   for case numbers. */
+static void
+on_data_sheet_fixed_height_notify (PsppireDataSheet *ds,
+                                   GParamSpec *pspec,
+                                   PsppireDataEditor *de)
 {
-  GtkWidget *widget;
-
-  widget =  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
-                         "var-store",  var_store,
-                         "data-store",  data_store,
-                         NULL);
-
+  enum
+    {
+      TL = GTK_XPANED_TOP_LEFT,
+      TR = GTK_XPANED_TOP_RIGHT,
+      BL = GTK_XPANED_BOTTOM_LEFT,
+      BR = GTK_XPANED_BOTTOM_RIGHT
+    };
 
+  int fixed_height = pspp_sheet_view_get_fixed_height (PSPP_SHEET_VIEW (ds));
 
-  return widget;
+  pspp_sheet_view_set_fixed_height (PSPP_SHEET_VIEW (de->data_sheets[TR]),
+                                    fixed_height);
+  pspp_sheet_view_set_fixed_height (PSPP_SHEET_VIEW (de->data_sheets[BR]),
+                                    fixed_height);
 }
 
-
 static void
-psppire_data_editor_remove_split (PsppireDataEditor *de)
+disconnect_data_sheets (PsppireDataEditor *de)
 {
-  if ( !de->split ) return;
-  de->split = FALSE;
+  PsppireDataSheet *ds;
+  int i;
 
-  g_object_ref (de->sheet_bin[0]);
-  gtk_container_remove (GTK_CONTAINER (de->paned), de->sheet_bin[0]);
+  FOR_EACH_DATA_SHEET (ds, i, de)
+    {
+      PsppSheetSelection *selection;
 
-  g_object_ref (de->paned);
-  gtk_container_remove (GTK_CONTAINER (de->data_vbox), de->paned);
+      if (ds == NULL)
+        {
+          /* This can only happen if 'dispose' runs more than once. */
+          continue;
+        }
 
-  gtk_box_pack_start (GTK_BOX (de->data_vbox), de->sheet_bin[0],
-                     TRUE, TRUE, 0);
+      if (i == GTK_XPANED_TOP_LEFT)
+        g_signal_handlers_disconnect_by_func (
+          ds, G_CALLBACK (on_data_sheet_fixed_height_notify), de);
 
-  g_object_unref (de->sheet_bin[0]);
+      g_signal_handlers_disconnect_by_func (
+        ds, G_CALLBACK (refresh_entry), de);
+      g_signal_handlers_disconnect_by_func (
+        ds, G_CALLBACK (on_data_sheet_var_double_clicked), de);
 
-  g_object_set (de->sheet_bin[0], "vscrollbar-policy",
-               GTK_POLICY_ALWAYS, NULL);
+      selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
+      g_signal_handlers_disconnect_by_func (
+        selection, G_CALLBACK (on_data_sheet_selection_changed), de);
 
-  g_object_set (de->sheet_bin[0], "hscrollbar-policy",
-               GTK_POLICY_ALWAYS, NULL);
+      de->data_sheets[i] = NULL;
+    }
 }
 
-
-static void
-psppire_data_editor_set_split (PsppireDataEditor *de)
+static GtkWidget *
+make_data_sheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
+                 gboolean show_value_labels)
 {
-  if ( de->split ) return;
-  de->split = TRUE;
-
-  g_object_ref (de->sheet_bin[0]);
-  gtk_container_remove (GTK_CONTAINER (de->data_vbox), de->sheet_bin[0]);
+  PsppSheetSelection *selection;
+  GtkWidget *ds;
 
-  gtk_xpaned_pack_top_left (GTK_XPANED (de->paned), de->sheet_bin [0],
-                           TRUE, TRUE);
+  ds = psppire_data_sheet_new ();
+  pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (ds), grid_lines);
+  psppire_data_sheet_set_value_labels (PSPPIRE_DATA_SHEET (ds),
+                                       show_value_labels);
 
-  gtk_box_pack_start (GTK_BOX (de->data_vbox), de->paned,
-                     TRUE, TRUE, 0);
+  g_signal_connect_swapped (ds, "notify::value-labels",
+                            G_CALLBACK (refresh_entry), de);
+  g_signal_connect (ds, "var-double-clicked",
+                    G_CALLBACK (on_data_sheet_var_double_clicked), de);
 
-  g_object_unref (de->paned);
+  selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
+  g_signal_connect (selection, "changed",
+                    G_CALLBACK (on_data_sheet_selection_changed), de);
 
-  g_object_set (de->sheet_bin[0], "vscrollbar-policy",
-               GTK_POLICY_NEVER, NULL);
-
-  g_object_set (de->sheet_bin[0], "hscrollbar-policy",
-               GTK_POLICY_NEVER, NULL);
+  return ds;
 }
 
-void
-psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
+static GtkWidget *
+make_single_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
+                       gboolean show_value_labels)
 {
-  if (split )
-    psppire_data_editor_set_split (de);
-  else
-    psppire_data_editor_remove_split (de);
+  GtkWidget *data_sheet_scroller;
 
-  gtk_widget_show_all (de->data_vbox);
-}
-
-static void data_sheet_set_clip (GtkSheet *sheet);
-static void data_sheet_contents_received_callback (GtkClipboard *clipboard,
-                                                  GtkSelectionData *sd,
-                                                  gpointer data);
+  de->data_sheets[0] = make_data_sheet (de, grid_lines, show_value_labels);
+  de->data_sheets[1] = de->data_sheets[2] = de->data_sheets[3] = NULL;
 
+  /* Put data sheet in scroller. */
+  data_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (data_sheet_scroller),
+                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+  gtk_container_add (GTK_CONTAINER (data_sheet_scroller), de->data_sheets[0]);
 
-void
-psppire_data_editor_clip_copy (PsppireDataEditor *de)
-{
-  data_sheet_set_clip (GTK_SHEET (de->data_sheet[0]));
+  return data_sheet_scroller;
 }
 
-void
-psppire_data_editor_clip_paste (PsppireDataEditor *de)
-{
-  GdkDisplay *display = gtk_widget_get_display ( GTK_WIDGET (de));
-  GtkClipboard *clipboard =
-    gtk_clipboard_get_for_display (display, GDK_SELECTION_CLIPBOARD);
-
-  gtk_clipboard_request_contents (clipboard,
-                                 gdk_atom_intern ("UTF8_STRING", TRUE),
-                                 data_sheet_contents_received_callback,
-                                 de);
-}
-
-
-
-void
-psppire_data_editor_clip_cut (PsppireDataEditor *de)
+static GtkWidget *
+make_split_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
+                      gboolean show_value_labels)
 {
-  gint max_rows, max_columns;
-  gint r;
-  GtkSheetRange range;
-  PsppireDataStore *ds = de->data_store;
-
-  data_sheet_set_clip (GTK_SHEET (de->data_sheet[0]));
-
-  /* Now blank all the cells */
-  gtk_sheet_get_selected_range (GTK_SHEET (de->data_sheet[0]), &range);
-
-   /* If nothing selected, then use active cell */
-  if ( range.row0 < 0 || range.col0 < 0 )
+  /* Panes, in the order in which we want to create them. */
+  enum
     {
-      gint row, col;
-      gtk_sheet_get_active_cell (GTK_SHEET (de->data_sheet[0]), &row, &col);
+      TL,                       /* top left */
+      TR,                       /* top right */
+      BL,                       /* bottom left */
+      BR                        /* bottom right */
+    };
 
-      range.row0 = range.rowi = row;
-      range.col0 = range.coli = col;
-    }
+  PsppSheetView *ds[4];
+  GtkXPaned *xpaned;
+  int i;
 
-  /* The sheet range can include cells that do not include data.
-     Exclude them from the range. */
-  max_rows = psppire_data_store_get_case_count (ds);
-  if (range.rowi >= max_rows)
-    {
-      if (max_rows == 0)
-        return;
-      range.rowi = max_rows - 1;
-    }
+  xpaned = GTK_XPANED (gtk_xpaned_new ());
 
-  max_columns = dict_get_var_cnt (ds->dict->dict);
-  if (range.coli >= max_columns)
+  for (i = 0; i < 4; i++)
     {
-      if (max_columns == 0)
-        return;
-      range.coli = max_columns - 1;
-    }
-
-  g_return_if_fail (range.rowi >= range.row0);
-  g_return_if_fail (range.row0 >= 0);
-  g_return_if_fail (range.coli >= range.col0);
-  g_return_if_fail (range.col0 >= 0);
+      GtkAdjustment *hadjust, *vadjust;
+      GtkPolicyType hpolicy, vpolicy;
+      GtkWidget *scroller;
 
+      de->data_sheets[i] = make_data_sheet (de, grid_lines, show_value_labels);
+      ds[i] = PSPP_SHEET_VIEW (de->data_sheets[i]);
 
-  for (r = range.row0; r <= range.rowi ; ++r )
-    {
-      gint c;
-
-      for (c = range.col0 ; c <= range.coli; ++c)
-       {
-         psppire_data_store_set_string (ds, "", r, c);
-       }
-    }
+      if (i == BL)
+        hadjust = pspp_sheet_view_get_hadjustment (ds[TL]);
+      else if (i == BR)
+        hadjust = pspp_sheet_view_get_hadjustment (ds[TR]);
+      else
+        hadjust = NULL;
 
-  /* and remove the selection */
-  gtk_sheet_unselect_range (GTK_SHEET (de->data_sheet[0]));
-}
+      if (i == TR)
+        vadjust = pspp_sheet_view_get_vadjustment (ds[TL]);
+      else if (i == BR)
+        vadjust = pspp_sheet_view_get_vadjustment (ds[BL]);
+      else
+        vadjust = NULL;
+
+      scroller = gtk_scrolled_window_new (hadjust, vadjust);
+      gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroller),
+                                           GTK_SHADOW_ETCHED_IN);
+      hpolicy = i == TL || i == TR ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS;
+      vpolicy = i == TL || i == BL ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS;
+      gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroller),
+                                      hpolicy, vpolicy);
+      gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ds[i]));
+
+      switch (i)
+        {
+        case TL:
+          gtk_xpaned_pack_top_left (xpaned, scroller, TRUE, TRUE);
+          break;
 
+        case TR:
+          gtk_xpaned_pack_top_right (xpaned, scroller, TRUE, TRUE);
+          break;
 
-\f
+        case BL:
+          gtk_xpaned_pack_bottom_left (xpaned, scroller, TRUE, TRUE);
+          break;
 
-/* Popup menu related stuff */
+        case BR:
+          gtk_xpaned_pack_bottom_right (xpaned, scroller, TRUE, TRUE);
+          break;
 
-static void
-popup_variable_menu (GtkSheet *sheet, gint column,
-                    GdkEventButton *event, gpointer data)
-{
-  GtkMenu *menu = GTK_MENU (data);
+        default:
+          g_warn_if_reached ();
+        }
+    }
 
-  PsppireDataStore *data_store =
-    PSPPIRE_DATA_STORE (gtk_sheet_get_model (sheet));
+  /* Bottom sheets don't display variable names. */
+  pspp_sheet_view_set_headers_visible (ds[BL], FALSE);
+  pspp_sheet_view_set_headers_visible (ds[BR], FALSE);
 
-  const struct variable *v =
-    psppire_dict_get_variable (data_store->dict, column);
+  /* Right sheets don't display case numbers. */
+  psppire_data_sheet_set_case_numbers (PSPPIRE_DATA_SHEET (ds[TR]), FALSE);
+  psppire_data_sheet_set_case_numbers (PSPPIRE_DATA_SHEET (ds[BR]), FALSE);
 
-  if ( v && event->button == 3)
-    {
-      gtk_sheet_select_column (sheet, column);
+  g_signal_connect (ds[TL], "notify::fixed-height",
+                    G_CALLBACK (on_data_sheet_fixed_height_notify), de);
 
-      gtk_menu_popup (menu,
-                     NULL, NULL, NULL, NULL,
-                     event->button, event->time);
-    }
+  return GTK_WIDGET (xpaned);
 }
 
+static void set_font_recursively (GtkWidget *w, gpointer data);
 
 static void
-popup_cases_menu (GtkSheet *sheet, gint row,
-                 GdkEventButton *event, gpointer data)
+psppire_data_editor_init (PsppireDataEditor *de)
 {
-  GtkMenu *menu = GTK_MENU (data);
-
-  PsppireDataStore *data_store =
-    PSPPIRE_DATA_STORE (gtk_sheet_get_model (sheet));
+  GtkWidget *var_sheet_scroller;
+  GtkWidget *hbox;
+  gchar *fontname = NULL;
 
-  if ( row <= psppire_data_store_get_case_count (data_store) &&
-       event->button == 3)
-    {
-      gtk_sheet_select_row (sheet, row);
+  de->font = NULL;
+  de->ui_manager = NULL;
+  de->old_vbox_widget = NULL;
 
-      gtk_menu_popup (menu,
-                     NULL, NULL, NULL, NULL,
-                     event->button, event->time);
-    }
-}
+  g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
 
-\f
+  de->cell_ref_label = gtk_label_new ("");
+  gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
+  gtk_widget_set_valign (de->cell_ref_label, GTK_ALIGN_CENTER);
 
-/* Sorting */
+  de->datum_entry = psppire_value_entry_new ();
+  g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
+                    "activate", G_CALLBACK (on_datum_entry_activate), de);
 
-static void
-do_sort (PsppireDataStore *ds, int var, gboolean descend)
-{
-  GString *string = g_string_new ("SORT CASES BY ");
-
-  const struct variable *v =
-    psppire_dict_get_variable (ds->dict, var);
+  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
+  gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
 
-  g_string_append_printf (string, "%s", var_get_name (v));
+  de->split = FALSE;
+  de->datasheet_vbox_widget
+    = make_single_datasheet (de, GTK_TREE_VIEW_GRID_LINES_BOTH, FALSE);
 
-  if ( descend )
-    g_string_append (string, " (D)");
+  de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+  gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
+  gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
+                      TRUE, TRUE, 0);
 
-  g_string_append (string, ".");
+  gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
+                           gtk_label_new_with_mnemonic (_("Data View")));
 
-  execute_syntax (create_syntax_string_source (string->str));
+  gtk_widget_show_all (de->vbox);
+
+  de->var_sheet = GTK_WIDGET (psppire_var_sheet_new ());
+  pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet),
+                                  GTK_TREE_VIEW_GRID_LINES_BOTH);
+  var_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (var_sheet_scroller),
+                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+  gtk_container_add (GTK_CONTAINER (var_sheet_scroller), de->var_sheet);
+  gtk_widget_show_all (var_sheet_scroller);
+  gtk_notebook_append_page (GTK_NOTEBOOK (de), var_sheet_scroller,
+                           gtk_label_new_with_mnemonic (_("Variable View")));
 
-  g_string_free (string, TRUE);
-}
+  g_signal_connect (de->var_sheet, "var-double-clicked",
+                    G_CALLBACK (on_var_sheet_var_double_clicked), de);
 
+  g_object_set (de, "can-focus", FALSE, NULL);
 
-/* Sort the data by the the variable which the editor has currently
-   selected */
-void
-psppire_data_editor_sort_ascending  (PsppireDataEditor *de)
-{
-  GtkSheetRange range;
-  gtk_sheet_get_selected_range (GTK_SHEET(de->data_sheet[0]), &range);
+  if (psppire_conf_get_string (psppire_conf_new (),
+                          "Data Editor", "font",
+                               &fontname) )
+    {
+      de->font = pango_font_description_from_string (fontname);
+      g_free (fontname);
+      set_font_recursively (GTK_WIDGET (de), de->font);
+    }
 
-  do_sort (de->data_store,  range.col0, FALSE);
+  psppire_data_editor_update_ui_manager (de);
 }
 
-
-/* Sort the data by the the variable which the editor has currently
-   selected */
-void
-psppire_data_editor_sort_descending (PsppireDataEditor *de)
+GtkWidget*
+psppire_data_editor_new (PsppireDict *dict,
+                        PsppireDataStore *data_store)
 {
-  GtkSheetRange range;
-  gtk_sheet_get_selected_range (GTK_SHEET(de->data_sheet[0]), &range);
-
-  do_sort (de->data_store,  range.col0, TRUE);
+  return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
+                        "dictionary",  dict,
+                        "data-store",  data_store,
+                        NULL);
 }
-
-
 \f
-
-
-/* Insert a new variable  before the currently selected position */
+/* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
+   sheet(s) and variable sheet. */
 void
-psppire_data_editor_insert_variable (PsppireDataEditor *de)
+psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
 {
-  glong posn = -1;
-
-  if ( de->data_sheet[0]->state == GTK_SHEET_COLUMN_SELECTED )
-    posn = GTK_SHEET (de->data_sheet[0])->range.col0;
-  else
-    posn = GTK_SHEET (de->data_sheet[0])->active_cell.col;
+  GtkTreeViewGridLines grid;
+  PsppireDataSheet *data_sheet;
+  int i;
 
-  if ( posn == -1 ) posn = 0;
+  grid = (grid_visible
+          ? GTK_TREE_VIEW_GRID_LINES_BOTH
+          : GTK_TREE_VIEW_GRID_LINES_NONE);
 
-  psppire_dict_insert_variable (de->data_store->dict, posn, NULL);
+  FOR_EACH_DATA_SHEET (data_sheet, i, de)
+    pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (data_sheet), grid);
+  pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
 }
 
-/* Insert a new case before the currently selected position */
-void
-psppire_data_editor_insert_case (PsppireDataEditor *de)
-{
-  glong posn = -1;
-
-  if ( de->data_sheet[0]->state == GTK_SHEET_ROW_SELECTED )
-    posn = GTK_SHEET (de->data_sheet[0])->range.row0;
-  else
-    posn = GTK_SHEET (de->data_sheet[0])->active_cell.row;
-
-  if ( posn == -1 ) posn = 0;
-
-  psppire_data_store_insert_new_case (de->data_store, posn);
-}
 
-/* Delete the cases currently selected in the data sheet */
-void
-psppire_data_editor_delete_cases    (PsppireDataEditor *de)
+static void
+set_font_recursively (GtkWidget *w, gpointer data)
 {
-  gint first = GTK_SHEET (de->data_sheet[0])->range.row0;
-  gint n = GTK_SHEET (de->data_sheet[0])->range.rowi - first + 1;
+  PangoFontDescription *font_desc = data;
 
-  psppire_data_store_delete_cases (de->data_store, first, n);
+  gtk_widget_override_font (w, font_desc);
 
-  gtk_sheet_unselect_range (GTK_SHEET (de->data_sheet[0]));
+  if ( GTK_IS_CONTAINER (w))
+    gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
 }
 
-/* Delete the variables currently selected in the
-   datasheet or variable sheet */
+/* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
 void
-psppire_data_editor_delete_variables (PsppireDataEditor *de)
+psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
 {
-  gint first, n;
-
-  switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
-    {
-    case PSPPIRE_DATA_EDITOR_DATA_VIEW:
-      first = GTK_SHEET (de->data_sheet[0])->range.col0;
-      n = GTK_SHEET (de->data_sheet[0])->range.coli - first + 1;
-      break;
-    case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
-      first = GTK_SHEET (de->var_sheet)->range.row0;
-      n = GTK_SHEET (de->var_sheet)->range.rowi - first + 1;
-      break;
-    default:
-      g_assert_not_reached ();
-      break;
-    }
+  gchar *font_name;
+  set_font_recursively (GTK_WIDGET (de), font_desc);
 
-  psppire_dict_delete_variables (de->var_store->dict, first, n);
-
-  gtk_sheet_unselect_range (GTK_SHEET (de->data_sheet[0]));
-  gtk_sheet_unselect_range (GTK_SHEET (de->var_sheet));
-}
+  if (de->font)
+    pango_font_description_free (de->font);
+  de->font = pango_font_description_copy (font_desc);
+  font_name = pango_font_description_to_string (de->font);
 
+  psppire_conf_set_string (psppire_conf_new (),
+                          "Data Editor", "font",
+                          font_name);
 
-void
-psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
-{
-  gtk_sheet_show_grid (GTK_SHEET (de->var_sheet), grid_visible);
-  gtk_sheet_show_grid (GTK_SHEET (de->data_sheet[0]), grid_visible);
 }
 
+/* If SPLIT is TRUE, splits DE's data sheet into four panes.
+   If SPLIT is FALSE, un-splits it into a single pane. */
 void
-psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
+psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
 {
-  psppire_data_store_set_font (de->data_store, font_desc);
-  psppire_var_store_set_font (de->var_store, font_desc);
-}
+  GtkTreeViewGridLines grid_lines;
+  gboolean labels;
 
+  if (split == de->split)
+    return;
 
-\f
-
-
-static void
-emit_selected_signal (PsppireDataEditor *de)
-{
-  gboolean data_selected = data_is_selected (de);
-
-  g_signal_emit (de, data_editor_signals[DATA_SELECTION_CHANGED], 0, data_selected);
-}
 
+  grid_lines = pspp_sheet_view_get_grid_lines (
+    PSPP_SHEET_VIEW (de->data_sheets[0]));
+  labels = psppire_data_sheet_get_value_labels (PSPPIRE_DATA_SHEET (
+                                                  de->data_sheets[0]));
 
-static void
-on_activate (PsppireDataEditor *de)
-{
-  gint row, col;
-  gtk_sheet_get_active_cell (GTK_SHEET (de->data_sheet[0]), &row, &col);
+  disconnect_data_sheets (de);
+  if (de->old_vbox_widget)
+    g_object_unref (de->old_vbox_widget);
+  de->old_vbox_widget = de->datasheet_vbox_widget;
+  g_object_ref (de->old_vbox_widget);
+  /* FIXME:  old_vbox_widget needs to be unreffed in dispose.
+       (currently it seems to provoke an error if I do that.  
+       I don't know why. */
+  gtk_container_remove (GTK_CONTAINER (de->vbox), de->datasheet_vbox_widget);
 
+  if (split)
+    de->datasheet_vbox_widget = make_split_datasheet (de, grid_lines, labels);
+  else
+    de->datasheet_vbox_widget = make_single_datasheet (de, grid_lines, labels);
 
-  if ( row < psppire_data_store_get_case_count (de->data_store)
-       &&
-       col < psppire_var_store_get_var_cnt (de->var_store))
-    {
-      emit_selected_signal (de);
-      return ;
-    }
+  psppire_data_editor_refresh_model (de);
 
-  emit_selected_signal (de);
-}
+  gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
+                      TRUE, TRUE, 0);
+  gtk_widget_show_all (de->vbox);
 
+  if (de->font)
+    set_font_recursively (GTK_WIDGET (de), de->font);
 
-static void
-on_deactivate (PsppireDataEditor *de)
-{
-  emit_selected_signal (de);
+  de->split = split;
+  g_object_notify (G_OBJECT (de), "split");
+  psppire_data_editor_update_ui_manager (de);
 }
 
-static void
-on_select_range (PsppireDataEditor *de)
+/* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
+   visible and selected in the active view in DE. */
+void
+psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
 {
-  GtkSheetRange range;
-
-  gtk_sheet_get_selected_range (GTK_SHEET (de->data_sheet[0]), &range);
+  PsppireDataSheet *data_sheet;
 
-  if ( range.rowi < psppire_data_store_get_case_count (de->data_store)
-       &&
-       range.coli < psppire_var_store_get_var_cnt (de->var_store))
+  switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
     {
-      emit_selected_signal (de);
-      return;
-    }
-
-  emit_selected_signal (de);
-}
-
+    case PSPPIRE_DATA_EDITOR_DATA_VIEW:
+      data_sheet = psppire_data_editor_get_active_data_sheet (de);
+      psppire_data_sheet_goto_variable (data_sheet, dict_index);
+      break;
 
-static gboolean
-on_switch_page (PsppireDataEditor *de, GtkNotebookPage *p,
-               gint pagenum, gpointer data)
-{
-  if ( pagenum != PSPPIRE_DATA_EDITOR_DATA_VIEW )
-    {
-      emit_selected_signal (de);
-      return TRUE;
+    case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
+      psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
+                                       dict_index);
+      break;
     }
-
-  on_select_range (de);
-
-  return TRUE;
 }
 
-
-
-static gboolean
-data_is_selected (PsppireDataEditor *de)
+/* Returns the "active" data sheet in DE.  If DE is in single-paned mode, this
+   is the only data sheet.  If DE is in split mode (showing four data sheets),
+   this is the focused data sheet or, if none is focused, the data sheet with
+   selected cells or, if none has selected cells, the upper-left data sheet. */
+PsppireDataSheet *
+psppire_data_editor_get_active_data_sheet (PsppireDataEditor *de)
 {
-  GtkSheetRange range;
-  gint row, col;
-
-  if ( gtk_notebook_get_current_page (GTK_NOTEBOOK (de)) != PSPPIRE_DATA_EDITOR_DATA_VIEW)
-    return FALSE;
-
-  gtk_sheet_get_active_cell (GTK_SHEET (de->data_sheet[0]), &row, &col);
-
-  if ( row >= psppire_data_store_get_case_count (de->data_store)
-       ||
-       col >= psppire_var_store_get_var_cnt (de->var_store))
+  if (de->split)
     {
-      return FALSE;
-    }
-
-  gtk_sheet_get_selected_range (GTK_SHEET (de->data_sheet[0]), &range);
+      PsppireDataSheet *data_sheet;
+      GtkWidget *scroller;
+      int i;
+
+      /* If one of the datasheet's scrollers is focused, choose that one. */
+      scroller = gtk_container_get_focus_child (
+        GTK_CONTAINER (de->datasheet_vbox_widget));
+      if (scroller != NULL)
+        return PSPPIRE_DATA_SHEET (gtk_bin_get_child (GTK_BIN (scroller)));
+
+      /* Otherwise if there's a nonempty selection in some data sheet, choose
+         that one. */
+      FOR_EACH_DATA_SHEET (data_sheet, i, de)
+        {
+          PsppSheetSelection *selection;
 
-  if ( range.rowi >= psppire_data_store_get_case_count (de->data_store)
-       ||
-       range.coli >= psppire_var_store_get_var_cnt (de->var_store))
-    {
-      return FALSE;
+          selection = pspp_sheet_view_get_selection (
+            PSPP_SHEET_VIEW (data_sheet));
+          if (pspp_sheet_selection_count_selected_rows (selection)
+              && pspp_sheet_selection_count_selected_columns (selection))
+            return data_sheet;
+        }
     }
 
-  return TRUE;
+  return PSPPIRE_DATA_SHEET (de->data_sheets[0]);
 }
 
+/* Returns the UI manager that should be merged into DE's toplevel widget's UI
+   manager to display menu items and toolbar items specific to DE's current
+   page and data sheet.
 
-static void
-on_select_row (GtkSheet *sheet, gint row, PsppireDataEditor *de)
+   DE's toplevel widget can watch for changes by connecting to DE's
+   notify::ui-manager signal. */
+GtkUIManager *
+psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
 {
-  g_signal_emit (de, data_editor_signals[CASES_SELECTED], 0, row);
+  psppire_data_editor_update_ui_manager (de);
+  return de->ui_manager;
 }
 
-
 static void
-on_select_variable (GtkSheet *sheet, gint var, PsppireDataEditor *de)
+psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
 {
-  g_signal_emit (de, data_editor_signals[VARIABLES_SELECTED], 0, var);
-}
-
-
-\f
-
-/* Clipboard stuff */
-
-
-#include <data/casereader.h>
-#include <data/case-map.h>
-#include <data/casewriter.h>
-
-#include <data/data-out.h>
-#include "xalloc.h"
-
-/* A casereader and dictionary holding the data currently in the clip */
-static struct casereader *clip_datasheet = NULL;
-static struct dictionary *clip_dict = NULL;
-
-
-static void data_sheet_update_clipboard (GtkSheet *);
-
-/* Set the clip according to the currently
-   selected range in the data sheet */
-static void
-data_sheet_set_clip (GtkSheet *sheet)
-{
-  int i;
-  struct casewriter *writer ;
-  GtkSheetRange range;
-  PsppireDataStore *ds;
-  struct case_map *map = NULL;
-  casenumber max_rows;
-  size_t max_columns;
-
-  ds = PSPPIRE_DATA_STORE (gtk_sheet_get_model (sheet));
-
-  gtk_sheet_get_selected_range (sheet, &range);
-
-   /* If nothing selected, then use active cell */
-  if ( range.row0 < 0 || range.col0 < 0 )
-    {
-      gint row, col;
-      gtk_sheet_get_active_cell (sheet, &row, &col);
-
-      range.row0 = range.rowi = row;
-      range.col0 = range.coli = col;
-    }
-
-  /* The sheet range can include cells that do not include data.
-     Exclude them from the range. */
-  max_rows = psppire_data_store_get_case_count (ds);
-  if (range.rowi >= max_rows)
-    {
-      if (max_rows == 0)
-        return;
-      range.rowi = max_rows - 1;
-    }
-  max_columns = dict_get_var_cnt (ds->dict->dict);
-  if (range.coli >= max_columns)
-    {
-      if (max_columns == 0)
-        return;
-      range.coli = max_columns - 1;
-    }
-
-  g_return_if_fail (range.rowi >= range.row0);
-  g_return_if_fail (range.row0 >= 0);
-  g_return_if_fail (range.coli >= range.col0);
-  g_return_if_fail (range.col0 >= 0);
-
-  /* Destroy any existing clip */
-  if ( clip_datasheet )
-    {
-      casereader_destroy (clip_datasheet);
-      clip_datasheet = NULL;
-    }
-
-  if ( clip_dict )
-    {
-      dict_destroy (clip_dict);
-      clip_dict = NULL;
-    }
+  PsppireDataSheet *data_sheet;
+  GtkUIManager *ui_manager;
 
-  /* Construct clip dictionary. */
-  clip_dict = dict_create ();
-  for (i = range.col0; i <= range.coli; i++)
-    {
-      const struct variable *old = dict_get_var (ds->dict->dict, i);
-      dict_clone_var_assert (clip_dict, old, var_get_name (old));
-    }
+  ui_manager = NULL;
 
-  /* Construct clip data. */
-  map = case_map_by_name (ds->dict->dict, clip_dict);
-  writer = autopaging_writer_create (dict_get_next_value_idx (clip_dict));
-  for (i = range.row0; i <= range.rowi ; ++i )
+  switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
     {
-      struct ccase old;
-
-      if (psppire_case_file_get_case (ds->case_file, i, &old))
+    case PSPPIRE_DATA_EDITOR_DATA_VIEW:
+      data_sheet = psppire_data_editor_get_active_data_sheet (de);
+      if (data_sheet != NULL)
+        ui_manager = psppire_data_sheet_get_ui_manager (data_sheet);
+      else
         {
-          struct ccase new;
-
-          case_map_execute (map, &old, &new);
-          case_destroy (&old);
-          casewriter_write (writer, &new);
+          /* This happens transiently in psppire_data_editor_split_window(). */
         }
-      else
-        casewriter_force_error (writer);
-    }
-  case_map_destroy (map);
-
-  clip_datasheet = casewriter_make_reader (writer);
-
-  data_sheet_update_clipboard (sheet);
-}
-
-enum {
-  SELECT_FMT_NULL,
-  SELECT_FMT_TEXT,
-  SELECT_FMT_HTML
-};
-
-
-/* Perform data_out for case CC, variable V, appending to STRING */
-static void
-data_out_g_string (GString *string, const struct variable *v,
-                  const struct ccase *cc)
-{
-  char *buf ;
-
-  const struct fmt_spec *fs = var_get_print_format (v);
-  const union value *val = case_data (cc, v);
-  buf = xzalloc (fs->w);
-
-  data_out (val, fs, buf);
-
-  g_string_append_len (string, buf, fs->w);
-
-  g_free (buf);
-}
-
-static GString *
-clip_to_text (void)
-{
-  casenumber r;
-  GString *string;
-
-  const size_t val_cnt = casereader_get_value_cnt (clip_datasheet);
-  const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
-  const size_t var_cnt = dict_get_var_cnt (clip_dict);
-
-  string = g_string_sized_new (10 * val_cnt * case_cnt);
-
-  for (r = 0 ; r < case_cnt ; ++r )
-    {
-      int c;
-      struct ccase cc;
-      if ( !  casereader_peek (clip_datasheet, r, &cc))
-       {
-         g_warning ("Clipboard seems to have inexplicably shrunk");
-         break;
-       }
-
-      for (c = 0 ; c < var_cnt ; ++c)
-       {
-         const struct variable *v = dict_get_var (clip_dict, c);
-         data_out_g_string (string, v, &cc);
-         if ( c < val_cnt - 1 )
-           g_string_append (string, "\t");
-       }
-
-      if ( r < case_cnt)
-       g_string_append (string, "\n");
-
-      case_destroy (&cc);
-    }
-
-  return string;
-}
-
-
-static GString *
-clip_to_html (void)
-{
-  casenumber r;
-  GString *string;
-
-  const size_t val_cnt = casereader_get_value_cnt (clip_datasheet);
-  const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
-  const size_t var_cnt = dict_get_var_cnt (clip_dict);
-
-
-  /* Guestimate the size needed */
-  string = g_string_sized_new (20 * val_cnt * case_cnt);
-
-  g_string_append (string, "<table>\n");
-  for (r = 0 ; r < case_cnt ; ++r )
-    {
-      int c;
-      struct ccase cc;
-      if ( !  casereader_peek (clip_datasheet, r, &cc))
-       {
-         g_warning ("Clipboard seems to have inexplicably shrunk");
-         break;
-       }
-      g_string_append (string, "<tr>\n");
-
-      for (c = 0 ; c < var_cnt ; ++c)
-       {
-         const struct variable *v = dict_get_var (clip_dict, c);
-         g_string_append (string, "<td>");
-         data_out_g_string (string, v, &cc);
-         g_string_append (string, "</td>\n");
-       }
-
-      g_string_append (string, "</tr>\n");
-
-      case_destroy (&cc);
-    }
-  g_string_append (string, "</table>\n");
-
-  return string;
-}
-
-
-
-static void
-clipboard_get_cb (GtkClipboard     *clipboard,
-                 GtkSelectionData *selection_data,
-                 guint             info,
-                 gpointer          data)
-{
-  GString *string = NULL;
-
-  switch (info)
-    {
-    case SELECT_FMT_TEXT:
-      string = clip_to_text ();
       break;
-    case SELECT_FMT_HTML:
-      string = clip_to_html ();
+
+    case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
+      ui_manager = psppire_var_sheet_get_ui_manager (
+        PSPPIRE_VAR_SHEET (de->var_sheet));
       break;
+
     default:
-      g_assert_not_reached ();
+      /* This happens transiently in psppire_data_editor_init(). */
+      break;
     }
 
-  gtk_selection_data_set (selection_data, selection_data->target,
-                         8,
-                         (const guchar *) string->str, string->len);
-
-  g_string_free (string, TRUE);
-}
-
-static void
-clipboard_clear_cb (GtkClipboard *clipboard,
-                   gpointer data)
-{
-  dict_destroy (clip_dict);
-  clip_dict = NULL;
-
-  casereader_destroy (clip_datasheet);
-  clip_datasheet = NULL;
-}
-
-
-static const GtkTargetEntry targets[] = {
-  { "UTF8_STRING",   0, SELECT_FMT_TEXT },
-  { "STRING",        0, SELECT_FMT_TEXT },
-  { "TEXT",          0, SELECT_FMT_TEXT },
-  { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
-  { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
-  { "text/plain",    0, SELECT_FMT_TEXT },
-  { "text/html",     0, SELECT_FMT_HTML }
-};
-
-
-
-static void
-data_sheet_update_clipboard (GtkSheet *sheet)
-{
-  GtkClipboard *clipboard =
-    gtk_widget_get_clipboard (GTK_WIDGET (sheet),
-                             GDK_SELECTION_CLIPBOARD);
-
-  if (!gtk_clipboard_set_with_owner (clipboard, targets,
-                                    G_N_ELEMENTS (targets),
-                                    clipboard_get_cb, clipboard_clear_cb,
-                                    G_OBJECT (sheet)))
-    clipboard_clear_cb (clipboard, sheet);
-}
-
-
-
-/* A callback for when the clipboard contents have been received */
-static void
-data_sheet_contents_received_callback (GtkClipboard *clipboard,
-                                     GtkSelectionData *sd,
-                                     gpointer data)
-{
-  gint count = 0;
-  gint row, column;
-  gint next_row, next_column;
-  gint first_column;
-  char *c;
-  PsppireDataEditor *data_editor = data;
-
-  if ( sd->length < 0 )
-    return;
-
-  if ( sd->type != gdk_atom_intern ("UTF8_STRING", FALSE))
-    return;
-
-  c = (char *) sd->data;
-
-  /* Paste text to selected position */
-  gtk_sheet_get_active_cell (GTK_SHEET (data_editor->data_sheet[0]),
-                            &row, &column);
-
-  g_return_if_fail (row >= 0);
-  g_return_if_fail (column >= 0);
-
-  first_column = column;
-  next_row = row;
-  next_column = column;
-  while (count < sd->length)
+  if (ui_manager != de->ui_manager)
     {
-      char *s = c;
-
-      row = next_row;
-      column = next_column;
-      while (*c != '\t' && *c != '\n' && count < sd->length)
-       {
-         c++;
-         count++;
-       }
-      if ( *c == '\t')
-       {
-         next_row = row ;
-         next_column = column + 1;
-       }
-      else if ( *c == '\n')
-       {
-         next_row = row + 1;
-         next_column = first_column;
-       }
-      *c++ = '\0';
-      count++;
-
-
-      /* Append some new cases if pasting beyond the last row */
-      if ( row >= psppire_data_store_get_case_count (data_editor->data_store))
-       psppire_data_store_insert_new_case (data_editor->data_store, row);
-
-      psppire_data_store_set_string (data_editor->data_store, s, row, column);
-    }
-}
-
+      if (de->ui_manager)
+        g_object_unref (de->ui_manager);
+      if (ui_manager)
+        g_object_ref (ui_manager);
+      de->ui_manager = ui_manager;
 
-static void
-on_owner_change (GtkClipboard *clip, GdkEventOwnerChange *event, gpointer data)
-{
-  gint i;
-  gboolean compatible_target = FALSE;
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-
-  for (i = 0 ; i < sizeof (targets) / sizeof(targets[0]) ; ++i )
-    {
-      GdkAtom atom = gdk_atom_intern (targets[i].target, TRUE);
-      if ( gtk_clipboard_wait_is_target_available (clip, atom))
-       {
-         compatible_target = TRUE;
-         break;
-       }
+      g_object_notify (G_OBJECT (de), "ui-manager");
     }
-
-  g_signal_emit (de, data_editor_signals[DATA_AVAILABLE_CHANGED], 0,
-                compatible_target);
 }
-
-