gui: Redo var sheet, data sheet, text import with PsppSheetView.
authorBen Pfaff <blp@cs.stanford.edu>
Tue, 24 Apr 2012 05:27:54 +0000 (22:27 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Tue, 26 Jun 2012 03:24:50 +0000 (20:24 -0700)
This commit replaces the existing variable and data sheets in the
GUI by new ones based on PsppSheetView.

Thanks to John Darrington and Michel Boaventura for helpful testing,
bug reports and suggestions.

30 files changed:
lib/gtk-contrib/gtkxpaned.c
src/ui/gui/automake.mk
src/ui/gui/data-editor.ui
src/ui/gui/data-sheet.ui [new file with mode: 0644]
src/ui/gui/find-dialog.c
src/ui/gui/goto-case-dialog.c
src/ui/gui/goto-case-dialog.h
src/ui/gui/marshaller-list
src/ui/gui/pspp-sheet-private.h
src/ui/gui/pspp-sheet-view.c
src/ui/gui/psppire-cell-renderer-button.c
src/ui/gui/psppire-data-editor.c
src/ui/gui/psppire-data-editor.h
src/ui/gui/psppire-data-sheet.c [new file with mode: 0644]
src/ui/gui/psppire-data-sheet.h [new file with mode: 0644]
src/ui/gui/psppire-data-store.c
src/ui/gui/psppire-data-store.h
src/ui/gui/psppire-data-window.c
src/ui/gui/psppire-data-window.h
src/ui/gui/psppire-dialog-action-var-info.c
src/ui/gui/psppire-dialog-action.c
src/ui/gui/psppire-var-sheet.c
src/ui/gui/psppire-var-sheet.h
src/ui/gui/psppire-var-store.c
src/ui/gui/psppire-var-store.h
src/ui/gui/text-data-import-dialog.c
src/ui/gui/text-data-import.ui
src/ui/gui/val-labs-dialog.c
src/ui/gui/val-labs-dialog.h
src/ui/gui/var-sheet.ui [new file with mode: 0644]

index 96f89ada7df785b0f3416daeb83d894820a3e8a2..7b30bfa1f58b3ab26486dd2dc8d08546ac2a7d9b 100644 (file)
@@ -3,6 +3,7 @@
 **      10        20        30        40        50        60        70        80
 **
 **  library for GtkXPaned-widget, a 2x2 grid-like variation of GtkPaned of gtk+
+**  Copyright (C) 2012 Free Software Foundation, Inc.
 **  Copyright (C) 2005-2006 Mirco "MacSlow" Müller <macslow@bangang.de>
 **
 **  This library is free software; you can redistribute it and/or
@@ -687,8 +688,8 @@ static void gtk_xpaned_size_request (GtkWidget* widget,
        {
                gtk_widget_size_request (xpaned->bottom_right_child, &child_requisition);
 
-               requisition->width = MAX (requisition->width, child_requisition.width);
-               requisition->height = MAX (requisition->height, child_requisition.height);
+               requisition->width = child_requisition.width;
+               requisition->height = child_requisition.height;
        }
 
        /* add 2 times the set border-width to the GtkXPaneds requisition */
@@ -2445,14 +2446,6 @@ gtk_xpaned_compute_position (GtkXPaned* xpaned,
                        xpaned->top_left_child_size.width = top_left_child_req->width;
                        xpaned->top_left_child_size.height = top_left_child_req->height;
                }
-               else if (top_left_child_req->width + top_right_child_req->width != 0)
-               {
-                       xpaned->top_left_child_size.width = allocation->width * ((gdouble)top_left_child_req->width / (top_left_child_req->width + top_right_child_req->width)) + 0.5;
-               }
-               else if (top_left_child_req->height + top_right_child_req->height != 0)
-               {
-                       xpaned->top_left_child_size.height = allocation->height * ((gdouble)top_left_child_req->height / (top_left_child_req->height + top_right_child_req->height)) + 0.5;
-               }
                else
                {
                        xpaned->top_left_child_size.width = allocation->width * 0.5 + 0.5;
index 3af7038e2e36651c37489f66d793dbaded3e016d..2b01346bd82578926c70598920d8c535904a9f08 100644 (file)
@@ -11,6 +11,7 @@ UI_FILES = \
        src/ui/gui/count.ui \
        src/ui/gui/crosstabs.ui \
        src/ui/gui/chi-square.ui \
+       src/ui/gui/data-sheet.ui \
        src/ui/gui/descriptives.ui \
        src/ui/gui/entry-dialog.ui \
        src/ui/gui/examine.ui \
@@ -42,7 +43,8 @@ UI_FILES = \
        src/ui/gui/variable-info.ui \
        src/ui/gui/data-editor.ui \
        src/ui/gui/output-viewer.ui \
-       src/ui/gui/syntax-editor.ui
+       src/ui/gui/syntax-editor.ui \
+       src/ui/gui/var-sheet.ui
 
 EXTRA_DIST += \
        src/ui/gui/OChangeLog \
@@ -216,6 +218,8 @@ src_ui_gui_psppire_SOURCES = \
        src/ui/gui/psppire-conf.h \
        src/ui/gui/psppire-data-editor.c \
        src/ui/gui/psppire-data-editor.h \
+       src/ui/gui/psppire-data-sheet.c \
+       src/ui/gui/psppire-data-sheet.h \
        src/ui/gui/psppire-data-store.c \
        src/ui/gui/psppire-data-store.h \
        src/ui/gui/psppire-data-window.c \
index cfa5b3e91834ea6e4cab79daaf83f02d0b8e7902..26821ebf17b936b2d31affbfb964b9c627b14c1f 100644 (file)
@@ -1,20 +1,6 @@
 <?xml version="1.0"?>
 <interface>
   <object class="GtkUIManager" id="uimanager1">
-    <child>
-      <object class="GtkActionGroup" id="actiongroup2">
-        <child>
-          <object class="GtkAction" id="sort-up">
-            <property name="stock-id">gtk-sort-ascending</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkAction" id="sort-down">
-            <property name="stock-id">gtk-sort-descending</property>
-          </object>
-        </child>
-      </object>
-    </child>
     <child>
       <object class="GtkActionGroup" id="actiongroup1">
         <child>
             <property name="label" translatable="yes">_Edit</property>
           </object>
         </child>
-        <child>
-          <object class="GtkAction" id="action_insert-variable">
-            <property name="name">action_insert-variable</property>
-            <property name="label" translatable="yes">Insert Variable</property>
-            <property name="tooltip" translatable="yes">Create a new variable at the current position</property>
-           <property name="stock-id">pspp-insert-variable</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkAction" id="edit_insert-case">
-            <property name="name">edit_insert-case</property>
-            <property name="label" translatable="yes">Insert Cases</property>
-            <property name="tooltip" translatable="yes">Create a new case at the current position</property>
-           <property name="stock-id">pspp-insert-case</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkAction" id="edit_goto-case">
-            <property name="label" translatable="yes">Go To Case...</property>
-            <property name="name">edit_goto-case</property>
-            <property name="tooltip" translatable="yes">Jump to a case in the data sheet</property>
-           <property name="stock-id">gtk-jump-to</property>
-
-          </object>
-        </child>
-        <child>
-          <object class="GtkAction" id="edit_cut">
-            <property name="stock-id">gtk-cut</property>
-            <property name="name">edit_cut</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkAction" id="edit_copy">
-            <property name="stock-id">gtk-copy</property>
-            <property name="name">edit_copy</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkAction" id="edit_paste">
-            <property name="stock-id">gtk-paste</property>
-            <property name="name">edit_paste</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkAction" id="edit_clear-variables">
-            <property name="name">edit_clear-variables</property>
-            <property name="label" translatable="yes">Cl_ear Variables</property>
-           <property name="tooltip" translatable="yes">Delete the variables at the selected position(s)</property>
-            <property name="stock-id">gtk-clear</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkAction" id="edit_clear-cases">
-            <property name="name">edit_clear-cases</property>
-            <property name="stock-id">gtk-clear</property>
-            <property name="label" translatable="yes">_Clear Cases</property>
-            <property name="tooltip" translatable="yes">Delete the cases at the selected position(s)</property>
-          </object>
-        </child>
-        <child>
-          <object class="GtkAction" id="edit_find">
-            <property name="stock-id">gtk-find</property>
-            <property name="name">edit_find</property>
-            <property name="label" translatable="yes">_Find...</property>
-          </object>
-        </child>
         <child>
           <object class="GtkAction" id="view">
             <property name="name">view</property>
           <separator/>
           <menuitem action="file_quit"/>
         </menu>
-        <menu action="edit">
-          <menuitem action="action_insert-variable"/>
-          <menuitem action="edit_insert-case"/>
-          <menuitem action="edit_goto-case"/>
-          <separator/>
-          <menuitem action="edit_cut"/>
-          <menuitem action="edit_copy"/>
-          <menuitem action="edit_paste"/>
-          <menuitem action="edit_clear-variables"/>
-          <menuitem action="edit_clear-cases"/>
-          <separator/>
-          <menuitem action="edit_find"/>
-        </menu>
+       <placeholder name="DataSheetEditMenu"/>
+       <placeholder name="VarSheetEditMenu"/>
         <menu action="view">
           <menuitem action="view_statusbar"/>
           <separator/>
         </menu>
       </menubar>
       <toolbar action="toolbar">
-        <placeholder name="tool-items">
-          <toolitem name="toolbar_open" action="file_open"/>
-          <toolitem name="toolbar_save" action="file_save"/>
-         <separator/>
-          <toolitem name="toolbar_goto-case" action="edit_goto-case"/>
-          <toolitem name="toolbar_goto-variable" action="utilities_variables"/>
-         <separator/>
-          <toolitem name="toolbar_find" action="edit_find"/>
-         <separator/>
-         <toolitem name="toolbar_insert-cases" action="edit_insert-case"/>
-         <toolitem name="toolbar_insert-variable" action="action_insert-variable"/>
-         <separator/>
-         <toolitem name="toolbar_split-file" action="data_split-file"/>
-         <toolitem name="toolbar_weight-cases" action="data_weight-cases"/>
-         <toolitem name="toolbar_select-cases" action="data_select-cases"/>
-         <separator/>
-         <toolitem name="toolbar_select-cases" action="view_value-labels"/>
-        </placeholder>
-      </toolbar>
-      <popup name="datasheet-variable-popup">
-       <menuitem action="action_insert-variable"/>
-       <separator/>
-       <menuitem action="edit_clear-variables"/>
+       <toolitem name="toolbar_open" action="file_open"/>
+       <toolitem name="toolbar_save" action="file_save"/>
        <separator/>
-        <menuitem action="sort-up"/>
-        <menuitem action="sort-down"/>
-      </popup>
-      <popup name="varsheet-variable-popup">
-       <menuitem action="action_insert-variable"/>
+       <placeholder name="DataSheetToolItems"/>
+       <placeholder name="VarSheetToolItems"/>
        <separator/>
-       <menuitem action="edit_clear-variables"/>
-      </popup>
-      <popup name="datasheet-cases-popup">
-       <menuitem action="edit_insert-case"/>
+       <toolitem name="toolbar_split-file" action="data_split-file"/>
+       <toolitem name="toolbar_weight-cases" action="data_weight-cases"/>
+       <toolitem name="toolbar_select-cases" action="data_select-cases"/>
        <separator/>
-       <menuitem action="edit_clear-cases"/>
-      </popup>
+       <toolitem name="toolbar_select-cases" action="view_value-labels"/>
+      </toolbar>
     </ui>
   </object>
   <!-- interface-requires gtk+ 2.6 -->
   <object class="GtkMenuBar" constructor="uimanager1" id="menubar">
     <property name="visible">True</property>
   </object>
-  <object class="GtkMenu" constructor="uimanager1" id="datasheet-variable-popup">
-    <property name="visible">True</property>
-  </object>
-  <object class="GtkMenu" constructor="uimanager1" id="varsheet-variable-popup">
-    <property name="visible">True</property>
-  </object>
-  <object class="GtkMenu" constructor="uimanager1" id="datasheet-cases-popup">
-    <property name="visible">True</property>
-  </object>
   <object class="GtkHandleBox" id="handlebox1">
     <property name="visible">True</property>
     <child>
diff --git a/src/ui/gui/data-sheet.ui b/src/ui/gui/data-sheet.ui
new file mode 100644 (file)
index 0000000..c97160d
--- /dev/null
@@ -0,0 +1,147 @@
+<?xml version="1.0"?>
+<interface>
+  <object class="GtkUIManager" id="data_sheet_uim">
+    <ui>
+      <menubar name="menubar">
+       <placeholder name="DataSheetEditMenu">
+         <menu name="edit" action="edit2">
+           <menuitem action="edit_insert-variable"/>
+           <menuitem action="edit_insert-case"/>
+           <menuitem action="edit_goto-variable"/>
+           <menuitem action="edit_goto-case"/>
+           <separator/>
+           <menuitem action="edit_cut"/>
+           <menuitem action="edit_copy"/>
+           <menuitem action="edit_paste"/>
+           <menuitem action="edit_clear-variables"/>
+           <menuitem action="edit_clear-cases"/>
+           <separator/>
+           <menuitem action="edit_find"/>
+         </menu>
+       </placeholder>
+      </menubar>
+      <toolbar name="toolbar">
+       <placeholder name="DataSheetToolItems">
+         <toolitem name="toolbar_goto-variable" action="edit_goto-variable"/>
+         <toolitem name="toolbar_goto-case" action="edit_goto-case"/>
+         <toolitem name="toolbar_find" action="edit_find"/>
+         <toolitem name="toolbar_insert-cases" action="edit_insert-case"/>
+         <toolitem name="toolbar_insert-variable" action="edit_insert-variable"/>
+       </placeholder>
+      </toolbar>
+      <popup name="datasheet-variable-popup">
+       <menuitem action="edit_insert-variable"/>
+       <separator/>
+       <menuitem action="edit_clear-variables"/>
+       <separator/>
+        <menuitem action="sort-up"/>
+        <menuitem action="sort-down"/>
+      </popup>
+      <popup name="datasheet-cases-popup">
+       <menuitem action="edit_insert-case"/>
+       <separator/>
+       <menuitem action="edit_clear-cases"/>
+      </popup>
+    </ui>
+    <child>
+      <object class="GtkActionGroup" id="actiongroup3">
+        <child>
+          <object class="GtkAction" id="edit2">
+            <property name="name">edit</property>
+            <property name="label" translatable="yes">_Edit</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkAction" id="edit_insert-variable">
+            <property name="name">edit_insert-variable</property>
+            <property name="label" translatable="yes">Insert Variable</property>
+            <property name="tooltip" translatable="yes">Create a new variable at the current position</property>
+           <property name="stock-id">pspp-insert-variable</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkAction" id="edit_insert-case">
+            <property name="name">edit_insert-case</property>
+            <property name="label" translatable="yes">Insert Cases</property>
+            <property name="tooltip" translatable="yes">Create a new case at the current position</property>
+           <property name="stock-id">pspp-insert-case</property>
+          </object>
+        </child>
+        <child>
+          <object class="PsppireDialogActionVarInfo" id="edit_goto-variable">
+            <property name="name">edit_goto-variable</property>
+            <property name="label" translatable="yes">Go To Variable...</property>
+           <property name="tooltip" translatable="yes">Jump to variable</property>
+           <property name="stock-id">pspp-goto-variable</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkAction" id="edit_goto-case">
+            <property name="label" translatable="yes">Go To Case...</property>
+            <property name="name">edit_goto-case</property>
+            <property name="tooltip" translatable="yes">Jump to a case in the data sheet</property>
+           <property name="stock-id">gtk-jump-to</property>
+
+          </object>
+        </child>
+        <child>
+          <object class="GtkAction" id="edit_cut">
+            <property name="stock-id">gtk-cut</property>
+            <property name="name">edit_cut</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkAction" id="edit_copy">
+            <property name="stock-id">gtk-copy</property>
+            <property name="name">edit_copy</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkAction" id="edit_paste">
+            <property name="stock-id">gtk-paste</property>
+            <property name="name">edit_paste</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkAction" id="edit_clear-variables">
+            <property name="name">edit_clear-variables</property>
+            <property name="label" translatable="yes">Cl_ear Variables</property>
+           <property name="tooltip" translatable="yes">Delete the variables at the selected position(s)</property>
+            <property name="stock-id">gtk-clear</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkAction" id="edit_clear-cases">
+            <property name="name">edit_clear-cases</property>
+            <property name="stock-id">gtk-clear</property>
+            <property name="label" translatable="yes">_Clear Cases</property>
+            <property name="tooltip" translatable="yes">Delete the cases at the selected position(s)</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkAction" id="edit_find">
+            <property name="stock-id">gtk-find</property>
+            <property name="name">edit_find</property>
+            <property name="label" translatable="yes">_Find...</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkAction" id="sort-up">
+            <property name="stock-id">gtk-sort-ascending</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkAction" id="sort-down">
+            <property name="stock-id">gtk-sort-descending</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkMenu" constructor="data_sheet_uim" id="datasheet-cases-popup">
+    <property name="visible">True</property>
+  </object>
+  <object class="GtkMenu" constructor="data_sheet_uim" id="datasheet-variable-popup">
+    <property name="visible">True</property>
+  </object>
+</interface>
index 0f856f94629d29c58383d9a1fc3d078015b47bf4..1f929b6485f88e5071d79bddb54e8b5c5044f7a8 100644 (file)
@@ -20,28 +20,29 @@ which match particular strings */
 
 #include <config.h>
 
-#include "find-dialog.h"
-#include "psppire-selector.h"
-#include "psppire-dialog.h"
-#include "builder-wrapper.h"
-#include "helper.h"
-#include "psppire-data-window.h"
-#include "dict-display.h"
-#include <data/value.h>
-#include <data/format.h>
-#include <data/datasheet.h>
-#include <data/data-in.h>
-#include "psppire-data-store.h"
 #include <ctype.h>
-#include <sys/types.h>
-#include <regex.h>
-#include <libpspp/cast.h>
-#include <libpspp/message.h>
-
 #include <gtk/gtk.h>
+#include <regex.h>
 #include <stdlib.h>
+#include <sys/types.h>
 
-#include "xalloc.h"
+#include "data/data-in.h"
+#include "data/datasheet.h"
+#include "data/format.h"
+#include "data/value.h"
+#include "libpspp/cast.h"
+#include "libpspp/message.h"
+#include "ui/gui/builder-wrapper.h"
+#include "ui/gui/dict-display.h"
+#include "ui/gui/find-dialog.h"
+#include "ui/gui/helper.h"
+#include "ui/gui/psppire-data-sheet.h"
+#include "ui/gui/psppire-data-store.h"
+#include "ui/gui/psppire-data-window.h"
+#include "ui/gui/psppire-dialog.h"
+#include "ui/gui/psppire-selector.h"
+
+#include "gl/xalloc.h"
 
 #include <gettext.h>
 #define _(msgid) gettext (msgid)
@@ -102,12 +103,13 @@ refresh (GObject *obj, const struct find_dialog *fd)
 static void
 do_find (GObject *obj, const struct find_dialog *fd)
 {
+  PsppireDataSheet *data_sheet;
   casenumber x = -1;
   gint column = -1;
   glong row;
 
-  g_object_get (fd->de->data_editor, "current-case", &row, NULL);
-
+  data_sheet = psppire_data_editor_get_active_data_sheet (fd->de->data_editor);
+  row = psppire_data_sheet_get_selected_case (data_sheet);
   if ( row < 0 )
     row = 0;
 
@@ -119,10 +121,8 @@ do_find (GObject *obj, const struct find_dialog *fd)
       gtk_notebook_set_current_page (GTK_NOTEBOOK (fd->de->data_editor),
                                     PSPPIRE_DATA_EDITOR_DATA_VIEW);
 
-      g_object_set (fd->de->data_editor,
-                   "current-case", x,
-                   "current-variable", column,
-                   NULL);
+      psppire_data_sheet_goto_case (data_sheet, x);
+      psppire_data_sheet_show_variable (data_sheet, column);
     }
 
 }
index 1b85a53665e7817abfd169d61be3a84ee677b735..ad10b256868cd4ab5c82ba0531073dc9869733e3 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (C) 2007, 2012  Free Software Foundation
+   Copyright (C) 2007, 2011, 2012  Free Software Foundation
 
    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
 
 
 static void
-refresh (const PsppireDataWindow *de, GtkBuilder *xml)
+refresh (PsppireDataSheet *ds, GtkBuilder *xml)
 {
-  PsppireDataStore *ds = NULL;
+  PsppireDataStore *data_store = psppire_data_sheet_get_data_store (ds);
   casenumber case_count ;
 
   GtkWidget *case_num_entry = get_widget_assert (xml, "goto-case-case-num-entry");
 
-  g_object_get (de->data_editor, "data-store", &ds, NULL);
-
-  case_count =  psppire_data_store_get_case_count (ds);
+  case_count =  psppire_data_store_get_case_count (data_store);
 
   gtk_spin_button_set_range (GTK_SPIN_BUTTON (case_num_entry), 1, case_count);
 }
 
 void
-goto_case_dialog (PsppireDataWindow *de)
+goto_case_dialog (PsppireDataSheet *ds)
 {
+  GtkWindow *top_level;
   gint response;
   GtkBuilder *xml = builder_new ("goto-case.ui");
-
   GtkWidget *dialog = get_widget_assert   (xml, "goto-case-dialog");
 
-  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
+  top_level = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (ds)));
+  gtk_window_set_transient_for (GTK_WINDOW (dialog), top_level);
 
-  refresh (de, xml);
+  refresh (ds, xml);
 
   response = psppire_dialog_run (PSPPIRE_DIALOG (dialog));
 
@@ -61,6 +60,6 @@ goto_case_dialog (PsppireDataWindow *de)
       case_num = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (case_num_entry))
        - FIRST_CASE_NUMBER ;
 
-      g_object_set (de->data_editor, "current-case", case_num, NULL);
+      psppire_data_sheet_goto_case (ds, case_num);
     }
 }
index 9435cb2c1f44ee805f1c4155a90ea7155c31ac2c..e7c70eee258fec818241c6c6ea232d72b4f24f70 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (C) 2007, 2010  Free Software Foundation
+   Copyright (C) 2007, 2010, 2011, 2012  Free Software Foundation
 
    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
@@ -17,8 +17,8 @@
 #ifndef __GOTO_CASE_DIALOG_H
 #define __GOTO_CASE_DIALOG_H
 
-#include "psppire-data-window.h"
+#include "psppire-data-sheet.h"
 
-void goto_case_dialog (PsppireDataWindow * data);
+void goto_case_dialog (PsppireDataSheet *ds);
 
 #endif
index 1f85ee6d7055428446408994273570feb62872ff..f3e4042b1c0cb324b45c457bce389e75f9d6f854 100644 (file)
@@ -5,6 +5,7 @@ BOOLEAN:BOOLEAN,BOOLEAN,BOOLEAN
 BOOLEAN:BOXED
 BOOLEAN:ENUM
 BOOLEAN:ENUM,INT
+BOOLEAN:INT
 BOOLEAN:BOXED,BOXED
 BOOLEAN:BOXED,POINTER
 BOOLEAN:OBJECT
index abb8366c6aee107a68e8cd3655e5bbdb630bc497..0cbd812d9ddc40abc1117dd32f576565044db289 100644 (file)
@@ -157,6 +157,7 @@ struct _PsppSheetViewPrivate
 
   /* Cell Editing */
   PsppSheetViewColumn *edited_column;
+  gint edited_row;
 
   /* Selection information */
   PsppSheetSelection *selection;
index eb7654fab90d904c5cecefaf7b03f280ee06114c..0219411a4688909d0782498fd1e93902d12e8329 100644 (file)
@@ -429,6 +429,8 @@ static void     remove_scroll_timeout                (PsppSheetView *tree_view);
 
 static guint tree_view_signals [LAST_SIGNAL] = { 0 };
 
+static GtkBindingSet *edit_bindings;
+
 \f
 
 /* GType Methods
@@ -445,9 +447,13 @@ pspp_sheet_view_class_init (PsppSheetViewClass *class)
   GtkObjectClass *object_class;
   GtkWidgetClass *widget_class;
   GtkContainerClass *container_class;
-  GtkBindingSet *binding_set;
+  GtkBindingSet *binding_set[2];
+  int i;
+
+  binding_set[0] = gtk_binding_set_by_class (class);
 
-  binding_set = gtk_binding_set_by_class (class);
+  binding_set[1] = gtk_binding_set_new ("PsppSheetViewEditing");
+  edit_bindings = binding_set[1];
 
   o_class = (GObjectClass *) class;
   object_class = (GtkObjectClass *) class;
@@ -873,110 +879,113 @@ pspp_sheet_view_class_init (PsppSheetViewClass *class)
                  G_TYPE_BOOLEAN, 0);
 
   /* Key bindings */
-  pspp_sheet_view_add_move_binding (binding_set, GDK_Up, 0, TRUE,
-                                 GTK_MOVEMENT_DISPLAY_LINES, -1);
-  pspp_sheet_view_add_move_binding (binding_set, GDK_KP_Up, 0, TRUE,
-                                 GTK_MOVEMENT_DISPLAY_LINES, -1);
+  for (i = 0; i < 2; i++)
+    {
+      pspp_sheet_view_add_move_binding (binding_set[i], GDK_Up, 0, TRUE,
+                                        GTK_MOVEMENT_DISPLAY_LINES, -1);
+      pspp_sheet_view_add_move_binding (binding_set[i], GDK_KP_Up, 0, TRUE,
+                                        GTK_MOVEMENT_DISPLAY_LINES, -1);
 
-  pspp_sheet_view_add_move_binding (binding_set, GDK_Down, 0, TRUE,
-                                 GTK_MOVEMENT_DISPLAY_LINES, 1);
-  pspp_sheet_view_add_move_binding (binding_set, GDK_KP_Down, 0, TRUE,
-                                 GTK_MOVEMENT_DISPLAY_LINES, 1);
+      pspp_sheet_view_add_move_binding (binding_set[i], GDK_Down, 0, TRUE,
+                                        GTK_MOVEMENT_DISPLAY_LINES, 1);
+      pspp_sheet_view_add_move_binding (binding_set[i], GDK_KP_Down, 0, TRUE,
+                                        GTK_MOVEMENT_DISPLAY_LINES, 1);
 
-  pspp_sheet_view_add_move_binding (binding_set, GDK_p, GDK_CONTROL_MASK, FALSE,
-                                 GTK_MOVEMENT_DISPLAY_LINES, -1);
+      pspp_sheet_view_add_move_binding (binding_set[i], GDK_p, GDK_CONTROL_MASK, FALSE,
+                                        GTK_MOVEMENT_DISPLAY_LINES, -1);
 
-  pspp_sheet_view_add_move_binding (binding_set, GDK_n, GDK_CONTROL_MASK, FALSE,
-                                 GTK_MOVEMENT_DISPLAY_LINES, 1);
+      pspp_sheet_view_add_move_binding (binding_set[i], GDK_n, GDK_CONTROL_MASK, FALSE,
+                                        GTK_MOVEMENT_DISPLAY_LINES, 1);
 
-  pspp_sheet_view_add_move_binding (binding_set, GDK_Home, 0, TRUE,
-                                 GTK_MOVEMENT_BUFFER_ENDS, -1);
-  pspp_sheet_view_add_move_binding (binding_set, GDK_KP_Home, 0, TRUE,
-                                 GTK_MOVEMENT_BUFFER_ENDS, -1);
+      pspp_sheet_view_add_move_binding (binding_set[i], GDK_Home, 0, TRUE,
+                                        GTK_MOVEMENT_BUFFER_ENDS, -1);
+      pspp_sheet_view_add_move_binding (binding_set[i], GDK_KP_Home, 0, TRUE,
+                                        GTK_MOVEMENT_BUFFER_ENDS, -1);
 
-  pspp_sheet_view_add_move_binding (binding_set, GDK_End, 0, TRUE,
-                                 GTK_MOVEMENT_BUFFER_ENDS, 1);
-  pspp_sheet_view_add_move_binding (binding_set, GDK_KP_End, 0, TRUE,
-                                 GTK_MOVEMENT_BUFFER_ENDS, 1);
+      pspp_sheet_view_add_move_binding (binding_set[i], GDK_End, 0, TRUE,
+                                        GTK_MOVEMENT_BUFFER_ENDS, 1);
+      pspp_sheet_view_add_move_binding (binding_set[i], GDK_KP_End, 0, TRUE,
+                                        GTK_MOVEMENT_BUFFER_ENDS, 1);
 
-  pspp_sheet_view_add_move_binding (binding_set, GDK_Page_Up, 0, TRUE,
-                                 GTK_MOVEMENT_PAGES, -1);
-  pspp_sheet_view_add_move_binding (binding_set, GDK_KP_Page_Up, 0, TRUE,
-                                 GTK_MOVEMENT_PAGES, -1);
+      pspp_sheet_view_add_move_binding (binding_set[i], GDK_Page_Up, 0, TRUE,
+                                        GTK_MOVEMENT_PAGES, -1);
+      pspp_sheet_view_add_move_binding (binding_set[i], GDK_KP_Page_Up, 0, TRUE,
+                                        GTK_MOVEMENT_PAGES, -1);
 
-  pspp_sheet_view_add_move_binding (binding_set, GDK_Page_Down, 0, TRUE,
-                                 GTK_MOVEMENT_PAGES, 1);
-  pspp_sheet_view_add_move_binding (binding_set, GDK_KP_Page_Down, 0, TRUE,
-                                 GTK_MOVEMENT_PAGES, 1);
+      pspp_sheet_view_add_move_binding (binding_set[i], GDK_Page_Down, 0, TRUE,
+                                        GTK_MOVEMENT_PAGES, 1);
+      pspp_sheet_view_add_move_binding (binding_set[i], GDK_KP_Page_Down, 0, TRUE,
+                                        GTK_MOVEMENT_PAGES, 1);
 
 
-  gtk_binding_entry_add_signal (binding_set, GDK_Right, 0, "move-cursor", 2,
-                               G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
-                               G_TYPE_INT, 1);
+      gtk_binding_entry_add_signal (binding_set[i], GDK_Right, 0, "move-cursor", 2,
+                                    G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
+                                    G_TYPE_INT, 1);
 
-  gtk_binding_entry_add_signal (binding_set, GDK_Left, 0, "move-cursor", 2,
-                               G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
-                               G_TYPE_INT, -1);
+      gtk_binding_entry_add_signal (binding_set[i], GDK_Left, 0, "move-cursor", 2,
+                                    G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
+                                    G_TYPE_INT, -1);
 
-  gtk_binding_entry_add_signal (binding_set, GDK_KP_Right, 0, "move-cursor", 2,
-                               G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
-                               G_TYPE_INT, 1);
+      gtk_binding_entry_add_signal (binding_set[i], GDK_KP_Right, 0, "move-cursor", 2,
+                                    G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
+                                    G_TYPE_INT, 1);
 
-  gtk_binding_entry_add_signal (binding_set, GDK_KP_Left, 0, "move-cursor", 2,
-                               G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
-                               G_TYPE_INT, -1);
+      gtk_binding_entry_add_signal (binding_set[i], GDK_KP_Left, 0, "move-cursor", 2,
+                                    G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
+                                    G_TYPE_INT, -1);
 
-  gtk_binding_entry_add_signal (binding_set, GDK_Right, GDK_CONTROL_MASK,
-                                "move-cursor", 2,
-                               G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
-                               G_TYPE_INT, 1);
+      gtk_binding_entry_add_signal (binding_set[i], GDK_Right, GDK_CONTROL_MASK,
+                                    "move-cursor", 2,
+                                    G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
+                                    G_TYPE_INT, 1);
 
-  gtk_binding_entry_add_signal (binding_set, GDK_Left, GDK_CONTROL_MASK,
-                                "move-cursor", 2,
-                               G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
-                               G_TYPE_INT, -1);
+      gtk_binding_entry_add_signal (binding_set[i], GDK_Left, GDK_CONTROL_MASK,
+                                    "move-cursor", 2,
+                                    G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
+                                    G_TYPE_INT, -1);
 
-  gtk_binding_entry_add_signal (binding_set, GDK_KP_Right, GDK_CONTROL_MASK,
-                                "move-cursor", 2,
-                               G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
-                               G_TYPE_INT, 1);
+      gtk_binding_entry_add_signal (binding_set[i], GDK_KP_Right, GDK_CONTROL_MASK,
+                                    "move-cursor", 2,
+                                    G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
+                                    G_TYPE_INT, 1);
 
-  gtk_binding_entry_add_signal (binding_set, GDK_KP_Left, GDK_CONTROL_MASK,
-                                "move-cursor", 2,
-                               G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
-                               G_TYPE_INT, -1);
+      gtk_binding_entry_add_signal (binding_set[i], GDK_KP_Left, GDK_CONTROL_MASK,
+                                    "move-cursor", 2,
+                                    G_TYPE_ENUM, GTK_MOVEMENT_VISUAL_POSITIONS,
+                                    G_TYPE_INT, -1);
+
+      gtk_binding_entry_add_signal (binding_set[i], GDK_f, GDK_CONTROL_MASK, "start-interactive-search", 0);
 
-  gtk_binding_entry_add_signal (binding_set, GDK_space, GDK_CONTROL_MASK, "toggle-cursor-row", 0);
-  gtk_binding_entry_add_signal (binding_set, GDK_KP_Space, GDK_CONTROL_MASK, "toggle-cursor-row", 0);
+      gtk_binding_entry_add_signal (binding_set[i], GDK_F, GDK_CONTROL_MASK, "start-interactive-search", 0);
+    }
 
-  gtk_binding_entry_add_signal (binding_set, GDK_a, GDK_CONTROL_MASK, "select-all", 0);
-  gtk_binding_entry_add_signal (binding_set, GDK_slash, GDK_CONTROL_MASK, "select-all", 0);
+  gtk_binding_entry_add_signal (binding_set[0], GDK_space, GDK_CONTROL_MASK, "toggle-cursor-row", 0);
+  gtk_binding_entry_add_signal (binding_set[0], GDK_KP_Space, GDK_CONTROL_MASK, "toggle-cursor-row", 0);
 
-  gtk_binding_entry_add_signal (binding_set, GDK_A, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "unselect-all", 0);
-  gtk_binding_entry_add_signal (binding_set, GDK_backslash, GDK_CONTROL_MASK, "unselect-all", 0);
+  gtk_binding_entry_add_signal (binding_set[0], GDK_a, GDK_CONTROL_MASK, "select-all", 0);
+  gtk_binding_entry_add_signal (binding_set[0], GDK_slash, GDK_CONTROL_MASK, "select-all", 0);
 
-  gtk_binding_entry_add_signal (binding_set, GDK_space, GDK_SHIFT_MASK, "select-cursor-row", 1,
+  gtk_binding_entry_add_signal (binding_set[0], GDK_A, GDK_CONTROL_MASK | GDK_SHIFT_MASK, "unselect-all", 0);
+  gtk_binding_entry_add_signal (binding_set[0], GDK_backslash, GDK_CONTROL_MASK, "unselect-all", 0);
+
+  gtk_binding_entry_add_signal (binding_set[0], GDK_space, GDK_SHIFT_MASK, "select-cursor-row", 1,
                                G_TYPE_BOOLEAN, TRUE);
-  gtk_binding_entry_add_signal (binding_set, GDK_KP_Space, GDK_SHIFT_MASK, "select-cursor-row", 1,
+  gtk_binding_entry_add_signal (binding_set[0], GDK_KP_Space, GDK_SHIFT_MASK, "select-cursor-row", 1,
                                G_TYPE_BOOLEAN, TRUE);
 
-  gtk_binding_entry_add_signal (binding_set, GDK_space, 0, "select-cursor-row", 1,
+  gtk_binding_entry_add_signal (binding_set[0], GDK_space, 0, "select-cursor-row", 1,
                                G_TYPE_BOOLEAN, TRUE);
-  gtk_binding_entry_add_signal (binding_set, GDK_KP_Space, 0, "select-cursor-row", 1,
+  gtk_binding_entry_add_signal (binding_set[0], GDK_KP_Space, 0, "select-cursor-row", 1,
                                G_TYPE_BOOLEAN, TRUE);
-  gtk_binding_entry_add_signal (binding_set, GDK_Return, 0, "select-cursor-row", 1,
+  gtk_binding_entry_add_signal (binding_set[0], GDK_Return, 0, "select-cursor-row", 1,
                                G_TYPE_BOOLEAN, TRUE);
-  gtk_binding_entry_add_signal (binding_set, GDK_ISO_Enter, 0, "select-cursor-row", 1,
+  gtk_binding_entry_add_signal (binding_set[0], GDK_ISO_Enter, 0, "select-cursor-row", 1,
                                G_TYPE_BOOLEAN, TRUE);
-  gtk_binding_entry_add_signal (binding_set, GDK_KP_Enter, 0, "select-cursor-row", 1,
+  gtk_binding_entry_add_signal (binding_set[0], GDK_KP_Enter, 0, "select-cursor-row", 1,
                                G_TYPE_BOOLEAN, TRUE);
 
-  gtk_binding_entry_add_signal (binding_set, GDK_BackSpace, 0, "select-cursor-parent", 0);
-  gtk_binding_entry_add_signal (binding_set, GDK_BackSpace, GDK_CONTROL_MASK, "select-cursor-parent", 0);
-
-  gtk_binding_entry_add_signal (binding_set, GDK_f, GDK_CONTROL_MASK, "start-interactive-search", 0);
-
-  gtk_binding_entry_add_signal (binding_set, GDK_F, GDK_CONTROL_MASK, "start-interactive-search", 0);
+  gtk_binding_entry_add_signal (binding_set[0], GDK_BackSpace, 0, "select-cursor-parent", 0);
+  gtk_binding_entry_add_signal (binding_set[0], GDK_BackSpace, GDK_CONTROL_MASK, "select-cursor-parent", 0);
 
   g_type_class_add_private (o_class, sizeof (PsppSheetViewPrivate));
 }
@@ -8026,7 +8035,7 @@ pspp_sheet_view_move_cursor_left_right (PsppSheetView *tree_view,
       gboolean left, right;
 
       column = list->data;
-      if (column->visible == FALSE)
+      if (column->visible == FALSE || column->row_head)
        goto loop_end;
 
       pspp_sheet_view_column_cell_set_cell_data (column,
@@ -12060,6 +12069,154 @@ pspp_sheet_view_editable_clicked (GtkButton *button,
                                                sheet_view);
 }
 
+static gboolean
+is_all_selected (GtkWidget *widget)
+{
+  GtkEntryBuffer *buffer;
+  gint start_pos, end_pos;
+
+  if (!GTK_IS_ENTRY (widget))
+    return FALSE;
+
+  buffer = gtk_entry_get_buffer (GTK_ENTRY (widget));
+  return (gtk_editable_get_selection_bounds (GTK_EDITABLE (widget),
+                                             &start_pos, &end_pos)
+          && start_pos == 0
+          && end_pos == gtk_entry_buffer_get_length (buffer));
+}
+
+static gboolean
+is_at_left (GtkWidget *widget)
+{
+  return (GTK_IS_ENTRY (widget)
+          && gtk_editable_get_position (GTK_EDITABLE (widget)) == 0);
+}
+
+static gboolean
+is_at_right (GtkWidget *widget)
+{
+  GtkEntryBuffer *buffer;
+  gint length;
+
+  if (!GTK_IS_ENTRY (widget))
+    return FALSE;
+
+  buffer = gtk_entry_get_buffer (GTK_ENTRY (widget));
+  length = gtk_entry_buffer_get_length (buffer);
+  return gtk_editable_get_position (GTK_EDITABLE (widget)) == length;
+}
+
+static gboolean
+pspp_sheet_view_event (GtkWidget *widget,
+                       GdkEventKey *event,
+                       PsppSheetView *tree_view)
+{
+  PsppSheetViewColumn *column;
+  GtkTreePath *path;
+  gboolean handled;
+  gboolean cancel;
+  guint keyval;
+  guint state;
+  gint row;
+
+  /* Intercept only key press events.
+     It would make sense to use "key-press-event" instead of "event", but
+     GtkEntry attaches its own signal handler to "key-press-event" that runs
+     before ours and overrides our desired behavior for GDK_Up and GDK_Down.
+  */
+  if (event->type != GDK_KEY_PRESS)
+    return FALSE;
+
+  if (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK))
+    {
+      /* Pass through most keys that include modifiers. */
+      if ((event->keyval == GDK_Tab || event->keyval == GDK_ISO_Left_Tab)
+          && !(event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)))
+        {
+          /* Special case for Shift-Tab. */
+        }
+      else
+        return FALSE;
+    }
+
+  keyval = event->keyval;
+  state = event->state & ~(GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK);
+  cancel = FALSE;
+  switch (event->keyval)
+    {
+    case GDK_Left:      case GDK_KP_Left:
+      if (!is_all_selected (widget) && !is_at_left (widget))
+        return FALSE;
+      break;
+
+    case GDK_Right:     case GDK_KP_Right:
+      if (!is_all_selected (widget) && !is_at_right (widget))
+        return FALSE;
+      break;
+
+    case GDK_Up:        case GDK_KP_Up:
+    case GDK_Down:      case GDK_KP_Down:
+      break;
+
+    case GDK_Page_Up:   case GDK_KP_Page_Up:
+    case GDK_Page_Down: case GDK_KP_Page_Down:
+      break;
+
+    case GDK_Escape:
+      cancel = TRUE;
+      break;
+
+    case GDK_Return:
+      keyval = GDK_Down;
+      break;
+
+    case GDK_Tab:
+    case GDK_ISO_Left_Tab:
+      keyval = event->state & GDK_SHIFT_MASK ? GDK_Left : GDK_Right;
+      break;
+
+    default:
+      return FALSE;
+    }
+
+  row = tree_view->priv->edited_row;
+  column = tree_view->priv->edited_column;
+  path = gtk_tree_path_new_from_indices (row, -1);
+
+  pspp_sheet_view_stop_editing (tree_view, cancel);
+  gtk_widget_grab_focus (GTK_WIDGET (tree_view));
+
+  pspp_sheet_view_set_cursor (tree_view, path, column, FALSE);
+  gtk_tree_path_free (path);
+
+  handled = gtk_binding_set_activate (edit_bindings, keyval, state,
+                                      GTK_OBJECT (tree_view));
+  if (handled)
+    g_signal_stop_emission_by_name (widget, "event");
+
+  pspp_sheet_view_get_cursor (tree_view, &path, NULL);
+  pspp_sheet_view_start_editing (tree_view, path);
+  gtk_tree_path_free (path);
+
+  return handled;
+}
+
+static void
+pspp_sheet_view_override_cell_keypresses (GtkWidget *widget,
+                                          gpointer data)
+{
+  PsppSheetView *sheet_view = data;
+
+  g_signal_connect (widget, "event",
+                    G_CALLBACK (pspp_sheet_view_event),
+                    sheet_view);
+
+  if (GTK_IS_CONTAINER (widget))
+    gtk_container_foreach (GTK_CONTAINER (widget),
+                           pspp_sheet_view_override_cell_keypresses,
+                           data);
+}
+
 static void
 pspp_sheet_view_real_start_editing (PsppSheetView       *tree_view,
                                  PsppSheetViewColumn *column,
@@ -12072,10 +12229,15 @@ pspp_sheet_view_real_start_editing (PsppSheetView       *tree_view,
   PsppSheetSelectionMode mode = pspp_sheet_selection_get_mode (tree_view->priv->selection);
   gint pre_val = tree_view->priv->vadjustment->value;
   GtkRequisition requisition;
+  gint row;
+
+  g_return_if_fail (gtk_tree_path_get_depth (path) == 1);
 
   tree_view->priv->edited_column = column;
   _pspp_sheet_view_column_start_editing (column, GTK_CELL_EDITABLE (cell_editable));
 
+  row = gtk_tree_path_get_indices (path)[0];
+  tree_view->priv->edited_row = row;
   pspp_sheet_view_real_set_cursor (tree_view, path, FALSE, TRUE);
   cell_area->y += pre_val - (int)tree_view->priv->vadjustment->value;
 
@@ -12116,11 +12278,14 @@ pspp_sheet_view_real_start_editing (PsppSheetView       *tree_view,
                         G_CALLBACK (pspp_sheet_view_editable_button_press_event),
                         tree_view);
       g_object_set_data (G_OBJECT (cell_editable), "pspp-sheet-view-node",
-                         GINT_TO_POINTER (gtk_tree_path_get_indices (path)[0]));
+                         GINT_TO_POINTER (row));
       g_signal_connect (cell_editable, "clicked",
                         G_CALLBACK (pspp_sheet_view_editable_clicked),
                         tree_view);
     }
+
+  pspp_sheet_view_override_cell_keypresses (GTK_WIDGET (cell_editable),
+                                            tree_view);
 }
 
 void
index 88de4bc655459b02286c93336bf80c10c232edd0..8bf90a24c68d19997bc8d29bc9900f5420fb8db3 100644 (file)
@@ -370,6 +370,16 @@ psppire_cell_renderer_button_press_event (GtkButton      *button,
 {
   PsppireCellRendererButton *cell_button = data;
 
+  if (event->button == 3)
+    {
+      /* Allow right-click events to propagate upward in the widget hierarchy.
+         Otherwise right-click menus, that trigger on a button-press-event on
+         the containing PsppSheetView, will pop up if the button is rendered as
+         a facade but not if the button widget exists.  */
+      g_signal_stop_emission_by_name (button, "button-press-event");
+      return FALSE;
+    }
+
   if (cell_button->click_time != 0)
     {
       GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (button));
index 16b1393df3977593f2e94c226ead0fedd478c6e5..575307e8c84a7509eb1b2da728aea8578caa8808 100644 (file)
    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <config.h>
-#include <gtk/gtk.h>
-#include <gtk-contrib/gtkextra-sheet.h>
-#include "psppire-data-editor.h"
-#include "psppire-var-sheet.h"
-#include "psppire.h"
 
-#include "psppire-data-store.h"
-#include <libpspp/i18n.h>
-#include <ui/gui/sheet/psppire-axis.h>
-#include "executor.h"
+#include "ui/gui/psppire-data-editor.h"
 
+#include <gtk/gtk.h>
 #include <gtk-contrib/gtkxpaned.h>
-#include <gettext.h>
-#define _(msgid) gettext (msgid)
-#define N_(msgid) msgid
-
-
-static void psppire_data_editor_remove_split (PsppireDataEditor *de);
-static void psppire_data_editor_set_split (PsppireDataEditor *de);
-
-enum {
-  DATA_SELECTION_CHANGED,
-  DATA_AVAILABLE_CHANGED,
-  CASES_SELECTED,
-  VARIABLES_SELECTED,
-  n_SIGNALS
-};
-
 
-static guint data_editor_signals [n_SIGNALS] = { 0 };
+#include "data/datasheet.h"
+#include "data/value-labels.h"
+#include "libpspp/range-set.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.h"
 
+#include <gettext.h>
+#define _(msgid) gettext (msgid)
 
-static gboolean data_is_selected (PsppireDataEditor *de);
+#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 void psppire_data_editor_class_init          (PsppireDataEditorClass *klass);
 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)
 {
@@ -86,344 +83,88 @@ psppire_data_editor_dispose (GObject *obj)
 {
   PsppireDataEditor *de = (PsppireDataEditor *) obj;
 
-  if (de->dispose_has_run)
-    return;
-
-  g_object_unref (de->data_window);
-  g_object_unref (de->data_store);
-  g_object_unref (de->var_store);
-
-  /* Make sure dispose does not run twice. */
-  de->dispose_has_run = TRUE;
-
-  /* Chain up to the parent class */
-  G_OBJECT_CLASS (parent_class)->dispose (obj);
-}
-
-static void
-psppire_data_editor_finalize (GObject *obj)
-{
-   /* Chain up to the parent class */
-   G_OBJECT_CLASS (parent_class)->finalize (obj);
-}
-
-
-
-static void popup_variable_column_menu (PsppireSheet *sheet, gint column,
-                                       GdkEventButton *event, gpointer data);
-
-static void popup_variable_row_menu (PsppireSheet *sheet, gint row,
-                                    GdkEventButton *event, gpointer data);
-
-
-static void popup_cases_menu (PsppireSheet *sheet, gint row,
-                             GdkEventButton *event, gpointer data);
-
-
-
-
+  disconnect_data_sheets (de);
 
-/* Callback which occurs when the data sheet's column title
-   is double clicked */
-static gboolean
-on_data_column_clicked (PsppireDataEditor *de, gint col, gpointer data)
-{
-  PsppireSheetRange visible_range;
-  gint current_row, current_column;
-
-  gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
-                                PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
-
-  psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->var_sheet),
-                            &current_row, &current_column);
-
-  psppire_sheet_set_active_cell (PSPPIRE_SHEET (de->var_sheet), col, current_column);
-
-
-  psppire_sheet_get_visible_range (PSPPIRE_SHEET (de->var_sheet), &visible_range);
-
-  if ( col < visible_range.row0 || col > visible_range.rowi)
-    psppire_sheet_moveto (PSPPIRE_SHEET (de->var_sheet), col, current_column, 0.5, 0.5);
-
-
-  return FALSE;
-}
-
-
-/* Callback which occurs when the var sheet's row title
-   button is double clicked */
-static gboolean
-on_var_row_clicked (PsppireDataEditor *de, gint row, gpointer data)
-{
-  PsppireSheetRange visible_range;
-
-  gint current_row, current_column;
-
-  gtk_notebook_set_current_page (GTK_NOTEBOOK(de), PSPPIRE_DATA_EDITOR_DATA_VIEW);
-
-  psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]),
-                            &current_row, &current_column);
-
-  psppire_sheet_set_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), current_row, row);
-
-  psppire_sheet_get_visible_range (PSPPIRE_SHEET (de->data_sheet[0]), &visible_range);
-
-  if ( row < visible_range.col0 || row > visible_range.coli)
-    psppire_sheet_moveto (PSPPIRE_SHEET (de->data_sheet[0]), -1, row, 0.5, 0.5);
-
-  return FALSE;
-}
+  if (de->data_store)
+    {
+      g_object_unref (de->data_store);
+      de->data_store = NULL;
+    }
 
+  if (de->var_store)
+    {
+      g_object_unref (de->var_store);
+      de->var_store = NULL;
+    }
 
-/* Moves the focus to a new cell.
-   Returns TRUE iff the move should be disallowed */
-static gboolean
-traverse_cell_callback (PsppireSheet *sheet,
-                       PsppireSheetCell *existing_cell,
-                       PsppireSheetCell *new_cell,
-                       gpointer data)
-{
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-  const PsppireDict *dict = de->data_store->dict;
+  if (de->font != NULL)
+    {
+      pango_font_description_free (de->font);
+      de->font = NULL;
+    }
 
-  if ( new_cell->col >= psppire_dict_get_var_cnt (dict))
-    return TRUE;
+  if (de->ui_manager)
+    {
+      g_object_unref (de->ui_manager);
+      de->ui_manager = NULL;
+    }
 
-  return FALSE;
+  /* Chain up to the parent class */
+  G_OBJECT_CLASS (parent_class)->dispose (obj);
 }
 
-
 enum
   {
     PROP_0,
-    PROP_DATA_WINDOW,
     PROP_DATA_STORE,
     PROP_VAR_STORE,
-    PROP_VS_ROW_MENU,
-    PROP_DS_COLUMN_MENU,
-    PROP_DS_ROW_MENU,
     PROP_VALUE_LABELS,
-    PROP_CURRENT_CASE,
-    PROP_CURRENT_VAR,
-    PROP_DATA_SELECTED,
-    PROP_SPLIT_WINDOW
+    PROP_SPLIT_WINDOW,
+    PROP_UI_MANAGER
   };
 
-
-#define DEFAULT_ROW_HEIGHT 25
-
-static void
-new_data_callback (PsppireDataStore *ds, gpointer data)
-{
-  gint i;
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-
-  casenumber n_cases =  psppire_data_store_get_case_count (ds);
-
-  for (i = 0; i < 2; ++i)
-    {
-      psppire_axis_clear (de->vaxis[i]);
-      psppire_axis_append_n (de->vaxis[i], n_cases, DEFAULT_ROW_HEIGHT);
-    }
-
-  /* All of the data (potentially) changed, so unselect any selected cell(s) in
-     the data sheets.  If we don't do this, then the sheet remembers the value
-     that was in the selected cell and stores it back, wiping out whatever
-     value there is in the new data.  Bug #30502. */
-  if (de->data_sheet[0] != NULL)
-    psppire_sheet_unselect_range (PSPPIRE_SHEET (de->data_sheet[0]));
-}
-
-static void
-case_inserted_callback (PsppireDataStore *ds, gint before, gpointer data)
-{
-  gint i;
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-
-  for (i = 0; i < 2; ++i)
-    psppire_axis_insert (de->vaxis[i], before, DEFAULT_ROW_HEIGHT);
-}
-
-
-static void
-cases_deleted_callback (PsppireDataStore *ds, gint first, gint n_cases, gpointer data)
-{
-  gint i;
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-
-  for (i = 0; i < 2; ++i)
-    psppire_axis_delete (de->vaxis[0], first, n_cases);
-}
-
-
-
-/* Return the width (in pixels) of an upper case M when rendered in the
-   current font of W
-*/
-static gint
-width_of_m (GtkWidget *w)
-{
-  PangoRectangle rect;
-  PangoLayout *layout = gtk_widget_create_pango_layout (w, "M");
-
-  pango_layout_get_pixel_extents (layout, NULL, &rect);
-
-  g_object_unref (layout);
-
-  return rect.width;
-}
-
-/* Callback for the axis' resize signal.
-   Changes the variable's display width */
-static void
-rewidth_variable (GtkWidget *w, gint unit, glong size)
-{
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (w);
-
-  const PsppireDict *dict = de->data_store->dict;
-  struct variable *var = psppire_dict_get_variable (dict, unit);
-
-  if (NULL == var)
-    return;
-
-  var_set_display_width (var, size / (float) width_of_m (w));
-}
-
-
-static void
-new_variables_callback (PsppireDict *dict, gpointer data)
-{
-  gint v;
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-  gint m_width = width_of_m (GTK_WIDGET (de));
-
-  PsppireAxis *vaxis;
-  g_object_get (de->var_sheet, "vertical-axis", &vaxis, NULL);
-
-  psppire_axis_clear (vaxis);
-  psppire_axis_append_n (vaxis, 1 + psppire_dict_get_var_cnt (dict), DEFAULT_ROW_HEIGHT);
-
-  g_signal_connect_swapped (de->haxis, "resize-unit",
-                           G_CALLBACK (rewidth_variable), de);
-
-  psppire_axis_clear (de->haxis);
-
-  for (v = 0 ; v < psppire_dict_get_var_cnt (dict); ++v)
-    {
-      const struct variable *var = psppire_dict_get_variable (dict, v);
-
-      psppire_axis_append (de->haxis, m_width * var_get_display_width (var));
-    }
-}
-
-static void
-insert_variable_callback (PsppireDict *dict, gint x, gpointer data)
-{
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-
-  gint m_width  = width_of_m (GTK_WIDGET (de));
-
-  PsppireAxis *var_vaxis;
-
-  const struct variable *var = psppire_dict_get_variable (dict, x);
-
-  g_object_get (de->var_sheet, "vertical-axis", &var_vaxis, NULL);
-
-  psppire_axis_insert (var_vaxis, x, DEFAULT_ROW_HEIGHT);
-
-
-  psppire_axis_insert (de->haxis, x, m_width * var_get_display_width (var));
-}
-
-
-static void
-delete_variable_callback (PsppireDict *dict,
-                          const struct variable *var UNUSED,
-                          gint dict_idx, gint case_idx UNUSED, gpointer data)
-{
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-
-  PsppireAxis *var_vaxis;
-  g_object_get (de->var_sheet, "vertical-axis", &var_vaxis, NULL);
-
-  psppire_axis_delete (var_vaxis, dict_idx, 1);
-
-  psppire_axis_delete (de->haxis, dict_idx, 1);
-}
-
-
 static void
-rewidth_variable_callback (PsppireDict *dict, gint posn, gpointer data)
+psppire_data_editor_refresh_model (PsppireDataEditor *de)
 {
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-  gint m_width = width_of_m (GTK_WIDGET (de));
-
-  const struct variable *var = psppire_dict_get_variable (dict, posn);
-
-  gint var_width = var_get_display_width (var);
-
-  /* Don't allow zero width */
-  if ( var_width < 1 )
-    var_width = 1;
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (de->var_sheet);
+  PsppireDataSheet *data_sheet;
+  int i;
 
-  psppire_axis_resize (de->haxis, posn, m_width * var_width);
+  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->var_store->dictionary);
 }
 
-
 static void
 psppire_data_editor_set_property (GObject         *object,
                                  guint            prop_id,
                                  const GValue    *value,
                                  GParamSpec      *pspec)
 {
-  int i;
   PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (object);
+  PsppireDataSheet *data_sheet;
+  int i;
 
   switch (prop_id)
     {
     case PROP_SPLIT_WINDOW:
       psppire_data_editor_split_window (de, g_value_get_boolean (value));
       break;
-    case PROP_DATA_WINDOW:
-      de->data_window = g_value_get_pointer (value);
-      g_object_ref (de->data_window);
-      break;
     case PROP_DATA_STORE:
-      if ( de->data_store) g_object_unref (de->data_store);
+      if ( de->data_store)
+        {
+          g_signal_handlers_disconnect_by_func (de->data_store,
+                                                G_CALLBACK (refresh_entry),
+                                                de);
+          g_object_unref (de->data_store);
+        }
+
       de->data_store = g_value_get_pointer (value);
       g_object_ref (de->data_store);
+      psppire_data_editor_refresh_model (de);
 
-      for (i = 0 ; i < 4 ; ++i )
-       {
-         g_object_set (de->data_sheet[i],
-                       "model", de->data_store,
-                       NULL);
-
-         g_signal_connect_swapped (de->data_store->dict, "filter-changed",
-                                   G_CALLBACK (gtk_widget_queue_draw),
-                                   de->data_sheet[i]);
-       }
-
-      g_signal_connect (de->data_store->dict, "backend-changed",
-                       G_CALLBACK (new_variables_callback), de);
-
-      g_signal_connect (de->data_store->dict, "variable-inserted",
-                       G_CALLBACK (insert_variable_callback), de);
-
-      g_signal_connect (de->data_store->dict, "variable-deleted",
-                       G_CALLBACK (delete_variable_callback), de);
-
-      g_signal_connect (de->data_store->dict, "variable-display-width-changed",
-                       G_CALLBACK (rewidth_variable_callback), de);
-
-      g_signal_connect (de->data_store, "backend-changed",
-                       G_CALLBACK (new_data_callback), de);
-
-      g_signal_connect (de->data_store, "case-inserted",
-                       G_CALLBACK (case_inserted_callback), de);
-
-      g_signal_connect (de->data_store, "cases-deleted",
-                       G_CALLBACK (cases_deleted_callback), de);
+      g_signal_connect_swapped (de->data_store, "case-changed",
+                                G_CALLBACK (refresh_entry), de);
 
       break;
     case PROP_VAR_STORE:
@@ -431,71 +172,15 @@ psppire_data_editor_set_property (GObject         *object,
       de->var_store = g_value_get_pointer (value);
       g_object_ref (de->var_store);
 
-      g_object_set (de->var_sheet,
-                   "model", de->var_store,
-                   NULL);
-      break;
-    case PROP_VS_ROW_MENU:
-      {
-       GObject *menu = g_value_get_object (value);
-
-       g_signal_connect (de->var_sheet, "button-event-row",
-                         G_CALLBACK (popup_variable_row_menu), menu);
-      }
-      break;
-    case PROP_DS_COLUMN_MENU:
-      {
-       GObject *menu = g_value_get_object (value);
-
-       g_signal_connect (de->data_sheet[0], "button-event-column",
-                         G_CALLBACK (popup_variable_column_menu), menu);
-      }
-      break;
-    case PROP_DS_ROW_MENU:
-      {
-       GObject *menu = g_value_get_object (value);
-
-       g_signal_connect (de->data_sheet[0], "button-event-row",
-                         G_CALLBACK (popup_cases_menu), menu);
-      }
-      break;
-    case PROP_CURRENT_VAR:
-      {
-       gint row, col;
-       gint var = g_value_get_long (value);
-       switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (object)))
-         {
-         case PSPPIRE_DATA_EDITOR_DATA_VIEW:
-           psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &col);
-           psppire_sheet_set_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), row, var);
-           psppire_sheet_moveto (PSPPIRE_SHEET (de->data_sheet[0]), -1, var, 0.5, 0.5);
-           break;
-         case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
-           psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->var_sheet), &row, &col);
-           psppire_sheet_set_active_cell (PSPPIRE_SHEET (de->var_sheet), var, col);
-           psppire_sheet_moveto (PSPPIRE_SHEET (de->var_sheet), var, -1,  0.5, 0.5);
-           break;
-         default:
-           g_assert_not_reached ();
-           break;
-         };
-      }
-      break;
-    case PROP_CURRENT_CASE:
-      {
-       gint row, col;
-       gint case_num = g_value_get_long (value);
-       psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &col);
-       psppire_sheet_set_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), case_num, col);
-       psppire_sheet_moveto (PSPPIRE_SHEET (de->data_sheet[0]), case_num, -1, 0.5, 0.5);
-      }
+      psppire_var_sheet_set_dictionary (PSPPIRE_VAR_SHEET (de->var_sheet),
+                                        de->var_store->dictionary);
       break;
     case PROP_VALUE_LABELS:
-      {
-       psppire_data_store_show_labels (de->data_store,
-                                       g_value_get_boolean (value));
-      }
+      FOR_EACH_DATA_SHEET (data_sheet, i, de)
+        psppire_data_sheet_set_value_labels (data_sheet,
+                                          g_value_get_boolean (value));
       break;
+    case PROP_UI_MANAGER:
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -515,74 +200,64 @@ psppire_data_editor_get_property (GObject         *object,
     case PROP_SPLIT_WINDOW:
       g_value_set_boolean (value, de->split);
       break;
-    case PROP_DATA_WINDOW:
-      g_value_set_pointer (value, de->data_window);
-      break;
     case PROP_DATA_STORE:
       g_value_set_pointer (value, de->data_store);
       break;
     case PROP_VAR_STORE:
       g_value_set_pointer (value, de->var_store);
       break;
-    case PROP_CURRENT_CASE:
-      {
-       gint row, column;
-       psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &column);
-       g_value_set_long (value, row);
-      }
-      break;
-    case PROP_CURRENT_VAR:
-      {
-       gint row, column;
-       psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &column);
-       g_value_set_long (value, column);
-      }
+    case PROP_VALUE_LABELS:
+      g_value_set_boolean (value,
+                           psppire_data_sheet_get_value_labels (
+                             PSPPIRE_DATA_SHEET (de->data_sheets[0])));
       break;
-    case PROP_DATA_SELECTED:
-      g_value_set_boolean (value, data_is_selected (de));
+    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;
-    };
+    }
+}
+
+static void
+psppire_data_editor_switch_page (GtkNotebook     *notebook,
+                                 GtkNotebookPage *page,
+                                 guint            page_num)
+{
+  GTK_NOTEBOOK_CLASS (parent_class)->switch_page (notebook, page, page_num);
+  psppire_data_editor_update_ui_manager (PSPPIRE_DATA_EDITOR (notebook));
 }
 
+static void
+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
 psppire_data_editor_class_init (PsppireDataEditorClass *klass)
 {
-  GParamSpec *data_window_spec ;
   GParamSpec *data_store_spec ;
   GParamSpec *var_store_spec ;
-  GParamSpec *column_menu_spec;
-  GParamSpec *ds_row_menu_spec;
-  GParamSpec *vs_row_menu_spec;
   GParamSpec *value_labels_spec;
-  GParamSpec *current_case_spec;
-  GParamSpec *current_var_spec;
-  GParamSpec *data_selected_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);
 
   parent_class = g_type_class_peek_parent (klass);
 
   object_class->dispose = psppire_data_editor_dispose;
-  object_class->finalize = psppire_data_editor_finalize;
-
   object_class->set_property = psppire_data_editor_set_property;
   object_class->get_property = psppire_data_editor_get_property;
 
-  
-
-  data_window_spec =
-    g_param_spec_pointer ("data-window",
-                         "Data Window",
-                         "A pointer to the data window associated with this editor",
-                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_READABLE );
+  container_class->set_focus_child = psppire_data_editor_set_focus_child;
 
-  g_object_class_install_property (object_class,
-                                   PROP_DATA_WINDOW,
-                                   data_window_spec);
+  notebook_class->switch_page = psppire_data_editor_switch_page;
 
   data_store_spec =
     g_param_spec_pointer ("data-store",
@@ -604,42 +279,6 @@ psppire_data_editor_class_init (PsppireDataEditorClass *klass)
                                    PROP_VAR_STORE,
                                    var_store_spec);
 
-  column_menu_spec =
-    g_param_spec_object ("datasheet-column-menu",
-                        "Data Sheet Column Menu",
-                        "A menu to be displayed when button 3 is pressed in thedata sheet's column title buttons",
-                        GTK_TYPE_MENU,
-                        G_PARAM_WRITABLE);
-
-  g_object_class_install_property (object_class,
-                                   PROP_DS_COLUMN_MENU,
-                                   column_menu_spec);
-
-
-  ds_row_menu_spec =
-    g_param_spec_object ("datasheet-row-menu",
-                        "Data Sheet Row Menu",
-                        "A menu to be displayed when button 3 is pressed in the data sheet's row title buttons",
-                        GTK_TYPE_MENU,
-                        G_PARAM_WRITABLE);
-
-  g_object_class_install_property (object_class,
-                                   PROP_DS_ROW_MENU,
-                                   ds_row_menu_spec);
-
-
-  vs_row_menu_spec =
-    g_param_spec_object ("varsheet-row-menu",
-                        "Variable Sheet Row Menu",
-                        "A menu to be displayed when button 3 is pressed in the variable sheet's row title buttons",
-                        GTK_TYPE_MENU,
-                        G_PARAM_WRITABLE);
-
-  g_object_class_install_property (object_class,
-                                   PROP_VS_ROW_MENU,
-                                   vs_row_menu_spec);
-
-
   value_labels_spec =
     g_param_spec_boolean ("value-labels",
                         "Value Labels",
@@ -652,45 +291,6 @@ psppire_data_editor_class_init (PsppireDataEditorClass *klass)
                                    value_labels_spec);
 
 
-  current_case_spec =
-    g_param_spec_long ("current-case",
-                      "Current Case",
-                      "Zero based number of the selected case",
-                      0, CASENUMBER_MAX,
-                      0,
-                      G_PARAM_WRITABLE | G_PARAM_READABLE);
-
-  g_object_class_install_property (object_class,
-                                   PROP_CURRENT_CASE,
-                                   current_case_spec);
-
-
-  current_var_spec =
-    g_param_spec_long ("current-variable",
-                      "Current Variable",
-                      "Zero based number of the selected variable",
-                      0, G_MAXINT,
-                      0,
-                      G_PARAM_WRITABLE | G_PARAM_READABLE);
-
-  g_object_class_install_property (object_class,
-                                   PROP_CURRENT_VAR,
-                                   current_var_spec);
-
-
-  data_selected_spec =
-    g_param_spec_boolean ("data-selected",
-                         "Data Selected",
-                         "True iff the data view is active and  one or more cells of data have been selected.",
-                         FALSE,
-                         G_PARAM_READABLE);
-
-  g_object_class_install_property (object_class,
-                                   PROP_DATA_SELECTED,
-                                   data_selected_spec);
-
-
-
   split_window_spec =
     g_param_spec_boolean ("split",
                          "Split Window",
@@ -702,723 +302,477 @@ psppire_data_editor_class_init (PsppireDataEditorClass *klass)
                                    PROP_SPLIT_WINDOW,
                                    split_window_spec);
 
-  data_editor_signals [DATA_SELECTION_CHANGED] =
-    g_signal_new ("data-selection-changed",
-                 G_TYPE_FROM_CLASS (klass),
-                 G_SIGNAL_RUN_FIRST,
-                 0,
-                 NULL, NULL,
-                 g_cclosure_marshal_VOID__BOOLEAN,
-                 G_TYPE_NONE,
-                 1,
-                 G_TYPE_BOOLEAN);
-
-  data_editor_signals [CASES_SELECTED] =
-    g_signal_new ("cases-selected",
-                 G_TYPE_FROM_CLASS (klass),
-                 G_SIGNAL_RUN_FIRST,
-                 0,
-                 NULL, NULL,
-                 g_cclosure_marshal_VOID__INT,
-                 G_TYPE_NONE,
-                 1,
-                 G_TYPE_INT);
-
-
-  data_editor_signals [VARIABLES_SELECTED] =
-    g_signal_new ("variables-selected",
-                 G_TYPE_FROM_CLASS (klass),
-                 G_SIGNAL_RUN_FIRST,
-                 0,
-                 NULL, NULL,
-                 g_cclosure_marshal_VOID__INT,
-                 G_TYPE_NONE,
-                 1,
-                 G_TYPE_INT);
-
-
-  data_editor_signals [DATA_AVAILABLE_CHANGED] =
-    g_signal_new ("data-available-changed",
-                 G_TYPE_FROM_CLASS (klass),
-                 G_SIGNAL_RUN_FIRST,
-                 0,
-                 NULL, NULL,
-                 g_cclosure_marshal_VOID__BOOLEAN,
-                 G_TYPE_NONE,
-                 1,
-                 G_TYPE_BOOLEAN);
+  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);
 }
 
-/* Update the data_ref_entry with the reference of the active cell */
-static gint
-update_data_ref_entry (const PsppireSheet *sheet,
-                      gint row, gint col,
-                      gint old_row, gint old_col,
-                      gpointer data)
+static gboolean
+on_data_sheet_var_double_clicked (PsppireDataSheet *data_sheet,
+                                  gint dict_index,
+                                  PsppireDataEditor *de)
 {
-  PsppireDataEditor *de = data;
-
-  PsppireDataStore *data_store =
-    PSPPIRE_DATA_STORE (psppire_sheet_get_model (sheet));
+  gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
+                                 PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
 
-  if (data_store)
-    {
-      const struct variable *var =
-       psppire_dict_get_variable (data_store->dict, col);
+  psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
+                                   dict_index);
 
-      if ( var )
-       {
-         gchar *text = g_strdup_printf ("%d: %s", row + FIRST_CASE_NUMBER,
-                                        var_get_name (var));
+  return TRUE;
+}
 
+static gboolean
+on_var_sheet_var_double_clicked (PsppireVarSheet *var_sheet, gint dict_index,
+                                 PsppireDataEditor *de)
+{
+  PsppireDataSheet *data_sheet;
 
-         gtk_entry_set_text (GTK_ENTRY (de->cell_ref_entry), text);
+  gtk_notebook_set_current_page (GTK_NOTEBOOK (de),
+                                 PSPPIRE_DATA_EDITOR_DATA_VIEW);
 
-         g_free (text);
-       }
-      else
-       goto blank_entry;
+  data_sheet = psppire_data_editor_get_active_data_sheet (de);
+  psppire_data_sheet_show_variable (data_sheet, dict_index);
 
-      if ( var )
-       {
-         gchar *text =
-           psppire_data_store_get_string (data_store, row,
-                                          var_get_dict_index(var));
+  return TRUE;
+}
 
-         if ( ! text )
-           goto blank_entry;
+/* 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);
 
-         g_strchug (text);
+  gchar *ref_cell_text;
+  GList *selected_columns, *iter;
+  struct variable *var;
+  gint n_cases;
+  gint n_vars;
 
-         gtk_entry_set_text (GTK_ENTRY (de->datum_entry), text);
+  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);
 
-         g_free (text);
-       }
-      else
-       goto blank_entry;
+  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);
     }
 
-  return FALSE;
-
- blank_entry:
-  gtk_entry_set_text (GTK_ENTRY (de->datum_entry), "");
+  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;
 
-  return FALSE;
-}
+      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));
 
-static void
-datum_entry_activate (GtkEntry *entry, gpointer data)
-{
-  gint row, column;
-  PsppireDataEditor *de = data;
+      show_value_labels = psppire_data_sheet_get_value_labels (data_sheet);
 
-  const gchar *text = gtk_entry_get_text (entry);
+      psppire_value_entry_set_variable (value_entry, var);
+      psppire_value_entry_set_show_value_label (value_entry,
+                                                show_value_labels);
 
-  psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &column);
+      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);
 
-  if ( row == -1 || column == -1)
-    return;
+      gtk_widget_set_sensitive (de->datum_entry, TRUE);
+    }
+  else
+    {
+      if (n_cases == 0 || n_vars == 0)
+        {
+          ref_cell_text = NULL;
+        }
+      else
+        {
+          GString *string;
+
+          string = g_string_sized_new (25);
+          g_string_append_printf (string,
+                                  ngettext ("%'d case", "%'d cases", n_cases),
+                                  n_cases);
+          g_string_append_c (string, ' ');
+          g_string_append_unichar (string, 0xd7); /* U+00D7 MULTIPLICATION SIGN */
+          g_string_append_c (string, ' ');
+          g_string_append_printf (string,
+                                  ngettext ("%'d variable", "%'d variables",
+                                            n_vars),
+                                  n_vars);
+          ref_cell_text = string->str;
+          g_string_free (string, FALSE);
+        }
+
+      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);
+    }
 
-  psppire_data_store_set_string (de->data_store, text, row, column);
+  gtk_label_set_label (GTK_LABEL (de->cell_ref_label),
+                       ref_cell_text ? ref_cell_text : "");
+  g_free (ref_cell_text);
 }
 
-static void on_activate (PsppireDataEditor *de);
-static gboolean on_switch_page (PsppireDataEditor *de, GtkNotebookPage *p, gint pagenum, gpointer data);
-static void on_select_range (PsppireDataEditor *de);
-
-static void on_select_row (PsppireSheet *, gint, PsppireDataEditor *);
-static void on_select_variable (PsppireSheet *, gint, PsppireDataEditor *);
-
-
-static void on_owner_change (GtkClipboard *,
-                            GdkEventOwnerChange *, gpointer);
-
 static void
-on_map (GtkWidget *w)
+on_datum_entry_activate (PsppireValueEntry *entry, PsppireDataEditor *de)
 {
-  GtkClipboard *clip = gtk_widget_get_clipboard (w, GDK_SELECTION_CLIPBOARD);
+  PsppireDataSheet *data_sheet = psppire_data_editor_get_active_data_sheet (de);
+  struct variable *var;
+  union value value;
+  int width;
+  gint row;
 
-  g_signal_connect (clip, "owner-change", G_CALLBACK (on_owner_change), w);
-}
+  row = psppire_data_sheet_get_current_case (data_sheet);
+  var = psppire_data_sheet_get_current_variable (data_sheet);
+  if (row < 0 || !var)
+    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);
+}
 
 static void
-init_sheet (PsppireDataEditor *de, int i,
-           GtkAdjustment *hadj, GtkAdjustment *vadj,
-           PsppireAxis *vaxis,
-           PsppireAxis *haxis
-           )
+on_data_sheet_selection_changed (PsppSheetSelection *selection,
+                                 PsppireDataEditor *de)
 {
-  de->sheet_bin[i] = gtk_scrolled_window_new (hadj, vadj);
-
-  de->data_sheet[i] = psppire_sheet_new (NULL);
+  /* 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;
 
-  g_object_set (de->sheet_bin[i],
-               "border-width", 3,
-               "shadow-type",  GTK_SHADOW_ETCHED_IN,
-               NULL);
+      FOR_EACH_DATA_SHEET (ds, i, de)
+        {
+          PsppSheetSelection *s;
 
-  g_object_set (haxis, "default-size", 75, NULL);
-  g_object_set (vaxis, "default-size", 25, NULL);
-
-  g_object_set (de->data_sheet[i],
-               "horizontal-axis", haxis,
-               "vertical-axis", vaxis,
-               NULL);
-
-  gtk_container_add (GTK_CONTAINER (de->sheet_bin[i]), de->data_sheet[i]);
-
-  g_signal_connect (de->data_sheet[i], "traverse",
-                   G_CALLBACK (traverse_cell_callback), de);
-
-  gtk_widget_show (de->sheet_bin[i]);
-}
-
-
-static void
-init_data_sheet (PsppireDataEditor *de)
-{
-  GtkAdjustment *vadj0, *hadj0;
-  GtkAdjustment *vadj1, *hadj1;
-  GtkWidget *sheet ;
-
-  de->vaxis[0] = psppire_axis_new ();
-  de->vaxis[1] = psppire_axis_new ();
-
-  /* There's only one horizontal axis, since the
-     column widths are parameters of the variables */
-  de->haxis = psppire_axis_new ();
-
-  de->split = TRUE;
-  de->paned = gtk_xpaned_new ();
-
-  init_sheet (de, 0, NULL, NULL, de->vaxis[0], de->haxis);
-  gtk_widget_show (de->sheet_bin[0]);
-  vadj0 = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (de->sheet_bin[0]));
-  hadj0 = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (de->sheet_bin[0]));
-
-  g_object_set (de->sheet_bin[0], "vscrollbar-policy", GTK_POLICY_NEVER, NULL);
-  g_object_set (de->sheet_bin[0], "hscrollbar-policy", GTK_POLICY_NEVER, NULL);
-
-  init_sheet (de, 1, NULL, vadj0, de->vaxis[0], de->haxis);
-  gtk_widget_show (de->sheet_bin[1]);
-  sheet = gtk_bin_get_child (GTK_BIN (de->sheet_bin[1]));
-  psppire_sheet_hide_row_titles (PSPPIRE_SHEET (sheet));
-  hadj1 = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (de->sheet_bin[1]));
-  g_object_set (de->sheet_bin[1], "vscrollbar-policy", GTK_POLICY_ALWAYS, NULL);
-  g_object_set (de->sheet_bin[1], "hscrollbar-policy", GTK_POLICY_NEVER, NULL);
-
-  init_sheet (de, 2, hadj0, NULL, de->vaxis[1], de->haxis);
-  gtk_widget_show (de->sheet_bin[2]);
-  sheet = gtk_bin_get_child (GTK_BIN (de->sheet_bin[2]));
-  psppire_sheet_hide_column_titles (PSPPIRE_SHEET (sheet));
-  g_object_set (de->sheet_bin[2], "vscrollbar-policy", GTK_POLICY_NEVER, NULL);
-  g_object_set (de->sheet_bin[2], "hscrollbar-policy", GTK_POLICY_ALWAYS, NULL);
-  vadj1 = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (de->sheet_bin[2]));
-
-  init_sheet (de, 3, hadj1, vadj1, de->vaxis[1], de->haxis);
-  gtk_widget_show (de->sheet_bin[3]);
-  sheet = gtk_bin_get_child (GTK_BIN (de->sheet_bin[3]));
-  psppire_sheet_hide_column_titles (PSPPIRE_SHEET (sheet));
-  psppire_sheet_hide_row_titles (PSPPIRE_SHEET (sheet));
-  g_object_set (de->sheet_bin[3], "vscrollbar-policy", GTK_POLICY_ALWAYS, NULL);
-  g_object_set (de->sheet_bin[3], "hscrollbar-policy", GTK_POLICY_ALWAYS, NULL);
-
-  gtk_xpaned_pack_top_left (GTK_XPANED (de->paned), de->sheet_bin[0], TRUE, TRUE);
-  gtk_xpaned_pack_top_right (GTK_XPANED (de->paned), de->sheet_bin[1], TRUE, TRUE);
-  gtk_xpaned_pack_bottom_left (GTK_XPANED (de->paned), de->sheet_bin[2], TRUE, TRUE);
-  gtk_xpaned_pack_bottom_right (GTK_XPANED (de->paned), de->sheet_bin[3], TRUE, TRUE);
-
-  gtk_xpaned_set_position_y (GTK_XPANED (de->paned), 150);
-  gtk_xpaned_set_position_x (GTK_XPANED (de->paned), 350);
-}
+          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
-psppire_data_editor_init (PsppireDataEditor *de)
+on_data_sheet_fixed_height_notify (PsppireDataSheet *ds,
+                                   GParamSpec *pspec,
+                                   PsppireDataEditor *de)
 {
-  GtkWidget *hbox = gtk_hbox_new (FALSE, 0);
-  GtkWidget *sw_vs = gtk_scrolled_window_new (NULL, NULL);
-
-  init_data_sheet (de);
-
-  de->data_vbox = gtk_vbox_new (FALSE, 0);
-  de->var_sheet = psppire_var_sheet_new ();
-
-  g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
-
-  de->datum_entry = gtk_entry_new ();
-  de->cell_ref_entry = gtk_entry_new ();
-
-  g_object_set (de->cell_ref_entry,
-               "sensitive", FALSE,
-               "editable",  FALSE,
-               "width_chars", 25,
-               NULL);
-
-  gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_entry, FALSE, FALSE, 0);
-  gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
-
-
-  gtk_container_add (GTK_CONTAINER (sw_vs), de->var_sheet);
-  gtk_widget_show_all (sw_vs);
-
-
-  gtk_box_pack_start (GTK_BOX (de->data_vbox), hbox, FALSE, FALSE, 0);
-  gtk_box_pack_start (GTK_BOX (de->data_vbox), de->paned, TRUE, TRUE, 0);
-
-
-  psppire_data_editor_remove_split (de);
-
-  gtk_widget_show_all (de->data_vbox);
-
-  gtk_notebook_append_page (GTK_NOTEBOOK (de), de->data_vbox,
-                           gtk_label_new_with_mnemonic (_("Data View")));
-
-  gtk_notebook_append_page (GTK_NOTEBOOK (de), sw_vs,
-                           gtk_label_new_with_mnemonic (_("Variable View")));
-
-  g_signal_connect (de->data_sheet[0], "activate",
-                   G_CALLBACK (update_data_ref_entry),
-                   de);
-
-  g_signal_connect (de->datum_entry, "activate",
-                   G_CALLBACK (datum_entry_activate),
-                   de);
-
-
-  g_signal_connect_swapped (de->data_sheet[0],
-                   "double-click-column",
-                   G_CALLBACK (on_data_column_clicked),
-                   de);
-
-  g_signal_connect_swapped (de->var_sheet,
-                   "double-click-row",
-                   G_CALLBACK (on_var_row_clicked),
-                   de);
-
-  g_signal_connect_swapped (de->data_sheet[0], "activate",
-                           G_CALLBACK (on_activate),
-                           de);
-
-  g_signal_connect_swapped (de->data_sheet[0], "select-range",
-                           G_CALLBACK (on_select_range),
-                           de);
-
-  g_signal_connect (de->data_sheet[0], "select-row",
-                   G_CALLBACK (on_select_row), de);
-
-  g_signal_connect (de->data_sheet[0], "select-column",
-                   G_CALLBACK (on_select_variable), de);
-
-
-  g_signal_connect (de->var_sheet, "select-row",
-                   G_CALLBACK (on_select_variable), de);
-
-
-  g_signal_connect_after (de, "switch-page",
-                   G_CALLBACK (on_switch_page),
-                   NULL);
-
-  g_object_set (de, "can-focus", FALSE, NULL);
-
-  g_signal_connect (de, "map", G_CALLBACK (on_map), NULL);
-
-
-  //     psppire_sheet_hide_column_titles (de->var_sheet);
-  //  psppire_sheet_hide_row_titles (de->data_sheet);
+  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));
 
-  de->dispose_has_run = FALSE;
+  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);
 }
 
-
-GtkWidget*
-psppire_data_editor_new (PsppireDataWindow *data_window,
-                         PsppireVarStore *var_store,
-                        PsppireDataStore *data_store)
-{
-  return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
-                        "data-window", data_window,
-                        "var-store",  var_store,
-                        "data-store",  data_store,
-                        NULL);
-}
-
-
 static void
-psppire_data_editor_remove_split (PsppireDataEditor *de)
+disconnect_data_sheets (PsppireDataEditor *de)
 {
-  if ( !de->split ) return;
-  de->split = FALSE;
+  PsppireDataSheet *ds;
+  int i;
 
-  g_object_ref (de->sheet_bin[0]);
-  gtk_container_remove (GTK_CONTAINER (de->paned), de->sheet_bin[0]);
+  FOR_EACH_DATA_SHEET (ds, i, de)
+    {
+      PsppSheetSelection *selection;
 
-  g_object_ref (de->paned);
-  gtk_container_remove (GTK_CONTAINER (de->data_vbox), de->paned);
+      if (ds == NULL)
+        {
+          /* This can only happen if 'dispose' runs more than once. */
+          continue;
+        }
 
-  gtk_box_pack_start (GTK_BOX (de->data_vbox), de->sheet_bin[0],
-                     TRUE, TRUE, 0);
+      if (i == GTK_XPANED_TOP_LEFT)
+        g_signal_handlers_disconnect_by_func (
+          ds, G_CALLBACK (on_data_sheet_fixed_height_notify), de);
 
-  g_object_unref (de->sheet_bin[0]);
+      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);
 
-  g_object_set (de->sheet_bin[0], "vscrollbar-policy",
-               GTK_POLICY_ALWAYS, NULL);
+      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);
 
-  g_object_set (de->sheet_bin[0], "hscrollbar-policy",
-               GTK_POLICY_ALWAYS, NULL);
+      de->data_sheets[i] = NULL;
+    }
 }
 
-
-static void
-psppire_data_editor_set_split (PsppireDataEditor *de)
+static GtkWidget *
+make_data_sheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines)
 {
-  if ( de->split ) return;
-  de->split = TRUE;
-
-  g_object_ref (de->sheet_bin[0]);
-  gtk_container_remove (GTK_CONTAINER (de->data_vbox), de->sheet_bin[0]);
+  PsppSheetSelection *selection;
+  GtkWidget *ds;
 
-  gtk_xpaned_pack_top_left (GTK_XPANED (de->paned), de->sheet_bin [0],
-                           TRUE, TRUE);
+  ds = psppire_data_sheet_new ();
+  pspp_sheet_view_set_grid_lines (PSPP_SHEET_VIEW (ds), grid_lines);
 
-  gtk_box_pack_start (GTK_BOX (de->data_vbox), de->paned,
-                     TRUE, TRUE, 0);
+  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);
 
-  g_object_unref (de->paned);
+  selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ds));
+  g_signal_connect (selection, "changed",
+                    G_CALLBACK (on_data_sheet_selection_changed), de);
 
-  g_object_set (de->sheet_bin[0], "vscrollbar-policy",
-               GTK_POLICY_NEVER, NULL);
-
-  g_object_set (de->sheet_bin[0], "hscrollbar-policy",
-               GTK_POLICY_NEVER, NULL);
+  return ds;
 }
 
-void
-psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
+static GtkWidget *
+make_single_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines)
 {
-  if (split )
-    psppire_data_editor_set_split (de);
-  else
-    psppire_data_editor_remove_split (de);
-
-  gtk_widget_show_all (de->data_vbox);
-}
+  GtkWidget *data_sheet_scroller;
 
-static void data_sheet_set_clip (PsppireSheet *sheet);
-static void data_sheet_contents_received_callback (GtkClipboard *clipboard,
-                                                  GtkSelectionData *sd,
-                                                  gpointer data);
+  de->data_sheets[0] = make_data_sheet (de, grid_lines);
+  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]);
 
-void
-psppire_data_editor_clip_copy (PsppireDataEditor *de)
-{
-  data_sheet_set_clip (PSPPIRE_SHEET (de->data_sheet[0]));
-}
-
-void
-psppire_data_editor_clip_paste (PsppireDataEditor *de)
-{
-  GdkDisplay *display = gtk_widget_get_display ( GTK_WIDGET (de));
-  GtkClipboard *clipboard =
-    gtk_clipboard_get_for_display (display, GDK_SELECTION_CLIPBOARD);
-
-  gtk_clipboard_request_contents (clipboard,
-                                 gdk_atom_intern ("UTF8_STRING", TRUE),
-                                 data_sheet_contents_received_callback,
-                                 de);
+  return data_sheet_scroller;
 }
 
-
-
-void
-psppire_data_editor_clip_cut (PsppireDataEditor *de)
+static GtkWidget *
+make_split_datasheet (PsppireDataEditor *de, GtkTreeViewGridLines grid_lines)
 {
-  gint max_rows, max_columns;
-  gint r;
-  PsppireSheetRange range;
-  PsppireDataStore *ds = de->data_store;
-
-  data_sheet_set_clip (PSPPIRE_SHEET (de->data_sheet[0]));
-
-  /* Now blank all the cells */
-  psppire_sheet_get_selected_range (PSPPIRE_SHEET (de->data_sheet[0]), &range);
-
-   /* If nothing selected, then use active cell */
-  if ( range.row0 < 0 || range.col0 < 0 )
+  /* Panes, in the order in which we want to create them. */
+  enum
     {
-      gint row, col;
-      psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &col);
+      TL,                       /* top left */
+      TR,                       /* top right */
+      BL,                       /* bottom left */
+      BR                        /* bottom right */
+    };
 
-      range.row0 = range.rowi = row;
-      range.col0 = range.coli = col;
-    }
+  PsppSheetView *ds[4];
+  GtkXPaned *xpaned;
+  int i;
 
-  /* The sheet range can include cells that do not include data.
-     Exclude them from the range. */
-  max_rows = psppire_data_store_get_case_count (ds);
-  if (range.rowi >= max_rows)
-    {
-      if (max_rows == 0)
-        return;
-      range.rowi = max_rows - 1;
-    }
+  xpaned = GTK_XPANED (gtk_xpaned_new ());
 
-  max_columns = dict_get_var_cnt (ds->dict->dict);
-  if (range.coli >= max_columns)
+  for (i = 0; i < 4; i++)
     {
-      if (max_columns == 0)
-        return;
-      range.coli = max_columns - 1;
-    }
-
-  g_return_if_fail (range.rowi >= range.row0);
-  g_return_if_fail (range.row0 >= 0);
-  g_return_if_fail (range.coli >= range.col0);
-  g_return_if_fail (range.col0 >= 0);
+      GtkAdjustment *hadjust, *vadjust;
+      GtkPolicyType hpolicy, vpolicy;
+      GtkWidget *scroller;
 
+      de->data_sheets[i] = make_data_sheet (de, grid_lines);
+      ds[i] = PSPP_SHEET_VIEW (de->data_sheets[i]);
 
-  for (r = range.row0; r <= range.rowi ; ++r )
-    {
-      gint c;
+      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;
 
-      for (c = range.col0 ; c <= range.coli; ++c)
-       {
-         psppire_data_store_set_string (ds, "", r, c);
-       }
+      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 ();
+        }
     }
 
-  /* and remove the selection */
-  psppire_sheet_unselect_range (PSPPIRE_SHEET (de->data_sheet[0]));
-}
-
-
-\f
+  /* 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);
 
-/* Popup menu related stuff */
+  /* 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);
 
-static void
-popup_variable_column_menu (PsppireSheet *sheet, gint column,
-                    GdkEventButton *event, gpointer data)
-{
-  GtkMenu *menu = GTK_MENU (data);
+  g_signal_connect (ds[TL], "notify::fixed-height",
+                    G_CALLBACK (on_data_sheet_fixed_height_notify), de);
 
-  PsppireDataStore *data_store =
-    PSPPIRE_DATA_STORE (psppire_sheet_get_model (sheet));
-
-  const struct variable *v =
-    psppire_dict_get_variable (data_store->dict, column);
-
-  if ( v && event->button == 3)
-    {
-      psppire_sheet_select_column (sheet, column);
-
-      gtk_menu_popup (menu,
-                     NULL, NULL, NULL, NULL,
-                     event->button, event->time);
-    }
+  return GTK_WIDGET (xpaned);
 }
 
-
 static void
-popup_variable_row_menu (PsppireSheet *sheet, gint row,
-                    GdkEventButton *event, gpointer data)
+psppire_data_editor_init (PsppireDataEditor *de)
 {
-  GtkMenu *menu = GTK_MENU (data);
-
-  PsppireVarStore *var_store =
-    PSPPIRE_VAR_STORE (psppire_sheet_get_model (sheet));
-  
-  PsppireDict *dict;
-  const struct variable *v ;
-
-  g_object_get (var_store, "dictionary", &dict, NULL);
+  GtkWidget *var_sheet_scroller;
+  GtkWidget *hbox;
 
-  v = psppire_dict_get_variable (dict, row);
-
-  if ( v && event->button == 3)
-    {
-      psppire_sheet_select_row (sheet, row);
+  de->font = NULL;
+  de->ui_manager = NULL;
 
-      gtk_menu_popup (menu,
-                     NULL, NULL, NULL, NULL,
-                     event->button, event->time);
-    }
-}
-
-
-static void
-popup_cases_menu (PsppireSheet *sheet, gint row,
-                 GdkEventButton *event, gpointer data)
-{
-  GtkMenu *menu = GTK_MENU (data);
+  g_object_set (de, "tab-pos", GTK_POS_BOTTOM, NULL);
 
-  PsppireDataStore *data_store =
-    PSPPIRE_DATA_STORE (psppire_sheet_get_model (sheet));
+  de->cell_ref_label = gtk_label_new ("");
+  gtk_label_set_width_chars (GTK_LABEL (de->cell_ref_label), 25);
+  gtk_misc_set_alignment (GTK_MISC (de->cell_ref_label), 0.0, 0.5);
 
-  if ( row <= psppire_data_store_get_case_count (data_store) &&
-       event->button == 3)
-    {
-      psppire_sheet_select_row (sheet, row);
+  de->datum_entry = psppire_value_entry_new ();
+  g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (de->datum_entry))),
+                    "activate", G_CALLBACK (on_datum_entry_activate), de);
 
-      gtk_menu_popup (menu,
-                     NULL, NULL, NULL, NULL,
-                     event->button, event->time);
-    }
-}
+  hbox = gtk_hbox_new (FALSE, 0);
+  gtk_box_pack_start (GTK_BOX (hbox), de->cell_ref_label, FALSE, FALSE, 0);
+  gtk_box_pack_start (GTK_BOX (hbox), de->datum_entry, TRUE, TRUE, 0);
 
-\f
+  de->split = FALSE;
+  de->datasheet_vbox_widget
+    = make_single_datasheet (de, GTK_TREE_VIEW_GRID_LINES_BOTH);
 
-/* Sorting */
+  de->vbox = gtk_vbox_new (FALSE, 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);
 
-static void
-do_sort (PsppireDataEditor *de, int var, gboolean descend)
-{
-  const struct variable *v
-    = psppire_dict_get_variable (de->data_store->dict, var);
-  gchar *syntax;
+  gtk_notebook_append_page (GTK_NOTEBOOK (de), de->vbox,
+                           gtk_label_new_with_mnemonic (_("Data View")));
 
-  syntax = g_strdup_printf ("SORT CASES BY %s%s.",
-                            var_get_name (v), descend ? " (D)" : "");
-  g_free (execute_syntax_string (de->data_window, syntax));
-}
+  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,
+                           gtk_label_new_with_mnemonic (_("Variable View")));
 
+  g_signal_connect (de->var_sheet, "var-double-clicked",
+                    G_CALLBACK (on_var_sheet_var_double_clicked), de);
 
-/* Sort the data by the the variable which the editor has currently
-   selected */
-void
-psppire_data_editor_sort_ascending  (PsppireDataEditor *de)
-{
-  PsppireSheetRange range;
-  psppire_sheet_get_selected_range (PSPPIRE_SHEET(de->data_sheet[0]), &range);
+  g_object_set (de, "can-focus", FALSE, NULL);
 
-  do_sort (de,  range.col0, FALSE);
+  psppire_data_editor_update_ui_manager (de);
 }
 
-
-/* Sort the data by the the variable which the editor has currently
-   selected */
-void
-psppire_data_editor_sort_descending (PsppireDataEditor *de)
+GtkWidget*
+psppire_data_editor_new (PsppireVarStore *var_store,
+                        PsppireDataStore *data_store)
 {
-  PsppireSheetRange range;
-  psppire_sheet_get_selected_range (PSPPIRE_SHEET(de->data_sheet[0]), &range);
-
-  do_sort (de,  range.col0, TRUE);
+  return  g_object_new (PSPPIRE_DATA_EDITOR_TYPE,
+                        "var-store",  var_store,
+                        "data-store",  data_store,
+                        NULL);
 }
-
-
 \f
-
-
-/* Insert a new variable  before the currently selected position */
+/* Turns the visible grid on or off, according to GRID_VISIBLE, for DE's data
+   sheet(s) and variable sheet. */
 void
-psppire_data_editor_insert_variable (PsppireDataEditor *de)
-{
-  glong posn = 0;
-
-  switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
-    {
-    case PSPPIRE_DATA_EDITOR_DATA_VIEW:
-      if ( PSPPIRE_SHEET (de->data_sheet[0])->select_status
-          == PSPPIRE_SHEET_COLUMN_SELECTED )
-       posn = PSPPIRE_SHEET (de->data_sheet[0])->range.col0;
-      else
-       posn = PSPPIRE_SHEET (de->data_sheet[0])->active_cell.col;
-      break;
-    case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
-      if ( PSPPIRE_SHEET (de->var_sheet)->select_status
-          == PSPPIRE_SHEET_ROW_SELECTED )
-       posn = PSPPIRE_SHEET (de->var_sheet)->range.row0;
-      else
-       posn = PSPPIRE_SHEET (de->var_sheet)->active_cell.row;
-      break;
-    default:
-      g_assert_not_reached ();
-      break;
-    };
-
-  psppire_dict_insert_variable (de->data_store->dict, posn, NULL);
-}
-
-/* Insert a new case before the currently selected position */
-void
-psppire_data_editor_insert_case (PsppireDataEditor *de)
-{
-  glong posn = -1;
-
-  if ( PSPPIRE_SHEET (de->data_sheet[0])->select_status == PSPPIRE_SHEET_ROW_SELECTED )
-    {
-      posn = PSPPIRE_SHEET (de->data_sheet[0])->range.row0;
-    }
-  else
-    {
-      posn = PSPPIRE_SHEET (de->data_sheet[0])->active_cell.row;
-    }
-
-  if ( posn == -1 ) posn = 0;
-
-  psppire_data_store_insert_new_case (de->data_store, posn);
-}
-
-/* Delete the cases currently selected in the data sheet */
-void
-psppire_data_editor_delete_cases    (PsppireDataEditor *de)
-{
-  gint first = PSPPIRE_SHEET (de->data_sheet[0])->range.row0;
-  gint n = PSPPIRE_SHEET (de->data_sheet[0])->range.rowi - first + 1;
-
-  psppire_data_store_delete_cases (de->data_store, first, n);
-
-  psppire_sheet_unselect_range (PSPPIRE_SHEET (de->data_sheet[0]));
-}
-
-/* Delete the variables currently selected in the
-   datasheet or variable sheet */
-void
-psppire_data_editor_delete_variables (PsppireDataEditor *de)
+psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
 {
-  PsppireDict *dict = NULL;
-  gint first, n;
-
-  switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
-    {
-    case PSPPIRE_DATA_EDITOR_DATA_VIEW:
-      first = PSPPIRE_SHEET (de->data_sheet[0])->range.col0;
-      n = PSPPIRE_SHEET (de->data_sheet[0])->range.coli - first + 1;
-      break;
-    case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
-      first = PSPPIRE_SHEET (de->var_sheet)->range.row0;
-      n = PSPPIRE_SHEET (de->var_sheet)->range.rowi - first + 1;
-      break;
-    default:
-      g_assert_not_reached ();
-      break;
-    }
-
-  g_object_get (de->var_store, "dictionary", &dict, NULL);
+  GtkTreeViewGridLines grid;
+  PsppireDataSheet *data_sheet;
+  int i;
 
-  psppire_dict_delete_variables (dict, first, n);
+  grid = (grid_visible
+          ? GTK_TREE_VIEW_GRID_LINES_BOTH
+          : GTK_TREE_VIEW_GRID_LINES_NONE);
 
-  psppire_sheet_unselect_range (PSPPIRE_SHEET (de->data_sheet[0]));
-  psppire_sheet_unselect_range (PSPPIRE_SHEET (de->var_sheet));
-}
-
-
-void
-psppire_data_editor_show_grid (PsppireDataEditor *de, gboolean grid_visible)
-{
-  psppire_sheet_show_grid (PSPPIRE_SHEET (de->var_sheet), grid_visible);
-  psppire_sheet_show_grid (PSPPIRE_SHEET (de->data_sheet[0]), grid_visible);
+  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);
 }
 
 
 static void
-set_font (GtkWidget *w, gpointer data)
+set_font_recursively (GtkWidget *w, gpointer data)
 {
   PangoFontDescription *font_desc = data;
   GtkRcStyle *style = gtk_widget_get_modifier_style (w);
@@ -1429,500 +783,163 @@ set_font (GtkWidget *w, gpointer data)
   gtk_widget_modify_style (w, style);
 
   if ( GTK_IS_CONTAINER (w))
-    gtk_container_foreach (GTK_CONTAINER (w), set_font, font_desc);
+    gtk_container_foreach (GTK_CONTAINER (w), set_font_recursively, font_desc);
 }
 
+/* Sets FONT_DESC as the font used by the data sheet(s) and variable sheet. */
 void
 psppire_data_editor_set_font (PsppireDataEditor *de, PangoFontDescription *font_desc)
 {
-  set_font (GTK_WIDGET (de), font_desc);
-}
-
+  set_font_recursively (GTK_WIDGET (de), font_desc);
 
-\f
-
-
-static void
-emit_selected_signal (PsppireDataEditor *de)
-{
-  gboolean data_selected = data_is_selected (de);
-
-  g_signal_emit (de, data_editor_signals[DATA_SELECTION_CHANGED], 0, data_selected);
+  if (de->font)
+    pango_font_description_free (de->font);
+  de->font = pango_font_description_copy (font_desc);
 }
 
-
-static void
-on_activate (PsppireDataEditor *de)
+/* If SPLIT is TRUE, splits DE's data sheet into four panes.
+   If SPLIT is FALSE, un-splits it into a single pane. */
+void
+psppire_data_editor_split_window (PsppireDataEditor *de, gboolean split)
 {
-  gint row, col;
-  psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &col);
+  GtkTreeViewGridLines grid_lines;
 
+  if (split == de->split)
+    return;
 
-  if ( row < psppire_data_store_get_case_count (de->data_store)
-       &&
-       col < psppire_var_store_get_var_cnt (de->var_store))
-    {
-      emit_selected_signal (de);
-      return ;
-    }
 
-  emit_selected_signal (de);
-}
+  grid_lines = pspp_sheet_view_get_grid_lines (
+    PSPP_SHEET_VIEW (de->data_sheets[0]));
 
+  disconnect_data_sheets (de);
+  gtk_widget_destroy (de->datasheet_vbox_widget);
 
-static void
-on_select_range (PsppireDataEditor *de)
-{
-  PsppireSheetRange range;
+  if (split)
+    de->datasheet_vbox_widget = make_split_datasheet (de, grid_lines);
+  else
+    de->datasheet_vbox_widget = make_single_datasheet (de, grid_lines);
+  psppire_data_editor_refresh_model (de);
 
-  psppire_sheet_get_selected_range (PSPPIRE_SHEET (de->data_sheet[0]), &range);
+  gtk_box_pack_start (GTK_BOX (de->vbox), de->datasheet_vbox_widget,
+                      TRUE, TRUE, 0);
+  gtk_widget_show_all (de->vbox);
 
-  if ( range.rowi < psppire_data_store_get_case_count (de->data_store)
-       &&
-       range.coli < psppire_var_store_get_var_cnt (de->var_store))
-    {
-      emit_selected_signal (de);
-      return;
-    }
+  if (de->font)
+    set_font_recursively (GTK_WIDGET (de), de->font);
 
-  emit_selected_signal (de);
+  de->split = split;
+  g_object_notify (G_OBJECT (de), "split");
+  psppire_data_editor_update_ui_manager (de);
 }
 
-
-static gboolean
-on_switch_page (PsppireDataEditor *de, GtkNotebookPage *p,
-               gint pagenum, gpointer data)
+/* Makes the variable with dictionary index DICT_INDEX in DE's dictionary
+   visible and selected in the active view in DE. */
+void
+psppire_data_editor_goto_variable (PsppireDataEditor *de, gint dict_index)
 {
-  switch (pagenum)
+  PsppireDataSheet *data_sheet;
+
+  switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
     {
     case PSPPIRE_DATA_EDITOR_DATA_VIEW:
-      gtk_widget_grab_focus (de->data_vbox);
-      on_select_range (de);
+      data_sheet = psppire_data_editor_get_active_data_sheet (de);
+      psppire_data_sheet_show_variable (data_sheet, dict_index);
       break;
+
     case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
-      gtk_widget_grab_focus (de->var_sheet);
-      emit_selected_signal (de);
+      psppire_var_sheet_goto_variable (PSPPIRE_VAR_SHEET (de->var_sheet),
+                                       dict_index);
       break;
-    default:
-      break;
-    };
-
-  return TRUE;
-}
-
-
-
-static gboolean
-data_is_selected (PsppireDataEditor *de)
-{
-  PsppireSheetRange range;
-  gint row, col;
-
-  if ( gtk_notebook_get_current_page (GTK_NOTEBOOK (de)) != PSPPIRE_DATA_EDITOR_DATA_VIEW)
-    return FALSE;
-
-  psppire_sheet_get_active_cell (PSPPIRE_SHEET (de->data_sheet[0]), &row, &col);
-
-  if ( row >= psppire_data_store_get_case_count (de->data_store)
-       ||
-       col >= psppire_var_store_get_var_cnt (de->var_store))
-    {
-      return FALSE;
     }
-
-  psppire_sheet_get_selected_range (PSPPIRE_SHEET (de->data_sheet[0]), &range);
-
-  if ( range.rowi >= psppire_data_store_get_case_count (de->data_store)
-       ||
-       range.coli >= psppire_var_store_get_var_cnt (de->var_store))
-    {
-      return FALSE;
-    }
-
-  return TRUE;
 }
 
+/* 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;
+        }
+    }
 
-static void
-on_select_row (PsppireSheet *sheet, gint row, PsppireDataEditor *de)
-{
-  g_signal_emit (de, data_editor_signals[CASES_SELECTED], 0, row);
+  return PSPPIRE_DATA_SHEET (de->data_sheets[0]);
 }
 
+/* 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.
 
-static void
-on_select_variable (PsppireSheet *sheet, gint var, PsppireDataEditor *de)
+   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)
 {
-  g_signal_emit (de, data_editor_signals[VARIABLES_SELECTED], 0, var);
+  psppire_data_editor_update_ui_manager (de);
+  return de->ui_manager;
 }
 
-
-\f
-
-/* Clipboard stuff */
-
-
-#include <data/casereader.h>
-#include <data/case-map.h>
-#include <data/casewriter.h>
-
-#include <data/data-out.h>
-#include "xalloc.h"
-
-/* A casereader and dictionary holding the data currently in the clip */
-static struct casereader *clip_datasheet = NULL;
-static struct dictionary *clip_dict = NULL;
-
-
-static void data_sheet_update_clipboard (PsppireSheet *);
-
-/* Set the clip according to the currently
-   selected range in the data sheet */
 static void
-data_sheet_set_clip (PsppireSheet *sheet)
+psppire_data_editor_update_ui_manager (PsppireDataEditor *de)
 {
-  int i;
-  struct casewriter *writer ;
-  PsppireSheetRange range;
-  PsppireDataStore *ds;
-  struct case_map *map = NULL;
-  casenumber max_rows;
-  size_t max_columns;
-  gint row0, rowi;
-  gint col0, coli;
-
-  ds = PSPPIRE_DATA_STORE (psppire_sheet_get_model (sheet));
-
-  psppire_sheet_get_selected_range (sheet, &range);
-
-  col0 = MIN (range.col0, range.coli);
-  coli = MAX (range.col0, range.coli);
-  row0 = MIN (range.row0, range.rowi);
-  rowi = MAX (range.row0, range.rowi);
-
-   /* If nothing selected, then use active cell */
-  if ( row0 < 0 || col0 < 0 )
-    {
-      gint row, col;
-      psppire_sheet_get_active_cell (sheet, &row, &col);
+  PsppireDataSheet *data_sheet;
+  GtkUIManager *ui_manager;
 
-      row0 = rowi = row;
-      col0 = coli = col;
-    }
+  ui_manager = NULL;
 
-  /* The sheet range can include cells that do not include data.
-     Exclude them from the range. */
-  max_rows = psppire_data_store_get_case_count (ds);
-  if (rowi >= max_rows)
-    {
-      if (max_rows == 0)
-        return;
-      rowi = max_rows - 1;
-    }
-  max_columns = dict_get_var_cnt (ds->dict->dict);
-  if (coli >= max_columns)
-    {
-      if (max_columns == 0)
-        return;
-      coli = max_columns - 1;
-    }
-
-  /* Destroy any existing clip */
-  if ( clip_datasheet )
-    {
-      casereader_destroy (clip_datasheet);
-      clip_datasheet = NULL;
-    }
-
-  if ( clip_dict )
-    {
-      dict_destroy (clip_dict);
-      clip_dict = NULL;
-    }
-
-  /* Construct clip dictionary. */
-  clip_dict = dict_create (dict_get_encoding (ds->dict->dict));
-  for (i = col0; i <= coli; i++)
-    dict_clone_var_assert (clip_dict, dict_get_var (ds->dict->dict, i));
-
-  /* Construct clip data. */
-  map = case_map_by_name (ds->dict->dict, clip_dict);
-  writer = autopaging_writer_create (dict_get_proto (clip_dict));
-  for (i = row0; i <= rowi ; ++i )
+  switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (de)))
     {
-      struct ccase *old = psppire_data_store_get_case (ds, i);
-      if (old != NULL)
-        casewriter_write (writer, case_map_execute (map, old));
+    case PSPPIRE_DATA_EDITOR_DATA_VIEW:
+      data_sheet = psppire_data_editor_get_active_data_sheet (de);
+      if (data_sheet != NULL)
+        ui_manager = psppire_data_sheet_get_ui_manager (data_sheet);
       else
-        casewriter_force_error (writer);
-    }
-  case_map_destroy (map);
-
-  clip_datasheet = casewriter_make_reader (writer);
-
-  data_sheet_update_clipboard (sheet);
-}
-
-enum {
-  SELECT_FMT_NULL,
-  SELECT_FMT_TEXT,
-  SELECT_FMT_HTML
-};
-
-
-/* Perform data_out for case CC, variable V, appending to STRING */
-static void
-data_out_g_string (GString *string, const struct variable *v,
-                  const struct ccase *cc)
-{
-  const struct fmt_spec *fs = var_get_print_format (v);
-  const union value *val = case_data (cc, v);
-
-  char *s = data_out (val, var_get_encoding (v), fs);
-
-  g_string_append (string, s);
-
-  g_free (s);
-}
-
-static GString *
-clip_to_text (void)
-{
-  casenumber r;
-  GString *string;
-
-  const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
-  const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
-  const size_t var_cnt = dict_get_var_cnt (clip_dict);
-
-  string = g_string_sized_new (10 * val_cnt * case_cnt);
-
-  for (r = 0 ; r < case_cnt ; ++r )
-    {
-      int c;
-      struct ccase *cc;
-
-      cc = casereader_peek (clip_datasheet, r);
-      if (cc == NULL)
-       {
-         g_warning ("Clipboard seems to have inexplicably shrunk");
-         break;
-       }
-
-      for (c = 0 ; c < var_cnt ; ++c)
-       {
-         const struct variable *v = dict_get_var (clip_dict, c);
-         data_out_g_string (string, v, cc);
-         if ( c < val_cnt - 1 )
-           g_string_append (string, "\t");
-       }
-
-      if ( r < case_cnt)
-       g_string_append (string, "\n");
-
-      case_unref (cc);
-    }
-
-  return string;
-}
-
-
-static GString *
-clip_to_html (void)
-{
-  casenumber r;
-  GString *string;
-
-  const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
-  const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
-  const size_t var_cnt = dict_get_var_cnt (clip_dict);
-
-  /* Guestimate the size needed */
-  string = g_string_sized_new (80 + 20 * val_cnt * case_cnt);
-
-  g_string_append (string,
-                  "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
-
-  g_string_append (string, "<table>\n");
-  for (r = 0 ; r < case_cnt ; ++r )
-    {
-      int c;
-      struct ccase *cc = casereader_peek (clip_datasheet, r);
-      if (cc == NULL)
-       {
-         g_warning ("Clipboard seems to have inexplicably shrunk");
-         break;
-       }
-      g_string_append (string, "<tr>\n");
-
-      for (c = 0 ; c < var_cnt ; ++c)
-       {
-         const struct variable *v = dict_get_var (clip_dict, c);
-         g_string_append (string, "<td>");
-         data_out_g_string (string, v, cc);
-         g_string_append (string, "</td>\n");
-       }
-
-      g_string_append (string, "</tr>\n");
-
-      case_unref (cc);
-    }
-  g_string_append (string, "</table>\n");
-
-  return string;
-}
-
-
-
-static void
-clipboard_get_cb (GtkClipboard     *clipboard,
-                 GtkSelectionData *selection_data,
-                 guint             info,
-                 gpointer          data)
-{
-  GString *string = NULL;
-
-  switch (info)
-    {
-    case SELECT_FMT_TEXT:
-      string = clip_to_text ();
+        {
+          /* This happens transiently in psppire_data_editor_split_window(). */
+        }
       break;
-    case SELECT_FMT_HTML:
-      string = clip_to_html ();
+
+    case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
+      ui_manager = psppire_var_sheet_get_ui_manager (
+        PSPPIRE_VAR_SHEET (de->var_sheet));
       break;
+
     default:
-      g_assert_not_reached ();
+      /* This happens transiently in psppire_data_editor_init(). */
+      break;
     }
 
-  gtk_selection_data_set (selection_data, selection_data->target,
-                         8,
-                         (const guchar *) string->str, string->len);
-
-  g_string_free (string, TRUE);
-}
-
-static void
-clipboard_clear_cb (GtkClipboard *clipboard,
-                   gpointer data)
-{
-  dict_destroy (clip_dict);
-  clip_dict = NULL;
-
-  casereader_destroy (clip_datasheet);
-  clip_datasheet = NULL;
-}
-
-
-static const GtkTargetEntry targets[] = {
-  { "UTF8_STRING",   0, SELECT_FMT_TEXT },
-  { "STRING",        0, SELECT_FMT_TEXT },
-  { "TEXT",          0, SELECT_FMT_TEXT },
-  { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
-  { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
-  { "text/plain",    0, SELECT_FMT_TEXT },
-  { "text/html",     0, SELECT_FMT_HTML }
-};
-
-
-
-static void
-data_sheet_update_clipboard (PsppireSheet *sheet)
-{
-  GtkClipboard *clipboard =
-    gtk_widget_get_clipboard (GTK_WIDGET (sheet),
-                             GDK_SELECTION_CLIPBOARD);
-
-  if (!gtk_clipboard_set_with_owner (clipboard, targets,
-                                    G_N_ELEMENTS (targets),
-                                    clipboard_get_cb, clipboard_clear_cb,
-                                    G_OBJECT (sheet)))
-    clipboard_clear_cb (clipboard, sheet);
-}
-
-
-
-/* A callback for when the clipboard contents have been received */
-static void
-data_sheet_contents_received_callback (GtkClipboard *clipboard,
-                                     GtkSelectionData *sd,
-                                     gpointer data)
-{
-  gint count = 0;
-  gint row, column;
-  gint next_row, next_column;
-  gint first_column;
-  char *c;
-  PsppireDataEditor *data_editor = data;
-
-  if ( sd->length < 0 )
-    return;
-
-  if ( sd->type != gdk_atom_intern ("UTF8_STRING", FALSE))
-    return;
-
-  c = (char *) sd->data;
-
-  /* Paste text to selected position */
-  psppire_sheet_get_active_cell (PSPPIRE_SHEET (data_editor->data_sheet[0]),
-                            &row, &column);
-
-  g_return_if_fail (row >= 0);
-  g_return_if_fail (column >= 0);
-
-  first_column = column;
-  next_row = row;
-  next_column = column;
-  while (count < sd->length)
+  if (ui_manager != de->ui_manager)
     {
-      char *s = c;
-
-      row = next_row;
-      column = next_column;
-      while (*c != '\t' && *c != '\n' && count < sd->length)
-       {
-         c++;
-         count++;
-       }
-      if ( *c == '\t')
-       {
-         next_row = row ;
-         next_column = column + 1;
-       }
-      else if ( *c == '\n')
-       {
-         next_row = row + 1;
-         next_column = first_column;
-       }
-      *c++ = '\0';
-      count++;
-
-
-      /* Append some new cases if pasting beyond the last row */
-      if ( row >= psppire_data_store_get_case_count (data_editor->data_store))
-       psppire_data_store_insert_new_case (data_editor->data_store, row);
-
-      psppire_data_store_set_string (data_editor->data_store, s, row, column);
-    }
-}
-
+      if (de->ui_manager)
+        g_object_unref (de->ui_manager);
+      if (ui_manager)
+        g_object_ref (ui_manager);
+      de->ui_manager = ui_manager;
 
-static void
-on_owner_change (GtkClipboard *clip, GdkEventOwnerChange *event, gpointer data)
-{
-  gint i;
-  gboolean compatible_target = FALSE;
-  PsppireDataEditor *de = PSPPIRE_DATA_EDITOR (data);
-
-  for (i = 0 ; i < sizeof (targets) / sizeof(targets[0]) ; ++i )
-    {
-      GdkAtom atom = gdk_atom_intern (targets[i].target, TRUE);
-      if ( gtk_clipboard_wait_is_target_available (clip, atom))
-       {
-         compatible_target = TRUE;
-         break;
-       }
+      g_object_notify (G_OBJECT (de), "ui-manager");
     }
-
-  g_signal_emit (de, data_editor_signals[DATA_AVAILABLE_CHANGED], 0,
-                compatible_target);
 }
index f6f55d7ca5c56c9381dc8dd0b332248eb09738a2..ea08f0131782a2f59b93ae61793c5b4c4e31f480 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (C) 2008, 2010 Free Software Foundation, Inc.
+   Copyright (C) 2008, 2010, 2011, 2012 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
 #ifndef __PSPPIRE_DATA_EDITOR_H__
 #define __PSPPIRE_DATA_EDITOR_H__
 
+/* PsppireDataEditor is a GtkNotebook for editing a single PSPP dataset.
+
+   PsppireDataEditor has two tabs that normally contain a PsppireDataSheet and
+   a PsppireVarSheet.  The user can choose to "split" the PsppireDataSheet view
+   into four views within the single tab.  PsppireDataEditor also adds some
+   decorations above the PsppireDataSheet to note the current cell and allow
+   the current cell to be edited.
+
+   PsppireDataEditor's normal parent in the widget hierarchy is
+   PsppireDataWindow. */
 
 #include <glib.h>
 #include <glib-object.h>
 #include <gtk/gtk.h>
 
-#include <ui/gui/sheet/psppire-axis.h>
 #include "psppire-var-store.h"
 #include "psppire-data-store.h"
+#include "ui/gui/pspp-sheet-view.h"
 
 G_BEGIN_DECLS
 
@@ -44,30 +54,27 @@ struct _PsppireDataEditor
   GtkNotebook parent;
 
   /* <private> */
-  gboolean dispose_has_run;
-  GtkWidget *cell_ref_entry;
-  GtkWidget *datum_entry;
-  GtkWidget *var_sheet;
-  struct _PsppireDataWindow *data_window;
   PsppireDataStore *data_store;
   PsppireVarStore *var_store;
 
-  GtkWidget *sheet_bin[4];
-  GtkWidget *data_sheet[4];
+  /* Font to use in var sheet and data sheet(s), NULL to use system default. */
+  struct _PangoFontDescription *font;
 
-  GtkWidget *data_vbox;
-
-  GtkWidget *paned;
-  gboolean split;
+  /* Variable sheet tab. */
+  GtkWidget *var_sheet;
 
-  PsppireAxis *vaxis[2];
+  /* Data sheet tab. */
+  GtkWidget *vbox;             /* Top-level widget in tab. */
+  GtkWidget *cell_ref_label;   /* GtkLabel that shows selected case and var. */
+  GtkWidget *datum_entry;      /* GtkComboBoxEntry for editing current cell. */
+  GtkWidget *datasheet_vbox_widget; /* ->vbox child that holds data sheets. */
+  GtkWidget *data_sheets[4];   /* Normally one data sheet; four, if split. */
+  gboolean split;              /* True if data sheets are split. */
 
-  /* There's only one horizontal axis, since the
-     column widths are parameters of the variables */
-  PsppireAxis *haxis;
+  /* UI manager for whichever var or data sheet is currently in use. */
+  GtkUIManager *ui_manager;
 };
 
-
 struct _PsppireDataEditorClass
 {
   GtkNotebookClass parent_class;
@@ -75,25 +82,19 @@ struct _PsppireDataEditorClass
 
 
 GType          psppire_data_editor_get_type        (void);
-GtkWidget*     psppire_data_editor_new             (struct _PsppireDataWindow *, PsppireVarStore *, PsppireDataStore *);
-void           psppire_data_editor_clip_copy       (PsppireDataEditor *);
-void           psppire_data_editor_clip_paste      (PsppireDataEditor *);
-void           psppire_data_editor_clip_cut        (PsppireDataEditor *);
-void           psppire_data_editor_sort_ascending  (PsppireDataEditor *);
-void           psppire_data_editor_sort_descending (PsppireDataEditor *);
-void           psppire_data_editor_insert_variable (PsppireDataEditor *);
-void           psppire_data_editor_delete_variables (PsppireDataEditor *);
+GtkWidget*     psppire_data_editor_new             (PsppireVarStore *, PsppireDataStore *);
 void           psppire_data_editor_show_grid       (PsppireDataEditor *, gboolean);
-void           psppire_data_editor_insert_case     (PsppireDataEditor *);
-void           psppire_data_editor_delete_cases    (PsppireDataEditor *);
 void           psppire_data_editor_set_font        (PsppireDataEditor *, PangoFontDescription *);
-void           psppire_data_editor_delete_cases    (PsppireDataEditor *);
 void           psppire_data_editor_split_window    (PsppireDataEditor *, gboolean );
 
+void           psppire_data_editor_goto_variable   (PsppireDataEditor *, gint dict_index);
 
-G_END_DECLS
+struct _PsppireDataSheet *psppire_data_editor_get_active_data_sheet (PsppireDataEditor *);
+
+GtkUIManager *psppire_data_editor_get_ui_manager (PsppireDataEditor *);
 
 enum {PSPPIRE_DATA_EDITOR_DATA_VIEW = 0, PSPPIRE_DATA_EDITOR_VARIABLE_VIEW};
 
+G_END_DECLS
 
 #endif /* __PSPPIRE_DATA_EDITOR_H__ */
diff --git a/src/ui/gui/psppire-data-sheet.c b/src/ui/gui/psppire-data-sheet.c
new file mode 100644 (file)
index 0000000..b25b189
--- /dev/null
@@ -0,0 +1,2236 @@
+/* PSPPIRE - a graphical user interface for PSPP.
+   Copyright (C) 2011, 2012 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
+   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,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   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, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "ui/gui/psppire-data-sheet.h"
+
+#include "data/case-map.h"
+#include "data/casereader.h"
+#include "data/casewriter.h"
+#include "data/data-out.h"
+#include "data/datasheet.h"
+#include "data/format.h"
+#include "data/value-labels.h"
+#include "libpspp/intern.h"
+#include "libpspp/range-set.h"
+#include "ui/gui/executor.h"
+#include "ui/gui/find-dialog.h"
+#include "ui/gui/goto-case-dialog.h"
+#include "ui/gui/builder-wrapper.h"
+#include "ui/gui/helper.h"
+#include "ui/gui/pspp-sheet-selection.h"
+#include "ui/gui/psppire-cell-renderer-button.h"
+#include "ui/gui/psppire-data-store.h"
+#include "ui/gui/psppire-data-window.h"
+#include "ui/gui/psppire-dialog-action-var-info.h"
+#include "ui/gui/psppire-empty-list-store.h"
+#include "ui/gui/psppire-marshal.h"
+
+#include "gl/intprops.h"
+#include "gl/xalloc.h"
+
+#include <gettext.h>
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+static void psppire_data_sheet_destroy (GtkObject *);
+static void psppire_data_sheet_unset_data_store (PsppireDataSheet *);
+
+static void psppire_data_sheet_update_clip_actions (PsppireDataSheet *);
+static void psppire_data_sheet_set_clip (PsppireDataSheet *);
+
+static void on_selection_changed (PsppSheetSelection *, gpointer);
+
+G_DEFINE_TYPE (PsppireDataSheet, psppire_data_sheet, PSPP_TYPE_SHEET_VIEW);
+
+static gboolean
+get_tooltip_location (GtkWidget *widget, GtkTooltip *tooltip,
+                      gint wx, gint wy,
+                      size_t *row, PsppSheetViewColumn **columnp)
+{
+  PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
+  gint bx, by;
+  GtkTreePath *path;
+  GtkTreeIter iter;
+  PsppSheetViewColumn *tree_column;
+  GtkTreeModel *tree_model;
+  bool ok;
+
+  /* Check that WIDGET is really visible on the screen before we
+     do anything else.  This is a bug fix for a sticky situation:
+     when text_data_import_assistant() returns, it frees the data
+     necessary to compose the tool tip message, but there may be
+     a tool tip under preparation at that point (even if there is
+     no visible tool tip) that will call back into us a little
+     bit later.  Perhaps the correct solution to this problem is
+     to make the data related to the tool tips part of a GObject
+     that only gets destroyed when all references are released,
+     but this solution appears to be effective too. */
+  if (!gtk_widget_get_mapped (widget))
+    return FALSE;
+
+  pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
+                                                     wx, wy, &bx, &by);
+  if (!pspp_sheet_view_get_path_at_pos (tree_view, bx, by,
+                                      &path, &tree_column, NULL, NULL))
+    return FALSE;
+
+  *columnp = tree_column;
+
+  pspp_sheet_view_set_tooltip_cell (tree_view, tooltip, path, tree_column,
+                                    NULL);
+
+  tree_model = pspp_sheet_view_get_model (tree_view);
+  ok = gtk_tree_model_get_iter (tree_model, &iter, path);
+  gtk_tree_path_free (path);
+  if (!ok)
+    return FALSE;
+
+  *row = GPOINTER_TO_INT (iter.user_data);
+  return TRUE;
+}
+
+static gboolean
+on_query_tooltip (GtkWidget *widget, gint wx, gint wy,
+                  gboolean keyboard_mode UNUSED,
+                  GtkTooltip *tooltip, gpointer data UNUSED)
+{
+  PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (widget);
+  PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
+  PsppSheetViewColumn *column;
+  struct variable *var;
+  const char *label;
+  union value v;
+  size_t row;
+  int width;
+
+  g_return_val_if_fail (data_store != NULL, FALSE);
+
+  if (!get_tooltip_location (widget, tooltip, wx, wy, &row, &column))
+    return FALSE;
+
+  var = g_object_get_data (G_OBJECT (column), "variable");
+  if (var == NULL)
+    {
+      if (g_object_get_data (G_OBJECT (column), "new-var-column") == NULL)
+        return FALSE;
+
+      gtk_tooltip_set_text (tooltip,
+                            _("Enter a number to add a new variable."));
+      return TRUE;
+    }
+  else if (row >= datasheet_get_n_rows (data_store->datasheet))
+    {
+      gtk_tooltip_set_text (tooltip, _("Enter a number to add a new case."));
+      return TRUE;
+    }
+
+  width = var_get_width (var);
+
+  value_init (&v, width);
+  datasheet_get_value (data_store->datasheet, row, var_get_case_index (var),
+                       &v);
+
+  label = var_lookup_value_label (var, &v);
+  if (label != NULL)
+    {
+      if (data_sheet->show_value_labels)
+        {
+          char *s = value_to_text (v, var);
+          gtk_tooltip_set_text (tooltip, s);
+          free (s);
+        }
+      else
+        gtk_tooltip_set_text (tooltip, label);
+    }
+  value_destroy (&v, width);
+
+  return label != NULL;
+}
+
+static void
+render_row_number_cell (PsppSheetViewColumn *tree_column,
+                        GtkCellRenderer *cell,
+                        GtkTreeModel *model,
+                        GtkTreeIter *iter,
+                        gpointer store_)
+{
+  PsppireDataStore *store = store_;
+  GValue gvalue = { 0, };
+  gint row;
+
+  row = GPOINTER_TO_INT (iter->user_data);
+
+  g_value_init (&gvalue, G_TYPE_INT);
+  g_value_set_int (&gvalue, row + 1);
+  g_object_set_property (G_OBJECT (cell), "label", &gvalue);
+  g_value_unset (&gvalue);
+
+  if (row < datasheet_get_n_rows (store->datasheet))
+    g_object_set (cell, "editable", TRUE, NULL);
+  else
+    g_object_set (cell, "editable", FALSE, NULL);
+
+  g_object_set (cell,
+                "slash", psppire_data_store_filtered (store, row),
+                NULL);
+}
+
+static void
+on_row_number_clicked (PsppireCellRendererButton *button,
+                       gchar *path_string,
+                       PsppSheetView *sheet_view)
+{
+  PsppSheetSelection *selection;
+  GtkTreePath *path;
+
+  path = gtk_tree_path_new_from_string (path_string);
+
+  selection = pspp_sheet_view_get_selection (sheet_view);
+  pspp_sheet_selection_unselect_all (selection);
+  pspp_sheet_selection_select_path (selection, path);
+  pspp_sheet_selection_select_all_columns (selection);
+
+  gtk_tree_path_free (path);
+}
+
+static void
+make_row_number_column (PsppireDataSheet *data_sheet,
+                        PsppireDataStore *ds)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppSheetViewColumn *column;
+  GtkCellRenderer *renderer;
+
+  renderer = psppire_cell_renderer_button_new ();
+  g_object_set (renderer, "xalign", 1.0, NULL);
+  g_signal_connect (renderer, "clicked", G_CALLBACK (on_row_number_clicked),
+                    sheet_view);
+
+  column = pspp_sheet_view_column_new_with_attributes (_("Case"),
+                                                       renderer, NULL);
+  g_object_set (column, "selectable", FALSE, "row-head", TRUE, NULL);
+  pspp_sheet_view_column_set_cell_data_func (
+    column, renderer, render_row_number_cell, ds, NULL);
+  pspp_sheet_view_column_set_fixed_width (column, 50);
+  pspp_sheet_view_column_set_visible (column, data_sheet->show_case_numbers);
+  pspp_sheet_view_append_column (sheet_view, column);
+}
+
+static void
+render_data_cell (PsppSheetViewColumn *tree_column,
+                  GtkCellRenderer *cell,
+                  GtkTreeModel *model,
+                  GtkTreeIter *iter,
+                  gpointer data_sheet_)
+{
+  PsppireDataSheet *data_sheet = data_sheet_;
+  PsppireDataStore *store = psppire_data_sheet_get_data_store (data_sheet);
+  struct variable *var;
+  gchar *string;
+  gint row;
+
+  double xalign;
+
+  row = GPOINTER_TO_INT (iter->user_data);
+  var = g_object_get_data (G_OBJECT (tree_column), "variable");
+
+  string = psppire_data_store_get_string (store, row, var,
+                                          data_sheet->show_value_labels);
+  if (string != NULL)
+    {
+      GValue gvalue = { 0 };
+
+      g_value_init (&gvalue, G_TYPE_STRING);
+      g_value_take_string (&gvalue, string);
+      g_object_set_property (G_OBJECT (cell), "text", &gvalue);
+      g_value_unset (&gvalue);
+    }
+  else
+    g_object_set (G_OBJECT (cell), "text", "", NULL);
+
+  switch (var_get_alignment (var))
+    {
+    case ALIGN_LEFT: xalign = 0.0; break;
+    case ALIGN_RIGHT: xalign = 1.0; break;
+    case ALIGN_CENTRE: xalign = 0.5; break;
+    default: xalign = 0.0; break;
+    }
+  g_object_set (cell,
+                "xalign", xalign,
+                "editable", TRUE,
+                NULL);
+}
+
+static gint
+get_string_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
+                  const char *string)
+{
+  gint width;
+  g_object_set (G_OBJECT (renderer), "text", string, (void *) NULL);
+  gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
+                              NULL, NULL, NULL, &width, NULL);
+  return width;
+}
+
+static gint
+get_monospace_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
+                     size_t char_cnt)
+{
+  struct string s;
+  gint width;
+
+  ds_init_empty (&s);
+  ds_put_byte_multiple (&s, '0', char_cnt);
+  ds_put_byte (&s, ' ');
+  width = get_string_width (treeview, renderer, ds_cstr (&s));
+  ds_destroy (&s);
+
+  return width;
+}
+
+static void
+on_data_column_editing_started (GtkCellRenderer *cell,
+                                GtkCellEditable *editable,
+                                const gchar     *path,
+                                gpointer         user_data)
+{
+  PsppSheetViewColumn *column = g_object_get_data (G_OBJECT (cell), "column");
+  PsppireDataSheet *data_sheet = g_object_get_data (G_OBJECT (cell), "data-sheet");
+  PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
+  struct variable *var;
+
+  g_return_if_fail (column);
+  g_return_if_fail (data_sheet);
+  g_return_if_fail (data_store);
+
+
+  g_object_ref (editable);
+  g_object_set_data_full (G_OBJECT (cell), "data-sheet-editable",
+                          editable, g_object_unref);
+
+  var = g_object_get_data (G_OBJECT (column), "variable");
+  g_return_if_fail (var);
+
+  if (var_has_value_labels (var) && GTK_IS_COMBO_BOX (editable))
+    {
+      const struct val_labs *labels = var_get_value_labels (var);
+      const struct val_lab *vl;
+      GtkListStore *list_store;
+
+      list_store = gtk_list_store_new (1, G_TYPE_STRING);
+      for (vl = val_labs_first (labels); vl != NULL;
+           vl = val_labs_next (labels, vl))
+        {
+          GtkTreeIter iter;
+
+          gtk_list_store_append (list_store, &iter);
+          gtk_list_store_set (list_store, &iter,
+                              0, val_lab_get_label (vl),
+                              -1);
+        }
+
+      gtk_combo_box_set_model (GTK_COMBO_BOX (editable),
+                               GTK_TREE_MODEL (list_store));
+      g_object_unref (list_store);
+    }
+}
+
+static void
+scroll_to_bottom (GtkWidget      *widget,
+                  GtkRequisition *requisition,
+                  gpointer        unused UNUSED)
+{
+  PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (widget);
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
+  GtkAdjustment *vadjust;
+
+  vadjust = pspp_sheet_view_get_vadjustment (sheet_view);
+  gtk_adjustment_set_value (vadjust, gtk_adjustment_get_upper (vadjust));
+
+  if (data_sheet->scroll_to_bottom_signal)
+    {
+      g_signal_handler_disconnect (data_sheet,
+                                   data_sheet->scroll_to_bottom_signal);
+      data_sheet->scroll_to_bottom_signal = 0;
+    }
+}
+
+static void
+on_data_column_edited (GtkCellRendererText *cell,
+                       gchar               *path_string,
+                       gchar               *new_text,
+                       gpointer             user_data)
+{
+  PsppSheetViewColumn *column = g_object_get_data (G_OBJECT (cell), "column");
+  PsppireDataSheet *data_sheet = g_object_get_data (G_OBJECT (cell), "data-sheet");
+  PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
+  GtkEditable *editable;
+  struct variable *var;
+  GtkTreePath *path;
+  gboolean is_val_lab;
+  gboolean new_row;
+  gint row;
+
+  path = gtk_tree_path_new_from_string (path_string);
+  row = gtk_tree_path_get_indices (path)[0];
+  gtk_tree_path_free (path);
+
+  var = g_object_get_data (G_OBJECT (column), "variable");
+
+  new_row = row == psppire_data_store_get_case_count (data_store);
+  if (new_row && new_text[0] == '\0')
+    return;
+
+  editable = g_object_steal_data (G_OBJECT (cell), "data-sheet-editable");
+  g_return_if_fail (editable != NULL);
+  is_val_lab = (GTK_IS_COMBO_BOX (editable)
+                && gtk_combo_box_get_active (GTK_COMBO_BOX (editable)) >= 0);
+  g_object_unref (editable);
+
+  psppire_data_store_set_string (data_store, new_text, row, var, is_val_lab);
+
+  if (new_row && !data_sheet->scroll_to_bottom_signal)
+    {
+      gtk_widget_queue_resize (GTK_WIDGET (data_sheet));
+      data_sheet->scroll_to_bottom_signal =
+        g_signal_connect (data_sheet, "size-request",
+                          G_CALLBACK (scroll_to_bottom), NULL);
+    }
+  else
+    {
+      /* We could be more specific about what to redraw, if it seems
+         important for performance. */
+      gtk_widget_queue_draw (GTK_WIDGET (data_sheet));
+    }
+}
+
+static void
+scroll_to_right (GtkWidget      *widget,
+                 PsppireDataSheet  *data_sheet)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppSheetViewColumn *column, *prev;
+  GList *columns, *iter;
+
+  column = NULL;
+  prev = NULL;
+  columns = pspp_sheet_view_get_columns (sheet_view);
+  for (iter = columns; iter; iter = iter->next)
+    {
+      PsppSheetViewColumn *c = iter->data;
+      if (g_object_get_data (G_OBJECT (c), "new-var-column"))
+        {
+          column = c;
+          break;
+        }
+      prev = c;
+    }
+  g_list_free (columns);
+
+  if (column == NULL)
+    return;
+
+  pspp_sheet_view_scroll_to_cell (sheet_view, NULL, column, FALSE, 0, 0);
+
+  if (prev)
+    {
+      GtkTreePath *path;
+
+      pspp_sheet_view_get_cursor (sheet_view, &path, NULL);
+      if (path)
+        {
+          pspp_sheet_view_set_cursor (sheet_view, path, prev, TRUE);
+          gtk_tree_path_free (path);
+        }
+    }
+
+  if (data_sheet->scroll_to_right_signal)
+    {
+      g_signal_handler_disconnect (widget, data_sheet->scroll_to_right_signal);
+      data_sheet->scroll_to_right_signal = 0;
+    }
+}
+
+static void
+on_new_variable_column_edited (GtkCellRendererText *cell,
+                               gchar               *path_string,
+                               gchar               *new_text,
+                               gpointer             user_data)
+{
+  PsppireDataSheet *data_sheet = g_object_get_data (G_OBJECT (cell), "data-sheet");
+  PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
+  PsppireDict *dict = data_store->dict;
+  struct variable *var;
+  GtkTreePath *path;
+  char name[64];
+  gint row;
+
+  if (new_text[0] == '\0')
+    {
+      /* User didn't enter anything so don't create a variable. */
+      return;
+    }
+
+  path = gtk_tree_path_new_from_string (path_string);
+  row = gtk_tree_path_get_indices (path)[0];
+  gtk_tree_path_free (path);
+
+  if (!psppire_dict_generate_name (dict, name, sizeof name))
+    return;
+
+  var = psppire_dict_insert_variable (dict, psppire_dict_get_var_cnt (dict),
+                                      name);
+  g_return_if_fail (var != NULL);
+
+  psppire_data_store_set_string (data_store, new_text, row, var, FALSE);
+
+  if (!data_sheet->scroll_to_right_signal)
+    {
+      gtk_widget_queue_resize (GTK_WIDGET (data_sheet));
+      data_sheet->scroll_to_right_signal =
+        g_signal_connect_after (gtk_widget_get_toplevel (GTK_WIDGET (data_sheet)), "check-resize",
+                                G_CALLBACK (scroll_to_right), data_sheet);
+    }
+  else
+    {
+      /* We could be more specific about what to redraw, if it seems
+         important for performance. */
+      gtk_widget_queue_draw (GTK_WIDGET (data_sheet));
+    }
+}
+
+static void
+calc_width_conversion (PsppireDataSheet *data_sheet,
+                       gint *base_width, gint *incr_width)
+{
+  GtkCellRenderer *cell;
+  gint w1, w10;
+
+  cell = gtk_cell_renderer_text_new ();
+  w1 = get_monospace_width (PSPP_SHEET_VIEW (data_sheet), cell, 1);
+  w10 = get_monospace_width (PSPP_SHEET_VIEW (data_sheet), cell, 10);
+  *incr_width = MAX (1, (w10 - w1) / 9);
+  *base_width = MAX (0, w10 - *incr_width * 10);
+  g_object_ref_sink (cell);
+  g_object_unref (cell);
+}
+
+static gint
+display_width_from_pixel_width (PsppireDataSheet *data_sheet,
+                                gint pixel_width)
+{
+  gint base_width, incr_width;
+
+  calc_width_conversion (data_sheet, &base_width, &incr_width);
+  return MAX ((pixel_width - base_width + incr_width / 2) / incr_width, 1);
+}
+
+static gint
+display_width_to_pixel_width (PsppireDataSheet *data_sheet,
+                              gint display_width,
+                              gint base_width,
+                              gint incr_width)
+{
+  return base_width + incr_width * display_width;
+}
+
+static void
+on_data_column_resized (GObject    *gobject,
+                        GParamSpec *pspec,
+                        gpointer    user_data)
+{
+  PsppireDataSheet *data_sheet = user_data;
+  PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
+  PsppSheetViewColumn *column = PSPP_SHEET_VIEW_COLUMN (gobject);
+  struct variable *var;
+  gint pixel_width;
+  int display_width;
+
+  if (data_store == NULL)
+    return;
+
+  pixel_width = pspp_sheet_view_column_get_width (column);
+  if (pixel_width == pspp_sheet_view_column_get_fixed_width (column))
+    {
+      /* Short-circuit the expensive display_width_from_pixel_width()
+         calculation, to make loading .sav files with 2000 columns visibly
+         faster. */
+      return;
+    }
+
+  var = g_object_get_data (G_OBJECT (column), "variable");
+  display_width = display_width_from_pixel_width (data_sheet, pixel_width);
+  var_set_display_width (var, display_width);
+}
+
+static void
+do_data_column_popup_menu (PsppSheetViewColumn *column,
+                           guint button, guint32 time)
+{
+  GtkWidget *sheet_view = pspp_sheet_view_column_get_tree_view (column);
+  PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (sheet_view);
+  GtkWidget *menu;
+
+  menu = get_widget_assert (data_sheet->builder, "datasheet-variable-popup");
+  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
+}
+
+static void
+on_data_column_popup_menu (PsppSheetViewColumn *column,
+                           gpointer user_data UNUSED)
+{
+  do_data_column_popup_menu (column, 0, gtk_get_current_event_time ());
+}
+
+static gboolean
+on_column_button_press_event (PsppSheetViewColumn *column,
+                              GdkEventButton *event,
+                              gpointer user_data UNUSED)
+{
+  PsppSheetSelection *selection;
+  PsppSheetView *sheet_view;
+
+  sheet_view = PSPP_SHEET_VIEW (pspp_sheet_view_column_get_tree_view (
+                                  column));
+  g_return_val_if_fail (sheet_view != NULL, FALSE);
+
+  selection = pspp_sheet_view_get_selection (sheet_view);
+  g_return_val_if_fail (selection != NULL, FALSE);
+
+  if (event->type == GDK_BUTTON_PRESS && event->button == 3)
+    {
+      do_data_column_popup_menu (column, event->button, event->time);
+      return TRUE;
+    }
+  else if (event->type == GDK_2BUTTON_PRESS && event->button == 1)
+    {
+      PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (sheet_view);
+      struct variable *var;
+
+      var = g_object_get_data (G_OBJECT (column), "variable");
+      if (var != NULL)
+        {
+          gboolean handled;
+
+          g_signal_emit_by_name (data_sheet, "var-double-clicked",
+                                 var_get_dict_index (var), &handled);
+          return handled;
+        }
+    }
+
+  return FALSE;
+}
+
+static gboolean
+on_data_column_query_tooltip (PsppSheetViewColumn *column,
+                              GtkTooltip *tooltip,
+                              gpointer user_data UNUSED)
+{
+  struct variable *var;
+  const char *text;
+
+  var = g_object_get_data (G_OBJECT (column), "variable");
+  g_return_val_if_fail (var != NULL, FALSE);
+
+  text = var_has_label (var) ? var_get_label (var) : var_get_name (var);
+  gtk_tooltip_set_text (tooltip, text);
+
+  return TRUE;
+}
+
+static void
+add_data_column_cell_renderer (PsppireDataSheet *data_sheet,
+                               PsppSheetViewColumn *column)
+{
+  GtkCellRenderer *cell;
+  struct variable *var;
+
+  var = g_object_get_data (G_OBJECT (column), "variable");
+  g_return_if_fail (var != NULL);
+
+  if (var_has_value_labels (var))
+    {
+      cell = gtk_cell_renderer_combo_new ();
+      g_object_set (G_OBJECT (cell),
+                    "has-entry", TRUE,
+                    "text-column", 0,
+                    NULL);
+    }
+  else
+    cell = gtk_cell_renderer_text_new ();
+
+  g_signal_connect (cell, "editing-started",
+                    G_CALLBACK (on_data_column_editing_started), NULL);
+  g_signal_connect (cell, "edited", G_CALLBACK (on_data_column_edited), NULL);
+
+  g_object_set_data (G_OBJECT (cell), "column", column);
+  g_object_set_data (G_OBJECT (cell), "data-sheet", data_sheet);
+
+  pspp_sheet_view_column_clear (column);
+  pspp_sheet_view_column_pack_start (column, cell, TRUE);
+
+  pspp_sheet_view_column_set_cell_data_func (
+    column, cell, render_data_cell, data_sheet, NULL);
+}
+
+static PsppSheetViewColumn *
+make_data_column (PsppireDataSheet *data_sheet, gint dict_idx,
+                  gint base_width, gint incr_width)
+{
+  PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
+  struct variable *var;
+  PsppSheetViewColumn *column;
+  char *name;
+  int width;
+
+  var = psppire_dict_get_variable (data_store->dict, dict_idx);
+
+  column = pspp_sheet_view_column_new ();
+
+  name = escape_underscores (var_get_name (var));
+  pspp_sheet_view_column_set_title (column, name);
+  free (name);
+
+  g_object_set_data (G_OBJECT (column), "variable", var);
+
+  width = display_width_to_pixel_width (data_sheet,
+                                        var_get_display_width (var),
+                                        base_width, incr_width);
+  pspp_sheet_view_column_set_min_width (column, 10);
+  pspp_sheet_view_column_set_fixed_width (column, width);
+  pspp_sheet_view_column_set_resizable (column, TRUE);
+
+  pspp_sheet_view_column_set_clickable (column, TRUE);
+  g_signal_connect (column, "notify::width",
+                    G_CALLBACK (on_data_column_resized), data_sheet);
+
+  g_signal_connect (column, "button-press-event",
+                    G_CALLBACK (on_column_button_press_event),
+                    data_sheet);
+  g_signal_connect (column, "query-tooltip",
+                    G_CALLBACK (on_data_column_query_tooltip), NULL);
+  g_signal_connect (column, "popup-menu",
+                    G_CALLBACK (on_data_column_popup_menu), data_sheet);
+
+  add_data_column_cell_renderer (data_sheet, column);
+
+  return column;
+}
+
+static void
+make_new_variable_column (PsppireDataSheet *data_sheet,
+                          gint base_width, gint incr_width)
+{
+  PsppSheetViewColumn *column;
+  GtkCellRenderer *cell;
+  int width;
+
+  cell = gtk_cell_renderer_text_new ();
+  g_object_set (cell, "editable", TRUE, NULL);
+
+  g_signal_connect (cell, "edited", G_CALLBACK (on_new_variable_column_edited),
+                    NULL);
+
+  column = pspp_sheet_view_column_new_with_attributes ("", cell, NULL);
+  g_object_set_data (G_OBJECT (column), "new-var-column", column);
+
+  width = display_width_to_pixel_width (data_sheet, 8, base_width, incr_width);
+  pspp_sheet_view_column_set_min_width (column, 10);
+  pspp_sheet_view_column_set_fixed_width (column, width);
+
+  g_object_set_data (G_OBJECT (cell), "data-sheet", data_sheet);
+  g_signal_connect (column, "button-press-event",
+                    G_CALLBACK (on_column_button_press_event),
+                    data_sheet);
+  g_signal_connect (column, "popup-menu",
+                    G_CALLBACK (on_data_column_popup_menu), data_sheet);
+
+  pspp_sheet_view_column_set_visible (column, data_sheet->may_create_vars);
+
+  pspp_sheet_view_append_column (PSPP_SHEET_VIEW (data_sheet), column);
+  data_sheet->new_variable_column = column;
+}
+
+static void
+psppire_data_sheet_model_changed (GObject    *gobject,
+                                  GParamSpec *pspec,
+                                  gpointer    user_data)
+{
+  PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (gobject);
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppireDataStore *data_store;
+
+  /* Remove old columns. */
+  for (;;)
+    {
+      PsppSheetViewColumn *column = pspp_sheet_view_get_column (sheet_view, 0);
+      if (column == NULL)
+        break;
+
+      pspp_sheet_view_remove_column (sheet_view, column);
+    }
+  data_sheet->new_variable_column = NULL;
+
+  if (pspp_sheet_view_get_model (sheet_view) == NULL)
+    {
+      /* Don't create any columns at all if there's no model.  Otherwise we'll
+         create some columns as part of the "dispose" callback for the sheet
+         view, which sets the model to NULL.  That causes warnings to be
+         logged and is obviously undesirable in any case. */
+      return;
+    }
+
+  /* Add new columns. */
+  data_store = psppire_data_sheet_get_data_store (data_sheet);
+  if (data_store != NULL)
+    {
+      gint base_width, incr_width;
+      int i;
+
+      calc_width_conversion (data_sheet, &base_width, &incr_width);
+
+      make_row_number_column (data_sheet, data_store);
+      for (i = 0; i < psppire_dict_get_var_cnt (data_store->dict); i++)
+        {
+          PsppSheetViewColumn *column;
+
+          column = make_data_column (data_sheet, i, base_width, incr_width);
+          pspp_sheet_view_append_column (sheet_view, column);
+        }
+      make_new_variable_column (data_sheet, base_width, incr_width);
+    }
+}
+
+enum
+  {
+    PROP_0,
+    PROP_DATA_STORE,
+    PROP_VALUE_LABELS,
+    PROP_CASE_NUMBERS,
+    PROP_CURRENT_CASE,
+    PROP_MAY_CREATE_VARS,
+    PROP_MAY_DELETE_VARS,
+    PROP_UI_MANAGER
+  };
+
+static void
+psppire_data_sheet_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  PsppireDataSheet *obj = PSPPIRE_DATA_SHEET (object);
+
+  switch (prop_id)
+    {
+    case PROP_DATA_STORE:
+      psppire_data_sheet_set_data_store (
+        obj, PSPPIRE_DATA_STORE (g_value_get_object (value)));
+      break;
+
+    case PROP_VALUE_LABELS:
+      psppire_data_sheet_set_value_labels (obj, g_value_get_boolean (value));
+      break;
+
+    case PROP_CASE_NUMBERS:
+      psppire_data_sheet_set_case_numbers (obj, g_value_get_boolean (value));
+      break;
+
+    case PROP_CURRENT_CASE:
+      psppire_data_sheet_goto_case (obj, g_value_get_long (value));
+      break;
+
+    case PROP_MAY_CREATE_VARS:
+      psppire_data_sheet_set_may_create_vars (obj,
+                                              g_value_get_boolean (value));
+      break;
+
+    case PROP_MAY_DELETE_VARS:
+      psppire_data_sheet_set_may_delete_vars (obj,
+                                              g_value_get_boolean (value));
+      break;
+
+    case PROP_UI_MANAGER:
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+psppire_data_sheet_get_property (GObject      *object,
+                                 guint         prop_id,
+                                 GValue       *value,
+                                 GParamSpec   *pspec)
+{
+  PsppireDataSheet *obj = PSPPIRE_DATA_SHEET (object);
+
+  switch (prop_id)
+    {
+    case PROP_DATA_STORE:
+      g_value_set_object (value, psppire_data_sheet_get_data_store (obj));
+      break;
+
+    case PROP_VALUE_LABELS:
+      g_value_set_boolean (value, psppire_data_sheet_get_value_labels (obj));
+      break;
+
+    case PROP_CASE_NUMBERS:
+      g_value_set_boolean (value, psppire_data_sheet_get_case_numbers (obj));
+      break;
+
+    case PROP_CURRENT_CASE:
+      g_value_set_long (value, psppire_data_sheet_get_selected_case (obj));
+      break;
+
+    case PROP_MAY_CREATE_VARS:
+      g_value_set_boolean (value, obj->may_create_vars);
+      break;
+
+    case PROP_MAY_DELETE_VARS:
+      g_value_set_boolean (value, obj->may_delete_vars);
+      break;
+
+    case PROP_UI_MANAGER:
+      g_value_set_object (value, psppire_data_sheet_get_ui_manager (obj));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+gboolean
+psppire_data_sheet_get_value_labels (const PsppireDataSheet *ds)
+{
+  return ds->show_value_labels;
+}
+
+void
+psppire_data_sheet_set_value_labels (PsppireDataSheet *ds,
+                                  gboolean show_value_labels)
+{
+  show_value_labels = !!show_value_labels;
+  if (show_value_labels != ds->show_value_labels)
+    {
+      ds->show_value_labels = show_value_labels;
+      g_object_notify (G_OBJECT (ds), "value-labels");
+      gtk_widget_queue_draw (GTK_WIDGET (ds));
+
+      /* Make the cell being edited refresh too. */
+      pspp_sheet_view_stop_editing (PSPP_SHEET_VIEW (ds), TRUE);
+    }
+}
+
+gboolean
+psppire_data_sheet_get_case_numbers (const PsppireDataSheet *ds)
+{
+  return ds->show_case_numbers;
+}
+
+void
+psppire_data_sheet_set_case_numbers (PsppireDataSheet *ds,
+                                     gboolean show_case_numbers)
+{
+  show_case_numbers = !!show_case_numbers;
+  if (show_case_numbers != ds->show_case_numbers)
+    {
+      PsppSheetViewColumn *column;
+
+      ds->show_case_numbers = show_case_numbers;
+      column = pspp_sheet_view_get_column (PSPP_SHEET_VIEW (ds), 0);
+      if (column)
+        pspp_sheet_view_column_set_visible (column, show_case_numbers);
+
+      g_object_notify (G_OBJECT (ds), "case-numbers");
+      gtk_widget_queue_draw (GTK_WIDGET (ds));
+    }
+}
+
+gboolean
+psppire_data_sheet_get_may_create_vars (PsppireDataSheet *data_sheet)
+{
+  return data_sheet->may_create_vars;
+}
+
+void
+psppire_data_sheet_set_may_create_vars (PsppireDataSheet *data_sheet,
+                                       gboolean may_create_vars)
+{
+  if (data_sheet->may_create_vars != may_create_vars)
+    {
+      data_sheet->may_create_vars = may_create_vars;
+      if (data_sheet->new_variable_column)
+        pspp_sheet_view_column_set_visible (data_sheet->new_variable_column,
+                                            may_create_vars);
+
+      on_selection_changed (pspp_sheet_view_get_selection (
+                              PSPP_SHEET_VIEW (data_sheet)), NULL);
+    }
+}
+
+gboolean
+psppire_data_sheet_get_may_delete_vars (PsppireDataSheet *data_sheet)
+{
+  return data_sheet->may_delete_vars;
+}
+
+void
+psppire_data_sheet_set_may_delete_vars (PsppireDataSheet *data_sheet,
+                                       gboolean may_delete_vars)
+{
+  if (data_sheet->may_delete_vars != may_delete_vars)
+    {
+      data_sheet->may_delete_vars = may_delete_vars;
+      on_selection_changed (pspp_sheet_view_get_selection (
+                              PSPP_SHEET_VIEW (data_sheet)), NULL);
+    }
+}
+
+static PsppSheetViewColumn *
+psppire_data_sheet_find_column_for_variable (PsppireDataSheet *data_sheet,
+                                             gint dict_index)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppireDataStore *data_store;
+  PsppSheetViewColumn *column;
+  struct variable *var;
+  GList *list, *iter;
+
+  data_store = psppire_data_sheet_get_data_store (data_sheet);
+  g_return_val_if_fail (data_store != NULL, NULL);
+  g_return_val_if_fail (data_store->dict != NULL, NULL);
+
+  var = psppire_dict_get_variable (data_store->dict, dict_index);
+  g_return_val_if_fail (var != NULL, NULL);
+
+  column = NULL;
+  list = pspp_sheet_view_get_columns (sheet_view);
+  for (iter = list; iter != NULL; iter = iter->next)
+    {
+      PsppSheetViewColumn *c = iter->data;
+      struct variable *v;
+
+      v = g_object_get_data (G_OBJECT (c), "variable");
+      if (v == var)
+        {
+          column = c;
+          break;
+        }
+    }
+  g_list_free (list);
+
+  return column;
+}
+
+void
+psppire_data_sheet_show_variable (PsppireDataSheet *data_sheet,
+                                  gint dict_index)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppSheetViewColumn *column;
+
+  column = psppire_data_sheet_find_column_for_variable (data_sheet,
+                                                        dict_index);
+  if (column != NULL)
+    pspp_sheet_view_scroll_to_cell (sheet_view, NULL, column,
+                                    FALSE, 0.0, 0.0);
+}
+
+struct variable *
+psppire_data_sheet_get_current_variable (const PsppireDataSheet *data_sheet)
+{
+  PsppSheetSelection *selection;
+  struct variable *var;
+  GList *selected_columns;
+  GList *iter;
+
+  selection = pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (data_sheet));
+  selected_columns = pspp_sheet_selection_get_selected_columns (selection);
+
+  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)
+        {
+          if (var)
+            {
+              var = NULL;
+              break;
+            }
+          else
+            var = v;
+        }
+    }
+
+  g_list_free (selected_columns);
+
+  return var;
+
+}
+void
+psppire_data_sheet_goto_case (PsppireDataSheet *data_sheet, gint case_index)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppireDataStore *store = data_sheet->data_store;
+  PsppSheetSelection *selection;
+  GtkTreePath *path;
+
+  g_return_if_fail (case_index >= 0);
+  g_return_if_fail (case_index < psppire_data_store_get_case_count (store));
+
+  path = gtk_tree_path_new_from_indices (case_index, -1);
+
+  /* Select the case. */
+  selection = pspp_sheet_view_get_selection (sheet_view);
+  pspp_sheet_selection_unselect_all (selection);
+  pspp_sheet_selection_select_path (selection, path);
+  pspp_sheet_selection_select_all_columns (selection);
+
+  /* Scroll so that the case is visible. */
+  pspp_sheet_view_scroll_to_cell (sheet_view, path, NULL, FALSE, 0.0, 0.0);
+
+  gtk_tree_path_free (path);
+}
+
+/* Returns the 0-based index of a selected case, if there is at least one, and
+   -1 otherwise.
+
+   If more than one case is selected, returns the one with the smallest index,
+   that is, the index of the case closest to the beginning of the file.  The
+   row that can be used to insert a new case is not considered a case. */
+gint
+psppire_data_sheet_get_selected_case (const PsppireDataSheet *data_sheet)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppireDataStore *store = data_sheet->data_store;
+  const struct range_set_node *node;
+  PsppSheetSelection *selection;
+  struct range_set *rows;
+  gint row;
+
+  selection = pspp_sheet_view_get_selection (sheet_view);
+  rows = pspp_sheet_selection_get_range_set (selection);
+  node = range_set_first (rows);
+  row = (node && node->start < psppire_data_store_get_case_count (store)
+         ? node->start
+         : -1);
+  range_set_destroy (rows);
+
+  return row;
+}
+
+/* Returns the 0-based index of a selected case, if exactly one case is
+   selected, and -1 otherwise.  Returns -1 if the row that can be used to
+   insert a new case is selected. */
+gint
+psppire_data_sheet_get_current_case (const PsppireDataSheet *data_sheet)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppireDataStore *store = data_sheet->data_store;
+  const struct range_set_node *node;
+  PsppSheetSelection *selection;
+  struct range_set *rows;
+  gint row;
+
+  selection = pspp_sheet_view_get_selection (sheet_view);
+  if (pspp_sheet_selection_count_selected_rows (selection) != 1)
+    return -1;
+
+  rows = pspp_sheet_selection_get_range_set (selection);
+  node = range_set_first (rows);
+  row = (node && node->start < psppire_data_store_get_case_count (store)
+         ? node->start
+         : -1);
+  range_set_destroy (rows);
+
+  return row;
+}
+
+GtkUIManager *
+psppire_data_sheet_get_ui_manager (PsppireDataSheet *data_sheet)
+{
+  return GTK_UI_MANAGER (get_object_assert (data_sheet->builder,
+                                            "data_sheet_uim",
+                                            GTK_TYPE_UI_MANAGER));
+}
+
+static void
+psppire_data_sheet_destroy (GtkObject *object)
+{
+  PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (object);
+
+  psppire_data_sheet_unset_data_store (data_sheet);
+  if (data_sheet->builder)
+    {
+      g_object_unref (data_sheet->builder);
+      data_sheet->builder = NULL;
+    }
+
+  GTK_OBJECT_CLASS (psppire_data_sheet_parent_class)->destroy (object);
+}
+
+static void
+psppire_data_sheet_class_init (PsppireDataSheetClass *class)
+{
+  GObjectClass *gobject_class;
+  GtkObjectClass *gtk_object_class;
+
+  gobject_class = G_OBJECT_CLASS (class);
+  gobject_class->set_property = psppire_data_sheet_set_property;
+  gobject_class->get_property = psppire_data_sheet_get_property;
+
+  gtk_object_class = GTK_OBJECT_CLASS (class);
+  gtk_object_class->destroy = psppire_data_sheet_destroy;
+
+  g_signal_new ("var-double-clicked",
+                G_OBJECT_CLASS_TYPE (gobject_class),
+                G_SIGNAL_RUN_LAST,
+                0,
+                g_signal_accumulator_true_handled, NULL,
+                psppire_marshal_BOOLEAN__INT,
+                G_TYPE_BOOLEAN, 1, G_TYPE_INT);
+
+  g_object_class_install_property (
+    gobject_class, PROP_DATA_STORE,
+    g_param_spec_object ("data-store",
+                         "Data Store",
+                         "The data store for the data sheet to display.",
+                         PSPPIRE_TYPE_DATA_STORE,
+                         G_PARAM_WRITABLE | G_PARAM_READABLE));
+
+  g_object_class_install_property (
+    gobject_class, PROP_VALUE_LABELS,
+    g_param_spec_boolean ("value-labels",
+                          "Value Labels",
+                          "Whether or not the data sheet should display labels instead of values",
+                         FALSE,
+                          G_PARAM_WRITABLE | G_PARAM_READABLE));
+
+  g_object_class_install_property (
+    gobject_class, PROP_CASE_NUMBERS,
+    g_param_spec_boolean ("case-numbers",
+                          "Case Numbers",
+                          "Whether or not the data sheet should display case numbers",
+                         FALSE,
+                          G_PARAM_WRITABLE | G_PARAM_READABLE));
+
+  g_object_class_install_property (
+    gobject_class,
+    PROP_CURRENT_CASE,
+    g_param_spec_long ("current-case",
+                      "Current Case",
+                      "Zero based number of the selected case",
+                      0, CASENUMBER_MAX,
+                      0,
+                      G_PARAM_WRITABLE | G_PARAM_READABLE));
+
+  g_object_class_install_property (
+    gobject_class,
+    PROP_MAY_CREATE_VARS,
+    g_param_spec_boolean ("may-create-vars",
+                          "May create variables",
+                          "Whether the user may create more variables",
+                          TRUE,
+                          G_PARAM_READWRITE));
+
+  g_object_class_install_property (
+    gobject_class,
+    PROP_MAY_DELETE_VARS,
+    g_param_spec_boolean ("may-delete-vars",
+                          "May delete variables",
+                          "Whether the user may delete variables",
+                          TRUE,
+                          G_PARAM_READWRITE));
+
+  g_object_class_install_property (
+    gobject_class,
+    PROP_UI_MANAGER,
+    g_param_spec_object ("ui-manager",
+                         "UI Manager",
+                         "UI manager for the data sheet.  The client should merge this UI manager with the active UI manager to obtain data sheet specific menu items and tool bar items.",
+                         GTK_TYPE_UI_MANAGER,
+                         G_PARAM_READABLE));
+}
+
+static void
+do_popup_menu (GtkWidget *widget, guint button, guint32 time)
+{
+  PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (widget);
+  GtkWidget *menu;
+
+  menu = get_widget_assert (data_sheet->builder, "datasheet-cases-popup");
+  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
+}
+
+static void
+on_popup_menu (GtkWidget *widget, gpointer user_data UNUSED)
+{
+  do_popup_menu (widget, 0, gtk_get_current_event_time ());
+}
+
+static gboolean
+on_button_pressed (GtkWidget *widget, GdkEventButton *event,
+                   gpointer user_data UNUSED)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
+
+  if (event->type == GDK_BUTTON_PRESS && event->button == 3)
+    {
+      PsppSheetSelection *selection;
+
+      selection = pspp_sheet_view_get_selection (sheet_view);
+      if (pspp_sheet_selection_count_selected_rows (selection) <= 1)
+        {
+          GtkTreePath *path;
+
+          if (pspp_sheet_view_get_path_at_pos (sheet_view, event->x, event->y,
+                                               &path, NULL, NULL, NULL))
+            {
+              pspp_sheet_selection_unselect_all (selection);
+              pspp_sheet_selection_select_path (selection, path);
+              pspp_sheet_selection_select_all_columns (selection);
+              gtk_tree_path_free (path);
+            }
+        }
+
+      do_popup_menu (widget, event->button, event->time);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+on_edit_clear_cases (GtkAction *action, PsppireDataSheet *data_sheet)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
+  const struct range_set_node *node;
+  struct range_set *selected;
+
+  selected = pspp_sheet_selection_get_range_set (selection);
+  for (node = range_set_last (selected); node != NULL;
+       node = range_set_prev (selected, node))
+    {
+      unsigned long int start = range_set_node_get_start (node);
+      unsigned long int count = range_set_node_get_width (node);
+
+      psppire_data_store_delete_cases (data_sheet->data_store, start, count);
+    }
+  range_set_destroy (selected);
+}
+
+static void
+on_selection_changed (PsppSheetSelection *selection,
+                      gpointer user_data UNUSED)
+{
+  PsppSheetView *sheet_view = pspp_sheet_selection_get_tree_view (selection);
+  PsppireDataSheet *data_sheet = PSPPIRE_DATA_SHEET (sheet_view);
+  gint n_selected_rows;
+  gboolean may_delete_cases, may_delete_vars, may_insert_vars;
+  GList *list, *iter;
+  GtkTreePath *path;
+  GtkAction *action;
+
+  n_selected_rows = pspp_sheet_selection_count_selected_rows (selection);
+
+  action = get_action_assert (data_sheet->builder, "edit_insert-case");
+  gtk_action_set_sensitive (action, n_selected_rows > 0);
+
+  switch (n_selected_rows)
+    {
+    case 0:
+      may_delete_cases = FALSE;
+      break;
+
+    case 1:
+      /* The row used for inserting new cases cannot be deleted. */
+      path = gtk_tree_path_new_from_indices (
+        psppire_data_store_get_case_count (data_sheet->data_store), -1);
+      may_delete_cases = !pspp_sheet_selection_path_is_selected (selection,
+                                                                 path);
+      gtk_tree_path_free (path);
+      break;
+
+    default:
+      may_delete_cases = TRUE;
+      break;
+    }
+  action = get_action_assert (data_sheet->builder, "edit_clear-cases");
+  gtk_action_set_sensitive (action, may_delete_cases);
+
+  may_delete_vars = may_insert_vars = FALSE;
+  list = pspp_sheet_selection_get_selected_columns (selection);
+  for (iter = list; iter != NULL; iter = iter->next)
+    {
+      PsppSheetViewColumn *column = iter->data;
+      struct variable *var = g_object_get_data (G_OBJECT (column), "variable");
+
+      if (var != NULL)
+        {
+          may_delete_vars = may_insert_vars = TRUE;
+          break;
+        }
+      if (g_object_get_data (G_OBJECT (column), "new-var-column") != NULL)
+        may_insert_vars = TRUE;
+    }
+  g_list_free (list);
+
+  may_insert_vars = may_insert_vars && data_sheet->may_create_vars;
+  may_delete_vars = may_delete_vars && data_sheet->may_delete_vars;
+
+  action = get_action_assert (data_sheet->builder, "edit_insert-variable");
+  gtk_action_set_sensitive (action, may_insert_vars);
+
+  action = get_action_assert (data_sheet->builder, "edit_clear-variables");
+  gtk_action_set_sensitive (action, may_delete_vars);
+
+  action = get_action_assert (data_sheet->builder, "sort-up");
+  gtk_action_set_sensitive (action, may_delete_vars);
+
+  action = get_action_assert (data_sheet->builder, "sort-down");
+  gtk_action_set_sensitive (action, may_delete_vars);
+
+  psppire_data_sheet_update_clip_actions (data_sheet);
+}
+
+static gboolean
+psppire_data_sheet_get_selected_range (PsppireDataSheet *data_sheet,
+                                    struct range_set **rowsp,
+                                    struct range_set **colsp)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppireDataStore *data_store = data_sheet->data_store;
+  PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
+  unsigned long n_cases;
+  struct range_set *rows, *cols;
+  GList *list, *iter;
+
+  if (data_store == NULL)
+    return FALSE;
+  n_cases = psppire_data_store_get_case_count (data_store);
+
+  rows = pspp_sheet_selection_get_range_set (selection);
+  range_set_set0 (rows, n_cases, ULONG_MAX - n_cases);
+  if (range_set_is_empty (rows))
+    {
+      range_set_destroy (rows);
+      return FALSE;
+    }
+
+  cols = range_set_create ();
+  list = pspp_sheet_selection_get_selected_columns (selection);
+  for (iter = list; iter != NULL; iter = iter->next)
+    {
+      PsppSheetViewColumn *column = iter->data;
+      struct variable *var = g_object_get_data (G_OBJECT (column), "variable");
+
+      if (var != NULL)
+        range_set_set1 (cols, var_get_dict_index (var), 1);
+    }
+  g_list_free (list);
+  if (range_set_is_empty (cols))
+    {
+      range_set_destroy (rows);
+      range_set_destroy (cols);
+      return FALSE;
+    }
+
+  *rowsp = rows;
+  *colsp = cols;
+  return TRUE;
+}
+
+static void
+on_edit_insert_case (GtkAction *action, PsppireDataSheet *data_sheet)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
+  PsppireDataStore *data_store = data_sheet->data_store;
+  struct range_set *selected;
+  unsigned long row;
+
+  selected = pspp_sheet_selection_get_range_set (selection);
+  row = range_set_scan (selected, 0);
+  range_set_destroy (selected);
+
+  if (row <= psppire_data_store_get_case_count (data_store))
+    psppire_data_store_insert_new_case (data_store, row);
+}
+
+static void
+on_edit_insert_variable (GtkAction *action, PsppireDataSheet *data_sheet)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
+  PsppireDict *dict = data_sheet->data_store->dict;
+  PsppSheetViewColumn *column;
+  struct variable *var;
+  gchar name[64];
+  GList *list;
+  gint index;
+
+  list = pspp_sheet_selection_get_selected_columns (selection);
+  if (list == NULL)
+    return;
+  column = list->data;
+  g_list_free (list);
+
+  var = g_object_get_data (G_OBJECT (column), "variable");
+  index = var ? var_get_dict_index (var) : psppire_dict_get_var_cnt (dict);
+  if (psppire_dict_generate_name (dict, name, sizeof name))
+    psppire_dict_insert_variable (dict, index, name);
+}
+
+static void
+on_edit_clear_variables (GtkAction *action, PsppireDataSheet *data_sheet)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
+  PsppireDict *dict = data_sheet->data_store->dict;
+  GList *list, *iter;
+
+  list = pspp_sheet_selection_get_selected_columns (selection);
+  if (list == NULL)
+    return;
+  list = g_list_reverse (list);
+  for (iter = list; iter; iter = iter->next)
+    {
+      PsppSheetViewColumn *column = iter->data;
+      struct variable *var;
+
+      var = g_object_get_data (G_OBJECT (column), "variable");
+      if (var != NULL)
+        psppire_dict_delete_variables (dict, var_get_dict_index (var), 1);
+    }
+  g_list_free (list);
+}
+
+enum sort_order
+  {
+    SORT_ASCEND,
+    SORT_DESCEND
+  };
+
+static void
+do_sort (PsppireDataSheet *data_sheet, enum sort_order order)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
+  PsppireDataWindow *pdw;
+  GList *list, *iter;
+  GString *syntax;
+  int n_vars;
+
+  pdw = psppire_data_window_for_data_store (data_sheet->data_store);
+  g_return_if_fail (pdw != NULL);
+
+  list = pspp_sheet_selection_get_selected_columns (selection);
+
+  syntax = g_string_new ("SORT CASES BY");
+  n_vars = 0;
+  for (iter = list; iter; iter = iter->next)
+    {
+      PsppSheetViewColumn *column = iter->data;
+      struct variable *var;
+
+      var = g_object_get_data (G_OBJECT (column), "variable");
+      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);
+}
+
+void
+on_sort_up (GtkAction *action, PsppireDataSheet *data_sheet)
+{
+  do_sort (data_sheet, SORT_ASCEND);
+}
+
+void
+on_sort_down (GtkAction *action, PsppireDataSheet *data_sheet)
+{
+  do_sort (data_sheet, SORT_DESCEND);
+}
+
+void
+on_edit_goto_case (GtkAction *action, PsppireDataSheet *data_sheet)
+{
+  goto_case_dialog (data_sheet);
+}
+
+void
+on_edit_find (GtkAction *action, PsppireDataSheet *data_sheet)
+{
+  PsppireDataWindow *pdw;
+
+  pdw = psppire_data_window_for_data_store (data_sheet->data_store);
+  g_return_if_fail (pdw != NULL);
+
+  find_dialog (pdw);
+}
+
+void
+on_edit_copy (GtkAction *action, PsppireDataSheet *data_sheet)
+{
+  psppire_data_sheet_set_clip (data_sheet);
+}
+
+static void
+psppire_data_sheet_init (PsppireDataSheet *obj)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (obj);
+  GtkAction *action;
+
+  obj->show_value_labels = FALSE;
+  obj->show_case_numbers = TRUE;
+  obj->may_create_vars = TRUE;
+  obj->may_delete_vars = TRUE;
+
+  obj->scroll_to_bottom_signal = 0;
+  obj->scroll_to_right_signal = 0;
+  obj->new_variable_column = NULL;
+  obj->container = NULL;
+
+  pspp_sheet_view_set_special_cells (sheet_view, PSPP_SHEET_VIEW_SPECIAL_CELLS_YES);
+
+  g_signal_connect (obj, "notify::model",
+                    G_CALLBACK (psppire_data_sheet_model_changed), NULL);
+
+  pspp_sheet_view_set_rubber_banding (sheet_view, TRUE);
+  pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (sheet_view),
+                                 PSPP_SHEET_SELECTION_RECTANGLE);
+
+  g_object_set (G_OBJECT (obj), "has-tooltip", TRUE, (void *) NULL);
+  g_signal_connect (obj, "query-tooltip",
+                    G_CALLBACK (on_query_tooltip), NULL);
+  g_signal_connect (obj, "button-press-event",
+                    G_CALLBACK (on_button_pressed), NULL);
+  g_signal_connect (obj, "popup-menu", G_CALLBACK (on_popup_menu), NULL);
+
+  obj->builder = builder_new ("data-sheet.ui");
+
+  action = get_action_assert (obj->builder, "edit_clear-cases");
+  g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_cases),
+                    obj);
+  gtk_action_set_sensitive (action, FALSE);
+  g_signal_connect (pspp_sheet_view_get_selection (sheet_view),
+                    "changed", G_CALLBACK (on_selection_changed), NULL);
+
+  action = get_action_assert (obj->builder, "edit_insert-case");
+  g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_case),
+                    obj);
+
+  action = get_action_assert (obj->builder, "edit_insert-variable");
+  g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_variable),
+                    obj);
+
+  action = get_action_assert (obj->builder, "edit_goto-case");
+  g_signal_connect (action, "activate", G_CALLBACK (on_edit_goto_case),
+                    obj);
+
+  action = get_action_assert (obj->builder, "edit_copy");
+  g_signal_connect (action, "activate", G_CALLBACK (on_edit_copy), obj);
+
+  action = get_action_assert (obj->builder, "edit_clear-variables");
+  g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_variables),
+                    obj);
+
+  action = get_action_assert (obj->builder, "edit_find");
+  g_signal_connect (action, "activate", G_CALLBACK (on_edit_find), obj);
+
+  action = get_action_assert (obj->builder, "sort-up");
+  g_signal_connect (action, "activate", G_CALLBACK (on_sort_up), obj);
+
+  action = get_action_assert (obj->builder, "sort-down");
+  g_signal_connect (action, "activate", G_CALLBACK (on_sort_down), obj);
+
+}
+
+GtkWidget *
+psppire_data_sheet_new (void)
+{
+  return g_object_new (PSPP_TYPE_DATA_SHEET, NULL);
+}
+
+PsppireDataStore *
+psppire_data_sheet_get_data_store (PsppireDataSheet *data_sheet)
+{
+  return data_sheet->data_store;
+}
+
+static void
+refresh_model (PsppireDataSheet *data_sheet)
+{
+  pspp_sheet_view_set_model (PSPP_SHEET_VIEW (data_sheet), NULL);
+
+  if (data_sheet->data_store != NULL)
+    {
+      PsppireEmptyListStore *model;
+      GtkAction *action;
+      int n_rows;
+
+      n_rows = psppire_data_store_get_case_count (data_sheet->data_store) + 1;
+      model = psppire_empty_list_store_new (n_rows);
+      pspp_sheet_view_set_model (PSPP_SHEET_VIEW (data_sheet),
+                                 GTK_TREE_MODEL (model));
+      g_object_unref (model);
+
+      action = get_action_assert (data_sheet->builder, "edit_copy");
+      g_signal_connect (action, "activate", G_CALLBACK (on_edit_copy),
+                        data_sheet);
+    }
+}
+
+static void
+on_case_inserted (PsppireDataStore *data_store, gint row,
+                  PsppireDataSheet *data_sheet)
+{
+  PsppireEmptyListStore *empty_list_store;
+  GtkTreeModel *tree_model;
+  gint n_rows;
+
+  g_return_if_fail (data_store == data_sheet->data_store);
+
+  n_rows = psppire_data_store_get_case_count (data_store) + 1;
+  if (row == n_rows - 1)
+    row++;
+
+  tree_model = pspp_sheet_view_get_model (PSPP_SHEET_VIEW (data_sheet));
+  empty_list_store = PSPPIRE_EMPTY_LIST_STORE (tree_model);
+  psppire_empty_list_store_set_n_rows (empty_list_store, n_rows);
+  psppire_empty_list_store_row_inserted (empty_list_store, row);
+}
+
+static void
+on_cases_deleted (PsppireDataStore *data_store, gint first, gint n_cases,
+                  PsppireDataSheet *data_sheet)
+{
+
+  g_return_if_fail (data_store == data_sheet->data_store);
+
+  if (n_cases > 1)
+    {
+      /* This is a bit of a cop-out.  We could do better, if it ever turns out
+         that this performs too poorly. */
+      refresh_model (data_sheet);
+    }
+  else
+    {
+      PsppireEmptyListStore *empty_list_store;
+      GtkTreeModel *tree_model;
+      gint n_rows = psppire_data_store_get_case_count (data_store) + 1;
+
+      tree_model = pspp_sheet_view_get_model (PSPP_SHEET_VIEW (data_sheet));
+      empty_list_store = PSPPIRE_EMPTY_LIST_STORE (tree_model);
+      psppire_empty_list_store_set_n_rows (empty_list_store, n_rows);
+      psppire_empty_list_store_row_deleted (empty_list_store, first);
+    }
+}
+
+static void
+on_case_change (PsppireDataStore *data_store, gint row,
+                PsppireDataSheet *data_sheet)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+
+  pspp_sheet_view_stop_editing (sheet_view, TRUE);
+  gtk_widget_queue_draw (GTK_WIDGET (data_sheet));
+}
+
+static void
+on_backend_changed (PsppireDataStore *data_store,
+                    PsppireDataSheet *data_sheet)
+{
+  g_return_if_fail (data_store == data_sheet->data_store);
+  refresh_model (data_sheet);
+}
+
+static void
+on_variable_display_width_changed (PsppireDict *dict, int dict_index,
+                                   PsppireDataSheet *data_sheet)
+{
+  PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
+  PsppSheetViewColumn *column;
+  struct variable *var;
+  int display_width;
+  gint pixel_width;
+
+  g_return_if_fail (data_sheet->data_store != NULL);
+  g_return_if_fail (dict == data_sheet->data_store->dict);
+
+  column = psppire_data_sheet_find_column_for_variable (data_sheet,
+                                                        dict_index);
+  if (column == NULL)
+    return;
+
+  var = psppire_dict_get_variable (data_store->dict, dict_index);
+  g_return_if_fail (var != NULL);
+
+  pixel_width = pspp_sheet_view_column_get_fixed_width (column);
+  display_width = display_width_from_pixel_width (data_sheet, pixel_width);
+  if (display_width != var_get_display_width (var))
+    {
+      gint base_width, incr_width;
+
+      display_width = var_get_display_width (var);
+      calc_width_conversion (data_sheet, &base_width, &incr_width);
+      pixel_width = display_width_to_pixel_width (data_sheet, display_width,
+                                                  base_width, incr_width);
+      pspp_sheet_view_column_set_fixed_width (column, pixel_width);
+    }
+}
+
+static void
+on_variable_changed (PsppireDict *dict, int dict_index,
+                     PsppireDataSheet *data_sheet)
+{
+  PsppireDataStore *data_store = psppire_data_sheet_get_data_store (data_sheet);
+  PsppSheetViewColumn *column;
+  GtkCellRenderer *cell;
+  struct variable *var;
+  GList *cells;
+  char *name;
+
+  g_return_if_fail (data_sheet->data_store != NULL);
+  g_return_if_fail (dict == data_sheet->data_store->dict);
+
+  column = psppire_data_sheet_find_column_for_variable (data_sheet,
+                                                        dict_index);
+  if (column == NULL)
+    return;
+
+  var = psppire_dict_get_variable (data_store->dict, dict_index);
+  g_return_if_fail (var != NULL);
+
+  name = escape_underscores (var_get_name (var));
+  if (strcmp (name, pspp_sheet_view_column_get_title (column)))
+    pspp_sheet_view_column_set_title (column, name);
+  free (name);
+
+  cells = pspp_sheet_view_column_get_cell_renderers (column);
+  g_return_if_fail (cells);
+  cell = cells->data;
+  g_list_free (cells);
+
+  if (var_has_value_labels (var) != GTK_IS_CELL_RENDERER_COMBO (cell))
+    {
+      /* Stop editing before we delete and replace the cell renderers.
+         Otherwise if this column is currently being edited, an eventual call
+         to pspp_sheet_view_stop_editing() will obtain a NULL cell and pass
+         that to gtk_cell_renderer_stop_editing(), which causes a critical.
+
+         It's possible that this is a bug in PsppSheetView, and it's possible
+         that PsppSheetView inherits that from GtkTreeView, but I haven't
+         investigated yet. */
+      pspp_sheet_view_stop_editing (PSPP_SHEET_VIEW (data_sheet), TRUE);
+
+      add_data_column_cell_renderer (data_sheet, column);
+    }
+}
+
+static void
+on_variable_inserted (PsppireDict *dict, int var_index,
+                      PsppireDataSheet *data_sheet)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  gint base_width, incr_width;
+  PsppSheetViewColumn *column;
+
+  calc_width_conversion (data_sheet, &base_width, &incr_width);
+  column = make_data_column (data_sheet, var_index, base_width, incr_width);
+  pspp_sheet_view_insert_column (sheet_view, column, var_index + 1);
+}
+
+static void
+on_variable_deleted (PsppireDict *dict,
+                     const struct variable *var, int case_idx, int width,
+                     PsppireDataSheet *data_sheet)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (data_sheet);
+  GList *columns, *iter;
+
+  columns = pspp_sheet_view_get_columns (sheet_view);
+  for (iter = columns; iter != NULL; iter = iter->next)
+    {
+      PsppSheetViewColumn *column = iter->data;
+      const struct variable *column_var;
+
+      column_var = g_object_get_data (G_OBJECT (column), "variable");
+      if (column_var == var)
+        pspp_sheet_view_remove_column (sheet_view, column);
+    }
+  g_list_free (columns);
+}
+
+static void
+psppire_data_sheet_unset_data_store (PsppireDataSheet *data_sheet)
+{
+  PsppireDataStore *store = data_sheet->data_store;
+
+  if (store == NULL)
+    return;
+
+  data_sheet->data_store = NULL;
+
+  g_signal_handlers_disconnect_by_func (
+    store, G_CALLBACK (on_backend_changed), data_sheet);
+  g_signal_handlers_disconnect_by_func (
+    store, G_CALLBACK (on_case_inserted), data_sheet);
+  g_signal_handlers_disconnect_by_func (
+    store, G_CALLBACK (on_cases_deleted), data_sheet);
+  g_signal_handlers_disconnect_by_func (
+    store, G_CALLBACK (on_case_change), data_sheet);
+
+  g_signal_handlers_disconnect_by_func (
+    store->dict, G_CALLBACK (on_variable_changed), data_sheet);
+  g_signal_handlers_disconnect_by_func (
+    store->dict, G_CALLBACK (on_variable_display_width_changed), data_sheet);
+  g_signal_handlers_disconnect_by_func (
+    store->dict, G_CALLBACK (on_variable_inserted), data_sheet);
+  g_signal_handlers_disconnect_by_func (
+    store->dict, G_CALLBACK (on_variable_deleted), data_sheet);
+
+  g_object_unref (store);
+}
+
+void
+psppire_data_sheet_set_data_store (PsppireDataSheet *data_sheet,
+                                PsppireDataStore *data_store)
+{
+  psppire_data_sheet_unset_data_store (data_sheet);
+
+  data_sheet->data_store = data_store;
+  if (data_store != NULL)
+    {
+      g_object_ref (data_store);
+      g_signal_connect (data_store, "backend-changed",
+                        G_CALLBACK (on_backend_changed), data_sheet);
+      g_signal_connect (data_store, "case-inserted",
+                        G_CALLBACK (on_case_inserted), data_sheet);
+      g_signal_connect (data_store, "cases-deleted",
+                        G_CALLBACK (on_cases_deleted), data_sheet);
+      g_signal_connect (data_store, "case-changed",
+                        G_CALLBACK (on_case_change), data_sheet);
+
+      /* XXX it's unclean to hook into the dict this way--what if the dict
+         changes?  As of this writing, though, nothing ever changes the
+         data_store's dict. */
+      g_signal_connect (data_store->dict, "variable-changed",
+                        G_CALLBACK (on_variable_changed),
+                        data_sheet);
+      g_signal_connect (data_store->dict, "variable-display-width-changed",
+                        G_CALLBACK (on_variable_display_width_changed),
+                        data_sheet);
+      g_signal_connect (data_store->dict, "variable-inserted",
+                        G_CALLBACK (on_variable_inserted), data_sheet);
+      g_signal_connect (data_store->dict, "variable-deleted",
+                        G_CALLBACK (on_variable_deleted), data_sheet);
+    }
+  refresh_model (data_sheet);
+}
+\f
+/* Clipboard stuff */
+
+/* A casereader and dictionary holding the data currently in the clip */
+static struct casereader *clip_datasheet = NULL;
+static struct dictionary *clip_dict = NULL;
+
+
+static void psppire_data_sheet_update_clipboard (PsppireDataSheet *);
+
+/* Set the clip according to the currently
+   selected range in the data sheet */
+static void
+psppire_data_sheet_set_clip (PsppireDataSheet *data_sheet)
+{
+  struct casewriter *writer ;
+  PsppireDataStore *ds = psppire_data_sheet_get_data_store (data_sheet);
+  struct case_map *map = NULL;
+  struct range_set *rows, *cols;
+  const struct range_set_node *node;
+
+  if (!psppire_data_sheet_get_selected_range (data_sheet, &rows, &cols))
+    return;
+
+
+  /* Destroy any existing clip */
+  if ( clip_datasheet )
+    {
+      casereader_destroy (clip_datasheet);
+      clip_datasheet = NULL;
+    }
+
+  if ( clip_dict )
+    {
+      dict_destroy (clip_dict);
+      clip_dict = NULL;
+    }
+
+  /* Construct clip dictionary. */
+  clip_dict = dict_create (dict_get_encoding (ds->dict->dict));
+  RANGE_SET_FOR_EACH (node, cols)
+    {
+      int dict_index;
+
+      for (dict_index = node->start; dict_index < node->end; dict_index++)
+        {
+          struct variable *var = dict_get_var (ds->dict->dict, dict_index);
+          dict_clone_var_assert (clip_dict, var);
+        }
+    }
+
+  /* Construct clip data. */
+  map = case_map_by_name (ds->dict->dict, clip_dict);
+  writer = autopaging_writer_create (dict_get_proto (clip_dict));
+  RANGE_SET_FOR_EACH (node, rows)
+    {
+      unsigned long int row;
+
+      for (row = node->start; row < node->end; row++)
+        {
+          struct ccase *old = psppire_data_store_get_case (ds, row);
+          if (old != NULL)
+            casewriter_write (writer, case_map_execute (map, old));
+          else
+            casewriter_force_error (writer);
+        }
+    }
+  case_map_destroy (map);
+
+  range_set_destroy (rows);
+  range_set_destroy (cols);
+
+  clip_datasheet = casewriter_make_reader (writer);
+
+  psppire_data_sheet_update_clipboard (data_sheet);
+}
+
+enum {
+  SELECT_FMT_NULL,
+  SELECT_FMT_TEXT,
+  SELECT_FMT_HTML
+};
+
+
+/* Perform data_out for case CC, variable V, appending to STRING */
+static void
+data_out_g_string (GString *string, const struct variable *v,
+                  const struct ccase *cc)
+{
+  const struct fmt_spec *fs = var_get_print_format (v);
+  const union value *val = case_data (cc, v);
+
+  char *s = data_out (val, var_get_encoding (v), fs);
+
+  g_string_append (string, s);
+
+  g_free (s);
+}
+
+static GString *
+clip_to_text (void)
+{
+  casenumber r;
+  GString *string;
+
+  const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
+  const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
+  const size_t var_cnt = dict_get_var_cnt (clip_dict);
+
+  string = g_string_sized_new (10 * val_cnt * case_cnt);
+
+  for (r = 0 ; r < case_cnt ; ++r )
+    {
+      int c;
+      struct ccase *cc;
+
+      cc = casereader_peek (clip_datasheet, r);
+      if (cc == NULL)
+       {
+         g_warning ("Clipboard seems to have inexplicably shrunk");
+         break;
+       }
+
+      for (c = 0 ; c < var_cnt ; ++c)
+       {
+         const struct variable *v = dict_get_var (clip_dict, c);
+         data_out_g_string (string, v, cc);
+         if ( c < val_cnt - 1 )
+           g_string_append (string, "\t");
+       }
+
+      if ( r < case_cnt)
+       g_string_append (string, "\n");
+
+      case_unref (cc);
+    }
+
+  return string;
+}
+
+
+static GString *
+clip_to_html (void)
+{
+  casenumber r;
+  GString *string;
+
+  const size_t val_cnt = caseproto_get_n_widths (casereader_get_proto (clip_datasheet));
+  const casenumber case_cnt = casereader_get_case_cnt (clip_datasheet);
+  const size_t var_cnt = dict_get_var_cnt (clip_dict);
+
+  /* Guestimate the size needed */
+  string = g_string_sized_new (80 + 20 * val_cnt * case_cnt);
+
+  g_string_append (string,
+                  "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
+
+  g_string_append (string, "<table>\n");
+  for (r = 0 ; r < case_cnt ; ++r )
+    {
+      int c;
+      struct ccase *cc = casereader_peek (clip_datasheet, r);
+      if (cc == NULL)
+       {
+         g_warning ("Clipboard seems to have inexplicably shrunk");
+         break;
+       }
+      g_string_append (string, "<tr>\n");
+
+      for (c = 0 ; c < var_cnt ; ++c)
+       {
+         const struct variable *v = dict_get_var (clip_dict, c);
+         g_string_append (string, "<td>");
+         data_out_g_string (string, v, cc);
+         g_string_append (string, "</td>\n");
+       }
+
+      g_string_append (string, "</tr>\n");
+
+      case_unref (cc);
+    }
+  g_string_append (string, "</table>\n");
+
+  return string;
+}
+
+
+
+static void
+psppire_data_sheet_clipboard_get_cb (GtkClipboard     *clipboard,
+                                     GtkSelectionData *selection_data,
+                                     guint             info,
+                                     gpointer          data)
+{
+  GString *string = NULL;
+
+  switch (info)
+    {
+    case SELECT_FMT_TEXT:
+      string = clip_to_text ();
+      break;
+    case SELECT_FMT_HTML:
+      string = clip_to_html ();
+      break;
+    default:
+      g_assert_not_reached ();
+    }
+
+  gtk_selection_data_set (selection_data, selection_data->target,
+                         8,
+                         (const guchar *) string->str, string->len);
+
+  g_string_free (string, TRUE);
+}
+
+static void
+psppire_data_sheet_clipboard_clear_cb (GtkClipboard *clipboard,
+                                       gpointer data)
+{
+  dict_destroy (clip_dict);
+  clip_dict = NULL;
+
+  casereader_destroy (clip_datasheet);
+  clip_datasheet = NULL;
+}
+
+
+static const GtkTargetEntry targets[] = {
+  { "UTF8_STRING",   0, SELECT_FMT_TEXT },
+  { "STRING",        0, SELECT_FMT_TEXT },
+  { "TEXT",          0, SELECT_FMT_TEXT },
+  { "COMPOUND_TEXT", 0, SELECT_FMT_TEXT },
+  { "text/plain;charset=utf-8", 0, SELECT_FMT_TEXT },
+  { "text/plain",    0, SELECT_FMT_TEXT },
+  { "text/html",     0, SELECT_FMT_HTML }
+};
+
+
+
+static void
+psppire_data_sheet_update_clipboard (PsppireDataSheet *sheet)
+{
+  GtkClipboard *clipboard =
+    gtk_widget_get_clipboard (GTK_WIDGET (sheet),
+                             GDK_SELECTION_CLIPBOARD);
+
+  if (!gtk_clipboard_set_with_owner (clipboard, targets,
+                                    G_N_ELEMENTS (targets),
+                                    psppire_data_sheet_clipboard_get_cb,
+                                     psppire_data_sheet_clipboard_clear_cb,
+                                    G_OBJECT (sheet)))
+    psppire_data_sheet_clipboard_clear_cb (clipboard, sheet);
+}
+
+static void
+psppire_data_sheet_update_clip_actions (PsppireDataSheet *data_sheet)
+{
+  struct range_set *rows, *cols;
+  GtkAction *action;
+  gboolean enable;
+
+  enable = psppire_data_sheet_get_selected_range (data_sheet, &rows, &cols);
+  if (enable)
+    {
+      range_set_destroy (rows);
+      range_set_destroy (cols);
+    }
+
+  action = get_action_assert (data_sheet->builder, "edit_copy");
+  gtk_action_set_sensitive (action, enable);
+
+  action = get_action_assert (data_sheet->builder, "edit_cut");
+  gtk_action_set_sensitive (action, enable);
+}
+
diff --git a/src/ui/gui/psppire-data-sheet.h b/src/ui/gui/psppire-data-sheet.h
new file mode 100644 (file)
index 0000000..e17278e
--- /dev/null
@@ -0,0 +1,96 @@
+/* PSPPIRE - a graphical user interface for PSPP.
+   Copyright (C) 2011, 2012 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
+   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,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   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, see <http://www.gnu.org/licenses/>. */
+
+#ifndef PSPPIRE_DATA_SHEET_H
+#define PSPPIRE_DATA_SHEET_H 1
+
+/* PsppireDataSheet is a PsppSheetView that displays the data in a dataset,
+   with one column per variable and one row per case.
+
+   PsppireDataSheet is usually a child of PsppireDataEditor in the widget
+   hierarchy.  Other widgets can also use it. */
+
+#include <gtk/gtk.h>
+#include "ui/gui/pspp-sheet-view.h"
+
+G_BEGIN_DECLS
+
+#define PSPP_TYPE_DATA_SHEET              (psppire_data_sheet_get_type())
+#define PSPPIRE_DATA_SHEET(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj),PSPP_TYPE_DATA_SHEET,PsppireDataSheet))
+#define PSPPIRE_DATA_SHEET_CLASS(class)   (G_TYPE_CHECK_CLASS_CAST ((class),PSPP_TYPE_DATA_SHEET,PsppireDataSheetClass))
+#define PSPP_IS_DATA_SHEET(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj),PSPP_TYPE_DATA_SHEET))
+#define PSPP_IS_DATA_SHEET_CLASS(class)   (G_TYPE_CHECK_CLASS_TYPE ((class),PSPP_TYPE_DATA_SHEET))
+#define PSPPIRE_DATA_SHEET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),PSPP_TYPE_DATA_SHEET,PsppireDataSheetClass))
+
+typedef struct _PsppireDataSheet      PsppireDataSheet;
+typedef struct _PsppireDataSheetClass PsppireDataSheetClass;
+
+struct _PsppireDataSheet {
+  PsppSheetView parent;
+
+  struct _PsppireDataStore *data_store;
+  gboolean show_value_labels;
+  gboolean show_case_numbers;
+  gboolean may_create_vars;
+  gboolean may_delete_vars;
+
+  guint scroll_to_bottom_signal;
+  guint scroll_to_right_signal;
+
+  PsppSheetViewColumn *new_variable_column;
+
+  GtkBuilder *builder;
+
+  GtkWidget *container;
+};
+
+struct _PsppireDataSheetClass {
+  PsppSheetViewClass parent_class;
+};
+
+GType psppire_data_sheet_get_type (void) G_GNUC_CONST;
+GtkWidget *psppire_data_sheet_new (void);
+
+struct _PsppireDataStore *psppire_data_sheet_get_data_store (PsppireDataSheet *);
+void psppire_data_sheet_set_data_store (PsppireDataSheet *,
+                                        struct _PsppireDataStore *);
+
+gboolean psppire_data_sheet_get_value_labels (const PsppireDataSheet *);
+void psppire_data_sheet_set_value_labels (PsppireDataSheet *,
+                                          gboolean show_value_labels);
+
+gboolean psppire_data_sheet_get_case_numbers (const PsppireDataSheet *);
+void psppire_data_sheet_set_case_numbers (PsppireDataSheet *,
+                                          gboolean show_case_numbers);
+
+gboolean psppire_data_sheet_get_may_create_vars (PsppireDataSheet *);
+void psppire_data_sheet_set_may_create_vars (PsppireDataSheet *, gboolean);
+
+gboolean psppire_data_sheet_get_may_delete_vars (PsppireDataSheet *);
+void psppire_data_sheet_set_may_delete_vars (PsppireDataSheet *, gboolean);
+
+void psppire_data_sheet_show_variable (PsppireDataSheet *, gint dict_index);
+struct variable *psppire_data_sheet_get_current_variable (const PsppireDataSheet *);
+
+void psppire_data_sheet_goto_case (PsppireDataSheet *, gint case_index);
+gint psppire_data_sheet_get_selected_case (const PsppireDataSheet *);
+gint psppire_data_sheet_get_current_case (const PsppireDataSheet *);
+
+GtkUIManager *psppire_data_sheet_get_ui_manager (PsppireDataSheet *);
+
+G_END_DECLS
+
+#endif /* PSPPIRE_DATA_SHEET_H */
index fb89dfa20e928c68557a82186b63680e5601b84b..83933c474c8c241b7628b10d7fd3db6545689f7f 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (C) 2006, 2008, 2009, 2010, 2012  Free Software Foundation
+   Copyright (C) 2006, 2008, 2009, 2010, 2011, 2012  Free Software Foundation
 
    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
@@ -25,7 +25,6 @@
 #include <data/data-out.h>
 #include <data/variable.h>
 
-#include <ui/gui/sheet/psppire-sheetmodel.h>
 #include <ui/gui/psppire-marshal.h>
 
 #include <pango/pango-context.h>
 
 static void psppire_data_store_init            (PsppireDataStore      *data_store);
 static void psppire_data_store_class_init      (PsppireDataStoreClass *class);
-static void psppire_data_store_sheet_model_init (PsppireSheetModelIface *iface);
 
 static void psppire_data_store_finalize        (GObject           *object);
 static void psppire_data_store_dispose        (GObject           *object);
 
-static gboolean psppire_data_store_clear_datum (PsppireSheetModel *model,
-                                         glong row, glong column);
-
-
 static gboolean psppire_data_store_insert_case (PsppireDataStore *ds,
                                                struct ccase *cc,
                                                casenumber posn);
@@ -68,8 +62,6 @@ static gboolean psppire_data_store_data_in (PsppireDataStore *ds,
                                            struct substring input,
                                            const struct fmt_spec *fmt);
 
-
-
 static GObjectClass *parent_class = NULL;
 
 
@@ -105,22 +97,9 @@ psppire_data_store_get_type (void)
         (GInstanceInitFunc) psppire_data_store_init,
       };
 
-      static const GInterfaceInfo sheet_model_info =
-      {
-       (GInterfaceInitFunc) psppire_data_store_sheet_model_init,
-       NULL,
-       NULL
-      };
-
-
       data_store_type = g_type_register_static (G_TYPE_OBJECT,
                                                "PsppireDataStore",
                                                &data_store_info, 0);
-
-      g_type_add_interface_static (data_store_type,
-                                  PSPPIRE_TYPE_SHEET_MODEL,
-                                  &sheet_model_info);
-
     }
 
   return data_store_type;
@@ -190,27 +169,6 @@ static gboolean
 psppire_data_store_insert_value (PsppireDataStore *ds,
                                  gint width, gint where);
 
-static bool
-psppire_data_store_get_value (const PsppireDataStore *ds,
-                             casenumber casenum, size_t idx,
-                             union value *value);
-
-
-static gboolean
-psppire_data_store_set_value (PsppireDataStore *ds, casenumber casenum,
-                             gint idx, union value *v);
-
-
-
-
-static glong
-psppire_data_store_get_var_count (const PsppireSheetModel *model)
-{
-  const PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
-
-  return psppire_dict_get_var_cnt (store->dict);
-}
-
 casenumber
 psppire_data_store_get_case_count (const PsppireDataStore *store)
 {
@@ -229,13 +187,6 @@ psppire_data_store_get_proto (const PsppireDataStore *store)
   return psppire_dict_get_proto (store->dict);
 }
 
-static casenumber
-psppire_data_store_get_case_count_wrapper (const PsppireSheetModel *model)
-{
-  const PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
-  return psppire_data_store_get_case_count (store);
-}
-
 static void
 psppire_data_store_init (PsppireDataStore *data_store)
 {
@@ -244,94 +195,24 @@ psppire_data_store_init (PsppireDataStore *data_store)
   data_store->dispose_has_run = FALSE;
 }
 
-static inline gchar *
-psppire_data_store_get_string_wrapper (const PsppireSheetModel *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 (PsppireSheetModel *model,
-                                      const gchar *text,
-                                      glong row, glong column)
-{
-  return psppire_data_store_set_string (PSPPIRE_DATA_STORE (model), text,
-                                       row, column);
-}
-
-
-
-static gchar * get_column_subtitle (const PsppireSheetModel *model, gint col);
-static gchar * get_column_button_label (const PsppireSheetModel *model, gint col);
-static gboolean get_column_sensitivity (const PsppireSheetModel *model, gint col);
-static GtkJustification get_column_justification (const PsppireSheetModel *model, gint col);
-
-static gchar * get_row_button_label (const PsppireSheetModel *model, gint row);
-static gboolean get_row_sensitivity (const PsppireSheetModel *model, gint row);
-static gboolean get_row_overstrike (const PsppireSheetModel *model, gint row);
-
-
-static void
-psppire_data_store_sheet_model_init (PsppireSheetModelIface *iface)
-{
-  iface->free_strings = TRUE;
-  iface->get_string = psppire_data_store_get_string_wrapper;
-  iface->set_string = psppire_data_store_set_string_wrapper;
-  iface->clear_datum = psppire_data_store_clear_datum;
-  iface->is_editable = NULL;
-  iface->get_foreground = NULL;
-  iface->get_background = NULL;
-  iface->get_column_count = psppire_data_store_get_var_count;
-  iface->get_row_count = psppire_data_store_get_case_count_wrapper;
-
-  iface->get_column_subtitle = get_column_subtitle;
-  iface->get_column_title = get_column_button_label;
-  iface->get_column_sensitivity = get_column_sensitivity;
-  iface->get_column_justification = get_column_justification;
-
-  iface->get_row_title = get_row_button_label;
-  iface->get_row_sensitivity = get_row_sensitivity;
-  iface->get_row_overstrike = get_row_overstrike;
-}
-
-
 /*
    A callback which occurs after a variable has been deleted.
  */
 static void
 delete_variable_callback (GObject *obj, const struct variable *var UNUSED,
                           gint dict_index, gint case_index,
-                         gpointer data)
+                          gpointer data)
 {
   PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
 
 
-  psppire_sheet_model_columns_deleted (PSPPIRE_SHEET_MODEL (store), dict_index, 1);
   datasheet_delete_columns (store->datasheet, case_index, 1);
   datasheet_insert_column (store->datasheet, NULL, -1, case_index);
-#if AXIS_TRANSITION
-
-
-  psppire_sheet_column_columns_changed (PSPPIRE_SHEET_COLUMN (store),
-                                  dict_index, -1);
-#endif
 }
 
 static void
 variable_changed_callback (GObject *obj, gint var_num, gpointer data)
 {
-#if AXIS_TRANSITION
-  PsppireDataStore *store  = PSPPIRE_DATA_STORE (data);
-
-  psppire_sheet_column_columns_changed (PSPPIRE_SHEET_COLUMN (store),
-                                 var_num, 1);
-
-  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (store),
-                              -1, var_num,
-                              -1, var_num);
-#endif
 }
 
 static void
@@ -348,14 +229,6 @@ insert_variable_callback (GObject *obj, gint var_num, gpointer data)
   variable = psppire_dict_get_variable (store->dict, var_num);
   posn = var_get_case_index (variable);
   psppire_data_store_insert_value (store, var_get_width (variable), posn);
-
-#if AXIS_TRANSITION
-
-  psppire_sheet_column_columns_changed (PSPPIRE_SHEET_COLUMN (store),
-                                 var_num, 1);
-#endif
-
-  psppire_sheet_model_columns_inserted (PSPPIRE_SHEET_MODEL (store), var_num, 1);
 }
 
 struct resize_datum_aux
@@ -437,9 +310,6 @@ psppire_data_store_set_reader (PsppireDataStore *ds,
 
   ds->datasheet = datasheet_create (reader);
 
-  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (ds),
-                              -1, -1, -1, -1);
-
   if ( ds->dict )
     for (i = 0 ; i < n_dict_signals; ++i )
       {
@@ -504,11 +374,6 @@ psppire_data_store_set_dictionary (PsppireDataStore *data_store, PsppireDict *di
 
 
   /* The entire model has changed */
-  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (data_store), -1, -1, -1, -1);
-
-#if AXIS_TRANSITION
-  psppire_sheet_column_columns_changed (PSPPIRE_SHEET_COLUMN (data_store), 0, -1);
-#endif
 
   if ( data_store->dict )
     for (i = 0 ; i < n_dict_signals; ++i )
@@ -575,132 +440,86 @@ psppire_data_store_insert_new_case (PsppireDataStore *ds, casenumber posn)
   return result;
 }
 
-
 gchar *
-psppire_data_store_get_string (PsppireDataStore *store, glong row, glong column)
+psppire_data_store_get_string (PsppireDataStore *store,
+                               glong row, const struct variable *var,
+                               bool use_value_label)
 {
-  gint idx;
-  char *text;
-  const struct fmt_spec *fp ;
-  const struct variable *pv ;
-  const struct dictionary *dict;
+  gchar *string;
   union value v;
   int width;
 
-  g_return_val_if_fail (store->dict, NULL);
-  g_return_val_if_fail (store->datasheet, NULL);
+  g_return_val_if_fail (store != NULL, NULL);
+  g_return_val_if_fail (var != NULL, NULL);
 
-  dict = store->dict->dict;
-
-  if (column >= psppire_dict_get_var_cnt (store->dict))
-    return NULL;
-
-  if ( row >= psppire_data_store_get_case_count (store))
+  if (row < 0 || row >= datasheet_get_n_rows (store->datasheet))
     return NULL;
 
-  pv = psppire_dict_get_variable (store->dict, column);
-
-  g_assert (pv);
-
-  idx = var_get_case_index (pv);
-  width = var_get_width (pv);
-
-  g_assert (idx >= 0);
-
+  width = var_get_width (var);
   value_init (&v, width);
-  if (!psppire_data_store_get_value (store, row, idx, &v))
-    return NULL;
+  datasheet_get_value (store->datasheet, row, var_get_case_index (var), &v);
 
-  if ( store->show_labels)
+  string = NULL;
+  if (use_value_label)
     {
-      const gchar *label = var_lookup_value_label (pv, &v);
-      if (label)
-        {
-          value_destroy (&v, width);
-         return g_strdup (label);
-        }
+      const char *label = var_lookup_value_label (var, &v);
+      if (label != NULL)
+        string = g_strdup (label);
     }
-
-  fp = var_get_print_format (pv);
-
-  text = data_out (&v, dict_get_encoding (dict), fp);
-
-  g_strchomp (text);
+  if (string == NULL)
+    string = value_to_text (v, var);
 
   value_destroy (&v, width);
-  return text;
-}
-
-
-static gboolean
-psppire_data_store_clear_datum (PsppireSheetModel *model,
-                                         glong row, glong col)
-{
-  PsppireDataStore *store = PSPPIRE_DATA_STORE (model);
 
-  union value v;
-  const struct variable *pv = psppire_dict_get_variable (store->dict, col);
-  int width = var_get_width (pv);
-
-  const gint index = var_get_case_index (pv) ;
-
-  value_init (&v, width);
-  value_set_missing (&v, width);
-  psppire_data_store_set_value (store, row, index, &v);
-  value_destroy (&v, width);
+  return string;
+}
 
-  psppire_sheet_model_range_changed (model, row, col, row, col);
 
+/* Attempts to update that part of the variable store which corresponds to VAR
+   within ROW with the value TEXT.
 
-  return TRUE;
-}
+   If USE_VALUE_LABEL is true, and TEXT is a value label for the column's
+   variable, then stores the value from that value label instead of the literal
+   TEXT.
 
-
-/* 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.
-*/
+   Returns true if anything was updated, false otherwise.  */
 gboolean
 psppire_data_store_set_string (PsppireDataStore *store,
-                              const gchar *text, glong row, glong col)
+                              const gchar *text,
+                               glong row, const struct variable *var,
+                               gboolean use_value_label)
 {
+  gint case_index;
   glong n_cases;
-  const struct variable *pv = psppire_dict_get_variable (store->dict, col);
-  if ( NULL == pv)
-    return FALSE;
+  gboolean ok;
 
   n_cases = psppire_data_store_get_case_count (store);
-
-  if ( row > n_cases)
+  if (row > n_cases)
     return FALSE;
-
   if (row == n_cases)
     psppire_data_store_insert_new_case (store, row);
 
-  psppire_data_store_data_in (store, row,
-                             var_get_case_index (pv), ss_cstr (text),
-                             var_get_print_format (pv));
-
-  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (store), row, col, row, col);
+  case_index = var_get_case_index (var);
+  if (use_value_label)
+    {
+      const struct val_labs *vls = var_get_value_labels (var);
+      const union value *value = vls ? val_labs_find_value (vls, text) : NULL;
+      if (value)
+        ok = datasheet_put_value (store->datasheet, row, case_index, value);
+      else
+        ok = FALSE;
+    }
+  else
+    ok = psppire_data_store_data_in (store, row, case_index, ss_cstr (text),
+                                     var_get_print_format (var));
 
-  return TRUE;
+  if (ok)
+    g_signal_emit (store, signals [CASE_CHANGED], 0, row);
+  return ok;
 }
 
 
 
-void
-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;
-
-  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (store),
-                                -1, -1, -1, -1);
-}
-
-
 void
 psppire_data_store_clear (PsppireDataStore *ds)
 {
@@ -743,93 +562,6 @@ psppire_data_store_get_reader (PsppireDataStore *ds)
 
 static const gchar null_var_name[]=N_("var");
 
-\f
-
-/* Row related funcs */
-
-static gchar *
-get_row_button_label (const PsppireSheetModel *model, gint unit)
-{
-  // PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
-
-  return g_strdup_printf (_("%d"), unit + FIRST_CASE_NUMBER);
-}
-
-
-static gboolean
-get_row_sensitivity (const PsppireSheetModel *model, gint unit)
-{
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
-
-  return (unit < psppire_data_store_get_case_count (ds));
-}
-
-
-\f
-
-/* Column related stuff */
-
-static gchar *
-get_column_subtitle (const PsppireSheetModel *model, gint col)
-{
-  const struct variable *v ;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
-
-  if ( col >= psppire_dict_get_var_cnt (ds->dict) )
-    return NULL;
-
-  v = psppire_dict_get_variable (ds->dict, col);
-
-  if ( ! var_has_label (v))
-    return NULL;
-
-  return xstrdup (var_get_label (v));
-}
-
-static gchar *
-get_column_button_label (const PsppireSheetModel *model, gint col)
-{
-  struct variable *pv ;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
-
-  if ( col >= psppire_dict_get_var_cnt (ds->dict) )
-    return xstrdup (gettext (null_var_name));
-
-  pv = psppire_dict_get_variable (ds->dict, col);
-
-  if (NULL == pv)
-    return NULL;
-
-  return xstrdup (var_get_name (pv));
-}
-
-static gboolean
-get_column_sensitivity (const PsppireSheetModel *model, gint col)
-{
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
-
-  return (col < psppire_dict_get_var_cnt (ds->dict));
-}
-
-
-
-static GtkJustification
-get_column_justification (const PsppireSheetModel *model, gint col)
-{
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
-  const struct variable *pv ;
-
-  if ( col >= psppire_dict_get_var_cnt (ds->dict) )
-    return GTK_JUSTIFY_LEFT;
-
-  pv = psppire_dict_get_variable (ds->dict, col);
-
-  return (var_get_alignment (pv) == ALIGN_LEFT ? GTK_JUSTIFY_LEFT
-          : var_get_alignment (pv) == ALIGN_RIGHT ? GTK_JUSTIFY_RIGHT
-          : GTK_JUSTIFY_CENTER);
-}
-
-
 
 \f
 
@@ -861,7 +593,6 @@ psppire_data_store_delete_cases (PsppireDataStore *ds, casenumber first,
   datasheet_delete_rows (ds->datasheet, first, n_cases);
 
   g_signal_emit (ds, signals [CASES_DELETED], 0, first, n_cases);
-  psppire_sheet_model_rows_deleted (PSPPIRE_SHEET_MODEL (ds), first, n_cases);
 
   return TRUE;
 }
@@ -883,10 +614,7 @@ psppire_data_store_insert_case (PsppireDataStore *ds,
   result = datasheet_insert_rows (ds->datasheet, posn, &cc, 1);
 
   if ( result )
-    {
-      g_signal_emit (ds, signals [CASE_INSERTED], 0, posn);
-      psppire_sheet_model_rows_inserted (PSPPIRE_SHEET_MODEL (ds), posn, 1);
-    }
+    g_signal_emit (ds, signals [CASE_INSERTED], 0, posn);
   else
     g_warning ("Cannot insert case at position %ld\n", posn);
 
@@ -894,38 +622,28 @@ psppire_data_store_insert_case (PsppireDataStore *ds,
 }
 
 
-/* Copies the IDXth value from case CASENUM into VALUE, which
-   must be of the correct width for IDX.
-   Returns true if successful, false on failure. */
-static bool
-psppire_data_store_get_value (const PsppireDataStore *ds,
-                             casenumber casenum, size_t idx,
-                             union value *value)
-{
-  g_return_val_if_fail (ds, false);
-  g_return_val_if_fail (ds->datasheet, false);
-  g_return_val_if_fail (idx < datasheet_get_n_columns (ds->datasheet), false);
-
-  return datasheet_get_value (ds->datasheet, casenum, idx, value);
-}
-
-
-
-/* Set the IDXth value of case C to V.
+/* Set the value of VAR in case CASENUM to V.
    V must be the correct width for IDX.
    Returns true if successful, false on I/O error. */
-static gboolean
+gboolean
 psppire_data_store_set_value (PsppireDataStore *ds, casenumber casenum,
-                             gint idx, union value *v)
+                             const struct variable *var, const union value *v)
 {
+  glong n_cases;
   bool ok;
 
   g_return_val_if_fail (ds, FALSE);
   g_return_val_if_fail (ds->datasheet, FALSE);
 
-  g_return_val_if_fail (idx < datasheet_get_n_columns (ds->datasheet), FALSE);
+  n_cases = psppire_data_store_get_case_count (ds);
+  if ( casenum > n_cases)
+    return FALSE;
 
-  ok = datasheet_put_value (ds->datasheet, casenum, idx, v);
+  if (casenum == n_cases)
+    psppire_data_store_insert_new_case (ds, casenum);
+
+  ok = datasheet_put_value (ds->datasheet, casenum, var_get_case_index (var),
+                            v);
   if (ok)
     g_signal_emit (ds, signals [CASE_CHANGED], 0, casenum);
 
@@ -964,9 +682,6 @@ psppire_data_store_data_in (PsppireDataStore *ds, casenumber casenum, gint idx,
         && datasheet_put_value (ds->datasheet, casenum, idx, &value));
   value_destroy (&value, width);
 
-  if (ok)
-    g_signal_emit (ds, signals [CASE_CHANGED], 0, casenum);
-
   return ok;
 }
 
@@ -988,40 +703,38 @@ psppire_data_store_insert_value (PsppireDataStore *ds,
     ds->datasheet = datasheet_create (NULL);
 
   value_init (&value, width);
-  if (width == 0)
-    value.f = 0;
-  else
-    value_set_missing (&value, width);
+  value_set_missing (&value, width);
 
   datasheet_insert_column (ds->datasheet, &value, width, where);
+  value_destroy (&value, width);
 
   return TRUE;
 }
 
-static gboolean
-get_row_overstrike (const PsppireSheetModel *model, gint row)
+gboolean
+psppire_data_store_filtered (PsppireDataStore *ds,
+                             glong row)
 {
   union value val;
-  PsppireDataStore *ds = PSPPIRE_DATA_STORE (model);
-
-  const struct dictionary *dict = ds->dict->dict;
 
-  const struct variable *filter = dict_get_filter (dict);
+  const struct dictionary *dict;
+  const struct variable *filter;
 
   if ( row < 0 || row >= datasheet_get_n_rows (ds->datasheet))
     return FALSE;
 
+  dict = ds->dict->dict;
+  g_return_val_if_fail (dict, FALSE);
+  filter = dict_get_filter (dict);
   if ( ! filter)
     return FALSE;
 
-  g_assert (var_is_numeric (filter));
-
+  g_return_val_if_fail (var_is_numeric (filter), FALSE);
   value_init (&val, 0);
   if ( ! datasheet_get_value (ds->datasheet, row,
-                             var_get_case_index (filter),
-                             &val) )
+                              var_get_case_index (filter),
+                              &val) )
     return FALSE;
 
-
   return (val.f == 0.0);
 }
index 6e2ec906fc49289eb0dd8865af87b7f00826d59a..8096962ad740e1266b70a4c72ef33fe53c7a5ec1 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (C) 2006, 2009  Free Software Foundation
+   Copyright (C) 2006, 2009, 2011, 2012  Free Software Foundation
 
    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
@@ -74,9 +74,6 @@ struct _PsppireDataStore
   PsppireDict *dict;
   struct datasheet *datasheet;
 
-  gboolean show_labels;
-
-  //  gint cf_handler_id [n_cf_signals];
   gint dict_handler_id [n_dict_signals];
 };
 
@@ -96,9 +93,6 @@ void psppire_data_store_set_reader (PsppireDataStore *ds,
 void psppire_data_store_set_dictionary (PsppireDataStore *data_store,
                                        PsppireDict *dict);
 
-void psppire_data_store_show_labels (PsppireDataStore *store,
-                                    gboolean show_labels);
-
 void psppire_data_store_clear (PsppireDataStore *data_store);
 
 gboolean psppire_data_store_insert_new_case (PsppireDataStore *ds, casenumber posn);
@@ -109,12 +103,17 @@ gboolean psppire_data_store_delete_cases (PsppireDataStore *ds, casenumber first
 
 struct casereader * psppire_data_store_get_reader (PsppireDataStore *ds);
 
-gchar * psppire_data_store_get_string (PsppireDataStore *ds,
-                                      casenumber row, glong column);
-
+gchar *psppire_data_store_get_string (PsppireDataStore *,
+                                      glong row, const struct variable *,
+                                      bool use_value_label);
+gboolean psppire_data_store_set_value (PsppireDataStore *,
+                                       casenumber casenum,
+                                       const struct variable *,
+                                       const union value *);
 gboolean psppire_data_store_set_string (PsppireDataStore *ds,
                                        const gchar *text,
-                                       glong row, glong column);
+                                       glong row, const struct variable *,
+                                        gboolean use_value_label);
 
 
 gboolean psppire_data_store_filtered (PsppireDataStore *ds,
index 6dc422e3404773b993ad2c1ed78b14fae19a1c85..8ef17fae00f8340c9d405c6ca0c164f25eb4c3fe 100644 (file)
@@ -35,9 +35,7 @@
 #include "ui/gui/crosstabs-dialog.h"
 #include "ui/gui/entry-dialog.h"
 #include "ui/gui/executor.h"
-#include "ui/gui/find-dialog.h"
 #include "ui/gui/frequencies-dialog.h"
-#include "ui/gui/goto-case-dialog.h"
 #include "ui/gui/help-menu.h"
 #include "ui/gui/helper.h"
 #include "ui/gui/helper.h"
@@ -45,6 +43,7 @@
 #include "ui/gui/npar-two-sample-related.h"
 #include "ui/gui/oneway-anova-dialog.h"
 #include "ui/gui/psppire-data-window.h"
+#include "ui/gui/psppire-dialog-action.h"
 #include "ui/gui/psppire-syntax-window.h"
 #include "ui/gui/psppire-window.h"
 #include "ui/gui/psppire.h"
@@ -88,6 +87,10 @@ static void psppire_data_window_get_property (GObject         *object,
                                               GValue          *value,
                                               GParamSpec      *pspec);
 
+static guint psppire_data_window_add_ui (PsppireDataWindow *, GtkUIManager *);
+static void psppire_data_window_remove_ui (PsppireDataWindow *,
+                                           GtkUIManager *, guint);
+
 GType
 psppire_data_window_get_type (void)
 {
@@ -152,24 +155,6 @@ psppire_data_window_class_init (PsppireDataWindowClass *class)
                           G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
 }
 \f
-static void
-set_paste_menuitem_sensitivity (PsppireDataWindow *de, gboolean x)
-{
-  GtkAction *edit_paste = get_action_assert (de->builder, "edit_paste");
-
-  gtk_action_set_sensitive (edit_paste, x);
-}
-
-static void
-set_cut_copy_menuitem_sensitivity (PsppireDataWindow *de, gboolean x)
-{
-  GtkAction *edit_copy = get_action_assert (de->builder, "edit_copy");
-  GtkAction *edit_cut = get_action_assert (de->builder, "edit_cut");
-
-  gtk_action_set_sensitive (edit_copy, x);
-  gtk_action_set_sensitive (edit_cut, x);
-}
-
 /* Run the EXECUTE command. */
 static void
 execute (PsppireDataWindow *dw)
@@ -430,19 +415,6 @@ save_file (PsppireWindow *w)
 }
 
 
-static void
-insert_case (PsppireDataWindow *dw)
-{
-  psppire_data_editor_insert_case (dw->data_editor);
-}
-
-static void
-on_insert_variable (PsppireDataWindow *dw)
-{
-  psppire_data_editor_insert_variable (dw->data_editor);
-}
-
-
 static void
 display_dict (PsppireDataWindow *de)
 {
@@ -627,27 +599,6 @@ on_rename_dataset (PsppireDataWindow *de)
   free (new_name);
 }
 
-static void
-on_edit_paste (PsppireDataWindow  *de)
-{
-  psppire_data_editor_clip_paste (de->data_editor);
-}
-
-static void
-on_edit_copy (PsppireDataWindow  *de)
-{
-  psppire_data_editor_clip_copy (de->data_editor);
-}
-
-
-
-static void
-on_edit_cut (PsppireDataWindow  *de)
-{
-  psppire_data_editor_clip_cut (de->data_editor);
-}
-
-
 static void
 status_bar_activate (PsppireDataWindow  *de, GtkToggleAction *action)
 {
@@ -742,7 +693,6 @@ file_quit (PsppireDataWindow *de)
   psppire_quit ();
 }
 
-
 static void
 on_recent_data_select (GtkMenuShell *menushell,
                       PsppireWindow *window)
@@ -827,75 +777,52 @@ on_recent_files_select (GtkMenuShell *menushell,   gpointer user_data)
   g_free (file);
 }
 
-
-
 static void
-enable_delete_cases (GtkWidget *w, gint case_num, gpointer data)
+set_unsaved (gpointer w)
 {
-  PsppireDataWindow *de = PSPPIRE_DATA_WINDOW (data);
-
-  gtk_action_set_visible (de->delete_cases, case_num != -1);
+  psppire_window_set_unsaved (PSPPIRE_WINDOW (w));
 }
 
-
 static void
-enable_delete_variables (GtkWidget *w, gint var, gpointer data)
+on_switch_page (PsppireDataEditor *de, GtkNotebookPage *p,
+               gint pagenum, PsppireDataWindow *dw)
 {
-  PsppireDataWindow *de = PSPPIRE_DATA_WINDOW (data);
-
-  gtk_action_set_visible (de->delete_variables, var != -1);
+  GtkWidget *page_menu_item;
+  gboolean is_ds;
+  const char *path;
+
+  is_ds = pagenum == PSPPIRE_DATA_EDITOR_DATA_VIEW;
+  path = (is_ds
+          ? "/ui/menubar/view/view_data"
+          : "/ui/menubar/view/view_variables");
+  page_menu_item = gtk_ui_manager_get_widget (dw->ui_manager, path);
+  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (page_menu_item), TRUE);
 }
 
-/* Callback for when the datasheet/varsheet is selected */
 static void
-on_switch_sheet (GtkNotebook *notebook,
-                GtkNotebookPage *page,
-                guint page_num,
-                gpointer user_data)
+on_ui_manager_changed (PsppireDataEditor *de,
+                       GParamSpec *pspec UNUSED,
+                       PsppireDataWindow *dw)
 {
-  PsppireDataWindow *de = PSPPIRE_DATA_WINDOW (user_data);
-
-  GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (de->builder, "uimanager1", GTK_TYPE_UI_MANAGER));
-
-  GtkWidget *view_data =
-    gtk_ui_manager_get_widget (uim,"/ui/menubar/view/view_data");
-
-  GtkWidget *view_variables =
-    gtk_ui_manager_get_widget (uim,"/ui/menubar/view/view_variables");
+  GtkUIManager *uim = psppire_data_editor_get_ui_manager (de);
+  if (uim == dw->uim)
+    return;
 
-  switch (page_num)
+  if (dw->uim)
     {
-    case PSPPIRE_DATA_EDITOR_VARIABLE_VIEW:
-      gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (view_variables),
-                                      TRUE);
-      gtk_action_set_sensitive (de->insert_variable, TRUE);
-      gtk_action_set_sensitive (de->insert_case, FALSE);
-      gtk_action_set_sensitive (de->invoke_goto_dialog, FALSE);
-      break;
-    case PSPPIRE_DATA_EDITOR_DATA_VIEW:
-      gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (view_data), TRUE);
-      gtk_action_set_sensitive (de->invoke_goto_dialog, TRUE);
-      gtk_action_set_sensitive (de->insert_case, TRUE);
-      break;
-    default:
-      g_assert_not_reached ();
-      break;
+      psppire_data_window_remove_ui (dw, dw->uim, dw->merge_id);
+      g_object_unref (dw->uim);
+      dw->uim = NULL;
     }
 
-#if 0
-  update_paste_menuitem (de, page_num);
-#endif
-}
-
-
-
-static void
-set_unsaved (gpointer w)
-{
-  psppire_window_set_unsaved (PSPPIRE_WINDOW (w));
+  dw->uim = uim;
+  if (dw->uim)
+    {
+      g_object_ref (dw->uim);
+      dw->merge_id = psppire_data_window_add_ui (dw, dw->uim);
+    }
 }
 
-
 /* Connects the action called ACTION_NAME to HANDLER passing DW as the auxilliary data.
    Returns a pointer to the action
 */
@@ -920,14 +847,15 @@ connect_action (PsppireDataWindow *dw, const char *action_name,
 static void
 psppire_data_window_init (PsppireDataWindow *de)
 {
-  GtkUIManager *uim;
-
   de->builder = builder_new ("data-editor.ui");
 
-  uim = GTK_UI_MANAGER (get_object_assert (de->builder, "uimanager1", GTK_TYPE_UI_MANAGER));
+  de->ui_manager = GTK_UI_MANAGER (get_object_assert (de->builder, "uimanager1", GTK_TYPE_UI_MANAGER));
 
   PSPPIRE_WINDOW (de)->menu =
-    GTK_MENU_SHELL (gtk_ui_manager_get_widget (uim,"/ui/menubar/windows/windows_minimise_all")->parent);
+    GTK_MENU_SHELL (gtk_ui_manager_get_widget (de->ui_manager, "/ui/menubar/windows/windows_minimise_all")->parent);
+
+  de->uim = NULL;
+  de->merge_id = 0;
 }
 
 static void
@@ -958,9 +886,14 @@ psppire_data_window_finish_init (PsppireDataWindow *de,
   hb = get_widget_assert (de->builder, "handlebox1");
   sb = get_widget_assert (de->builder, "status-bar");
 
+  de->uim = NULL;
+  de->merge_id = 0;
+
   de->data_editor =
-    PSPPIRE_DATA_EDITOR (psppire_data_editor_new (de, de->var_store,
+    PSPPIRE_DATA_EDITOR (psppire_data_editor_new (de->var_store,
                                                   de->data_store));
+  g_signal_connect (de->data_editor, "switch-page",
+                    G_CALLBACK (on_switch_page), de);
 
   g_signal_connect_swapped (de->data_store, "case-changed",
                            G_CALLBACK (set_unsaved), de);
@@ -982,17 +915,6 @@ psppire_data_window_finish_init (PsppireDataWindow *de,
 
   gtk_container_add (GTK_CONTAINER (de), box);
 
-  set_cut_copy_menuitem_sensitivity (de, FALSE);
-
-  g_signal_connect_swapped (de->data_editor, "data-selection-changed",
-                           G_CALLBACK (set_cut_copy_menuitem_sensitivity), de);
-
-
-  set_paste_menuitem_sensitivity (de, FALSE);
-
-  g_signal_connect_swapped (de->data_editor, "data-available-changed",
-                           G_CALLBACK (set_paste_menuitem_sensitivity), de);
-
   g_signal_connect (dict, "weight-changed",
                    G_CALLBACK (on_weight_change),
                    de);
@@ -1006,10 +928,6 @@ psppire_data_window_finish_init (PsppireDataWindow *de,
                    de);
 
 
-  connect_action (de, "edit_copy", G_CALLBACK (on_edit_copy));
-
-  connect_action (de, "edit_cut", G_CALLBACK (on_edit_cut));
-
   connect_action (de, "file_new_data", G_CALLBACK (create_data_window));
 
   connect_action (de, "file_import-text", G_CALLBACK (text_data_import_assistant));
@@ -1026,40 +944,13 @@ psppire_data_window_finish_init (PsppireDataWindow *de,
 
   connect_action (de, "file_information_external-file", G_CALLBACK (sysfile_info));
 
-  connect_action (de, "edit_paste", G_CALLBACK (on_edit_paste));
-
-  de->insert_case = connect_action (de, "edit_insert-case", G_CALLBACK (insert_case));
-
-  de->insert_variable = connect_action (de, "action_insert-variable", G_CALLBACK (on_insert_variable));
-
-  de->invoke_goto_dialog = connect_action (de, "edit_goto-case", G_CALLBACK (goto_case_dialog));
-
   g_signal_connect_swapped (get_action_assert (de->builder, "view_value-labels"), "toggled", G_CALLBACK (toggle_value_labels), de);
 
-  {
-    de->delete_cases = get_action_assert (de->builder, "edit_clear-cases");
-
-    g_signal_connect_swapped (de->delete_cases, "activate", G_CALLBACK (psppire_data_editor_delete_cases), de->data_editor);
-
-    gtk_action_set_visible (de->delete_cases, FALSE);
-  }
-
-
-  {
-    de->delete_variables = get_action_assert (de->builder, "edit_clear-variables");
-
-    g_signal_connect_swapped (de->delete_variables, "activate", G_CALLBACK (psppire_data_editor_delete_variables), de->data_editor);
-
-    gtk_action_set_visible (de->delete_variables, FALSE);
-  }
-
-
   connect_action (de, "data_transpose", G_CALLBACK (transpose_dialog));
   connect_action (de, "data_select-cases", G_CALLBACK (select_cases_dialog));
   connect_action (de, "data_aggregate", G_CALLBACK (aggregate_dialog));
   connect_action (de, "transform_compute", G_CALLBACK (compute_dialog));
   connect_action (de, "transform_autorecode", G_CALLBACK (autorecode_dialog));
-  connect_action (de, "edit_find", G_CALLBACK (find_dialog));
   connect_action (de, "data_split-file", G_CALLBACK (split_file_dialog));
   connect_action (de, "data_weight-cases", G_CALLBACK (weight_cases_dialog));
   connect_action (de, "oneway-anova", G_CALLBACK (oneway_anova_dialog));
@@ -1080,13 +971,11 @@ psppire_data_window_finish_init (PsppireDataWindow *de,
   connect_action (de, "two-related-samples", G_CALLBACK (two_related_dialog));
 
   {
-    GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (de->builder, "uimanager1", GTK_TYPE_UI_MANAGER));
-
     GtkWidget *recent_data =
-      gtk_ui_manager_get_widget (uim,"/ui/menubar/file/file_recent-data");
+      gtk_ui_manager_get_widget (de->ui_manager, "/ui/menubar/file/file_recent-data");
 
     GtkWidget *recent_files =
-      gtk_ui_manager_get_widget (uim,"/ui/menubar/file/file_recent-files");
+      gtk_ui_manager_get_widget (de->ui_manager, "/ui/menubar/file/file_recent-files");
 
 
     GtkWidget *menu_data = gtk_recent_chooser_menu_new_for_manager (
@@ -1134,21 +1023,6 @@ psppire_data_window_finish_init (PsppireDataWindow *de,
   connect_action (de, "file_new_syntax", G_CALLBACK (create_syntax_window));
 
 
-  g_signal_connect (de->data_editor,
-                   "cases-selected",
-                   G_CALLBACK (enable_delete_cases),
-                   de);
-
-  g_signal_connect (de->data_editor,
-                   "variables-selected",
-                   G_CALLBACK (enable_delete_variables),
-                   de);
-
-
-  g_signal_connect (de->data_editor,
-                   "switch-page",
-                   G_CALLBACK (on_switch_sheet), de);
-
   gtk_notebook_set_current_page (GTK_NOTEBOOK (de->data_editor), PSPPIRE_DATA_EDITOR_VARIABLE_VIEW);
   gtk_notebook_set_current_page (GTK_NOTEBOOK (de->data_editor), PSPPIRE_DATA_EDITOR_DATA_VIEW);
 
@@ -1170,36 +1044,11 @@ psppire_data_window_finish_init (PsppireDataWindow *de,
 
   g_signal_connect_swapped (get_action_assert (de->builder, "windows_split"), "toggled", G_CALLBACK (toggle_split_window), de);
 
-  {
-    GtkUIManager *uim = GTK_UI_MANAGER (get_object_assert (de->builder, "uimanager1", GTK_TYPE_UI_MANAGER));
-
-    merge_help_menu (uim);
-  }
-
-  {
-    GtkWidget *data_sheet_cases_popup_menu = get_widget_assert (de->builder,
-                                                               "datasheet-cases-popup");
-
-    GtkWidget *var_sheet_variable_popup_menu = get_widget_assert (de->builder,
-                                                                 "varsheet-variable-popup");
-
-    GtkWidget *data_sheet_variable_popup_menu = get_widget_assert (de->builder,
-                                                                  "datasheet-variable-popup");
+  merge_help_menu (de->ui_manager);
 
-    g_signal_connect_swapped (get_action_assert (de->builder, "sort-up"), "activate",
-                             G_CALLBACK (psppire_data_editor_sort_ascending),
-                             de->data_editor);
-
-    g_signal_connect_swapped (get_action_assert (de->builder, "sort-down"), "activate",
-                             G_CALLBACK (psppire_data_editor_sort_descending),
-                             de->data_editor);
-
-    g_object_set (de->data_editor,
-                 "datasheet-column-menu", data_sheet_variable_popup_menu,
-                 "datasheet-row-menu", data_sheet_cases_popup_menu,
-                 "varsheet-row-menu", var_sheet_variable_popup_menu,
-                 NULL);
-  }
+  g_signal_connect (de->data_editor, "notify::ui-manager",
+                    G_CALLBACK (on_ui_manager_changed), de);
+  on_ui_manager_changed (de->data_editor, NULL, de);
 
   gtk_widget_show (GTK_WIDGET (de->data_editor));
   gtk_widget_show (box);
@@ -1278,6 +1127,71 @@ psppire_data_window_get_property (GObject         *object,
     };
 }
 
+static guint
+psppire_data_window_add_ui (PsppireDataWindow *pdw, GtkUIManager *uim)
+{
+  gchar *ui_string;
+  guint merge_id;
+  GList *list;
+
+  ui_string = gtk_ui_manager_get_ui (uim);
+  merge_id = gtk_ui_manager_add_ui_from_string (pdw->ui_manager, ui_string,
+                                                -1, NULL);
+  g_free (ui_string);
+
+  g_return_val_if_fail (merge_id != 0, 0);
+
+  list = gtk_ui_manager_get_action_groups (uim);
+  for (; list != NULL; list = list->next)
+    {
+      GtkActionGroup *action_group = list->data;
+      GList *actions = gtk_action_group_list_actions (action_group);
+      GList *action;
+
+      for (action = actions; action != NULL; action = action->next)
+        {
+          GtkAction *a = action->data;
+
+          if (PSPPIRE_IS_DIALOG_ACTION (a))
+            g_object_set (a, "manager", pdw->ui_manager, NULL);
+        }
+
+      gtk_ui_manager_insert_action_group (pdw->ui_manager, action_group, 0);
+    }
+
+  gtk_window_add_accel_group (GTK_WINDOW (pdw),
+                              gtk_ui_manager_get_accel_group (uim));
+
+  return merge_id;
+}
+
+static void
+psppire_data_window_remove_ui (PsppireDataWindow *pdw,
+                               GtkUIManager *uim, guint merge_id)
+{
+  GList *list;
+
+  g_return_if_fail (merge_id != 0);
+
+  gtk_ui_manager_remove_ui (pdw->ui_manager, merge_id);
+
+  list = gtk_ui_manager_get_action_groups (uim);
+  for (; list != NULL; list = list->next)
+    {
+      GtkActionGroup *action_group = list->data;
+      gtk_ui_manager_remove_action_group (pdw->ui_manager, action_group);
+    }
+
+  gtk_window_remove_accel_group (GTK_WINDOW (pdw),
+                                 gtk_ui_manager_get_accel_group (uim));
+
+  /* Our caller unrefs 'uim', possibly causing 'uim' to be freed.  The
+     following call appears to be necessary to ensure that pdw->ui_manager
+     drops all references to 'uim'.  Otherwise, I get valgrind complaints about
+     access to freed memory (and segfaults) on e.g. Windows|Split View.  */
+  gtk_ui_manager_ensure_update (pdw->ui_manager);
+}
+
 GtkWidget*
 psppire_data_window_new (struct dataset *ds)
 {
@@ -1358,6 +1272,18 @@ psppire_data_window_for_dataset (struct dataset *ds)
   return NULL;
 }
 
+PsppireDataWindow *
+psppire_data_window_for_data_store (PsppireDataStore *data_store)
+{
+  PsppireDataWindow *pdw;
+
+  ll_for_each (pdw, PsppireDataWindow, ll, &all_data_windows)
+    if (pdw->data_store == data_store)
+      return pdw;
+
+  return NULL;
+}
+
 void
 create_data_window (void)
 {
@@ -1371,10 +1297,14 @@ open_data_window (PsppireWindow *victim, const char *file_name)
 
   if (PSPPIRE_IS_DATA_WINDOW (victim)
       && psppire_data_window_is_empty (PSPPIRE_DATA_WINDOW (victim)))
-    window = GTK_WIDGET (victim);
+    {
+      window = GTK_WIDGET (victim);
+      gtk_widget_hide (GTK_WIDGET (PSPPIRE_DATA_WINDOW (window)->data_editor));
+    }
   else
     window = psppire_data_window_new (NULL);
 
   psppire_window_load (PSPPIRE_WINDOW (window), file_name);
-  gtk_widget_show (window);
+  gtk_widget_show_all (window);
 }
+
index 93b51b0cee9aa7e5f7e973672869de8dd923e620..f939d9ff1522ecb35679526d5b1ad24f78b7e59c 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (C) 2008, 2010, 2011  Free Software Foundation
+   Copyright (C) 2008, 2010, 2011, 2012  Free Software Foundation
 
    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
 #ifndef __PSPPIRE_DATA_WINDOW_H__
 #define __PSPPIRE_DATA_WINDOW_H__
 
+/* PsppireDataWindow is a top-level window for editing a PSPP dataset.
+
+   PsppireDataWindow contains a PsppireDataEditor.
+
+   PsppireDataWindow's own functionality basically amounts to managing menus
+   and toolbars.  In addition to maintaining some menu itema and toolbar items
+   of its own, it merges in menu and toolbar items provided by its child
+   PsppireDataEditor (based on the "ui-manager" property of PsppireDataEditor).
+ */
 
 #include <glib.h>
 #include <glib-object.h>
@@ -50,24 +59,20 @@ struct _PsppireDataWindow
   /* <private> */
   PsppireDataEditor *data_editor;
   GtkBuilder *builder;
+  GtkUIManager *ui_manager;
 
   PsppireVarStore *var_store;
   struct dataset *dataset;
   PsppireDataStore *data_store;
 
-  GtkAction *invoke_goto_dialog;
-
-  GtkAction *insert_variable;
-  GtkAction *insert_case;
-  GtkAction *delete_variables;
-  GtkAction *delete_cases;
-
-
   gboolean save_as_portable;
 
   struct ll ll;                 /* In global 'all_data_windows' list. */
   unsigned long int lazy_serial;
   unsigned int dataset_seqno;
+
+  GtkUIManager *uim;
+  guint merge_id;
 };
 
 struct _PsppireDataWindowClass
@@ -86,6 +91,7 @@ void psppire_data_window_set_default (PsppireDataWindow *);
 void psppire_data_window_undefault (PsppireDataWindow *);
 
 PsppireDataWindow *psppire_data_window_for_dataset (struct dataset *);
+PsppireDataWindow *psppire_data_window_for_data_store (PsppireDataStore *);
 
 bool psppire_data_window_is_empty (PsppireDataWindow *);
 void create_data_window (void);
index 226fc58a9677df63c1a2fb1cfea13449de91ba5c..0d22e775f597faaa865ed1e83ae0c84252d79210 100644 (file)
 
 #include <config.h>
 
-#include "psppire-dialog-action-var-info.h"
-
-#include <data/variable.h>
-#include <data/format.h>
-#include <data/value-labels.h>
-#include <libpspp/i18n.h>
-
-#include "var-display.h"
-#include "helper.h"
-#include "psppire-var-view.h"
-#include "psppire-dictview.h"
-
-#include "psppire-dialog.h"
-#include "builder-wrapper.h"
-#include "psppire-data-window.h"
+#include "ui/gui/psppire-dialog-action-var-info.h"
+
+#include <gtk/gtk.h>
+
+#include "data/format.h"
+#include "data/value-labels.h"
+#include "data/variable.h"
+#include "libpspp/i18n.h"
+#include "ui/gui/builder-wrapper.h"
+#include "ui/gui/helper.h"
+#include "ui/gui/psppire-data-window.h"
+#include "ui/gui/psppire-dialog.h"
+#include "ui/gui/psppire-dictview.h"
+#include "ui/gui/psppire-var-store.h"
+#include "ui/gui/var-display.h"
 
 static void psppire_dialog_action_var_info_init            (PsppireDialogActionVarInfo      *act);
 static void psppire_dialog_action_var_info_class_init      (PsppireDialogActionVarInfoClass *class);
@@ -178,9 +178,8 @@ jump_to (PsppireDialog *d, gint response, gpointer data)
 
   g_object_get (pda, "top-level", &dw, NULL);
 
-  g_object_set (dw->data_editor,
-               "current-variable", var_get_dict_index (var),
-               NULL);
+  psppire_data_editor_goto_variable (dw->data_editor,
+                                     var_get_dict_index (var));
 }
 
 static void
@@ -228,4 +227,3 @@ static void
 psppire_dialog_action_var_info_init (PsppireDialogActionVarInfo *act)
 {
 }
-
index 8bf5a8d5450ac2106e1f435bc7d07bdfc7699c80..2a23fa6139f41181ab17c9fa29acb9202fb298c2 100644 (file)
@@ -140,7 +140,7 @@ psppire_dialog_action_class_init (PsppireDialogActionClass *class)
                         "Manager",
                         "The GtkUIManager which created this object",
                         GTK_TYPE_UI_MANAGER,
-                        G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
+                        G_PARAM_READWRITE);
 
   GParamSpec *toplevel_spec =
     g_param_spec_object ("top-level",
index 648d9fd786ee149bdeafd7e972c382d4893dc66a..037af7b93ec90a0f11d0ed1b28b1addbeb780a2d 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (C) 2008, 2009, 2011 Free Software Foundation, Inc.
+   Copyright (C) 2011, 2012 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
    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <config.h>
-#include "psppire-var-sheet.h"
-#include <ui/gui/sheet/psppire-axis.h>
 
-#include "builder-wrapper.h"
-#include "helper.h"
-
-#include "customentry.h"
-#include <data/variable.h>
-#include "psppire-var-store.h"
+#include "ui/gui/psppire-var-sheet.h"
+
+#include "data/format.h"
+#include "data/value-labels.h"
+#include "libpspp/range-set.h"
+#include "ui/gui/builder-wrapper.h"
+#include "ui/gui/helper.h"
+#include "ui/gui/missing-val-dialog.h"
+#include "ui/gui/pspp-sheet-selection.h"
+#include "ui/gui/psppire-cell-renderer-button.h"
+#include "ui/gui/psppire-data-editor.h"
+#include "ui/gui/psppire-data-window.h"
+#include "ui/gui/psppire-dialog-action-var-info.h"
+#include "ui/gui/psppire-empty-list-store.h"
+#include "ui/gui/psppire-marshal.h"
+#include "ui/gui/psppire-var-store.h"
+#include "ui/gui/val-labs-dialog.h"
+#include "ui/gui/var-display.h"
+#include "ui/gui/var-type-dialog.h"
+
+#include "gl/intprops.h"
 
 #include <gettext.h>
 #define _(msgid) gettext (msgid)
 #define N_(msgid) msgid
 
+enum vs_column
+  {
+    VS_NAME,
+    VS_TYPE,
+    VS_WIDTH,
+    VS_DECIMALS,
+    VS_LABEL,
+    VS_VALUES,
+    VS_MISSING,
+    VS_COLUMNS,
+    VS_ALIGN,
+    VS_MEASURE
+  };
 
-static void psppire_var_sheet_class_init  (PsppireVarSheetClass *klass);
-static void psppire_var_sheet_init        (PsppireVarSheet      *vs);
-static void psppire_var_sheet_realize     (GtkWidget *w);
-static void psppire_var_sheet_unrealize   (GtkWidget *w);
+G_DEFINE_TYPE (PsppireVarSheet, psppire_var_sheet, PSPP_TYPE_SHEET_VIEW);
 
+static void
+set_spin_cell (GtkCellRenderer *cell, int value, int min, int max, int step)
+{
+  char text[INT_BUFSIZE_BOUND (int)];
+  GtkAdjustment *adjust;
+
+  if (max > min)
+    adjust = GTK_ADJUSTMENT (gtk_adjustment_new (value, min, max,
+                                                 step, step, 0.0));
+  else
+    adjust = NULL;
+
+  sprintf (text, "%d", value);
+  g_object_set (cell,
+                "text", text,
+                "adjustment", adjust,
+                "editable", TRUE,
+                NULL);
+}
 
-enum
-  {
-    PSPPIRE_VAR_SHEET_MAY_CREATE_VARS = 1
-  };
+static void
+error_dialog (GtkWindow *w, gchar *primary_text, gchar *secondary_text)
+{
+  GtkWidget *dialog =
+    gtk_message_dialog_new (w,
+                           GTK_DIALOG_DESTROY_WITH_PARENT,
+                           GTK_MESSAGE_ERROR,
+                           GTK_BUTTONS_CLOSE, "%s", primary_text);
 
-GType
-psppire_var_sheet_get_type (void)
+  g_object_set (dialog, "icon-name", "psppicon", NULL);
+
+  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+                                           "%s", secondary_text);
+
+  gtk_dialog_run (GTK_DIALOG (dialog));
+
+  gtk_widget_destroy (dialog);
+}
+
+static void
+on_name_column_editing_started (GtkCellRenderer *cell,
+                                GtkCellEditable *editable,
+                                const gchar     *path,
+                                gpointer         user_data)
 {
-  static GType vs_type = 0;
+  PsppireVarSheet *var_sheet = g_object_get_data (G_OBJECT (cell), "var-sheet");
+  PsppireDict *dict = psppire_var_sheet_get_dictionary (var_sheet);
 
-  if (!vs_type)
+  g_return_if_fail (var_sheet);
+          g_return_if_fail (dict);
+
+  if (GTK_IS_ENTRY (editable))
     {
-      static const GTypeInfo vs_info =
-      {
-       sizeof (PsppireVarSheetClass),
-       NULL, /* base_init */
-        NULL, /* base_finalize */
-       (GClassInitFunc) psppire_var_sheet_class_init,
-        NULL, /* class_finalize */
-       NULL, /* class_data */
-        sizeof (PsppireVarSheet),
-       0,
-       (GInstanceInitFunc) psppire_var_sheet_init,
-      };
-
-      vs_type = g_type_register_static (PSPPIRE_TYPE_SHEET, "PsppireVarSheet",
-                                       &vs_info, 0);
+      GtkEntry *entry = GTK_ENTRY (editable);
+      if (gtk_entry_get_text (entry)[0] == '\0')
+        {
+          gchar name[64];
+          if (psppire_dict_generate_name (dict, name, sizeof name))
+            gtk_entry_set_text (entry, name);
+        }
     }
+}
+
+static void
+scroll_to_bottom (GtkWidget      *widget,
+                  GtkRequisition *requisition,
+                  gpointer        unused UNUSED)
+{
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
+  GtkAdjustment *vadjust;
+
+  vadjust = pspp_sheet_view_get_vadjustment (sheet_view);
+  gtk_adjustment_set_value (vadjust, gtk_adjustment_get_upper (vadjust));
 
-  return vs_type;
+  if (var_sheet->scroll_to_bottom_signal)
+    {
+      g_signal_handler_disconnect (var_sheet,
+                                   var_sheet->scroll_to_bottom_signal);
+      var_sheet->scroll_to_bottom_signal = 0;
+    }
 }
 
-static GObjectClass * parent_class = NULL;
+static void
+on_var_column_edited (GtkCellRendererText *cell,
+                      gchar               *path_string,
+                      gchar               *new_text,
+                      gpointer             user_data)
+{
+  PsppireVarSheet *var_sheet = user_data;
+  GtkWindow *window = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (var_sheet)));
+  struct dictionary *dict = var_sheet->dict->dict;
+  enum vs_column column_id;
+  struct variable *var;
+  int width, decimals;
+  GtkTreePath *path;
+  gint row;
+
+  path = gtk_tree_path_new_from_string (path_string);
+  row = gtk_tree_path_get_indices (path)[0];
+  gtk_tree_path_free (path);
+
+  column_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (cell),
+                                                  "column-id"));
+
+  var = psppire_dict_get_variable (var_sheet->dict, row);
+  if (var == NULL)
+    {
+      g_return_if_fail (column_id == VS_NAME);
+
+      if (!dict_id_is_valid (dict, new_text, false))
+        error_dialog (window,
+                      g_strdup (_("Cannot create variable.")),
+                      g_strdup_printf (_("\"%s\" is not a valid variable "
+                                         "name."), new_text));
+      else if (dict_lookup_var (dict, new_text) != NULL)
+        error_dialog (window,
+                      g_strdup (_("Cannot create variable.")),
+                      g_strdup_printf (_("This dictionary already contains "
+                                         "a variable named \"%s\"."),
+                                         new_text));
+      else
+        {
+          dict_create_var (var_sheet->dict->dict, new_text, 0);
+          if (!var_sheet->scroll_to_bottom_signal)
+            {
+              gtk_widget_queue_resize (GTK_WIDGET (var_sheet));
+              var_sheet->scroll_to_bottom_signal =
+                g_signal_connect (var_sheet, "size-request",
+                                  G_CALLBACK (scroll_to_bottom), NULL);
+            }
+        }
+
+      return;
+    }
+
+  switch (column_id)
+    {
+    case VS_NAME:
+      if (!dict_id_is_valid (dict, new_text, false))
+        error_dialog (window,
+                      g_strdup (_("Cannot rename variable.")),
+                      g_strdup_printf (_("\"%s\" is not a valid variable "
+                                         "name."), new_text));
+      else if (dict_lookup_var (dict, new_text) != NULL
+               && dict_lookup_var (dict, new_text) != var)
+        error_dialog (window,
+                      g_strdup (_("Cannot rename variable.")),
+                      g_strdup_printf (_("This dictionary already contains "
+                                         "a variable named \"%s\"."),
+                                         new_text));
+      else
+        dict_rename_var (dict, var, new_text);
+      break;
+
+    case VS_TYPE:
+      /* Not reachable. */
+      break;
+
+    case VS_WIDTH:
+      width = atoi (new_text);
+      if (width > 0)
+        {
+          struct fmt_spec format;
+
+          format = *var_get_print_format (var);
+          fmt_change_width (&format, width, var_sheet->format_use);
+          var_set_print_format (var, &format);
+          var_set_width (var, fmt_var_width (&format));
+        }
+      break;
+
+    case VS_DECIMALS:
+      decimals = atoi (new_text);
+      if (decimals >= 0)
+        {
+          struct fmt_spec format;
+
+          format = *var_get_print_format (var);
+          fmt_change_decimals (&format, decimals, var_sheet->format_use);
+          var_set_print_format (var, &format);
+        }
+      break;
+
+    case VS_LABEL:
+      var_set_label (var, new_text, false);
+      break;
+
+    case VS_VALUES:
+    case VS_MISSING:
+      break;
+
+    case VS_COLUMNS:
+      width = atoi (new_text);
+      if (width > 0 && width < 2 * MAX_STRING)
+        var_set_display_width (var, width);
+      break;
+
+    case VS_ALIGN:
+      if (!strcmp (new_text, alignment_to_string (ALIGN_LEFT)))
+        var_set_alignment (var, ALIGN_LEFT);
+      else if (!strcmp (new_text, alignment_to_string (ALIGN_CENTRE)))
+        var_set_alignment (var, ALIGN_CENTRE);
+      else if (!strcmp (new_text, alignment_to_string (ALIGN_RIGHT)))
+        var_set_alignment (var, ALIGN_RIGHT);
+      break;
+
+    case VS_MEASURE:
+      if (!strcmp (new_text, measure_to_string (MEASURE_NOMINAL)))
+        var_set_measure (var, MEASURE_NOMINAL);
+      else if (!strcmp (new_text, measure_to_string (MEASURE_ORDINAL)))
+        var_set_measure (var, MEASURE_ORDINAL);
+      else if (!strcmp (new_text, measure_to_string (MEASURE_SCALE)))
+        var_set_measure (var, MEASURE_SCALE);
+      break;
+    }
+}
+
+static void
+render_popup_cell (PsppSheetViewColumn *tree_column,
+                   GtkCellRenderer *cell,
+                   GtkTreeModel *model,
+                   GtkTreeIter *iter,
+                   void *user_data)
+{
+  PsppireVarSheet *var_sheet = user_data;
+  gint row;
+
+  row = GPOINTER_TO_INT (iter->user_data);
+  g_object_set (cell,
+                "editable", row < psppire_dict_get_var_cnt (var_sheet->dict),
+                NULL);
+}
 
 static void
-psppire_var_sheet_dispose (GObject *obj)
+render_var_cell (PsppSheetViewColumn *tree_column,
+                 GtkCellRenderer *cell,
+                 GtkTreeModel *model,
+                 GtkTreeIter *iter,
+                 void *user_data)
+{
+  PsppireVarSheet *var_sheet = user_data;
+  const struct fmt_spec *print;
+  enum vs_column column_id;
+  struct variable *var;
+  gint row;
+
+  column_id = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
+                                                  "column-number")) - 1;
+  row = GPOINTER_TO_INT (iter->user_data);
+
+  if (row >= psppire_dict_get_var_cnt (var_sheet->dict))
+    {
+      g_object_set (cell,
+                    "text", "",
+                    "editable", column_id == VS_NAME,
+                    NULL);
+      if (column_id == VS_WIDTH
+          || column_id == VS_DECIMALS
+          || column_id == VS_COLUMNS)
+        g_object_set (cell, "adjustment", NULL, NULL);
+      return;
+    }
+
+  var = psppire_dict_get_variable (var_sheet->dict, row);
+
+  print = var_get_print_format (var);
+  switch (column_id)
+    {
+    case VS_NAME:
+      g_object_set (cell,
+                    "text", var_get_name (var),
+                    "editable", TRUE,
+                    NULL);
+      break;
+
+    case VS_TYPE:
+      g_object_set (cell,
+                    "text", fmt_gui_name (print->type),
+                    "editable", FALSE,
+                    NULL);
+      break;
+
+    case VS_WIDTH:
+      {
+        int step = fmt_step_width (print->type);
+        if (var_is_numeric (var))
+          set_spin_cell (cell, print->w,
+                         fmt_min_width (print->type, var_sheet->format_use),
+                         fmt_max_width (print->type, var_sheet->format_use),
+                         step);
+        else
+          set_spin_cell (cell, print->w, 0, 0, step);
+      }
+      break;
+
+    case VS_DECIMALS:
+      if (fmt_takes_decimals (print->type))
+        {
+          int max_w = fmt_max_width (print->type, var_sheet->format_use);
+          int max_d = fmt_max_decimals (print->type, max_w,
+                                        var_sheet->format_use);
+          set_spin_cell (cell, print->d, 0, max_d, 1);
+        }
+      else
+        g_object_set (cell,
+                      "text", "",
+                      "editable", FALSE,
+                      "adjustment", NULL,
+                      NULL);
+      break;
+
+    case VS_LABEL:
+      g_object_set (cell,
+                    "text", var_has_label (var) ? var_get_label (var) : "",
+                    "editable", TRUE,
+                    NULL);
+      break;
+
+    case VS_VALUES:
+      g_object_set (cell, "editable", FALSE, NULL);
+      if ( ! var_has_value_labels (var))
+        g_object_set (cell, "text", _("None"), NULL);
+      else
+        {
+          const struct val_labs *vls = var_get_value_labels (var);
+          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);
+
+          g_object_set (cell, "text", text, NULL);
+          free (labels);
+        }
+      break;
+
+    case VS_MISSING:
+      {
+        char *text = missing_values_to_string (var_sheet->dict, var, NULL);
+        g_object_set (cell,
+                      "text", text,
+                      "editable", FALSE,
+                      NULL);
+        free (text);
+      }
+      break;
+
+    case VS_COLUMNS:
+      set_spin_cell (cell, var_get_display_width (var), 1, 2 * MAX_STRING, 1);
+      break;
+
+    case VS_ALIGN:
+      g_object_set (cell,
+                    "text", alignment_to_string (var_get_alignment (var)),
+                    "editable", TRUE,
+                    NULL);
+      break;
+
+    case VS_MEASURE:
+      g_object_set (cell,
+                    "text", measure_to_string (var_get_measure (var)),
+                    "editable", TRUE,
+                    NULL);
+      break;
+    }
+}
+
+static struct variable *
+path_string_to_variable (PsppireVarSheet *var_sheet, gchar *path_string)
 {
-  PsppireVarSheet *vs = (PsppireVarSheet *)obj;
+  PsppireDict *dict;
+  GtkTreePath *path;
+  gint row;
+
+  path = gtk_tree_path_new_from_string (path_string);
+  row = gtk_tree_path_get_indices (path)[0];
+  gtk_tree_path_free (path);
+
+  dict = psppire_var_sheet_get_dictionary (var_sheet);
+  g_return_val_if_fail (dict != NULL, NULL);
 
-  if (vs->dispose_has_run)
-    return;
+  return psppire_dict_get_variable (dict, row);
+}
 
-  /* Make sure dispose does not run twice. */
-  vs->dispose_has_run = TRUE;
+static void
+on_type_click (PsppireCellRendererButton *cell,
+               gchar *path,
+               PsppireVarSheet *var_sheet)
+{
+  var_sheet->var_type_dialog->pv = path_string_to_variable (var_sheet, path);
+  var_type_dialog_show (var_sheet->var_type_dialog);
+}
 
-  /* Chain up to the parent class */
-  G_OBJECT_CLASS (parent_class)->dispose (obj);
+static void
+on_value_labels_click (PsppireCellRendererButton *cell,
+                       gchar *path,
+                       PsppireVarSheet *var_sheet)
+{
+  struct variable *var = path_string_to_variable (var_sheet, path);
+  val_labs_dialog_set_target_variable (var_sheet->val_labs_dialog, var);
+  val_labs_dialog_show (var_sheet->val_labs_dialog);
 }
 
 static void
-psppire_var_sheet_finalize (GObject *obj)
+on_missing_values_click (PsppireCellRendererButton *cell,
+                         gchar *path,
+                         PsppireVarSheet *var_sheet)
 {
-   /* Chain up to the parent class */
-   G_OBJECT_CLASS (parent_class)->finalize (obj);
+  var_sheet->missing_val_dialog->pv = path_string_to_variable (var_sheet,
+                                                               path);
+  missing_val_dialog_show (var_sheet->missing_val_dialog);
 }
 
+static gint
+get_string_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
+                  const char *string)
+{
+  gint width;
+  g_object_set (G_OBJECT (renderer),
+                PSPPIRE_IS_CELL_RENDERER_BUTTON (renderer) ? "label" : "text",
+                string, (void *) NULL);
+  gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
+                              NULL, NULL, NULL, &width, NULL);
+  return width;
+}
+
+static gint
+get_monospace_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
+                     size_t char_cnt)
+{
+  struct string s;
+  gint width;
+
+  ds_init_empty (&s);
+  ds_put_byte_multiple (&s, '0', char_cnt);
+  ds_put_byte (&s, ' ');
+  width = get_string_width (treeview, renderer, ds_cstr (&s));
+  ds_destroy (&s);
+
+  return width;
+}
 
-struct column_parameters
+static PsppSheetViewColumn *
+add_var_sheet_column (PsppireVarSheet *var_sheet, GtkCellRenderer *renderer,
+                      enum vs_column column_id,
+                      const char *title, int width)
 {
-  gchar label[20];
-  gint width ;
-};
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
+  int title_width, content_width;
+  PsppSheetViewColumn *column;
+
+  column = pspp_sheet_view_column_new_with_attributes (title, renderer, NULL);
+  g_object_set_data (G_OBJECT (column), "column-number",
+                     GINT_TO_POINTER (column_id) + 1);
+
+  pspp_sheet_view_column_set_cell_data_func (
+    column, renderer, render_var_cell, var_sheet, NULL);
 
-#define n_ALIGNMENTS 3
+  title_width = get_string_width (sheet_view, renderer, title);
+  content_width = get_monospace_width (sheet_view, renderer, width);
+  g_object_set_data (G_OBJECT (column), "content-width",
+                     GINT_TO_POINTER (content_width));
 
-const gchar *const alignments[n_ALIGNMENTS + 1]={
-  N_("Left"),
-  N_("Right"),
-  N_("Center"),
-  0
-};
+  pspp_sheet_view_column_set_fixed_width (column,
+                                          MAX (title_width, content_width));
+  pspp_sheet_view_column_set_resizable (column, TRUE);
 
-const gchar *const measures[n_MEASURES + 1]={
-  N_("Nominal"),
-  N_("Ordinal"),
-  N_("Scale"),
-  0
-};
+  pspp_sheet_view_append_column (sheet_view, column);
 
+  g_signal_connect (renderer, "edited",
+                    G_CALLBACK (on_var_column_edited),
+                    var_sheet);
+  g_object_set_data (G_OBJECT (renderer), "column-id",
+                     GINT_TO_POINTER (column_id));
+  g_object_set_data (G_OBJECT (renderer), "var-sheet", var_sheet);
 
+  return column;
+}
+
+static PsppSheetViewColumn *
+add_text_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
+                 const char *title, int width)
+{
+  return add_var_sheet_column (var_sheet, gtk_cell_renderer_text_new (),
+                               column_id, title, width);
+}
+
+static PsppSheetViewColumn *
+add_spin_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
+                 const char *title, int width)
+{
+  return add_var_sheet_column (var_sheet, gtk_cell_renderer_spin_new (),
+                               column_id, title, width);
+}
 
-/* Create a list store from an array of strings */
-static GtkListStore *
-create_label_list (const gchar *const *labels)
+static PsppSheetViewColumn *
+add_combo_column (PsppireVarSheet *var_sheet, enum vs_column column_id,
+                  const char *title, int width,
+                  ...)
 {
-  const gchar *s;
-  gint i = 0;
+  GtkCellRenderer *cell;
+  GtkListStore *store;
+  const char *name;
+  va_list args;
+
+  store = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING);
+  va_start (args, width);
+  while ((name = va_arg (args, const char *)) != NULL)
+    {
+      int value = va_arg (args, int);
+      gtk_list_store_insert_with_values (store, NULL, G_MAXINT,
+                                         0, value,
+                                         1, name,
+                                         -1);
+    }
+  va_end (args);
+
+  cell = gtk_cell_renderer_combo_new ();
+  g_object_set (cell,
+                "has-entry", FALSE,
+                "model", GTK_TREE_MODEL (store),
+                "text-column", 1,
+                NULL);
+
+  return add_var_sheet_column (var_sheet, cell, column_id, title, width);
+
+}
+
+static void
+add_popup_menu (PsppireVarSheet *var_sheet,
+                PsppSheetViewColumn *column,
+                void (*on_click) (PsppireCellRendererButton *,
+                                  gchar *path,
+                                  PsppireVarSheet *var_sheet))
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
+  const char *button_label = "...";
+  GtkCellRenderer *button_renderer;
+  gint content_width;
+
+  button_renderer = psppire_cell_renderer_button_new ();
+  g_object_set (button_renderer,
+                "label", button_label,
+                "editable", TRUE,
+                NULL);
+  g_signal_connect (button_renderer, "clicked", G_CALLBACK (on_click),
+                    var_sheet);
+  pspp_sheet_view_column_pack_start (column, button_renderer, FALSE);
+  pspp_sheet_view_column_set_cell_data_func (
+    column, button_renderer, render_popup_cell, var_sheet, NULL);
+
+  content_width = GPOINTER_TO_INT (g_object_get_data (
+                                     G_OBJECT (column), "content-width"));
+  content_width += get_string_width (sheet_view, button_renderer,
+                                     button_label);
+  if (content_width > pspp_sheet_view_column_get_fixed_width (column))
+    pspp_sheet_view_column_set_fixed_width (column, content_width);
+}
+
+static gboolean
+get_tooltip_location (GtkWidget *widget, GtkTooltip *tooltip,
+                      gint wx, gint wy, size_t *row, size_t *column)
+{
+  PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
+  gint bx, by;
+  GtkTreePath *path;
   GtkTreeIter iter;
+  PsppSheetViewColumn *tree_column;
+  GtkTreeModel *tree_model;
+  gpointer column_ptr;
+  bool ok;
+
+  /* Check that WIDGET is really visible on the screen before we
+     do anything else.  This is a bug fix for a sticky situation:
+     when text_data_import_assistant() returns, it frees the data
+     necessary to compose the tool tip message, but there may be
+     a tool tip under preparation at that point (even if there is
+     no visible tool tip) that will call back into us a little
+     bit later.  Perhaps the correct solution to this problem is
+     to make the data related to the tool tips part of a GObject
+     that only gets destroyed when all references are released,
+     but this solution appears to be effective too. */
+  if (!gtk_widget_get_mapped (widget))
+    return FALSE;
+
+  pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
+                                                     wx, wy, &bx, &by);
+  if (!pspp_sheet_view_get_path_at_pos (tree_view, bx, by,
+                                      &path, &tree_column, NULL, NULL))
+    return FALSE;
+
+  column_ptr = g_object_get_data (G_OBJECT (tree_column), "column-number");
+  if (column_ptr == NULL)
+    return FALSE;
+  *column = GPOINTER_TO_INT (column_ptr) - 1;
+
+  pspp_sheet_view_set_tooltip_cell (tree_view, tooltip, path, tree_column,
+                                    NULL);
+
+  tree_model = pspp_sheet_view_get_model (tree_view);
+  ok = gtk_tree_model_get_iter (tree_model, &iter, path);
+  gtk_tree_path_free (path);
+  if (!ok)
+    return FALSE;
+
+  *row = GPOINTER_TO_INT (iter.user_data);
+  return TRUE;
+}
+
+static gboolean
+on_query_var_tooltip (GtkWidget *widget, gint wx, gint wy,
+                      gboolean keyboard_mode UNUSED,
+                      GtkTooltip *tooltip, gpointer *user_data UNUSED)
+{
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
+  PsppireDict *dict;
+  struct variable *var;
+  size_t row, column;
+
+  if (!get_tooltip_location (widget, tooltip, wx, wy, &row, &column))
+    return FALSE;
 
-  GtkListStore *list_store = gtk_list_store_new (1, G_TYPE_STRING);
+  dict = psppire_var_sheet_get_dictionary (var_sheet);
+  g_return_val_if_fail (dict != NULL, FALSE);
 
-  while ( (s = labels[i++]))
+  if (row >= psppire_dict_get_var_cnt (dict))
     {
-      gtk_list_store_append (list_store, &iter);
-      gtk_list_store_set (list_store, &iter,
-                         0, gettext (s),
-                         -1);
+      gtk_tooltip_set_text (tooltip, _("Enter a variable name to add a "
+                                       "new variable."));
+      return TRUE;
     }
 
-  return list_store;
+  var = psppire_dict_get_variable (dict, row);
+  g_return_val_if_fail (var != NULL, FALSE);
+
+  switch (column)
+    {
+    case VS_TYPE:
+      {
+        char text[FMT_STRING_LEN_MAX + 1];
+
+        fmt_to_string (var_get_print_format (var), text);
+        gtk_tooltip_set_text (tooltip, text);
+        return TRUE;
+      }
+
+    case VS_VALUES:
+      if (var_has_value_labels (var))
+        {
+          const struct val_labs *vls = var_get_value_labels (var);
+          const struct val_lab **labels = val_labs_sorted (vls);
+          struct string s;
+          size_t i;
+
+          ds_init_empty (&s);
+          for (i = 0; i < val_labs_count (vls); i++)
+            {
+              const struct val_lab *vl = labels[i];
+              gchar *vstr;
+
+              if (i >= 10 || ds_length (&s) > 500)
+                {
+                  ds_put_cstr (&s, "...");
+                  break;
+                }
+
+              vstr = value_to_text (vl->value, var);
+              ds_put_format (&s, _("{%s, %s}\n"), vstr,
+                             val_lab_get_escaped_label (vl));
+              free (vstr);
+
+            }
+          ds_chomp_byte (&s, '\n');
+
+          gtk_tooltip_set_text (tooltip, ds_cstr (&s));
+          ds_destroy (&s);
+
+          return TRUE;
+        }
+    }
+
+  return FALSE;
 }
 
+static void
+do_popup_menu (GtkWidget *widget, guint button, guint32 time)
+{
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (widget);
+  GtkWidget *menu;
+
+  menu = get_widget_assert (var_sheet->builder, "varsheet-variable-popup");
+  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time);
+}
+
+static void
+on_popup_menu (GtkWidget *widget, gpointer user_data UNUSED)
+{
+  do_popup_menu (widget, 0, gtk_get_current_event_time ());
+}
+
+static gboolean
+on_button_pressed (GtkWidget *widget, GdkEventButton *event,
+                   gpointer user_data UNUSED)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (widget);
+
+  if (event->type == GDK_BUTTON_PRESS && event->button == 3)
+    {
+      PsppSheetSelection *selection;
+
+      selection = pspp_sheet_view_get_selection (sheet_view);
+      if (pspp_sheet_selection_count_selected_rows (selection) <= 1)
+        {
+          GtkTreePath *path;
+
+          if (pspp_sheet_view_get_path_at_pos (sheet_view, event->x, event->y,
+                                               &path, NULL, NULL, NULL))
+            {
+              pspp_sheet_selection_unselect_all (selection);
+              pspp_sheet_selection_select_path (selection, path);
+              gtk_tree_path_free (path);
+            }
+        }
+
+      do_popup_menu (widget, event->button, event->time);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+\f
+GType
+psppire_fmt_use_get_type (void)
+{
+  static GType etype = 0;
+  if (etype == 0)
+    {
+      static const GEnumValue values[] =
+       {
+         { FMT_FOR_INPUT, "FMT_FOR_INPUT", "input" },
+         { FMT_FOR_OUTPUT, "FMT_FOR_OUTPUT", "output" },
+         { 0, NULL, NULL }
+       };
+
+      etype = g_enum_register_static
+       (g_intern_static_string ("PsppireFmtUse"), values);
+    }
+  return etype;
+}
+
+enum
+  {
+    PROP_0,
+    PROP_DICTIONARY,
+    PROP_MAY_CREATE_VARS,
+    PROP_MAY_DELETE_VARS,
+    PROP_FORMAT_TYPE,
+    PROP_UI_MANAGER
+  };
 
 static void
 psppire_var_sheet_set_property (GObject      *object,
-                                guint         property_id,
-                                const GValue *value,
-                                GParamSpec   *pspec)
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
 {
-  PsppireVarSheet *self = (PsppireVarSheet *) object;
+  PsppireVarSheet *obj = PSPPIRE_VAR_SHEET (object);
 
-  switch (property_id)
+  switch (prop_id)
     {
-    case PSPPIRE_VAR_SHEET_MAY_CREATE_VARS:
-      self->may_create_vars = g_value_get_boolean (value);
+    case PROP_DICTIONARY:
+      psppire_var_sheet_set_dictionary (obj,
+                                        PSPPIRE_DICT (g_value_get_object (
+                                                        value)));
+      break;
+
+    case PROP_MAY_CREATE_VARS:
+      psppire_var_sheet_set_may_create_vars (obj,
+                                             g_value_get_boolean (value));
       break;
 
+    case PROP_MAY_DELETE_VARS:
+      psppire_var_sheet_set_may_delete_vars (obj,
+                                             g_value_get_boolean (value));
+      break;
+
+    case PROP_FORMAT_TYPE:
+      obj->format_use = g_value_get_enum (value);
+      break;
+
+    case PROP_UI_MANAGER:
     default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
     }
 }
 
 static void
 psppire_var_sheet_get_property (GObject      *object,
-                                guint         property_id,
+                                guint         prop_id,
                                 GValue       *value,
                                 GParamSpec   *pspec)
 {
-  PsppireVarSheet *self = (PsppireVarSheet *) object;
+  PsppireVarSheet *obj = PSPPIRE_VAR_SHEET (object);
 
-  switch (property_id)
+  switch (prop_id)
     {
-    case PSPPIRE_VAR_SHEET_MAY_CREATE_VARS:
-      g_value_set_boolean (value, self->may_create_vars);
+    case PROP_DICTIONARY:
+      g_value_set_object (value,
+                          G_OBJECT (psppire_var_sheet_get_dictionary (obj)));
+      break;
+
+    case PROP_MAY_CREATE_VARS:
+      g_value_set_boolean (value, obj->may_create_vars);
+      break;
+
+    case PROP_MAY_DELETE_VARS:
+      g_value_set_boolean (value, obj->may_delete_vars);
+      break;
+
+    case PROP_FORMAT_TYPE:
+      g_value_set_enum (value, obj->format_use);
+      break;
+
+    case PROP_UI_MANAGER:
+      g_value_set_object (value, psppire_var_sheet_get_ui_manager (obj));
       break;
 
     default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
     }
 }
 
+static void
+psppire_var_sheet_realize (GtkWidget *w)
+{
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (w);
+  GtkWindow *toplevel;
+
+  GTK_WIDGET_CLASS (psppire_var_sheet_parent_class)->realize (w);
+
+  toplevel = GTK_WINDOW (gtk_widget_get_toplevel (w));
+  var_sheet->val_labs_dialog = val_labs_dialog_create (toplevel);
+  var_sheet->missing_val_dialog = missing_val_dialog_create (toplevel);
+  var_sheet->var_type_dialog = var_type_dialog_create (toplevel);
+}
 
 static void
-psppire_var_sheet_class_init (PsppireVarSheetClass *klass)
+psppire_var_sheet_destroy (GtkObject *obj)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (obj);
+
+  GTK_OBJECT_CLASS (psppire_var_sheet_parent_class)->destroy (obj);
+
+  psppire_var_sheet_set_dictionary (var_sheet, NULL);
+
+  if (var_sheet->val_labs_dialog)
+    {
+      g_object_unref (var_sheet->val_labs_dialog);
+      var_sheet->val_labs_dialog = NULL;
+    }
+
+  if (var_sheet->missing_val_dialog)
+    {
+      g_object_unref (var_sheet->missing_val_dialog);
+      var_sheet->missing_val_dialog = NULL;
+    }
+
+  if (var_sheet->var_type_dialog)
+    {
+      g_object_unref (var_sheet->var_type_dialog);
+      var_sheet->var_type_dialog = NULL;
+    }
+}
+
+static void
+psppire_var_sheet_class_init (PsppireVarSheetClass *class)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (class);
+  GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (class);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
   GParamSpec *pspec;
 
-  parent_class = g_type_class_peek_parent (klass);
+  gobject_class->set_property = psppire_var_sheet_set_property;
+  gobject_class->get_property = psppire_var_sheet_get_property;
 
-  object_class->dispose = psppire_var_sheet_dispose;
-  object_class->finalize = psppire_var_sheet_finalize;
   widget_class->realize = psppire_var_sheet_realize;
-  widget_class->unrealize = psppire_var_sheet_unrealize;
-  object_class->set_property = psppire_var_sheet_set_property;
-  object_class->get_property = psppire_var_sheet_get_property;
+
+  gtk_object_class->destroy = psppire_var_sheet_destroy;
+
+  g_signal_new ("var-double-clicked",
+                G_OBJECT_CLASS_TYPE (gobject_class),
+                G_SIGNAL_RUN_LAST,
+                0,
+                g_signal_accumulator_true_handled, NULL,
+                psppire_marshal_BOOLEAN__INT,
+                G_TYPE_BOOLEAN, 1, G_TYPE_INT);
+
+  pspec = g_param_spec_object ("dictionary",
+                               "Dictionary displayed by the sheet",
+                               "The PsppireDict that the sheet displays "
+                               "may allow the user to edit",
+                               PSPPIRE_TYPE_DICT,
+                               G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_DICTIONARY, pspec);
 
   pspec = g_param_spec_boolean ("may-create-vars",
                                 "May create variables",
                                 "Whether the user may create more variables",
                                 TRUE,
                                 G_PARAM_READWRITE);
-  g_object_class_install_property (object_class,
-                                   PSPPIRE_VAR_SHEET_MAY_CREATE_VARS,
-                                   pspec);
+  g_object_class_install_property (gobject_class, PROP_MAY_CREATE_VARS, pspec);
 
-  klass->measure_list = create_label_list (measures);
-  klass->alignment_list = create_label_list (alignments);
+  pspec = g_param_spec_boolean ("may-delete-vars",
+                                "May delete variables",
+                                "Whether the user may delete variables",
+                                TRUE,
+                                G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_MAY_DELETE_VARS, pspec);
+
+  pspec = g_param_spec_enum ("format-use",
+                             "Use of variable format",
+                             ("Whether variables have input or output "
+                              "formats"),
+                             PSPPIRE_TYPE_FMT_USE,
+                             FMT_FOR_OUTPUT,
+                             G_PARAM_READWRITE);
+  g_object_class_install_property (gobject_class, PROP_FORMAT_TYPE, pspec);
+
+  pspec = g_param_spec_object ("ui-manager",
+                               "UI Manager",
+                               "UI manager for the variable sheet.  The client should merge this UI manager with the active UI manager to obtain variable sheet specific menu items and tool bar items.",
+                               GTK_TYPE_UI_MANAGER,
+                               G_PARAM_READABLE);
+  g_object_class_install_property (gobject_class, PROP_UI_MANAGER, pspec);
 }
 
-
-
-/* Callback for when the alignment combo box
-   item is selected */
 static void
-change_alignment (GtkComboBox *cb,
-                 struct variable *var)
+render_row_number_cell (PsppSheetViewColumn *tree_column,
+                        GtkCellRenderer *cell,
+                        GtkTreeModel *model,
+                        GtkTreeIter *iter,
+                        gpointer user_data)
 {
-  gint active_item = gtk_combo_box_get_active (cb);
+  PsppireVarSheet *var_sheet = user_data;
+  GValue gvalue = { 0, };
+  gint row;
 
-  if ( active_item < 0 ) return ;
-
-  var_set_alignment (var, active_item);
-}
+  row = GPOINTER_TO_INT (iter->user_data);
 
+  g_value_init (&gvalue, G_TYPE_INT);
+  g_value_set_int (&gvalue, row + 1);
+  g_object_set_property (G_OBJECT (cell), "label", &gvalue);
+  g_value_unset (&gvalue);
 
+  if (!var_sheet->dict || row < psppire_dict_get_var_cnt (var_sheet->dict))
+    g_object_set (cell, "editable", TRUE, NULL);
+  else
+    g_object_set (cell, "editable", FALSE, NULL);
+}
 
-/* Callback for when the measure combo box
-   item is selected */
 static void
-change_measure (GtkComboBox *cb,
-               struct variable *var)
+psppire_var_sheet_row_number_double_clicked (PsppireCellRendererButton *button,
+                                             gchar *path_string,
+                                             PsppireVarSheet *var_sheet)
 {
-  gint active_item = gtk_combo_box_get_active (cb);
+  GtkTreePath *path;
 
-  if ( active_item < 0 ) return ;
+  g_return_if_fail (var_sheet->dict != NULL);
 
-  var_set_measure (var, active_item);
+  path = gtk_tree_path_new_from_string (path_string);
+  if (gtk_tree_path_get_depth (path) == 1)
+    {
+      gint *indices = gtk_tree_path_get_indices (path);
+      if (indices[0] < psppire_dict_get_var_cnt (var_sheet->dict))
+        {
+          gboolean handled;
+          g_signal_emit_by_name (var_sheet, "var-double-clicked",
+                                 indices[0], &handled);
+        }
+    }
+  gtk_tree_path_free (path);
 }
 
+static PsppSheetViewColumn *
+make_row_number_column (PsppireVarSheet *var_sheet)
+{
+  PsppSheetViewColumn *column;
+  GtkCellRenderer *renderer;
+
+  renderer = psppire_cell_renderer_button_new ();
+  g_object_set (renderer, "xalign", 1.0, NULL);
+  g_signal_connect (renderer, "double-clicked",
+                    G_CALLBACK (psppire_var_sheet_row_number_double_clicked),
+                    var_sheet);
+
+  column = pspp_sheet_view_column_new_with_attributes (_("Variable"),
+                                                       renderer, NULL);
+  pspp_sheet_view_column_set_cell_data_func (
+    column, renderer, render_row_number_cell, var_sheet, NULL);
+  pspp_sheet_view_column_set_fixed_width (column, 50);
+  return column;
+}
 
-/* Moves the focus to a new cell.
-   Returns TRUE iff the move should be disallowed */
-static gboolean
-traverse_cell_callback (PsppireSheet *sheet,
-                       const PsppireSheetCell *existing_cell,
-                       PsppireSheetCell *new_cell)
+static void
+on_edit_clear_variables (GtkAction *action, PsppireVarSheet *var_sheet)
 {
-  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (sheet);
-  PsppireVarStore *var_store = PSPPIRE_VAR_STORE (psppire_sheet_get_model (sheet));
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
+  PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
+  PsppireDict *dict = var_sheet->dict;
+  const struct range_set_node *node;
+  struct range_set *selected;
+
+  selected = pspp_sheet_selection_get_range_set (selection);
+  for (node = range_set_last (selected); node != NULL;
+       node = range_set_prev (selected, node))
+    {
+      int i;
+
+      for (i = 1; i <= range_set_node_get_width (node); i++)
+        {
+          unsigned long row = range_set_node_get_end (node) - i;
+          if (row >= 0 && row < psppire_dict_get_var_cnt (dict))
+            psppire_dict_delete_variables (dict, row, 1);
+        }
+    }
+  range_set_destroy (selected);
+}
 
-  gint n_vars = psppire_var_store_get_var_cnt (var_store);
+static void
+on_selection_changed (PsppSheetSelection *selection,
+                      gpointer user_data UNUSED)
+{
+  PsppSheetView *sheet_view = pspp_sheet_selection_get_tree_view (selection);
+  PsppireVarSheet *var_sheet = PSPPIRE_VAR_SHEET (sheet_view);
+  gint n_selected_rows;
+  gboolean may_delete;
+  GtkTreePath *path;
+  GtkAction *action;
 
-  if (new_cell->col >=  PSPPIRE_VAR_STORE_n_COLS)
-    return TRUE;
+  n_selected_rows = pspp_sheet_selection_count_selected_rows (selection);
 
-  if (new_cell->row >= n_vars && !var_sheet->may_create_vars)
-    return TRUE;
+  action = get_action_assert (var_sheet->builder, "edit_insert-variable");
+  gtk_action_set_sensitive (action, (var_sheet->may_create_vars
+                                     && n_selected_rows > 0));
 
-  if ( existing_cell->row == n_vars && new_cell->row >= n_vars)
+  switch (n_selected_rows)
     {
-      GtkEntry *entry = psppire_sheet_get_entry (sheet);
-
-      const gchar *name = gtk_entry_get_text (entry);
-
-      if (! psppire_dict_check_name (var_store->dictionary, name, TRUE))
-       return TRUE;
+    case 0:
+      may_delete = FALSE;
+      break;
 
-      psppire_dict_insert_variable (var_store->dictionary, existing_cell->row, name);
+    case 1:
+      /* The row used for inserting new variables cannot be deleted. */
+      path = gtk_tree_path_new_from_indices (
+        psppire_dict_get_var_cnt (var_sheet->dict), -1);
+      may_delete = !pspp_sheet_selection_path_is_selected (selection, path);
+      gtk_tree_path_free (path);
+      break;
 
-      return FALSE;
+    default:
+      may_delete = TRUE;
+      break;
     }
+  action = get_action_assert (var_sheet->builder, "edit_clear-variables");
+  gtk_action_set_sensitive (action, var_sheet->may_delete_vars && may_delete);
+}
+
+static void
+on_edit_insert_variable (GtkAction *action, PsppireVarSheet *var_sheet)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
+  PsppSheetSelection *selection = pspp_sheet_view_get_selection (sheet_view);
+  PsppireDict *dict = var_sheet->dict;
+  struct range_set *selected;
+  unsigned long row;
 
+  selected = pspp_sheet_selection_get_range_set (selection);
+  row = range_set_scan (selected, 0);
+  range_set_destroy (selected);
 
-  /* If the destination cell is outside the current  variables, then
-     automatically create variables for the new rows.
-  */
-  if ( ((new_cell->row > n_vars) ||
-        (new_cell->row == n_vars &&
-        new_cell->col != PSPPIRE_VAR_STORE_COL_NAME)) )
+  if (row <= psppire_dict_get_var_cnt (dict))
     {
-      gint i;
-      for ( i = n_vars ; i <= new_cell->row; ++i )
-       psppire_dict_insert_variable (var_store->dictionary, i, NULL);
+      gchar name[64];;
+      if (psppire_dict_generate_name (dict, name, sizeof name))
+        psppire_dict_insert_variable (dict, row, name);
     }
-
-  return FALSE;
 }
 
-
-
-/*
-   Callback whenever the active cell changes on the var sheet.
-*/
 static void
-var_sheet_change_active_cell (PsppireVarSheet *vs,
-                             gint row, gint column,
-                             gint oldrow, gint oldcolumn,
-                             gpointer data)
+psppire_var_sheet_init (PsppireVarSheet *obj)
 {
-  PsppireVarStore *var_store;
-  PsppireVarSheetClass *vs_class =
-    PSPPIRE_VAR_SHEET_CLASS(G_OBJECT_GET_CLASS (vs));
-
-  struct variable *var ;
-  PsppireSheet *sheet = PSPPIRE_SHEET (vs);
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (obj);
+  PsppSheetViewColumn *column;
+  GtkAction *action;
+  GList *list;
 
-  g_return_if_fail (sheet != NULL);
+  obj->dict = NULL;
+  obj->format_use = PSPPIRE_TYPE_FMT_USE;
+  obj->may_create_vars = TRUE;
+  obj->may_delete_vars = TRUE;
 
-  var_store = PSPPIRE_VAR_STORE (psppire_sheet_get_model (sheet));
+  obj->scroll_to_bottom_signal = 0;
 
-  g_assert (var_store);
+  obj->container = NULL;
 
-  g_return_if_fail (oldcolumn == PSPPIRE_VAR_STORE_COL_NAME ||
-                   row < psppire_var_store_get_var_cnt (var_store));
+  pspp_sheet_view_append_column (sheet_view, make_row_number_column (obj));
 
-  var = psppire_var_store_get_var (var_store, row);
+  column = add_text_column (obj, VS_NAME, _("Name"), 12);
+  list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
+  g_signal_connect (list->data, "editing-started",
+                    G_CALLBACK (on_name_column_editing_started), NULL);
+  g_list_free (list);
 
-  switch (column)
-    {
-    case PSPPIRE_VAR_STORE_COL_ALIGN:
-      {
-       GtkEntry *entry;
-       static GtkListStore *list_store = NULL;
-       GtkComboBoxEntry *cbe;
-       psppire_sheet_change_entry (sheet, GTK_TYPE_COMBO_BOX_ENTRY);
-       entry = psppire_sheet_get_entry (sheet);
-       cbe = GTK_COMBO_BOX_ENTRY (GTK_WIDGET (entry)->parent);
+  column = add_text_column (obj, VS_TYPE, _("Type"), 8);
+  add_popup_menu (obj, column, on_type_click);
 
-       if ( ! list_store) list_store = create_label_list (alignments);
+  add_spin_column (obj, VS_WIDTH, _("Width"), 5);
 
-       gtk_combo_box_set_model (GTK_COMBO_BOX (cbe),
-                               GTK_TREE_MODEL (vs_class->alignment_list));
+  add_spin_column (obj, VS_DECIMALS, _("Decimals"), 2);
 
-       gtk_combo_box_entry_set_text_column (cbe, 0);
-
-       g_signal_connect (cbe, "changed",
-                        G_CALLBACK (change_alignment), var);
-      }
-      break;
+  add_text_column (obj, VS_LABEL, _("Label"), 20);
 
-    case PSPPIRE_VAR_STORE_COL_MEASURE:
-      {
-       GtkEntry *entry;
-       GtkComboBoxEntry *cbe;
-       psppire_sheet_change_entry (sheet, GTK_TYPE_COMBO_BOX_ENTRY);
-       entry = psppire_sheet_get_entry (sheet);
-       cbe = GTK_COMBO_BOX_ENTRY (GTK_WIDGET (entry)->parent);
+  column = add_text_column (obj, VS_VALUES, _("Value Labels"), 20);
+  add_popup_menu (obj, column, on_value_labels_click);
 
-       gtk_combo_box_set_model (GTK_COMBO_BOX (cbe),
-                               GTK_TREE_MODEL (vs_class->measure_list));
+  column = add_text_column (obj, VS_MISSING, _("Missing Values"), 20);
+  add_popup_menu (obj, column, on_missing_values_click);
 
-       gtk_combo_box_entry_set_text_column (cbe, 0);
+  add_spin_column (obj, VS_COLUMNS, _("Columns"), 3);
 
-       g_signal_connect (cbe, "changed",
-                         G_CALLBACK (change_measure), var);
-      }
-      break;
+  add_combo_column (obj, VS_ALIGN, _("Align"), 6,
+                    alignment_to_string (ALIGN_LEFT), ALIGN_LEFT,
+                    alignment_to_string (ALIGN_CENTRE), ALIGN_CENTRE,
+                    alignment_to_string (ALIGN_RIGHT), ALIGN_RIGHT,
+                    NULL);
 
-    case PSPPIRE_VAR_STORE_COL_VALUES:
-      {
-       PsppireCustomEntry *customEntry;
+  add_combo_column (obj, VS_MEASURE, _("Measure"), 10,
+                    measure_to_string (MEASURE_NOMINAL), MEASURE_NOMINAL,
+                    measure_to_string (MEASURE_ORDINAL), MEASURE_ORDINAL,
+                    measure_to_string (MEASURE_SCALE), MEASURE_SCALE,
+                    NULL);
 
-       psppire_sheet_change_entry (sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
+  pspp_sheet_view_set_rubber_banding (sheet_view, TRUE);
+  pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (sheet_view),
+                                 PSPP_SHEET_SELECTION_MULTIPLE);
 
-       customEntry =
-         PSPPIRE_CUSTOM_ENTRY (psppire_sheet_get_entry (sheet));
+  g_object_set (G_OBJECT (obj), "has-tooltip", TRUE, NULL);
+  g_signal_connect (obj, "query-tooltip",
+                    G_CALLBACK (on_query_var_tooltip), NULL);
+  g_signal_connect (obj, "button-press-event",
+                    G_CALLBACK (on_button_pressed), NULL);
+  g_signal_connect (obj, "popup-menu", G_CALLBACK (on_popup_menu), NULL);
 
-       val_labs_dialog_set_target_variable (vs->val_labs_dialog, var);
+  obj->builder = builder_new ("var-sheet.ui");
 
-       g_signal_connect_swapped (customEntry,
-                                 "clicked",
-                                 G_CALLBACK (val_labs_dialog_show),
-                                 vs->val_labs_dialog);
-      }
-      break;
+  action = get_action_assert (obj->builder, "edit_clear-variables");
+  g_signal_connect (action, "activate", G_CALLBACK (on_edit_clear_variables),
+                    obj);
+  gtk_action_set_sensitive (action, FALSE);
+  g_signal_connect (pspp_sheet_view_get_selection (sheet_view),
+                    "changed", G_CALLBACK (on_selection_changed), NULL);
 
-    case PSPPIRE_VAR_STORE_COL_MISSING:
-      {
-       PsppireCustomEntry *customEntry;
+  action = get_action_assert (obj->builder, "edit_insert-variable");
+  gtk_action_set_sensitive (action, FALSE);
+  g_signal_connect (action, "activate", G_CALLBACK (on_edit_insert_variable),
+                    obj);
+}
 
-       psppire_sheet_change_entry (sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
+GtkWidget *
+psppire_var_sheet_new (void)
+{
+  return g_object_new (PSPPIRE_VAR_SHEET_TYPE, NULL);
+}
 
-       customEntry =
-         PSPPIRE_CUSTOM_ENTRY (psppire_sheet_get_entry (sheet));
+PsppireDict *
+psppire_var_sheet_get_dictionary (PsppireVarSheet *var_sheet)
+{
+  return var_sheet->dict;
+}
 
-       vs->missing_val_dialog->pv =
-         psppire_var_store_get_var (var_store, row);
+static void
+refresh_model (PsppireVarSheet *var_sheet)
+{
+  pspp_sheet_view_set_model (PSPP_SHEET_VIEW (var_sheet), NULL);
 
-       g_signal_connect_swapped (customEntry,
-                                 "clicked",
-                                 G_CALLBACK (missing_val_dialog_show),
-                                 vs->missing_val_dialog);
-      }
-      break;
+  if (var_sheet->dict != NULL)
+    {
+      PsppireEmptyListStore *store;
+      int n_rows;
+
+      n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
+                + var_sheet->may_create_vars);
+      store = psppire_empty_list_store_new (n_rows);
+      pspp_sheet_view_set_model (PSPP_SHEET_VIEW (var_sheet),
+                                 GTK_TREE_MODEL (store));
+      g_object_unref (store);
+    }
+}
 
-    case PSPPIRE_VAR_STORE_COL_TYPE:
-      {
-       PsppireCustomEntry *customEntry;
+static void
+on_var_inserted (PsppireDict *dict, glong row, PsppireVarSheet *var_sheet)
+{
+  PsppireEmptyListStore *store;
+  int n_rows;
 
-       psppire_sheet_change_entry (sheet, PSPPIRE_CUSTOM_ENTRY_TYPE);
+  g_return_if_fail (dict == var_sheet->dict);
 
-       customEntry =
-         PSPPIRE_CUSTOM_ENTRY (psppire_sheet_get_entry (sheet));
+  store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
+                                      PSPP_SHEET_VIEW (var_sheet)));
+  g_return_if_fail (store != NULL);
 
+  n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
+            + var_sheet->may_create_vars);
+  psppire_empty_list_store_set_n_rows (store, n_rows);
+  psppire_empty_list_store_row_inserted (store, row);
+}
 
-       /* Popup the Variable Type dialog box */
-       vs->var_type_dialog->pv = var;
+static void
+on_var_deleted (PsppireDict *dict,
+                const struct variable *var, int dict_idx, int case_idx,
+                PsppireVarSheet *var_sheet)
+{
+  PsppireEmptyListStore *store;
+  int n_rows;
 
-       g_signal_connect_swapped (customEntry,
-                                "clicked",
-                                G_CALLBACK (var_type_dialog_show),
-                                 vs->var_type_dialog);
-      }
-      break;
+  g_return_if_fail (dict == var_sheet->dict);
 
-    case PSPPIRE_VAR_STORE_COL_WIDTH:
-    case PSPPIRE_VAR_STORE_COL_DECIMALS:
-    case PSPPIRE_VAR_STORE_COL_COLUMNS:
-      {
-       if ( psppire_sheet_model_is_editable (PSPPIRE_SHEET_MODEL(var_store),
-                                             row, column))
-         {
-           gint r_min, r_max;
-
-           const gchar *s = psppire_sheet_cell_get_text (sheet, row, column);
-
-           if (s)
-             {
-               GtkSpinButton *spinButton ;
-               const gint current_value  = g_strtod (s, NULL);
-               GtkObject *adj ;
-
-               const struct fmt_spec *fmt = var_get_print_format (var);
-               switch (column)
-                 {
-                 case PSPPIRE_VAR_STORE_COL_WIDTH:
-                   r_min = MAX (fmt->d + 1, fmt_min_output_width (fmt->type));
-                   r_max = fmt_max_output_width (fmt->type);
-                   break;
-                 case PSPPIRE_VAR_STORE_COL_DECIMALS:
-                   r_min = 0 ;
-                   r_max = fmt_max_output_decimals (fmt->type, fmt->w);
-                   break;
-                 case PSPPIRE_VAR_STORE_COL_COLUMNS:
-                   r_min = 1;
-                   r_max = 255 ; /* Is this a sensible value ? */
-                   break;
-                 default:
-                   g_assert_not_reached ();
-                 }
-
-               adj = gtk_adjustment_new (current_value,
-                                         r_min, r_max,
-                                         1.0, 1.0, /* steps */
-                                         0);
-
-               psppire_sheet_change_entry (sheet, GTK_TYPE_SPIN_BUTTON);
-
-               spinButton =
-                 GTK_SPIN_BUTTON (psppire_sheet_get_entry (sheet));
-
-               gtk_spin_button_set_adjustment (spinButton, GTK_ADJUSTMENT (adj));
-               gtk_spin_button_set_digits (spinButton, 0);
-             }
-         }
-      }
-      break;
+  store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
+                                      PSPP_SHEET_VIEW (var_sheet)));
+  g_return_if_fail (store != NULL);
 
-    default:
-      psppire_sheet_change_entry (sheet, GTK_TYPE_ENTRY);
-      break;
-    }
+  n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
+            + var_sheet->may_create_vars);
+  psppire_empty_list_store_set_n_rows (store, n_rows);
+  psppire_empty_list_store_row_deleted (store, dict_idx);
 }
 
-
 static void
-psppire_var_sheet_realize (GtkWidget *w)
+on_backend_changed (PsppireDict *dict, PsppireVarSheet *var_sheet)
+{
+  g_return_if_fail (dict == var_sheet->dict);
+  refresh_model (var_sheet);
+}
+
+void
+psppire_var_sheet_set_dictionary (PsppireVarSheet *var_sheet,
+                                  PsppireDict *dict)
 {
-  PsppireVarSheet *vs = PSPPIRE_VAR_SHEET (w);
+  enum {
+    BACKEND_CHANGED,
+    VARIABLE_INSERTED,
+    VARIABLE_DELETED,
+    N_SIGNALS
+  };
 
-  GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (vs));
+  if (var_sheet->dict != NULL)
+    {
+      if (var_sheet->dict_signals)
+        {
+          int i;
+
+          for (i = 0; i < N_SIGNALS; i++)
+            g_signal_handler_disconnect (var_sheet->dict,
+                                         var_sheet->dict_signals[i]);
+
+          g_free (var_sheet->dict_signals);
+          var_sheet->dict_signals = NULL;
+        }
+      g_object_unref (var_sheet->dict);
+    }
 
-  vs->val_labs_dialog = val_labs_dialog_create (GTK_WINDOW (toplevel));
+  var_sheet->dict = dict;
 
-  vs->missing_val_dialog = missing_val_dialog_create (GTK_WINDOW (toplevel));
-  vs->var_type_dialog = var_type_dialog_create (GTK_WINDOW (toplevel));
+  if (dict != NULL)
+    {
+      g_object_ref (dict);
 
-  /* Chain up to the parent class */
-  GTK_WIDGET_CLASS (parent_class)->realize (w);
-}
+      var_sheet->dict_signals = g_malloc0 (
+        N_SIGNALS * sizeof *var_sheet->dict_signals);
 
-static void
-psppire_var_sheet_unrealize (GtkWidget *w)
-{
-  PsppireVarSheet *vs = PSPPIRE_VAR_SHEET (w);
+      var_sheet->dict_signals[BACKEND_CHANGED]
+        = g_signal_connect (dict, "backend-changed",
+                            G_CALLBACK (on_backend_changed), var_sheet);
 
-  g_free (vs->val_labs_dialog);
-  g_free (vs->missing_val_dialog);
-  g_free (vs->var_type_dialog);
+      var_sheet->dict_signals[VARIABLE_DELETED]
+        = g_signal_connect (dict, "variable-inserted",
+                            G_CALLBACK (on_var_inserted), var_sheet);
 
-  /* Chain up to the parent class */
-  GTK_WIDGET_CLASS (parent_class)->unrealize (w);
+      var_sheet->dict_signals[VARIABLE_INSERTED]
+        = g_signal_connect (dict, "variable-deleted",
+                            G_CALLBACK (on_var_deleted), var_sheet);
+    }
+  refresh_model (var_sheet);
 }
 
+gboolean
+psppire_var_sheet_get_may_create_vars (PsppireVarSheet *var_sheet)
+{
+  return var_sheet->may_create_vars;
+}
 
-
-static void
-psppire_var_sheet_init (PsppireVarSheet *vs)
+void
+psppire_var_sheet_set_may_create_vars (PsppireVarSheet *var_sheet,
+                                       gboolean may_create_vars)
 {
-  GtkBuilder *builder = builder_new ("data-editor.ui");
+  if (var_sheet->may_create_vars != may_create_vars)
+    {
+      PsppireEmptyListStore *store;
+      gint n_rows;
 
-  connect_help (builder);
+      var_sheet->may_create_vars = may_create_vars;
 
-  g_object_unref (builder);
+      store = PSPPIRE_EMPTY_LIST_STORE (pspp_sheet_view_get_model (
+                                          PSPP_SHEET_VIEW (var_sheet)));
+      g_return_if_fail (store != NULL);
 
-  vs->dispose_has_run = FALSE;
-  vs->may_create_vars = TRUE;
+      n_rows = (psppire_dict_get_var_cnt (var_sheet->dict)
+                + var_sheet->may_create_vars);
+      psppire_empty_list_store_set_n_rows (store, n_rows);
 
-  g_signal_connect (vs, "activate",
-                   G_CALLBACK (var_sheet_change_active_cell),
-                   NULL);
+      if (may_create_vars)
+        psppire_empty_list_store_row_inserted (store, n_rows - 1);
+      else
+        psppire_empty_list_store_row_deleted (store, n_rows);
 
-  g_signal_connect (vs, "traverse",
-                   G_CALLBACK (traverse_cell_callback), NULL);
+      on_selection_changed (pspp_sheet_view_get_selection (
+                              PSPP_SHEET_VIEW (var_sheet)), NULL);
+    }
 }
 
-
-static const struct column_parameters column_def[] = {
-  { N_("Name"),    80},
-  { N_("Type"),    100},
-  { N_("Width"),   57},
-  { N_("Decimals"),91},
-  { N_("Label"),   95},
-  { N_("Values"),  103},
-  { N_("Missing"), 95},
-  { N_("Columns"), 80},
-  { N_("Align"),   69},
-  { N_("Measure"), 99},
-};
-
-GtkWidget*
-psppire_var_sheet_new (void)
+gboolean
+psppire_var_sheet_get_may_delete_vars (PsppireVarSheet *var_sheet)
 {
-  gint i;
-  PsppireAxis *ha = psppire_axis_new ();
-  PsppireAxis *va = psppire_axis_new ();
-
-  GtkWidget *w = g_object_new (psppire_var_sheet_get_type (), NULL);
-
-  for (i = 0 ; i < 10 ; ++i)
-    psppire_axis_append (ha, column_def[i].width);
+  return var_sheet->may_delete_vars;
+}
 
-  g_object_set (va,
-               "default-size", 25,
-               NULL);
+void
+psppire_var_sheet_set_may_delete_vars (PsppireVarSheet *var_sheet,
+                                       gboolean may_delete_vars)
+{
+  if (var_sheet->may_delete_vars != may_delete_vars)
+    {
+      var_sheet->may_delete_vars = may_delete_vars;
+      on_selection_changed (pspp_sheet_view_get_selection (
+                              PSPP_SHEET_VIEW (var_sheet)), NULL);
+    }
+}
 
-  g_object_set (ha, "minimum-extent", 0,
-               NULL);
+void
+psppire_var_sheet_goto_variable (PsppireVarSheet *var_sheet, int dict_index)
+{
+  PsppSheetView *sheet_view = PSPP_SHEET_VIEW (var_sheet);
+  GtkTreePath *path;
 
-  g_object_set (w,
-               "horizontal-axis", ha,
-               "vertical-axis", va,
-               NULL);
+  path = gtk_tree_path_new_from_indices (dict_index, -1);
+  pspp_sheet_view_scroll_to_cell (sheet_view, path, NULL, FALSE, 0.0, 0.0);
+  pspp_sheet_view_set_cursor (sheet_view, path, NULL, FALSE);
+  gtk_tree_path_free (path);
+}
 
-  return w;
+GtkUIManager *
+psppire_var_sheet_get_ui_manager (PsppireVarSheet *var_sheet)
+{
+  return GTK_UI_MANAGER (get_object_assert (var_sheet->builder,
+                                            "var_sheet_uim",
+                                            GTK_TYPE_UI_MANAGER));
 }
+
index b996275c70404e94ea09e4c79a046d2783796dcb..97efcf456ad1a2450c6192a8f608f0889dd6ce05 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (C) 2008 Free Software Foundation, Inc.
+   Copyright (C) 2008, 2011, 2012 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
 #ifndef __PSPPIRE_VAR_SHEET_H__
 #define __PSPPIRE_VAR_SHEET_H__
 
+/* PsppireVarSheet is a PsppSheetView that displays the variables in a
+   dictionary, one variable per row.
 
-#include <glib.h>
-#include <glib-object.h>
-#include <gtk-contrib/psppire-sheet.h>
-#include "val-labs-dialog.h"
-#include "missing-val-dialog.h"
-#include "var-type-dialog.h"
+   PsppireDataSheet is usually a child of PsppireDataEditor in the widget
+   hierarchy.  Other widgets can also use it. */
+
+#include <gtk/gtk.h>
+#include "data/format.h"
+#include "ui/gui/pspp-sheet-view.h"
 
 
 G_BEGIN_DECLS
 
+#define PSPPIRE_TYPE_FMT_USE (psppire_fmt_use_get_type ())
+
+GType psppire_fmt_use_get_type (void) G_GNUC_CONST;
+\f
 #define PSPPIRE_VAR_SHEET_TYPE            (psppire_var_sheet_get_type ())
 #define PSPPIRE_VAR_SHEET(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), PSPPIRE_VAR_SHEET_TYPE, PsppireVarSheet))
 #define PSPPIRE_VAR_SHEET_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), PSPPIRE_VAR_SHEET_TYPE, PsppireVarSheetClass))
@@ -40,34 +46,48 @@ typedef struct _PsppireVarSheetClass  PsppireVarSheetClass;
 
 struct _PsppireVarSheet
 {
-  PsppireSheet parent;
+  PsppSheetView parent;
 
-  gboolean dispose_has_run;
   gboolean may_create_vars;
+  gboolean may_delete_vars;
+  enum fmt_use format_use;
 
-  struct val_labs_dialog *val_labs_dialog ;
-  struct missing_val_dialog *missing_val_dialog ;
-  struct var_type_dialog *var_type_dialog ;
-};
+  struct _PsppireDict *dict;
+  struct val_labs_dialog *val_labs_dialog;
+  struct missing_val_dialog *missing_val_dialog;
+  struct var_type_dialog *var_type_dialog;
 
+  gulong scroll_to_bottom_signal;
+  gulong *dict_signals;
 
-struct _PsppireVarSheetClass
-{
-  PsppireSheetClass parent_class;
-
-  GtkListStore *alignment_list;
-  GtkListStore *measure_list;
+  GtkBuilder *builder;
 
-  void (*var_sheet)(PsppireVarSheet*);
+  GtkWidget *container;
+  gulong on_switch_page_handler;
 };
 
+struct _PsppireVarSheetClass
+{
+  PsppSheetViewClass parent_class;
+};
 
 GType          psppire_var_sheet_get_type        (void);
 GtkWidget*     psppire_var_sheet_new             (void);
 
-G_END_DECLS
+struct _PsppireDict *psppire_var_sheet_get_dictionary (PsppireVarSheet *);
+void psppire_var_sheet_set_dictionary (PsppireVarSheet *,
+                                       struct _PsppireDict *);
 
+gboolean psppire_var_sheet_get_may_create_vars (PsppireVarSheet *);
+void psppire_var_sheet_set_may_create_vars (PsppireVarSheet *, gboolean);
 
+gboolean psppire_var_sheet_get_may_delete_vars (PsppireVarSheet *);
+void psppire_var_sheet_set_may_delete_vars (PsppireVarSheet *, gboolean);
 
+void psppire_var_sheet_goto_variable (PsppireVarSheet *, int dict_index);
+
+GtkUIManager *psppire_var_sheet_get_ui_manager (PsppireVarSheet *);
+
+G_END_DECLS
 
 #endif /* __PSPPIRE_VAR_SHEET_H__ */
index bc348efa22db9dfc2bb19a3e438d15c018225cc0..f76d1b9c7b827fb8b9717a949fd77f0a04c399fb 100644 (file)
@@ -25,8 +25,6 @@
 
 #include <gobject/gvaluecollector.h>
 
-#include <ui/gui/sheet/psppire-sheetmodel.h>
-
 #include "psppire-var-store.h"
 #include "helper.h"
 
 
 #include "var-display.h"
 
-static void
-var_change_callback (GtkWidget *w, gint n, gpointer data)
-{
-  PsppireSheetModel *model = PSPPIRE_SHEET_MODEL (data);
-
-  psppire_sheet_model_range_changed (model,
-                                n, 0, n, PSPPIRE_VAR_STORE_n_COLS);
-}
-
-
-static void
-var_delete_callback (GtkWidget *w, const struct variable *var UNUSED,
-                     gint dict_idx, gint case_idx UNUSED, gpointer data)
-{
-  PsppireSheetModel *model = PSPPIRE_SHEET_MODEL (data);
-
-  psppire_sheet_model_rows_deleted (model, dict_idx, 1);
-}
-
-
-
-static void
-var_insert_callback (GtkWidget *w, glong row, gpointer data)
-{
-  PsppireSheetModel *model = PSPPIRE_SHEET_MODEL (data);
-
-  psppire_sheet_model_rows_inserted (model, row, 1);
-}
-
-static void
-refresh (PsppireDict  *d, gpointer data)
-{
-  PsppireVarStore *vs = data;
-
-  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (vs), -1, -1, -1, -1);
-}
-
 enum
   {
     PROP_0,
-    PSPPIRE_VAR_STORE_FORMAT_TYPE,
     PSPPIRE_VAR_STORE_DICT
   };
 
 static void         psppire_var_store_init            (PsppireVarStore      *var_store);
 static void         psppire_var_store_class_init      (PsppireVarStoreClass *class);
-static void         psppire_var_store_sheet_model_init (PsppireSheetModelIface *iface);
 static void         psppire_var_store_finalize        (GObject           *object);
 static void         psppire_var_store_dispose        (GObject           *object);
 
 
-static gchar *psppire_var_store_get_string (const PsppireSheetModel *sheet_model, glong row, glong column);
-
-static gboolean  psppire_var_store_clear (PsppireSheetModel *model,  glong row, glong col);
-
-
-static gboolean psppire_var_store_set_string (PsppireSheetModel *model,
-                                         const gchar *text, glong row, glong column);
-
-static glong psppire_var_store_get_row_count (const PsppireSheetModel * model);
-static glong psppire_var_store_get_column_count (const PsppireSheetModel * model);
-
-static gchar *text_for_column (PsppireVarStore *vs, const struct variable *pv,
-                              gint c, GError **err);
-
-
 static GObjectClass *parent_class = NULL;
 
-GType
-psppire_var_store_format_type_get_type (void)
-{
-  static GType etype = 0;
-  if (etype == 0)
-    {
-      static const GEnumValue values[] =
-       {
-         { PSPPIRE_VAR_STORE_INPUT_FORMATS,
-            "PSPPIRE_VAR_STORE_INPUT_FORMATS",
-            "input" },
-         { PSPPIRE_VAR_STORE_OUTPUT_FORMATS,
-            "PSPPIRE_VAR_STORE_OUTPUT_FORMATS",
-            "output" },
-         { 0, NULL, NULL }
-       };
-
-      etype = g_enum_register_static
-       (g_intern_static_string ("PsppireVarStoreFormatType"), values);
-
-    }
-  return etype;
-}
-
 GType
 psppire_var_store_get_type (void)
 {
@@ -153,18 +73,7 @@ psppire_var_store_get_type (void)
         (GInstanceInitFunc) psppire_var_store_init,
       };
 
-      static const GInterfaceInfo sheet_model_info =
-      {
-       (GInterfaceInitFunc) psppire_var_store_sheet_model_init,
-       NULL,
-       NULL
-      };
-
       var_store_type = g_type_register_static (G_TYPE_OBJECT, "PsppireVarStore", &var_store_info, 0);
-
-      g_type_add_interface_static (var_store_type,
-                                  PSPPIRE_TYPE_SHEET_MODEL,
-                                  &sheet_model_info);
     }
 
   return var_store_type;
@@ -180,29 +89,10 @@ psppire_var_store_set_property (GObject      *object,
 
   switch (property_id)
     {
-    case PSPPIRE_VAR_STORE_FORMAT_TYPE:
-      self->format_type = g_value_get_enum (value);
-      break;
-
     case PSPPIRE_VAR_STORE_DICT:
       if ( self->dictionary)
        g_object_unref (self->dictionary);
       self->dictionary = g_value_dup_object (value);
-      g_signal_connect (self->dictionary, "variable-changed", G_CALLBACK (var_change_callback),
-                       self);
-
-      g_signal_connect (self->dictionary, "variable-deleted", G_CALLBACK (var_delete_callback),
-                       self);
-
-      g_signal_connect (self->dictionary, "variable-inserted",
-                       G_CALLBACK (var_insert_callback), self);
-
-      g_signal_connect (self->dictionary, "backend-changed", G_CALLBACK (refresh),
-                       self);
-
-      /* The entire model has changed */
-      psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (self), -1, -1, -1, -1);
-
       break;
 
     default:
@@ -221,10 +111,6 @@ psppire_var_store_get_property (GObject      *object,
 
   switch (property_id)
     {
-    case PSPPIRE_VAR_STORE_FORMAT_TYPE:
-      g_value_set_enum (value, self->format_type);
-      break;
-
     case PSPPIRE_VAR_STORE_DICT:
       g_value_take_object (value, self->dictionary);
       break;
@@ -240,7 +126,6 @@ static void
 psppire_var_store_class_init (PsppireVarStoreClass *class)
 {
   GObjectClass *object_class;
-  GParamSpec *format_pspec;
   GParamSpec *dict_pspec;
 
   parent_class = g_type_class_peek_parent (class);
@@ -251,18 +136,6 @@ psppire_var_store_class_init (PsppireVarStoreClass *class)
   object_class->set_property = psppire_var_store_set_property;
   object_class->get_property = psppire_var_store_get_property;
 
-  format_pspec = g_param_spec_enum ("format-type",
-                             "Variable format type",
-                             ("Whether variables have input or output "
-                              "formats"),
-                             PSPPIRE_TYPE_VAR_STORE_FORMAT_TYPE,
-                             PSPPIRE_VAR_STORE_OUTPUT_FORMATS,
-                             G_PARAM_READWRITE);
-
-  g_object_class_install_property (object_class,
-                                   PSPPIRE_VAR_STORE_FORMAT_TYPE,
-                                   format_pspec);
-
   dict_pspec = g_param_spec_object ("dictionary",
                                    "Dictionary",
                                    "The PsppireDict represented by this var store",
@@ -274,111 +147,18 @@ psppire_var_store_class_init (PsppireVarStoreClass *class)
                                    dict_pspec);
 }
 
-#define DISABLED_COLOR "gray"
-
 static void
 psppire_var_store_init (PsppireVarStore *var_store)
 {
-  if ( ! gdk_color_parse (DISABLED_COLOR, &var_store->disabled))
-       g_critical ("Could not parse color `%s'", DISABLED_COLOR);
-
   var_store->dictionary = NULL;
-  var_store->format_type = PSPPIRE_VAR_STORE_OUTPUT_FORMATS;
 }
 
-static gboolean
-psppire_var_store_item_editable (PsppireVarStore *var_store, glong row, glong column)
-{
-  const struct fmt_spec *write_spec ;
-
-  struct variable *pv = psppire_var_store_get_var (var_store, row);
-
-  if ( !pv )
-    return TRUE;
-
-  if ( var_is_alpha (pv) && column == PSPPIRE_VAR_STORE_COL_DECIMALS )
-    return FALSE;
-
-  write_spec = var_get_print_format (pv);
-
-  switch ( write_spec->type )
-    {
-    case FMT_DATE:
-    case FMT_EDATE:
-    case FMT_SDATE:
-    case FMT_ADATE:
-    case FMT_JDATE:
-    case FMT_QYR:
-    case FMT_MOYR:
-    case FMT_WKYR:
-    case FMT_DATETIME:
-    case FMT_TIME:
-    case FMT_DTIME:
-    case FMT_WKDAY:
-    case FMT_MONTH:
-      if ( column == PSPPIRE_VAR_STORE_COL_DECIMALS || column == PSPPIRE_VAR_STORE_COL_WIDTH)
-       return FALSE;
-      break;
-    default:
-      break;
-    }
-
-  return TRUE;
-}
-
-
 struct variable *
 psppire_var_store_get_var (PsppireVarStore *store, glong row)
 {
   return psppire_dict_get_variable (store->dictionary, row);
 }
 
-static gboolean
-psppire_var_store_is_editable (const PsppireSheetModel *model, glong row, glong column)
-{
-  PsppireVarStore *store = PSPPIRE_VAR_STORE (model);
-  return psppire_var_store_item_editable (store, row, column);
-}
-
-
-static GdkColor *
-psppire_var_store_get_foreground (const PsppireSheetModel *model, glong row, glong column)
-{
-  PsppireVarStore *store = PSPPIRE_VAR_STORE (model);
-
-  if ( ! psppire_var_store_item_editable (store, row, column) )
-    return &store->disabled;
-
-  return NULL;
-}
-
-
-static gchar *get_column_title (const PsppireSheetModel *model, gint col);
-static gchar *get_row_title (const PsppireSheetModel *model, gint row);
-static gboolean get_row_sensitivity (const PsppireSheetModel *model, gint row);
-
-static void
-psppire_var_store_sheet_model_init (PsppireSheetModelIface *iface)
-{
-  iface->get_row_count = psppire_var_store_get_row_count;
-  iface->get_column_count = psppire_var_store_get_column_count;
-  iface->free_strings = TRUE;
-  iface->get_string = psppire_var_store_get_string;
-  iface->set_string = psppire_var_store_set_string;
-  iface->clear_datum = psppire_var_store_clear;
-  iface->is_editable = psppire_var_store_is_editable;
-  iface->get_foreground = psppire_var_store_get_foreground;
-  iface->get_background = NULL;
-  iface->get_justification = NULL;
-
-  iface->get_column_title = get_column_title;
-
-  iface->get_row_title = get_row_title;
-  iface->get_row_sensitivity = get_row_sensitivity;
-
-  iface->get_row_overstrike = NULL;
-}
-
 /**
  * psppire_var_store_new:
  * @dict: The dictionary for this var_store.
@@ -398,39 +178,6 @@ psppire_var_store_new (PsppireDict *dict)
   return retval;
 }
 
-#if 0
-/**
- * psppire_var_store_replace_set_dictionary:
- * @var_store: The variable store
- * @dict: The dictionary to set
- *
- * If a dictionary is already associated with the var-store, then it will be
- * destroyed.
- **/
-void
-psppire_var_store_set_dictionary (PsppireVarStore *var_store, PsppireDict *dict)
-{
-  if ( var_store->dict ) g_object_unref (var_store->dict);
-
-  var_store->dict = dict;
-
-  g_signal_connect (dict, "variable-changed", G_CALLBACK (var_change_callback),
-                  var_store);
-
-  g_signal_connect (dict, "variable-deleted", G_CALLBACK (var_delete_callback),
-                  var_store);
-
-  g_signal_connect (dict, "variable-inserted",
-                   G_CALLBACK (var_insert_callback), var_store);
-
-  g_signal_connect (dict, "backend-changed", G_CALLBACK (refresh),
-                   var_store);
-
-  /* The entire model has changed */
-  psppire_sheet_model_range_changed (PSPPIRE_SHEET_MODEL (var_store), -1, -1, -1, -1);
-}
-#endif
-
 static void
 psppire_var_store_finalize (GObject *object)
 {
@@ -451,338 +198,9 @@ psppire_var_store_dispose (GObject *object)
 }
 
 
-static gchar *
-psppire_var_store_get_string (const PsppireSheetModel *model,
-                             glong row, glong column)
-{
-  PsppireVarStore *store = PSPPIRE_VAR_STORE (model);
-
-  struct variable *pv;
-
-  if ( row >= psppire_dict_get_var_cnt (store->dictionary))
-    return 0;
-
-  pv = psppire_dict_get_variable (store->dictionary, row);
-
-  return text_for_column (store, pv, column, 0);
-}
-
-
-/* Clears that part of the variable store, if possible, which corresponds
-   to ROW, COL.
-   Returns true if anything was updated, false otherwise.
-*/
-static gboolean
-psppire_var_store_clear (PsppireSheetModel *model,  glong row, glong col)
-{
-  struct variable *pv ;
-
-  PsppireVarStore *var_store = PSPPIRE_VAR_STORE (model);
-
-  if ( row >= psppire_dict_get_var_cnt (var_store->dictionary))
-      return FALSE;
-
-  pv = psppire_var_store_get_var (var_store, row);
-
-  if ( !pv )
-    return FALSE;
-
-  switch (col)
-    {
-    case PSPPIRE_VAR_STORE_COL_LABEL:
-      var_clear_label (pv);
-      return TRUE;
-      break;
-    }
-
-  return FALSE;
-}
-
-/* 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_var_store_set_string (PsppireSheetModel *model,
-                         const gchar *text, glong row, glong col)
-{
-  struct variable *pv ;
-
-  PsppireVarStore *var_store = PSPPIRE_VAR_STORE (model);
-
-  if ( row >= psppire_dict_get_var_cnt (var_store->dictionary))
-      return FALSE;
-
-  pv = psppire_var_store_get_var (var_store, row);
-
-  if ( !pv )
-    return FALSE;
-
-  switch (col)
-    {
-    case PSPPIRE_VAR_STORE_COL_NAME:
-      {
-       gboolean ok;
-       ok =  psppire_dict_rename_var (var_store->dictionary, pv, text);
-       return ok;
-      }
-    case PSPPIRE_VAR_STORE_COL_COLUMNS:
-      if ( ! text) return FALSE;
-      var_set_display_width (pv, atoi (text));
-      return TRUE;
-      break;
-    case PSPPIRE_VAR_STORE_COL_WIDTH:
-      {
-       const int width = atoi (text);
-       if ( ! text)
-         return FALSE;
-
-       if (width < 0)
-         return FALSE;
-
-       if ( var_is_alpha (pv))
-         {
-           if ( width > MAX_STRING )
-             return FALSE;
-           var_set_width (pv, width);
-         }
-       else
-         {
-            bool for_input
-              = var_store->format_type == PSPPIRE_VAR_STORE_INPUT_FORMATS;
-           struct fmt_spec fmt ;
-           fmt = *var_get_print_format (pv);
-           if ( width < fmt_min_width (fmt.type, for_input)
-                ||
-                width > fmt_max_width (fmt.type, for_input))
-             return FALSE;
-
-           fmt.w = width;
-           fmt.d = MIN (fmt_max_decimals (fmt.type, width, for_input), fmt.d);
-
-           var_set_both_formats (pv, &fmt);
-         }
-
-       return TRUE;
-      }
-      break;
-    case PSPPIRE_VAR_STORE_COL_DECIMALS:
-      {
-        bool for_input
-          = var_store->format_type == PSPPIRE_VAR_STORE_INPUT_FORMATS;
-       int decimals;
-       struct fmt_spec fmt;
-       if ( ! text) return FALSE;
-       decimals = atoi (text);
-       fmt = *var_get_print_format (pv);
-       if ( decimals >
-            fmt_max_decimals (fmt.type,
-                               fmt.w,
-                               for_input
-                               ))
-         return FALSE;
-
-       fmt.d = decimals;
-       var_set_both_formats (pv, &fmt);
-       return TRUE;
-      }
-      break;
-    case PSPPIRE_VAR_STORE_COL_LABEL:
-      {
-       var_set_label (pv, text, true);
-       return TRUE;
-      }
-      break;
-    case PSPPIRE_VAR_STORE_COL_TYPE:
-    case PSPPIRE_VAR_STORE_COL_VALUES:
-    case PSPPIRE_VAR_STORE_COL_MISSING:
-    case PSPPIRE_VAR_STORE_COL_ALIGN:
-    case PSPPIRE_VAR_STORE_COL_MEASURE:
-      /* These can be modified only by their respective dialog boxes */
-      return FALSE;
-      break;
-    default:
-      g_assert_not_reached ();
-      return FALSE;
-    }
-
-  return TRUE;
-}
-
-
-static const gchar none[] = N_("None");
-
-static  gchar *
-text_for_column (PsppireVarStore *vs,
-                const struct variable *pv, gint c, GError **err)
-{
-  PsppireDict *dict = vs->dictionary;
-
-  const struct fmt_spec *format = var_get_print_format (pv);
-
-  switch (c)
-    {
-    case PSPPIRE_VAR_STORE_COL_NAME:
-      return xstrdup (var_get_name (pv));
-      break;
-    case PSPPIRE_VAR_STORE_COL_TYPE:
-      return xstrdup (fmt_gui_name (format->type));
-      break;
-    case PSPPIRE_VAR_STORE_COL_WIDTH:
-      {
-       gchar *s;
-       GString *gstr = g_string_sized_new (10);
-       g_string_printf (gstr, _("%d"), format->w);
-       s = g_locale_to_utf8 (gstr->str, gstr->len, 0, 0, err);
-       g_string_free (gstr, TRUE);
-       return s;
-      }
-      break;
-    case PSPPIRE_VAR_STORE_COL_DECIMALS:
-      {
-       gchar *s;
-       GString *gstr = g_string_sized_new (10);
-       g_string_printf (gstr, _("%d"), format->d);
-       s = g_locale_to_utf8 (gstr->str, gstr->len, 0, 0, err);
-       g_string_free (gstr, TRUE);
-       return s;
-      }
-      break;
-    case PSPPIRE_VAR_STORE_COL_COLUMNS:
-      {
-       gchar *s;
-       GString *gstr = g_string_sized_new (10);
-       g_string_printf (gstr, _("%d"), var_get_display_width (pv));
-       s = g_locale_to_utf8 (gstr->str, gstr->len, 0, 0, err);
-       g_string_free (gstr, TRUE);
-       return s;
-      }
-      break;
-    case PSPPIRE_VAR_STORE_COL_LABEL:
-      {
-       const char *label = var_get_label (pv);
-       if (label)
-         return xstrdup (label);
-       return NULL;
-      }
-      break;
-
-    case PSPPIRE_VAR_STORE_COL_MISSING:
-      {
-       return missing_values_to_string (dict, pv, err);
-      }
-      break;
-    case PSPPIRE_VAR_STORE_COL_VALUES:
-      {
-       if ( ! var_has_value_labels (pv))
-         return xstrdup (gettext (none));
-       else
-         {
-           const struct val_labs *vls = var_get_value_labels (pv);
-            const struct val_lab **labels = val_labs_sorted (vls);
-           const struct val_lab *vl = labels[0];
-            free (labels);
-
-           g_assert (vl);
-
-           {
-             gchar *const vstr = value_to_text (vl->value, pv);
-
-             return g_strdup_printf (_("{%s,`%s'}_"), vstr,
-                                      val_lab_get_escaped_label (vl));
-           }
-         }
-      }
-      break;
-    case PSPPIRE_VAR_STORE_COL_ALIGN:
-      {
-       const gint align = var_get_alignment (pv);
-
-       g_assert (align < n_ALIGNMENTS);
-       return xstrdup (alignment_to_string (align));
-      }
-      break;
-    case PSPPIRE_VAR_STORE_COL_MEASURE:
-      {
-       return xstrdup (measure_to_string (var_get_measure (pv)));
-      }
-      break;
-    }
-  return 0;
-}
-
-
-
 /* Return the number of variables */
 gint
 psppire_var_store_get_var_cnt (PsppireVarStore  *store)
 {
   return psppire_dict_get_var_cnt (store->dictionary);
 }
-
-
-static glong
-psppire_var_store_get_row_count (const PsppireSheetModel * model)
-{
-  gint rows = 0;
-  PsppireVarStore *vs = PSPPIRE_VAR_STORE (model);
-
-  if (vs->dictionary)
-    rows =  psppire_dict_get_var_cnt (vs->dictionary);
-
-  return rows ;
-}
-
-static glong
-psppire_var_store_get_column_count (const PsppireSheetModel * model)
-{
-  return PSPPIRE_VAR_STORE_n_COLS ;
-}
-
-\f
-
-/* Row related funcs */
-
-
-static gboolean
-get_row_sensitivity (const PsppireSheetModel *model, gint row)
-{
-  PsppireVarStore *vs = PSPPIRE_VAR_STORE (model);
-
-  if ( ! vs->dictionary)
-    return FALSE;
-
-  return  row < psppire_dict_get_var_cnt (vs->dictionary);
-}
-
-
-static gchar *
-get_row_title (const PsppireSheetModel *model, gint unit)
-{
-  return g_strdup_printf (_("%d"), unit + 1);
-}
-
-
-\f
-
-static const gchar *column_titles[] = {
-  N_("Name"),
-  N_("Type"),
-  N_("Width"),
-  N_("Decimals"),
-  N_("Label"),
-  N_("Values"),
-  N_("Missing"),
-  N_("Columns"),
-  N_("Align"),
-  N_("Measure"),
-};
-
-
-static gchar *
-get_column_title (const PsppireSheetModel *model, gint col)
-{
-  if ( col >= 10)
-    return NULL;
-  return g_strdup (gettext (column_titles[col]));
-}
index e4c2ecd827c47fd9bc6747fddcb1d710357e157b..de199fa0ccc1047f2d28242d1788dd222f4763c1 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (C) 2006  Free Software Foundation
+   Copyright (C) 2006, 2011, 2012  Free Software Foundation
 
    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
 
 G_BEGIN_DECLS
 
-/* PSPPIRE variable store format type, to determine whether a
-   PSPPIRE variable store contains variable input formats or
-   variable output formats.  */
-GType psppire_var_store_format_type_get_type (void);
-
-typedef enum
-  {
-    PSPPIRE_VAR_STORE_INPUT_FORMATS,
-    PSPPIRE_VAR_STORE_OUTPUT_FORMATS
-  }
-PsppireVarStoreFormatType;
-
-#define PSPPIRE_TYPE_VAR_STORE_FORMAT_TYPE \
-        (psppire_var_store_format_type_get_type ())
-
 /* PSPPIRE variable store. */
 #define GTK_TYPE_VAR_STORE            (psppire_var_store_get_type ())
 
@@ -64,8 +49,6 @@ struct _PsppireVarStore
 
   /*< private >*/
   PsppireDict *dictionary;
-  GdkColor disabled;
-  PsppireVarStoreFormatType format_type;
 };
 
 struct _PsppireVarStoreClass
@@ -87,24 +70,6 @@ struct variable * psppire_var_store_get_var (PsppireVarStore *store, glong row);
 /* Return the number of variables */
 gint psppire_var_store_get_var_cnt (PsppireVarStore      *var_store);
 
-void psppire_var_store_set_font (PsppireVarStore *store, const PangoFontDescription *fd);
-
-
 G_END_DECLS
 
-
-enum {
- PSPPIRE_VAR_STORE_COL_NAME,
- PSPPIRE_VAR_STORE_COL_TYPE,
- PSPPIRE_VAR_STORE_COL_WIDTH,
- PSPPIRE_VAR_STORE_COL_DECIMALS,
- PSPPIRE_VAR_STORE_COL_LABEL,
- PSPPIRE_VAR_STORE_COL_VALUES,
- PSPPIRE_VAR_STORE_COL_MISSING,
- PSPPIRE_VAR_STORE_COL_COLUMNS,
- PSPPIRE_VAR_STORE_COL_ALIGN,
- PSPPIRE_VAR_STORE_COL_MEASURE,
- PSPPIRE_VAR_STORE_n_COLS
-};
-
 #endif /* __PSPPIRE_VAR_STORE_H__ */
index c3d5ada9bd535bc7756582e47d8ad0f609d67a13..a30178664c60578508ec09d3a3b9479fe75ac448 100644 (file)
@@ -20,7 +20,6 @@
 
 #include <errno.h>
 #include <fcntl.h>
-#include <gtk-contrib/psppire-sheet.h>
 #include <gtk/gtk.h>
 #include <limits.h>
 #include <stdlib.h>
 #include "libpspp/i18n.h"
 #include "libpspp/line-reader.h"
 #include "libpspp/message.h"
+#include "ui/gui/builder-wrapper.h"
 #include "ui/gui/checkbox-treeview.h"
 #include "ui/gui/dialog-common.h"
 #include "ui/gui/executor.h"
 #include "ui/gui/helper.h"
-#include "ui/gui/builder-wrapper.h"
+#include "ui/gui/pspp-sheet-selection.h"
+#include "ui/gui/pspp-sheet-view.h"
 #include "ui/gui/psppire-data-window.h"
 #include "ui/gui/psppire-dialog.h"
 #include "ui/gui/psppire-encoding-selector.h"
 #include "ui/gui/psppire-empty-list-store.h"
+#include "ui/gui/psppire-scanf.h"
 #include "ui/gui/psppire-var-sheet.h"
 #include "ui/gui/psppire-var-store.h"
-#include "ui/gui/psppire-scanf.h"
 #include "ui/syntax-gen.h"
 
 #include "gl/error.h"
@@ -115,7 +116,7 @@ struct first_line_page
     bool variable_names; /* Variable names above first line of data? */
 
     GtkWidget *page;
-    GtkTreeView *tree_view;
+    PsppSheetView *tree_view;
     GtkWidget *variable_names_cb;
   };
 static void init_first_line_page (struct import_assistant *);
@@ -140,7 +141,7 @@ struct separators_page
     GtkWidget *quote_combo;
     GtkEntry *quote_entry;
     GtkWidget *escape_cb;
-    GtkTreeView *fields_tree_view;
+    PsppSheetView *fields_tree_view;
   };
 /* The columns that the separators divide the data into. */
 struct column
@@ -177,7 +178,7 @@ struct formats_page
     struct dictionary *dict;
 
     GtkWidget *page;
-    GtkTreeView *data_tree_view;
+    PsppSheetView *data_tree_view;
     PsppireDict *psppire_dict;
     struct variable **modified_vars;
     size_t modified_var_cnt;
@@ -205,17 +206,17 @@ static gboolean get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
                                       size_t *row, size_t *column);
 static void make_tree_view (const struct import_assistant *ia,
                             size_t first_line,
-                            GtkTreeView **tree_view);
+                            PsppSheetView **tree_view);
 static void add_line_number_column (const struct import_assistant *,
-                                    GtkTreeView *);
-static gint get_monospace_width (GtkTreeView *, GtkCellRenderer *,
+                                    PsppSheetView *);
+static gint get_monospace_width (PsppSheetView *, GtkCellRenderer *,
                                  size_t char_cnt);
-static gint get_string_width (GtkTreeView *, GtkCellRenderer *,
+static gint get_string_width (PsppSheetView *, GtkCellRenderer *,
                               const char *string);
-static GtkTreeViewColumn *make_data_column (struct import_assistant *,
-                                            GtkTreeView *, bool input,
+static PsppSheetViewColumn *make_data_column (struct import_assistant *,
+                                            PsppSheetView *, bool input,
                                             gint column_idx);
-static GtkTreeView *create_data_tree_view (bool input, GtkContainer *parent,
+static PsppSheetView *create_data_tree_view (bool input, GtkContainer *parent,
                                            struct import_assistant *);
 static void push_watch_cursor (struct import_assistant *);
 static void pop_watch_cursor (struct import_assistant *);
@@ -861,9 +862,9 @@ on_intro_amount_changed (struct import_assistant *ia)
 \f
 /* The "first line" page of the assistant. */
 
-static GtkTreeView *create_lines_tree_view (GtkContainer *parent_window,
+static PsppSheetView *create_lines_tree_view (GtkContainer *parent_window,
                                             struct import_assistant *);
-static void on_first_line_change (GtkTreeSelection *,
+static void on_first_line_change (PsppSheetSelection *,
                                   struct import_assistant *);
 static void on_variable_names_cb_toggle (GtkToggleButton *,
                                          struct import_assistant *);
@@ -883,11 +884,12 @@ init_first_line_page (struct import_assistant *ia)
   p->tree_view = create_lines_tree_view (
     GTK_CONTAINER (get_widget_assert (builder, "first-line-scroller")), ia);
   p->variable_names_cb = get_widget_assert (builder, "variable-names");
-  gtk_tree_selection_set_mode (
-    gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
-    GTK_SELECTION_BROWSE);
+  pspp_sheet_selection_set_mode (
+    pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (p->tree_view)),
+    PSPP_SHEET_SELECTION_BROWSE);
+  pspp_sheet_view_set_rubber_banding (PSPP_SHEET_VIEW (p->tree_view), TRUE);
   set_first_line (ia);
-  g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
+  g_signal_connect (pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (p->tree_view)),
                     "changed", G_CALLBACK (on_first_line_change), ia);
   g_signal_connect (p->variable_names_cb, "toggled",
                     G_CALLBACK (on_variable_names_cb_toggle), ia);
@@ -903,7 +905,7 @@ reset_first_line_page (struct import_assistant *ia)
 }
 
 static void
-render_line (GtkTreeViewColumn *tree_column,
+render_line (PsppSheetViewColumn *tree_column,
              GtkCellRenderer *cell,
              GtkTreeModel *tree_model,
              GtkTreeIter *iter,
@@ -921,11 +923,11 @@ render_line (GtkTreeViewColumn *tree_column,
 
 /* Creates and returns a tree view that contains each of the
    lines in IA's file as a row. */
-static GtkTreeView *
+static PsppSheetView *
 create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia)
 {
-  GtkTreeView *tree_view;
-  GtkTreeViewColumn *column;
+  PsppSheetView *tree_view;
+  PsppSheetViewColumn *column;
   size_t max_line_length;
   gint content_width, header_width;
   size_t i;
@@ -933,11 +935,11 @@ create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia)
 
   make_tree_view (ia, 0, &tree_view);
 
-  column = gtk_tree_view_column_new_with_attributes (
-     title, ia->asst.fixed_renderer, (void *) NULL);
-  gtk_tree_view_column_set_cell_data_func (column, ia->asst.fixed_renderer,
+  column = pspp_sheet_view_column_new_with_attributes (
+    title, ia->asst.fixed_renderer, (void *) NULL);
+  pspp_sheet_view_column_set_cell_data_func (column, ia->asst.fixed_renderer,
                                            render_line, NULL, NULL);
-  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
+  pspp_sheet_view_column_set_resizable (column, TRUE);
 
   max_line_length = 0;
   for (i = 0; i < ia->file.line_cnt; i++)
@@ -949,11 +951,9 @@ create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia)
   content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
                                        max_line_length);
   header_width = get_string_width (tree_view, ia->asst.prop_renderer, title);
-  gtk_tree_view_column_set_fixed_width (column, MAX (content_width,
+  pspp_sheet_view_column_set_fixed_width (column, MAX (content_width,
                                                      header_width));
-  gtk_tree_view_append_column (tree_view, column);
-
-  gtk_tree_view_set_fixed_height_mode (tree_view, true);
+  pspp_sheet_view_append_column (tree_view, column);
 
   gtk_container_add (parent, GTK_WIDGET (tree_view));
   gtk_widget_show (GTK_WIDGET (tree_view));
@@ -964,7 +964,7 @@ create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia)
 /* Called when the line selected in the first_line tree view
    changes. */
 static void
-on_first_line_change (GtkTreeSelection *selection UNUSED,
+on_first_line_change (PsppSheetSelection *selection UNUSED,
                       struct import_assistant *ia)
 {
   get_first_line (ia);
@@ -986,7 +986,7 @@ set_first_line (struct import_assistant *ia)
   GtkTreePath *path;
 
   path = gtk_tree_path_new_from_indices (ia->first_line.skip_lines, -1);
-  gtk_tree_view_set_cursor (GTK_TREE_VIEW (ia->first_line.tree_view),
+  pspp_sheet_view_set_cursor (PSPP_SHEET_VIEW (ia->first_line.tree_view),
                             path, NULL, false);
   gtk_tree_path_free (path);
 
@@ -1001,12 +1001,12 @@ set_first_line (struct import_assistant *ia)
 static void
 get_first_line (struct import_assistant *ia)
 {
-  GtkTreeSelection *selection;
+  PsppSheetSelection *selection;
   GtkTreeIter iter;
   GtkTreeModel *model;
 
-  selection = gtk_tree_view_get_selection (ia->first_line.tree_view);
-  if (gtk_tree_selection_get_selected (selection, &model, &iter))
+  selection = pspp_sheet_view_get_selection (ia->first_line.tree_view);
+  if (pspp_sheet_selection_get_selected (selection, &model, &iter))
     {
       GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
       int row = gtk_tree_path_get_indices (path)[0];
@@ -1043,7 +1043,7 @@ static void on_quote_combo_change (GtkComboBox *combo,
 static void on_quote_cb_toggle (GtkToggleButton *quote_cb,
                                 struct import_assistant *);
 static void on_separator_toggle (GtkToggleButton *, struct import_assistant *);
-static void render_input_cell (GtkTreeViewColumn *tree_column,
+static void render_input_cell (PsppSheetViewColumn *tree_column,
                                GtkCellRenderer *cell,
                                GtkTreeModel *model, GtkTreeIter *iter,
                                gpointer ia);
@@ -1121,7 +1121,7 @@ init_separators_page (struct import_assistant *ia)
 
   set_separators (ia);
   set_quote_list (GTK_COMBO_BOX_ENTRY (p->quote_combo));
-  p->fields_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "fields"));
+  p->fields_tree_view = PSPP_SHEET_VIEW (get_widget_assert (builder, "fields"));
   g_signal_connect (p->quote_combo, "changed",
                     G_CALLBACK (on_quote_combo_change), ia);
   g_signal_connect (p->quote_cb, "toggled",
@@ -1546,7 +1546,7 @@ on_separator_toggle (GtkToggleButton *toggle UNUSED,
 /* Called to render one of the cells in the fields preview tree
    view. */
 static void
-render_input_cell (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
+render_input_cell (PsppSheetViewColumn *tree_column, GtkCellRenderer *cell,
                    GtkTreeModel *model, GtkTreeIter *iter,
                    gpointer ia_)
 {
@@ -1612,7 +1612,7 @@ init_formats_page (struct import_assistant *ia)
 
   p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Formats"),
                                    GTK_ASSISTANT_PAGE_CONFIRM);
-  p->data_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "data"));
+  p->data_tree_view = PSPP_SHEET_VIEW (get_widget_assert (builder, "data"));
   p->modified_vars = NULL;
   p->modified_var_cnt = 0;
   p->dict = NULL;
@@ -1639,7 +1639,6 @@ prepare_formats_page (struct import_assistant *ia)
 {
   struct dictionary *dict;
   PsppireDict *psppire_dict;
-  PsppireVarStore *var_store;
   GtkBin *vars_scroller;
   GtkWidget *old_var_sheet;
   PsppireVarSheet *var_sheet;
@@ -1705,14 +1704,13 @@ prepare_formats_page (struct import_assistant *ia)
      psppire_dict for now, but it should.  After it does, we
      should g_object_ref the psppire_dict here, since we also
      hold a reference via ia->formats.dict. */
-  var_store = psppire_var_store_new (psppire_dict);
-  g_object_set (var_store,
-                "format-type", PSPPIRE_VAR_STORE_INPUT_FORMATS,
-                (void *) NULL);
   var_sheet = PSPPIRE_VAR_SHEET (psppire_var_sheet_new ());
   g_object_set (var_sheet,
-                "model", var_store,
+                "dictionary", psppire_dict,
                 "may-create-vars", FALSE,
+                "may-delete-vars", FALSE,
+                "format-use", FMT_FOR_INPUT,
+                "enable-grid-lines", PSPP_SHEET_VIEW_GRID_LINES_BOTH,
                 (void *) NULL);
 
   vars_scroller = GTK_BIN (get_widget_assert (ia->asst.builder, "vars-scroller"));
@@ -1764,14 +1762,14 @@ on_variable_change (PsppireDict *dict, int dict_idx,
                     struct import_assistant *ia)
 {
   struct formats_page *p = &ia->formats;
-  GtkTreeView *tv = ia->formats.data_tree_view;
+  PsppSheetView *tv = ia->formats.data_tree_view;
   gint column_idx = dict_idx + 1;
 
   push_watch_cursor (ia);
 
   /* Remove previous column and replace with new column. */
-  gtk_tree_view_remove_column (tv, gtk_tree_view_get_column (tv, column_idx));
-  gtk_tree_view_insert_column (tv, make_data_column (ia, tv, false, dict_idx),
+  pspp_sheet_view_remove_column (tv, pspp_sheet_view_get_column (tv, column_idx));
+  pspp_sheet_view_insert_column (tv, make_data_column (ia, tv, false, dict_idx),
                                column_idx);
 
   /* Save a copy of the modified variable in modified_vars, so
@@ -1859,7 +1857,7 @@ parse_field (struct import_assistant *ia,
 /* Called to render one of the cells in the data preview tree
    view. */
 static void
-render_output_cell (GtkTreeViewColumn *tree_column,
+render_output_cell (PsppSheetViewColumn *tree_column,
                     GtkCellRenderer *cell,
                     GtkTreeModel *model,
                     GtkTreeIter *iter,
@@ -1919,11 +1917,11 @@ get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
                       const struct import_assistant *ia,
                       size_t *row, size_t *column)
 {
-  GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
+  PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
   gint bx, by;
   GtkTreePath *path;
   GtkTreeIter iter;
-  GtkTreeViewColumn *tree_column;
+  PsppSheetViewColumn *tree_column;
   GtkTreeModel *tree_model;
   bool ok;
 
@@ -1940,16 +1938,16 @@ get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
   if (!gtk_widget_get_mapped (widget))
     return FALSE;
 
-  gtk_tree_view_convert_widget_to_bin_window_coords (tree_view,
+  pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
                                                      wx, wy, &bx, &by);
-  if (!gtk_tree_view_get_path_at_pos (tree_view, bx, by,
+  if (!pspp_sheet_view_get_path_at_pos (tree_view, bx, by,
                                       &path, &tree_column, NULL, NULL))
     return FALSE;
 
   *column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
                                                 "column-number"));
 
-  tree_model = gtk_tree_view_get_model (tree_view);
+  tree_model = pspp_sheet_view_get_model (tree_view);
   ok = gtk_tree_model_get_iter (tree_model, &iter, path);
   gtk_tree_path_free (path);
   if (!ok)
@@ -1962,24 +1960,25 @@ get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
 static void
 make_tree_view (const struct import_assistant *ia,
                 size_t first_line,
-                GtkTreeView **tree_view)
+                PsppSheetView **tree_view)
 {
   GtkTreeModel *model;
 
-  *tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
+  *tree_view = PSPP_SHEET_VIEW (pspp_sheet_view_new ());
+  pspp_sheet_view_set_grid_lines (*tree_view, PSPP_SHEET_VIEW_GRID_LINES_BOTH);
   model = GTK_TREE_MODEL (psppire_empty_list_store_new (
                             ia->file.line_cnt - first_line));
   g_object_set_data (G_OBJECT (model), "lines", ia->file.lines + first_line);
   g_object_set_data (G_OBJECT (model), "first-line",
                      GINT_TO_POINTER (first_line));
-  gtk_tree_view_set_model (*tree_view, model);
+  pspp_sheet_view_set_model (*tree_view, model);
   g_object_unref (model);
 
   add_line_number_column (ia, *tree_view);
 }
 
 static void
-render_line_number (GtkTreeViewColumn *tree_column,
+render_line_number (PsppSheetViewColumn *tree_column,
                     GtkCellRenderer *cell,
                     GtkTreeModel *tree_model,
                     GtkTreeIter *iter,
@@ -1997,23 +1996,22 @@ render_line_number (GtkTreeViewColumn *tree_column,
 
 static void
 add_line_number_column (const struct import_assistant *ia,
-                        GtkTreeView *treeview)
+                        PsppSheetView *treeview)
 {
-  GtkTreeViewColumn *column;
+  PsppSheetViewColumn *column;
 
-  column = gtk_tree_view_column_new_with_attributes (
+  column = pspp_sheet_view_column_new_with_attributes (
     _("Line"), ia->asst.prop_renderer, (void *) NULL);
-  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
-  gtk_tree_view_column_set_fixed_width (
+  pspp_sheet_view_column_set_fixed_width (
     column, get_monospace_width (treeview, ia->asst.prop_renderer, 5));
-  gtk_tree_view_column_set_resizable (column, TRUE);
-  gtk_tree_view_column_set_cell_data_func (column, ia->asst.prop_renderer,
-                                           render_line_number, NULL, NULL);
-  gtk_tree_view_append_column (treeview, column);
+  pspp_sheet_view_column_set_resizable (column, TRUE);
+  pspp_sheet_view_column_set_cell_data_func (column, ia->asst.prop_renderer,
+                                             render_line_number, NULL, NULL);
+  pspp_sheet_view_append_column (treeview, column);
 }
 
 static gint
-get_monospace_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
+get_monospace_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
                      size_t char_cnt)
 {
   struct string s;
@@ -2029,7 +2027,7 @@ get_monospace_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
 }
 
 static gint
-get_string_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
+get_string_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
                   const char *string)
 {
   gint width;
@@ -2039,15 +2037,15 @@ get_string_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
   return width;
 }
 
-static GtkTreeViewColumn *
-make_data_column (struct import_assistant *ia, GtkTreeView *tree_view,
+static PsppSheetViewColumn *
+make_data_column (struct import_assistant *ia, PsppSheetView *tree_view,
                   bool input, gint dict_idx)
 {
   struct variable *var = NULL;
   struct column *column = NULL;
   size_t char_cnt;
   gint content_width, header_width;
-  GtkTreeViewColumn *tree_column;
+  PsppSheetViewColumn *tree_column;
   char *name;
 
   if (input)
@@ -2062,44 +2060,43 @@ make_data_column (struct import_assistant *ia, GtkTreeView *tree_view,
   header_width = get_string_width (tree_view, ia->asst.prop_renderer,
                                    name);
 
-  tree_column = gtk_tree_view_column_new ();
+  tree_column = pspp_sheet_view_column_new ();
   g_object_set_data (G_OBJECT (tree_column), "column-number",
                      GINT_TO_POINTER (dict_idx));
-  gtk_tree_view_column_set_title (tree_column, name);
-  gtk_tree_view_column_pack_start (tree_column, ia->asst.fixed_renderer,
+  pspp_sheet_view_column_set_title (tree_column, name);
+  pspp_sheet_view_column_pack_start (tree_column, ia->asst.fixed_renderer,
                                    FALSE);
-  gtk_tree_view_column_set_cell_data_func (
+  pspp_sheet_view_column_set_cell_data_func (
     tree_column, ia->asst.fixed_renderer,
     input ? render_input_cell : render_output_cell, ia, NULL);
-  gtk_tree_view_column_set_sizing (tree_column, GTK_TREE_VIEW_COLUMN_FIXED);
-  gtk_tree_view_column_set_fixed_width (tree_column, MAX (content_width,
+  pspp_sheet_view_column_set_fixed_width (tree_column, MAX (content_width,
                                                           header_width));
+  pspp_sheet_view_column_set_resizable (tree_column, TRUE);
 
   free (name);
 
   return tree_column;
 }
 
-static GtkTreeView *
+static PsppSheetView *
 create_data_tree_view (bool input, GtkContainer *parent,
                        struct import_assistant *ia)
 {
-  GtkTreeView *tree_view;
+  PsppSheetView *tree_view;
   gint i;
 
   make_tree_view (ia, ia->first_line.skip_lines, &tree_view);
-  gtk_tree_selection_set_mode (gtk_tree_view_get_selection (tree_view),
-                               GTK_SELECTION_NONE);
+  pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (tree_view),
+                               PSPP_SHEET_SELECTION_NONE);
 
   for (i = 0; i < ia->separators.column_cnt; i++)
-    gtk_tree_view_append_column (tree_view,
+    pspp_sheet_view_append_column (tree_view,
                                  make_data_column (ia, tree_view, input, i));
 
   g_object_set (G_OBJECT (tree_view), "has-tooltip", TRUE, (void *) NULL);
   g_signal_connect (tree_view, "query-tooltip",
                     G_CALLBACK (input ? on_query_input_tooltip
                                 : on_query_output_tooltip), ia);
-  gtk_tree_view_set_fixed_height_mode (tree_view, true);
 
   gtk_container_add (parent, GTK_WIDGET (tree_view));
   gtk_widget_show (GTK_WIDGET (tree_view));
index d8a33d118a39beec98546e09147e42308a6b5ae6..7c09e49aa2621b1c5f7ee51b2ecf5a045be9ca8a 100644 (file)
@@ -158,7 +158,7 @@ The selected file contains N lines of text.  Only the first M of these will be s
             <property name="hscrollbar_policy">automatic</property>
             <property name="vscrollbar_policy">automatic</property>
             <child>
-              <object class="GtkTreeView" id="first-line">
+              <object class="PsppSheetView" id="first-line">
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@@ -508,7 +508,7 @@ The selected file contains N lines of text.  Only the first M of these will be s
                     <property name="hscrollbar_policy">automatic</property>
                     <property name="vscrollbar_policy">automatic</property>
                     <child>
-                      <object class="GtkTreeView" id="fields">
+                      <object class="PsppSheetView" id="fields">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
@@ -621,7 +621,7 @@ The selected file contains N lines of text.  Only the first M of these will be s
                         <property name="hscrollbar_policy">automatic</property>
                         <property name="vscrollbar_policy">automatic</property>
                         <child>
-                          <object class="GtkTreeView" id="data">
+                          <object class="PsppSheetView" id="data">
                             <property name="visible">True</property>
                             <property name="can_focus">True</property>
                             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
index 6b556d6de7861baa006530b2cfcf943fe0ff46d9..ad28681105573a441a71099f3b59cef2504a767e 100644 (file)
@@ -26,7 +26,6 @@
 #include "val-labs-dialog.h"
 #include <data/value-labels.h>
 #include <data/format.h>
-#include "psppire-var-sheet.h"
 #include "psppire-var-store.h"
 #include <libpspp/i18n.h>
 
index 85feaaca7891ec1115639d70886730fc26078c08..3440ebd4c4a387192b3209d8e4f48856c1e1ba7d 100644 (file)
@@ -24,7 +24,6 @@
 
 #include <gtk/gtk.h>
 #include <data/variable.h>
-//#include <gtk-contrib/psppire-sheet.h>
 #include "psppire-var-store.h"
 
 struct val_labs;
diff --git a/src/ui/gui/var-sheet.ui b/src/ui/gui/var-sheet.ui
new file mode 100644 (file)
index 0000000..148a8d8
--- /dev/null
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<interface>
+  <object class="GtkUIManager" id="var_sheet_uim">
+    <ui>
+      <menubar name="menubar">
+       <placeholder name="VarSheetEditMenu">
+         <menu action="edit">
+           <menuitem action="edit_insert-variable"/>
+           <menuitem action="edit_goto-variable"/>
+           <separator/>
+           <menuitem action="edit_cut"/>
+           <menuitem action="edit_copy"/>
+           <menuitem action="edit_paste"/>
+           <menuitem action="edit_clear-variables"/>
+         </menu>
+       </placeholder>
+      </menubar>
+      <toolbar name="toolbar">
+       <placeholder name="VarSheetToolItems">
+         <toolitem name="toolbar_goto-variable" action="edit_goto-variable"/>
+         <toolitem name="toolbar_insert-variable" action="edit_insert-variable"/>
+       </placeholder>
+      </toolbar>
+      <popup name="varsheet-variable-popup">
+       <menuitem action="edit_insert-variable"/>
+       <separator/>
+       <menuitem action="edit_clear-variables"/>
+      </popup>
+    </ui>
+    <child>
+      <object class="GtkActionGroup" id="actiongroup4">
+       <child>
+         <object class="GtkAction" id="edit">
+           <property name="name">edit</property>
+           <property name="label" translatable="yes">_Edit</property>
+         </object>
+       </child>
+       <child>
+         <object class="GtkAction" id="edit_insert-variable">
+           <property name="name">edit_insert-variable</property>
+           <property name="label" translatable="yes">Insert Variable</property>
+           <property name="tooltip" translatable="yes">Create a new variable at the current position</property>
+           <property name="stock-id">pspp-insert-variable</property>
+         </object>
+       </child>
+        <child>
+          <object class="PsppireDialogActionVarInfo" id="edit_goto-variable">
+            <property name="name">edit_goto-variable</property>
+            <property name="label" translatable="yes">Go To Variable...</property>
+           <property name="tooltip" translatable="yes">Jump to variable</property>
+           <property name="stock-id">pspp-goto-variable</property>
+          </object>
+        </child>
+       <child>
+         <object class="GtkAction" id="edit_cut">
+           <property name="stock-id">gtk-cut</property>
+           <property name="name">edit_cut</property>
+         </object>
+       </child>
+       <child>
+         <object class="GtkAction" id="edit_copy">
+           <property name="stock-id">gtk-copy</property>
+           <property name="name">edit_copy</property>
+         </object>
+       </child>
+       <child>
+         <object class="GtkAction" id="edit_paste">
+           <property name="stock-id">gtk-paste</property>
+           <property name="name">edit_paste</property>
+         </object>
+       </child>
+       <child>
+         <object class="GtkAction" id="edit_clear-variables">
+           <property name="name">edit_clear-variables</property>
+           <property name="label" translatable="yes">Cl_ear Variables</property>
+           <property name="tooltip" translatable="yes">Delete the variables at the selected position(s)</property>
+           <property name="stock-id">gtk-clear</property>
+         </object>
+       </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkMenu" constructor="var_sheet_uim" id="varsheet-variable-popup">
+    <property name="visible">True</property>
+  </object>
+</interface>