Rename myreversefunc -> psppire_data_store_string_to_value
[pspp] / src / ui / gui / psppire-data-store.c
index 7dca919c019ed11610943029231eedb13c8323cd..f97b8eaf1cdc5b246ec81685e590640b79686fd0 100644 (file)
@@ -1,11 +1,10 @@
-/* psppire-data-store.c
+/* PSPPIRE - a graphical user interface for PSPP.
+   Copyright (C) 2006, 2008, 2009, 2010, 2011, 2012,
+   2013, 2016, 2017  Free Software Foundation
 
-   PSPPIRE --- A Graphical User Interface for PSPP
-   Copyright (C) 2006  Free Software Foundation
-
-   This program is free software; you can redistribute it and/or modify
+   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
-   the Free Software Foundation; either version 2 of the License, or
+   the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301, USA. */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <config.h>
 #include <string.h>
 #include <stdlib.h>
 #include <gettext.h>
 #define _(msgid) gettext (msgid)
-#define N_(msgid) msgid
+#define P_(msgid) msgid
 
 #include <data/datasheet.h>
 #include <data/data-out.h>
 #include <data/variable.h>
 
-#include <gtksheet/gtksheet.h>
-#include <gtksheet/gsheetmodel.h>
-#include <gtksheet/gsheet-column-iface.h>
+#include <ui/gui/psppire-marshal.h>
 
 #include <pango/pango-context.h>
 
 #include "psppire-data-store.h"
-#include "psppire-case-file.h"
+#include <libpspp/i18n.h>
 #include "helper.h"
 
 #include <data/dictionary.h>
 #include <data/missing-values.h>
 #include <data/value-labels.h>
 #include <data/data-in.h>
+#include <data/format.h>
+
+#include <math/sort.h>
+
+#include "xalloc.h"
+#include "xmalloca.h"
 
+#include "value-variant.h"
 
 static void psppire_data_store_init            (PsppireDataStore      *data_store);
 static void psppire_data_store_class_init      (PsppireDataStoreClass *class);
-static void psppire_data_store_sheet_model_init (GSheetModelIface *iface);
-static void psppire_data_store_sheet_column_init (GSheetColumnIface *iface);
-static void psppire_data_store_sheet_row_init (GSheetRowIface *iface);
 
 static void psppire_data_store_finalize        (GObject           *object);
+static void psppire_data_store_dispose        (GObject           *object);
 
-static gboolean psppire_data_store_clear_datum (GSheetModel *model,
-                                         gint row, gint column);
+static gboolean psppire_data_store_insert_case (PsppireDataStore *ds,
+                                               struct ccase *cc,
+                                               casenumber posn);
 
 
-#define MIN_COLUMNS 10
-
-#define TRAILING_ROWS 10
+static gboolean psppire_data_store_data_in (PsppireDataStore *ds,
+                                           casenumber casenum, gint idx,
+                                           struct substring input,
+                                           const struct fmt_spec *fmt);
 
 static GObjectClass *parent_class = NULL;
 
 
-enum  {FONT_CHANGED,
-       n_SIGNALS};
+enum
+  {
+    ITEMS_CHANGED,
+    CASE_CHANGED,
+    n_SIGNALS
+  };
 
 static guint signals [n_SIGNALS];
 
-
-inline GType
-psppire_data_store_get_type (void)
+static gint
+__tree_model_iter_n_children (GtkTreeModel *tree_model,
+                            GtkTreeIter *iter)
 {
-  static GType data_store_type = 0;
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (tree_model);
 
-  if (!data_store_type)
-    {
-      static const GTypeInfo data_store_info =
-      {
-       sizeof (PsppireDataStoreClass),
-       NULL,           /* base_init */
-       NULL,           /* base_finalize */
-        (GClassInitFunc) psppire_data_store_class_init,
-       NULL,           /* class_finalize */
-       NULL,           /* class_data */
-        sizeof (PsppireDataStore),
-       0,
-        (GInstanceInitFunc) psppire_data_store_init,
-      };
+  if (store->datasheet == NULL)
+    return 0;
 
-      static const GInterfaceInfo sheet_model_info =
-      {
-       (GInterfaceInitFunc) psppire_data_store_sheet_model_init,
-       NULL,
-       NULL
-      };
+  gint n =  datasheet_get_n_rows (store->datasheet);
 
-      static const GInterfaceInfo sheet_column_info =
-      {
-       (GInterfaceInitFunc) psppire_data_store_sheet_column_init,
-       NULL,
-       NULL
-      };
+  return n;
+}
 
-      static const GInterfaceInfo sheet_row_info =
-      {
-       (GInterfaceInitFunc) psppire_data_store_sheet_row_init,
-       NULL,
-       NULL
-      };
+static GtkTreeModelFlags
+__tree_model_get_flags (GtkTreeModel *model)
+{
+  g_return_val_if_fail (PSPPIRE_IS_DATA_STORE (model), (GtkTreeModelFlags) 0);
+
+  return GTK_TREE_MODEL_LIST_ONLY;
+}
 
+static gint
+__tree_model_get_n_columns (GtkTreeModel *tree_model)
+{
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (tree_model);
+
+  return psppire_dict_get_var_cnt (store->dict);
+}
 
-      data_store_type = g_type_register_static (G_TYPE_OBJECT, "PsppireDataStore",
-                                               &data_store_info, 0);
 
-      g_type_add_interface_static (data_store_type,
-                                  G_TYPE_SHEET_MODEL,
-                                  &sheet_model_info);
+static gboolean
+__iter_nth_child (GtkTreeModel *tree_model,
+                 GtkTreeIter *iter,
+                 GtkTreeIter *parent,
+                 gint n)
+{
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (tree_model);
 
-      g_type_add_interface_static (data_store_type,
-                                  G_TYPE_SHEET_COLUMN,
-                                  &sheet_column_info);
+  g_assert (parent == NULL);
+  g_return_val_if_fail (store, FALSE);
 
-      g_type_add_interface_static (data_store_type,
-                                  G_TYPE_SHEET_ROW,
-                                  &sheet_row_info);
+  if (!store->datasheet || n >= datasheet_get_n_rows (store->datasheet))
+    {
+      iter->stamp = -1;
+      iter->user_data = NULL;
+      return FALSE;
     }
 
-  return data_store_type;
-}
+  iter->user_data = GINT_TO_POINTER (n);
+  iter->stamp = store->stamp;
 
+  return TRUE;
+}
 
-static void
-psppire_data_store_class_init (PsppireDataStoreClass *class)
+/* Set the contents of OUT to reflect the information provided by IN, COL, and
+   ROW, for MODEL.  Returns TRUE if successful. */
+gboolean
+psppire_data_store_string_to_value (GtkTreeModel *model, gint col, gint row,
+                                   const gchar *in, GValue *out)
 {
-  GObjectClass *object_class;
+  PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
 
-  parent_class = g_type_class_peek_parent (class);
-  object_class = (GObjectClass*) class;
+  while (col >= psppire_dict_get_var_cnt (store->dict))
+    {
+      const struct variable *var =
+       psppire_dict_insert_variable (store->dict,
+                                     psppire_dict_get_var_cnt (store->dict),
+                                     NULL);
+      g_return_val_if_fail (var, FALSE);
+    }
 
-  object_class->finalize = psppire_data_store_finalize;
+  const struct variable *variable = psppire_dict_get_variable (store->dict, col);
+  g_return_val_if_fail (variable, FALSE);
 
-  signals [FONT_CHANGED] =
-    g_signal_new ("font_changed",
-                 G_TYPE_FROM_CLASS (class),
-                 G_SIGNAL_RUN_FIRST,
-                 0,
-                 NULL, NULL,
-                 g_cclosure_marshal_VOID__VOID,
-                 G_TYPE_NONE,
-                 0);
-}
+  const struct fmt_spec *fmt = var_get_print_format (variable);
 
+  int width = var_get_width (variable);
 
+  union value val;
+  value_init (&val, width);
+  char *xx =
+    data_in (ss_cstr (in), psppire_dict_encoding (store->dict),
+            fmt->type, &val, width, "UTF-8");
 
-static gint
-psppire_data_store_get_var_count (const GSheetModel *model)
-{
-  const PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
+  GVariant *vrnt = value_variant_new (&val, width);
+  value_destroy (&val, width);
 
-  return psppire_dict_get_var_cnt (store->dict);
+  g_value_init (out, G_TYPE_VARIANT);
+  g_value_set_variant (out, vrnt);
+  free (xx);
+  return TRUE;
 }
 
-static gint
-psppire_data_store_get_case_count (const GSheetModel *model)
+static char *
+unlabeled_value (PsppireDataStore *store, const struct variable *variable, const union value *val)
 {
-  const PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
-
-  return psppire_case_file_get_case_count (store->case_file);
+  const struct fmt_spec *fmt = var_get_print_format (variable);
+  return data_out (val, psppire_dict_encoding (store->dict),  fmt);
 }
 
-
-static void
-psppire_data_store_init (PsppireDataStore *data_store)
+gchar *
+psppire_data_store_value_to_string (gpointer unused, PsppireDataStore *store, gint col, gint row, const GValue *v)
 {
-  data_store->dict = 0;
-  data_store->case_file = 0;
-  data_store->width_of_m = 10;
-}
+  const struct variable *variable = psppire_dict_get_variable (store->dict, col);
+  g_return_val_if_fail (variable, g_strdup ("???"));
 
-const PangoFontDescription *
-psppire_data_store_get_font_desc (const GSheetModel *model,
-                             gint row, gint column)
-{
-  PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
+  GVariant *vrnt = g_value_get_variant (v);
+  union value val;
+  value_variant_get (&val, vrnt);
 
-  return store->font_desc;
+  char *out = unlabeled_value (store, variable, &val);
+
+  value_destroy_from_variant (&val, vrnt);
+
+  return out;
 }
 
-static inline gchar *
-psppire_data_store_get_string_wrapper (const GSheetModel *model, gint row,
-                                      gint column)
+gchar *
+psppire_data_store_value_to_string_with_labels (gpointer unused, PsppireDataStore *store, gint col, gint row, const GValue *v)
 {
-  return psppire_data_store_get_string (PSPPIRE_DATA_STORE (model), row, column);
-}
+  const struct variable *variable = psppire_dict_get_variable (store->dict, col);
+  g_return_val_if_fail (variable, g_strdup ("???"));
 
+  GVariant *vrnt = g_value_get_variant (v);
+  union value val;
+  value_variant_get (&val, vrnt);
 
-static inline gboolean
-psppire_data_store_set_string_wrapper (GSheetModel *model,
-                                      const gchar *text,
-                                      gint row, gint column)
-{
-  return psppire_data_store_set_string (PSPPIRE_DATA_STORE (model), text,
-                                       row, column);
-}
+  char *out = NULL;
 
+  const struct val_labs *vls = var_get_value_labels (variable);
+  struct val_lab *vl = val_labs_lookup (vls, &val);
+  if (vl != NULL)
+    out = strdup (val_lab_get_label (vl));
+  else
+    out = unlabeled_value (store, variable, &val);
 
+  value_destroy_from_variant (&val, vrnt);
 
+  return out;
+}
 
 static void
-psppire_data_store_sheet_model_init (GSheetModelIface *iface)
+__get_value (GtkTreeModel *tree_model,
+            GtkTreeIter *iter,
+            gint column,
+            GValue *value)
 {
-  iface->free_strings = TRUE;
-  iface->get_string = psppire_data_store_get_string_wrapper;
-  iface->set_string = psppire_data_store_set_string_wrapper;
-  iface->clear_datum = psppire_data_store_clear_datum;
-  iface->is_editable = NULL;
-  iface->is_visible = NULL;
-  iface->get_foreground = NULL;
-  iface->get_background = NULL;
-  iface->get_font_desc = psppire_data_store_get_font_desc;
-  iface->get_cell_border = NULL;
-  iface->get_column_count = psppire_data_store_get_var_count;
-  iface->get_row_count = psppire_data_store_get_case_count;
-}
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (tree_model);
 
-static
-gboolean always_true ()
-{
-  return TRUE;
-}
+  g_return_if_fail (iter->stamp == store->stamp);
 
+  const struct variable *variable = psppire_dict_get_variable (store->dict, column);
+  if (NULL == variable)
+    return;
 
-static void
-delete_cases_callback (GtkWidget *w, gint first, gint n_cases, gpointer data)
-{
-  PsppireDataStore *store  ;
+  g_value_init (value, G_TYPE_VARIANT);
 
-  g_return_if_fail (data);
+  gint row = GPOINTER_TO_INT (iter->user_data);
 
-  store  = PSPPIRE_DATA_STORE (data);
+  struct ccase *cc = datasheet_get_row (store->datasheet, row);
 
-  g_assert (first >= 0);
+  const union value *val = case_data_idx (cc, var_get_case_index (variable));
 
-  g_sheet_model_rows_deleted (G_SHEET_MODEL (store), first, n_cases);
+  GVariant *vv = value_variant_new (val, var_get_width (variable));
+
+  g_value_set_variant (value, vv);
+
+  case_unref (cc);
 }
 
 
 static void
-insert_case_callback (GtkWidget *w, gint casenum, gpointer data)
+__tree_model_init (GtkTreeModelIface *iface)
 {
-  PsppireDataStore *store  ;
+  iface->get_flags       = __tree_model_get_flags;
+  iface->get_n_columns   = __tree_model_get_n_columns ;
+  iface->get_column_type = NULL;
+  iface->get_iter        = NULL;
+  iface->iter_next       = NULL;
+  iface->get_path        = NULL;
+  iface->get_value       = __get_value;
 
-  g_return_if_fail (data);
+  iface->iter_children   = NULL;
+  iface->iter_has_child  = NULL;
+  iface->iter_n_children = __tree_model_iter_n_children;
+  iface->iter_nth_child  = __iter_nth_child;
+  iface->iter_parent     = NULL;
+}
 
-  store  = PSPPIRE_DATA_STORE (data);
 
-  g_sheet_model_range_changed (G_SHEET_MODEL (store),
-                              casenum, -1,
-                              psppire_case_file_get_case_count (store->case_file),
-                              -1);
+GType
+psppire_data_store_get_type (void)
+{
+  static GType data_store_type = 0;
 
-  g_sheet_model_rows_inserted (G_SHEET_MODEL (store), casenum, 1);
-}
+  if (!data_store_type)
+    {
+      static const GTypeInfo data_store_info =
+      {
+       sizeof (PsppireDataStoreClass),
+       NULL,           /* base_init */
+       NULL,           /* base_finalize */
+        (GClassInitFunc) psppire_data_store_class_init,
+       NULL,           /* class_finalize */
+       NULL,           /* class_data */
+        sizeof (PsppireDataStore),
+       0,
+        (GInstanceInitFunc) psppire_data_store_init,
+      };
 
+      static const GInterfaceInfo tree_model_info = {
+       (GInterfaceInitFunc) __tree_model_init,
+       NULL,
+       NULL
+      };
 
-static void
-changed_case_callback (GtkWidget *w, gint casenum, gpointer data)
-{
-  PsppireDataStore *store  ;
-  g_return_if_fail (data);
+      data_store_type = g_type_register_static (G_TYPE_OBJECT,
+                                               "PsppireDataStore",
+                                               &data_store_info, 0);
 
-  store  = PSPPIRE_DATA_STORE (data);
+      g_type_add_interface_static (data_store_type, GTK_TYPE_TREE_MODEL,
+                                  &tree_model_info);
+    }
 
-  g_sheet_model_range_changed (G_SHEET_MODEL (store),
-                                casenum, -1,
-                                casenum, -1);
+  return data_store_type;
 }
 
 
 static void
-delete_variables_callback (GObject *obj, gint var_num, gint n_vars, gpointer data)
+psppire_data_store_class_init (PsppireDataStoreClass *class)
 {
-  PsppireDataStore *store ;
+  GObjectClass *object_class;
 
-  g_return_if_fail (data);
+  parent_class = g_type_class_peek_parent (class);
+  object_class = (GObjectClass*) class;
 
-  store  = PSPPIRE_DATA_STORE (data);
+  object_class->finalize = psppire_data_store_finalize;
+  object_class->dispose = psppire_data_store_dispose;
 
-  g_sheet_model_columns_deleted (G_SHEET_MODEL (store), var_num, n_vars);
+  signals [ITEMS_CHANGED] =
+    g_signal_new ("items-changed",
+                 G_TYPE_FROM_CLASS (class),
+                 G_SIGNAL_RUN_FIRST,
+                 0,
+                 NULL, NULL,
+                 psppire_marshal_VOID__UINT_UINT_UINT,
+                 G_TYPE_NONE,
+                 3,
+                 G_TYPE_UINT,  /* Index of the start of the change */
+                 G_TYPE_UINT,  /* The number of items deleted */
+                 G_TYPE_UINT); /* The number of items inserted */
 
-  g_sheet_column_columns_changed (G_SHEET_COLUMN (store),
-                                  var_num, -1);
+  signals [CASE_CHANGED] =
+    g_signal_new ("case-changed",
+                 G_TYPE_FROM_CLASS (class),
+                 G_SIGNAL_RUN_FIRST,
+                 0,
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__INT,
+                 G_TYPE_NONE,
+                 1,
+                 G_TYPE_INT);
 }
 
 
-static void
-variable_changed_callback (GObject *obj, gint var_num, gpointer data)
+
+casenumber
+psppire_data_store_get_case_count (const PsppireDataStore *store)
 {
-  PsppireDataStore *store;
+  return datasheet_get_n_rows (store->datasheet);
+}
 
-  g_return_if_fail (data);
+size_t
+psppire_data_store_get_value_count (const PsppireDataStore *store)
+{
+  return psppire_dict_get_value_cnt (store->dict);
+}
 
-  store  = PSPPIRE_DATA_STORE (data);
+const struct caseproto *
+psppire_data_store_get_proto (const PsppireDataStore *store)
+{
+  return psppire_dict_get_proto (store->dict);
+}
 
-  g_sheet_column_columns_changed (G_SHEET_COLUMN (store),
-                                 var_num, 1);
+static void
+psppire_data_store_init (PsppireDataStore *data_store)
+{
+  data_store->dict = NULL;
+  data_store->datasheet = NULL;
+  data_store->dispose_has_run = FALSE;
+  data_store->stamp = g_random_int ();
+}
 
 
-  g_sheet_model_range_changed (G_SHEET_MODEL (store),
-                              -1, var_num,
-                              -1, var_num);
+static void
+psppire_data_store_delete_value (PsppireDataStore *store, gint case_index)
+{
+  g_return_if_fail (store->datasheet);
+  datasheet_delete_columns (store->datasheet, case_index, 1);
+  datasheet_insert_column (store->datasheet, NULL, -1, case_index);
 }
 
+
+/*
+   A callback which occurs after a variable has been deleted.
+ */
 static void
-insert_variable_callback (GObject *obj, gint var_num, gpointer data)
+delete_variable_callback (GObject *obj, const struct variable *var UNUSED,
+                          gint dict_index, gint case_index,
+                          gpointer data)
 {
-  PsppireDataStore *store;
-  gint posn;
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
 
-  g_return_if_fail (data);
+  psppire_data_store_delete_value (store, case_index);
+}
 
-  store  = PSPPIRE_DATA_STORE (data);
+struct resize_datum_aux
+  {
+    const struct dictionary *dict;
+    const struct variable *new_variable;
+    const struct variable *old_variable;
+  };
 
-  if ( var_num > 0 )
-    {
-      struct variable *variable =
-       psppire_dict_get_variable (store->dict, var_num);
+static void
+resize_datum (const union value *old, union value *new, const void *aux_)
+{
+  const struct resize_datum_aux *aux = aux_;
+  int new_width = var_get_width (aux->new_variable);
+  const char *enc = dict_get_encoding (aux->dict);
+  const struct fmt_spec *newfmt = var_get_print_format (aux->new_variable);
+  char *s = data_out (old, enc, var_get_print_format (aux->old_variable));
+  enum fmt_type type = (fmt_usable_for_input (newfmt->type)
+                        ? newfmt->type
+                        : FMT_DOLLAR);
+  free (data_in (ss_cstr (s), enc, type, new, new_width, enc));
+  free (s);
+}
 
-      g_assert (variable != NULL);
+static void
+variable_changed_callback (GObject *obj, gint var_num, guint what, const struct variable *oldvar,
+                          gpointer data)
+{
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
+  struct variable *variable = psppire_dict_get_variable (store->dict, var_num);
 
-      posn = var_get_case_index (variable);
-    }
-  else
+  if (what & VAR_TRAIT_WIDTH)
     {
-      posn = 0;
+      int posn = var_get_case_index (variable);
+      struct resize_datum_aux aux;
+      aux.old_variable = oldvar;
+      aux.new_variable = variable;
+      aux.dict = store->dict->dict;
+      datasheet_resize_column (store->datasheet, posn, var_get_width (variable),
+                               resize_datum, &aux);
     }
-
-  psppire_case_file_insert_values (store->case_file, 1, posn);
-
-  g_sheet_column_columns_changed (G_SHEET_COLUMN (store),
-                                 var_num, 1);
-
-  g_sheet_model_columns_inserted (G_SHEET_MODEL (store), var_num, 1);
 }
 
-
 static void
-dict_size_change_callback (GObject *obj,
-                         gint posn, gint adjustment, gpointer data)
+insert_variable_callback (GObject *obj, gint var_num, gpointer data)
 {
-  PsppireDataStore *store ;
+  struct variable *variable;
+  PsppireDataStore *store;
+  gint posn;
 
   g_return_if_fail (data);
 
   store  = PSPPIRE_DATA_STORE (data);
 
-  psppire_case_file_insert_values (store->case_file, adjustment, posn);
+  variable = psppire_dict_get_variable (store->dict, var_num);
+  posn = var_get_case_index (variable);
+  psppire_data_store_insert_value (store, var_get_width (variable), posn);
 }
 
-
-
 /**
  * psppire_data_store_new:
  * @dict: The dictionary for this data_store.
@@ -374,41 +461,43 @@ psppire_data_store_new (PsppireDict *dict)
 {
   PsppireDataStore *retval;
 
-  retval = g_object_new (GTK_TYPE_DATA_STORE, NULL);
+  retval = g_object_new (PSPPIRE_TYPE_DATA_STORE, NULL);
 
   psppire_data_store_set_dictionary (retval, dict);
 
   return retval;
 }
 
-
 void
-psppire_data_store_set_case_file (PsppireDataStore *data_store,
-                                 PsppireCaseFile *cf)
+psppire_data_store_set_reader (PsppireDataStore *ds,
+                              struct casereader *reader)
 {
-  if ( data_store->case_file)
+  gint i;
+  gint old_n = 0;
+  if ( ds->datasheet)
     {
-      g_object_unref (data_store->case_file);
+      old_n = datasheet_get_n_rows (ds->datasheet);
+      datasheet_destroy (ds->datasheet);
     }
 
-  data_store->case_file = cf;
+  ds->datasheet = datasheet_create (reader);
 
-  g_signal_connect (data_store->case_file, "cases-deleted",
-                  G_CALLBACK (delete_cases_callback),
-                  data_store);
-
-  g_signal_connect (data_store->case_file, "case-inserted",
-                  G_CALLBACK (insert_case_callback),
-                  data_store);
+  gint new_n = datasheet_get_n_rows (ds->datasheet);
 
+  if ( ds->dict )
+    for (i = 0 ; i < n_dict_signals; ++i )
+      {
+       if ( ds->dict_handler_id [i] > 0)
+         {
+           g_signal_handler_unblock (ds->dict,
+                                     ds->dict_handler_id[i]);
+         }
+      }
 
-  g_signal_connect (data_store->case_file, "case-changed",
-                  G_CALLBACK (changed_case_callback),
-                  data_store);
+  g_signal_emit (ds, signals[ITEMS_CHANGED], 0, 0, old_n, new_n);
 }
 
 
-
 /**
  * psppire_data_store_replace_set_dictionary:
  * @data_store: The variable store
@@ -420,237 +509,211 @@ psppire_data_store_set_case_file (PsppireDataStore *data_store,
 void
 psppire_data_store_set_dictionary (PsppireDataStore *data_store, PsppireDict *dict)
 {
+  int i;
+
+  /* Disconnect any existing handlers */
+  if ( data_store->dict )
+    for (i = 0 ; i < n_dict_signals; ++i )
+      {
+       g_signal_handler_disconnect (data_store->dict,
+                                    data_store->dict_handler_id[i]);
+      }
+
   data_store->dict = dict;
 
-  g_signal_connect (dict, "variable-inserted",
-                  G_CALLBACK (insert_variable_callback),
-                  data_store);
+  if ( dict != NULL)
+    {
+
+      data_store->dict_handler_id [VARIABLE_INSERTED] =
+       g_signal_connect (dict, "variable-inserted",
+                         G_CALLBACK (insert_variable_callback),
+                         data_store);
 
-  g_signal_connect (dict, "variables-deleted",
-                  G_CALLBACK (delete_variables_callback),
-                  data_store);
+      data_store->dict_handler_id [VARIABLE_DELETED] =
+       g_signal_connect (dict, "variable-deleted",
+                         G_CALLBACK (delete_variable_callback),
+                         data_store);
 
-  g_signal_connect (dict, "variable-changed",
-                  G_CALLBACK (variable_changed_callback),
-                  data_store);
+      data_store->dict_handler_id [VARIABLE_CHANGED] =
+       g_signal_connect (dict, "variable-changed",
+                         G_CALLBACK (variable_changed_callback),
+                         data_store);
+    }
 
 
-  g_signal_connect (dict, "dict-size-changed",
-                   G_CALLBACK (dict_size_change_callback),
-                   data_store);
 
   /* The entire model has changed */
-  g_sheet_model_range_changed (G_SHEET_MODEL (data_store), -1, -1, -1, -1);
 
-  g_sheet_column_columns_changed (G_SHEET_COLUMN (data_store), 0, -1);
+  if ( data_store->dict )
+    for (i = 0 ; i < n_dict_signals; ++i )
+      {
+       if ( data_store->dict_handler_id [i] > 0)
+         {
+           g_signal_handler_block (data_store->dict,
+                                   data_store->dict_handler_id[i]);
+         }
+      }
 }
 
 static void
 psppire_data_store_finalize (GObject *object)
 {
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (object);
+
+  if (ds->datasheet)
+    {
+      datasheet_destroy (ds->datasheet);
+      ds->datasheet = NULL;
+    }
 
   /* must chain up */
   (* parent_class->finalize) (object);
 }
 
 
+static void
+psppire_data_store_dispose (GObject *object)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (object);
+
+  if (ds->dispose_has_run)
+    return;
+
+  psppire_data_store_set_dictionary (ds, NULL);
+
+  /* must chain up */
+  (* parent_class->dispose) (object);
+
+  ds->dispose_has_run = TRUE;
+}
+
+
 
 /* Insert a blank case before POSN */
 gboolean
-psppire_data_store_insert_new_case (PsppireDataStore *ds, gint posn)
+psppire_data_store_insert_new_case (PsppireDataStore *ds, casenumber posn)
 {
   gboolean result;
-  gint val_cnt, v;
-  struct ccase cc;
+  const struct caseproto *proto;
+  struct ccase *cc;
   g_return_val_if_fail (ds, FALSE);
 
+  proto = datasheet_get_proto (ds->datasheet);
+  g_return_val_if_fail (caseproto_get_n_widths (proto) > 0, FALSE);
+  g_return_val_if_fail (posn <= psppire_data_store_get_case_count (ds), FALSE);
 
-  /* Opportunity for optimisation exists here when creating a blank case */
-  val_cnt = datasheet_get_column_cnt (ds->case_file->datasheet) ;
-
-  case_create (&cc, val_cnt);
-
-  memset ( case_data_rw_idx (&cc, 0), 0, val_cnt * MAX_SHORT_STRING);
-
-  for (v = 0 ; v < psppire_dict_get_var_cnt (ds->dict) ; ++v)
-    {
-      const struct variable *pv = psppire_dict_get_variable (ds->dict, v);
-      if ( var_is_alpha (pv))
-       continue;
-
-      case_data_rw (&cc, pv)->f = SYSMIS;
-    }
+  cc = case_create (proto);
+  case_set_missing (cc);
 
-  result = psppire_case_file_insert_case (ds->case_file, &cc, posn);
+  result = psppire_data_store_insert_case (ds, cc, posn);
 
-  case_destroy (&cc);
+  case_unref (cc);
 
   return result;
 }
 
-
-gchar *
-psppire_data_store_get_string (PsppireDataStore *store, gint row, gint column)
+gboolean
+psppire_data_store_get_value (PsppireDataStore *store,
+                             glong row, const struct variable *var,
+                             union value *val)
 {
-  gint idx;
-  char *text;
-  const struct fmt_spec *fp ;
-  const struct variable *pv ;
-  union value *v ;
-  GString *s;
-
-  g_return_val_if_fail (store->dict, NULL);
-  g_return_val_if_fail (store->case_file, NULL);
-
-  if (column >= psppire_dict_get_var_cnt (store->dict))
-    return NULL;
-
-  if ( row >= psppire_case_file_get_case_count (store->case_file))
-    return NULL;
+  g_return_val_if_fail (store != NULL, FALSE);
+  g_return_val_if_fail (store->datasheet != NULL, FALSE);
+  g_return_val_if_fail (var != NULL, FALSE);
 
-  pv = psppire_dict_get_variable (store->dict, column);
+  if (row < 0 || row >= datasheet_get_n_rows (store->datasheet))
+    return FALSE;
 
-  g_assert (pv);
+  int width = var_get_width (var);
+  value_init (val, width);
+  datasheet_get_value (store->datasheet, row, var_get_case_index (var), val);
 
-  idx = var_get_case_index (pv);
+  return TRUE;
+}
 
-  g_assert (idx >= 0);
 
-  v = psppire_case_file_get_value (store->case_file, row, idx, NULL,
-                                   var_get_width (pv));
 
-  g_return_val_if_fail (v, NULL);
+gchar *
+psppire_data_store_get_string (PsppireDataStore *store,
+                               glong row, const struct variable *var,
+                               bool use_value_label)
+{
+  gchar *string;
+  union value v;
+  int width = var_get_width (var);
+  if (! psppire_data_store_get_value (store, row, var, &v))
+    return NULL;
 
-  if ( store->show_labels)
+  string = NULL;
+  if (use_value_label)
     {
-      const gchar *label = var_lookup_value_label (pv, v);
-      if (label)
-        {
-          free (v);
-         return pspp_locale_to_utf8 (label, -1, 0);
-        }
+      const char *label = var_lookup_value_label (var, &v);
+      if (label != NULL)
+        string = g_strdup (label);
     }
+  if (string == NULL)
+    string = value_to_text (v, var);
 
-  fp = var_get_write_format (pv);
-
-  s = g_string_sized_new (fp->w + 1);
-  g_string_set_size (s, fp->w);
-
-  memset (s->str, 0, fp->w);
-
-  g_assert (fp->w == s->len);
-
-  /* Converts binary value V into printable form in the exactly
-     FP->W character in buffer S according to format specification
-     FP.  No null terminator is appended to the buffer.  */
-  data_out (v, fp, s->str);
-
-  text = pspp_locale_to_utf8 (s->str, fp->w, 0);
-  g_string_free (s, TRUE);
+  value_destroy (&v, width);
 
-  g_strchomp (text);
-
-  free (v);
-  return text;
+  return string;
 }
 
 
-static gboolean
-psppire_data_store_clear_datum (GSheetModel *model,
-                                         gint row, gint col)
-
-{
-  PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
+/* Attempts to update that part of the variable store which corresponds to VAR
+   within ROW with the value TEXT.
 
-  union value v;
-  const struct variable *pv = psppire_dict_get_variable (store->dict, col);
+   If USE_VALUE_LABEL is true, and TEXT is a value label for the column's
+   variable, then stores the value from that value label instead of the literal
+   TEXT.
 
-  const gint index = var_get_case_index (pv) ;
-
-  if ( var_is_numeric (pv))
-    v.f = SYSMIS;
-  else
-    memcpy (v.s, "", MAX_SHORT_STRING);
-
-  psppire_case_file_set_value (store->case_file, row, index, &v,
-                             var_get_width (pv));
-
-  return TRUE;
-}
-
-
-/* Attempts to update that part of the variable store which corresponds
-   to ROW, COL with  the value TEXT.
-   Returns true if anything was updated, false otherwise.
-*/
+   Returns true if anything was updated, false otherwise.  */
 gboolean
 psppire_data_store_set_string (PsppireDataStore *store,
-                              const gchar *text, gint row, gint col)
-{
-  const struct variable *pv = psppire_dict_get_variable (store->dict, col);
-  g_return_val_if_fail (pv, FALSE);
-
-#if 0
-  /* Allow the user to insert a lot of blank cases, simply by skipping rows */
-  for (r = psppire_case_file_get_case_count (store->case_file); r <= row ; ++r)
+                              const gchar *text,
+                               glong row, const struct variable *var,
+                               gboolean use_value_label)
+{
+  gint case_index;
+  glong n_cases;
+  gboolean ok;
+
+  n_cases = psppire_data_store_get_case_count (store);
+  if (row > n_cases)
+    return FALSE;
+  if (row == n_cases)
+    psppire_data_store_insert_new_case (store, row);
+
+  case_index = var_get_case_index (var);
+  if (use_value_label)
     {
-
-      gint c;
-
-      psppire_case_array_insert_case (store->cases, r, 0, 0);
-
-
-      for (c = 0 ; c < psppire_dict_get_var_cnt (store->dict); ++c )
-       psppire_data_store_clear_datum (model, r, c);
+      const struct val_labs *vls = var_get_value_labels (var);
+      const union value *value = vls ? val_labs_find_value (vls, text) : NULL;
+      if (value)
+        ok = datasheet_put_value (store->datasheet, row, case_index, value);
+      else
+        ok = FALSE;
     }
-#endif
-
-  psppire_case_file_data_in (store->case_file, row,
-                             var_get_case_index (pv), ss_cstr (text),
-                             var_get_write_format (pv));
+  else
+    ok = psppire_data_store_data_in (store, row, case_index, ss_cstr (text),
+                                     var_get_print_format (var));
 
-  return TRUE;
+  if (ok)
+    g_signal_emit (store, signals [CASE_CHANGED], 0, row);
+  return ok;
 }
 
 
-void
-psppire_data_store_set_font (PsppireDataStore *store,
-                           const PangoFontDescription *fd)
-{
-  g_return_if_fail (store);
-  g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
-
-  store->font_desc = fd;
-#if 0
-  store->width_of_m = calc_m_width (fd);
-#endif
-  g_signal_emit (store, signals [FONT_CHANGED], 0);
-
-
-  g_sheet_model_range_changed (G_SHEET_MODEL (store),
-                                -1, -1, -1, -1);
-}
-
 
 void
-psppire_data_store_show_labels (PsppireDataStore *store, gboolean show_labels)
+psppire_data_store_clear (PsppireDataStore *ds)
 {
-  g_return_if_fail (store);
-  g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
-
-  store->show_labels = show_labels;
+  datasheet_destroy (ds->datasheet);
+  ds->datasheet = NULL;
 
-  g_sheet_model_range_changed (G_SHEET_MODEL (store),
-                                -1, -1, -1, -1);
-}
-
-
-void
-psppire_data_store_clear (PsppireDataStore *data_store)
-{
-  psppire_case_file_clear (data_store->case_file);
+  psppire_dict_clear (ds->dict);
 
-  psppire_dict_clear (data_store->dict);
+  g_signal_emit (ds, signals [ITEMS_CHANGED], 0, 0, -1, 0);
 }
 
 
@@ -659,199 +722,198 @@ psppire_data_store_clear (PsppireDataStore *data_store)
 struct casereader *
 psppire_data_store_get_reader (PsppireDataStore *ds)
 {
+  int i;
   struct casereader *reader ;
 
-  reader = psppire_case_file_make_reader (ds->case_file);
-
-  return reader;
-}
-
-
-
-/* Column related funcs */
-
-static gint
-geometry_get_column_count (const GSheetColumn *geom)
-{
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
-
-  return MAX (MIN_COLUMNS, psppire_dict_get_var_cnt (ds->dict));
-}
-
-
-
-static gint
-geometry_get_width (const GSheetColumn *geom, gint unit)
-{
-  const struct variable *pv ;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
-
-  if ( unit >= psppire_dict_get_var_cnt (ds->dict) )
-    return ds->width_of_m * 8 ;
+  if ( ds->dict )
+    for (i = 0 ; i < n_dict_signals; ++i )
+      {
+       g_signal_handler_block (ds->dict,
+                               ds->dict_handler_id[i]);
+      }
 
-  pv = psppire_dict_get_variable (ds->dict, unit);
+  reader = datasheet_make_reader (ds->datasheet);
 
-  if ( pv == NULL )
-    return ds->width_of_m * 8 ;
+  /* We must not reference this again */
+  ds->datasheet = NULL;
 
-  return ds->width_of_m * var_get_display_width (pv);
+  return reader;
 }
 
-static void
-geometry_set_width (GSheetColumn *geom, gint unit, gint width)
+/* Returns the CASENUMth case, or a null pointer on failure.
+ */
+struct ccase *
+psppire_data_store_get_case (const PsppireDataStore *ds,
+                            casenumber casenum)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
-
-  struct variable *pv = psppire_dict_get_variable (ds->dict, unit);
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
 
-  var_set_display_width (pv, width / ds->width_of_m );
+  return datasheet_get_row (ds->datasheet, casenum);
 }
 
 
-
-static GtkJustification
-geometry_get_justification (const GSheetColumn *geom, gint unit)
+gboolean
+psppire_data_store_delete_cases (PsppireDataStore *ds, casenumber first,
+                                casenumber n_cases)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
-  const struct variable *pv ;
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
 
+  g_return_val_if_fail (first + n_cases <=
+                       psppire_data_store_get_case_count (ds), FALSE);
 
-  if ( unit >= psppire_dict_get_var_cnt (ds->dict) )
-    return GTK_JUSTIFY_LEFT;
 
-  pv = psppire_dict_get_variable (ds->dict, unit);
+  datasheet_delete_rows (ds->datasheet, first, n_cases);
 
-  return (var_get_alignment (pv) == ALIGN_LEFT ? GTK_JUSTIFY_LEFT
-          : var_get_alignment (pv) == ALIGN_RIGHT ? GTK_JUSTIFY_RIGHT
-          : GTK_JUSTIFY_CENTER);
+  g_signal_emit (ds, signals[ITEMS_CHANGED], 0, first, n_cases, 0);
+
+  return TRUE;
 }
 
 
-static const gchar null_var_name[]=N_("var");
 
-static gchar *
-geometry_get_column_button_label (const GSheetColumn *geom, gint unit)
+/* Insert case CC into the case file before POSN */
+static gboolean
+psppire_data_store_insert_case (PsppireDataStore *ds,
+                               struct ccase *cc,
+                               casenumber posn)
 {
-  gchar *text;
-  struct variable *pv ;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
+  bool result ;
 
-  if ( unit >= psppire_dict_get_var_cnt (ds->dict) )
-    return g_locale_to_utf8 (null_var_name, -1, 0, 0, 0);
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
 
-  pv = psppire_dict_get_variable (ds->dict, unit);
+  cc = case_ref (cc);
+  result = datasheet_insert_rows (ds->datasheet, posn, &cc, 1);
 
-  text =  pspp_locale_to_utf8 (var_get_name (pv), -1, 0);
+  if ( result )
+    {
+      g_signal_emit (ds, signals[ITEMS_CHANGED], 0, posn, 0, 1);
+    }
+  else
+    g_warning ("Cannot insert case at position %ld\n", posn);
 
-  return text;
+  return result;
 }
 
 
-static gchar *
-geometry_get_column_subtitle (const GSheetColumn *geom, gint unit)
+/* Set the value of VAR in case CASENUM to V.
+   V must be the correct width for IDX.
+   Returns true if successful, false on I/O error. */
+gboolean
+psppire_data_store_set_value (PsppireDataStore *ds, casenumber casenum,
+                             const struct variable *var, const union value *v)
 {
-  gchar *text;
-  const struct variable *v ;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
+  glong n_cases;
+  bool ok;
 
-  if ( unit >= psppire_dict_get_var_cnt (ds->dict) )
-    return NULL;
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
 
-  v = psppire_dict_get_variable (ds->dict, unit);
+  n_cases = psppire_data_store_get_case_count (ds);
+  if ( casenum > n_cases)
+    return FALSE;
 
-  if ( ! var_has_label (v))
-    return NULL;
+  if (casenum == n_cases)
+    psppire_data_store_insert_new_case (ds, casenum);
 
-  text =  pspp_locale_to_utf8 (var_get_label (v), -1, 0);
+  ok = datasheet_put_value (ds->datasheet, casenum, var_get_case_index (var),
+                            v);
+  if (ok)
+    {
+      g_signal_emit (ds, signals [CASE_CHANGED], 0, casenum);
+      g_signal_emit (ds, signals [ITEMS_CHANGED], 0, casenum, 1, 1);
+    }
 
-  return text;
+  return ok;
 }
 
 
-static gboolean
-geometry_get_sensitivity (const GSheetColumn *geom, gint unit)
-{
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
-
-  return (unit < psppire_dict_get_var_cnt (ds->dict));
-}
 
 
-static void
-psppire_data_store_sheet_column_init (GSheetColumnIface *iface)
+/* Set the IDXth value of case C using D_IN */
+static gboolean
+psppire_data_store_data_in (PsppireDataStore *ds, casenumber casenum, gint idx,
+                           struct substring input, const struct fmt_spec *fmt)
 {
-  iface->get_column_count = geometry_get_column_count;
-  iface->get_width = geometry_get_width;
-  iface->set_width = geometry_set_width;
-  iface->get_visibility = always_true;
-  iface->get_sensitivity = geometry_get_sensitivity;
-  iface->get_justification = geometry_get_justification;
-  iface->get_button_label = geometry_get_column_button_label;
-  iface->get_subtitle = geometry_get_column_subtitle;
-}
+  union value value;
+  int width;
+  bool ok;
 
+  PsppireDict *dict;
 
-/* Row related funcs */
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
 
-static gint
-geometry_get_row_count (const GSheetRow *geom, gpointer data)
-{
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
+  g_return_val_if_fail (idx < datasheet_get_n_columns (ds->datasheet), FALSE);
 
-  return TRAILING_ROWS + psppire_case_file_get_case_count (ds->case_file);
-}
+  dict = ds->dict;
 
+  width = fmt_var_width (fmt);
+  g_return_val_if_fail (caseproto_get_width (
+                          datasheet_get_proto (ds->datasheet), idx) == width,
+                        FALSE);
+  value_init (&value, width);
+  ok = (datasheet_get_value (ds->datasheet, casenum, idx, &value)
+        && data_in_msg (input, UTF8, fmt->type, &value, width,
+                        dict_get_encoding (dict->dict))
+        && datasheet_put_value (ds->datasheet, casenum, idx, &value));
+  value_destroy (&value, width);
 
-static gint
-geometry_get_height (const GSheetRow *geom, gint unit, gpointer data)
-{
-  return 25;
+  return ok;
 }
 
-
-static gboolean
-geometry_get_row_sensitivity (const GSheetRow *geom, gint unit, gpointer data)
+/* Resize the cases in the casefile, by inserting a value of the
+   given WIDTH into every one of them at the position immediately
+   preceding WHERE.
+*/
+gboolean
+psppire_data_store_insert_value (PsppireDataStore *ds,
+                                 gint width, gint where)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
-
-
-  return (unit < psppire_case_file_get_case_count (ds->case_file));
-}
+  union value value;
 
+  g_return_val_if_fail (ds, FALSE);
 
-static gchar *
-geometry_get_row_button_label (const GSheetRow *geom, gint unit, gpointer data)
-{
-  gchar *text;
-  gchar *s;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
+  g_assert (width >= 0);
 
-  if ( unit >
-       TRAILING_ROWS + psppire_case_file_get_case_count (ds->case_file))
-    return 0;
+  if ( ! ds->datasheet )
+    ds->datasheet = datasheet_create (NULL);
 
-  s = g_strdup_printf (_("%d"), unit);
+  value_init (&value, width);
+  value_set_missing (&value, width);
 
-  text =  pspp_locale_to_utf8 (s, -1, 0);
+  datasheet_insert_column (ds->datasheet, &value, width, where);
+  value_destroy (&value, width);
 
-  g_free (s);
-
-  return text;
+  return TRUE;
 }
 
-
-static void
-psppire_data_store_sheet_row_init (GSheetRowIface *iface)
+gboolean
+psppire_data_store_filtered (PsppireDataStore *ds,
+                             glong row)
 {
-  iface->get_row_count = geometry_get_row_count;
+  union value val;
 
-  iface->get_height = geometry_get_height;
-  iface->set_height = 0;
-  iface->get_visibility = always_true;
-  iface->get_sensitivity = geometry_get_row_sensitivity;
+  const struct dictionary *dict;
+  const struct variable *filter;
 
-  iface->get_button_label = geometry_get_row_button_label;
-}
+  if ( row < 0 || row >= datasheet_get_n_rows (ds->datasheet))
+    return FALSE;
 
+  dict = ds->dict->dict;
+  g_return_val_if_fail (dict, FALSE);
+  filter = dict_get_filter (dict);
+  if ( ! filter)
+    return FALSE;
 
+  g_return_val_if_fail (var_is_numeric (filter), FALSE);
+  value_init (&val, 0);
+  if ( ! datasheet_get_value (ds->datasheet, row,
+                              var_get_case_index (filter),
+                              &val) )
+    return FALSE;
 
+  return (val.f == 0.0);
+}