Automatically infer variables' measurement level from format and data.
[pspp] / src / ui / gui / psppire-import-textfile.c
index 0d9562a54fb5961dab1c30bb358b3785c75309d5..1b6a4d65bdd18163858b3aa19201d848d5895eab 100644 (file)
 /* Chooses a name for each column on the separators page */
 static void choose_column_names (PsppireImportAssistant *ia);
 
-/* Revises the contents of the fields tree view based on the
-   currently chosen set of separators. */
-static void
-revise_fields_preview (PsppireImportAssistant *ia)
+static gunichar
+get_quote_character (const PsppireImportAssistant *ia)
 {
-  choose_column_names (ia);
-}
-
-
-struct separator_count_node
-{
-  struct hmap_node node;
-  int occurance; /* The number of times the separator occurs in a line */
-  int quantity;  /* The number of lines with this occurance */
-};
+  GtkToggleButton *quote_single = GTK_TOGGLE_BUTTON (ia->quote_single);
+  GtkToggleButton *quote_double = GTK_TOGGLE_BUTTON (ia->quote_double);
+  GtkToggleButton *quote_custom = GTK_TOGGLE_BUTTON (ia->quote_custom);
 
+  if (gtk_toggle_button_get_active (quote_custom))
+    {
+      GtkEntry *quote_custom_entry = GTK_ENTRY (ia->quote_custom_entry);
+      const gchar *text = gtk_entry_get_text (quote_custom_entry);
+      return text && text[0] ? g_utf8_get_char (text) : 0;
+    }
+  else
+    return (gtk_toggle_button_get_active (quote_single) ? '\''
+            : gtk_toggle_button_get_active (quote_double) ? '"'
+            : 0);
+}
 
-/* Picks the most likely separator and quote characters based on
-   IA's file data. */
+/* Revises the contents of the fields tree view based on the
+   currently chosen set of separators and quotes. */
 static void
-choose_likely_separators (PsppireImportAssistant *ia)
+revise_fields_preview (PsppireImportAssistant *ia)
 {
-  gint first_line = 0;
-  g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
-
-  gboolean valid;
-  GtkTreeIter iter;
-  int j;
-
-  struct hmap count_map[SEPARATOR_CNT];
-  for (j = 0; j < SEPARATOR_CNT; ++j)
-    hmap_init (count_map + j);
-
-  GtkTreePath *p = gtk_tree_path_new_from_indices (first_line, -1);
-
-  for (valid = gtk_tree_model_get_iter (GTK_TREE_MODEL (ia->text_file), &iter, p);
-       valid;
-       valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->text_file), &iter))
+  GSList *delimiters = NULL;
+  for (int i = 0; i < N_SEPARATORS; i++)
     {
-      gchar *line_text = NULL;
-      gtk_tree_model_get (GTK_TREE_MODEL (ia->text_file), &iter, 1, &line_text, -1);
-
-      gint *counts = xzalloc (sizeof *counts * SEPARATOR_CNT);
-
-      struct substring cs = ss_cstr (line_text);
-      for (;
-          UINT32_MAX != ss_first_mb (cs);
-          ss_get_mb (&cs))
-       {
-         ucs4_t character = ss_first_mb (cs);
-
-         int s;
-         for (s = 0; s < SEPARATOR_CNT; ++s)
-           {
-             if (character == separators[s].c)
-               counts[s]++;
-           }
-       }
-
-      int j;
-      for (j = 0; j < SEPARATOR_CNT; ++j)
+      const struct separator *s = &separators[i];
+      GtkWidget *button = get_widget_assert (ia->text_builder, s->name);
+      if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
        {
-         if (counts[j] > 0)
-           {
-             struct separator_count_node *cn = NULL;
-             unsigned int hash = hash_int (counts[j], 0);
-             HMAP_FOR_EACH_WITH_HASH (cn, struct separator_count_node, node, hash, &count_map[j])
-               {
-                 if (cn->occurance == counts[j])
-                   break;
-               }
-
-             if (cn == NULL)
-               {
-                 struct separator_count_node *new_cn = xzalloc (sizeof *new_cn);
-                 new_cn->occurance = counts[j];
-                 new_cn->quantity = 1;
-                 hmap_insert (&count_map[j], &new_cn->node, hash);
-               }
-             else
-               cn->quantity++;
-           }
+         delimiters = g_slist_prepend (delimiters,  GINT_TO_POINTER (s->c));
        }
-
-      free (line_text);
-      free (counts);
     }
-  gtk_tree_path_free (p);
 
-  if (hmap_count (count_map) > 0)
-    {
-      int most_frequent = -1;
-      int largest = 0;
-      for (j = 0; j < SEPARATOR_CNT; ++j)
-        {
-          struct separator_count_node *cn;
-          struct separator_count_node *next;
-          HMAP_FOR_EACH_SAFE (cn, next, struct separator_count_node, node, &count_map[j])
-            {
-              if (largest < cn->quantity)
-                {
-                  largest = cn->quantity;
-                  most_frequent = j;
-                }
-              free (cn);
-            }
-          hmap_destroy (&count_map[j]);
-        }
-
-      g_return_if_fail (most_frequent >= 0);
+  g_object_set (ia->delimiters_model,
+                "delimiters", delimiters,
+                "quote", get_quote_character (ia),
+                NULL);
 
-      GtkWidget *toggle =
-        get_widget_assert (ia->text_builder, separators[most_frequent].name);
-      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), TRUE);
-    }
+  choose_column_names (ia);
 }
 
+
 static void
 repopulate_delimiter_columns (PsppireImportAssistant *ia)
 {
@@ -421,8 +348,8 @@ intro_on_enter (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTA
                                       "preview purposes in the following screens.  ",
                                       "Only the first %zu lines of the file will be shown for "
                                       "preview purposes in the following screens.  ",
-                                      ia->text_file->line_cnt),
-                        ia->text_file->line_cnt);
+                                      ia->text_file->n_lines),
+                        ia->text_file->n_lines);
        }
     }
 
@@ -534,20 +461,6 @@ static void
 on_separator_toggle (GtkToggleButton *toggle UNUSED,
                      PsppireImportAssistant *ia)
 {
-  int i;
-  GSList *delimiters = NULL;
-  for (i = 0; i < SEPARATOR_CNT; i++)
-    {
-      const struct separator *s = &separators[i];
-      GtkWidget *button = get_widget_assert (ia->text_builder, s->name);
-      if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
-       {
-         delimiters = g_slist_prepend (delimiters,  GINT_TO_POINTER (s->c));
-       }
-    }
-
-  g_object_set (ia->delimiters_model, "delimiters", delimiters, NULL);
-
   revise_fields_preview (ia);
 }
 
@@ -573,51 +486,56 @@ on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
   revise_fields_preview (ia);
 }
 
-/* Called when the user changes the selection in the combo box
-   that selects a quote character. */
+/* Called when the user changes any of the quote settings. */
 static void
-on_quote_combo_change (GtkComboBox *combo, PsppireImportAssistant *ia)
+on_quote_change (GtkWidget *widget UNUSED, PsppireImportAssistant *ia)
 {
-  //  revise_fields_preview (ia);
+  GtkToggleButton *quote_custom = GTK_TOGGLE_BUTTON (ia->quote_custom);
+  bool is_custom = gtk_toggle_button_get_active (quote_custom);
+  gtk_widget_set_sensitive (ia->quote_custom_entry, is_custom);
+  revise_fields_preview (ia);
 }
 
-/* Called when the user toggles the checkbox that enables
-   quoting. */
 static void
-on_quote_cb_toggle (GtkToggleButton *quote_cb, PsppireImportAssistant *ia)
+on_quote_custom_entry_change (GObject *gobject UNUSED,
+                              GParamSpec *arg1 UNUSED,
+                              PsppireImportAssistant *ia)
 {
-  bool is_active = gtk_toggle_button_get_active (quote_cb);
-  gtk_widget_set_sensitive (ia->quote_combo, is_active);
   revise_fields_preview (ia);
 }
 
-
 /* Called when the Reset button is clicked. */
 static void
 reset_separators_page (PsppireImportAssistant *ia)
 {
   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->custom_cb), FALSE);
-  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->quote_cb), FALSE);
   gtk_entry_set_text (GTK_ENTRY (ia->custom_entry), "");
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->quote_double), TRUE);
+  gtk_entry_set_text (GTK_ENTRY (ia->quote_custom_entry), "");
 
-  for (gint i = 0; i < SEPARATOR_CNT; i++)
+  for (gint i = 0; i < N_SEPARATORS; i++)
     {
       const struct separator *s = &separators[i];
       GtkWidget *button = get_widget_assert (ia->text_builder, s->name);
-      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
+      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), s->c == ',');
     }
 
-  repopulate_delimiter_columns (ia);
+  if (ia->delimiters_model)
+    {
+      repopulate_delimiter_columns (ia);
 
-  revise_fields_preview (ia);
-  choose_likely_separators (ia);
+      revise_fields_preview (ia);
+    }
 }
 
 /* Called just before the separators page becomes visible in the
    assistant. */
 static void
-prepare_separators_page (PsppireImportAssistant *ia)
+prepare_separators_page (PsppireImportAssistant *ia, GtkWidget *new_page, enum IMPORT_ASSISTANT_DIRECTION dir)
 {
+  if (dir != IMPORT_ASSISTANT_FORWARDS)
+    return;
+
   gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view),
                           GTK_TREE_MODEL (ia->delimiters_model));
 
@@ -646,13 +564,16 @@ 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_cb = get_widget_assert (builder, "quote-cb");
+  ia->quote_none = get_widget_assert (builder, "quote-none");
+  ia->quote_single = get_widget_assert (builder, "quote-single");
+  ia->quote_double = get_widget_assert (builder, "quote-double");
+  ia->quote_custom = get_widget_assert (builder, "quote-custom");
+  ia->quote_custom_entry = get_widget_assert (builder, "quote-custom-entry");
 
   gtk_widget_set_sensitive (ia->custom_entry,
                            gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->custom_cb)));
 
-  gtk_combo_box_set_active (GTK_COMBO_BOX (ia->quote_combo), 0);
+  gtk_entry_set_max_length (GTK_ENTRY (ia->quote_custom_entry), 1);
 
   if (ia->fields_tree_view == NULL)
     {
@@ -663,15 +584,16 @@ separators_page_create (PsppireImportAssistant *ia)
       gtk_widget_show_all (scroller);
     }
 
-  g_signal_connect (ia->quote_combo, "changed",
-                    G_CALLBACK (on_quote_combo_change), ia);
-  g_signal_connect (ia->quote_cb, "toggled",
-                    G_CALLBACK (on_quote_cb_toggle), ia);
+  g_signal_connect (ia->quote_none, "toggled", G_CALLBACK (on_quote_change), ia);
+  g_signal_connect (ia->quote_single, "toggled", G_CALLBACK (on_quote_change), ia);
+  g_signal_connect (ia->quote_double, "toggled", G_CALLBACK (on_quote_change), ia);
+  g_signal_connect (ia->quote_custom, "toggled", G_CALLBACK (on_quote_change), ia);
+  g_signal_connect (ia->quote_custom_entry, "notify::text", G_CALLBACK (on_quote_custom_entry_change), ia);
   g_signal_connect (ia->custom_entry, "notify::text",
                     G_CALLBACK (on_separators_custom_entry_notify), ia);
   g_signal_connect (ia->custom_cb, "toggled",
                     G_CALLBACK (on_separators_custom_cb_toggle), ia);
-  for (i = 0; i < SEPARATOR_CNT; i++)
+  for (i = 0; i < N_SEPARATORS; i++)
     g_signal_connect (get_widget_assert (builder, separators[i].name),
                       "toggled", G_CALLBACK (on_separator_toggle), ia);
 
@@ -719,6 +641,7 @@ my_read (struct casereader *reader, void *aux, casenumber idx)
              char *xx = data_in (ss_cstr (ss),
                                  "UTF-8",
                                  var_get_write_format (var)->type,
+                                  settings_get_fmt_settings (),
                                  v, var_get_width (var), "UTF-8");
 
              free (xx);
@@ -739,7 +662,7 @@ my_destroy (struct casereader *reader, void *aux)
 }
 
 static void
-my_advance (struct casereader *reader, void *aux, casenumber cnt)
+my_advance (struct casereader *reader, void *aux, casenumber n)
 {
   g_print ("%s:%d\n", __FILE__, __LINE__);
 }
@@ -747,7 +670,7 @@ my_advance (struct casereader *reader, void *aux, casenumber cnt)
 static struct casereader *
 textfile_create_reader (PsppireImportAssistant *ia)
 {
-  int n_vars = dict_get_var_cnt (ia->dict);
+  int n_vars = dict_get_n_vars (ia->dict);
 
   int i;
 
@@ -821,7 +744,7 @@ ia_variable_changed_cb (GObject *obj, gint var_num, guint what,
   PsppireImportAssistant *ia  = PSPPIRE_IMPORT_ASSISTANT (data);
 
   struct caseproto *proto = caseproto_create();
-  for (int i = 0; i < dict_get_var_cnt (ia->dict); i++)
+  for (int i = 0; i < dict_get_n_vars (ia->dict); i++)
     {
       const struct variable *var = dict_get_var (ia->dict, i);
       int width = var_get_width (var);
@@ -878,10 +801,9 @@ first_line_append_syntax (const PsppireImportAssistant *ia, struct string *s)
 static void
 apply_dict (const struct dictionary *dict, struct string *s)
 {
-  size_t var_cnt = dict_get_var_cnt (dict);
-  size_t i;
+  size_t n_vars = dict_get_n_vars (dict);
 
-  for (i = 0; i < var_cnt; i++)
+  for (size_t i = 0; i < n_vars; i++)
     {
       struct variable *var = dict_get_var (dict, i);
       const char *name = var_get_name (var);
@@ -920,12 +842,11 @@ apply_dict (const struct dictionary *dict, struct string *s)
           const struct val_labs *vls = var_get_value_labels (var);
           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 (i = 0; i < n_labels; i++)
+          for (size_t j = 0; j < n_labels; j++)
             {
-              const struct val_lab *vl = labels[i];
+              const struct val_lab *vl = labels[j];
               ds_put_cstr (s, "\n  ");
               syntax_gen_value (s, &vl->value, width, format);
               ds_put_byte (s, ' ');
@@ -937,7 +858,8 @@ apply_dict (const struct dictionary *dict, struct string *s)
       if (var_has_label (var))
         syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
                          name, var_get_label (var));
-      if (measure != var_default_measure (type))
+      if (measure != var_default_measure_for_type (type)
+          && measure != MEASURE_UNKNOWN)
         syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
                          name, measure_to_syntax (measure));
       if (role != ROLE_INPUT)
@@ -971,22 +893,19 @@ intro_append_syntax (const PsppireImportAssistant *ia, struct string *s)
 static void
 formats_append_syntax (const PsppireImportAssistant *ia, struct string *s)
 {
-  int i;
-  int var_cnt;
-
   g_return_if_fail (ia->dict);
 
   ds_put_cstr (s, "  /VARIABLES=\n");
 
-  var_cnt = dict_get_var_cnt (ia->dict);
-  for (i = 0; i < var_cnt; i++)
+  int n_vars = dict_get_n_vars (ia->dict);
+  for (int i = 0; i < n_vars; i++)
     {
       struct variable *var = dict_get_var (ia->dict, i);
       char format_string[FMT_STRING_LEN_MAX + 1];
       fmt_to_string (var_get_print_format (var), format_string);
       ds_put_format (s, "    %s %s%s\n",
                     var_get_name (var), format_string,
-                    i == var_cnt - 1 ? "." : "");
+                    i == n_vars - 1 ? "." : "");
     }
 }
 
@@ -999,7 +918,7 @@ separators_append_syntax (const PsppireImportAssistant *ia, struct string *s)
 
   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->text_builder, "tab"))))
     ds_put_cstr (s, "\\t");
-  for (i = 0; i < SEPARATOR_CNT; i++)
+  for (i = 0; i < N_SEPARATORS; i++)
     {
       const struct separator *seps = &separators[i];
       GtkWidget *button = get_widget_assert (ia->text_builder, seps->name);
@@ -1013,13 +932,13 @@ separators_append_syntax (const PsppireImportAssistant *ia, struct string *s)
     }
   ds_put_cstr (s, "\"\n");
 
-  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->quote_cb)))
+  gunichar quote = get_quote_character (ia);
+  if (quote)
     {
-      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);
+      GString *quote_s = g_string_new (NULL);
+      g_string_append_unichar (quote_s, quote);
+      syntax_gen_pspp (s, "  /QUALIFIER=%sq\n", quote_s->str);
+      g_string_free (quote_s, TRUE);
     }
 }