gui: Make text import assistant accept only one quote character.
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 27 May 2022 22:37:10 +0000 (15:37 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sat, 28 May 2022 01:32:23 +0000 (18:32 -0700)
This aligns with what SPSS does.

src/ui/gui/psppire-delimited-text.c
src/ui/gui/psppire-delimited-text.h
src/ui/gui/psppire-import-assistant.h
src/ui/gui/psppire-import-textfile.c
src/ui/gui/text-data-import.ui

index 6c8f0ae35b168984fe5a521466c4d3bc5d1b6c9d..59b423010ec10a118da91823d2705d4e6fc05383 100644 (file)
@@ -32,7 +32,7 @@ enum
     PROP_0,
     PROP_CHILD,
     PROP_DELIMITERS,
-    PROP_QUOTES,
+    PROP_QUOTE,
     PROP_FIRST_LINE
   };
 
@@ -64,7 +64,7 @@ count_delims (PsppireDelimitedText *tf)
 
             if (c == quote)
               quote = -1;
-            else if (c == tf->quotes[0] || c == tf->quotes[1])
+            else if (tf->quote && c == tf->quote)
               quote = c;
 
            if (quote == -1)
@@ -116,17 +116,8 @@ psppire_delimited_text_set_property (GObject         *object,
       g_slist_free (tf->delimiters);
       tf->delimiters =  g_slist_copy (g_value_get_pointer (value));
       break;
-    case PROP_QUOTES:
-      {
-        tf->quotes[0] = tf->quotes[1] = -1;
-
-        const gchar *s = g_value_get_string (value);
-        for (size_t i = 0; i < 2 && s && s[0]; i++)
-          {
-            tf->quotes[i] = g_utf8_get_char (s);
-            s = g_utf8_find_next_char (s, NULL);
-          }
-      }
+    case PROP_QUOTE:
+      tf->quote = g_value_get_uint (value);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -153,16 +144,8 @@ psppire_delimited_text_get_property (GObject         *object,
     case PROP_DELIMITERS:
       g_value_set_pointer (value, text_file->delimiters);
       break;
-    case PROP_QUOTES:
-      {
-        GString *s = g_string_new (NULL);
-        for (size_t i = 0; i < 2; i++)
-          {
-            gunichar quote = text_file->quotes[i];
-            if (quote && quote != -1)
-              g_string_append_unichar (s, quote);
-          }
-      }
+    case PROP_QUOTE:
+      g_value_set_uint (value, text_file->quote);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -387,7 +370,7 @@ split_row_into_fields (PsppireDelimitedText *file, gint n)
       gboolean char_is_quote = FALSE;
       if (quote == -1)
         {
-          if (character == file->quotes[0] || character == file->quotes[1])
+          if (file->quote && character == file->quote)
             {
               quote = character;
               char_is_quote = TRUE;
@@ -503,11 +486,11 @@ psppire_delimited_text_class_init (PsppireDelimitedTextClass *class)
                          P_("A GSList of gunichars which delimit the fields."),
                          G_PARAM_READWRITE);
 
-  GParamSpec *quotes_spec =
-    g_param_spec_string ("quotes",
-                         "Field Quotes",
-                         P_("A string of characters that quote the fields."),
-                         P_(""),
+  GParamSpec *quote_spec =
+    g_param_spec_unichar ("quote",
+                         "Quote Character",
+                         P_("A character that quotes the field, or 0 to disable quoting."),
+                         0,
                          G_PARAM_READWRITE);
 
   GParamSpec *child_spec =
@@ -529,8 +512,8 @@ psppire_delimited_text_class_init (PsppireDelimitedTextClass *class)
                                    delimiters_spec);
 
   g_object_class_install_property (object_class,
-                                   PROP_QUOTES,
-                                   quotes_spec);
+                                   PROP_QUOTE,
+                                   quote_spec);
 
   g_object_class_install_property (object_class,
                                    PROP_FIRST_LINE,
@@ -555,7 +538,7 @@ psppire_delimited_text_init (PsppireDelimitedText *text_file)
 
   text_file->max_delimiters = 0;
 
-  text_file->quotes[0] = text_file->quotes[1] = -1;
+  text_file->quote = 0;
 
   text_file->dispose_has_run = FALSE;
   text_file->stamp = g_random_int ();
index 3e1094b777c9470995d266dceff98db5d66b45fa..b5c919f4fbc16872e91cb05b007e5be6fc2700be 100644 (file)
@@ -61,7 +61,7 @@ struct _PsppireDelimitedText
   GSList *delimiters;
   gint max_delimiters;
 
-  gunichar quotes[2];
+  gunichar quote;
 
   /*< private >*/
   gboolean dispose_has_run ;
index 881fc5bad04427dd74aef0f96cc59d25a2e31913..deec75c8789931ac0fb8aeddbeb346d040cc8ca3 100644 (file)
@@ -105,8 +105,11 @@ struct _PsppireImportAssistant
 
   GtkWidget *custom_cb;
   GtkWidget *custom_entry;
-  GtkWidget *quote_cb;
-  GtkWidget *quote_combo;
+  GtkWidget *quote_none;
+  GtkWidget *quote_single;
+  GtkWidget *quote_double;
+  GtkWidget *quote_custom;
+  GtkWidget *quote_custom_entry;
 
   GtkWidget *fields_tree_view;
 
index 3034ed02f637a26d657787b5abebd04411affdec..ec53d175ede48cbab7d708262c1c3c56bc244dca 100644 (file)
 /* Chooses a name for each column on the separators page */
 static void choose_column_names (PsppireImportAssistant *ia);
 
+static gunichar
+get_quote_character (const PsppireImportAssistant *ia)
+{
+  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);
+}
+
 /* Revises the contents of the fields tree view based on the
    currently chosen set of separators and quotes. */
 static void
@@ -63,15 +82,9 @@ revise_fields_preview (PsppireImportAssistant *ia)
        }
     }
 
-  GtkComboBoxText *cbt = GTK_COMBO_BOX_TEXT (ia->quote_combo);
-  GtkToggleButton *quote_cb = GTK_TOGGLE_BUTTON (ia->quote_cb);
-  const gchar *quotes = (gtk_toggle_button_get_active (quote_cb)
-                         ? gtk_combo_box_text_get_active_text (cbt)
-                         : "");
-
   g_object_set (ia->delimiters_model,
                 "delimiters", delimiters,
-                "quotes", quotes,
+                "quote", get_quote_character (ia),
                 NULL);
 
   choose_column_names (ia);
@@ -473,33 +486,32 @@ 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)
 {
+  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_entry_set_text (GTK_ENTRY (ia->custom_entry), "");
-  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->quote_cb), TRUE);
-  gtk_combo_box_set_active (GTK_COMBO_BOX (ia->quote_combo), 0);
+  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 < N_SEPARATORS; i++)
     {
@@ -552,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_entry_set_max_length (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (ia->quote_combo))), 1);
+  gtk_entry_set_max_length (GTK_ENTRY (ia->quote_custom_entry), 1);
 
   if (ia->fields_tree_view == NULL)
     {
@@ -569,10 +584,11 @@ 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",
@@ -915,13 +931,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);
     }
 }
 
index ff830e39178fefc5ef90b8379969e2da907421c5..7bc38dbc88d26eed3e82d0d49ee71ff75a424f2f 100644 (file)
             <property name="label_xalign">0</property>
             <property name="shadow_type">none</property>
             <child>
+              <!-- n-columns=3 n-rows=4 -->
               <object class="GtkGrid" id="table1">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
-                <property name="label" translatable="yes">&lt;b&gt;Separators&lt;/b&gt;</property>
+                <property name="label" translatable="yes">&lt;b&gt;What characters separate fields?&lt;/b&gt;</property>
                 <property name="use_markup">True</property>
               </object>
             </child>
             <property name="label_xalign">0</property>
             <property name="shadow_type">none</property>
             <child>
-              <object class="GtkGrid" id="table2">
+              <object class="GtkBox">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
-                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
-                <property name="margin_start">12</property>
-                <property name="row_spacing">6</property>
-                <property name="column_spacing">6</property>
+                <property name="orientation">vertical</property>
                 <child>
-                  <object class="GtkComboBoxText" id="quote-combo">
+                  <object class="GtkRadioButton" id="quote-none">
+                    <property name="label" translatable="yes">None</property>
                     <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
-                    <property name="has_frame">False</property>
-                    <property name="has_entry">True</property>
-                    <items>
-                      <item translatable="no">"</item>
-                      <item translatable="no">'</item>
-                    </items>
-                    <child internal-child="entry">
-                      <object class="GtkEntry" id="combobox-entry1">
-                        <property name="can_focus">False</property>
-                      </object>
-                    </child>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="halign">start</property>
+                    <property name="active">True</property>
+                    <property name="draw_indicator">True</property>
                   </object>
                   <packing>
-                    <property name="left_attach">1</property>
-                    <property name="top_attach">0</property>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkCheckButton" id="quote-cb">
-                    <property name="label" translatable="yes">Quote separator characters with</property>
+                  <object class="GtkRadioButton" id="quote-single">
+                    <property name="label" translatable="yes">Single quote (')</property>
                     <property name="visible">True</property>
                     <property name="can_focus">True</property>
                     <property name="receives_default">False</property>
-                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="halign">start</property>
+                    <property name="active">True</property>
                     <property name="draw_indicator">True</property>
+                    <property name="group">quote-none</property>
                   </object>
                   <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">0</property>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkRadioButton" id="quote-double">
+                    <property name="label" translatable="yes">Double quote (")</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">False</property>
+                    <property name="halign">start</property>
+                    <property name="active">True</property>
+                    <property name="draw_indicator">True</property>
+                    <property name="group">quote-none</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkRadioButton" id="quote-custom">
+                        <property name="label" translatable="yes">Custom:</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                        <property name="group">quote-none</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="quote-custom-entry">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="width_chars">1</property>
+                        <property name="max-width_chars">1</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">3</property>
                   </packing>
                 </child>
               </object>
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
-                <property name="label" translatable="yes">&lt;b&gt;Quoting&lt;/b&gt;</property>
+                <property name="label" translatable="yes">&lt;b&gt;What character quotes fields?&lt;/b&gt;</property>
                 <property name="use_markup">True</property>
               </object>
             </child>