Avoid crash when attempting to open files with invalid encoding
[pspp] / src / ui / gui / psppire-data-store.c
index 7aa0ff339c94409e7b961c1e63aac61fd3d8e849..8832e5175e73160989175ff255be8c9dc73af7c7 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, 2010, 2011, 2012, 2013  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
 
-#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/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 <math/sort.h>
 
-#include <data/file-handle-def.h>
-#include <data/sys-file-writer.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_finalize        (GObject           *object);
+static void psppire_data_store_dispose        (GObject           *object);
 
-static const gchar *const psppire_data_store_get_string(GSheetModel *sheet_model, gint row, gint column);
+static gboolean psppire_data_store_insert_case (PsppireDataStore *ds,
+                                               struct ccase *cc,
+                                               casenumber posn);
 
-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_data_in (PsppireDataStore *ds,
+                                           casenumber casenum, gint idx,
+                                           struct substring input,
+                                           const struct fmt_spec *fmt);
 
+static GObjectClass *parent_class = NULL;
 
-#define MIN_COLUMNS 10
 
-#define max(A,B) ((A>B)?A:B)
+enum
+  {
+    BACKEND_CHANGED,
+    CASES_DELETED,
+    CASE_INSERTED,
+    CASE_CHANGED,
+    n_SIGNALS
+  };
+
+static guint signals [n_SIGNALS];
 
-static GObjectClass *parent_class = NULL;
 
-inline GType
+GType
 psppire_data_store_get_type (void)
 {
   static GType data_store_type = 0;
@@ -83,38 +97,15 @@ psppire_data_store_get_type (void)
         (GInstanceInitFunc) psppire_data_store_init,
       };
 
-      static const GInterfaceInfo sheet_model_info =
-      {
-       (GInterfaceInitFunc) psppire_data_store_sheet_model_init,
-       NULL,
-       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,
-                                  &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)
 {
@@ -124,131 +115,159 @@ 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 void
-psppire_data_store_init (PsppireDataStore *data_store)
-{
-  data_store->dict = 0;
-  data_store->cases = 0;
-}
 
-const PangoFontDescription *
-psppire_data_store_get_font_desc(GSheetModel *model,
-                             gint row, gint column)
+static gboolean
+psppire_data_store_insert_value (PsppireDataStore *ds,
+                                 gint width, gint where);
+
+casenumber
+psppire_data_store_get_case_count (const PsppireDataStore *store)
 {
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
-  
-  return store->font_desc;
+  return datasheet_get_n_rows (store->datasheet);
 }
 
-
-static void
-psppire_data_store_sheet_model_init (GSheetModelIface *iface)
+size_t
+psppire_data_store_get_value_count (const PsppireDataStore *store)
 {
-  iface->get_string = psppire_data_store_get_string;
-  iface->set_string = psppire_data_store_set_string;
-  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;
+  return psppire_dict_get_value_cnt (store->dict);
 }
 
-static
-gboolean always_true()
+const struct caseproto *
+psppire_data_store_get_proto (const PsppireDataStore *store)
 {
-  return TRUE;
+  return psppire_dict_get_proto (store->dict);
 }
 
-
-
 static void
-delete_cases_callback(GtkWidget *w, gint first, gint n_cases, gpointer data)
+psppire_data_store_init (PsppireDataStore *data_store)
 {
-  PsppireDataStore *store  ;
-
-  g_return_if_fail (data);
-
-  store  = PSPPIRE_DATA_STORE(data);
-
-  g_assert(first >= 0);
-
-  g_sheet_model_rows_deleted (G_SHEET_MODEL(store), first, n_cases);
+  data_store->dict = NULL;
+  data_store->datasheet = NULL;
+  data_store->dispose_has_run = FALSE;
 }
 
-
+/*
+   A callback which occurs after a variable has been deleted.
+ */
 static void
-insert_case_callback(GtkWidget *w, gint casenum, gpointer data)
+delete_variable_callback (GObject *obj, const struct variable *var UNUSED,
+                          gint dict_index, gint case_index,
+                          gpointer data)
 {
-  PsppireDataStore *store  ;
-  g_return_if_fail (data);
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
+
+  g_return_if_fail (store->datasheet);
 
-  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);
+  datasheet_delete_columns (store->datasheet, case_index, 1);
+  datasheet_insert_column (store->datasheet, NULL, -1, case_index);
 }
 
+struct resize_datum_aux
+  {
+    const struct dictionary *dict;
+    const struct variable *new_variable;
+    const struct variable *old_variable;
+  };
 
 static void
-changed_case_callback(GtkWidget *w, gint casenum, gpointer data)
+resize_datum (const union value *old, union value *new, const void *aux_)
 {
-  PsppireDataStore *store  ;
-  g_return_if_fail (data);
-
-  store  = PSPPIRE_DATA_STORE(data);
-  
-  g_sheet_model_range_changed (G_SHEET_MODEL(store),
-                                casenum, -1,
-                                casenum, -1);
-
+  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);
 }
 
-
 static void
-delete_variables_callback(GtkWidget *w, gint var_num, gint n_vars, gpointer data)
+variable_changed_callback (GObject *obj, gint var_num, guint what, const struct variable *oldvar,
+                          gpointer data)
 {
-  PsppireDataStore *store ;
-
-  g_return_if_fail (data);
-
-  store  = PSPPIRE_DATA_STORE(data);
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
+  struct variable *variable = psppire_dict_get_variable (store->dict, var_num);
 
-  g_sheet_column_columns_deleted(G_SHEET_COLUMN(store),
-                                  var_num, n_vars);
+  if (what & VAR_TRAIT_WIDTH)
+    {
+      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);
+    }
 }
 
-
 static void
-insert_variable_callback(GtkWidget *w, gint var_num, gpointer data)
+insert_variable_callback (GObject *obj, gint var_num, gpointer data)
 {
+  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,
-                                psppire_case_array_get_n_cases(store->cases),
-                                -1);
-  */
-
-  psppire_case_array_resize(store->cases, 
-                        dict_get_next_value_idx (store->dict->dict));
+  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);
 }
 
-
-
-
 /**
  * psppire_data_store_new:
  * @dict: The dictionary for this data_store.
@@ -257,32 +276,42 @@ insert_variable_callback(GtkWidget *w, 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);
+  retval = g_object_new (PSPPIRE_TYPE_DATA_STORE, NULL);
 
-  g_signal_connect(cases, "case-inserted", G_CALLBACK(insert_case_callback), 
-                  retval);
+  psppire_data_store_set_dictionary (retval, dict);
 
+  return retval;
+}
 
-  g_signal_connect(cases, "case-changed", G_CALLBACK(changed_case_callback), 
-                  retval);
+void
+psppire_data_store_set_reader (PsppireDataStore *ds,
+                              struct casereader *reader)
+{
+  gint i;
 
+  if ( ds->datasheet)
+    datasheet_destroy (ds->datasheet);
 
-  psppire_data_store_set_dictionary(retval, dict);
+  ds->datasheet = datasheet_create (reader);
 
+  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]);
+         }
+      }
 
-  return retval;
+  g_signal_emit (ds, signals[BACKEND_CHANGED], 0);
 }
 
 
-
 /**
  * psppire_data_store_replace_set_dictionary:
  * @data_store: The variable store
@@ -292,403 +321,407 @@ 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);
 
-  g_signal_connect(dict, "variable-inserted", 
-                  G_CALLBACK(insert_variable_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, "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);
+
+  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 const gchar *const 
-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);
 
-  const struct fmt_spec *fp ;
-  const struct PsppireVariable *pv ;
-  const union value *v ;
-  GString *s;
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
+  if (ds->dispose_has_run)
+    return;
 
-  g_return_val_if_fail(store->dict, NULL);
-  g_return_val_if_fail(store->cases, NULL);
+  psppire_data_store_set_dictionary (ds, NULL);
 
-  if (column >= psppire_dict_get_var_cnt(store->dict))
-    return NULL;
+  /* must chain up */
+  (* parent_class->dispose) (object);
 
-  if ( row >= psppire_case_array_get_n_cases(store->cases))
-    return NULL;
+  ds->dispose_has_run = TRUE;
+}
 
 
-  pv = psppire_dict_get_variable(store->dict, column);
 
-  v =  psppire_case_array_get_value(store->cases, row, 
-                             psppire_variable_get_index(pv));
+/* Insert a blank case before POSN */
+gboolean
+psppire_data_store_insert_new_case (PsppireDataStore *ds, casenumber posn)
+{
+  gboolean result;
+  const struct caseproto *proto;
+  struct ccase *cc;
+  g_return_val_if_fail (ds, FALSE);
 
-  if ( store->show_labels) 
-    {
-      const struct val_labs * vl = psppire_variable_get_value_labels(pv);
+  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);
 
-      const gchar *label;
-      if ( (label = val_labs_find(vl, *v)) )
-       {
-         return label;
-       }
-    }
+  cc = case_create (proto);
+  case_set_missing (cc);
 
-  fp = psppire_variable_get_write_spec(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 (s->str, fp, v);
-  
-  return g_string_free(s, FALSE);
-#if 0
-  {
-    static gchar buf[255];
-    GError *err = NULL;
-    gchar *text = g_locale_to_utf8(s, fp->w, 0, 0, &err);
-    if ( !err ) 
-      { 
-       g_snprintf(buf, 255, text);
-       g_free(text);
-      }
-    else
-      {
-       g_warning("Cannot convert string \"%s\" to utf-8: %s\n", s, err->message);
-       g_error_free(err);
-       return NULL;
-      }
-
-  return buf ;
-  }
-#endif
-}
+  result = psppire_data_store_insert_case (ds, cc, posn);
 
+  case_unref (cc);
 
-static gboolean
-set_null_string_value(union value *val, gpointer data)
-{
-  strcpy(val->s, "");
-  return TRUE;
+  return result;
 }
 
-static gboolean
-set_sysmis_value(union value *val, gpointer data)
+gchar *
+psppire_data_store_get_string (PsppireDataStore *store,
+                               glong row, const struct variable *var,
+                               bool use_value_label)
 {
-  val->f = SYSMIS;
-  return TRUE;
-}
+  gchar *string;
+  union value v;
+  int width;
 
+  g_return_val_if_fail (store != NULL, NULL);
+  g_return_val_if_fail (store->datasheet != NULL, NULL);
+  g_return_val_if_fail (var != NULL, NULL);
 
-static gboolean 
-psppire_data_store_clear_datum(GSheetModel *model, 
-                                         gint row, gint col)
+  if (row < 0 || row >= datasheet_get_n_rows (store->datasheet))
+    return NULL;
 
-{
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
+  width = var_get_width (var);
+  value_init (&v, width);
+  datasheet_get_value (store->datasheet, row, var_get_case_index (var), &v);
 
-  const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
+  string = NULL;
+  if (use_value_label)
+    {
+      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);
 
-  const gint index = psppire_variable_get_index(pv) ;
+  value_destroy (&v, width);
 
-  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;
+  return string;
 }
 
 
-static gboolean
-fillit(union value *val, gpointer data)
+/* Attempts to update that part of the variable store which corresponds to VAR
+   within ROW with the value TEXT.
+
+   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.
+
+   Returns true if anything was updated, false otherwise.  */
+gboolean
+psppire_data_store_set_string (PsppireDataStore *store,
+                              const gchar *text,
+                               glong row, const struct variable *var,
+                               gboolean use_value_label)
 {
-  struct data_in *d_in = data;
+  gint case_index;
+  glong n_cases;
+  gboolean ok;
 
-  d_in->v = val;
+  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);
 
-  if ( ! data_in(d_in) ) 
+  case_index = var_get_case_index (var);
+  if (use_value_label)
     {
-      g_warning("Cant encode string\n");
-      return FALSE;
+      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;
     }
+  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;
 }
 
 
-/* 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)
-{
-  gint r;
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
 
-  const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
+void
+psppire_data_store_clear (PsppireDataStore *ds)
+{
+  datasheet_destroy (ds->datasheet);
+  ds->datasheet = NULL;
 
-  for(r = psppire_case_array_get_n_cases(store->cases) ; r <= row ; ++r ) 
-    {
-      gint c;
-      psppire_case_array_insert_case(store->cases, r);
+  psppire_dict_clear (ds->dict);
 
-      for (c = 0 ; c < psppire_dict_get_var_cnt(store->dict); ++c ) 
-       psppire_data_store_clear_datum(model, r, c);
-    }
+  g_signal_emit (ds, signals [CASES_DELETED], 0, 0, -1);
+}
 
-  {
-    const gint index = psppire_variable_get_index(pv);
 
-    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_case_array_set_value(store->cases, row, index, fillit, &d_in);
-  }
+/* Return a casereader made from this datastore */
+struct casereader *
+psppire_data_store_get_reader (PsppireDataStore *ds)
+{
+  int i;
+  struct casereader *reader ;
 
-  return TRUE;
-}
+  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);
 
-void
-psppire_data_store_set_font(PsppireDataStore *store, PangoFontDescription *fd)
-{
-  g_return_if_fail (store);
-  g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
+  /* We must not reference this again */
+  ds->datasheet = NULL;
 
-  store->font_desc = fd;
-  g_sheet_model_range_changed (G_SHEET_MODEL(store),
-                                -1, -1, -1, -1);
+  return reader;
 }
 
 
-void
-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->show_labels = show_labels;
+/* Column related funcs */
 
-  g_sheet_model_range_changed (G_SHEET_MODEL(store),
-                                -1, -1, -1, -1);
-}
 
+static const gchar null_var_name[]=N_("var");
 
 
-static gboolean 
-write_case(const struct ccase *cc, 
-          gpointer aux)
-{
-  struct sfm_writer *writer = aux;
+\f
 
-  if ( ! sfm_write_case(writer, cc) )
-    return FALSE;
 
+/* 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 TRUE;
+  return datasheet_get_row (ds->datasheet, casenum);
 }
 
-void
-psppire_data_store_create_system_file(PsppireDataStore *store,
-                             struct file_handle *handle)
+
+gboolean
+psppire_data_store_delete_cases (PsppireDataStore *ds, casenumber first,
+                                casenumber n_cases)
 {
-  const struct sfm_write_options wo = {
-    true, /* writeable */
-    false, /* dont compress */
-    3 /* version */
-  }; 
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
 
-  struct sfm_writer *writer ;
+  g_return_val_if_fail (first + n_cases <=
+                       psppire_data_store_get_case_count (ds), FALSE);
 
-  g_assert(handle);
 
-  writer = sfm_open_writer(handle, store->dict->dict, wo);
+  datasheet_delete_rows (ds->datasheet, first, n_cases);
 
-  if ( ! writer) 
-    return;
+  g_signal_emit (ds, signals [CASES_DELETED], 0, first, n_cases);
 
-  psppire_case_array_iterate_case(store->cases, write_case, writer);
-
-  sfm_close_writer(writer);
+  return TRUE;
 }
 
 
 
-/* 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));
-}
-
-/* Return the width that an  'M' character would occupy when typeset at
-   row, col */
-static guint 
-M_width(GtkSheet *sheet, gint row, gint col)
+/* Insert case CC into the case file before POSN */
+static gboolean
+psppire_data_store_insert_case (PsppireDataStore *ds,
+                               struct ccase *cc,
+                               casenumber posn)
 {
-  GtkSheetCellAttr attributes;
-  PangoRectangle rect;
-  /* FIXME: make this a member of the data store */
-  static PangoLayout *layout = 0;
-
-  gtk_sheet_get_attributes(sheet, row, col, &attributes);
+  bool result ;
 
-  if (! layout ) 
-    layout = gtk_widget_create_pango_layout (GTK_WIDGET(sheet), "M");
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
 
-  g_assert(layout);
-  
-  pango_layout_set_font_description (layout, 
-                                    attributes.font_desc);
+  cc = case_ref (cc);
+  result = datasheet_insert_rows (ds->datasheet, posn, &cc, 1);
 
-  pango_layout_get_extents (layout, NULL, &rect);
-
-#if 0
-  g_object_unref(G_OBJECT(layout));
-#endif
+  if ( result )
+    g_signal_emit (ds, signals [CASE_INSERTED], 0, posn);
+  else
+    g_warning ("Cannot insert case at position %ld\n", posn);
 
-  return PANGO_PIXELS(rect.width);
+  return result;
 }
 
 
-/* Return the number of pixels corresponding to a column of 
-   WIDTH characters */
-static inline guint 
-columnWidthToPixels(GtkSheet *sheet, gint column, guint width)
+/* 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)
 {
-  return (M_width(sheet, 0, column) * width);
-}
+  glong n_cases;
+  bool ok;
 
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
 
-static gint
-geometry_get_width(const GSheetColumn *geom, gint unit, GtkSheet *sheet)
-{
-  const struct PsppireVariable *pv ;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  n_cases = psppire_data_store_get_case_count (ds);
+  if ( casenum > n_cases)
+    return FALSE;
 
-  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
-    return 75;
+  if (casenum == n_cases)
+    psppire_data_store_insert_new_case (ds, casenum);
 
-  /* FIXME: We can optimise this by caching the widths until they're resized */
-  pv = psppire_dict_get_variable(ds->dict, unit);
+  ok = datasheet_put_value (ds->datasheet, casenum, var_get_case_index (var),
+                            v);
+  if (ok)
+    g_signal_emit (ds, signals [CASE_CHANGED], 0, casenum);
 
-  return columnWidthToPixels(sheet, unit, psppire_variable_get_columns(pv));
+  return ok;
 }
 
 
 
 
-static void
-geometry_set_width(GSheetColumn *geom, gint unit, gint width, GtkSheet *sheet)
+/* 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)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
-
-  struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
+  union value value;
+  int width;
+  bool ok;
 
-  psppire_variable_set_columns(pv, width / M_width(sheet, 1, unit));
-}
+  PsppireDict *dict;
 
+  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);
 
-static GtkJustification
-geometry_get_justification(const GSheetColumn *geom, gint unit)
-{
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
-  const struct PsppireVariable *pv ;
+  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);
 
-  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
-    return GTK_JUSTIFY_LEFT;
+  return ok;
+}
 
-  pv = psppire_dict_get_variable(ds->dict, unit);
+/* 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;
 
-  /* Kludge: Happily GtkJustification is defined similarly
-     to enum alignment from pspp/variable.h */
-  return psppire_variable_get_alignment(pv);
-}
+  g_return_val_if_fail (ds, FALSE);
 
+  g_assert (width >= 0);
 
-static const gchar null_var_name[]=_("var");
-static const gchar *
-geometry_get_button_label(const GSheetColumn *geom, gint unit)
-{
-  struct PsppireVariable *pv ;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  if ( ! ds->datasheet )
+    ds->datasheet = datasheet_create (NULL);
 
-  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
-    return null_var_name;
+  value_init (&value, width);
+  value_set_missing (&value, width);
 
-  pv = psppire_dict_get_variable(ds->dict, unit);
+  datasheet_insert_column (ds->datasheet, &value, width, where);
+  value_destroy (&value, width);
 
-  return psppire_variable_get_name(pv);
+  return TRUE;
 }
 
-
-static gboolean
-geometry_get_sensitivity(const GSheetColumn *geom, gint unit)
+gboolean
+psppire_data_store_filtered (PsppireDataStore *ds,
+                             glong row)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  union value val;
 
+  const struct dictionary *dict;
+  const struct variable *filter;
 
-  return (unit < psppire_dict_get_var_cnt(ds->dict));
-}
+  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;
 
-static void
-psppire_data_store_sheet_column_init (GSheetColumnIface *iface)
-{
-  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_button_label;
+  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);
 }