Merge branch 'master' into import-gui
[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/error.h"
56 #include "gl/intprops.h"
57 #include "gl/xalloc.h"
58
59 #include "gettext.h"
60 #define _(msgid) gettext (msgid)
61 #define N_(msgid) msgid
62
63 struct import_assistant;
64
65 /* Choose a file */
66 static char *choose_file (GtkWindow *parent_window, gchar **encodingp);
67
68
69
70
71 /* Obtains the file to import from the user and initializes IA's
72    file substructure.  PARENT_WINDOW must be the window to use
73    as the file chooser window's parent.
74
75    Returns true if successful, false if the file name could not
76    be obtained or the file could not be read. */
77 bool
78 init_file (struct import_assistant *ia, GtkWindow *parent_window)
79 {
80   enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
81   struct file *file = &ia->file;
82
83   file->lines = NULL;
84   file->file_name = choose_file (parent_window, &file->encoding);
85   if (file->file_name == NULL)
86     return false;
87
88   if (ia->spreadsheet == NULL)
89     ia->spreadsheet = gnumeric_probe (file->file_name, false);
90
91   if (ia->spreadsheet == NULL)
92     ia->spreadsheet = ods_probe (file->file_name, false);
93
94   if (ia->spreadsheet == NULL)
95     {
96     struct string input;
97     struct line_reader *reader = line_reader_for_file (file->encoding, file->file_name, O_RDONLY);
98     if (reader == NULL)
99       {
100         msg (ME, _("Could not open `%s': %s"),
101              file->file_name, strerror (errno));
102         return false;
103       }
104
105     ds_init_empty (&input);
106     file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines);
107     for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++)
108       {
109         ds_clear (&input);
110         if (!line_reader_read (reader, &input, MAX_LINE_LEN + 1)
111             || ds_length (&input) > MAX_LINE_LEN)
112           {
113             if (line_reader_eof (reader))
114               break;
115             else if (line_reader_error (reader))
116               msg (ME, _("Error reading `%s': %s"),
117                    file->file_name, strerror (line_reader_error (reader)));
118             else
119               msg (ME, _("Failed to read `%s', because it contains a line "
120                          "over %d bytes long and therefore appears not to be "
121                          "a text file."),
122                    file->file_name, MAX_LINE_LEN);
123             line_reader_close (reader);
124             destroy_file (ia);
125             ds_destroy (&input);
126             return false;
127           }
128
129         ds_init_cstr (&file->lines[file->line_cnt],
130                       recode_string ("UTF-8", line_reader_get_encoding (reader),
131                                      ds_cstr (&input), ds_length (&input)));
132       }
133     ds_destroy (&input);
134
135     if (file->line_cnt == 0)
136       {
137         msg (ME, _("`%s' is empty."), file->file_name);
138         line_reader_close (reader);
139         destroy_file (ia);
140         return false;
141       }
142
143     /* Estimate the number of lines in the file. */
144     if (file->line_cnt < MAX_PREVIEW_LINES)
145       file->total_lines = file->line_cnt;
146     else
147       {
148         struct stat s;
149         off_t position = line_reader_tell (reader);
150         if (fstat (line_reader_fileno (reader), &s) == 0 && position > 0)
151           file->total_lines = (double) file->line_cnt / position * s.st_size;
152         else
153           file->total_lines = 0;
154       }
155
156     line_reader_close (reader);
157   }
158
159   return true;
160 }
161
162 /* Frees IA's file substructure. */
163 void
164 destroy_file (struct import_assistant *ia)
165 {
166   struct file *f = &ia->file;
167   size_t i;
168
169   if (f->lines)
170     {
171       for (i = 0; i < f->line_cnt; i++)
172         ds_destroy (&f->lines[i]);
173       free (f->lines);
174     }
175
176   g_free (f->file_name);
177   g_free (f->encoding);
178 }
179
180 /* Obtains the file to read from the user.  If successful, returns the name of
181    the file and stores the user's chosen encoding for the file into *ENCODINGP.
182    The caller must free each of these strings with g_free().
183
184    On failure, stores a null pointer and stores NULL in *ENCODINGP.
185
186    PARENT_WINDOW must be the window to use as the file chooser window's
187    parent. */
188 static char *
189 choose_file (GtkWindow *parent_window, gchar **encodingp)
190 {
191   char *file_name;
192   GtkFileFilter *filter = NULL;
193
194   GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
195                                         parent_window,
196                                         GTK_FILE_CHOOSER_ACTION_OPEN,
197                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
198                                         GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
199                                         NULL);
200
201   g_object_set (dialog, "local-only", FALSE, NULL);
202
203   filter = gtk_file_filter_new ();
204   gtk_file_filter_set_name (filter, _("Text Files"));
205   gtk_file_filter_add_mime_type (filter, "text/*");
206   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
207
208   filter = gtk_file_filter_new ();
209   gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
210   gtk_file_filter_add_pattern (filter, "*.txt");
211   gtk_file_filter_add_pattern (filter, "*.TXT");
212   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
213
214   filter = gtk_file_filter_new ();
215   gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
216   gtk_file_filter_add_mime_type (filter, "text/plain");
217   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
218
219   filter = gtk_file_filter_new ();
220   gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
221   gtk_file_filter_add_mime_type (filter, "text/csv");
222   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
223
224   /* I've never encountered one of these, but it's listed here:
225      http://www.iana.org/assignments/media-types/text/tab-separated-values  */
226   filter = gtk_file_filter_new ();
227   gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
228   gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
229   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
230
231   filter = gtk_file_filter_new ();
232   gtk_file_filter_set_name (filter, _("Gnumeric Spreadsheet Files"));
233   gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
234   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
235
236   filter = gtk_file_filter_new ();
237   gtk_file_filter_set_name (filter, _("OpenDocument Spreadsheet Files"));
238   gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
239   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
240
241   filter = gtk_file_filter_new ();
242   gtk_file_filter_set_name (filter, _("All Spreadsheet Files"));
243   gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
244   gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
245   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
246
247   gtk_file_chooser_set_extra_widget (
248     GTK_FILE_CHOOSER (dialog), psppire_encoding_selector_new ("Auto", true));
249
250   filter = gtk_file_filter_new ();
251   gtk_file_filter_set_name (filter, _("All Files"));
252   gtk_file_filter_add_pattern (filter, "*");
253   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
254
255   switch (gtk_dialog_run (GTK_DIALOG (dialog)))
256     {
257     case GTK_RESPONSE_ACCEPT:
258       file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
259       *encodingp = psppire_encoding_selector_get_encoding (
260         gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (dialog)));
261       break;
262     default:
263       file_name = NULL;
264       *encodingp = NULL;
265       break;
266     }
267   gtk_widget_destroy (dialog);
268
269   return file_name;
270 }