gui: Redo var sheet, data sheet, text import with PsppSheetView.
[pspp] / src / ui / gui / psppire-data-editor.c
index c1908387f7765a834d48ff0d0af184300b540cdb..575307e8c84a7509eb1b2da728aea8578caa8808 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (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/gtkextra-sheet.h>
-#include "psppire-data-editor.h"
-#include "psppire-var-sheet.h"
 
-#include <language/syntax-string-source.h>
-#include "psppire-data-store.h"
-#include <gtksheet/psppire-axis-hetero.h>
-#include <gtksheet/psppire-axis-uniform.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 "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.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)
 {
@@ -87,240 +83,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;
+  disconnect_data_sheets (de);
 
-  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);
-
-  gtk_sheet_get_active_cell (GTK_SHEET (de->data_sheet[0]),
-                            &current_row, &current_column);
-
-  gtk_sheet_set_active_cell (GTK_SHEET (de->data_sheet[0]), current_row, row);
-
-  gtk_sheet_get_visible_range (GTK_SHEET (de->data_sheet[0]), &visible_range);
-
-  if ( row < visible_range.col0 || row > visible_range.coli)
+  if (de->data_store)
     {
-      gtk_sheet_moveto (GTK_SHEET (de->data_sheet[0]),
-                       -1, row, 0, 0);
+      g_object_unref (de->data_store);
+      de->data_store = NULL;
     }
 
-  return FALSE;
-}
-
+  if (de->var_store)
+    {
+      g_object_unref (de->var_store);
+      de->var_store = NULL;
+    }
 
-/* Moves the focus to a new cell.
-   Returns TRUE iff the move should be disallowed */
-static gboolean
-traverse_cell_callback (GtkSheet *sheet,
-                       GtkSheetCell *existing_cell,
-                       GtkSheetCell *new_cell,
-                       gpointer data)
-{
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-  const PsppireDict *dict = de->data_store->dict;
+  if (de->font != NULL)
+    {
+      pango_font_description_free (de->font);
+      de->font = NULL;
+    }
 
-  if ( new_cell->col >= psppire_dict_get_var_cnt (dict))
-    return TRUE;
+  if (de->ui_manager)
+    {
+      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_VALUE_LABELS,
-    PROP_CURRENT_CASE,
-    PROP_CURRENT_VAR,
-    PROP_DATA_SELECTED,
-    PROP_SPLIT_WINDOW
+    PROP_SPLIT_WINDOW,
+    PROP_UI_MANAGER
   };
 
-
-#define WIDTH_OF_M 10
-#define DEFAULT_ROW_HEIGHT 25
-
 static void
-new_data_callback (PsppireDataStore *ds, gpointer data)
+psppire_data_editor_refresh_model (PsppireDataEditor *de)
 {
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-  gint i;
-  for (i = 0 ; i < 4 ; ++i)
-    {
-      PsppireAxisUniform *vaxis;
-      casenumber n_cases =  psppire_case_file_get_case_count (ds->case_file);
-
-      g_object_get (de->data_sheet[i], "vertical-axis", &vaxis, NULL);
-
-      psppire_axis_uniform_set_count (vaxis, n_cases);
-    }
-}
-
-static void
-new_variables_callback (PsppireDict *dict, gpointer data)
-{
-  gint v, i;
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-
-  PsppireAxisHetero *vaxis;
-  g_object_get (de->var_sheet, "vertical-axis", &vaxis, NULL);
-
-  psppire_axis_hetero_clear (vaxis);
-
-  for (v = 0 ; v < psppire_dict_get_var_cnt (dict); ++v)
-    psppire_axis_hetero_append (vaxis, DEFAULT_ROW_HEIGHT);
-
-  for (i = 0 ; i < 4 ; ++i)
-    {
-      PsppireAxisHetero *haxis;
-      g_object_get (de->data_sheet[i], "horizontal-axis", &haxis, NULL);
-
-      psppire_axis_hetero_clear (haxis);
-
-      for (v = 0 ; v < psppire_dict_get_var_cnt (dict); ++v)
-       {
-         const struct variable *var = psppire_dict_get_variable (dict, v);
-
-         psppire_axis_hetero_append (haxis, 10 * var_get_display_width (var));
-       }
-    }
-}
-
-static void
-insert_variable_callback (PsppireDict *dict, gint x, gpointer data)
-{
-  gint i;
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-
-  PsppireAxisHetero *var_vaxis;
-  g_object_get (de->var_sheet, "vertical-axis", &var_vaxis, NULL);
-
-  psppire_axis_hetero_insert (var_vaxis, DEFAULT_ROW_HEIGHT, x);
-
-  for (i = 0 ; i < 4 ; ++i)
-    {
-      const struct variable *var = psppire_dict_get_variable (dict, x);
-      PsppireAxisHetero *haxis;
-      g_object_get (de->data_sheet[i], "horizontal-axis", &haxis, NULL);
-
-      psppire_axis_hetero_insert (haxis, WIDTH_OF_M * var_get_display_width (var), x);
-    }
-}
-
-
-static void
-delete_variable_callback (PsppireDict *dict, gint posn,
-                         gint x UNUSED, gint y UNUSED, gpointer data)
-{
-  gint i;
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-
-  PsppireAxisHetero *var_vaxis;
-  g_object_get (de->var_sheet, "vertical-axis", &var_vaxis, NULL);
-
-  psppire_axis_hetero_remove (var_vaxis, posn);
-
-  for (i = 0 ; i < 4 ; ++i)
-    {
-      PsppireAxisHetero *haxis;
-      g_object_get (de->data_sheet[i], "horizontal-axis", &haxis, NULL);
-
-      psppire_axis_hetero_remove (haxis, posn);
-    }
-}
-
-
-static void
-rewidth_variable_callback (PsppireDict *dict, gint posn, gpointer data)
-{
-  gint i;
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-
-  for (i = 0 ; i < 4 ; ++i)
-    {
-      const struct variable *var = psppire_dict_get_variable (dict, posn);
-      PsppireAxisHetero *haxis;
-      g_object_get (de->data_sheet[i], "horizontal-axis", &haxis, NULL);
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (de->var_sheet);
+  PsppireDataSheet *data_sheet;
+  int i;
 
-      psppire_axis_hetero_resize_unit (haxis,
-                                      WIDTH_OF_M *
-                                      var_get_display_width (var), posn);
-    }
+  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->var_store->dictionary);
 }
 
-
 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)
     {
@@ -328,84 +151,36 @@ 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],
-                     "model", de->data_store,
-                     NULL);
-
-      g_signal_connect (de->data_store->dict, "backend-changed",   G_CALLBACK (new_variables_callback), de);
-      g_signal_connect (de->data_store->dict, "variable-inserted", G_CALLBACK (insert_variable_callback), de);
-      g_signal_connect (de->data_store->dict, "variable-deleted",  G_CALLBACK (delete_variable_callback), de);
-      g_signal_connect (de->data_store->dict, "variable-display-width-changed",  G_CALLBACK (rewidth_variable_callback), de);
+      g_signal_connect_swapped (de->data_store, "case-changed",
+                                G_CALLBACK (refresh_entry), de);
 
-      g_signal_connect (de->data_store, "backend-changed", G_CALLBACK (new_data_callback), de);
       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,
-                   "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 (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]), -1, 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, -1,  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, -1, 0.5, 0.5);
-      }
+      psppire_var_sheet_set_dictionary (PSPPIRE_VAR_SHEET (de->var_sheet),
+                                        de->var_store->dictionary);
       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;
@@ -431,52 +206,59 @@ psppire_data_editor_get_property (GObject         *object,
     case PROP_VAR_STORE:
       g_value_set_pointer (value, de->var_store);
       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,
+                                 GtkNotebookPage *page,
+                                 guint            page_num)
+{
+  GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, page, 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 *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",
@@ -497,29 +279,6 @@ psppire_data_editor_class_init (PsppireDataEditorClass *klass)
                                    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);
-
   value_labels_spec =
     g_param_spec_boolean ("value-labels",
                         "Value Labels",
@@ -532,45 +291,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",
@@ -582,1168 +302,644 @@ 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,
-                      gint old_row, gint old_col,
-                      gpointer data)
+static gboolean
+on_data_sheet_var_double_clicked (PsppireDataSheet *data_sheet,
+                                  gint dict_index,
+                                  PsppireDataEditor *de)
 {
-  PsppireDataEditor *de = data;
+  gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
+                                 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
 
-  PsppireDataStore *data_store =
-    PSPPIRE_DATA_STORE (gtk_sheet_get_model (sheet));
+  psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
+                                   dict_index);
 
-  if (data_store)
-    {
-      const struct variable *var =
-       psppire_dict_get_variable (data_store->dict, col);
+  return TRUE;
+}
 
-      if ( var )
-       {
-         gchar *text = g_strdup_printf ("%d: %s", row + FIRST_CASE_NUMBER,
-                                        var_get_name (var));
+static gboolean
+on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
+                                 PsppireDataEditor *de)
+{
+  PsppireDataSheet *data_sheet;
 
-         gchar *s = pspp_locale_to_utf8 (text, -1, 0);
+  gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
+                                 PSPPIRE_DATA_EDITOR_DATA_VIEW);
 
-         g_free (text);
+  data_sheet = psppire_data_editor_get_active_data_sheet (de);
+  psppire_data_sheet_show_variable (data_sheet, dict_index);
 
-         gtk_entry_set_text (GTK_ENTRY (de->cell_ref_entry), s);
+  return TRUE;
+}
 
-         g_free (s);
-       }
-      else
-       goto blank_entry;
+/* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
+   active cell or cells. */
+static void
+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);
 
-      if ( var )
-       {
-         gchar *text =
-           psppire_data_store_get_string (data_store, row,
-                                          var_get_dict_index(var));
+  gchar *ref_cell_text;
+  GList *selected_columns, *iter;
+  struct variable *var;
+  gint n_cases;
+  gint n_vars;
 
-         if ( ! text )
-           goto blank_entry;
+  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);
 
-         g_strchug (text);
+  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;
 
-         gtk_entry_set_text (GTK_ENTRY (de->datum_entry), text);
+      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);
+    }
 
-         g_free (text);
-       }
-      else
-       goto blank_entry;
+  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;
 
-    }
+      selected_rows = pspp_sheet_selection_get_range_set (selection);
+      row = range_set_scan (selected_rows, 0);
+      range_set_destroy (selected_rows);
 
-  return FALSE;
+      ref_cell_text = g_strdup_printf ("%d : %s", row + 1, var_get_name (var));
 
- blank_entry:
-  gtk_entry_set_text (GTK_ENTRY (de->datum_entry), "");
+      show_value_labels = psppire_data_sheet_get_value_labels (data_sheet);
 
-  return FALSE;
-}
+      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
-datum_entry_activate (GtkEntry *entry, gpointer data)
-{
-  gint row, column;
-  PsppireDataEditor *de = data;
+      gtk_widget_set_sensitive (de->datum_entry, TRUE);
+    }
+  else
+    {
+      if (n_cases == 0 || n_vars == 0)
+        {
+          ref_cell_text = NULL;
+        }
+      else
+        {
+          GString *string;
+
+          string = g_string_sized_new (25);
+          g_string_append_printf (string,
+                                  ngettext ("%'d case", "%'d cases", n_cases),
+                                  n_cases);
+          g_string_append_c (string, ' ');
+          g_string_append_unichar (string, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
+          g_string_append_c (string, ' ');
+          g_string_append_printf (string,
+                                  ngettext ("%'d variable", "%'d variables",
+                                            n_vars),
+                                  n_vars);
+          ref_cell_text = string->str;
+          g_string_free (string, FALSE);
+        }
+
+      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);
+    }
 
-  const gchar *text = gtk_entry_get_text (entry);
+  gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
+                       ref_cell_text ? ref_cell_text : "");
+  g_free (ref_cell_text);
+}
 
-  gtk_sheet_get_active_cell (GTK_SHEET (de->data_sheet[0]), &row, &column);
+static void
+on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
+{
+  PsppireDataSheet *data_sheet = psppire_data_editor_get_active_data_sheet (de);
+  struct variable *var;
+  union value value;
+  int width;
+  gint row;
 
-  if ( row == -1 || column == -1)
+  row = psppire_data_sheet_get_current_case (data_sheet);
+  var = psppire_data_sheet_get_current_variable (data_sheet);
+  if (row < 0 || !var)
     return;
 
-  psppire_data_store_set_string (de->data_store, text, row, column);
+  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 on_activate (PsppireDataEditor *de);
-static gboolean on_switch_page (PsppireDataEditor *de, GtkNotebookPage *p, gint pagenum, gpointer data);
-static void on_select_range (PsppireDataEditor *de);
+static void
+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;
 
-static void on_select_row (GtkSheet *, gint, PsppireDataEditor *);
-static void on_select_variable (GtkSheet *, gint, PsppireDataEditor *);
+      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);
+        }
+    }
 
-static void on_owner_change (GtkClipboard *,
-                            GdkEventOwnerChange *, gpointer);
+  refresh_entry (de);
+}
 
+/* 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_map (GtkWidget *w)
+on_data_sheet_fixed_height_notify (PsppireDataSheet *ds,
+                                   GParamSpec *pspec,
+                                   PsppireDataEditor *de)
 {
-  GtkClipboard *clip = gtk_widget_get_clipboard (w, GDK_SELECTION_CLIPBOARD);
+  enum
+    {
+      TL = GTK_XPANED_TOP_LEFT,
+      TR = GTK_XPANED_TOP_RIGHT,
+      BL = GTK_XPANED_BOTTOM_LEFT,
+      BR = GTK_XPANED_BOTTOM_RIGHT
+    };
 
-  g_signal_connect (clip, "owner-change", G_CALLBACK (on_owner_change), w);
-}
+  int fixed_height = pspp_sheet_view_get_fixed_height (PSPP_SHEET_VIEW (ds));
 
+  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
-init_sheet (PsppireDataEditor *de, int i,
-           GtkAdjustment *hadj, GtkAdjustment *vadj)
+disconnect_data_sheets (PsppireDataEditor *de)
 {
-  PsppireAxisHetero *haxis = psppire_axis_hetero_new ();
-  PsppireAxisUniform *vaxis = psppire_axis_uniform_new ();
-  de->sheet_bin[i] = gtk_scrolled_window_new (hadj, vadj);
-
-  de->data_sheet[i] = gtk_sheet_new (NULL, NULL, NULL);
+  PsppireDataSheet *ds;
+  int i;
 
-  g_object_set (de->sheet_bin[i],
-               "border-width", 3,
-               "shadow-type",  GTK_SHADOW_ETCHED_IN,
-               NULL);
+  FOR_EACH_DATA_SHEET (ds, i, de)
+    {
+      PsppSheetSelection *selection;
 
-  g_object_set (haxis, "default-size", 75, NULL);
-  g_object_set (vaxis, "default-size", 25, NULL);
+      if (ds == NULL)
+        {
+          /* This can only happen if 'dispose' runs more than once. */
+          continue;
+        }
 
-  g_object_set (de->data_sheet[i],
-               "horizontal-axis", haxis,
-               "vertical-axis", vaxis,
-               NULL);
+      if (i == GTK_XPANED_TOP_LEFT)
+        g_signal_handlers_disconnect_by_func (
+          ds, G_CALLBACK (on_data_sheet_fixed_height_notify), de);
 
-  gtk_container_add (GTK_CONTAINER (de->sheet_bin[i]), de->data_sheet[i]);
+      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_signal_connect (de->data_sheet[i], "traverse",
-                   G_CALLBACK (traverse_cell_callback), de);
+      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);
 
-  gtk_widget_show (de->sheet_bin[i]);
+      de->data_sheets[i] = NULL;
+    }
 }
 
-
-static void
-init_data_sheet (PsppireDataEditor *de)
+static GtkWidget *
+make_data_sheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines)
 {
-  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);
-}
-
+  PsppSheetSelection *selection;
+  GtkWidget *ds;
 
-static void
-psppire_data_editor_init (PsppireDataEditor *de)
-{
-  GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
-  GtkWidget *sw_vs = gtk_scrolled_window_new (NULL, NULL);
+  ds = psppire_data_sheet_new ();
+  pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (ds), grid_lines);
 
-  init_data_sheet (de);
+  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);
 
-  de->data_vbox = gtk_vbox_new (FALSE, 0);
-  de->var_sheet = psppire_var_sheet_new ();
+  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, "tab-pos", GTK_POS_BOTTOM, NULL);
+  return ds;
+}
 
-  de->datum_entry = gtk_entry_new ();
-  de->cell_ref_entry = gtk_entry_new ();
+static GtkWidget *
+make_single_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines)
+{
+  GtkWidget *data_sheet_scroller;
 
-  g_object_set (de->cell_ref_entry,
-               "sensitive", FALSE,
-               "editable",  FALSE,
-               "width_chars", 25,
-               NULL);
+  de->data_sheets[0] = make_data_sheet (de, grid_lines);
+  de->data_sheets[1] = de->data_sheets[2] = de->data_sheets[3] = 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);
+  /* 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]);
 
+  return data_sheet_scroller;
+}
 
-  gtk_container_add (GTK_CONTAINER (sw_vs), de->var_sheet);
-  gtk_widget_show_all (sw_vs);
+static GtkWidget *
+make_split_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines)
+{
+  /* Panes, in the order in which we want to create them. */
+  enum
+    {
+      TL,                       /* top left */
+      TR,                       /* top right */
+      BL,                       /* bottom left */
+      BR                        /* bottom right */
+    };
 
+  PsppSheetView *ds[4];
+  GtkXPaned *xpaned;
+  int i;
 
-  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);
+  xpaned = GTK_XPANED (gtk_xpaned_new ());
 
+  for (i = 0; i < 4; i++)
+    {
+      GtkAdjustment *hadjust, *vadjust;
+      GtkPolicyType hpolicy, vpolicy;
+      GtkWidget *scroller;
 
-  psppire_data_editor_remove_split (de);
+      de->data_sheets[i] = make_data_sheet (de, grid_lines);
+      ds[i] = PSPP_SHEET_VIEW (de->data_sheets[i]);
 
-  gtk_widget_show_all (de->data_vbox);
+      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;
 
-  gtk_notebook_append_page (GTK_NOTEBOOK (de), de->data_vbox,
-                           gtk_label_new_with_mnemonic (_("Data View")));
+      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;
 
-  gtk_notebook_append_page (GTK_NOTEBOOK (de), sw_vs,
-                           gtk_label_new_with_mnemonic (_("Variable View")));
+        case TR:
+          gtk_xpaned_pack_top_right (xpaned, scroller, TRUE, TRUE);
+          break;
 
-  g_signal_connect (de->data_sheet[0], "activate",
-                   G_CALLBACK (update_data_ref_entry),
-                   de);
+        case BL:
+          gtk_xpaned_pack_bottom_left (xpaned, scroller, TRUE, TRUE);
+          break;
 
-  g_signal_connect (de->datum_entry, "activate",
-                   G_CALLBACK (datum_entry_activate),
-                   de);
+        case BR:
+          gtk_xpaned_pack_bottom_right (xpaned, scroller, TRUE, TRUE);
+          break;
 
+        default:
+          g_warn_if_reached ();
+        }
+    }
 
-  g_signal_connect_swapped (de->data_sheet[0],
-                   "double-click-column",
-                   G_CALLBACK (on_data_column_clicked),
-                   de);
+  /* 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);
 
-  g_signal_connect_swapped (de->var_sheet,
-                   "double-click-row",
-                   G_CALLBACK (on_var_row_clicked),
-                   de);
+  /* 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);
 
-  g_signal_connect_swapped (de->data_sheet[0], "activate",
-                           G_CALLBACK (on_activate),
-                           de);
+  g_signal_connect (ds[TL], "notify::fixed-height",
+                    G_CALLBACK (on_data_sheet_fixed_height_notify), de);
 
-  g_signal_connect_swapped (de->data_sheet[0], "select-range",
-                           G_CALLBACK (on_select_range),
-                           de);
+  return GTK_WIDGET (xpaned);
+}
 
-  g_signal_connect (de->data_sheet[0], "select-row",
-                   G_CALLBACK (on_select_row), de);
+static void
+psppire_data_editor_init (PsppireDataEditor *de)
+{
+  GtkWidget *var_sheet_scroller;
+  GtkWidget *hbox;
 
-  g_signal_connect (de->data_sheet[0], "select-column",
-                   G_CALLBACK (on_select_variable), de);
+  de->font = NULL;
+  de->ui_manager = NULL;
 
+  g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
 
-  g_signal_connect (de->var_sheet, "select-row",
-                   G_CALLBACK (on_select_variable), de);
+  de->cell_ref_label = gtk_label_new ("");
+  gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
+  gtk_misc_set_alignment (GTK_MISC (de->cell_ref_label), 0.0, 0.5);
 
+  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);
 
-  g_signal_connect_after (de, "switch-page",
-                   G_CALLBACK (on_switch_page),
-                   NULL);
+  hbox = gtk_hbox_new (FALSE, 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);
 
+  de->split = FALSE;
+  de->datasheet_vbox_widget
+    = make_single_datasheet (de, GTK_TREE_VIEW_GRID_LINES_BOTH);
 
-  g_signal_connect (de, "map", G_CALLBACK (on_map), NULL);
+  de->vbox = gtk_vbox_new (FALSE, 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);
 
+  gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
+                           gtk_label_new_with_mnemonic (_("Data View")));
 
+  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")));
 
-  //     gtk_sheet_hide_column_titles (de->var_sheet);
-  //  gtk_sheet_hide_row_titles (de->data_sheet);
+  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);
 
-  de->dispose_has_run = FALSE;
+  psppire_data_editor_update_ui_manager (de);
 }
 
-
 GtkWidget*
 psppire_data_editor_new (PsppireVarStore *var_store,
                         PsppireDataStore *data_store)
 {
   return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
-                                    "var-store",  var_store,
-                                    "data-store",  data_store,
-                                    NULL);
+                        "var-store",  var_store,
+                        "data-store",  data_store,
+                        NULL);
 }
-
-
-static void
-psppire_data_editor_remove_split (PsppireDataEditor *de)
-{
-  if ( !de->split ) return;
-  de->split = FALSE;
-
-  g_object_ref (de->sheet_bin[0]);
-  gtk_container_remove (GTK_CONTAINER (de->paned), de->sheet_bin[0]);
-
-  g_object_ref (de->paned);
-  gtk_container_remove (GTK_CONTAINER (de->data_vbox), de->paned);
-
-  gtk_box_pack_start (GTK_BOX (de->data_vbox), de->sheet_bin[0],
-                     TRUE, TRUE, 0);
-
-  g_object_unref (de->sheet_bin[0]);
-
-  g_object_set (de->sheet_bin[0], "vscrollbar-policy",
-               GTK_POLICY_ALWAYS, NULL);
-
-  g_object_set (de->sheet_bin[0], "hscrollbar-policy",
-               GTK_POLICY_ALWAYS, NULL);
-}
-
-
-static void
-psppire_data_editor_set_split (PsppireDataEditor *de)
-{
-  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]);
-
-  gtk_xpaned_pack_top_left (GTK_XPANED (de->paned), de->sheet_bin [0],
-                           TRUE, TRUE);
-
-  gtk_box_pack_start (GTK_BOX (de->data_vbox), de->paned,
-                     TRUE, TRUE, 0);
-
-  g_object_unref (de->paned);
-
-  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);
-}
-
-void
-psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
-{
-  if (split )
-    psppire_data_editor_set_split (de);
-  else
-    psppire_data_editor_remove_split (de);
-
-  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);
-
-
-void
-psppire_data_editor_clip_copy (PsppireDataEditor *de)
-{
-  data_sheet_set_clip (GTK_SHEET (de->data_sheet[0]));
-}
-
-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)
-{
-  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 )
-    {
-      gint row, col;
-      gtk_sheet_get_active_cell (GTK_SHEET (de->data_sheet[0]), &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);
-
-
-  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);
-       }
-    }
-
-  /* and remove the selection */
-  gtk_sheet_unselect_range (GTK_SHEET (de->data_sheet[0]));
-}
-
-
 \f
-
-/* Popup menu related stuff */
-
-static void
-popup_variable_menu (GtkSheet *sheet, gint column,
-                    GdkEventButton *event, gpointer data)
-{
-  GtkMenu *menu = GTK_MENU (data);
-
-  PsppireDataStore *data_store =
-    PSPPIRE_DATA_STORE (gtk_sheet_get_model (sheet));
-
-  const struct variable *v =
-    psppire_dict_get_variable (data_store->dict, column);
-
-  if ( v && event->button == 3)
-    {
-      gtk_sheet_select_column (sheet, column);
-
-      gtk_menu_popup (menu,
-                     NULL, NULL, NULL, NULL,
-                     event->button, event->time);
-    }
-}
-
-
-static void
-popup_cases_menu (GtkSheet *sheet, gint row,
-                 GdkEventButton *event, gpointer data)
+/* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
+   sheet(s) and variable sheet. */
+void
+psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
 {
-  GtkMenu *menu = GTK_MENU (data);
-
-  PsppireDataStore *data_store =
-    PSPPIRE_DATA_STORE (gtk_sheet_get_model (sheet));
+  GtkTreeViewGridLines grid;
+  PsppireDataSheet *data_sheet;
+  int i;
 
-  if ( row <= psppire_data_store_get_case_count (data_store) &&
-       event->button == 3)
-    {
-      gtk_sheet_select_row (sheet, row);
+  grid = (grid_visible
+          ? GTK_TREE_VIEW_GRID_LINES_BOTH
+          : GTK_TREE_VIEW_GRID_LINES_NONE);
 
-      gtk_menu_popup (menu,
-                     NULL, NULL, NULL, NULL,
-                     event->button, event->time);
-    }
+  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);
 }
 
-\f
-
-/* Sorting */
 
 static void
-do_sort (PsppireDataStore *ds, int var, gboolean descend)
+set_font_recursively (GtkWidget *w, gpointer data)
 {
-  GString *string = g_string_new ("SORT CASES BY ");
-
-  const struct variable *v =
-    psppire_dict_get_variable (ds->dict, var);
-
-  g_string_append_printf (string, "%s", var_get_name (v));
-
-  if ( descend )
-    g_string_append (string, " (D)");
-
-  g_string_append (string, ".");
+  PangoFontDescription *font_desc = data;
+  GtkRcStyle *style = gtk_widget_get_modifier_style (w);
 
-  execute_syntax (create_syntax_string_source (string->str));
+  pango_font_description_free (style->font_desc);
+  style->font_desc = pango_font_description_copy (font_desc);
 
-  g_string_free (string, TRUE);
-}
-
-
-/* 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);
+  gtk_widget_modify_style (w, style);
 
-  do_sort (de->data_store,  range.col0, FALSE);
+  if ( GTK_IS_CONTAINER (w))
+    gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
 }
 
-
-/* Sort the data by the the variable which the editor has currently
-   selected */
+/* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
 void
-psppire_data_editor_sort_descending (PsppireDataEditor *de)
+psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
 {
-  GtkSheetRange range;
-  gtk_sheet_get_selected_range (GTK_SHEET(de->data_sheet[0]), &range);
+  set_font_recursively (GTK_WIDGET (de), font_desc);
 
-  do_sort (de->data_store,  range.col0, TRUE);
+  if (de->font)
+    pango_font_description_free (de->font);
+  de->font = pango_font_description_copy (font_desc);
 }
 
-
-\f
-
-
-/* Insert a new variable  before the currently selected position */
+/* 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_insert_variable (PsppireDataEditor *de)
+psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
 {
-  glong posn = -1;
+  GtkTreeViewGridLines grid_lines;
 
-  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;
+  if (split == de->split)
+    return;
 
-  if ( posn == -1 ) posn = 0;
 
-  psppire_dict_insert_variable (de->data_store->dict, posn, NULL);
-}
+  grid_lines = pspp_sheet_view_get_grid_lines (
+    PSPP_SHEET_VIEW (de->data_sheets[0]));
 
-/* Insert a new case before the currently selected position */
-void
-psppire_data_editor_insert_case (PsppireDataEditor *de)
-{
-  glong posn = -1;
+  disconnect_data_sheets (de);
+  gtk_widget_destroy (de->datasheet_vbox_widget);
 
-  if ( de->data_sheet[0]->state == GTK_SHEET_ROW_SELECTED )
-    posn = GTK_SHEET (de->data_sheet[0])->range.row0;
+  if (split)
+    de->datasheet_vbox_widget = make_split_datasheet (de, grid_lines);
   else
-    posn = GTK_SHEET (de->data_sheet[0])->active_cell.row;
+    de->datasheet_vbox_widget = make_single_datasheet (de, grid_lines);
+  psppire_data_editor_refresh_model (de);
 
-  if ( posn == -1 ) posn = 0;
+  gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
+                      TRUE, TRUE, 0);
+  gtk_widget_show_all (de->vbox);
 
-  psppire_data_store_insert_new_case (de->data_store, posn);
-}
+  if (de->font)
+    set_font_recursively (GTK_WIDGET (de), de->font);
 
-/* Delete the cases currently selected in the data sheet */
-void
-psppire_data_editor_delete_cases    (PsppireDataEditor *de)
-{
-  gint first = GTK_SHEET (de->data_sheet[0])->range.row0;
-  gint n = GTK_SHEET (de->data_sheet[0])->range.rowi - first + 1;
-
-  psppire_data_store_delete_cases (de->data_store, first, n);
-
-  gtk_sheet_unselect_range (GTK_SHEET (de->data_sheet[0]));
+  de->split = split;
+  g_object_notify (G_OBJECT (de), "split");
+  psppire_data_editor_update_ui_manager (de);
 }
 
-/* Delete the variables currently selected in the
-   datasheet or variable sheet */
+/* 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_delete_variables (PsppireDataEditor *de)
+psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
 {
-  gint first, n;
+  PsppireDataSheet *data_sheet;
 
   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;
+      data_sheet = psppire_data_editor_get_active_data_sheet (de);
+      psppire_data_sheet_show_variable (data_sheet, dict_index);
       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 ();
+      psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
+                                       dict_index);
       break;
     }
-
-  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));
-}
-
-
-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);
-}
-
-void
-psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
-{
-  psppire_data_store_set_font (de->data_store, font_desc);
-  psppire_var_store_set_font (de->var_store, font_desc);
-}
-
-
-\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);
 }
 
-
-static void
-on_activate (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)
 {
-  gint row, col;
-  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)
     {
-      emit_selected_signal (de);
-      return ;
-    }
+      PsppireDataSheet *data_sheet;
+      GtkWidget *scroller;
+      int i;
 
-  emit_selected_signal (de);
-}
-
-
-static void
-on_select_range (PsppireDataEditor *de)
-{
-  GtkSheetRange range;
-
-  gtk_sheet_get_selected_range (GTK_SHEET (de->data_sheet[0]), &range);
-
-  if ( range.rowi < psppire_data_store_get_case_count (de->data_store)
-       &&
-       range.coli < psppire_var_store_get_var_cnt (de->var_store))
-    {
-      emit_selected_signal (de);
-      return;
-    }
+      /* 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)));
 
-  emit_selected_signal (de);
-}
-
-
-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;
-    }
-
-  on_select_range (de);
-
-  return TRUE;
-}
-
-
-
-static gboolean
-data_is_selected (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))
-    {
-      return FALSE;
-    }
-
-  gtk_sheet_get_selected_range (GTK_SHEET (de->data_sheet[0]), &range);
+      /* 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)
-{
-  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)
+psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
 {
-  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;
-    }
+  PsppireDataSheet *data_sheet;
+  GtkUIManager *ui_manager;
 
-  if ( clip_dict )
-    {
-      dict_destroy (clip_dict);
-      clip_dict = NULL;
-    }
-
-  /* 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);
-    }
-}
-
-
-static void
-on_owner_change (GtkClipboard *clip, GdkEventOwnerChange *event, gpointer data)
-{
-  gint i;
-  gboolean compatible_target = FALSE;
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
+      if (de->ui_manager)
+        g_object_unref (de->ui_manager);
+      if (ui_manager)
+        g_object_ref (ui_manager);
+      de->ui_manager = ui_manager;
 
-  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);
 }
-
-