Remove unnecessary gpointer variable from gsheet-row-iface
[pspp-builds.git] / src / ui / gui / psppire-data-store.c
index 6af1b2246088243905add8ac7d9468df4a403b64..47292d92fc6c12391eb1f2107df97ab595852741 100644 (file)
@@ -1,11 +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
 
-   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,
@@ -14,9 +12,7 @@
    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>
 #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 <gtksheet/gsheet-row-iface.h>
 
 #include <pango/pango-context.h>
 
 #include <data/value-labels.h>
 #include <data/data-in.h>
 
-#include <data/file-handle-def.h>
-#include <data/sys-file-writer.h>
-
-
 
 static void psppire_data_store_init            (PsppireDataStore      *data_store);
 static void psppire_data_store_class_init      (PsppireDataStoreClass *class);
@@ -57,14 +48,10 @@ static void psppire_data_store_sheet_column_init (GSheetColumnIface *iface);
 static void psppire_data_store_sheet_row_init (GSheetRowIface *iface);
 
 static void psppire_data_store_finalize        (GObject           *object);
-
-static 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 void psppire_data_store_dispose        (GObject           *object);
 
 static gboolean psppire_data_store_clear_datum (GSheetModel *model,
-                                         gint row, gint column);
+                                         glong row, glong column);
 
 
 #define MIN_COLUMNS 10
@@ -151,6 +138,7 @@ 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 [FONT_CHANGED] =
     g_signal_new ("font_changed",
@@ -165,7 +153,7 @@ psppire_data_store_class_init (PsppireDataStoreClass *class)
 
 
 
-static gint
+static glong
 psppire_data_store_get_var_count (const GSheetModel *model)
 {
   const PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
@@ -173,39 +161,69 @@ psppire_data_store_get_var_count (const GSheetModel *model)
   return psppire_dict_get_var_cnt (store->dict);
 }
 
-static gint
-psppire_data_store_get_case_count (const GSheetModel *model)
+inline casenumber
+psppire_data_store_get_case_count (const PsppireDataStore *store)
 {
-  const PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
-
   return psppire_case_file_get_case_count (store->case_file);
 }
 
+size_t
+psppire_data_store_get_value_count (const PsppireDataStore *store)
+{
+  return psppire_dict_get_value_cnt (store->dict);
+}
+
+inline casenumber
+psppire_data_store_get_case_count_wrapper (const GSheetModel *model)
+{
+  const PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
+  return psppire_data_store_get_case_count (store);
+}
 
 static void
 psppire_data_store_init (PsppireDataStore *data_store)
 {
   data_store->dict = 0;
-  data_store->case_file = 0;
+  data_store->case_file = NULL;
   data_store->width_of_m = 10;
+  data_store->dispose_has_run = FALSE;
 }
 
 const PangoFontDescription *
 psppire_data_store_get_font_desc (const GSheetModel *model,
-                             gint row, gint column)
+                             glong row, glong column)
 {
   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->free_strings = TRUE;
-  iface->get_string = psppire_data_store_get_string;
-  iface->set_string = psppire_data_store_set_string;
+  iface->get_string = psppire_data_store_get_string_wrapper;
+  iface->set_string = psppire_data_store_set_string_wrapper;
   iface->clear_datum = psppire_data_store_clear_datum;
   iface->is_editable = NULL;
   iface->is_visible = NULL;
@@ -214,7 +232,7 @@ psppire_data_store_sheet_model_init (GSheetModelIface *iface)
   iface->get_font_desc = psppire_data_store_get_font_desc;
   iface->get_cell_border = NULL;
   iface->get_column_count = psppire_data_store_get_var_count;
-  iface->get_row_count = psppire_data_store_get_case_count;
+  iface->get_row_count = psppire_data_store_get_case_count_wrapper;
 }
 
 static
@@ -225,7 +243,8 @@ gboolean always_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  ;
 
@@ -240,7 +259,7 @@ delete_cases_callback (GtkWidget *w, gint first, gint n_cases, gpointer data)
 
 
 static void
-insert_case_callback (GtkWidget *w, gint casenum, gpointer data)
+insert_case_callback (GtkWidget *w, casenumber casenum, gpointer data)
 {
   PsppireDataStore *store  ;
 
@@ -271,30 +290,27 @@ changed_case_callback (GtkWidget *w, gint casenum, gpointer data)
 }
 
 
+/*
+   A callback which occurs after a variable has been deleted.
+ */
 static void
-delete_variables_callback (GObject *obj, gint var_num, gint n_vars, 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);
-
-  store  = PSPPIRE_DATA_STORE (data);
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
 
-  g_sheet_model_columns_deleted (G_SHEET_MODEL (store), var_num, n_vars);
+  g_sheet_model_columns_deleted (G_SHEET_MODEL (store), dict_index, 1);
 
   g_sheet_column_columns_changed (G_SHEET_COLUMN (store),
-                                  var_num, -1);
+                                  dict_index, -1);
 }
 
 
 static void
 variable_changed_callback (GObject *obj, gint var_num, gpointer data)
 {
-  PsppireDataStore *store;
-
-  g_return_if_fail (data);
-
-  store  = PSPPIRE_DATA_STORE (data);
+  PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
 
   g_sheet_column_columns_changed (G_SHEET_COLUMN (store),
                                  var_num, 1);
@@ -342,13 +358,16 @@ static void
 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_case_file_insert_values (store->case_file, adjustment,
+                                    new_val_width - adjustment +
+                                    var_get_case_index(v));
 }
 
 
@@ -373,6 +392,53 @@ psppire_data_store_new (PsppireDict *dict)
 }
 
 
+void
+psppire_data_store_set_case_file (PsppireDataStore *ds,
+                                 PsppireCaseFile *cf)
+{
+  gint i;
+  if ( ds->case_file)  g_object_unref (ds->case_file);
+
+
+  ds->case_file = cf;
+
+  g_sheet_model_range_changed (G_SHEET_MODEL (ds),
+                              -1, -1, -1, -1);
+
+  for (i = 0 ; i < n_cf_signals ; ++i )
+    {
+      if ( ds->cf_handler_id [i] > 0 )
+       g_signal_handler_disconnect (ds->case_file,
+                                    ds->cf_handler_id[i]);
+    }
+
+
+  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]);
+         }
+      }
+
+  ds->cf_handler_id [CASES_DELETED] =
+    g_signal_connect (ds->case_file, "cases-deleted",
+                     G_CALLBACK (delete_cases_callback),
+                     ds);
+
+  ds->cf_handler_id [CASE_INSERTED] =
+    g_signal_connect (ds->case_file, "case-inserted",
+                     G_CALLBACK (insert_case_callback),
+                     ds);
+
+  ds->cf_handler_id [CASE_CHANGED] =
+    g_signal_connect (ds->case_file, "case-changed",
+                     G_CALLBACK (changed_case_callback),
+                     ds);
+}
+
 
 /**
  * psppire_data_store_replace_set_dictionary:
@@ -385,76 +451,112 @@ psppire_data_store_new (PsppireDict *dict)
 void
 psppire_data_store_set_dictionary (PsppireDataStore *data_store, PsppireDict *dict)
 {
+  int i;
+
+  /* Disconnect any existing handlers */
+  if ( data_store->dict )
+    for (i = 0 ; i < n_dict_signals; ++i )
+      {
+       g_signal_handler_disconnect (data_store->dict,
+                                    data_store->dict_handler_id[i]);
+      }
+
   data_store->dict = dict;
 
-  if ( data_store->case_file)
+  if ( dict != NULL)
     {
-      g_object_unref (data_store->case_file);
-      data_store->case_file = 0;
+
+      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);
     }
 
-  data_store->case_file = psppire_case_file_new ();
 
 
+  /* The entire model has changed */
+  g_sheet_model_range_changed (G_SHEET_MODEL (data_store), -1, -1, -1, -1);
 
-  g_signal_connect (data_store->case_file, "cases-deleted",
-                  G_CALLBACK (delete_cases_callback),
-                  data_store);
+  g_sheet_column_columns_changed (G_SHEET_COLUMN (data_store), 0, -1);
 
-  g_signal_connect (data_store->case_file, "case-inserted",
-                  G_CALLBACK (insert_case_callback),
-                  data_store);
 
+  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]);
+         }
+      }
+}
 
-  g_signal_connect (data_store->case_file, "case-changed",
-                  G_CALLBACK (changed_case_callback),
-                  data_store);
+static void
+psppire_data_store_finalize (GObject *object)
+{
 
-  g_signal_connect (dict, "variable-inserted",
-                  G_CALLBACK (insert_variable_callback),
-                  data_store);
+  /* must chain up */
+  (* parent_class->finalize) (object);
+}
 
-  g_signal_connect (dict, "variables-deleted",
-                  G_CALLBACK (delete_variables_callback),
-                  data_store);
 
-  g_signal_connect (dict, "variable-changed",
-                  G_CALLBACK (variable_changed_callback),
-                  data_store);
+static void
+psppire_data_store_dispose (GObject *object)
+{
+  PsppireDataStore *ds = PSPPIRE_DATA_STORE (object);
 
+  if (ds->dispose_has_run)
+    return;
 
-  g_signal_connect (dict, "dict-size-changed",
-                   G_CALLBACK (dict_size_change_callback),
-                   data_store);
+  if (ds->case_file) g_object_unref (ds->case_file);
 
-  /* The entire model has changed */
-  g_sheet_model_range_changed (G_SHEET_MODEL (data_store), -1, -1, -1, -1);
+  /* must chain up */
+  (* parent_class->dispose) (object);
 
-  g_sheet_column_columns_changed (G_SHEET_COLUMN (data_store), 0, -1);
+  ds->dispose_has_run = TRUE;
 }
 
-static void
-psppire_data_store_finalize (GObject *object)
+
+gboolean
+psppire_data_store_delete_cases (PsppireDataStore *ds,
+                                casenumber first, casenumber count)
 {
+  g_return_val_if_fail (ds, FALSE);
 
-  /* must chain up */
-  (* parent_class->finalize) (object);
+  return psppire_case_file_delete_cases (ds->case_file, count, first);
 }
 
 
 
 /* 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;
   g_return_val_if_fail (ds, FALSE);
 
+  val_cnt = datasheet_get_column_cnt (ds->case_file->datasheet) ;
 
-  /* Opportunity for optimisation exists here when creating a blank case */
-  val_cnt = casefile_get_value_cnt (ds->case_file->flexifile) ;
+  g_return_val_if_fail (val_cnt > 0, FALSE);
+
+  g_return_val_if_fail (posn <= psppire_data_store_get_case_count (ds), FALSE);
 
   case_create (&cc, val_cnt);
 
@@ -477,16 +579,15 @@ psppire_data_store_insert_new_case (PsppireDataStore *ds, gint posn)
 }
 
 
-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);
@@ -505,19 +606,19 @@ psppire_data_store_get_string (const GSheetModel *model, gint row, gint column)
 
   g_assert (idx >= 0);
 
-  v = psppire_case_file_get_value (store->case_file, row, idx);
+  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 struct val_labs * vl = var_get_value_labels (pv);
-
-      const gchar *label;
-      if ( (label = val_labs_find (vl, *v)) )
-       {
+      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);
@@ -539,14 +640,14 @@ 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)
-
+                                         glong row, glong col)
 {
   PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
 
@@ -571,29 +672,21 @@ psppire_data_store_clear_datum (GSheetModel *model,
    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);
-
+  glong n_cases;
   const struct variable *pv = psppire_dict_get_variable (store->dict, col);
   g_return_val_if_fail (pv, FALSE);
 
-#if 0
-  /* Allow the user to insert a lot of blank cases, simply by skipping rows */
-  for (r = psppire_case_file_get_case_count (store->case_file); r <= row ; ++r)
-    {
-
-      gint c;
+  n_cases = psppire_data_store_get_case_count (store);
 
-      psppire_case_array_insert_case (store->cases, r, 0, 0);
+  if ( row > n_cases)
+    return FALSE;
 
-
-      for (c = 0 ; c < psppire_dict_get_var_cnt (store->dict); ++c )
-       psppire_data_store_clear_datum (model, r, c);
-    }
-#endif
+  if (row == n_cases)
+    psppire_data_store_insert_new_case (store, row);
 
   psppire_case_file_data_in (store->case_file, row,
                              var_get_case_index (pv), ss_cstr (text),
@@ -635,62 +728,46 @@ psppire_data_store_show_labels (PsppireDataStore *store, gboolean show_labels)
 }
 
 
-
-/* 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)
+psppire_data_store_clear (PsppireDataStore *data_store)
 {
-  gint i, var_cnt;
-  const struct sfm_write_options wo = {
-    true, /* writeable */
-    false, /* dont compress */
-    3 /* version */
-  };
-
-  struct sfm_writer *writer ;
-
-  g_assert (handle);
+  psppire_case_file_clear (data_store->case_file);
 
-  writer = sfm_open_writer (handle, store->dict->dict, wo);
+  psppire_dict_clear (data_store->dict);
+}
 
-  if ( ! writer)
-    return;
 
 
-  var_cnt = psppire_data_store_get_var_count (G_SHEET_MODEL (store));
+/* Return a casereader made from this datastore */
+struct casereader *
+psppire_data_store_get_reader (PsppireDataStore *ds)
+{
+  int i;
+  struct casereader *reader ;
 
-  for (i = 0 ; i < psppire_case_file_get_case_count (store->case_file); ++i )
+  for (i = 0 ; i < n_cf_signals ; ++i )
     {
-      struct ccase c;
-
-      case_create (&c, var_cnt);
-      psppire_case_file_get_case (store->case_file, i, &c);
-      sfm_write_case (writer, &c);
-
-      case_destroy (&c);
+      g_signal_handler_disconnect (ds->case_file, ds->cf_handler_id[i]);
+      ds->cf_handler_id[i] = 0 ;
     }
 
-  sfm_close_writer (writer);
-}
-
-
+  if ( ds->dict )
+    for (i = 0 ; i < n_dict_signals; ++i )
+      {
+       g_signal_handler_block (ds->dict,
+                               ds->dict_handler_id[i]);
+      }
 
-void
-psppire_data_store_clear (PsppireDataStore *data_store)
-{
-  psppire_case_file_clear (data_store->case_file);
+  reader = psppire_case_file_make_reader (ds->case_file);
 
-  psppire_dict_clear (data_store->dict);
+  return reader;
 }
 
 
 
-
 /* Column related funcs */
 
-static gint
+static glong
 geometry_get_column_count (const GSheetColumn *geom)
 {
   PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
@@ -701,7 +778,7 @@ geometry_get_column_count (const GSheetColumn *geom)
 
 
 static gint
-geometry_get_width (const GSheetColumn *geom, gint unit)
+geometry_get_width (const GSheetColumn *geom, glong unit)
 {
   const struct variable *pv ;
   PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
@@ -718,7 +795,7 @@ geometry_get_width (const GSheetColumn *geom, gint unit)
 }
 
 static void
-geometry_set_width (GSheetColumn *geom, gint unit, gint width)
+geometry_set_width (GSheetColumn *geom, glong unit, gint width)
 {
   PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
 
@@ -730,7 +807,7 @@ geometry_set_width (GSheetColumn *geom, gint unit, gint width)
 
 
 static GtkJustification
-geometry_get_justification (const GSheetColumn *geom, gint unit)
+geometry_get_justification (const GSheetColumn *geom, glong unit)
 {
   PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
   const struct variable *pv ;
@@ -750,7 +827,7 @@ geometry_get_justification (const GSheetColumn *geom, gint unit)
 static const gchar null_var_name[]=N_("var");
 
 static gchar *
-geometry_get_column_button_label (const GSheetColumn *geom, gint unit)
+geometry_get_column_button_label (const GSheetColumn *geom, glong unit)
 {
   gchar *text;
   struct variable *pv ;
@@ -767,8 +844,29 @@ geometry_get_column_button_label (const GSheetColumn *geom, gint unit)
 }
 
 
+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;
+
+  v = psppire_dict_get_variable (ds->dict, unit);
+
+  if ( ! var_has_label (v))
+    return NULL;
+
+  text =  pspp_locale_to_utf8 (var_get_label (v), -1, 0);
+
+  return text;
+}
+
+
 static gboolean
-geometry_get_sensitivity (const GSheetColumn *geom, gint unit)
+geometry_get_sensitivity (const GSheetColumn *geom, glong unit)
 {
   PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
 
@@ -786,29 +884,48 @@ psppire_data_store_sheet_column_init (GSheetColumnIface *iface)
   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;
 }
 
 
 /* Row related funcs */
 
-static gint
-geometry_get_row_count (const GSheetRow *geom, gpointer data)
+static glong
+geometry_get_row_count (const GSheetRow *geom)
 {
   PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
 
   return TRAILING_ROWS + psppire_case_file_get_case_count (ds->case_file);
 }
 
+#define ROW_HEIGHT 25
 
 static gint
-geometry_get_height (const GSheetRow *geom, gint unit, gpointer data)
+geometry_get_height (const GSheetRow *geom, glong unit)
+{
+  return ROW_HEIGHT;
+}
+
+static guint
+geometry_get_top_ypixel (const GSheetRow *geo, glong row)
 {
-  return 25;
+  return row * ROW_HEIGHT;
+}
+
+static glong
+geometry_pixel_to_row (const GSheetRow *geo, guint pixel)
+{
+  glong row  = pixel / ROW_HEIGHT;
+
+  if (row >= geometry_get_row_count (geo))
+    row = geometry_get_row_count (geo) - 1;
+
+  return row;
 }
 
 
 static gboolean
-geometry_get_row_sensitivity (const GSheetRow *geom, gint unit, gpointer data)
+geometry_get_row_sensitivity (const GSheetRow *geom, glong unit)
 {
   PsppireDataStore *ds = PSPPIRE_DATA_STORE (geom);
 
@@ -818,7 +935,7 @@ geometry_get_row_sensitivity (const GSheetRow *geom, gint unit, gpointer data)
 
 
 static gchar *
-geometry_get_row_button_label (const GSheetRow *geom, gint unit, gpointer data)
+geometry_get_row_button_label (const GSheetRow *geom, glong unit)
 {
   gchar *text;
   gchar *s;
@@ -828,7 +945,7 @@ geometry_get_row_button_label (const GSheetRow *geom, gint unit, gpointer data)
        TRAILING_ROWS + psppire_case_file_get_case_count (ds->case_file))
     return 0;
 
-  s = g_strdup_printf (_("%d"), unit);
+  s = g_strdup_printf (_("%ld"), unit + FIRST_CASE_NUMBER);
 
   text =  pspp_locale_to_utf8 (s, -1, 0);
 
@@ -837,7 +954,6 @@ geometry_get_row_button_label (const GSheetRow *geom, gint unit, gpointer data)
   return text;
 }
 
-
 static void
 psppire_data_store_sheet_row_init (GSheetRowIface *iface)
 {
@@ -847,7 +963,7 @@ psppire_data_store_sheet_row_init (GSheetRowIface *iface)
   iface->set_height = 0;
   iface->get_visibility = always_true;
   iface->get_sensitivity = geometry_get_row_sensitivity;
-
+  iface->top_ypixel = geometry_get_top_ypixel;
+  iface->pixel_to_row = geometry_pixel_to_row;
   iface->get_button_label = geometry_get_row_button_label;
 }
-