Merge "master" into "psppsheet" to obtain bug fixes from "master".
[pspp] / src / ui / gui / text-data-import-dialog.c
index 215a444c4861ba2d5d2faa0117b0608158af982a..0abeb56f6884c75b013925e8876ddfd02124e9cc 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (C) 2008, 2009, 2010, 2011  Free Software Foundation
+   Copyright (C) 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
@@ -19,7 +19,7 @@
 #include "ui/gui/text-data-import-dialog.h"
 
 #include <errno.h>
-#include <gtk-contrib/psppire-sheet.h>
+#include <fcntl.h>
 #include <gtk/gtk.h>
 #include <limits.h>
 #include <stdlib.h>
 #include "language/lexer/lexer.h"
 #include "libpspp/assertion.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/descriptives-dialog.h"
 #include "ui/gui/dialog-common.h"
 #include "ui/gui/executor.h"
 #include "ui/gui/helper.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/widget-io.h"
 #include "ui/syntax-gen.h"
 
 #include "gl/error.h"
+#include "gl/intprops.h"
 #include "gl/xalloc.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 #define N_(msgid) msgid
 
-
-/* TextImportModel, a GtkTreeModel used by the text data import
-   dialog. */
-enum
-  {
-    TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER, /* 1-based line number in file */
-    TEXT_IMPORT_MODEL_COLUMN_LINE,        /* The line from the file. */
-  };
-typedef struct TextImportModel TextImportModel;
-typedef struct TextImportModelClass TextImportModelClass;
-
-TextImportModel *text_import_model_new (struct string *lines, size_t line_cnt,
-                                        size_t first_line);
-gint text_import_model_iter_to_row (const GtkTreeIter *);
-
 struct import_assistant;
 
 /* The file to be imported. */
 struct file
   {
     char *file_name;        /* File name. */
+    gchar *encoding;        /* Encoding. */
     unsigned long int total_lines; /* Number of lines in file. */
     bool total_is_exact;    /* Is total_lines exact (or an estimate)? */
 
@@ -124,7 +115,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 *);
@@ -149,7 +140,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
@@ -186,7 +177,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;
@@ -214,19 +205,18 @@ 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 char *escape_underscores (const char *in);
 static void push_watch_cursor (struct import_assistant *);
 static void pop_watch_cursor (struct import_assistant *);
 
@@ -372,6 +362,8 @@ generate_syntax (const struct import_assistant *ia)
                    "  /TYPE=TXT\n"
                    "  /FILE=%sq\n",
                    ia->file.file_name);
+  if (ia->file.encoding && strcmp (ia->file.encoding, "Auto"))
+    syntax_gen_pspp (&s, "  /ENCODING=%sq\n", ia->file.encoding);
   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
                                       ia->intro.n_cases_button)))
     ds_put_format (&s, "  /IMPORTCASES=FIRST %d\n",
@@ -427,7 +419,7 @@ generate_syntax (const struct import_assistant *ia)
 \f
 /* Choosing a file and reading it. */
 
-static char *choose_file (GtkWindow *parent_window);
+static char *choose_file (GtkWindow *parent_window, gchar **encodingp);
 
 /* Obtains the file to import from the user and initializes IA's
    file substructure.  PARENT_WINDOW must be the window to use
@@ -441,14 +433,14 @@ init_file (struct import_assistant *ia, GtkWindow *parent_window)
   struct file *file = &ia->file;
   enum { MAX_PREVIEW_LINES = 1000 }; /* Max number of lines to read. */
   enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
-  FILE *stream;
+  struct line_reader *reader;
 
-  file->file_name = choose_file (parent_window);
+  file->file_name = choose_file (parent_window, &file->encoding);
   if (file->file_name == NULL)
     return false;
 
-  stream = fopen (file->file_name, "r");
-  if (stream == NULL)
+  reader = line_reader_for_file (file->encoding, file->file_name, O_RDONLY);
+  if (reader == NULL)
     {
       msg (ME, _("Could not open `%s': %s"),
            file->file_name, strerror (errno));
@@ -461,30 +453,29 @@ init_file (struct import_assistant *ia, GtkWindow *parent_window)
       struct string *line = &file->lines[file->line_cnt];
 
       ds_init_empty (line);
-      if (!ds_read_line (line, stream, MAX_LINE_LEN))
+      if (!line_reader_read (reader, line, MAX_LINE_LEN + 1)
+          || ds_length (line) > MAX_LINE_LEN)
         {
-          if (feof (stream))
+          if (line_reader_eof (reader))
             break;
-          else if (ferror (stream))
+          else if (line_reader_error (reader))
             msg (ME, _("Error reading `%s': %s"),
-                 file->file_name, strerror (errno));
+                 file->file_name, strerror (line_reader_error (reader)));
           else
             msg (ME, _("Failed to read `%s', because it contains a line "
                        "over %d bytes long and therefore appears not to be "
                        "a text file."),
                  file->file_name, MAX_LINE_LEN);
-          fclose (stream);
+          line_reader_close (reader);
           destroy_file (ia);
           return false;
         }
-      ds_chomp_byte (line, '\n');
-      ds_chomp_byte (line, '\r');
     }
 
   if (file->line_cnt == 0)
     {
       msg (ME, _("`%s' is empty."), file->file_name);
-      fclose (stream);
+      line_reader_close (reader);
       destroy_file (ia);
       return false;
     }
@@ -495,13 +486,15 @@ init_file (struct import_assistant *ia, GtkWindow *parent_window)
   else
     {
       struct stat s;
-      off_t position = ftello (stream);
-      if (fstat (fileno (stream), &s) == 0 && position > 0)
+      off_t position = line_reader_tell (reader);
+      if (fstat (line_reader_fileno (reader), &s) == 0 && position > 0)
         file->total_lines = (double) file->line_cnt / position * s.st_size;
       else
         file->total_lines = 0;
     }
 
+  line_reader_close (reader);
+
   return true;
 }
 
@@ -516,32 +509,78 @@ destroy_file (struct import_assistant *ia)
     ds_destroy (&f->lines[i]);
   free (f->lines);
   g_free (f->file_name);
+  g_free (f->encoding);
 }
 
-/* Obtains the file to read from the user and returns the name of
-   the file as a string that must be freed with g_free if
-   successful, otherwise a null pointer.  PARENT_WINDOW must be
-   the window to use as the file chooser window's parent. */
+/* Obtains the file to read from the user.  If successful, returns the name of
+   the file and stores the user's chosen encoding for the file into *ENCODINGP.
+   The caller must free each of these strings with g_free().
+
+   On failure, stores a null pointer and stores NULL in *ENCODINGP.
+
+   PARENT_WINDOW must be the window to use as the file chooser window's
+   parent. */
 static char *
-choose_file (GtkWindow *parent_window)
+choose_file (GtkWindow *parent_window, gchar **encodingp)
 {
-  GtkWidget *dialog;
   char *file_name;
+  GtkFileFilter *filter = NULL;
 
-  dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
+  GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
                                         parent_window,
                                         GTK_FILE_CHOOSER_ACTION_OPEN,
                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                         GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
                                         NULL);
 
+  g_object_set (dialog, "local-only", FALSE, NULL);
+
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("Text files"));
+  gtk_file_filter_add_mime_type (filter, "text/*");
+  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
+  gtk_file_filter_add_pattern (filter, "*.txt");
+  gtk_file_filter_add_pattern (filter, "*.TXT");
+  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
+  gtk_file_filter_add_mime_type (filter, "text/plain");
+  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
+  gtk_file_filter_add_mime_type (filter, "text/csv");
+  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+  /* I've never encountered one of these, but it's listed here:
+     http://www.iana.org/assignments/media-types/text/tab-separated-values  */
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
+  gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
+  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
+  gtk_file_chooser_set_extra_widget (
+    GTK_FILE_CHOOSER (dialog), psppire_encoding_selector_new ("Auto", true));
+
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("All Files"));
+  gtk_file_filter_add_pattern (filter, "*");
+  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
+
   switch (gtk_dialog_run (GTK_DIALOG (dialog)))
     {
     case GTK_RESPONSE_ACCEPT:
       file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
+      *encodingp = psppire_encoding_selector_get_encoding (
+        gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (dialog)));
       break;
     default:
       file_name = NULL;
+      *encodingp = NULL;
       break;
     }
   gtk_widget_destroy (dialog);
@@ -581,7 +620,7 @@ init_assistant (struct import_assistant *ia, GtkWindow *parent_window)
   gtk_window_set_title (GTK_WINDOW (a->assistant),
                         _("Importing Delimited Text Data"));
   gtk_window_set_transient_for (GTK_WINDOW (a->assistant), parent_window);
-  gtk_window_set_icon_name (GTK_WINDOW (a->assistant), "psppicon");
+  gtk_window_set_icon_name (GTK_WINDOW (a->assistant), "pspp");
 
   a->prop_renderer = gtk_cell_renderer_text_new ();
   g_object_ref_sink (a->prop_renderer);
@@ -728,7 +767,7 @@ init_intro_page (struct import_assistant *ia)
 
   p->n_cases_spin = gtk_spin_button_new_with_range (0, INT_MAX, 100);
 
-  hbox_n_cases = widget_scanf (_("Only the first %4d cases"), &p->n_cases_spin);
+  hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &p->n_cases_spin);
 
   table  = get_widget_assert (builder, "button-table");
 
@@ -738,7 +777,7 @@ init_intro_page (struct import_assistant *ia)
 
   p->percent_spin = gtk_spin_button_new_with_range (0, 100, 10);
 
-  hbox_percent = widget_scanf (_("Only the first %3d %% of file (approximately)"), &p->percent_spin);
+  hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"), &p->percent_spin);
 
   gtk_table_attach_defaults (GTK_TABLE (table), hbox_percent,
                             1, 2,
@@ -822,9 +861,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 *);
@@ -844,11 +883,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);
@@ -863,13 +903,30 @@ reset_first_line_page (struct import_assistant *ia)
   set_first_line (ia);
 }
 
+static void
+render_line (PsppSheetViewColumn *tree_column,
+             GtkCellRenderer *cell,
+             GtkTreeModel *tree_model,
+             GtkTreeIter *iter,
+             gpointer data)
+{
+  gint row = empty_list_store_iter_to_row (iter);
+  struct string *lines;
+
+  lines = g_object_get_data (G_OBJECT (tree_model), "lines");
+  g_return_if_fail (lines != NULL);
+
+  g_object_set (cell, "text", ds_cstr (&lines[row]), NULL);
+}
+
+
 /* 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;
@@ -877,13 +934,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,
-     "text", TEXT_IMPORT_MODEL_COLUMN_LINE,
-     (void *) NULL
-     );
-  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
+  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);
+  pspp_sheet_view_column_set_resizable (column, TRUE);
 
   max_line_length = 0;
   for (i = 0; i < ia->file.line_cnt; i++)
@@ -895,11 +950,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));
@@ -910,7 +963,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);
@@ -932,7 +985,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);
 
@@ -947,12 +1000,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];
@@ -989,7 +1042,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);
@@ -1041,6 +1094,7 @@ set_quote_list (GtkComboBoxEntry *cb)
     }
 
   gtk_combo_box_set_model (GTK_COMBO_BOX (cb), GTK_TREE_MODEL (list));
+  g_object_unref (list);
 
   gtk_combo_box_entry_set_text_column (cb, 0);
 }
@@ -1066,7 +1120,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",
@@ -1491,7 +1545,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_)
 {
@@ -1502,7 +1556,7 @@ render_input_cell (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
 
   column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
                                                "column-number"));
-  row = text_import_model_iter_to_row (iter) + ia->first_line.skip_lines;
+  row = empty_list_store_iter_to_row (iter) + ia->first_line.skip_lines;
   field = ia->separators.columns[column].contents[row];
   if (field.string != NULL)
     {
@@ -1557,7 +1611,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;
@@ -1571,7 +1625,7 @@ destroy_formats_page (struct import_assistant *ia)
 
   if (p->psppire_dict != NULL)
     {
-      /* This destroys p->dict also. */
+      dict_destroy (p->psppire_dict->dict);
       g_object_unref (p->psppire_dict);
     }
   clear_modified_vars (ia);
@@ -1584,7 +1638,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;
@@ -1650,14 +1703,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"));
@@ -1709,14 +1761,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
@@ -1770,7 +1822,7 @@ parse_field (struct import_assistant *ia,
     {
       char *error;
 
-      error = data_in (field, C_ENCODING, in->type, &val, var_get_width (var),
+      error = data_in (field, "UTF-8", in->type, &val, var_get_width (var),
                        dict_get_encoding (ia->formats.dict));
       if (error != NULL)
         {
@@ -1804,7 +1856,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,
@@ -1816,7 +1868,7 @@ render_output_cell (GtkTreeViewColumn *tree_column,
   bool ok;
 
   ok = parse_field (ia,
-                    (text_import_model_iter_to_row (iter)
+                    (empty_list_store_iter_to_row (iter)
                      + ia->first_line.skip_lines),
                     GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
                                                         "column-number")),
@@ -1864,11 +1916,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;
 
@@ -1885,59 +1937,80 @@ 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)
     return FALSE;
 
-  *row = text_import_model_iter_to_row (&iter) + ia->first_line.skip_lines;
+  *row = empty_list_store_iter_to_row (&iter) + ia->first_line.skip_lines;
   return TRUE;
 }
 
 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 ());
-  model = GTK_TREE_MODEL (text_import_model_new (
-                            ia->file.lines + first_line,
-                            ia->file.line_cnt - first_line, first_line));
-  gtk_tree_view_set_model (*tree_view, model);
+  *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));
+  pspp_sheet_view_set_model (*tree_view, model);
+  g_object_unref (model);
 
   add_line_number_column (ia, *tree_view);
 }
 
+static void
+render_line_number (PsppSheetViewColumn *tree_column,
+                    GtkCellRenderer *cell,
+                    GtkTreeModel *tree_model,
+                    GtkTreeIter *iter,
+                    gpointer data)
+{
+  gint row = empty_list_store_iter_to_row (iter);
+  char s[INT_BUFSIZE_BOUND (int)];
+  int first_line;
+
+  first_line = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_model),
+                                                   "first-line"));
+  sprintf (s, "%d", first_line + row);
+  g_object_set (cell, "text", s, NULL);
+}
+
 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 (
-                                                    _("Line"), ia->asst.prop_renderer,
-    "text", TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER,
-    (void *) NULL);
-  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
-  gtk_tree_view_column_set_fixed_width (
+  column = pspp_sheet_view_column_new_with_attributes (
+    _("Line"), ia->asst.prop_renderer, (void *) NULL);
+  pspp_sheet_view_column_set_fixed_width (
     column, get_monospace_width (treeview, ia->asst.prop_renderer, 5));
-  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;
@@ -1953,7 +2026,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;
@@ -1963,15 +2036,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)
@@ -1986,44 +2059,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));
@@ -2031,290 +2103,6 @@ create_data_tree_view (bool input, GtkContainer *parent,
   return tree_view;
 }
 
-static char *
-escape_underscores (const char *in)
-{
-  char *out = xmalloc (2 * strlen (in) + 1);
-  char *p;
-
-  p = out;
-  for (; *in != '\0'; in++)
-    {
-      if (*in == '_')
-        *p++ = '_';
-      *p++ = *in;
-    }
-  *p = '\0';
-
-  return out;
-}
-\f
-/* TextImportModel, a GtkTreeModel implementation used by some
-   pages of the assistant. */
-
-#define G_TYPE_TEXT_IMPORT_MODEL (text_import_model_get_type ())
-#define TEXT_IMPORT_MODEL(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), G_TYPE_TEXT_IMPORT_MODEL, TextImportModel))
-#define TEXT_IMPORT_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), G_TYPE_TEXT_IMPORT_MODEL, TextImportModelClass))
-#define IS_TEXT_IMPORT_MODEL(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), G_TYPE_TEXT_IMPORT_MODEL))
-#define IS_TEXT_IMPORT_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), G_TYPE_TEXT_IMPORT_MODEL))
-#define TEXT_IMPORT_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_TEXT_IMPORT_MODEL, TextImportModelClass))
-
-/* Random number used in 'stamp' member of GtkTreeIter. */
-#define TREE_MODEL_STAMP 0x7efd67d3
-
-struct TextImportModel
-{
-  GObject             parent;
-  struct string *lines;
-  size_t line_cnt;
-  size_t first_line;
-};
-
-struct TextImportModelClass
-{
-  GObjectClass parent_class;
-};
-
-GType text_import_model_get_type (void);
-static void text_import_model_tree_model_init (gpointer iface, gpointer data);
-
-GType
-text_import_model_get_type (void)
-{
-  static GType object_type = 0;
-
-  if (!object_type)
-    {
-      static const GTypeInfo object_info = {
-       sizeof (TextImportModelClass),
-       (GBaseInitFunc) NULL,
-       (GBaseFinalizeFunc) NULL,
-       NULL,   /* class_init */
-       NULL,   /* class_finalize */
-       NULL,   /* class_data */
-       sizeof (TextImportModel),
-       0,      /* n_preallocs */
-       NULL,   /* instance_init */
-      };
-
-      static const GInterfaceInfo tree_model_info = {
-       text_import_model_tree_model_init,
-       NULL,
-       NULL
-      };
-
-      object_type = g_type_register_static (G_TYPE_OBJECT,
-                                           "TextImportModel",
-                                           &object_info, 0);
-
-      g_type_add_interface_static (object_type, GTK_TYPE_TREE_MODEL,
-                                  &tree_model_info);
-
-
-    }
-
-  return object_type;
-}
-
-
-/* Creates and returns a new TextImportModel that contains the
-   LINE_CNT lines in LINES.  The lines before FIRST_LINE in LINES
-   are not part of the model, but they are included in the line
-   numbers in the TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER column.
-
-   The caller retains responsibility for freeing LINES and must
-   ensure that its lifetime and that of the strings that it
-   contains exceeds that of the TextImportModel. */
-TextImportModel *
-text_import_model_new (struct string *lines, size_t line_cnt,
-                       size_t first_line)
-{
-  TextImportModel *new_text_import_model
-    = g_object_new (G_TYPE_TEXT_IMPORT_MODEL, NULL);
-  new_text_import_model->lines = lines;
-  new_text_import_model->line_cnt = line_cnt;
-  new_text_import_model->first_line = first_line;
-  return new_text_import_model;
-}
-
-
-static gboolean
-tree_model_iter_has_child  (GtkTreeModel *tree_model,
-                           GtkTreeIter  *iter)
-{
-  return FALSE;
-}
-
-static gboolean
-tree_model_iter_parent (GtkTreeModel *tree_model,
-                       GtkTreeIter *iter,
-                       GtkTreeIter *child)
-{
-  return TRUE;
-}
-
-static GtkTreeModelFlags
-tree_model_get_flags (GtkTreeModel *model)
-{
-  g_return_val_if_fail (IS_TEXT_IMPORT_MODEL (model), (GtkTreeModelFlags) 0);
-
-  return GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST;
-}
-
-
-static gint
-tree_model_n_columns (GtkTreeModel *model)
-{
-  return 2;
-}
-
-static GType
-tree_model_column_type (GtkTreeModel *model, gint index)
-{
-  return (index == TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER ? G_TYPE_INT
-          : index == TEXT_IMPORT_MODEL_COLUMN_LINE ? G_TYPE_STRING
-          : -1);
-}
-
-static gboolean
-init_iter (TextImportModel *list, gint idx, GtkTreeIter *iter)
-{
-  if (idx < 0 || idx >= list->line_cnt)
-    {
-      iter->stamp = 0;
-      iter->user_data = GINT_TO_POINTER (-1);
-      return FALSE;
-    }
-  else
-    {
-      iter->stamp = TREE_MODEL_STAMP;
-      iter->user_data = GINT_TO_POINTER (idx);
-      return TRUE;
-    }
-}
-
-static gboolean
-tree_model_get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path)
-{
-  gint *indices, depth;
-
-  TextImportModel *list = TEXT_IMPORT_MODEL (model);
-
-  g_return_val_if_fail (path, FALSE);
-
-  indices = gtk_tree_path_get_indices (path);
-  depth = gtk_tree_path_get_depth (path);
-
-  g_return_val_if_fail (depth == 1, FALSE);
-
-  return init_iter (list, indices[0], iter);
-}
-
-
-static gboolean
-tree_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter)
-{
-  TextImportModel *list = TEXT_IMPORT_MODEL (model);
-  gint idx;
-
-  assert (iter->stamp == TREE_MODEL_STAMP);
-
-  idx = GPOINTER_TO_INT (iter->user_data);
-  return init_iter (list, idx == -1 ? -1 : idx + 1, iter);
-}
-
-static GtkTreePath *
-tree_model_get_path (GtkTreeModel *model, GtkTreeIter *iter)
-{
-  GtkTreePath *path;
-
-  g_return_val_if_fail (iter->stamp == TREE_MODEL_STAMP, FALSE);
-
-  path = gtk_tree_path_new ();
-  gtk_tree_path_append_index (path, GPOINTER_TO_INT (iter->user_data));
-
-  return path;
-}
-
-static void
-tree_model_get_value (GtkTreeModel *model, GtkTreeIter *iter,
-                     gint column, GValue *value)
-{
-  TextImportModel *list = TEXT_IMPORT_MODEL (model);
-  gint idx;
-
-  g_return_if_fail (iter->stamp == TREE_MODEL_STAMP);
-
-  idx = GPOINTER_TO_INT (iter->user_data);
-  assert (idx >= 0 && idx < list->line_cnt);
-
-  if (column == 0)
-    {
-      g_value_init (value, G_TYPE_INT);
-      g_value_set_int (value, idx + list->first_line + 1);
-    }
-  else
-    {
-      g_value_init (value, G_TYPE_STRING);
-      g_value_set_static_string (value, ds_cstr (&list->lines[idx]));
-    }
-}
-
-static gboolean
-tree_model_iter_children (GtkTreeModel *tree_model,
-                         GtkTreeIter *iter,
-                         GtkTreeIter *parent)
-{
-  return FALSE;
-}
-
-static gint
-tree_model_n_children (GtkTreeModel *model, GtkTreeIter  *iter)
-{
-  TextImportModel *list = TEXT_IMPORT_MODEL (model);
-
-  return iter == NULL ? list->line_cnt : 0;
-}
-
-static gboolean
-tree_model_nth_child (GtkTreeModel *model, GtkTreeIter *iter,
-                     GtkTreeIter *parent, gint n)
-{
-  TextImportModel *list = TEXT_IMPORT_MODEL (model);
-  g_return_val_if_fail (IS_TEXT_IMPORT_MODEL (model), FALSE);
-
-  if (parent)
-    return FALSE;
-  return init_iter (list, n, iter);
-}
-
-static void
-text_import_model_tree_model_init (gpointer iface_, gpointer data UNUSED)
-{
-  GtkTreeModelIface *iface = (GtkTreeModelIface *) iface_;
-
-  iface->get_flags = tree_model_get_flags;
-  iface->get_n_columns = tree_model_n_columns;
-  iface->get_column_type = tree_model_column_type;
-  iface->get_iter = tree_model_get_iter;
-  iface->iter_next = tree_model_iter_next;
-  iface->get_path = tree_model_get_path;
-  iface->get_value = tree_model_get_value;
-
-  iface->iter_children = tree_model_iter_children;
-  iface->iter_has_child = tree_model_iter_has_child;
-  iface->iter_n_children = tree_model_n_children;
-  iface->iter_nth_child = tree_model_nth_child;
-  iface->iter_parent = tree_model_iter_parent;
-}
-
-gint
-text_import_model_iter_to_row (const GtkTreeIter *iter)
-{
-  assert (iter->stamp == TREE_MODEL_STAMP);
-  return GPOINTER_TO_INT (iter->user_data);
-}
-
 /* Increments the "watch cursor" level, setting the cursor for
    the assistant window to a watch face to indicate to the user
    that the ongoing operation may take some time. */