Fix crash on text import dialog
[pspp-builds.git] / src / ui / gui / text-data-import-dialog.c
index 9c984d417d1b2685b638781dc8594c1435bdcbb5..6f33ff6d8bb8b6547a76a91bc06d35260a3a1881 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (C) 2008  Free Software Foundation
+   Copyright (C) 2008, 2009  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
 
 #include <config.h>
 
+#include <gtk/gtk.h>
+
+
+
 #include "checkbox-treeview.h"
 #include "descriptives-dialog.h"
 
 #include <errno.h>
-#include <gtk/gtk.h>
-#include <gtksheet/gtksheet.h>
+
+#include <gtk-contrib/psppire-sheet.h>
 #include <limits.h>
 #include <stdlib.h>
 #include <sys/stat.h>
 #include <libpspp/assertion.h>
 #include <libpspp/message.h>
 #include <ui/syntax-gen.h>
-#include <ui/gui/data-editor.h>
+#include <ui/gui/psppire-data-window.h>
 #include <ui/gui/dialog-common.h>
 #include <ui/gui/helper.h>
 #include <ui/gui/psppire-dialog.h>
 #include <ui/gui/psppire-var-sheet.h>
 #include <ui/gui/psppire-var-store.h>
-#include <ui/gui/syntax-editor.h>
+#include "executor.h"
 
 #include "error.h"
 #include "xalloc.h"
@@ -50,6 +54,7 @@
 #define _(msgid) gettext (msgid)
 #define N_(msgid) msgid
 
+
 /* TextImportModel, a GtkTreeModel used by the text data import
    dialog. */
 enum
@@ -83,12 +88,13 @@ static void destroy_file (struct import_assistant *);
 /* The main body of the GTK+ assistant and related data. */
 struct assistant
   {
-    GladeXML *xml;
+    GtkBuilder *builder;
     GtkAssistant *assistant;
     GMainLoop *main_loop;
     GtkWidget *paste_button;
     GtkWidget *reset_button;
     int response;
+    int watch_cursor;
 
     GtkCellRenderer *prop_renderer;
     GtkCellRenderer *fixed_renderer;
@@ -222,13 +228,13 @@ static GtkTreeViewColumn *make_data_column (struct import_assistant *,
 static GtkTreeView *create_data_tree_view (bool input, GtkContainer *parent,
                                            struct import_assistant *);
 static void escape_underscores (const char *in, char *out);
+static void push_watch_cursor (struct import_assistant *);
+static void pop_watch_cursor (struct import_assistant *);
 
 /* Pops up the Text Data Import assistant. */
 void
-text_data_import_assistant (GObject *o, gpointer de_)
+text_data_import_assistant (GObject *o, GtkWindow *parent_window)
 {
-  struct data_editor *de = de_;
-  GtkWindow *parent_window = de->parent.window;
   struct import_assistant *ia;
 
   ia = xzalloc (sizeof *ia);
@@ -262,9 +268,7 @@ text_data_import_assistant (GObject *o, gpointer de_)
     case PSPPIRE_RESPONSE_PASTE:
       {
        char *syntax = generate_syntax (ia);
-       struct syntax_editor *se =
-         (struct syntax_editor *) window_create (WINDOW_SYNTAX, NULL);
-       gtk_text_buffer_insert_at_cursor (se->buffer, syntax, -1);
+        paste_syntax_in_new_window (syntax);
        free (syntax);
       }
       break;
@@ -306,11 +310,9 @@ apply_dict (const struct dictionary *dict, struct string *s)
           syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
           for (j = 0; j < mv_n_values (mv); j++)
             {
-              union value value;
               if (j)
                 ds_put_cstr (s, ", ");
-              mv_get_value (mv, &value, j);
-              syntax_gen_value (s, &value, width, format);
+              syntax_gen_value (s, mv_get_value (mv, j), width, format);
             }
 
           if (mv_has_range (mv))
@@ -326,18 +328,20 @@ apply_dict (const struct dictionary *dict, struct string *s)
       if (var_has_value_labels (var))
         {
           const struct val_labs *vls = var_get_value_labels (var);
-          struct val_labs_iterator *iter;
-          struct val_lab *vl;
+          const struct val_lab **labels = val_labs_sorted (vls);
+          size_t n_labels = val_labs_count (vls);
+          size_t i;
 
           syntax_gen_pspp (s, "VALUE LABELS %ss", name);
-          for (vl = val_labs_first_sorted (vls, &iter); vl != NULL;
-               vl = val_labs_next (vls, &iter))
+          for (i = 0; i < n_labels; i++)
             {
+              const struct val_lab *vl = labels[i];
               ds_put_cstr (s, "\n  ");
               syntax_gen_value (s, &vl->value, width, format);
               ds_put_char (s, ' ');
-              syntax_gen_string (s, ss_cstr (vl->label));
+              syntax_gen_string (s, ss_cstr (val_lab_get_label (vl)));
             }
+          free (labels);
           ds_put_cstr (s, ".\n");
         }
       if (var_has_label (var))
@@ -571,7 +575,7 @@ init_assistant (struct import_assistant *ia, GtkWindow *parent_window)
 {
   struct assistant *a = &ia->asst;
 
-  a->xml = XML_NEW ("text-data-import.glade");
+  a->builder = builder_new ("text-data-import.ui");
   a->assistant = GTK_ASSISTANT (gtk_assistant_new ());
   g_signal_connect (a->assistant, "prepare", G_CALLBACK (on_prepare), ia);
   g_signal_connect (a->assistant, "cancel", G_CALLBACK (on_cancel), ia);
@@ -585,6 +589,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");
 
   a->prop_renderer = gtk_cell_renderer_text_new ();
   g_object_ref_sink (a->prop_renderer);
@@ -603,7 +608,7 @@ destroy_assistant (struct import_assistant *ia)
 
   g_object_unref (a->prop_renderer);
   g_object_unref (a->fixed_renderer);
-  g_object_unref (a->xml);
+  g_object_unref (a->builder);
 }
 
 /* Appends a page of the given TYPE, with PAGE as its content, to
@@ -630,6 +635,7 @@ add_page_to_assistant (struct import_assistant *ia,
   gtk_assistant_append_page (ia->asst.assistant, content);
   gtk_assistant_set_page_type (ia->asst.assistant, content, type);
   gtk_assistant_set_page_title (ia->asst.assistant, content, title_copy);
+  gtk_assistant_set_page_complete (ia->asst.assistant, content, true);
 
   free (title_copy);
 
@@ -643,6 +649,13 @@ static void
 on_prepare (GtkAssistant *assistant, GtkWidget *page,
             struct import_assistant *ia)
 {
+
+  if (gtk_assistant_get_page_type (assistant, page)
+      == GTK_ASSISTANT_PAGE_CONFIRM)
+    gtk_widget_grab_focus (assistant->apply);
+  else
+    gtk_widget_grab_focus (assistant->forward);
+
   if (page == ia->separators.page)
     prepare_separators_page (ia);
   else if (page == ia->formats.page)
@@ -653,11 +666,6 @@ on_prepare (GtkAssistant *assistant, GtkWidget *page,
     gtk_widget_show (ia->asst.paste_button);
   else
     gtk_widget_hide (ia->asst.paste_button);
-
-  /* Make the user visit each page in the assistant once.  Seems
-     like a reasonable user interface, plus visiting the final
-     page is what constructs the dictionary. */
-  gtk_assistant_set_page_complete (assistant, page, true);
 }
 
 /* Called when the Cancel button in the assistant is clicked. */
@@ -719,17 +727,17 @@ static void on_intro_amount_changed (GtkToggleButton *button,
 static void
 init_intro_page (struct import_assistant *ia)
 {
-  GladeXML *xml = ia->asst.xml;
+  GtkBuilder *builder = ia->asst.builder;
   struct intro_page *p = &ia->intro;
   struct string s;
 
-  p->page = add_page_to_assistant (ia, get_widget_assert (xml, "Intro"),
+  p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Intro"),
                                    GTK_ASSISTANT_PAGE_INTRO);
-  p->all_cases_button = get_widget_assert (xml, "import-all-cases");
-  p->n_cases_button = get_widget_assert (xml, "import-n-cases");
-  p->n_cases_spin = get_widget_assert (xml, "n-cases-spin");
-  p->percent_button = get_widget_assert (xml, "import-percent");
-  p->percent_spin = get_widget_assert (xml, "percent-spin");
+  p->all_cases_button = get_widget_assert (builder, "import-all-cases");
+  p->n_cases_button = get_widget_assert (builder, "import-n-cases");
+  p->n_cases_spin = get_widget_assert (builder, "n-cases-spin");
+  p->percent_button = get_widget_assert (builder, "import-percent");
+  p->percent_spin = get_widget_assert (builder, "percent-spin");
   g_signal_connect (p->all_cases_button, "toggled",
                     G_CALLBACK (on_intro_amount_changed), ia);
   g_signal_connect (p->n_cases_button, "toggled",
@@ -767,7 +775,7 @@ init_intro_page (struct import_assistant *ia)
     }
   ds_put_cstr (&s, _("You may choose below how much of the file should "
                      "actually be imported."));
-  gtk_label_set_text (GTK_LABEL (get_widget_assert (xml, "intro-label")),
+  gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
                       ds_cstr (&s));
   ds_destroy (&s);
 }
@@ -812,14 +820,14 @@ static void
 init_first_line_page (struct import_assistant *ia)
 {
   struct first_line_page *p = &ia->first_line;
-  GladeXML *xml = ia->asst.xml;
+  GtkBuilder *builder = ia->asst.builder;
 
-  p->page = add_page_to_assistant (ia, get_widget_assert (xml, "FirstLine"),
+  p->page = add_page_to_assistant (ia, get_widget_assert (builder, "FirstLine"),
                                    GTK_ASSISTANT_PAGE_CONTENT);
-  gtk_widget_destroy (get_widget_assert (xml, "first-line"));
+  gtk_widget_destroy (get_widget_assert (builder, "first-line"));
   p->tree_view = create_lines_tree_view (
-    GTK_CONTAINER (get_widget_assert (xml, "first-line-scroller")), ia);
-  p->variable_names_cb = get_widget_assert (xml, "variable-names");
+    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);
@@ -993,37 +1001,63 @@ static const struct separator separators[] =
   };
 #define SEPARATOR_CNT (sizeof separators / sizeof *separators)
 
+static void
+set_quote_list (GtkComboBoxEntry *cb)
+{
+  GtkListStore *list =  gtk_list_store_new (1, G_TYPE_STRING);
+  GtkTreeIter iter;
+  gint i;
+  const gchar *seperator[3] = {"'\"", "\'", "\""};
+
+  for (i = 0; i < 3; i++)
+    {
+      const gchar *s = seperator[i];
+
+      /* Add a new row to the model */
+      gtk_list_store_append (list, &iter);
+      gtk_list_store_set (list, &iter,
+                          0, s,
+                          -1);
+
+    }
+
+  gtk_combo_box_set_model (GTK_COMBO_BOX (cb), GTK_TREE_MODEL (list));
+
+  gtk_combo_box_entry_set_text_column (cb, 0);
+}
+
 /* Initializes IA's separators substructure. */
 static void
 init_separators_page (struct import_assistant *ia)
 {
-  GladeXML *xml = ia->asst.xml;
+  GtkBuilder *builder = ia->asst.builder;
   struct separators_page *p = &ia->separators;
   size_t i;
 
   choose_likely_separators (ia);
 
-  p->page = add_page_to_assistant (ia, get_widget_assert (xml, "Separators"),
+  p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Separators"),
                                    GTK_ASSISTANT_PAGE_CONTENT);
-  p->custom_cb = get_widget_assert (xml, "custom-cb");
-  p->custom_entry = get_widget_assert (xml, "custom-entry");
-  p->quote_combo = get_widget_assert (xml, "quote-combo");
+  p->custom_cb = get_widget_assert (builder, "custom-cb");
+  p->custom_entry = get_widget_assert (builder, "custom-entry");
+  p->quote_combo = get_widget_assert (builder, "quote-combo");
   p->quote_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (p->quote_combo)));
-  p->quote_cb = get_widget_assert (xml, "quote-cb");
-  p->escape_cb = get_widget_assert (xml, "escape");
+  p->quote_cb = get_widget_assert (builder, "quote-cb");
+  p->escape_cb = get_widget_assert (builder, "escape");
 
   set_separators (ia);
-  p->fields_tree_view = GTK_TREE_VIEW (get_widget_assert (xml, "fields"));
-  g_signal_connect (GTK_COMBO_BOX (p->quote_combo), "changed",
+  set_quote_list (GTK_COMBO_BOX_ENTRY (p->quote_combo));
+  p->fields_tree_view = GTK_TREE_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",
                     G_CALLBACK (on_quote_cb_toggle), ia);
-  g_signal_connect (GTK_ENTRY (p->custom_entry), "notify::text",
+  g_signal_connect (p->custom_entry, "notify::text",
                     G_CALLBACK (on_separators_custom_entry_notify), ia);
   g_signal_connect (p->custom_cb, "toggled",
                     G_CALLBACK (on_separators_custom_cb_toggle), ia);
   for (i = 0; i < SEPARATOR_CNT; i++)
-    g_signal_connect (get_widget_assert (xml, separators[i].name),
+    g_signal_connect (get_widget_assert (builder, separators[i].name),
                       "toggled", G_CALLBACK (on_separator_toggle), ia);
   g_signal_connect (p->escape_cb, "toggled",
                     G_CALLBACK (on_separator_toggle), ia);
@@ -1282,6 +1316,9 @@ static void
 revise_fields_preview (struct import_assistant *ia)
 {
   GtkWidget *w;
+
+  push_watch_cursor (ia);
+
   w = GTK_WIDGET (ia->separators.fields_tree_view);
   gtk_widget_destroy (w);
   get_separators (ia);
@@ -1289,8 +1326,10 @@ revise_fields_preview (struct import_assistant *ia)
   choose_column_names (ia);
   ia->separators.fields_tree_view = create_data_tree_view (
     true,
-    GTK_CONTAINER (get_widget_assert (ia->asst.xml, "fields-scroller")),
+    GTK_CONTAINER (get_widget_assert (ia->asst.builder, "fields-scroller")),
     ia);
+
+  pop_watch_cursor (ia);
 }
 
 /* Sets the widgets to match IA's separators substructure. */
@@ -1328,7 +1367,7 @@ set_separators (struct import_assistant *ia)
   for (i = 0; i < SEPARATOR_CNT; i++)
     {
       const struct separator *s = &separators[i];
-      GtkWidget *button = get_widget_assert (ia->asst.xml, s->name);
+      GtkWidget *button = get_widget_assert (ia->asst.builder, s->name);
       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
                                     (seps & (1u << i)) != 0);
     }
@@ -1340,6 +1379,7 @@ set_separators (struct import_assistant *ia)
   ds_destroy (&custom);
 
   any_quotes = !ds_is_empty (&s->quotes);
+
   gtk_entry_set_text (s->quote_entry,
                       any_quotes ? ds_cstr (&s->quotes) : "\"");
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->quote_cb),
@@ -1361,7 +1401,7 @@ get_separators (struct import_assistant *ia)
   for (i = 0; i < SEPARATOR_CNT; i++)
     {
       const struct separator *sep = &separators[i];
-      GtkWidget *button = get_widget_assert (ia->asst.xml, sep->name);
+      GtkWidget *button = get_widget_assert (ia->asst.builder, sep->name);
       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
         ds_put_char (&s->separators, sep->c);
     }
@@ -1495,14 +1535,15 @@ static void clear_modified_vars (struct import_assistant *);
 static void
 init_formats_page (struct import_assistant *ia)
 {
-  GladeXML *xml = ia->asst.xml;
+  GtkBuilder *builder = ia->asst.builder;
   struct formats_page *p = &ia->formats;
 
-  p->page = add_page_to_assistant (ia, get_widget_assert (xml, "Formats"),
+  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 (xml, "data"));
+  p->data_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "data"));
   p->modified_vars = NULL;
   p->modified_var_cnt = 0;
+  p->dict = NULL;
 }
 
 /* Frees IA's formats substructure. */
@@ -1536,6 +1577,8 @@ prepare_formats_page (struct import_assistant *ia)
   unsigned long int number = 0;
   size_t column_idx;
 
+  push_watch_cursor (ia);
+
   dict = dict_create ();
   fg = fmt_guesser_create ();
   for (column_idx = 0; column_idx < s->column_cnt; column_idx++)
@@ -1589,17 +1632,15 @@ prepare_formats_page (struct import_assistant *ia)
      hold a reference via ia->formats.dict. */
   var_store = psppire_var_store_new (psppire_dict);
   g_object_set (var_store,
-                "trailing-rows", 1,
                 "format-type", PSPPIRE_VAR_STORE_INPUT_FORMATS,
                 (void *) NULL);
   var_sheet = PSPPIRE_VAR_SHEET (psppire_var_sheet_new ());
   g_object_set (var_sheet,
-                "row-geometry", var_store,
                 "model", var_store,
                 "may-create-vars", FALSE,
                 (void *) NULL);
 
-  vars_scroller = GTK_BIN (get_widget_assert (ia->asst.xml, "vars-scroller"));
+  vars_scroller = GTK_BIN (get_widget_assert (ia->asst.builder, "vars-scroller"));
   old_var_sheet = gtk_bin_get_child (vars_scroller);
   if (old_var_sheet != NULL)
     gtk_widget_destroy (old_var_sheet);
@@ -1609,8 +1650,10 @@ prepare_formats_page (struct import_assistant *ia)
   gtk_widget_destroy (GTK_WIDGET (ia->formats.data_tree_view));
   ia->formats.data_tree_view = create_data_tree_view (
     false,
-    GTK_CONTAINER (get_widget_assert (ia->asst.xml, "data-scroller")),
+    GTK_CONTAINER (get_widget_assert (ia->asst.builder, "data-scroller")),
     ia);
+
+  pop_watch_cursor (ia);
 }
 
 /* Clears the set of user-modified variables from IA's formats
@@ -1649,6 +1692,8 @@ on_variable_change (PsppireDict *dict, int dict_idx,
   GtkTreeView *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),
@@ -1671,6 +1716,8 @@ on_variable_change (PsppireDict *dict, int dict_idx,
     var_destroy (p->modified_vars[dict_idx]);
   p->modified_vars[dict_idx]
     = var_clone (psppire_dict_get_variable (dict, dict_idx));
+
+  pop_watch_cursor (ia);
 }
 
 /* Parses the contents of the field at (ROW,COLUMN) according to
@@ -1686,7 +1733,7 @@ parse_field (struct import_assistant *ia,
              char **outputp, char **tooltipp)
 {
   struct substring field;
-  union value *val;
+  union value val;
   struct variable *var;
   const struct fmt_spec *in;
   struct fmt_spec out;
@@ -1695,15 +1742,17 @@ parse_field (struct import_assistant *ia,
 
   field = ia->separators.columns[column].contents[row];
   var = dict_get_var (ia->formats.dict, column);
-  val = value_create (var_get_width (var));
+  value_init (&val, var_get_width (var));
   in = var_get_print_format (var);
   out = fmt_for_output_from_input (in);
   tooltip = NULL;
   if (field.string != NULL)
     {
       msg_disable ();
+
       if (!data_in (field, LEGACY_NATIVE, in->type, 0, 0, 0,
-                    val, var_get_width (var)))
+                   ia->formats.dict,
+                    &val, var_get_width (var)))
         {
           char fmt_string[FMT_STRING_LEN_MAX + 1];
           fmt_to_string (in, fmt_string);
@@ -1718,16 +1767,13 @@ parse_field (struct import_assistant *ia,
     {
       tooltip = xstrdup (_("This input line has too few separators "
                            "to fill in this field."));
-      value_set_missing (val, var_get_width (var));
+      value_set_missing (&val, var_get_width (var));
     }
   if (outputp != NULL)
     {
-      char *output = xmalloc (out.w + 1);
-      data_out (val, &out, output);
-      output[out.w] = '\0';
-      *outputp = output;
+      *outputp = data_out (&val, dict_get_encoding (ia->formats.dict),  &out);
     }
-  free (val);
+  value_destroy (&val, var_get_width (var));
 
   ok = tooltip == NULL;
   if (tooltipp != NULL)
@@ -1808,6 +1854,19 @@ get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
   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_MAPPED (widget))
+    return FALSE;
+
   gtk_tree_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,
@@ -2229,3 +2288,32 @@ 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. */
+static void
+push_watch_cursor (struct import_assistant *ia)
+{
+  if (++ia->asst.watch_cursor == 1)
+    {
+      GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
+      GdkDisplay *display = gtk_widget_get_display (widget);
+      GdkCursor *cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
+      gdk_window_set_cursor (widget->window, cursor);
+      gdk_cursor_unref (cursor);
+      gdk_display_flush (display);
+    }
+}
+
+/* Decrements the "watch cursor" level.  If the level reaches
+   zero, the cursor is reset to its default shape. */
+static void
+pop_watch_cursor (struct import_assistant *ia)
+{
+  if (--ia->asst.watch_cursor == 0)
+    {
+      GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
+      gdk_window_set_cursor (widget->window, NULL);
+    }
+}