Make cases simpler, faster, and easier to understand.
[pspp-builds.git] / src / ui / gui / psppire-data-store.c
index 660e24dd352eb8332ade378ac745747cf308c24c..71d7d5f53976c124fc779a7a56fa127b13eaa09e 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 <gettext.h>
-#define _(msgid) gettext(msgid)
+#define _(msgid) gettext (msgid)
 #define N_(msgid) msgid
 
-#include <data/casefile.h>
-#include <data/case.h>
+#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/sheet/psppire-sheetmodel.h>
+#include <ui/gui/psppire-marshal.h>
 
 #include <pango/pango-context.h>
 
 #include "psppire-data-store.h"
-#include "psppire-case-file.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"
 
 
 
 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_sheet_model_init (PsppireSheetModelIface *iface);
 
 static void psppire_data_store_finalize        (GObject           *object);
+static void psppire_data_store_dispose        (GObject           *object);
+
+static gboolean psppire_data_store_clear_datum (PsppireSheetModel *model,
+                                         glong row, glong column);
 
-static gchar *psppire_data_store_get_string(const GSheetModel *sheet_model, gint row, gint column);
 
-static gboolean psppire_data_store_set_string(GSheetModel *model, 
-                                         const gchar *text, gint row, gint column);
+static gboolean psppire_data_store_insert_case (PsppireDataStore *ds,
+                                               struct ccase *cc,
+                                               casenumber posn);
 
-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);
 
-#define MIN_COLUMNS 10
 
-#define TRAILING_ROWS 10
 
 static GObjectClass *parent_class = NULL;
 
 
-enum  {FONT_CHANGED,
-       n_SIGNALS};
+enum
+  {
+    BACKEND_CHANGED,
+    CASES_DELETED,
+    CASE_INSERTED,
+    CASE_CHANGED,
+    n_SIGNALS
+  };
 
-static guint signal[n_SIGNALS];
+static guint signal[n_SIGNALS];
 
 
-inline GType
+GType
 psppire_data_store_get_type (void)
 {
   static GType data_store_type = 0;
@@ -108,35 +111,15 @@ psppire_data_store_get_type (void)
        NULL
       };
 
-      static const GInterfaceInfo sheet_column_info =
-      {
-       (GInterfaceInitFunc) psppire_data_store_sheet_column_init,
-       NULL,
-       NULL
-      };
 
-      static const GInterfaceInfo sheet_row_info =
-      {
-       (GInterfaceInitFunc) psppire_data_store_sheet_row_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);
-
-      g_type_add_interface_static (data_store_type,
-                                  G_TYPE_SHEET_ROW,
-                                  &sheet_row_info);
     }
 
   return data_store_type;
@@ -152,155 +135,213 @@ 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;
 
-  signal[FONT_CHANGED] =
-    g_signal_new ("font_changed",
-                 G_TYPE_FROM_CLASS(class),
+  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, 
+                 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);
 
 
-static gint
-psppire_data_store_get_var_count (const GSheetModel *model)
-{
-  const PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
-  
-  return psppire_dict_get_var_cnt(store->dict);
+  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_case_count (const GSheetModel *model)
+
+
+static gboolean
+psppire_data_store_insert_values (PsppireDataStore *ds,
+                                 gint n_values, gint where);
+
+static union value *
+psppire_data_store_get_value (const PsppireDataStore *ds,
+                             casenumber casenum, size_t idx,
+                             union value *value, int width);
+
+
+static gboolean
+psppire_data_store_set_value (PsppireDataStore *ds, casenumber casenum,
+                             gint idx, union value *v, gint width);
+
+
+
+
+static glong
+psppire_data_store_get_var_count (const PsppireSheetModel *model)
 {
-  const PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
+  const PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
 
-  return psppire_case_file_get_case_count(store->case_file);
+  return psppire_dict_get_var_cnt (store->dict);
 }
 
-
-static void
-psppire_data_store_init (PsppireDataStore *data_store)
+casenumber
+psppire_data_store_get_case_count (const PsppireDataStore *store)
 {
-  data_store->dict = 0;
-  data_store->case_file = 0;
-  data_store->width_of_m = 10;
+  return datasheet_get_row_cnt (store->datasheet);
 }
 
-const PangoFontDescription *
-psppire_data_store_get_font_desc(const GSheetModel *model,
-                             gint row, gint column)
+size_t
+psppire_data_store_get_value_count (const PsppireDataStore *store)
 {
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
-  
-  return store->font_desc;
+  return psppire_dict_get_value_cnt (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_sheet_model_init (GSheetModelIface *iface)
+psppire_data_store_init (PsppireDataStore *data_store)
 {
-  iface->free_strings = TRUE;
-  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;
-  iface->get_column_count = psppire_data_store_get_var_count;
-  iface->get_row_count = psppire_data_store_get_case_count;
+  data_store->dict = 0;
+  data_store->datasheet = NULL;
+  data_store->dispose_has_run = FALSE;
 }
 
-static
-gboolean always_true()
+static inline gchar *
+psppire_data_store_get_string_wrapper (const PsppireSheetModel *model, glong row,
+                                      glong column)
 {
-  return TRUE;
+  return psppire_data_store_get_string (PSPPIRE_DATA_STORE (model), row, column);
 }
 
 
-static void
-delete_cases_callback(GtkWidget *w, gint first, gint n_cases, gpointer data)
+static inline gboolean
+psppire_data_store_set_string_wrapper (PsppireSheetModel *model,
+                                      const gchar *text,
+                                      glong row, glong column)
 {
-  PsppireDataStore *store  ;
+  return psppire_data_store_set_string (PSPPIRE_DATA_STORE (model), text,
+                                       row, column);
+}
 
-  g_return_if_fail (data);
 
-  store  = PSPPIRE_DATA_STORE(data);
 
-  g_assert(first >= 0);
+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);
 
-  g_sheet_model_rows_deleted (G_SHEET_MODEL(store), first, n_cases);
-}
+static gchar * get_row_button_label (const PsppireSheetModel *model, gint row);
+static gboolean get_row_sensitivity (const PsppireSheetModel *model, gint row);
 
 
 static void
-insert_case_callback(GtkWidget *w, gint casenum, gpointer data)
+psppire_data_store_sheet_model_init (PsppireSheetModelIface *iface)
 {
-  PsppireDataStore *store  ;
-
-  g_return_if_fail (data);
+  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->get_foreground = NULL;
+  iface->get_background = NULL;
+  iface->get_column_count = psppire_data_store_get_var_count;
+  iface->get_row_count = psppire_data_store_get_case_count_wrapper;
 
-  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);
+  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;
 
-  g_sheet_model_rows_inserted (G_SHEET_MODEL(store), casenum, 1);
+  iface->get_row_title = get_row_button_label;
+  iface->get_row_sensitivity = get_row_sensitivity;
 }
 
 
+/*
+   A callback which occurs after a variable has been deleted.
+ */
 static void
-changed_case_callback(GtkWidget *w, gint casenum, gpointer data)
+delete_variable_callback (GObject *obj, gint dict_index,
+                         gint case_index, gint val_cnt,
+                         gpointer data)
 {
-  PsppireDataStore *store  ;
-  g_return_if_fail (data);
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
+
+
+  psppire_sheet_model_columns_deleted (PSPPIRE_SHEET_MODEL (store), dict_index, 1);
+#if AXIS_TRANSITION
+
 
-  store  = PSPPIRE_DATA_STORE(data);
-  
-  g_sheet_model_range_changed (G_SHEET_MODEL(store),
-                                casenum, -1,
-                                casenum, -1);
+  psppire_sheet_column_columns_changed (PSPPIRE_SHEET_COLUMN (store),
+                                  dict_index, -1);
+#endif
 }
 
 
+
 static void
-delete_variables_callback(GObject *obj, gint var_num, gint n_vars, gpointer data)
+variable_changed_callback (GObject *obj, gint var_num, gpointer data)
 {
-  PsppireDataStore *store ;
-
-  g_return_if_fail (data);
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
 
-  store  = PSPPIRE_DATA_STORE(data);
+#if AXIS_TRANSITION
+  psppire_sheet_column_columns_changed (PSPPIRE_SHEET_COLUMN (store),
+                                 var_num, 1);
 
-  g_sheet_model_columns_deleted (G_SHEET_MODEL(store), var_num, n_vars);
 
-  g_sheet_column_columns_changed(G_SHEET_COLUMN(store),
-                                  var_num, -1);
+  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (store),
+                              -1, var_num,
+                              -1, var_num);
+#endif
 }
 
 static void
-insert_variable_callback(GObject *obj, gint var_num, gpointer data)
+insert_variable_callback (GObject *obj, gint var_num, gpointer data)
 {
   PsppireDataStore *store;
   gint posn;
 
   g_return_if_fail (data);
 
-  store  = PSPPIRE_DATA_STORE(data);
-  
-  if ( var_num > 0 ) 
+  store  = PSPPIRE_DATA_STORE (data);
+
+  if ( var_num > 0 )
     {
-      struct variable *variable;
-      variable = psppire_dict_get_variable(store->dict, var_num);
+      struct variable *variable =
+       psppire_dict_get_variable (store->dict, var_num);
+
+      g_assert (variable != NULL);
 
       posn = var_get_case_index (variable);
     }
@@ -309,26 +350,32 @@ insert_variable_callback(GObject *obj, gint var_num, gpointer data)
       posn = 0;
     }
 
-  psppire_case_file_insert_values(store->case_file, 1, posn);
+  psppire_data_store_insert_values (store, 1, posn);
+
+#if AXIS_TRANSITION
 
-  g_sheet_column_columns_changed(G_SHEET_COLUMN(store),
+  psppire_sheet_column_columns_changed (PSPPIRE_SHEET_COLUMN (store),
                                  var_num, 1);
+#endif
 
-  g_sheet_model_columns_inserted (G_SHEET_MODEL(store), var_num, 1);
+  psppire_sheet_model_columns_inserted (PSPPIRE_SHEET_MODEL (store), var_num, 1);
 }
 
 
 static void
-dict_size_change_callback(GObject *obj, 
+dict_size_change_callback (GObject *obj,
                          gint posn, gint adjustment, gpointer data)
 {
-  PsppireDataStore *store ;
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
 
-  g_return_if_fail (data);
+  const struct variable *v = psppire_dict_get_variable (store->dict, posn);
 
-  store  = PSPPIRE_DATA_STORE(data);
+  const gint new_val_width = value_cnt_from_width (var_get_width (v));
 
-  psppire_case_file_insert_values (store->case_file, adjustment, posn);
+  if ( adjustment > 0 )
+    psppire_data_store_insert_values (store, adjustment,
+                                    new_val_width - adjustment +
+                                    var_get_case_index(v));
 }
 
 
@@ -347,12 +394,37 @@ psppire_data_store_new (PsppireDict *dict)
 
   retval = g_object_new (GTK_TYPE_DATA_STORE, NULL);
 
-  psppire_data_store_set_dictionary(retval, dict);
-
+  psppire_data_store_set_dictionary (retval, dict);
 
   return retval;
 }
 
+void
+psppire_data_store_set_reader (PsppireDataStore *ds,
+                              struct casereader *reader)
+{
+  gint i;
+
+  if ( ds->datasheet)
+    datasheet_destroy (ds->datasheet);
+
+  ds->datasheet = datasheet_create (reader);
+
+  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (ds),
+                              -1, -1, -1, -1);
+
+  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);
+}
 
 
 /**
@@ -364,52 +436,62 @@ psppire_data_store_new (PsppireDict *dict)
  * destroyed.
  **/
 void
-psppire_data_store_set_dictionary(PsppireDataStore *data_store, PsppireDict *dict)
+psppire_data_store_set_dictionary (PsppireDataStore *data_store, PsppireDict *dict)
 {
-  gint var_cnt = psppire_dict_get_next_value_idx(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;
 
-  if ( data_store->case_file)
+  if ( dict != NULL)
     {
-      g_object_unref(data_store->case_file);
-      data_store->case_file = 0;
-    }
-
-  data_store->case_file = psppire_case_file_new(var_cnt);
-
-  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);
 
+      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(data_store->case_file, "case-changed", 
-                  G_CALLBACK(changed_case_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 */
+  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (data_store), -1, -1, -1, -1);
 
-  g_signal_connect (dict, "dict-size-changed", 
-                   G_CALLBACK(dict_size_change_callback),
-                   data_store);
+#if AXIS_TRANSITION
+  psppire_sheet_column_columns_changed (PSPPIRE_SHEET_COLUMN (data_store), 0, -1);
+#endif
 
-  /* 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
@@ -421,89 +503,115 @@ psppire_data_store_finalize (GObject *object)
 }
 
 
+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, gint posn)
+psppire_data_store_insert_new_case (PsppireDataStore *ds, casenumber posn)
 {
   gboolean result;
-  gint val_cnt, v; 
-  struct ccase cc;
+  gint val_cnt, v;
+  struct ccase *cc;
   g_return_val_if_fail (ds, FALSE);
 
+  val_cnt = datasheet_get_column_cnt (ds->datasheet) ;
+
+  g_return_val_if_fail (val_cnt > 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 = casefile_get_value_cnt(ds->case_file->flexifile) ;
-  
-  case_create (&cc, val_cnt);
+  cc = case_create (val_cnt);
 
-  memset ( case_data_rw_idx (&cc, 0), 0, val_cnt * MAX_SHORT_STRING);
+  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) 
+  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;
+      case_data_rw (cc, pv)->f = SYSMIS;
     }
 
-  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;
 }
 
 
-static gchar *
-psppire_data_store_get_string (const GSheetModel *model, gint row, gint column)
+gchar *
+psppire_data_store_get_string (PsppireDataStore *store, glong row, glong column)
 {
   gint idx;
   char *text;
   const struct fmt_spec *fp ;
   const struct variable *pv ;
-  const union value *v ;
+  union value *v ;
   GString *s;
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
 
   g_return_val_if_fail (store->dict, NULL);
-  g_return_val_if_fail (store->case_file, NULL);
+  g_return_val_if_fail (store->datasheet, NULL);
 
   if (column >= psppire_dict_get_var_cnt (store->dict))
     return NULL;
 
-  if ( row >= psppire_case_file_get_case_count (store->case_file))
+  if ( row >= psppire_data_store_get_case_count (store))
     return NULL;
 
   pv = psppire_dict_get_variable (store->dict, column);
 
+  g_assert (pv);
+
   idx = var_get_case_index (pv);
 
-  v = psppire_case_file_get_value (store->case_file, row, idx);
+  g_assert (idx >= 0);
 
-  g_return_val_if_fail(v, NULL);
+  v = psppire_data_store_get_value (store, row, idx, NULL,
+                                   var_get_width (pv));
 
-  if ( store->show_labels) 
-    {
-      const struct val_labs * vl = var_get_value_labels (pv);
+  g_return_val_if_fail (v, NULL);
 
-      const gchar *label;
-      if ( (label = val_labs_find(vl, *v)) )
-       {
-         return pspp_locale_to_utf8(label, -1, 0);
-       }
+  if ( store->show_labels)
+    {
+      const gchar *label = var_lookup_value_label (pv, v);
+      if (label)
+        {
+          free (v);
+         return pspp_locale_to_utf8 (label, -1, 0);
+        }
     }
 
   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.  */
@@ -514,16 +622,16 @@ psppire_data_store_get_string (const GSheetModel *model, gint row, gint column)
 
   g_strchomp (text);
 
+  free (v);
   return text;
 }
 
 
-static gboolean 
-psppire_data_store_clear_datum (GSheetModel *model, 
-                                         gint row, gint col)
-
+static gboolean
+psppire_data_store_clear_datum (PsppireSheetModel *model,
+                                         glong row, glong col)
 {
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
+  PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
 
   union value v;
   const struct variable *pv = psppire_dict_get_variable (store->dict, col);
@@ -533,295 +641,368 @@ psppire_data_store_clear_datum (GSheetModel *model,
   if ( var_is_numeric (pv))
     v.f = SYSMIS;
   else
-    memcpy(v.s, "", MAX_SHORT_STRING);
+    memcpy (v.s, "", MAX_SHORT_STRING);
+
+  psppire_data_store_set_value (store, row, index, &v,
+                               var_get_width (pv));
 
-  psppire_case_file_set_value(store->case_file, row, index, &v, 
-                             var_get_width (pv));
+  psppire_sheet_model_range_changed (model, row, col, row, col);
 
   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)
 {
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
-
-  const struct variable *pv = psppire_dict_get_variable(store->dict, col);
-  g_return_val_if_fail(pv, FALSE);
+  glong n_cases;
+  const struct variable *pv = psppire_dict_get_variable (store->dict, col);
+  if ( NULL == pv)
+    return 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) 
-    {
+  n_cases = psppire_data_store_get_case_count (store);
 
-      gint c;
+  if ( row > n_cases)
+    return FALSE;
 
-      psppire_case_array_insert_case(store->cases, r, 0, 0);
+  if (row == n_cases)
+    psppire_data_store_insert_new_case (store, row);
 
+  psppire_data_store_data_in (store, row,
+                             var_get_case_index (pv), ss_cstr (text),
+                             var_get_write_format (pv));
 
-      for (c = 0 ; c < psppire_dict_get_var_cnt(store->dict); ++c ) 
-       psppire_data_store_clear_datum(model, r, c);
-    }
-#endif
+  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (store), row, col, row, col);
 
-  psppire_case_file_data_in (store->case_file, row,
-                             var_get_case_index (pv), ss_cstr (text),
-                             var_get_write_format (pv));
-  
   return TRUE;
 }
 
 
+
 void
-psppire_data_store_set_font(PsppireDataStore *store, 
-                           const 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;
-#if 0
-  store->width_of_m = calc_m_width(fd);
-#endif
-  g_signal_emit(store, signal[FONT_CHANGED], 0);  
-
+  store->show_labels = show_labels;
 
-  g_sheet_model_range_changed (G_SHEET_MODEL(store),
+  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);
 }
 
 
 
-/* FIXME: There's no reason to actually have this function.
-   It should be done by a procedure */
-void
-psppire_data_store_create_system_file(PsppireDataStore *store,
-                             struct file_handle *handle)
+/* Return a casereader made from this datastore */
+struct casereader *
+psppire_data_store_get_reader (PsppireDataStore *ds)
 {
-  gint i, var_cnt;
-  const struct sfm_write_options wo = {
-    true, /* writeable */
-    false, /* dont compress */
-    3 /* version */
-  }; 
+  int i;
+  struct casereader *reader ;
 
-  struct sfm_writer *writer ;
+  if ( ds->dict )
+    for (i = 0 ; i < n_dict_signals; ++i )
+      {
+       g_signal_handler_block (ds->dict,
+                               ds->dict_handler_id[i]);
+      }
 
-  g_assert(handle);
+  reader = datasheet_make_reader (ds->datasheet);
 
-  writer = sfm_open_writer(handle, store->dict->dict, wo);
+  /* We must not reference this again */
+  ds->datasheet = NULL;
 
-  if ( ! writer) 
-    return;
+  return reader;
+}
 
 
-  var_cnt = psppire_data_store_get_var_count (G_SHEET_MODEL(store));
 
-  for (i = 0 ; i < psppire_case_file_get_case_count(store->case_file); ++i ) 
-    {
-      struct ccase c;
-      
-      case_create (&c, var_cnt);
-      psppire_case_file_get_case (store->case_file, i, &c);
-      sfm_write_case (writer, &c);
+/* Column related funcs */
 
-      case_destroy (&c);
-    }
 
-  sfm_close_writer(writer);
-}
+static const gchar null_var_name[]=N_("var");
 
+\f
 
+/* Row related funcs */
 
-void 
-psppire_data_store_clear(PsppireDataStore *data_store)
+static gchar *
+get_row_button_label (const PsppireSheetModel *model, gint unit)
 {
-  psppire_case_file_clear(data_store->case_file);
-
-  psppire_dict_clear(data_store->dict);
-}
+  gchar *s = g_strdup_printf (_("%d"), unit + FIRST_CASE_NUMBER);
 
+  gchar *text =  pspp_locale_to_utf8 (s, -1, 0);
 
+  g_free (s);
 
+  return text;
+}
 
-/* Column related funcs */
 
-static gint
-geometry_get_column_count(const GSheetColumn *geom)
+static gboolean
+get_row_sensitivity (const PsppireSheetModel *model, gint unit)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
 
-  return MAX(MIN_COLUMNS, psppire_dict_get_var_cnt(ds->dict));
+  return (unit < psppire_data_store_get_case_count (ds));
 }
 
 
+\f
+
+/* Column related stuff */
 
-static gint
-geometry_get_width(const GSheetColumn *geom, gint unit)
+static gchar *
+get_column_subtitle (const PsppireSheetModel *model, gint col)
 {
-  const struct variable *pv ;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  gchar *text;
+  const struct variable *v ;
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
 
-  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
-    return ds->width_of_m * 8 ;
+  if ( col >= psppire_dict_get_var_cnt (ds->dict) )
+    return NULL;
 
-  pv = psppire_dict_get_variable (ds->dict, unit);
+  v = psppire_dict_get_variable (ds->dict, col);
 
-  if ( pv == NULL ) 
-    return ds->width_of_m * 8 ;
+  if ( ! var_has_label (v))
+    return NULL;
+
+  text =  pspp_locale_to_utf8 (var_get_label (v), -1, 0);
 
-  return ds->width_of_m * var_get_display_width (pv);
+  return text;
 }
 
-static void
-geometry_set_width(GSheetColumn *geom, gint unit, gint width)
+static gchar *
+get_column_button_label (const PsppireSheetModel *model, gint col)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  gchar *text;
+  struct variable *pv ;
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
 
-  struct variable *pv = psppire_dict_get_variable (ds->dict, unit);
+  if ( col >= psppire_dict_get_var_cnt (ds->dict) )
+    return g_locale_to_utf8 (null_var_name, -1, 0, 0, 0);
 
-  var_set_display_width (pv, width / ds->width_of_m );
+  pv = psppire_dict_get_variable (ds->dict, col);
+
+  text =  pspp_locale_to_utf8 (var_get_name (pv), -1, 0);
+
+  return text;
+}
+
+static gboolean
+get_column_sensitivity (const PsppireSheetModel *model, gint col)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
+
+  return (col < psppire_dict_get_var_cnt (ds->dict));
 }
 
 
 
 static GtkJustification
-geometry_get_justification(const GSheetColumn *geom, gint unit)
+get_column_justification (const PsppireSheetModel *model, gint col)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
   const struct variable *pv ;
 
-
-  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
+  if ( col >= psppire_dict_get_var_cnt (ds->dict) )
     return GTK_JUSTIFY_LEFT;
 
-  pv = psppire_dict_get_variable(ds->dict, unit);
+  pv = psppire_dict_get_variable (ds->dict, col);
 
-  /* Kludge: Happily GtkJustification is defined similarly
-     to enum alignment from pspp/variable.h */
-  return var_get_alignment(pv);
+  return (var_get_alignment (pv) == ALIGN_LEFT ? GTK_JUSTIFY_LEFT
+          : var_get_alignment (pv) == ALIGN_RIGHT ? GTK_JUSTIFY_RIGHT
+          : GTK_JUSTIFY_CENTER);
 }
 
 
-static const gchar null_var_name[]=N_("var");
-static gchar *
-geometry_get_column_button_label(const GSheetColumn *geom, gint unit)
-{
-  gchar *text;
-  struct variable *pv ;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
 
-  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
-    return g_locale_to_utf8(null_var_name, -1, 0, 0, 0);
+\f
 
-  pv = psppire_dict_get_variable (ds->dict, unit);
 
-  text =  pspp_locale_to_utf8 (var_get_name (pv), -1, 0);
+/* 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 text;
+  return datasheet_get_row (ds->datasheet, casenum);
 }
 
 
-static gboolean
-geometry_get_sensitivity(const GSheetColumn *geom, gint unit)
+gboolean
+psppire_data_store_delete_cases (PsppireDataStore *ds, casenumber first,
+                                casenumber n_cases)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
 
-  return (unit < psppire_dict_get_var_cnt(ds->dict));
-}
+  g_return_val_if_fail (first + n_cases <=
+                       psppire_data_store_get_case_count (ds), 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_column_button_label;
+  datasheet_delete_rows (ds->datasheet, first, n_cases);
+
+  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;
 }
 
 
-/* Row related funcs */
 
-static gint
-geometry_get_row_count(const GSheetRow *geom, gpointer data)
+/* 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);
+  bool result ;
 
-  return TRAILING_ROWS + psppire_case_file_get_case_count(ds->case_file);
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
+
+  case_ref (cc);
+  result = datasheet_insert_rows (ds->datasheet, posn, &cc, 1);
+
+  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);
+
+  return result;
 }
 
 
-static gint
-geometry_get_height(const GSheetRow *geom, gint unit, gpointer data)
+/* Copies the IDXth value from case CASENUM into VALUE.
+   If VALUE is null, then memory is allocated is allocated with
+   malloc.  Returns the value if successful, NULL on failure. */
+static union value *
+psppire_data_store_get_value (const PsppireDataStore *ds,
+                             casenumber casenum, size_t idx,
+                             union value *value, int width)
 {
-  return 25;
+  bool allocated;
+
+  g_return_val_if_fail (ds, false);
+  g_return_val_if_fail (ds->datasheet, false);
+
+  g_return_val_if_fail (idx < datasheet_get_column_cnt (ds->datasheet), false);
+
+  if (value == NULL)
+    {
+      value = xnmalloc (value_cnt_from_width (width), sizeof *value);
+      allocated = true;
+    }
+  else
+    allocated = false;
+  if (!datasheet_get_value (ds->datasheet, casenum, idx, value, width))
+    {
+      if (allocated)
+        free (value);
+      value = NULL;
+    }
+  return value;
 }
 
 
+
+/* Set the IDXth value of case C to V.
+   Returns true if successful, false on I/O error. */
 static gboolean
-geometry_get_row_sensitivity(const GSheetRow *geom, gint unit, gpointer data)
+psppire_data_store_set_value (PsppireDataStore *ds, casenumber casenum,
+                             gint idx, union value *v, gint width)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  bool ok;
+
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
 
-  
-  return (unit < psppire_case_file_get_case_count(ds->case_file));
+  g_return_val_if_fail (idx < datasheet_get_column_cnt (ds->datasheet), FALSE);
+
+  ok = datasheet_put_value (ds->datasheet, casenum, idx, v, width);
+  if (ok)
+    g_signal_emit (ds, signals [CASE_CHANGED], 0, casenum);
+
+  return ok;
 }
 
 
-static gchar *
-geometry_get_row_button_label(const GSheetRow *geom, gint unit, gpointer data)
+
+
+/* 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)
 {
-  gchar *text;
-  gchar *s;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  union value *value = NULL;
+  int width;
+  bool ok;
 
-  if ( unit > 
-       TRAILING_ROWS + psppire_case_file_get_case_count(ds->case_file))
-    return 0;
+  g_return_val_if_fail (ds, FALSE);
+  g_return_val_if_fail (ds->datasheet, FALSE);
 
-  s = g_strdup_printf(_("%d"), unit);
+  g_return_val_if_fail (idx < datasheet_get_column_cnt (ds->datasheet), FALSE);
 
-  text =  pspp_locale_to_utf8(s, -1, 0);
-  
-  g_free(s);
-  
-  return text;
-}
+  width = fmt_var_width (fmt);
+  value = xmalloca (value_cnt_from_width (width) * sizeof *value);
+  ok = (datasheet_get_value (ds->datasheet, casenum, idx, value, width)
+        && data_in (input, LEGACY_NATIVE, fmt->type, 0, 0, 0, value, width)
+        && datasheet_put_value (ds->datasheet, casenum, idx, value, width));
 
+  freea (value);
 
-static void
-psppire_data_store_sheet_row_init (GSheetRowIface *iface)
+  if (ok)
+    g_signal_emit (ds, signals [CASE_CHANGED], 0, casenum);
+
+  return ok;
+}
+
+/* Resize the cases in the casefile, by inserting N_VALUES into every
+   one of them at the position immediately preceeding WHERE.
+*/
+static gboolean
+psppire_data_store_insert_values (PsppireDataStore *ds,
+                                 gint n_values, gint where)
 {
-  iface->get_row_count = geometry_get_row_count;
+  g_return_val_if_fail (ds, FALSE);
+
+  if ( n_values == 0 )
+    return FALSE;
 
-  iface->get_height = geometry_get_height;
-  iface->set_height = 0;
-  iface->get_visibility = always_true;
-  iface->get_sensitivity = geometry_get_row_sensitivity;
+  g_assert (n_values > 0);
 
-  iface->get_button_label = geometry_get_row_button_label;
+  if ( ! ds->datasheet )
+    ds->datasheet = datasheet_create (NULL);
+
+  {
+    union value *values = xcalloc (n_values, sizeof *values);
+    datasheet_insert_columns (ds->datasheet, values, n_values, where);
+    free (values);
+  }
+
+  return TRUE;
 }