Display data in the var sheet properly
[pspp] / src / ui / gui / psppire-data-editor.c
index 5dccc849fc29d2247e8b7c4736214f9518c5a999..27d2f140991eaf220dbcae2b2a4a2ae64e682fea 100644 (file)
@@ -1,5 +1,6 @@
 /* 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,
+   2017 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 "data/value-labels.h"
 #include "libpspp/range-set.h"
 #include "libpspp/str.h"
+#include "ui/gui/executor.h"
 #include "ui/gui/helper.h"
-#include "ui/gui/pspp-sheet-selection.h"
+#include "ui/gui/var-display.h"
 #include "ui/gui/psppire-data-store.h"
+#include "ui/gui/psppire-data-window.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)
@@ -43,7 +49,7 @@ 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,
@@ -51,7 +57,7 @@ create_spin_renderer (GType type)
   g_object_set (r,
                "adjustment", adj,
                NULL);
-  
+
   return r;
 }
 
@@ -59,9 +65,9 @@ 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)
     {
@@ -86,18 +92,51 @@ create_combo_renderer (GType type)
   return r;
 }
 
-GtkCellRenderer *xx ;
-GtkCellRenderer *column_width_renderer ;
-GtkCellRenderer *measure_renderer ;
-GtkCellRenderer *alignment_renderer ;
+static gchar *
+var_sheet_data_to_string (GtkTreeModel *m, gint col, gint row, const GValue *in)
+{
+  if (col >= n_DICT_COLS - 1) /* -1 because psppire-dict has an extra column */
+    return NULL;
 
+  const struct variable *var = psppire_dict_get_variable (PSPPIRE_DICT (m), row);
+  if (var == NULL)
+    return NULL;
 
+  if (col == DICT_TVM_COL_TYPE)
+    {
+      const struct fmt_spec *print = var_get_print_format (var);
+      return strdup (fmt_gui_name (print->type));
+    }
+  else if (col == DICT_TVM_COL_MISSING_VALUES)
+    return missing_values_to_string (var, NULL);
+  else if (col == DICT_TVM_COL_VALUE_LABELS)
+    {
+      const struct val_labs *vls = var_get_value_labels (var);
+      if (vls == NULL)
+       return strdup (_("None"));
+      const struct val_lab **labels = val_labs_sorted (vls);
+      const struct val_lab *vl = labels[0];
+      gchar *vstr = value_to_text (vl->value, var);
+      char *text = xasprintf (_("{%s, %s}..."), vstr,
+                             val_lab_get_escaped_label (vl));
+      free (vstr);
+      free (labels);
+      return text;
+    }
+
+  return jmd_sheet_default_forward_conversion (m, col, row, in);
+}
+
+static GtkCellRenderer *spin_renderer;
+static GtkCellRenderer *column_width_renderer;
+static GtkCellRenderer *measure_renderer;
+static GtkCellRenderer *alignment_renderer;
 
 static GtkCellRenderer *
 select_renderer_func (gint col, gint row, GType type)
 {
-  if (!xx)
-    xx = create_spin_renderer (type);
+  if (!spin_renderer)
+    spin_renderer = create_spin_renderer (type);
 
   if (col == DICT_TVM_COL_ROLE && !column_width_renderer)
     column_width_renderer = create_combo_renderer (type);
@@ -113,18 +152,18 @@ select_renderer_func (gint col, gint row, GType type)
     case DICT_TVM_COL_WIDTH:
     case DICT_TVM_COL_DECIMAL:
     case DICT_TVM_COL_COLUMNS:
-      return xx;
-      
+      return spin_renderer;
+
     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;
 }
 
@@ -134,7 +173,6 @@ static void psppire_data_editor_init                (PsppireDataEditor      *de)
 
 static void disconnect_data_sheets (PsppireDataEditor *);
 static void refresh_entry (PsppireDataEditor *);
-static void psppire_data_editor_update_ui_manager (PsppireDataEditor *);
 
 GType
 psppire_data_editor_get_type (void)
@@ -190,12 +228,6 @@ psppire_data_editor_dispose (GObject *obj)
       de->font = NULL;
     }
 
-  if (de->ui_manager)
-    {
-      g_object_unref (de->ui_manager);
-      de->ui_manager = NULL;
-    }
-
   /* Chain up to the parent class */
   G_OBJECT_CLASS (parent_class)->dispose (obj);
 }
@@ -206,8 +238,7 @@ enum
     PROP_DATA_STORE,
     PROP_DICTIONARY,
     PROP_VALUE_LABELS,
-    PROP_SPLIT_WINDOW,
-    PROP_UI_MANAGER
+    PROP_SPLIT_WINDOW
   };
 
 static void
@@ -221,10 +252,17 @@ change_var_property (PsppireDict *dict, gint col, gint row, GValue *value)
   /* Return the IDXth variable */
   struct variable *var =  psppire_dict_get_variable (dict, row);
 
+  if (NULL == var)
+    var = psppire_dict_insert_variable (dict, row, NULL);
+
   switch (col)
     {
     case DICT_TVM_COL_NAME:
-      dict_rename_var (dict->dict, var, g_value_get_string (value));
+      {
+       const char *name = g_value_get_string (value);
+       if (psppire_dict_check_name (dict, name, FALSE))
+         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));
@@ -232,6 +270,15 @@ change_var_property (PsppireDict *dict, gint col, gint row, GValue *value)
     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;
@@ -243,13 +290,18 @@ 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;
-  value_init (&v, var_get_width (var));
-  v.f = g_value_get_double (value);
-  
+
+  GVariant *vrnt = g_value_get_variant (value);
+
+  value_variant_get (&v, vrnt);
+
   psppire_data_store_set_value (store, row, var, &v);
 
-  value_destroy (&v, var_get_width (var));
+  value_destroy_from_variant (&v, vrnt);
 }
 
 static void
@@ -283,7 +335,7 @@ psppire_data_editor_set_property (GObject         *object,
 
       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);
 
@@ -302,7 +354,7 @@ psppire_data_editor_set_property (GObject         *object,
       break;
     case PROP_VALUE_LABELS:
       break;
-    case PROP_UI_MANAGER:
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -330,9 +382,6 @@ psppire_data_editor_get_property (GObject         *object,
       break;
     case PROP_VALUE_LABELS:
       break;
-    case PROP_UI_MANAGER:
-      g_value_set_object (value, psppire_data_editor_get_ui_manager (de));
-      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -345,7 +394,7 @@ psppire_data_editor_switch_page (GtkNotebook     *notebook,
                                  guint            page_num)
 {
   GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, w, page_num);
-  psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (notebook));
+
 }
 
 static void
@@ -353,7 +402,7 @@ psppire_data_editor_set_focus_child (GtkContainer *container,
                                      GtkWidget    *widget)
 {
   GTK_CONTAINER_CLASS (parent_class)->set_focus_child (container, widget);
-  psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (container));
+
 }
 
 static void
@@ -363,7 +412,7 @@ psppire_data_editor_class_init (PsppireDataEditorClass *klass)
   GParamSpec *dict_spec ;
   GParamSpec *value_labels_spec;
   GParamSpec *split_window_spec;
-  GParamSpec *ui_manager_spec;
+
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
   GtkNotebookClass *notebook_class = GTK_NOTEBOOK_CLASS (klass);
@@ -420,21 +469,11 @@ psppire_data_editor_class_init (PsppireDataEditorClass *klass)
   g_object_class_install_property (object_class,
                                    PROP_SPLIT_WINDOW,
                                    split_window_spec);
-
-  ui_manager_spec =
-    g_param_spec_object ("ui-manager",
-                         "UI Manager",
-                         "UI manager for the active notebook tab.  The client should merge this UI manager with the active UI manager to obtain menu items and tool bar items specific to the active notebook tab.",
-                         GTK_TYPE_UI_MANAGER,
-                         G_PARAM_READABLE);
-  g_object_class_install_property (object_class,
-                                   PROP_UI_MANAGER,
-                                   ui_manager_spec);
 }
 
 
 static void
-on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
+on_var_sheet_var_double_clicked (void *var_sheet, gint dict_index,
                                  PsppireDataEditor *de)
 {
   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
@@ -443,10 +482,12 @@ on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
   jmd_sheet_scroll_to (JMD_SHEET (de->data_sheet), dict_index, -1);
 }
 
+
 static void
 on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
                                  PsppireDataEditor *de)
 {
+
   gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
                                  PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
 
@@ -454,12 +495,27 @@ on_data_sheet_var_double_clicked (JmdSheet *data_sheet, gint dict_index,
 }
 
 
+
 /* Refreshes 'de->cell_ref_label' and 'de->datum_entry' from the currently
    active cell or cells. */
 static void
 refresh_entry (PsppireDataEditor *de)
 {
-  g_print ("%s\n", __FUNCTION__);
+  union value val;
+  gint row, col;
+  jmd_sheet_get_active_cell (JMD_SHEET (de->data_sheet), &col, &row);
+
+  const struct variable *var = psppire_dict_get_variable (de->dict, col);
+  psppire_value_entry_set_variable (PSPPIRE_VALUE_ENTRY (de->datum_entry), var);
+
+  int width = var_get_width (var);
+  if (! psppire_data_store_get_value (PSPPIRE_DATA_STORE (de->data_store),
+                                     row, var, &val))
+    return;
+
+  psppire_value_entry_set_value (PSPPIRE_VALUE_ENTRY (de->datum_entry),
+                                &val, width);
+  value_destroy (&val, width);
 }
 
 static void
@@ -486,9 +542,10 @@ on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
     {
       /* A single cell is selected */
       const struct variable *var = psppire_dict_get_variable (de->dict, sel->start_x);
-      
-      ref_cell_text = g_strdup_printf (_("%d : %s"),
-                                      sel->start_y + 1, var_get_name (var));
+
+      if (var)
+       ref_cell_text = g_strdup_printf (_("%d : %s"),
+                                        sel->start_y + 1, var_get_name (var));
     }
   else
     {
@@ -508,24 +565,358 @@ on_data_selection_change (PsppireDataEditor *de, JmdRange *sel)
                     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);
 }
 
 
 static void set_font_recursively (GtkWidget *w, gpointer data);
 
+gchar *myconvfunc (GtkTreeModel *m, gint col, gint row, const GValue *v);
+void myreversefunc (GtkTreeModel *model, gint col, gint row, const gchar *in, GValue *out);
+
+
+enum sort_order
+  {
+    SORT_ASCEND,
+    SORT_DESCEND
+  };
+
+static void
+do_sort (PsppireDataEditor *de, enum sort_order order)
+{
+  JmdRange *range = JMD_SHEET(de->data_sheet)->selection;
+
+  int n_vars = 0;
+  int i;
+
+  PsppireDataWindow *pdw =
+     psppire_data_window_for_data_store (de->data_store);
+
+  GString *syntax = g_string_new ("SORT CASES BY");
+  for (i = range->start_x ; i <= range->end_x; ++i)
+    {
+      const struct variable *var = psppire_dict_get_variable (de->dict, i);
+      if (var != NULL)
+        {
+          g_string_append_printf (syntax, " %s", var_get_name (var));
+          n_vars++;
+        }
+    }
+  if (n_vars > 0)
+    {
+      if (order == SORT_DESCEND)
+        g_string_append (syntax, " (DOWN)");
+      g_string_append_c (syntax, '.');
+      execute_const_syntax_string (pdw, syntax->str);
+    }
+  g_string_free (syntax, TRUE);
+}
+
+
+static void
+sort_ascending (PsppireDataEditor *de)
+{
+  do_sort (de, SORT_ASCEND);
+
+  gtk_widget_queue_draw (GTK_WIDGET (de));
+}
+
+static void
+sort_descending (PsppireDataEditor *de)
+{
+  do_sort (de, SORT_DESCEND);
+
+  gtk_widget_queue_draw (GTK_WIDGET (de));
+}
+
+static void
+delete_cases (PsppireDataEditor *de)
+{
+  JmdRange *range = JMD_SHEET(de->data_sheet)->selection;
+
+  psppire_data_store_delete_cases (de->data_store, range->start_y,
+                                  range->end_y - range->start_y + 1);
+
+  gtk_widget_queue_draw (GTK_WIDGET (de));
+}
+
+static void
+insert_new_case (PsppireDataEditor *de)
+{
+  gint item = GPOINTER_TO_INT (g_object_get_data
+                               (G_OBJECT (de->data_sheet_cases_row_popup), "item"));
+
+  psppire_data_store_insert_new_case (de->data_store, item);
+
+  gtk_widget_queue_draw (GTK_WIDGET (de));
+}
+
+static void
+data_delete_variables (PsppireDataEditor *de)
+{
+  JmdRange *range = JMD_SHEET(de->data_sheet)->selection;
+
+  psppire_dict_delete_variables (de->dict, range->start_x,
+                                (range->end_x - range->start_x + 1));
+
+  gtk_widget_queue_draw (GTK_WIDGET (de->data_sheet));
+}
+
+static void
+var_delete_variables (PsppireDataEditor *de)
+{
+  JmdRange *range = JMD_SHEET(de->var_sheet)->selection;
+
+  psppire_dict_delete_variables (de->dict, range->start_y,
+                                (range->end_y - range->start_y + 1));
+
+  gtk_widget_queue_draw (GTK_WIDGET (de->var_sheet));
+}
+
+
+static void
+insert_new_variable_data (PsppireDataEditor *de)
+{
+  gint item = GPOINTER_TO_INT (g_object_get_data
+                               (G_OBJECT (de->data_sheet_cases_column_popup),
+                                "item"));
+
+  const struct variable *v = psppire_dict_insert_variable (de->dict, item, NULL);
+  psppire_data_store_insert_value (de->data_store, var_get_width(v),
+                                  var_get_case_index (v));
+
+  gtk_widget_queue_draw (GTK_WIDGET (de));
+}
+
+static void
+insert_new_variable_var (PsppireDataEditor *de)
+{
+  gint item = GPOINTER_TO_INT (g_object_get_data
+                               (G_OBJECT (de->var_sheet_row_popup),
+                                "item"));
+
+  const struct variable *v = psppire_dict_insert_variable (de->dict, item, NULL);
+  psppire_data_store_insert_value (de->data_store, var_get_width(v),
+                                  var_get_case_index (v));
+
+  gtk_widget_queue_draw (GTK_WIDGET (de));
+}
+
+
+static GtkWidget *
+create_var_row_header_popup_menu (PsppireDataEditor *de)
+{
+  GtkWidget *menu = gtk_menu_new ();
+
+  GtkWidget *item =
+    gtk_menu_item_new_with_mnemonic  (_("_Insert Variable"));
+  g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable_var),
+                           de);
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+  item = gtk_separator_menu_item_new ();
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+  de->var_clear_variables_menu_item =
+    gtk_menu_item_new_with_mnemonic (_("Cl_ear Variables"));
+  g_signal_connect_swapped (de->var_clear_variables_menu_item, "activate",
+                           G_CALLBACK (var_delete_variables), de);
+  gtk_widget_set_sensitive (de->var_clear_variables_menu_item, FALSE);
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->var_clear_variables_menu_item);
+
+  gtk_widget_show_all (menu);
+  return menu;
+}
+
+static GtkWidget *
+create_data_row_header_popup_menu (PsppireDataEditor *de)
+{
+  GtkWidget *menu = gtk_menu_new ();
+
+  GtkWidget *item =
+    gtk_menu_item_new_with_mnemonic  (_("_Insert Case"));
+
+  g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_case), de);
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+  item = gtk_separator_menu_item_new ();
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+  de->data_clear_cases_menu_item = gtk_menu_item_new_with_mnemonic (_("Cl_ear Cases"));
+  gtk_widget_set_sensitive (de->data_clear_cases_menu_item, FALSE);
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->data_clear_cases_menu_item);
+  g_signal_connect_swapped (de->data_clear_cases_menu_item, "activate",
+                           G_CALLBACK (delete_cases), de);
+
+  gtk_widget_show_all (menu);
+  return menu;
+}
+
+static GtkWidget *
+create_data_column_header_popup_menu (PsppireDataEditor *de)
+{
+  GtkWidget *menu = gtk_menu_new ();
+
+  GtkWidget *item =
+    gtk_menu_item_new_with_mnemonic  (_("_Insert Variable"));
+  g_signal_connect_swapped (item, "activate", G_CALLBACK (insert_new_variable_data),
+                           de);
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+  item = gtk_separator_menu_item_new ();
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+  de->data_clear_variables_menu_item =
+    gtk_menu_item_new_with_mnemonic  (_("Cl_ear Variables"));
+  g_signal_connect_swapped (de->data_clear_variables_menu_item, "activate",
+                           G_CALLBACK (data_delete_variables), de);
+  gtk_widget_set_sensitive (de->data_clear_variables_menu_item, FALSE);
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->data_clear_variables_menu_item);
+
+  item = gtk_separator_menu_item_new ();
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+
+  de->data_sort_ascending_menu_item =
+    gtk_menu_item_new_with_mnemonic (_("Sort _Ascending"));
+  g_signal_connect_swapped (de->data_sort_ascending_menu_item, "activate",
+                           G_CALLBACK (sort_ascending), de);
+  gtk_widget_set_sensitive (de->data_sort_ascending_menu_item, FALSE);
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->data_sort_ascending_menu_item);
+
+  de->data_sort_descending_menu_item =
+    gtk_menu_item_new_with_mnemonic (_("Sort _Descending"));
+  g_signal_connect_swapped (de->data_sort_descending_menu_item, "activate",
+                           G_CALLBACK (sort_descending), de);
+  gtk_widget_set_sensitive (de->data_sort_descending_menu_item, FALSE);
+  gtk_menu_shell_append (GTK_MENU_SHELL (menu), de->data_sort_descending_menu_item);
+
+  gtk_widget_show_all (menu);
+  return menu;
+}
+
+static void
+set_var_popup_sensitivity (JmdSheet *sheet, gpointer selection, gpointer p)
+{
+
+  JmdRange *range = selection;
+  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
+  gint width = gtk_tree_model_get_n_columns (sheet->data_model);
+
+  gboolean whole_row_selected = (range->start_x == 0 &&
+                                range->end_x == width - 1 - 1);
+  /*  PsppireDict has an "extra" column: TVM_COL_VAR   ^^^ */
+  gtk_widget_set_sensitive (de->var_clear_variables_menu_item, whole_row_selected);
+}
+
+static void
+set_menu_items_sensitivity (JmdSheet *sheet, gpointer selection, gpointer p)
+{
+  JmdRange *range = selection;
+  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
+  gint width = gtk_tree_model_get_n_columns (sheet->data_model);
+  gint length = psppire_data_store_get_case_count (de->data_store);
+
+
+  gboolean whole_row_selected = (range->start_x == 0 && range->end_x == width - 1);
+  gtk_widget_set_sensitive (de->data_clear_cases_menu_item, whole_row_selected);
+
+
+  gboolean whole_column_selected =
+    (range->start_y == 0 && range->end_y == length - 1);
+  gtk_widget_set_sensitive (de->data_clear_variables_menu_item,
+                           whole_column_selected);
+  gtk_widget_set_sensitive (de->data_sort_ascending_menu_item,
+                           whole_column_selected);
+  gtk_widget_set_sensitive (de->data_sort_descending_menu_item,
+                           whole_column_selected);
+}
+
+static void
+show_variables_row_popup (JmdSheet *sheet, int row, uint button,
+                         uint state, gpointer p)
+{
+  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
+  GListModel *vmodel = NULL;
+  g_object_get (sheet, "vmodel", &vmodel, NULL);
+  if (vmodel == NULL)
+    return;
+
+  guint n_items = g_list_model_get_n_items (vmodel);
+
+  if (row >= n_items)
+    return;
+
+  if (button != 3)
+    return;
+
+  g_object_set_data (G_OBJECT (de->var_sheet_row_popup), "item",
+                    GINT_TO_POINTER (row));
+
+  gtk_menu_popup_at_pointer (GTK_MENU (de->var_sheet_row_popup), NULL);
+}
+
+static void
+show_cases_row_popup (JmdSheet *sheet, int row, uint button, uint state, gpointer p)
+{
+  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
+  GListModel *vmodel = NULL;
+  g_object_get (sheet, "vmodel", &vmodel, NULL);
+  if (vmodel == NULL)
+    return;
+
+  guint n_items = g_list_model_get_n_items (vmodel);
+
+  if (row >= n_items)
+    return;
+
+  if (button != 3)
+    return;
+
+  g_object_set_data (G_OBJECT (de->data_sheet_cases_row_popup), "item",
+                    GINT_TO_POINTER (row));
+
+  gtk_menu_popup_at_pointer (GTK_MENU (de->data_sheet_cases_row_popup), NULL);
+}
+
+static void
+show_cases_column_popup (JmdSheet *sheet, int column, uint button, uint state,
+                        gpointer p)
+{
+  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (p);
+  GListModel *hmodel = NULL;
+  g_object_get (sheet, "hmodel", &hmodel, NULL);
+  if (hmodel == NULL)
+    return;
+
+  guint n_items = g_list_model_get_n_items (hmodel);
+
+  if (column >= n_items)
+    return;
+
+  if (button != 3)
+    return;
+
+  g_object_set_data (G_OBJECT (de->data_sheet_cases_column_popup), "item",
+                    GINT_TO_POINTER (column));
+
+  gtk_menu_popup_at_pointer (GTK_MENU (de->data_sheet_cases_column_popup), NULL);
+}
+
+
 static void
 psppire_data_editor_init (PsppireDataEditor *de)
 {
   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->ui_manager = NULL;
   de->old_vbox_widget = NULL;
 
   g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
@@ -543,7 +934,25 @@ psppire_data_editor_init (PsppireDataEditor *de)
   gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
 
   de->split = FALSE;
-  de->data_sheet = g_object_new (JMD_TYPE_SHEET, NULL);
+  de->data_sheet = jmd_sheet_new ();
+
+  de->data_sheet_cases_column_popup = create_data_column_header_popup_menu (de);
+  de->data_sheet_cases_row_popup = create_data_row_header_popup_menu (de);
+  de->var_sheet_row_popup = create_var_row_header_popup_menu (de);
+
+  g_signal_connect (de->data_sheet, "row-header-pressed",
+                   G_CALLBACK (show_cases_row_popup), de);
+
+  g_signal_connect (de->data_sheet, "column-header-pressed",
+                   G_CALLBACK (show_cases_column_popup), de);
+
+  g_signal_connect (de->data_sheet, "selection-changed",
+                   G_CALLBACK (set_menu_items_sensitivity), de);
+
+  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);
@@ -553,7 +962,7 @@ psppire_data_editor_init (PsppireDataEditor *de)
 
   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")));
 
@@ -562,21 +971,30 @@ psppire_data_editor_init (PsppireDataEditor *de)
   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);
 
-  
+  jmd_sheet_set_conversion_func (JMD_SHEET (de->var_sheet),
+                                var_sheet_data_to_string, NULL);
+
+  g_signal_connect (de->var_sheet, "row-header-pressed",
+                   G_CALLBACK (show_variables_row_popup), de);
+
+  g_signal_connect (de->var_sheet, "selection-changed",
+                   G_CALLBACK (set_var_popup_sensitivity), de);
+
+
   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")));
 
   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);
 
@@ -594,7 +1012,6 @@ psppire_data_editor_init (PsppireDataEditor *de)
       set_font_recursively (GTK_WIDGET (de), de->font);
     }
 
-  psppire_data_editor_update_ui_manager (de);
 }
 
 GtkWidget*
@@ -612,13 +1029,8 @@ psppire_data_editor_new (PsppireDict *dict,
 void
 psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
 {
-  GtkTreeViewGridLines grid;
-
-  grid = (grid_visible
-          ? GTK_TREE_VIEW_GRID_LINES_BOTH
-          : GTK_TREE_VIEW_GRID_LINES_NONE);
-
-  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);
 }
 
 
@@ -627,7 +1039,28 @@ set_font_recursively (GtkWidget *w, gpointer data)
 {
   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);
@@ -648,7 +1081,7 @@ psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_
   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.
@@ -670,7 +1103,6 @@ psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
 
   de->split = split;
   g_object_notify (G_OBJECT (de), "split");
-  psppire_data_editor_update_ui_manager (de);
 }
 
 /* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
@@ -679,49 +1111,3 @@ void
 psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
 {
 }
-
-
-/* Returns the UI manager that should be merged into DE's toplevel widget's UI
-   manager to display menu items and toolbar items specific to DE's current
-   page and data sheet.
-
-   DE's toplevel widget can watch for changes by connecting to DE's
-   notify::ui-manager signal. */
-GtkUIManager *
-psppire_data_editor_get_ui_manager (PsppireDataEditor *de)
-{
-  psppire_data_editor_update_ui_manager (de);
-  return de->ui_manager;
-}
-
-static void
-psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
-{
-  GtkUIManager *ui_manager;
-
-  ui_manager = NULL;
-
-  switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
-    {
-    case PSPPIRE_DATA_EDITOR_DATA_VIEW:
-      break;
-
-    case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
-      break;
-
-    default:
-      /* This happens transiently in psppire_data_editor_init(). */
-      break;
-    }
-
-  if (ui_manager != de->ui_manager)
-    {
-      if (de->ui_manager)
-        g_object_unref (de->ui_manager);
-      if (ui_manager)
-        g_object_ref (ui_manager);
-      de->ui_manager = ui_manager;
-
-      g_object_notify (G_OBJECT (de), "ui-manager");
-    }
-}