Remove debug printfs that escaped from my local tree.
[pspp-builds.git] / src / ui / gui / psppire-data-store.c
index 5b40635b66606553f6545f342f56b5c67b6b63d8..b67e27c139024308fecd33da12e0db50beb09d38 100644 (file)
@@ -1,12 +1,9 @@
-/* psppire-data-store.c
-   PSPPIRE --- A Graphical User Interface for PSPP
-   Copyright (C) 2006  Free Software Foundation
-   Written by John Darrington
+/* PSPPIRE - a graphical user interface for PSPP.
+   Copyright (C) 2006, 2008, 2009  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 <minmax.h>
+#include <gettext.h>
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
 
-#include <gtksheet/gtksheet.h>
-#include <gtksheet/gsheetmodel.h>
-#include <gtksheet/gsheet-column-iface.h>
+#include <data/datasheet.h>
+#include <data/data-out.h>
+#include <data/variable.h>
+
+#include <ui/gui/sheet/psppire-sheetmodel.h>
+#include <ui/gui/psppire-marshal.h>
+
+#include <pango/pango-context.h>
 
-#include "psppire-variable.h"
 #include "psppire-data-store.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 <data/file-handle-def.h>
-#include <data/sys-file-writer.h>
+#include <math/sort.h>
+
+#include "xalloc.h"
+#include "xmalloca.h"
 
-#define _(A) A
-#define N_(A) A
 
 
 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_model_init (PsppireSheetModelIface *iface);
+
 static void psppire_data_store_finalize        (GObject           *object);
+static void psppire_data_store_dispose        (GObject           *object);
 
-static const gchar *psppire_data_store_get_string(GSheetModel *sheet_model, gint row, gint column);
+static gboolean psppire_data_store_clear_datum (PsppireSheetModel *model,
+                                         glong row, glong column);
 
-static gboolean psppire_data_store_set_string(GSheetModel *model, 
-                                         const gchar *text, gint row, gint column);
 
-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
+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;
 
-inline GType
+
+enum
+  {
+    BACKEND_CHANGED,
+    CASES_DELETED,
+    CASE_INSERTED,
+    CASE_CHANGED,
+    n_SIGNALS
+  };
+
+static guint signals [n_SIGNALS];
+
+
+GType
 psppire_data_store_get_type (void)
 {
   static GType data_store_type = 0;
@@ -91,31 +112,21 @@ psppire_data_store_get_type (void)
        NULL
       };
 
-      static const GInterfaceInfo sheet_column_info =
-      {
-       (GInterfaceInitFunc) psppire_data_store_sheet_column_init,
-       NULL,
-       NULL
-      };
-
-
 
-      data_store_type = g_type_register_static (G_TYPE_OBJECT, "PsppireDataStore",
+      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,
+                                  PSPPIRE_TYPE_SHEET_MODEL,
                                   &sheet_model_info);
 
-      g_type_add_interface_static (data_store_type,
-                                  G_TYPE_SHEET_COLUMN,
-                                  &sheet_column_info);
-
     }
 
   return data_store_type;
 }
 
+
 static void
 psppire_data_store_class_init (PsppireDataStoreClass *class)
 {
@@ -125,155 +136,277 @@ psppire_data_store_class_init (PsppireDataStoreClass *class)
   object_class = (GObjectClass*) class;
 
   object_class->finalize = psppire_data_store_finalize;
+  object_class->dispose = psppire_data_store_dispose;
+
+  signals [BACKEND_CHANGED] =
+    g_signal_new ("backend-changed",
+                 G_TYPE_FROM_CLASS (class),
+                 G_SIGNAL_RUN_FIRST,
+                 0,
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__VOID,
+                 G_TYPE_NONE,
+                 0);
+
+  signals [CASE_INSERTED] =
+    g_signal_new ("case-inserted",
+                 G_TYPE_FROM_CLASS (class),
+                 G_SIGNAL_RUN_FIRST,
+                 0,
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__INT,
+                 G_TYPE_NONE,
+                 1,
+                 G_TYPE_INT);
+
+
+  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);
+
+  signals [CASES_DELETED] =
+    g_signal_new ("cases-deleted",
+                 G_TYPE_FROM_CLASS (class),
+                 G_SIGNAL_RUN_FIRST,
+                 0,
+                 NULL, NULL,
+                 psppire_marshal_VOID__INT_INT,
+                 G_TYPE_NONE,
+                 2,
+                 G_TYPE_INT,
+                 G_TYPE_INT);
 }
 
 
 
-static gint
-psppire_data_store_get_var_count (const GSheetModel *model)
+static gboolean
+psppire_data_store_insert_value (PsppireDataStore *ds,
+                                 gint width, gint where);
+
+static bool
+psppire_data_store_get_value (const PsppireDataStore *ds,
+                             casenumber casenum, size_t idx,
+                             union value *value);
+
+
+static gboolean
+psppire_data_store_set_value (PsppireDataStore *ds, casenumber casenum,
+                             gint idx, union value *v);
+
+
+
+
+static glong
+psppire_data_store_get_var_count (const PsppireSheetModel *model)
+{
+  const PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
+
+  return psppire_dict_get_var_cnt (store->dict);
+}
+
+casenumber
+psppire_data_store_get_case_count (const PsppireDataStore *store)
 {
-  const PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
-  
-  return psppire_dict_get_var_cnt(store->dict);
+  return datasheet_get_n_rows (store->datasheet);
 }
 
-static gint
-psppire_data_store_get_case_count (const GSheetModel *model)
+size_t
+psppire_data_store_get_value_count (const PsppireDataStore *store)
 {
-  const PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
+  return psppire_dict_get_value_cnt (store->dict);
+}
 
-  return psppire_case_array_get_n_cases(store->cases);
+const struct caseproto *
+psppire_data_store_get_proto (const PsppireDataStore *store)
+{
+  return psppire_dict_get_proto (store->dict);
 }
 
+static casenumber
+psppire_data_store_get_case_count_wrapper (const PsppireSheetModel *model)
+{
+  const PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
+  return psppire_data_store_get_case_count (store);
+}
 
 static void
 psppire_data_store_init (PsppireDataStore *data_store)
 {
   data_store->dict = 0;
-  data_store->cases = 0;
+  data_store->datasheet = NULL;
+  data_store->dispose_has_run = FALSE;
+}
+
+static inline gchar *
+psppire_data_store_get_string_wrapper (const PsppireSheetModel *model, glong row,
+                                      glong column)
+{
+  return psppire_data_store_get_string (PSPPIRE_DATA_STORE (model), row, column);
 }
 
-const PangoFontDescription *
-psppire_data_store_get_font_desc(GSheetModel *model,
-                             gint row, gint column)
+
+static inline gboolean
+psppire_data_store_set_string_wrapper (PsppireSheetModel *model,
+                                      const gchar *text,
+                                      glong row, glong column)
 {
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
-  
-  return store->font_desc;
+  return psppire_data_store_set_string (PSPPIRE_DATA_STORE (model), text,
+                                       row, column);
 }
 
 
+
+static gchar * get_column_subtitle (const PsppireSheetModel *model, gint col);
+static gchar * get_column_button_label (const PsppireSheetModel *model, gint col);
+static gboolean get_column_sensitivity (const PsppireSheetModel *model, gint col);
+static GtkJustification get_column_justification (const PsppireSheetModel *model, gint col);
+
+static gchar * get_row_button_label (const PsppireSheetModel *model, gint row);
+static gboolean get_row_sensitivity (const PsppireSheetModel *model, gint row);
+static gboolean get_row_overstrike (const PsppireSheetModel *model, gint row);
+
+
 static void
-psppire_data_store_sheet_model_init (GSheetModelIface *iface)
+psppire_data_store_sheet_model_init (PsppireSheetModelIface *iface)
 {
   iface->free_strings = TRUE;
-  iface->get_string = psppire_data_store_get_string;
-  iface->set_string = psppire_data_store_set_string;
+  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;
-}
+  iface->get_row_count = psppire_data_store_get_case_count_wrapper;
 
-static
-gboolean always_true()
-{
-  return TRUE;
+  iface->get_column_subtitle = get_column_subtitle;
+  iface->get_column_title = get_column_button_label;
+  iface->get_column_sensitivity = get_column_sensitivity;
+  iface->get_column_justification = get_column_justification;
+
+  iface->get_row_title = get_row_button_label;
+  iface->get_row_sensitivity = get_row_sensitivity;
+  iface->get_row_overstrike = get_row_overstrike;
 }
 
 
+/*
+   A callback which occurs after a variable has been deleted.
+ */
 static void
-delete_cases_callback(GtkWidget *w, gint first, gint n_cases, gpointer data)
+delete_variable_callback (GObject *obj, gint dict_index,
+                         gint case_index, gint width,
+                         gpointer data)
 {
-  PsppireDataStore *store  ;
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
 
-  g_return_if_fail (data);
 
-  store  = PSPPIRE_DATA_STORE(data);
+  psppire_sheet_model_columns_deleted (PSPPIRE_SHEET_MODEL (store), dict_index, 1);
+  datasheet_delete_columns (store->datasheet, case_index, 1);
+  datasheet_insert_column (store->datasheet, NULL, -1, case_index);
+#if AXIS_TRANSITION
 
-  g_assert(first >= 0);
 
-  g_sheet_model_rows_deleted (G_SHEET_MODEL(store), first, n_cases);
+  psppire_sheet_column_columns_changed (PSPPIRE_SHEET_COLUMN (store),
+                                  dict_index, -1);
+#endif
 }
 
-
 static void
-insert_case_callback(GtkWidget *w, gint casenum, gpointer data)
+variable_changed_callback (GObject *obj, gint var_num, gpointer data)
 {
-  PsppireDataStore *store  ;
+#if AXIS_TRANSITION
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
 
-  g_return_if_fail (data);
+  psppire_sheet_column_columns_changed (PSPPIRE_SHEET_COLUMN (store),
+                                 var_num, 1);
 
-  store  = PSPPIRE_DATA_STORE(data);
-  
-  g_sheet_model_range_changed (G_SHEET_MODEL(store),
-                              casenum, -1,
-                              psppire_case_array_get_n_cases(store->cases),
-                              -1);
+  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (store),
+                              -1, var_num,
+                              -1, var_num);
+#endif
 }
 
-
 static void
-changed_case_callback(GtkWidget *w, gint casenum, 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);
-  
-  g_sheet_model_range_changed (G_SHEET_MODEL(store),
-                                casenum, -1,
-                                casenum, -1);
+  store  = PSPPIRE_DATA_STORE (data);
 
-}
+  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);
 
+#if AXIS_TRANSITION
 
-static void
-delete_variables_callback(GObject *obj, gint var_num, gint n_vars, gpointer data)
-{
-  PsppireDataStore *store ;
+  psppire_sheet_column_columns_changed (PSPPIRE_SHEET_COLUMN (store),
+                                 var_num, 1);
+#endif
 
-  g_return_if_fail (data);
+  psppire_sheet_model_columns_inserted (PSPPIRE_SHEET_MODEL (store), var_num, 1);
+}
 
-  store  = PSPPIRE_DATA_STORE(data);
+struct resize_datum_aux
+  {
+    int old_width;
+    int new_width;
+  };
 
-  g_sheet_column_columns_deleted(G_SHEET_COLUMN(store),
-                                  var_num, n_vars);
 
-  g_sheet_model_columns_deleted (G_SHEET_MODEL(store), var_num, n_vars);
-}
+void
+resize_datum (const union value *old, union value *new, void *aux_)
+{
+  struct resize_datum_aux *aux = aux_;
 
+  if (aux->new_width == 0)
+    {
+      /* FIXME: try to parse string as number. */
+      new->f = SYSMIS;
+    }
+  else if (aux->old_width == 0)
+    {
+      /* FIXME: format number as string. */
+      value_set_missing (new, aux->new_width);
+    }
+  else
+    value_copy_rpad (new, aux->new_width, old, aux->old_width, ' ');
+}
 
 static void
-insert_variable_callback(GObject *obj, gint var_num, gpointer data)
+dict_size_change_callback (GObject *obj,
+                         gint var_num, gint old_width, gpointer data)
 {
-  PsppireDataStore *store;
-
-  g_return_if_fail (data);
-
-  store  = PSPPIRE_DATA_STORE(data);
-  
-  /* 
-  g_sheet_model_range_changed (G_SHEET_MODEL(store),
-                                casenum, -1,
-                                psppire_case_array_get_n_cases(store->cases),
-                                -1);
-  */
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
+  struct variable *variable;
+  int posn;
 
-  psppire_case_array_resize(store->cases, 
-                        dict_get_next_value_idx (store->dict->dict));
+  variable = psppire_dict_get_variable (store->dict, var_num);
+  posn = var_get_case_index (variable);
 
-  g_sheet_model_columns_inserted (G_SHEET_MODEL(store), var_num, 1);
+  if (old_width != var_get_width (variable))
+    {
+      struct resize_datum_aux aux;
+      aux.old_width = old_width;
+      aux.new_width = var_get_width (variable);
+      datasheet_resize_column (store->datasheet, posn, aux.new_width,
+                               resize_datum, &aux);
+    }
 }
 
 
 
-
 /**
  * psppire_data_store_new:
  * @dict: The dictionary for this data_store.
@@ -282,31 +415,45 @@ insert_variable_callback(GObject *obj, gint var_num, gpointer data)
  * Return value: a new #PsppireDataStore
  **/
 PsppireDataStore *
-psppire_data_store_new (PsppireDict *dict, PsppireCaseArray *cases)
+psppire_data_store_new (PsppireDict *dict)
 {
   PsppireDataStore *retval;
 
   retval = g_object_new (GTK_TYPE_DATA_STORE, NULL);
 
-  retval->cases = cases;
-  g_signal_connect(cases, "cases-deleted", G_CALLBACK(delete_cases_callback), 
-                  retval);
+  psppire_data_store_set_dictionary (retval, dict);
 
-  g_signal_connect(cases, "case-inserted", G_CALLBACK(insert_case_callback), 
-                  retval);
+  return retval;
+}
 
+void
+psppire_data_store_set_reader (PsppireDataStore *ds,
+                              struct casereader *reader)
+{
+  gint i;
 
-  g_signal_connect(cases, "case-changed", G_CALLBACK(changed_case_callback), 
-                  retval);
+  if ( ds->datasheet)
+    datasheet_destroy (ds->datasheet);
 
-  psppire_data_store_set_dictionary(retval, dict);
+  ds->datasheet = datasheet_create (reader);
 
+  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (ds),
+                              -1, -1, -1, -1);
 
-  return retval;
+  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_emit (ds, signals[BACKEND_CHANGED], 0);
 }
 
 
-
 /**
  * psppire_data_store_replace_set_dictionary:
  * @data_store: The variable store
@@ -316,28 +463,62 @@ psppire_data_store_new (PsppireDict *dict, PsppireCaseArray *cases)
  * destroyed.
  **/
 void
-psppire_data_store_set_dictionary(PsppireDataStore *data_store, PsppireDict *dict)
+psppire_data_store_set_dictionary (PsppireDataStore *data_store, PsppireDict *dict)
 {
-#if 0
-  if ( data_store->dict ) g_object_unref(data_store->dict);
-#endif
+  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;
 
-  psppire_case_array_resize(data_store->cases, 
-                        dict_get_next_value_idx (data_store->dict->dict));
+  if ( dict != NULL)
+    {
 
+      data_store->dict_handler_id [VARIABLE_INSERTED] =
+       g_signal_connect (dict, "variable-inserted",
+                         G_CALLBACK (insert_variable_callback),
+                         data_store);
+
+      data_store->dict_handler_id [VARIABLE_DELETED] =
+       g_signal_connect (dict, "variable-deleted",
+                         G_CALLBACK (delete_variable_callback),
+                         data_store);
+
+      data_store->dict_handler_id [VARIABLE_CHANGED] =
+       g_signal_connect (dict, "variable-changed",
+                         G_CALLBACK (variable_changed_callback),
+                         data_store);
+
+      data_store->dict_handler_id [SIZE_CHANGED] =
+       g_signal_connect (dict, "dict-size-changed",
+                         G_CALLBACK (dict_size_change_callback),
+                         data_store);
+    }
 
-  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);
 
   /* The entire model has changed */
-  g_sheet_model_range_changed (G_SHEET_MODEL(data_store), -1, -1, -1, -1);
+  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (data_store), -1, -1, -1, -1);
+
+#if AXIS_TRANSITION
+  psppire_sheet_column_columns_changed (PSPPIRE_SHEET_COLUMN (data_store), 0, -1);
+#endif
+
+  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
@@ -349,357 +530,521 @@ psppire_data_store_finalize (GObject *object)
 }
 
 
-static const gchar *
-psppire_data_store_get_string(GSheetModel *model, gint row, gint column)
+static void
+psppire_data_store_dispose (GObject *object)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (object);
+
+  if (ds->dispose_has_run)
+    return;
+
+  if (ds->datasheet)
+    {
+      datasheet_destroy (ds->datasheet);
+      ds->datasheet = 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, casenumber posn)
 {
-  const char *text;
+  gboolean result;
+  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);
+
+  cc = case_create (proto);
+  case_set_missing (cc);
+
+  result = psppire_data_store_insert_case (ds, cc, posn);
+
+  case_unref (cc);
+
+  return result;
+}
+
+
+gchar *
+psppire_data_store_get_string (PsppireDataStore *store, glong row, glong column)
+{
+  gint idx;
+  char *text;
   const struct fmt_spec *fp ;
-  const struct PsppireVariable *pv ;
-  const union value *v ;
+  const struct variable *pv ;
+  union value v;
+  int width;
   GString *s;
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
 
-  g_return_val_if_fail(store->dict, NULL);
-  g_return_val_if_fail(store->cases, NULL);
+  g_return_val_if_fail (store->dict, NULL);
+  g_return_val_if_fail (store->datasheet, NULL);
 
-  if (column >= psppire_dict_get_var_cnt(store->dict))
+  if (column >= psppire_dict_get_var_cnt (store->dict))
     return NULL;
 
-  if ( row >= psppire_case_array_get_n_cases(store->cases))
+  if ( row >= psppire_data_store_get_case_count (store))
     return NULL;
 
+  pv = psppire_dict_get_variable (store->dict, column);
 
-  pv = psppire_dict_get_variable(store->dict, column);
+  g_assert (pv);
 
-  v =  psppire_case_array_get_value(store->cases, row, 
-                             psppire_variable_get_index(pv));
+  idx = var_get_case_index (pv);
+  width = var_get_width (pv);
 
-  if ( store->show_labels) 
-    {
-      const struct val_labs * vl = psppire_variable_get_value_labels(pv);
+  g_assert (idx >= 0);
 
-      const gchar *label;
-      if ( (label = val_labs_find(vl, *v)) )
-       {
-         return pspp_locale_to_utf8(label, -1, 0);
-       }
+  value_init (&v, width);
+  if (!psppire_data_store_get_value (store, row, idx, &v))
+    return NULL;
+
+  if ( store->show_labels)
+    {
+      const gchar *label = var_lookup_value_label (pv, &v);
+      if (label)
+        {
+          value_destroy (&v, width);
+         return recode_string (UTF8, psppire_dict_encoding (store->dict),
+                               label, -1);
+        }
     }
 
-  fp = psppire_variable_get_write_spec(pv);
+  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_string_set_size (s, fp->w);
+
+  memset (s->str, 0, fp->w);
+
+  g_assert (fp->w == s->len);
 
-  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 (s->str, fp, v);
+  data_out (&v, fp, s->str);
+
+  text = recode_string (UTF8, psppire_dict_encoding (store->dict),
+                       s->str, fp->w);
+  g_string_free (s, TRUE);
 
-  
-  text = pspp_locale_to_utf8(s->str, fp->w, 0);
-  g_string_free(s, TRUE);
+  g_strchomp (text);
 
+  value_destroy (&v, width);
   return text;
 }
 
 
 static gboolean
-set_null_string_value(union value *val, gpointer data)
-{
-  strcpy(val->s, "");
-  return TRUE;
-}
-
-static gboolean
-set_sysmis_value(union value *val, gpointer data)
+psppire_data_store_clear_datum (PsppireSheetModel *model,
+                                         glong row, glong col)
 {
-  val->f = SYSMIS;
-  return TRUE;
-}
+  PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
 
+  union value v;
+  const struct variable *pv = psppire_dict_get_variable (store->dict, col);
+  int width = var_get_width (pv);
 
-static gboolean 
-psppire_data_store_clear_datum(GSheetModel *model, 
-                                         gint row, gint col)
+  const gint index = var_get_case_index (pv) ;
 
-{
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
+  value_init (&v, width);
+  value_set_missing (&v, width);
+  psppire_data_store_set_value (store, row, index, &v);
+  value_destroy (&v, width);
 
-  const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
+  psppire_sheet_model_range_changed (model, row, col, row, col);
 
-  const gint index = psppire_variable_get_index(pv) ;
 
-  if ( psppire_variable_get_type(pv) == NUMERIC) 
-    psppire_case_array_set_value(store->cases, row, index, set_sysmis_value,0);
-  else
-    psppire_case_array_set_value(store->cases, row, index, set_null_string_value,0);
   return TRUE;
 }
 
 
-static gboolean
-fillit(union value *val, gpointer data)
-{
-  struct data_in *d_in = data;
-
-  d_in->v = val;
-
-  if ( ! data_in(d_in) ) 
-    {
-      g_warning("Cant encode string\n");
-      return FALSE;
-    }
-
-  return TRUE;
-}
-
-
-/* Attempts to update that part of the variable store which corresponds 
+/* 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.
 */
-static gboolean 
-psppire_data_store_set_string(GSheetModel *model, 
-                         const gchar *text, gint row, gint col)
+gboolean
+psppire_data_store_set_string (PsppireDataStore *store,
+                              const gchar *text, glong row, glong col)
 {
-  gint r;
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
+  gchar *s;
+  glong n_cases;
+  const struct variable *pv = psppire_dict_get_variable (store->dict, col);
+  if ( NULL == pv)
+    return FALSE;
 
-  const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
-  g_return_val_if_fail(pv, FALSE);
+  n_cases = psppire_data_store_get_case_count (store);
 
-  for(r = psppire_case_array_get_n_cases(store->cases) ; r <= row ; ++r ) 
-    {
-      gint c;
-      psppire_case_array_insert_case(store->cases, r, 0, 0);
+  if ( row > n_cases)
+    return FALSE;
 
-      for (c = 0 ; c < psppire_dict_get_var_cnt(store->dict); ++c ) 
-       psppire_data_store_clear_datum(model, r, c);
-    }
+  if (row == n_cases)
+    psppire_data_store_insert_new_case (store, row);
 
-  {
-    const gint index = psppire_variable_get_index(pv);
+  s = recode_string (psppire_dict_encoding (store->dict), UTF8, text, -1);
 
-    struct data_in d_in;
-    d_in.s = text;
-    d_in.e = text + strlen(text);
-    d_in.v = 0;
-    d_in.f1 = d_in.f2 = 0;
-    d_in.format = * psppire_variable_get_write_spec(pv);
-    d_in.flags = 0;
+  psppire_data_store_data_in (store, row,
+                             var_get_case_index (pv), ss_cstr (s),
+                             var_get_write_format (pv));
+  free (s);
 
-    psppire_case_array_set_value(store->cases, row, index, fillit, &d_in);
-  }
+  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (store), row, col, row, col);
 
   return TRUE;
 }
 
 
+
 void
-psppire_data_store_set_font(PsppireDataStore *store, PangoFontDescription *fd)
+psppire_data_store_show_labels (PsppireDataStore *store, gboolean show_labels)
 {
   g_return_if_fail (store);
   g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
 
-  store->font_desc = fd;
-  g_sheet_model_range_changed (G_SHEET_MODEL(store),
+  store->show_labels = show_labels;
+
+  psppire_sheet_model_range_changed (PSPPIRE_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));
+  datasheet_destroy (ds->datasheet);
+  ds->datasheet = NULL;
 
-  store->show_labels = show_labels;
+  psppire_dict_clear (ds->dict);
 
-  g_sheet_model_range_changed (G_SHEET_MODEL(store),
-                                -1, -1, -1, -1);
+  g_signal_emit (ds, signals [CASES_DELETED], 0, 0, -1);
 }
 
 
 
-static gboolean 
-write_case(const struct ccase *cc, 
-          gpointer aux)
+/* Return a casereader made from this datastore */
+struct casereader *
+psppire_data_store_get_reader (PsppireDataStore *ds)
 {
-  struct sfm_writer *writer = aux;
+  int i;
+  struct casereader *reader ;
 
-  if ( ! sfm_write_case(writer, cc) )
-    return FALSE;
+  if ( ds->dict )
+    for (i = 0 ; i < n_dict_signals; ++i )
+      {
+       g_signal_handler_block (ds->dict,
+                               ds->dict_handler_id[i]);
+      }
 
+  reader = datasheet_make_reader (ds->datasheet);
 
-  return TRUE;
+  /* We must not reference this again */
+  ds->datasheet = NULL;
+
+  return reader;
 }
 
-void
-psppire_data_store_create_system_file(PsppireDataStore *store,
-                             struct file_handle *handle)
+
+
+/* Column related funcs */
+
+
+static const gchar null_var_name[]=N_("var");
+
+\f
+
+/* Row related funcs */
+
+static gchar *
+get_row_button_label (const PsppireSheetModel *model, gint unit)
 {
-  const struct sfm_write_options wo = {
-    true, /* writeable */
-    false, /* dont compress */
-    3 /* version */
-  }; 
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
+  gchar *s = g_strdup_printf (_("%d"), unit + FIRST_CASE_NUMBER);
 
-  struct sfm_writer *writer ;
+  gchar *text =  recode_string (UTF8, psppire_dict_encoding (ds->dict),
+                               s, -1);
 
-  g_assert(handle);
+  g_free (s);
 
-  writer = sfm_open_writer(handle, store->dict->dict, wo);
+  return text;
+}
 
-  if ( ! writer) 
-    return;
 
-  psppire_case_array_iterate_case(store->cases, write_case, writer);
+static gboolean
+get_row_sensitivity (const PsppireSheetModel *model, gint unit)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
 
-  sfm_close_writer(writer);
+  return (unit < psppire_data_store_get_case_count (ds));
 }
 
 
+\f
 
-/* Column related funcs */
+/* Column related stuff */
 
-static gint
-geometry_get_column_count(const GSheetColumn *geom)
+static gchar *
+get_column_subtitle (const PsppireSheetModel *model, gint col)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  gchar *text;
+  const struct variable *v ;
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
 
-  return MAX(MIN_COLUMNS, psppire_dict_get_var_cnt(ds->dict));
+  if ( col >= psppire_dict_get_var_cnt (ds->dict) )
+    return NULL;
+
+  v = psppire_dict_get_variable (ds->dict, col);
+
+  if ( ! var_has_label (v))
+    return NULL;
+
+  text =  recode_string (UTF8, psppire_dict_encoding (ds->dict),
+                        var_get_label (v), -1);
+
+  return text;
 }
 
-/* Return the width that an  'M' character would occupy when typeset at
-   row, col */
-static guint 
-M_width(GtkSheet *sheet, gint row, gint col)
+static gchar *
+get_column_button_label (const PsppireSheetModel *model, gint col)
 {
-  GtkSheetCellAttr attributes;
-  PangoRectangle rect;
-  /* FIXME: make this a member of the data store */
-  static PangoLayout *layout = 0;
+  gchar *text;
+  struct variable *pv ;
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
 
-  gtk_sheet_get_attributes(sheet, row, col, &attributes);
+  if ( col >= psppire_dict_get_var_cnt (ds->dict) )
+    return g_locale_to_utf8 (null_var_name, -1, 0, 0, 0);
 
-  if (! layout ) 
-    layout = gtk_widget_create_pango_layout (GTK_WIDGET(sheet), "M");
+  pv = psppire_dict_get_variable (ds->dict, col);
 
-  g_assert(layout);
-  
-  pango_layout_set_font_description (layout, 
-                                    attributes.font_desc);
+  text = recode_string (UTF8, psppire_dict_encoding (ds->dict),
+                       var_get_name (pv), -1);
 
-  pango_layout_get_extents (layout, NULL, &rect);
+  return text;
+}
 
-#if 0
-  g_object_unref(G_OBJECT(layout));
-#endif
+static gboolean
+get_column_sensitivity (const PsppireSheetModel *model, gint col)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
 
-  return PANGO_PIXELS(rect.width);
+  return (col < psppire_dict_get_var_cnt (ds->dict));
 }
 
 
-/* Return the number of pixels corresponding to a column of 
-   WIDTH characters */
-static inline guint 
-columnWidthToPixels(GtkSheet *sheet, gint column, guint width)
+
+static GtkJustification
+get_column_justification (const PsppireSheetModel *model, gint col)
 {
-  return (M_width(sheet, 0, column) * width);
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
+  const struct variable *pv ;
+
+  if ( col >= psppire_dict_get_var_cnt (ds->dict) )
+    return GTK_JUSTIFY_LEFT;
+
+  pv = psppire_dict_get_variable (ds->dict, col);
+
+  return (var_get_alignment (pv) == ALIGN_LEFT ? GTK_JUSTIFY_LEFT
+          : var_get_alignment (pv) == ALIGN_RIGHT ? GTK_JUSTIFY_RIGHT
+          : GTK_JUSTIFY_CENTER);
 }
 
 
-static gint
-geometry_get_width(const GSheetColumn *geom, gint unit, GtkSheet *sheet)
-{
-  const struct PsppireVariable *pv ;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
 
-  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
-    return 75;
+\f
+
 
-  /* FIXME: We can optimise this by caching the widths until they're resized */
-  pv = psppire_dict_get_variable(ds->dict, unit);
+/* Returns the CASENUMth case, or a null pointer on failure.
+ */
+struct ccase *
+psppire_data_store_get_case (const PsppireDataStore *ds,
+                            casenumber casenum)
+{
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
 
-  return columnWidthToPixels(sheet, unit, psppire_variable_get_columns(pv));
+  return datasheet_get_row (ds->datasheet, casenum);
 }
 
 
+gboolean
+psppire_data_store_delete_cases (PsppireDataStore *ds, casenumber first,
+                                casenumber n_cases)
+{
+  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);
 
-static void
-geometry_set_width(GSheetColumn *geom, gint unit, gint width, GtkSheet *sheet)
-{
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
 
-  struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
+  datasheet_delete_rows (ds->datasheet, first, n_cases);
 
-  psppire_variable_set_columns(pv, width / M_width(sheet, 1, unit));
+  g_signal_emit (ds, signals [CASES_DELETED], 0, first, n_cases);
+  psppire_sheet_model_rows_deleted (PSPPIRE_SHEET_MODEL (ds), first, n_cases);
+
+  return TRUE;
 }
 
 
 
-static GtkJustification
-geometry_get_justification(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)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
-  const struct PsppireVariable *pv ;
+  bool result ;
 
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
 
-  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
-    return GTK_JUSTIFY_LEFT;
+  case_ref (cc);
+  result = datasheet_insert_rows (ds->datasheet, posn, &cc, 1);
 
-  pv = psppire_dict_get_variable(ds->dict, unit);
+  if ( result )
+    {
+      g_signal_emit (ds, signals [CASE_INSERTED], 0, posn);
+      psppire_sheet_model_rows_inserted (PSPPIRE_SHEET_MODEL (ds), posn, 1);
+    }
+  else
+    g_warning ("Cannot insert case at position %ld\n", posn);
 
-  /* Kludge: Happily GtkJustification is defined similarly
-     to enum alignment from pspp/variable.h */
-  return psppire_variable_get_alignment(pv);
+  return result;
 }
 
 
-static const gchar null_var_name[]=_("var");
-static const gchar *
-geometry_get_button_label(const GSheetColumn *geom, gint unit)
+/* Copies the IDXth value from case CASENUM into VALUE, which
+   must be of the correct width for IDX.
+   Returns true if successful, false on failure. */
+static bool
+psppire_data_store_get_value (const PsppireDataStore *ds,
+                             casenumber casenum, size_t idx,
+                             union value *value)
 {
-  const gchar *text;
-  struct PsppireVariable *pv ;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  g_return_val_if_fail (ds, false);
+  g_return_val_if_fail (ds->datasheet, false);
+  g_return_val_if_fail (idx < datasheet_get_n_columns (ds->datasheet), false);
 
-  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
-    return pspp_locale_to_utf8(null_var_name, -1, 0);
+  return datasheet_get_value (ds->datasheet, casenum, idx, value);
+}
 
-  pv = psppire_dict_get_variable(ds->dict, unit);
 
-  text =  pspp_locale_to_utf8(psppire_variable_get_name(pv), -1, 0);
 
-  return text;
+/* Set the IDXth value of case C to V.
+   V must be the correct width for IDX.
+   Returns true if successful, false on I/O error. */
+static gboolean
+psppire_data_store_set_value (PsppireDataStore *ds, casenumber casenum,
+                             gint idx, union value *v)
+{
+  bool ok;
+
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
+
+  g_return_val_if_fail (idx < datasheet_get_n_columns (ds->datasheet), FALSE);
+
+  ok = datasheet_put_value (ds->datasheet, casenum, idx, v);
+  if (ok)
+    g_signal_emit (ds, signals [CASE_CHANGED], 0, casenum);
+
+  return ok;
 }
 
 
+
+
+/* Set the IDXth value of case C using D_IN */
 static gboolean
-geometry_get_sensitivity(const GSheetColumn *geom, gint unit)
+psppire_data_store_data_in (PsppireDataStore *ds, casenumber casenum, gint idx,
+                           struct substring input, const struct fmt_spec *fmt)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  union value value;
+  int width;
+  bool ok;
+
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
 
+  g_return_val_if_fail (idx < datasheet_get_n_columns (ds->datasheet), FALSE);
 
-  return (unit < psppire_dict_get_var_cnt(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 (input, LEGACY_NATIVE, fmt->type, 0, 0, 0, &value, width)
+        && datasheet_put_value (ds->datasheet, casenum, idx, &value));
+  value_destroy (&value, width);
+
+  if (ok)
+    g_signal_emit (ds, signals [CASE_CHANGED], 0, casenum);
+
+  return ok;
 }
 
+/* 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.
+*/
+static gboolean
+psppire_data_store_insert_value (PsppireDataStore *ds,
+                                 gint width, gint where)
+{
+  union value value;
 
-static void
-psppire_data_store_sheet_column_init (GSheetColumnIface *iface)
+  g_return_val_if_fail (ds, FALSE);
+
+  g_assert (width >= 0);
+
+  if ( ! ds->datasheet )
+    ds->datasheet = datasheet_create (NULL);
+
+  value_init (&value, width);
+  if (width == 0)
+    value.f = 0;
+  else
+    value_set_missing (&value, width);
+
+  datasheet_insert_column (ds->datasheet, &value, width, where);
+
+  return TRUE;
+}
+
+static gboolean
+get_row_overstrike (const PsppireSheetModel *model, gint row)
 {
-  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;
+  union value val;
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
+
+  const struct dictionary *dict = ds->dict->dict;
+
+  const struct variable *filter = dict_get_filter (dict);
+
+  if ( row < 0 || row >= datasheet_get_n_rows (ds->datasheet))
+    return FALSE;
+
+  if ( ! filter)
+    return FALSE;
+
+  g_assert (var_is_numeric (filter));
+
+  value_init (&val, 0);
+  if ( ! datasheet_get_value (ds->datasheet, row,
+                             var_get_case_index (filter),
+                             &val) )
+    return FALSE;
+
 
-  iface->get_button_label = geometry_get_button_label;
+  return (val.f == 0.0);
 }