src/ui/gui/windows-menu.c (repopulate_windows_menu): Avoid memory leak
[pspp] / src / ui / gui / psppire-import-assistant.c
index e376bacc7e667cf3ae9076d071c193fa940936ea..635941c2d25d55e72be412e4d9c0fad835fe5e6f 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPPIRE - a graphical user interface for PSPP.
-   Copyright (C) 2015, 2016, 2017, 2018  Free Software Foundation
+   Copyright (C) 2015, 2016, 2017, 2018, 2020  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
@@ -128,6 +128,7 @@ psppire_import_assistant_finalize (GObject *object)
   ds_destroy (&ia->quotes);
 
   dict_unref (ia->dict);
+  dict_unref (ia->casereader_dict);
 
   g_object_unref (ia->builder);
 
@@ -300,7 +301,8 @@ choose_likely_separators (PsppireImportAssistant *ia)
       hmap_destroy (&count_map[j]);
     }
 
-  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->builder, separators[most_frequent].name)), TRUE);
+  const char* sepname = separators[MAX (0,most_frequent)].name;
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->builder, sepname)), TRUE);
 }
 
 static void
@@ -729,6 +731,8 @@ psppire_import_assistant_init (PsppireImportAssistant *ia)
   ia->file_name = NULL;
 
   ia->spreadsheet = NULL;
+  ia->dict = NULL;
+  ia->casereader_dict = NULL;
 
   ia->main_loop = g_main_loop_new (NULL, TRUE);
 
@@ -1024,32 +1028,6 @@ psppire_import_assistant_new (GtkWindow *toplevel)
 
 \f
 
-static void
-set_quote_list (GtkComboBox *cb)
-{
-  GtkListStore *list =  gtk_list_store_new (1, G_TYPE_STRING);
-  GtkTreeIter iter;
-  gint i;
-  const gchar *separator[3] = {"'\"", "\'", "\""};
-
-  for (i = 0; i < 3; i++)
-    {
-      const gchar *s = separator[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));
-  g_object_unref (list);
-
-  gtk_combo_box_set_entry_text_column (cb, 0);
-}
-
 /* Chooses a name for each column on the separators page */
 static void
 choose_column_names (PsppireImportAssistant *ia)
@@ -1159,10 +1137,9 @@ separators_page_create (PsppireImportAssistant *ia)
   ia->custom_cb = get_widget_assert (builder, "custom-cb");
   ia->custom_entry = get_widget_assert (builder, "custom-entry");
   ia->quote_combo = get_widget_assert (builder, "quote-combo");
-  ia->quote_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (ia->quote_combo)));
   ia->quote_cb = get_widget_assert (builder, "quote-cb");
 
-  set_quote_list (GTK_COMBO_BOX (ia->quote_combo));
+  gtk_combo_box_set_active (GTK_COMBO_BOX (ia->quote_combo), 0);
 
   if (ia->fields_tree_view == NULL)
     {
@@ -1215,12 +1192,19 @@ my_read (struct casereader *reader, void *aux, casenumber idx)
          GValue value = {0};
          gtk_tree_model_get_value (tm, &iter, i + 1, &value);
 
-         const struct variable *var = dict_get_var (ia->dict, i);
+         const struct variable *var = dict_get_var (ia->casereader_dict, i);
 
          const gchar *ss = g_value_get_string (&value);
          if (ss)
            {
              union value *v = case_data_rw (c, var);
+             /* In this reader we derive the union value from the
+                string in the tree_model. We retrieve the width and format
+                from a dictionary which is stored directly after
+                the reader creation. Changes in ia->dict in the
+                variable window are not reflected here and therefore
+                this is always compatible with the width in the
+                caseproto. See bug #58298 */
              char *xx = data_in (ss_cstr (ss),
                                  "UTF-8",
                                  var_get_write_format (var)->type,
@@ -1251,6 +1235,101 @@ my_advance (struct casereader *reader, void *aux, casenumber cnt)
   g_print ("%s:%d\n", __FILE__, __LINE__);
 }
 
+static struct casereader *
+textfile_create_reader (PsppireImportAssistant *ia)
+{
+  int n_vars = dict_get_var_cnt (ia->dict);
+
+  int i;
+
+  struct fmt_guesser **fg = xcalloc (n_vars, sizeof *fg);
+  for (i = 0 ; i < n_vars; ++i)
+    {
+      fg[i] = fmt_guesser_create ();
+    }
+
+  gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
+
+  GtkTreeIter iter;
+  gboolean ok;
+  for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->delimiters_model), &iter);
+       ok;
+       ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->delimiters_model), &iter))
+    {
+      for (i = 0 ; i < n_vars; ++i)
+       {
+         gchar *s = NULL;
+         gtk_tree_model_get (GTK_TREE_MODEL (ia->delimiters_model), &iter, i+1, &s, -1);
+         if (s)
+           fmt_guesser_add (fg[i], ss_cstr (s));
+         free (s);
+       }
+    }
+
+  struct caseproto *proto = caseproto_create ();
+  for (i = 0 ; i < n_vars; ++i)
+    {
+      struct fmt_spec fs;
+      fmt_guesser_guess (fg[i], &fs);
+
+      fmt_fix (&fs, FMT_FOR_INPUT);
+
+      struct variable *var = dict_get_var (ia->dict, i);
+
+      int width = fmt_var_width (&fs);
+
+      var_set_width_and_formats (var, width,
+                                &fs, &fs);
+
+      proto = caseproto_add_width (proto, width);
+      fmt_guesser_destroy (fg[i]);
+    }
+
+  free (fg);
+
+  struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class,  ia);
+  /* Store the dictionary at this point when the casereader is created.
+     my_read depends on the dictionary to interpret the strings in the treeview.
+     This guarantees that the union value is produced according to the
+     caseproto in the reader. */
+  ia->casereader_dict = dict_clone (ia->dict);
+  caseproto_unref (proto);
+  return cr;
+}
+
+/* When during import the variable type is changed, the reader is reinitialized
+   based on the new dictionary with a fresh caseprototype. The default behaviour
+   when a variable type is changed and the column is resized is that the union
+   value is interpreted with new variable type and an overlay for that column
+   is generated. Here we reinit to the original reader based on strings.
+   As a result you can switch from string to numeric to string without loosing
+   the string information. */
+static void
+ia_variable_changed_cb (GObject *obj, gint var_num, guint what,
+                       const struct variable *oldvar, gpointer data)
+{
+  PsppireImportAssistant *ia  = PSPPIRE_IMPORT_ASSISTANT (data);
+
+  struct caseproto *proto = caseproto_create();
+  for (int i = 0; i < dict_get_var_cnt (ia->dict); i++)
+    {
+      const struct variable *var = dict_get_var (ia->dict, i);
+      int width = var_get_width (var);
+      proto = caseproto_add_width (proto, width);
+    }
+
+  gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
+
+  PsppireDataStore *store = NULL;
+  g_object_get (ia->data_sheet, "data-model", &store, NULL);
+
+  struct casereader *cr = casereader_create_random (proto, n_rows,
+                                                   &my_casereader_class, ia);
+  psppire_data_store_set_reader (store, cr);
+  dict_unref (ia->casereader_dict);
+  ia->casereader_dict = dict_clone (ia->dict);
+}
+
 /* Called just before the formats page of the assistant is
    displayed. */
 static void
@@ -1284,64 +1363,17 @@ prepare_formats_page (PsppireImportAssistant *ia)
     }
   else
     {
-      PsppireDict *dict = psppire_dict_new_from_dict (ia->dict);
-      g_object_set (ia->var_sheet, "data-model", dict, NULL);
-
-      int i;
-
-      struct fmt_guesser **fg = xcalloc (sizeof *fg, dict_get_var_cnt (ia->dict));
-      for (i = 0 ; i < dict_get_var_cnt (ia->dict); ++i)
-       {
-         fg[i] = fmt_guesser_create ();
-       }
-
-      gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
-
-      GtkTreeIter iter;
-      gboolean ok;
-      for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->delimiters_model), &iter);
-          ok;
-          ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->delimiters_model), &iter))
-       {
-         for (i = 0 ; i < dict_get_var_cnt (ia->dict); ++i)
-           {
-             gchar *s = NULL;
-             gtk_tree_model_get (GTK_TREE_MODEL (ia->delimiters_model), &iter, i+1, &s, -1);
-             if (s)
-               fmt_guesser_add (fg[i], ss_cstr (s));
-             free (s);
-           }
-       }
-
-      struct caseproto *proto = caseproto_create ();
-      for (i = 0 ; i < dict_get_var_cnt (ia->dict); ++i)
-       {
-         struct fmt_spec fs;
-         fmt_guesser_guess (fg[i], &fs);
-
-         fmt_fix (&fs, FMT_FOR_INPUT);
-
-         struct variable *var = dict_get_var (ia->dict, i);
-
-         int width = fmt_var_width (&fs);
-
-         var_set_width_and_formats (var, width,
-                                    &fs, &fs);
-
-         proto = caseproto_add_width (proto, width);
-         fmt_guesser_destroy (fg[i]);
-       }
-
-      free (fg);
-
-      struct casereader *reader =
-       casereader_create_random (proto, n_rows, &my_casereader_class,  ia);
+      struct casereader *reader = textfile_create_reader (ia);
 
+      PsppireDict *dict = psppire_dict_new_from_dict (ia->dict);
       PsppireDataStore *store = psppire_data_store_new (dict);
       psppire_data_store_set_reader (store, reader);
+      g_signal_connect (dict, "variable-changed",
+                        G_CALLBACK (ia_variable_changed_cb),
+                        ia);
 
       g_object_set (ia->data_sheet, "data-model", store, NULL);
-      caseproto_unref (proto);
+      g_object_set (ia->var_sheet, "data-model", dict, NULL);
     }
 
   gint pmax;
@@ -1415,8 +1447,15 @@ separators_append_syntax (const PsppireImportAssistant *ia, struct string *s)
        }
     }
   ds_put_cstr (s, "\"\n");
-  if (!ds_is_empty (&ia->quotes))
-    syntax_gen_pspp (s, "  /QUALIFIER=%sq\n", ds_cstr (&ia->quotes));
+
+  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->quote_cb)))
+    {
+      GtkComboBoxText *cbt = GTK_COMBO_BOX_TEXT (ia->quote_combo);
+      gchar *quotes = gtk_combo_box_text_get_active_text (cbt);
+      if (quotes && *quotes)
+        syntax_gen_pspp (s, "  /QUALIFIER=%sq\n", quotes);
+      free (quotes);
+    }
 }
 
 static void
@@ -1643,4 +1682,3 @@ psppire_import_assistant_run (PsppireImportAssistant *asst)
   g_main_loop_run (asst->main_loop);
   return asst->response;
 }
-