Merge remote-tracking branch 'origin/master' into sheet
authorJohn Darrington <john@darrington.wattle.id.au>
Mon, 15 Aug 2016 12:16:26 +0000 (14:16 +0200)
committerJohn Darrington <john@darrington.wattle.id.au>
Mon, 15 Aug 2016 12:16:26 +0000 (14:16 +0200)
1  2 
configure.ac
src/ui/gui/psppire-data-editor.c

diff --combined configure.ac
index 401779f5da0f5e8c64e94b88f45642d9b71f136c,c5b14901b2e74336bd11841f2611db0b38a31715..f921e6df0bb2792f14b7e16f301a5f12e3590f8a
@@@ -60,7 -60,7 +60,7 @@@ f
  
  dnl Xmllint is used in the rules to build the documentation.  It is not actually necessary,
  dnl but is used for post-build consistency checks.  Thus, non-developers can live without it.
- dnl However for it to be usefull, it needs to be a certain version and have certain features.
+ dnl However for it to be useful, it needs to be a certain version and have certain features.
  dnl The macros below check that it the xmllint available is up to scratch.  If it isn't
  dnl then a dummy /bin/echo is subsituted instead.
  
@@@ -90,8 -90,8 +90,8 @@@ if test "$with_cairo" != no && test "$w
    PKG_CHECK_MODULES([GTKSOURCEVIEW], [gtksourceview-3.0 >= 3.4.2], [],
      [PSPP_REQUIRED_PREREQ([gtksourceview 3.0 version 3.4.2 or later (or use --without-gui)])])
  
 -  PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.32], [],
 -    [PSPP_REQUIRED_PREREQ([glib 2.0 version 2.32 or later (or use --without-gui)])])
 +  PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.44], [],
 +    [PSPP_REQUIRED_PREREQ([glib 2.0 version 2.44 or later (or use --without-gui)])])
  
    AC_ARG_VAR([GLIB_GENMARSHAL])
    AC_CHECK_PROGS([GLIB_GENMARSHAL], [glib-genmarshal])
@@@ -311,7 -311,7 +311,7 @@@ PSPP_READLIN
  dnl Checks for header files.
  AC_CHECK_HEADERS([sys/wait.h fpu_control.h ieeefp.h fenv.h pwd.h])
  
- dnl Some systems dont have SIGWINCH
+ dnl Some systems don't have SIGWINCH
  AC_CHECK_DECLS([SIGWINCH], [], [],
            [#include <signal.h>
            /* NetBSD declares sys_siglist in unistd.h.  */
index f3a3059cb906835d69fe3d6d9d0af62f0c078b86,85fa58b331b21f26317d44681b025546983fd4ba..40a2c490b4460fcdb5a874dd28fb1d648ce3f55f
@@@ -1,5 -1,5 +1,5 @@@
  /* PSPPIRE - a graphical user interface for PSPP.
 -   Copyright (C) 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
 +   Copyright (C) 2008, 2009, 2010, 2011, 2012, 2016 Free Software Foundation, Inc.
  
     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
  #include "libpspp/range-set.h"
  #include "libpspp/str.h"
  #include "ui/gui/helper.h"
 -#include "ui/gui/pspp-sheet-selection.h"
 -#include "ui/gui/psppire-data-sheet.h"
  #include "ui/gui/psppire-data-store.h"
  #include "ui/gui/psppire-value-entry.h"
 -#include "ui/gui/psppire-var-sheet.h"
  #include "ui/gui/psppire-conf.h"
 +#include "ui/gui/psppire-var-sheet-header.h"
 +
 +#include "value-variant.h"
 +
 +
 +#include "ui/gui/efficient-sheet/jmd-sheet.h"
 +#include "ui/gui/efficient-sheet/jmd-sheet-body.h"
  
  #include <gettext.h>
  #define _(msgid) gettext (msgid)
  
 -#define FOR_EACH_DATA_SHEET(DATA_SHEET, IDX, DATA_EDITOR)       \
 -  for ((IDX) = 0;                                               \
 -       (IDX) < 4                                                \
 -         && ((DATA_SHEET) = PSPPIRE_DATA_SHEET (                \
 -               (DATA_EDITOR)->data_sheets[IDX])) != NULL;       \
 -       (IDX)++)
 +
 +static GtkCellRenderer *
 +create_spin_renderer (GType type)
 +{
 +  GtkCellRenderer *r = gtk_cell_renderer_spin_new ();
 +      
 +  GtkAdjustment *adj = gtk_adjustment_new (0,
 +                                         0, G_MAXDOUBLE,
 +                                         1, 1,
 +                                         0);
 +  g_object_set (r,
 +              "adjustment", adj,
 +              NULL);
 +  
 +  return r;
 +}
 +
 +static GtkCellRenderer *
 +create_combo_renderer (GType type)
 +{
 +  GtkListStore *list_store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
 +  
 +  GEnumClass *ec = g_type_class_ref (type);
 +  
 +  const GEnumValue *ev ;
 +  for (ev = ec->values; ev->value_name; ++ev)
 +    {
 +      GtkTreeIter iter;
 +
 +      gtk_list_store_append (list_store, &iter);
 +
 +      gtk_list_store_set (list_store, &iter,
 +                        0, ev->value,
 +                        1, gettext (ev->value_nick),
 +                        -1);
 +    }
 +
 +  GtkCellRenderer *r = gtk_cell_renderer_combo_new ();
 +
 +  g_object_set (r,
 +              "model", list_store,
 +              "text-column", 1,
 +              "has-entry", TRUE,
 +              NULL);
 +
 +  return r;
 +}
 +
 +GtkCellRenderer *xx ;
 +GtkCellRenderer *column_width_renderer ;
 +GtkCellRenderer *measure_renderer ;
 +GtkCellRenderer *alignment_renderer ;
 +
 +
 +
 +static GtkCellRenderer *
 +select_renderer_func (gint col, gint row, GType type)
 +{
 +  if (!xx)
 +    xx = create_spin_renderer (type);
 +
 +  if (col == DICT_TVM_COL_ROLE && !column_width_renderer)
 +    column_width_renderer = create_combo_renderer (type);
 +
 +  if (col == DICT_TVM_COL_MEASURE && !measure_renderer)
 +    measure_renderer = create_combo_renderer (type);
 +
 +  if (col == DICT_TVM_COL_ALIGNMENT && !alignment_renderer)
 +    alignment_renderer = create_combo_renderer (type);
 +
 +  switch  (col)
 +    {
 +    case DICT_TVM_COL_WIDTH:
 +    case DICT_TVM_COL_DECIMAL:
 +    case DICT_TVM_COL_COLUMNS:
 +      return xx;
 +      
 +    case DICT_TVM_COL_ALIGNMENT:
 +      return alignment_renderer;
 +
 +    case DICT_TVM_COL_MEASURE:
 +      return measure_renderer;
 +      
 +    case DICT_TVM_COL_ROLE:
 +      return column_width_renderer;
 +    }
 +  
 +  return NULL;
 +}
 +
  
  static void psppire_data_editor_class_init          (PsppireDataEditorClass *klass);
  static void psppire_data_editor_init                (PsppireDataEditor      *de);
@@@ -207,57 -119,13 +207,57 @@@ enu
  static void
  psppire_data_editor_refresh_model (PsppireDataEditor *de)
  {
 -  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (de->var_sheet);
 -  PsppireDataSheet *data_sheet;
 -  int i;
 +}
 +
 +static void
 +change_var_property (PsppireDict *dict, gint col, gint row, GValue *value)
 +{
 +  /* Return the IDXth variable */
 +  struct variable *var =  psppire_dict_get_variable (dict, row);
  
 -  FOR_EACH_DATA_SHEET (data_sheet, i, de)
 -    psppire_data_sheet_set_data_store (data_sheet, de->data_store);
 -  psppire_var_sheet_set_dictionary (var_sheet, de->dict);
 +  switch (col)
 +    {
 +    case DICT_TVM_COL_NAME:
 +      dict_rename_var (dict->dict, var, g_value_get_string (value));
 +      break;
 +    case DICT_TVM_COL_LABEL:
 +      var_set_label (var, g_value_get_string (value));
 +      break;
 +    case DICT_TVM_COL_COLUMNS:
 +      var_set_display_width (var, g_value_get_int (value));
 +      break;
 +    case DICT_TVM_COL_MEASURE:
 +      var_set_measure (var, g_value_get_enum (value));
 +      break;
 +    case DICT_TVM_COL_ALIGNMENT:
 +      var_set_alignment (var, g_value_get_enum (value));
 +      break;
 +    case DICT_TVM_COL_ROLE:
 +      var_set_role (var, g_value_get_enum (value));
 +      break;
 +    default:
 +      g_message ("Changing col %d of var sheet not yet supported", col);
 +      break;
 +    }
 +}
 +
 +static void
 +change_data_value (PsppireDataStore *store, gint col, gint row, GValue *value)
 +{
 +  const struct variable *var = psppire_dict_get_variable (store->dict, col);
 +
 +  if (NULL == var)
 +    return;
 +
 +  union value v;
 +
 +  GVariant *vrnt = g_value_get_variant (value);
 +  
 +  value_variant_get (&v, vrnt);
 +  
 +  psppire_data_store_set_value (store, row, var, &v);
 +
 +  value_destroy_from_variant (&v, vrnt);
  }
  
  static void
@@@ -267,6 -135,8 +267,6 @@@ psppire_data_editor_set_property (GObje
                                  GParamSpec      *pspec)
  {
    PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
 -  PsppireDataSheet *data_sheet;
 -  int i;
  
    switch (prop_id)
      {
  
        de->data_store = g_value_get_pointer (value);
        g_object_ref (de->data_store);
 +      g_print ("NEW STORE\n");
 +
 +      g_object_set (de->data_sheet, "data-model", de->data_store, NULL);
        psppire_data_editor_refresh_model (de);
  
 +      g_signal_connect_swapped (de->data_sheet, "value-changed",
 +                              G_CALLBACK (change_data_value), de->data_store);
 +      
        g_signal_connect_swapped (de->data_store, "case-changed",
                                  G_CALLBACK (refresh_entry), de);
  
        de->dict = g_value_get_pointer (value);
        g_object_ref (de->dict);
  
 -      psppire_var_sheet_set_dictionary (PSPPIRE_VAR_SHEET (de->var_sheet),
 -                                        de->dict);
 +      g_object_set (de->data_sheet, "hmodel", de->dict, NULL);
 +      g_object_set (de->var_sheet, "data-model", de->dict, NULL);
 +      g_signal_connect_swapped (de->var_sheet, "value-changed",
 +                              G_CALLBACK (change_var_property), de->dict);
 +
        break;
      case PROP_VALUE_LABELS:
 -      FOR_EACH_DATA_SHEET (data_sheet, i, de)
 -        psppire_data_sheet_set_value_labels (data_sheet,
 -                                          g_value_get_boolean (value));
        break;
  
      default:
@@@ -337,6 -201,9 +337,6 @@@ psppire_data_editor_get_property (GObje
        g_value_set_pointer (value, de->dict);
        break;
      case PROP_VALUE_LABELS:
 -      g_value_set_boolean (value,
 -                           psppire_data_sheet_get_value_labels (
 -                             PSPPIRE_DATA_SHEET (de->data_sheets[0])));
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@@ -428,120 -295,396 +428,123 @@@ psppire_data_editor_class_init (Psppire
  
  }
  
 -static gboolean
 -on_data_sheet_var_double_clicked (PsppireDataSheet *data_sheet,
 -                                  gint dict_index,
 -                                  PsppireDataEditor *de)
 +
 +static void
 +on_var_sheet_var_double_clicked (void *var_sheet, gint dict_index,
 +                                 PsppireDataEditor *de)
  {
    gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
 -                                 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
 -
 -  psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
 -                                   dict_index);
 +                                 PSPPIRE_DATA_EDITOR_DATA_VIEW);
  
 -  return TRUE;
 +  jmd_sheet_scroll_to (JMD_SHEET (de->data_sheet), dict_index, -1);
  }
  
 -static gboolean
 -on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
 +
 +static void
 +on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
                                   PsppireDataEditor *de)
  {
 -  PsppireDataSheet *data_sheet;
 -
 +  
    gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
 -                                 PSPPIRE_DATA_EDITOR_DATA_VIEW);
 -
 -  data_sheet = psppire_data_editor_get_active_data_sheet (de);
 -  psppire_data_sheet_goto_variable (data_sheet, dict_index);
 +                                 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
  
 -  return TRUE;
 +  jmd_sheet_scroll_to (JMD_SHEET (de->var_sheet), -1, dict_index);
  }
  
 +
 +
  /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
     active cell or cells. */
  static void
  refresh_entry (PsppireDataEditor *de)
  {
 -  PsppireDataSheet *data_sheet = psppire_data_editor_get_active_data_sheet (de);
 -  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
 -  PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
 -
 -  gchar *ref_cell_text;
 -  GList *selected_columns, *iter;
 -  struct variable *var;
 -  gint n_cases;
 -  gint n_vars;
 -
 -  selected_columns = pspp_sheet_selection_get_selected_columns (selection);
 -  n_vars = 0;
 -  var = NULL;
 -  for (iter = selected_columns; iter != NULL; iter = iter->next)
 -    {
 -      PsppSheetViewColumn *column = iter->data;
 -      struct variable *v = g_object_get_data (G_OBJECT (column), "variable");
 -      if (v != NULL)
 -        {
 -          var = v;
 -          n_vars++;
 -        }
 -    }
 -  g_list_free (selected_columns);
 -
 -  n_cases = pspp_sheet_selection_count_selected_rows (selection);
 -  if (n_cases > 0)
 -    {
 -      /* The final row is selectable but it isn't a case (it's just used to add
 -         more cases), so don't count it. */
 -      GtkTreePath *path;
 -      gint case_count;
 -
 -      case_count = psppire_data_store_get_case_count (de->data_store);
 -      path = gtk_tree_path_new_from_indices (case_count, -1);
 -      if (pspp_sheet_selection_path_is_selected (selection, path))
 -        n_cases--;
 -      gtk_tree_path_free (path);
 -    }
 -
 -  ref_cell_text = NULL;
 -  if (n_cases == 1 && n_vars == 1)
 -    {
 -      PsppireValueEntry *value_entry = PSPPIRE_VALUE_ENTRY (de->datum_entry);
 -      struct range_set *selected_rows;
 -      gboolean show_value_labels;
 -      union value value;
 -      int width;
 -      gint row;
 -
 -      selected_rows = pspp_sheet_selection_get_range_set (selection);
 -      row = range_set_scan (selected_rows, 0);
 -      range_set_destroy (selected_rows);
 -
 -      ref_cell_text = g_strdup_printf ("%d : %s", row + 1, var_get_name (var));
 -
 -      show_value_labels = psppire_data_sheet_get_value_labels (data_sheet);
 -
 -      psppire_value_entry_set_variable (value_entry, var);
 -      psppire_value_entry_set_show_value_label (value_entry,
 -                                                show_value_labels);
 -
 -      width = var_get_width (var);
 -      value_init (&value, width);
 -      datasheet_get_value (de->data_store->datasheet,
 -                           row, var_get_case_index (var), &value);
 -      psppire_value_entry_set_value (value_entry, &value, width);
 -      value_destroy (&value, width);
 -
 -      gtk_widget_set_sensitive (de->datum_entry, TRUE);
 -    }
 -  else
 -    {
 -      if (n_cases == 0 || n_vars == 0)
 -        {
 -          ref_cell_text = NULL;
 -        }
 -      else
 -        {
 -          struct string s;
 -
 -          /* The glib string library does not understand the ' printf modifier
 -             on all platforms, but the "struct string" library does (because
 -             Gnulib fixes that problem), so use the latter.  */
 -          ds_init_empty (&s);
 -          ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
 -                         n_cases);
 -          ds_put_byte (&s, ' ');
 -          ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
 -          ds_put_byte (&s, ' ');
 -          ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
 -                                       n_vars),
 -                         n_vars);
 -          ref_cell_text = ds_steal_cstr (&s);
 -        }
 -
 -      psppire_value_entry_set_variable (PSPPIRE_VALUE_ENTRY (de->datum_entry),
 -                                        NULL);
 -      gtk_entry_set_text (
 -        GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))), "");
 -      gtk_widget_set_sensitive (de->datum_entry, FALSE);
 -    }
 +  union value val;
 +  gint row, col;
 +  jmd_sheet_get_active_cell (JMD_SHEET (de->data_sheet), &col, &row);
  
 -  gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
 -                       ref_cell_text ? ref_cell_text : "");
 -  g_free (ref_cell_text);
 -}
 +  const struct variable *var = psppire_dict_get_variable (de->dict, col);
 +  psppire_value_entry_set_variable (PSPPIRE_VALUE_ENTRY (de->datum_entry), var);
  
 -static void
 -on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
 -{
 -  PsppireDataSheet *data_sheet = psppire_data_editor_get_active_data_sheet (de);
 -  struct variable *var;
 -  union value value;
 -  int width;
 -  gint row;
 -
 -  row = psppire_data_sheet_get_current_case (data_sheet);
 -  var = psppire_data_sheet_get_current_variable (data_sheet);
 -  if (row < 0 || !var)
 +  int width = var_get_width (var);
 +  if (! psppire_data_store_get_value (PSPPIRE_DATA_STORE (de->data_store),
 +                                    row, var, &val))
      return;
  
 -  width = var_get_width (var);
 -  value_init (&value, width);
 -  if (psppire_value_entry_get_value (PSPPIRE_VALUE_ENTRY (de->datum_entry),
 -                                     &value, width))
 -    psppire_data_store_set_value (de->data_store, row, var, &value);
 -  value_destroy (&value, width);
 +  psppire_value_entry_set_value (PSPPIRE_VALUE_ENTRY (de->datum_entry),
 +                               &val, width);
 +  value_destroy (&val, width);
  }
  
  static void
 -on_data_sheet_selection_changed (PsppSheetSelection *selection,
 -                                 PsppireDataEditor *de)
 +on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
  {
 -  /* In a split view, ensure that only a single data sheet has a nonempty
 -     selection.  */
 -  if (de->split
 -      && pspp_sheet_selection_count_selected_rows (selection)
 -      && pspp_sheet_selection_count_selected_columns (selection))
 -    {
 -      PsppireDataSheet *ds;
 -      int i;
 -
 -      FOR_EACH_DATA_SHEET (ds, i, de)
 -        {
 -          PsppSheetSelection *s;
 -
 -          s = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
 -          if (s != selection)
 -            pspp_sheet_selection_unselect_all (s);
 -        }
 -    }
 -
 -  refresh_entry (de);
  }
  
 -/* Ensures that rows in the right-hand panes in the split view have the same
 -   row height as the left-hand panes.  Otherwise, the rows in the right-hand
 -   pane tend to be smaller, because the right-hand pane doesn't have buttons
 -   for case numbers. */
 -static void
 -on_data_sheet_fixed_height_notify (PsppireDataSheet *ds,
 -                                   GParamSpec *pspec,
 -                                   PsppireDataEditor *de)
 -{
 -  enum
 -    {
 -      TL = GTK_XPANED_TOP_LEFT,
 -      TR = GTK_XPANED_TOP_RIGHT,
 -      BL = GTK_XPANED_BOTTOM_LEFT,
 -      BR = GTK_XPANED_BOTTOM_RIGHT
 -    };
 -
 -  int fixed_height = pspp_sheet_view_get_fixed_height (PSPP_SHEET_VIEW (ds));
 -
 -  pspp_sheet_view_set_fixed_height (PSPP_SHEET_VIEW (de->data_sheets[TR]),
 -                                    fixed_height);
 -  pspp_sheet_view_set_fixed_height (PSPP_SHEET_VIEW (de->data_sheets[BR]),
 -                                    fixed_height);
 -}
  
  static void
  disconnect_data_sheets (PsppireDataEditor *de)
  {
 -  PsppireDataSheet *ds;
 -  int i;
 -
 -  FOR_EACH_DATA_SHEET (ds, i, de)
 -    {
 -      PsppSheetSelection *selection;
 -
 -      if (ds == NULL)
 -        {
 -          /* This can only happen if 'dispose' runs more than once. */
 -          continue;
 -        }
 -
 -      if (i == GTK_XPANED_TOP_LEFT)
 -        g_signal_handlers_disconnect_by_func (
 -          ds, G_CALLBACK (on_data_sheet_fixed_height_notify), de);
 -
 -      g_signal_handlers_disconnect_by_func (
 -        ds, G_CALLBACK (refresh_entry), de);
 -      g_signal_handlers_disconnect_by_func (
 -        ds, G_CALLBACK (on_data_sheet_var_double_clicked), de);
 -
 -      selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
 -      g_signal_handlers_disconnect_by_func (
 -        selection, G_CALLBACK (on_data_sheet_selection_changed), de);
 -
 -      de->data_sheets[i] = NULL;
 -    }
 -}
 -
 -static GtkWidget *
 -make_data_sheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
 -                 gboolean show_value_labels)
 -{
 -  PsppSheetSelection *selection;
 -  GtkWidget *ds;
 -
 -  ds = psppire_data_sheet_new ();
 -  pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (ds), grid_lines);
 -  psppire_data_sheet_set_value_labels (PSPPIRE_DATA_SHEET (ds),
 -                                       show_value_labels);
 -
 -  g_signal_connect_swapped (ds, "notify::value-labels",
 -                            G_CALLBACK (refresh_entry), de);
 -  g_signal_connect (ds, "var-double-clicked",
 -                    G_CALLBACK (on_data_sheet_var_double_clicked), de);
 -
 -  selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
 -  g_signal_connect (selection, "changed",
 -                    G_CALLBACK (on_data_sheet_selection_changed), de);
 -
 -  return ds;
  }
  
 -static GtkWidget *
 -make_single_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
 -                       gboolean show_value_labels)
 +/* Called when the active cell or the selection in the data sheet changes */
 +static void
 +on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
  {
 -  GtkWidget *data_sheet_scroller;
 -
 -  de->data_sheets[0] = make_data_sheet (de, grid_lines, show_value_labels);
 -  de->data_sheets[1] = de->data_sheets[2] = de->data_sheets[3] = NULL;
 -
 -  /* Put data sheet in scroller. */
 -  data_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
 -  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (data_sheet_scroller),
 -                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
 -  gtk_container_add (GTK_CONTAINER (data_sheet_scroller), de->data_sheets[0]);
 +  gchar *ref_cell_text = NULL;
  
 -  return data_sheet_scroller;
 -}
 +  gint n_cases = abs (sel->end_y - sel->start_y) + 1;
 +  gint n_vars = abs (sel->end_x - sel->start_x) + 1;
  
 -static GtkWidget *
 -make_split_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines,
 -                      gboolean show_value_labels)
 -{
 -  /* Panes, in the order in which we want to create them. */
 -  enum
 +  if (n_cases == 1 && n_vars == 1)
      {
 -      TL,                       /* top left */
 -      TR,                       /* top right */
 -      BL,                       /* bottom left */
 -      BR                        /* bottom right */
 -    };
 -
 -  PsppSheetView *ds[4];
 -  GtkXPaned *xpaned;
 -  int i;
 -
 -  xpaned = GTK_XPANED (gtk_xpaned_new ());
 +      /* A single cell is selected */
 +      const struct variable *var = psppire_dict_get_variable (de->dict, sel->start_x);
  
 -  for (i = 0; i < 4; i++)
 +      if (var)
 +      ref_cell_text = g_strdup_printf (_("%d : %s"),
 +                                       sel->start_y + 1, var_get_name (var));
 +    }
 +  else
      {
 -      GtkAdjustment *hadjust, *vadjust;
 -      GtkPolicyType hpolicy, vpolicy;
 -      GtkWidget *scroller;
 -
 -      de->data_sheets[i] = make_data_sheet (de, grid_lines, show_value_labels);
 -      ds[i] = PSPP_SHEET_VIEW (de->data_sheets[i]);
 -
 -      if (i == BL)
 -        hadjust = pspp_sheet_view_get_hadjustment (ds[TL]);
 -      else if (i == BR)
 -        hadjust = pspp_sheet_view_get_hadjustment (ds[TR]);
 -      else
 -        hadjust = NULL;
 -
 -      if (i == TR)
 -        vadjust = pspp_sheet_view_get_vadjustment (ds[TL]);
 -      else if (i == BR)
 -        vadjust = pspp_sheet_view_get_vadjustment (ds[BL]);
 -      else
 -        vadjust = NULL;
 -
 -      scroller = gtk_scrolled_window_new (hadjust, vadjust);
 -      gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroller),
 -                                           GTK_SHADOW_ETCHED_IN);
 -      hpolicy = i == TL || i == TR ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS;
 -      vpolicy = i == TL || i == BL ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS;
 -      gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroller),
 -                                      hpolicy, vpolicy);
 -      gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ds[i]));
 -
 -      switch (i)
 -        {
 -        case TL:
 -          gtk_xpaned_pack_top_left (xpaned, scroller, TRUE, TRUE);
 -          break;
 -
 -        case TR:
 -          gtk_xpaned_pack_top_right (xpaned, scroller, TRUE, TRUE);
 -          break;
 -
 -        case BL:
 -          gtk_xpaned_pack_bottom_left (xpaned, scroller, TRUE, TRUE);
 -          break;
 -
 -        case BR:
 -          gtk_xpaned_pack_bottom_right (xpaned, scroller, TRUE, TRUE);
 -          break;
 -
 -        default:
 -          g_warn_if_reached ();
 -        }
 +      struct string s;
 +
 +      /* The glib string library does not understand the ' printf modifier
 +       on all platforms, but the "struct string" library does (because
 +       Gnulib fixes that problem), so use the latter.  */
 +      ds_init_empty (&s);
 +      ds_put_format (&s, ngettext ("%'d case", "%'d cases", n_cases),
 +                   n_cases);
 +      ds_put_byte (&s, ' ');
 +      ds_put_unichar (&s, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
 +      ds_put_byte (&s, ' ');
 +      ds_put_format (&s, ngettext ("%'d variable", "%'d variables",
 +                                 n_vars),
 +                   n_vars);
 +      ref_cell_text = ds_steal_cstr (&s);
      }
 +  
 +  gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
 +                     ref_cell_text ? ref_cell_text : "");
 +  
 +  g_free (ref_cell_text);
 +}
  
 -  /* Bottom sheets don't display variable names. */
 -  pspp_sheet_view_set_headers_visible (ds[BL], FALSE);
 -  pspp_sheet_view_set_headers_visible (ds[BR], FALSE);
 -
 -  /* Right sheets don't display case numbers. */
 -  psppire_data_sheet_set_case_numbers (PSPPIRE_DATA_SHEET (ds[TR]), FALSE);
 -  psppire_data_sheet_set_case_numbers (PSPPIRE_DATA_SHEET (ds[BR]), FALSE);
  
 -  g_signal_connect (ds[TL], "notify::fixed-height",
 -                    G_CALLBACK (on_data_sheet_fixed_height_notify), de);
 +static void set_font_recursively (GtkWidget *w, gpointer data);
  
 -  return GTK_WIDGET (xpaned);
 -}
 +gchar *myconvfunc (GtkTreeModel *m, gint col, gint row, const GValue *v);
 +void myreversefunc (GtkTreeModel *model, gint col, gint row, const gchar *in, GValue *out);
  
 -static void set_font_recursively (GtkWidget *w, gpointer data);
  
  static void
  psppire_data_editor_init (PsppireDataEditor *de)
  {
 -  GtkWidget *var_sheet_scroller;
    GtkWidget *hbox;
    gchar *fontname = NULL;
  
+   GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (de));
+   gtk_style_context_add_class (context, "psppire-data-editor");
+   
    de->font = NULL;
    de->old_vbox_widget = NULL;
  
    gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
  
    de->split = FALSE;
 -  de->datasheet_vbox_widget
 -    = make_single_datasheet (de, GTK_TREE_VIEW_GRID_LINES_BOTH, FALSE);
 -
 +  de->data_sheet = jmd_sheet_new ();
 +  jmd_sheet_body_set_conversion_func
 +    (JMD_SHEET_BODY (JMD_SHEET(de->data_sheet)->selected_body),
 +     myconvfunc, myreversefunc);
 +                                    
 +  GtkWidget *data_button = jmd_sheet_get_button (JMD_SHEET (de->data_sheet));
 +  gtk_button_set_label (GTK_BUTTON (data_button), _("Case"));
    de->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
    gtk_box_pack_start (GTK_BOX (de->vbox), hbox, FALSE, FALSE, 0);
 -  gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
 -                      TRUE, TRUE, 0);
 +  gtk_box_pack_start (GTK_BOX (de->vbox), de->data_sheet, TRUE, TRUE, 0);
 +
  
 +  g_signal_connect_swapped (de->data_sheet, "selection-changed",
 +                  G_CALLBACK (on_data_selection_change), de);
 +  
    gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
                            gtk_label_new_with_mnemonic (_("Data View")));
  
    gtk_widget_show_all (de->vbox);
  
 -  de->var_sheet = GTK_WIDGET (psppire_var_sheet_new ());
 -  pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet),
 -                                  GTK_TREE_VIEW_GRID_LINES_BOTH);
 -  var_sheet_scroller = gtk_scrolled_window_new (NULL, NULL);
 -  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (var_sheet_scroller),
 -                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
 -  gtk_container_add (GTK_CONTAINER (var_sheet_scroller), de->var_sheet);
 -  gtk_widget_show_all (var_sheet_scroller);
 -  gtk_notebook_append_page (GTK_NOTEBOOK (de), var_sheet_scroller,
 +  de->var_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
 +
 +  PsppireVarSheetHeader *vsh = g_object_new (PSPPIRE_TYPE_VAR_SHEET_HEADER, NULL);
 +  
 +  g_object_set (de->var_sheet,
 +              "hmodel", vsh,
 +              "select-renderer-func", select_renderer_func,
 +              NULL);
 +
 +  
 +  GtkWidget *var_button = jmd_sheet_get_button (JMD_SHEET (de->var_sheet));
 +  gtk_button_set_label (GTK_BUTTON (var_button), _("Variable"));
 +  
 +  gtk_notebook_append_page (GTK_NOTEBOOK (de), de->var_sheet,
                            gtk_label_new_with_mnemonic (_("Variable View")));
  
 -  g_signal_connect (de->var_sheet, "var-double-clicked",
 +  gtk_widget_show_all (de->var_sheet);
 +  
 +  g_signal_connect (de->var_sheet, "row-header-double-clicked",
                      G_CALLBACK (on_var_sheet_var_double_clicked), de);
  
 +  g_signal_connect (de->data_sheet, "column-header-double-clicked",
 +                    G_CALLBACK (on_data_sheet_var_double_clicked), de);
 +
    g_object_set (de, "can-focus", FALSE, NULL);
  
    if (psppire_conf_get_string (psppire_conf_new (),
@@@ -632,8 -758,17 +635,8 @@@ psppire_data_editor_new (PsppireDict *d
  void
  psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
  {
 -  GtkTreeViewGridLines grid;
 -  PsppireDataSheet *data_sheet;
 -  int i;
 -
 -  grid = (grid_visible
 -          ? GTK_TREE_VIEW_GRID_LINES_BOTH
 -          : GTK_TREE_VIEW_GRID_LINES_NONE);
 -
 -  FOR_EACH_DATA_SHEET (data_sheet, i, de)
 -    pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (data_sheet), grid);
 -  pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (de->var_sheet), grid);
 +  g_object_set (JMD_SHEET (de->var_sheet), "gridlines", grid_visible, NULL);
 +  g_object_set (JMD_SHEET (de->data_sheet), "gridlines", grid_visible, NULL);
  }
  
  
@@@ -642,7 -777,28 +645,28 @@@ set_font_recursively (GtkWidget *w, gpo
  {
    PangoFontDescription *font_desc = data;
  
-   gtk_widget_override_font (w, font_desc);
+   GtkStyleContext *style = gtk_widget_get_style_context (w);
+   GtkCssProvider *cssp = gtk_css_provider_new ();
+   gchar *str = pango_font_description_to_string (font_desc);
+   gchar *css =
+     g_strdup_printf ("* {font: %s}", str);
+   g_free (str);
+   GError *err = NULL;
+   gtk_css_provider_load_from_data (cssp, css, -1, &err);
+   if (err)
+     {
+       g_warning ("Failed to load font css \"%s\": %s", css, err->message);
+       g_error_free (err);
+     }
+   g_free (css);
+   gtk_style_context_add_provider (style,
+                                 GTK_STYLE_PROVIDER (cssp),
+                                 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+   g_object_unref (cssp);
  
    if ( GTK_IS_CONTAINER (w))
      gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
@@@ -663,7 -819,7 +687,7 @@@ psppire_data_editor_set_font (PsppireDa
    psppire_conf_set_string (psppire_conf_new (),
                           "Data Editor", "font",
                           font_name);
+   g_free (font_name);
  }
  
  /* If SPLIT is TRUE, splits DE's data sheet into four panes.
  void
  psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
  {
 -  GtkTreeViewGridLines grid_lines;
 -  gboolean labels;
 -
    if (split == de->split)
      return;
  
 -
 -  grid_lines = pspp_sheet_view_get_grid_lines (
 -    PSPP_SHEET_VIEW (de->data_sheets[0]));
 -  labels = psppire_data_sheet_get_value_labels (PSPPIRE_DATA_SHEET (
 -                                                  de->data_sheets[0]));
 -
    disconnect_data_sheets (de);
 -  if (de->old_vbox_widget)
 -    g_object_unref (de->old_vbox_widget);
 -  de->old_vbox_widget = de->datasheet_vbox_widget;
 -  g_object_ref (de->old_vbox_widget);
 -  /* FIXME:  old_vbox_widget needs to be unreffed in dispose.
 -      (currently it seems to provoke an error if I do that.  
 -      I don't know why. */
 -  gtk_container_remove (GTK_CONTAINER (de->vbox), de->datasheet_vbox_widget);
 -
 -  if (split)
 -    de->datasheet_vbox_widget = make_split_datasheet (de, grid_lines, labels);
 -  else
 -    de->datasheet_vbox_widget = make_single_datasheet (de, grid_lines, labels);
  
    psppire_data_editor_refresh_model (de);
  
 -  gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
 -                      TRUE, TRUE, 0);
    gtk_widget_show_all (de->vbox);
  
    if (de->font)
  void
  psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
  {
 -  PsppireDataSheet *data_sheet;
 -
 -  switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
 -    {
 -    case PSPPIRE_DATA_EDITOR_DATA_VIEW:
 -      data_sheet = psppire_data_editor_get_active_data_sheet (de);
 -      psppire_data_sheet_goto_variable (data_sheet, dict_index);
 -      break;
 -
 -    case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
 -      psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
 -                                       dict_index);
 -      break;
 -    }
 -}
 -
 -/* Returns the "active" data sheet in DE.  If DE is in single-paned mode, this
 -   is the only data sheet.  If DE is in split mode (showing four data sheets),
 -   this is the focused data sheet or, if none is focused, the data sheet with
 -   selected cells or, if none has selected cells, the upper-left data sheet. */
 -PsppireDataSheet *
 -psppire_data_editor_get_active_data_sheet (PsppireDataEditor *de)
 -{
 -  if (de->split)
 -    {
 -      PsppireDataSheet *data_sheet;
 -      GtkWidget *scroller;
 -      int i;
 -
 -      /* If one of the datasheet's scrollers is focused, choose that one. */
 -      scroller = gtk_container_get_focus_child (
 -        GTK_CONTAINER (de->datasheet_vbox_widget));
 -      if (scroller != NULL)
 -        return PSPPIRE_DATA_SHEET (gtk_bin_get_child (GTK_BIN (scroller)));
 -
 -      /* Otherwise if there's a nonempty selection in some data sheet, choose
 -         that one. */
 -      FOR_EACH_DATA_SHEET (data_sheet, i, de)
 -        {
 -          PsppSheetSelection *selection;
 -
 -          selection = pspp_sheet_view_get_selection (
 -            PSPP_SHEET_VIEW (data_sheet));
 -          if (pspp_sheet_selection_count_selected_rows (selection)
 -              && pspp_sheet_selection_count_selected_columns (selection))
 -            return data_sheet;
 -        }
 -    }
 -
 -  return PSPPIRE_DATA_SHEET (de->data_sheets[0]);
  }