Use the msg function to report errors wherever possible.
[pspp] / src / ui / gui / page-file.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013  Free Software Foundation
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "ui/gui/text-data-import-dialog.h"
20
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <gtk/gtk.h>
24 #include <limits.h>
25 #include <stdlib.h>
26 #include <sys/stat.h>
27
28 #include "data/data-in.h"
29 #include "data/data-out.h"
30 #include "data/format-guesser.h"
31 #include "data/casereader.h"
32 #include "data/gnumeric-reader.h"
33 #include "data/ods-reader.h"
34 #include "data/spreadsheet-reader.h"
35 #include "data/value-labels.h"
36 #include "language/data-io/data-parser.h"
37 #include "language/lexer/lexer.h"
38 #include "libpspp/assertion.h"
39 #include "libpspp/i18n.h"
40 #include "libpspp/line-reader.h"
41 #include "libpspp/message.h"
42 #include "ui/gui/checkbox-treeview.h"
43 #include "ui/gui/dialog-common.h"
44 #include "ui/gui/executor.h"
45 #include "ui/gui/helper.h"
46 #include "ui/gui/builder-wrapper.h"
47 #include "ui/gui/psppire-data-window.h"
48 #include "ui/gui/psppire-dialog.h"
49 #include "ui/gui/psppire-encoding-selector.h"
50 #include "ui/gui/psppire-empty-list-store.h"
51 #include "ui/gui/psppire-var-sheet.h"
52 #include "ui/gui/psppire-scanf.h"
53 #include "ui/syntax-gen.h"
54
55 #include "gl/intprops.h"
56 #include "gl/xalloc.h"
57
58 #include "gettext.h"
59 #define _(msgid) gettext (msgid)
60 #define N_(msgid) msgid
61
62 struct import_assistant;
63
64 /* Choose a file */
65 static char *choose_file (GtkWindow *parent_window, gchar **encodingp);
66
67
68
69
70 /* Obtains the file to import from the user and initializes IA's
71    file substructure.  PARENT_WINDOW must be the window to use
72    as the file chooser window's parent.
73
74    Returns true if successful, false if the file name could not
75    be obtained or the file could not be read. */
76 bool
77 init_file (struct import_assistant *ia, GtkWindow *parent_window)
78 {
79   enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
80   struct file *file = &ia->file;
81
82   file->lines = NULL;
83   file->file_name = choose_file (parent_window, &file->encoding);
84   if (file->file_name == NULL)
85     return false;
86
87   if (ia->spreadsheet == NULL)
88     ia->spreadsheet = gnumeric_probe (file->file_name, false);
89
90   if (ia->spreadsheet == NULL)
91     ia->spreadsheet = ods_probe (file->file_name, false);
92
93   if (ia->spreadsheet == NULL)
94     {
95     struct string input;
96     struct line_reader *reader = line_reader_for_file (file->encoding, file->file_name, O_RDONLY);
97     if (reader == NULL)
98       {
99         msg_error (errno, _("Could not open `%s'"),
100              file->file_name);
101         return false;
102       }
103
104     ds_init_empty (&input);
105     file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines);
106     for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++)
107       {
108         ds_clear (&input);
109         if (!line_reader_read (reader, &input, MAX_LINE_LEN + 1)
110             || ds_length (&input) > MAX_LINE_LEN)
111           {
112             if (line_reader_eof (reader))
113               break;
114             else if (line_reader_error (reader))
115               msg (ME, _("Error reading `%s': %s"),
116                    file->file_name, strerror (line_reader_error (reader)));
117             else
118               msg (ME, _("Failed to read `%s', because it contains a line "
119                          "over %d bytes long and therefore appears not to be "
120                          "a text file."),
121                    file->file_name, MAX_LINE_LEN);
122             line_reader_close (reader);
123             destroy_file (ia);
124             ds_destroy (&input);
125             return false;
126           }
127
128         ds_init_cstr (&file->lines[file->line_cnt],
129                       recode_string ("UTF-8", line_reader_get_encoding (reader),
130                                      ds_cstr (&input), ds_length (&input)));
131       }
132     ds_destroy (&input);
133
134     if (file->line_cnt == 0)
135       {
136         msg (ME, _("`%s' is empty."), file->file_name);
137         line_reader_close (reader);
138         destroy_file (ia);
139         return false;
140       }
141
142     /* Estimate the number of lines in the file. */
143     if (file->line_cnt < MAX_PREVIEW_LINES)
144       file->total_lines = file->line_cnt;
145     else
146       {
147         struct stat s;
148         off_t position = line_reader_tell (reader);
149         if (fstat (line_reader_fileno (reader), &s) == 0 && position > 0)
150           file->total_lines = (double) file->line_cnt / position * s.st_size;
151         else
152           file->total_lines = 0;
153       }
154
155     line_reader_close (reader);
156   }
157
158   return true;
159 }
160
161 /* Frees IA's file substructure. */
162 void
163 destroy_file (struct import_assistant *ia)
164 {
165   struct file *f = &ia->file;
166   size_t i;
167
168   if (f->lines)
169     {
170       for (i = 0; i < f->line_cnt; i++)
171         ds_destroy (&f->lines[i]);
172       free (f->lines);
173     }
174
175   g_free (f->file_name);
176   g_free (f->encoding);
177 }
178
179 /* Obtains the file to read from the user.  If successful, returns the name of
180    the file and stores the user's chosen encoding for the file into *ENCODINGP.
181    The caller must free each of these strings with g_free().
182
183    On failure, stores a null pointer and stores NULL in *ENCODINGP.
184
185    PARENT_WINDOW must be the window to use as the file chooser window's
186    parent. */
187 static char *
188 choose_file (GtkWindow *parent_window, gchar **encodingp)
189 {
190   char *file_name;
191   GtkFileFilter *filter = NULL;
192
193   GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
194                                         parent_window,
195                                         GTK_FILE_CHOOSER_ACTION_OPEN,
196                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
197                                         GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
198                                         NULL);
199
200   g_object_set (dialog, "local-only", FALSE, NULL);
201
202   filter = gtk_file_filter_new ();
203   gtk_file_filter_set_name (filter, _("Text Files"));
204   gtk_file_filter_add_mime_type (filter, "text/*");
205   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
206
207   filter = gtk_file_filter_new ();
208   gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
209   gtk_file_filter_add_pattern (filter, "*.txt");
210   gtk_file_filter_add_pattern (filter, "*.TXT");
211   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
212
213   filter = gtk_file_filter_new ();
214   gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
215   gtk_file_filter_add_mime_type (filter, "text/plain");
216   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
217
218   filter = gtk_file_filter_new ();
219   gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
220   gtk_file_filter_add_mime_type (filter, "text/csv");
221   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
222
223   /* I've never encountered one of these, but it's listed here:
224      http://www.iana.org/assignments/media-types/text/tab-separated-values  */
225   filter = gtk_file_filter_new ();
226   gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
227   gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
228   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
229
230   filter = gtk_file_filter_new ();
231   gtk_file_filter_set_name (filter, _("Gnumeric Spreadsheet Files"));
232   gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
233   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
234
235   filter = gtk_file_filter_new ();
236   gtk_file_filter_set_name (filter, _("OpenDocument Spreadsheet Files"));
237   gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
238   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
239
240   filter = gtk_file_filter_new ();
241   gtk_file_filter_set_name (filter, _("All Spreadsheet Files"));
242   gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
243   gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
244   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
245
246   gtk_file_chooser_set_extra_widget (
247     GTK_FILE_CHOOSER (dialog), psppire_encoding_selector_new ("Auto", true));
248
249   filter = gtk_file_filter_new ();
250   gtk_file_filter_set_name (filter, _("All Files"));
251   gtk_file_filter_add_pattern (filter, "*");
252   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
253
254   switch (gtk_dialog_run (GTK_DIALOG (dialog)))
255     {
256     case GTK_RESPONSE_ACCEPT:
257       file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
258       *encodingp = psppire_encoding_selector_get_encoding (
259         gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (dialog)));
260       break;
261     default:
262       file_name = NULL;
263       *encodingp = NULL;
264       break;
265     }
266   gtk_widget_destroy (dialog);
267
268   return file_name;
269 }