Added markup to GUI elements where necessary
[pspp] / src / ui / gui / text-data-import-dialog.c
index 10dc626609a467f961e04f14a552f871ccea1549..f8fa9c3a4623e1d43d688517991e098407675926 100644 (file)
@@ -19,6 +19,7 @@
 #include "ui/gui/text-data-import-dialog.h"
 
 #include <errno.h>
+#include <fcntl.h>
 #include <gtk-contrib/psppire-sheet.h>
 #include <gtk/gtk.h>
 #include <limits.h>
@@ -33,6 +34,7 @@
 #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/checkbox-treeview.h"
 #include "ui/gui/dialog-common.h"
@@ -41,6 +43,7 @@
 #include "ui/gui/builder-wrapper.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-var-sheet.h"
 #include "ui/gui/psppire-var-store.h"
@@ -61,6 +64,7 @@ struct import_assistant;
 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)? */
 
@@ -213,7 +217,6 @@ static GtkTreeViewColumn *make_data_column (struct import_assistant *,
                                             gint column_idx);
 static GtkTreeView *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 *);
 
@@ -359,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",
@@ -414,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
@@ -428,50 +433,55 @@ 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;
+  struct string input;
 
-  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));
       return false;
     }
 
+  ds_init_empty (&input);
   file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines);
   for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++)
     {
-      struct string *line = &file->lines[file->line_cnt];
-
-      ds_init_empty (line);
-      if (!ds_read_line (line, stream, MAX_LINE_LEN))
+      ds_clear (&input);
+      if (!line_reader_read (reader, &input, MAX_LINE_LEN + 1)
+          || ds_length (&input) > 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);
+          ds_destroy (&input);
           return false;
         }
-      ds_chomp_byte (line, '\n');
-      ds_chomp_byte (line, '\r');
+
+      ds_init_cstr (&file->lines[file->line_cnt],
+                    recode_string ("UTF-8", line_reader_get_encoding (reader),
+                                   ds_cstr (&input), ds_length (&input)));
     }
+  ds_destroy (&input);
 
   if (file->line_cnt == 0)
     {
       msg (ME, _("`%s' is empty."), file->file_name);
-      fclose (stream);
+      line_reader_close (reader);
       destroy_file (ia);
       return false;
     }
@@ -482,13 +492,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;
 }
 
@@ -503,14 +515,19 @@ 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)
 {
   char *file_name;
   GtkFileFilter *filter = NULL;
@@ -552,6 +569,9 @@ choose_file (GtkWindow *parent_window)
   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, "*");
@@ -561,9 +581,12 @@ choose_file (GtkWindow *parent_window)
     {
     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);
@@ -1078,6 +1101,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);
 }
@@ -1608,7 +1632,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);
@@ -1807,7 +1831,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)
         {
@@ -1955,6 +1979,7 @@ make_tree_view (const struct import_assistant *ia,
   g_object_set_data (G_OBJECT (model), "first-line",
                      GINT_TO_POINTER (first_line));
   gtk_tree_view_set_model (*tree_view, model);
+  g_object_unref (model);
 
   add_line_number_column (ia, *tree_view);
 }
@@ -2088,24 +2113,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;
-}
-
 /* 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. */