Used type casenumber in GUI code where appropriate. Also, generally
[pspp-builds.git] / src / ui / gui / psppire-data-store.c
index e8537bcc1bce2d85a6b25dd83135bdc86a140340..61fee9bc35c99ef032410c0c60e9d5e1b91a5945 100644 (file)
@@ -1,12 +1,9 @@
-/* psppire-data-store.c
-   PSPPIRE --- A Graphical User Interface for PSPP
+/* PSPPIRE - a graphical user interface for PSPP.
    Copyright (C) 2006  Free Software Foundation
-   Written by John Darrington
 
-   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 <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 "psppire-variable.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/file-handle-def.h>
-#include <data/sys-file-writer.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 const gchar *const psppire_data_store_get_string(GSheetModel *sheet_model, gint row, gint column);
+static void psppire_data_store_sheet_row_init (GSheetRowIface *iface);
 
-static gboolean psppire_data_store_set_string(GSheetModel *model, 
-                                         const gchar *text, gint row, gint column);
+static void psppire_data_store_finalize        (GObject           *object);
 
-static gboolean psppire_data_store_clear_datum(GSheetModel *model, 
-                                         gint row, gint column);
+static gboolean psppire_data_store_clear_datum (GSheetModel *model,
+                                         glong row, glong column);
 
 
 #define MIN_COLUMNS 10
 
-#define max(A,B) ((A>B)?A:B)
+#define TRAILING_ROWS 10
 
 static GObjectClass *parent_class = NULL;
 
+
+enum  {FONT_CHANGED,
+       n_SIGNALS};
+
+static guint signals [n_SIGNALS];
+
+
 inline GType
 psppire_data_store_get_type (void)
 {
@@ -97,6 +100,12 @@ psppire_data_store_get_type (void)
        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",
@@ -110,11 +119,15 @@ psppire_data_store_get_type (void)
                                   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;
 }
 
+
 static void
 psppire_data_store_class_init (PsppireDataStoreClass *class)
 {
@@ -124,31 +137,85 @@ psppire_data_store_class_init (PsppireDataStoreClass *class)
   object_class = (GObjectClass*) class;
 
   object_class->finalize = psppire_data_store_finalize;
+
+  signals [FONT_CHANGED] =
+    g_signal_new ("font_changed",
+                 G_TYPE_FROM_CLASS (class),
+                 G_SIGNAL_RUN_FIRST,
+                 0,
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__VOID,
+                 G_TYPE_NONE,
+                 0);
+}
+
+
+
+static glong
+psppire_data_store_get_var_count (const GSheetModel *model)
+{
+  const PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
+
+  return psppire_dict_get_var_cnt (store->dict);
+}
+
+casenumber
+psppire_data_store_get_case_count (PsppireDataStore *store)
+{
+  return psppire_case_file_get_case_count (store->case_file);
 }
 
+static glong
+psppire_data_store_get_case_count_from_model (const GSheetModel *model)
+{
+  const PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
+
+  return psppire_case_file_get_case_count (store->case_file);
+}
 
 static void
 psppire_data_store_init (PsppireDataStore *data_store)
 {
   data_store->dict = 0;
-  data_store->cases = 0;
+  data_store->case_file = 0;
+  data_store->width_of_m = 10;
 }
 
 const PangoFontDescription *
-psppire_data_store_get_font_desc(GSheetModel *model,
-                             gint row, gint column)
+psppire_data_store_get_font_desc (const GSheetModel *model,
+                             glong row, glong column)
 {
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
-  
+  PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
+
   return store->font_desc;
 }
 
+static inline gchar *
+psppire_data_store_get_string_wrapper (const GSheetModel *model, glong row,
+                                      glong column)
+{
+  return psppire_data_store_get_string (PSPPIRE_DATA_STORE (model), row, column);
+}
+
+
+static inline gboolean
+psppire_data_store_set_string_wrapper (GSheetModel *model,
+                                      const gchar *text,
+                                      glong row, glong column)
+{
+  return psppire_data_store_set_string (PSPPIRE_DATA_STORE (model), text,
+                                       row, column);
+}
+
+
+
 
 static void
 psppire_data_store_sheet_model_init (GSheetModelIface *iface)
 {
-  iface->get_string = psppire_data_store_get_string;
-  iface->set_string = psppire_data_store_set_string;
+  iface->free_strings = TRUE;
+  iface->get_string = psppire_data_store_get_string_wrapper;
+  iface->set_string = psppire_data_store_set_string_wrapper;
   iface->clear_datum = psppire_data_store_clear_datum;
   iface->is_editable = NULL;
   iface->is_visible = NULL;
@@ -156,97 +223,145 @@ psppire_data_store_sheet_model_init (GSheetModelIface *iface)
   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_from_model;
 }
 
 static
-gboolean always_true()
+gboolean always_true ()
 {
   return TRUE;
 }
 
 
-
 static void
-delete_cases_callback(GtkWidget *w, gint first, gint n_cases, gpointer data)
+delete_cases_callback (GtkWidget *w,
+        casenumber first, casenumber n_cases, gpointer data)
 {
   PsppireDataStore *store  ;
 
   g_return_if_fail (data);
 
-  store  = PSPPIRE_DATA_STORE(data);
+  store  = PSPPIRE_DATA_STORE (data);
 
-  g_assert(first >= 0);
+  g_assert (first >= 0);
 
-  g_sheet_model_rows_deleted (G_SHEET_MODEL(store), first, n_cases);
+  g_sheet_model_rows_deleted (G_SHEET_MODEL (store), first, n_cases);
 }
 
 
 static void
-insert_case_callback(GtkWidget *w, gint casenum, gpointer data)
+insert_case_callback (GtkWidget *w, casenumber casenum, 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);
+  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);
+
+  g_sheet_model_rows_inserted (G_SHEET_MODEL (store), casenum, 1);
 }
 
 
 static void
-changed_case_callback(GtkWidget *w, gint casenum, gpointer data)
+changed_case_callback (GtkWidget *w, gint casenum, gpointer data)
 {
   PsppireDataStore *store  ;
   g_return_if_fail (data);
 
-  store  = PSPPIRE_DATA_STORE(data);
-  
-  g_sheet_model_range_changed (G_SHEET_MODEL(store),
+  store  = PSPPIRE_DATA_STORE (data);
+
+  g_sheet_model_range_changed (G_SHEET_MODEL (store),
                                 casenum, -1,
                                 casenum, -1);
-
 }
 
 
 static void
-delete_variables_callback(GtkWidget *w, gint var_num, gint n_vars, gpointer data)
+delete_variables_callback (GObject *obj, gint var_num, gint n_vars, gpointer data)
 {
   PsppireDataStore *store ;
 
   g_return_if_fail (data);
 
-  store  = PSPPIRE_DATA_STORE(data);
+  store  = PSPPIRE_DATA_STORE (data);
+
+  g_sheet_model_columns_deleted (G_SHEET_MODEL (store), var_num, n_vars);
 
-  g_sheet_column_columns_deleted(G_SHEET_COLUMN(store),
-                                  var_num, n_vars);
+  g_sheet_column_columns_changed (G_SHEET_COLUMN (store),
+                                  var_num, -1);
 }
 
 
 static void
-insert_variable_callback(GtkWidget *w, gint var_num, gpointer data)
+variable_changed_callback (GObject *obj, gint var_num, 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);
-  */
+  store  = PSPPIRE_DATA_STORE (data);
+
+  g_sheet_column_columns_changed (G_SHEET_COLUMN (store),
+                                 var_num, 1);
+
+
+  g_sheet_model_range_changed (G_SHEET_MODEL (store),
+                              -1, var_num,
+                              -1, var_num);
+}
+
+static void
+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 )
+    {
+      struct variable *variable =
+       psppire_dict_get_variable (store->dict, var_num);
+
+      g_assert (variable != NULL);
+
+      posn = var_get_case_index (variable);
+    }
+  else
+    {
+      posn = 0;
+    }
 
-  psppire_case_array_resize(store->cases, 
-                        dict_get_next_value_idx (store->dict->dict));
+  psppire_case_file_insert_values (store->case_file, 1, posn);
 
+  g_sheet_column_columns_changed (G_SHEET_COLUMN (store),
+                                 var_num, 1);
+
+  g_sheet_model_columns_inserted (G_SHEET_MODEL (store), var_num, 1);
 }
 
 
+static void
+dict_size_change_callback (GObject *obj,
+                         gint posn, gint adjustment, gpointer data)
+{
+  PsppireDataStore *store ;
+
+  g_return_if_fail (data);
+
+  store  = PSPPIRE_DATA_STORE (data);
+
+  psppire_case_file_insert_values (store->case_file, adjustment, posn);
+}
+
 
 
 /**
@@ -257,28 +372,41 @@ 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);
+  psppire_data_store_set_dictionary (retval, dict);
+
+  return retval;
+}
 
-  g_signal_connect(cases, "case-inserted", G_CALLBACK(insert_case_callback), 
-                  retval);
 
+void
+psppire_data_store_set_case_file (PsppireDataStore *data_store,
+                                 PsppireCaseFile *cf)
+{
+  if ( data_store->case_file)
+    {
+      g_object_unref (data_store->case_file);
+    }
 
-  g_signal_connect(cases, "case-changed", G_CALLBACK(changed_case_callback), 
-                  retval);
+  data_store->case_file = cf;
 
+  g_signal_connect (data_store->case_file, "cases-deleted",
+                  G_CALLBACK (delete_cases_callback),
+                  data_store);
 
-  psppire_data_store_set_dictionary(retval, dict);
+  g_signal_connect (data_store->case_file, "case-inserted",
+                  G_CALLBACK (insert_case_callback),
+                  data_store);
 
 
-  return retval;
+  g_signal_connect (data_store->case_file, "case-changed",
+                  G_CALLBACK (changed_case_callback),
+                  data_store);
 }
 
 
@@ -292,29 +420,31 @@ 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
-
   data_store->dict = dict;
 
-  psppire_case_array_resize(data_store->cases, 
-                        dict_get_next_value_idx (data_store->dict->dict));
-
+  g_signal_connect (dict, "variable-inserted",
+                  G_CALLBACK (insert_variable_callback),
+                  data_store);
 
-  g_signal_connect(dict, "variable-inserted", 
-                  G_CALLBACK(insert_variable_callback), 
+  g_signal_connect (dict, "variables-deleted",
+                  G_CALLBACK (delete_variables_callback),
                   data_store);
 
-  g_signal_connect(dict, "variables-deleted", 
-                  G_CALLBACK(delete_variables_callback), 
+  g_signal_connect (dict, "variable-changed",
+                  G_CALLBACK (variable_changed_callback),
                   data_store);
 
 
+  g_signal_connect (dict, "dict-size-changed",
+                   G_CALLBACK (dict_size_change_callback),
+                   data_store);
+
   /* The entire model has changed */
-  g_sheet_model_range_changed (G_SHEET_MODEL(data_store), -1, -1, -1, -1);
+  g_sheet_model_range_changed (G_SHEET_MODEL (data_store), -1, -1, -1, -1);
+
+  g_sheet_column_columns_changed (G_SHEET_COLUMN (data_store), 0, -1);
 }
 
 static void
@@ -326,366 +456,400 @@ psppire_data_store_finalize (GObject *object)
 }
 
 
-static const gchar *const 
-psppire_data_store_get_string(GSheetModel *model, gint row, gint column)
+
+/* Insert a blank case before POSN */
+gboolean
+psppire_data_store_insert_new_case (PsppireDataStore *ds, casenumber posn)
 {
-  const struct fmt_spec *fp ;
-  const struct PsppireVariable *pv ;
-  const union value *v ;
-  static gchar s[255];
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
+  gboolean result;
+  gint val_cnt, v;
+  struct ccase cc;
+  g_return_val_if_fail (ds, FALSE);
 
-  g_return_val_if_fail(store->dict, NULL);
-  g_return_val_if_fail(store->cases, NULL);
 
-  if (column >= psppire_dict_get_var_cnt(store->dict))
-    return NULL;
+  /* Opportunity for optimisation exists here when creating a blank case */
+  val_cnt = datasheet_get_column_cnt (ds->case_file->datasheet) ;
 
-  if ( row >= psppire_case_array_get_n_cases(store->cases))
-    return NULL;
-
-  pv = psppire_dict_get_variable(store->dict, column);
+  case_create (&cc, val_cnt);
 
-  v =  psppire_case_array_get_value(store->cases, row, 
-                             psppire_variable_get_index(pv));
+  memset ( case_data_rw_idx (&cc, 0), 0, val_cnt * MAX_SHORT_STRING);
 
-  if ( store->show_labels) 
+  for (v = 0 ; v < psppire_dict_get_var_cnt (ds->dict) ; ++v)
     {
-      const struct val_labs * vl = psppire_variable_get_value_labels(pv);
+      const struct variable *pv = psppire_dict_get_variable (ds->dict, v);
+      if ( var_is_alpha (pv))
+       continue;
 
-      const gchar *label;
-      if ( (label = val_labs_find(vl, *v)) )
-       {
-         return label;
-       }
+      case_data_rw (&cc, pv)->f = SYSMIS;
     }
 
-  fp = psppire_variable_get_write_spec(pv);
+  result = psppire_case_file_insert_case (ds->case_file, &cc, posn);
 
-  if ( psppire_variable_get_type(pv) == NUMERIC ) 
-    {
-      /* 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, fp, v);
-      s[fp->w] = '\0';
-    }
-  else
-    {
-      const gint len = psppire_variable_get_width(pv);
-      memcpy(s, v->s, len);
-      s[len] = '\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;
-      }
+  case_destroy (&cc);
 
-  return buf ;
-  }
+  return result;
 }
 
 
-static gboolean
-set_null_string_value(union value *val, gpointer data)
+gchar *
+psppire_data_store_get_string (PsppireDataStore *store, glong row, glong column)
 {
-  strcpy(val->s, "");
-  return TRUE;
-}
+  gint idx;
+  char *text;
+  const struct fmt_spec *fp ;
+  const struct variable *pv ;
+  union value *v ;
+  GString *s;
 
-static gboolean
-set_sysmis_value(union value *val, gpointer data)
-{
-  val->f = SYSMIS;
-  return TRUE;
-}
+  g_return_val_if_fail (store->dict, NULL);
+  g_return_val_if_fail (store->case_file, NULL);
 
+  if (column >= psppire_dict_get_var_cnt (store->dict))
+    return NULL;
 
-static gboolean 
-psppire_data_store_clear_datum(GSheetModel *model, 
-                                         gint row, gint col)
+  if ( row >= psppire_case_file_get_case_count (store->case_file))
+    return NULL;
 
-{
-  PsppireDataStore *store = PSPPIRE_DATA_STORE(model);
+  pv = psppire_dict_get_variable (store->dict, column);
 
-  const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
+  g_assert (pv);
 
-  const gint index = psppire_variable_get_index(pv) ;
+  idx = var_get_case_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;
+  g_assert (idx >= 0);
+
+  v = psppire_case_file_get_value (store->case_file, row, idx, NULL,
+                                   var_get_width (pv));
+
+  g_return_val_if_fail (v, NULL);
+
+  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.  */
+  data_out (v, fp, s->str);
+
+  text = pspp_locale_to_utf8 (s->str, fp->w, 0);
+  g_string_free (s, TRUE);
+
+  g_strchomp (text);
+
+  free (v);
+  return text;
 }
 
 
 static gboolean
-fillit(union value *val, gpointer data)
+psppire_data_store_clear_datum (GSheetModel *model,
+                                         glong row, glong col)
 {
-  struct data_in *d_in = data;
+  PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
 
-  d_in->v = val;
+  union value v;
+  const struct variable *pv = psppire_dict_get_variable (store->dict, col);
 
-  if ( ! data_in(d_in) ) 
-    {
-      g_warning("Cant encode string\n");
-      return FALSE;
-    }
+  const gint index = var_get_case_index (pv) ;
+
+  if ( var_is_numeric (pv))
+    v.f = SYSMIS;
+  else
+    memcpy (v.s, "", MAX_SHORT_STRING);
+
+  psppire_case_file_set_value (store->case_file, row, index, &v,
+                             var_get_width (pv));
 
   return TRUE;
 }
 
 
-/* Attempts to update that part of the variable store which corresponds 
+/* 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);
+  const struct variable *pv = psppire_dict_get_variable (store->dict, col);
+  g_return_val_if_fail (pv, FALSE);
 
-  const struct PsppireVariable *pv = psppire_dict_get_variable(store->dict, col);
-
-  for(r = psppire_case_array_get_n_cases(store->cases) ; r <= row ; ++r ) 
+#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)
     {
+
       gint c;
-      psppire_case_array_insert_case(store->cases, r);
 
-      for (c = 0 ; c < psppire_dict_get_var_cnt(store->dict); ++c ) 
-       psppire_data_store_clear_datum(model, r, c);
-    }
+      psppire_case_array_insert_case (store->cases, r, 0, 0);
 
-  {
-    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;
+      for (c = 0 ; c < psppire_dict_get_var_cnt (store->dict); ++c )
+       psppire_data_store_clear_datum (model, r, c);
+    }
+#endif
 
-    psppire_case_array_set_value(store->cases, row, index, fillit, &d_in);
-  }
+  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, PangoFontDescription *fd)
+psppire_data_store_set_font (PsppireDataStore *store,
+                           const PangoFontDescription *fd)
 {
   g_return_if_fail (store);
   g_return_if_fail (PSPPIRE_IS_DATA_STORE (store));
 
   store->font_desc = fd;
-  g_sheet_model_range_changed (G_SHEET_MODEL(store),
+#if 0
+  store->width_of_m = calc_m_width (fd);
+#endif
+  g_signal_emit (store, signals [FONT_CHANGED], 0);
+
+
+  g_sheet_model_range_changed (G_SHEET_MODEL (store),
                                 -1, -1, -1, -1);
 }
 
 
 void
-psppire_data_store_show_labels(PsppireDataStore *store, gboolean show_labels)
+psppire_data_store_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;
 
-  g_sheet_model_range_changed (G_SHEET_MODEL(store),
+  g_sheet_model_range_changed (G_SHEET_MODEL (store),
                                 -1, -1, -1, -1);
 }
 
 
-
-static gboolean 
-write_case(const struct ccase *cc, 
-          gpointer aux)
+void
+psppire_data_store_clear (PsppireDataStore *data_store)
 {
-  struct sfm_writer *writer = aux;
+  psppire_case_file_clear (data_store->case_file);
 
-  if ( ! sfm_write_case(writer, cc) )
-    return FALSE;
+  psppire_dict_clear (data_store->dict);
+}
 
 
-  return TRUE;
-}
 
-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)
 {
-  const struct sfm_write_options wo = {
-    true, /* writeable */
-    false, /* dont compress */
-    3 /* version */
-  }; 
+  struct casereader *reader ;
 
-  struct sfm_writer *writer ;
+  reader = psppire_case_file_make_reader (ds->case_file);
 
-  g_assert(handle);
+  return reader;
+}
 
-  writer = sfm_open_writer(handle, store->dict->dict, wo);
 
-  if ( ! writer) 
-    return;
 
-  psppire_case_array_iterate_case(store->cases, write_case, writer);
+/* Column related funcs */
 
-  sfm_close_writer(writer);
-}
+static glong
+geometry_get_column_count (const GSheetColumn *geom)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
 
+  return MAX (MIN_COLUMNS, psppire_dict_get_var_cnt (ds->dict));
+}
 
 
-/* Column related funcs */
 
 static gint
-geometry_get_column_count(const GSheetColumn *geom)
+geometry_get_width (const GSheetColumn *geom, glong unit)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  const struct variable *pv ;
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
+
+  if ( unit >= psppire_dict_get_var_cnt (ds->dict) )
+    return ds->width_of_m * 8 ;
 
-  return max(MIN_COLUMNS, psppire_dict_get_var_cnt(ds->dict));
+  pv = psppire_dict_get_variable (ds->dict, unit);
+
+  if ( pv == NULL )
+    return ds->width_of_m * 8 ;
+
+  return ds->width_of_m * var_get_display_width (pv);
 }
 
-/* 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 void
+geometry_set_width (GSheetColumn *geom, glong unit, gint width)
 {
-  GtkSheetCellAttr attributes;
-  PangoRectangle rect;
-  /* FIXME: make this a member of the data store */
-  static PangoLayout *layout = 0;
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
 
+  struct variable *pv = psppire_dict_get_variable (ds->dict, unit);
 
-  gtk_sheet_get_attributes(sheet, row, col, &attributes);
+  var_set_display_width (pv, width / ds->width_of_m );
+}
 
-  if (! layout ) 
-    layout = gtk_widget_create_pango_layout (GTK_WIDGET(sheet), "M");
-  
-  pango_layout_set_font_description (layout, 
-                                    attributes.font_desc);
 
-  pango_layout_get_extents (layout, NULL, &rect);
 
-#if 0
-  g_object_unref(G_OBJECT(layout));
-#endif
+static GtkJustification
+geometry_get_justification (const GSheetColumn *geom, glong unit)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
+  const struct variable *pv ;
 
-  return PANGO_PIXELS(rect.width);
-}
 
+  if ( unit >= psppire_dict_get_var_cnt (ds->dict) )
+    return GTK_JUSTIFY_LEFT;
 
-/* Return the number of pixels corresponding to a column of 
-   WIDTH characters */
-static inline guint 
-columnWidthToPixels(GtkSheet *sheet, gint column, guint width)
-{
-  return (M_width(sheet, 0, column) * width);
+  pv = psppire_dict_get_variable (ds->dict, unit);
+
+  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)
+static const gchar null_var_name[]=N_("var");
+
+static gchar *
+geometry_get_column_button_label (const GSheetColumn *geom, glong unit)
 {
-  const struct PsppireVariable *pv ;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  gchar *text;
+  struct variable *pv ;
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
 
-  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
-    return 75;
+  if ( unit >= psppire_dict_get_var_cnt (ds->dict) )
+    return g_locale_to_utf8 (null_var_name, -1, 0, 0, 0);
 
-  /* FIXME: We can optimise this by caching the widths until they're resized */
-  pv = psppire_dict_get_variable(ds->dict, unit);
+  pv = psppire_dict_get_variable (ds->dict, unit);
 
-  return columnWidthToPixels(sheet, unit, psppire_variable_get_columns(pv));
+  text =  pspp_locale_to_utf8 (var_get_name (pv), -1, 0);
+
+  return text;
 }
 
 
+static gchar *
+geometry_get_column_subtitle (const GSheetColumn *geom, glong unit)
+{
+  gchar *text;
+  const struct variable *v ;
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
 
+  if ( unit >= psppire_dict_get_var_cnt (ds->dict) )
+    return NULL;
 
-static void
-geometry_set_width(GSheetColumn *geom, gint unit, gint width, GtkSheet *sheet)
-{
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  v = psppire_dict_get_variable (ds->dict, unit);
+
+  if ( ! var_has_label (v))
+    return NULL;
 
-  struct PsppireVariable *pv = psppire_dict_get_variable(ds->dict, unit);
+  text =  pspp_locale_to_utf8 (var_get_label (v), -1, 0);
 
-  psppire_variable_set_columns(pv, width / M_width(sheet, 1, unit));
+  return text;
 }
 
 
+static gboolean
+geometry_get_sensitivity (const GSheetColumn *geom, glong unit)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
 
-static GtkJustification
-geometry_get_justification(const GSheetColumn *geom, gint unit)
+  return (unit < psppire_dict_get_var_cnt (ds->dict));
+}
+
+
+static void
+psppire_data_store_sheet_column_init (GSheetColumnIface *iface)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
-  const struct PsppireVariable *pv ;
+  iface->get_column_count = geometry_get_column_count;
+  iface->get_width = geometry_get_width;
+  iface->set_width = geometry_set_width;
+  iface->get_visibility = always_true;
+  iface->get_sensitivity = geometry_get_sensitivity;
+  iface->get_justification = geometry_get_justification;
+  iface->get_button_label = geometry_get_column_button_label;
+  iface->get_subtitle = geometry_get_column_subtitle;
+}
 
 
-  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
-    return GTK_JUSTIFY_LEFT;
+/* Row related funcs */
 
-  pv = psppire_dict_get_variable(ds->dict, unit);
+static glong
+geometry_get_row_count (const GSheetRow *geom, gpointer data)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
 
-  /* Kludge: Happily GtkJustification is defined similarly
-     to enum alignment from pspp/variable.h */
-  return psppire_variable_get_alignment(pv);
+  return TRAILING_ROWS + psppire_case_file_get_case_count (ds->case_file);
 }
 
 
-static const gchar null_var_name[]=_("var");
-static const gchar *
-geometry_get_button_label(const GSheetColumn *geom, gint unit)
+static gint
+geometry_get_height (const GSheetRow *geom, glong unit, gpointer data)
 {
-  struct PsppireVariable *pv ;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  return 25;
+}
 
-  if ( unit >= psppire_dict_get_var_cnt(ds->dict) )
-    return null_var_name;
 
-  pv = psppire_dict_get_variable(ds->dict, unit);
+static gboolean
+geometry_get_row_sensitivity (const GSheetRow *geom, glong unit, gpointer data)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
+
 
-  return psppire_variable_get_name(pv);
+  return (unit < psppire_case_file_get_case_count (ds->case_file));
 }
 
 
-static gboolean
-geometry_get_sensitivity(const GSheetColumn *geom, gint unit)
+static gchar *
+geometry_get_row_button_label (const GSheetRow *geom, glong unit, gpointer data)
 {
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE(geom);
+  gchar *text;
+  gchar *s;
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
+
+  if ( unit >
+       TRAILING_ROWS + psppire_case_file_get_case_count (ds->case_file))
+    return 0;
 
+  s = g_strdup_printf (_("%ld"), unit + FIRST_CASE_NUMBER);
 
-  return (unit < psppire_dict_get_var_cnt(ds->dict));
+  text =  pspp_locale_to_utf8 (s, -1, 0);
+
+  g_free (s);
+
+  return text;
 }
 
 
 static void
-psppire_data_store_sheet_column_init (GSheetColumnIface *iface)
+psppire_data_store_sheet_row_init (GSheetRowIface *iface)
 {
-  iface->get_column_count = geometry_get_column_count;
-  iface->get_width = geometry_get_width;
-  iface->set_width = geometry_set_width;
+  iface->get_row_count = geometry_get_row_count;
+
+  iface->get_height = geometry_get_height;
+  iface->set_height = 0;
   iface->get_visibility = always_true;
-  iface->get_sensitivity = geometry_get_sensitivity;
-  iface->get_justification = geometry_get_justification;
+  iface->get_sensitivity = geometry_get_row_sensitivity;
 
-  iface->get_button_label = geometry_get_button_label;
+  iface->get_button_label = geometry_get_row_button_label;
 }