Basic working spreadsheet import dialog (sort of)
[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-contrib/psppire-sheet.h>
24 #include <gtk/gtk.h>
25 #include <limits.h>
26 #include <stdlib.h>
27 #include <sys/stat.h>
28
29 #include "data/data-in.h"
30 #include "data/data-out.h"
31 #include "data/format-guesser.h"
32 #include "data/gnumeric-reader.h"
33 #include "data/spreadsheet-reader.h"
34 #include "data/value-labels.h"
35 #include "language/data-io/data-parser.h"
36 #include "language/lexer/lexer.h"
37 #include "libpspp/assertion.h"
38 #include "libpspp/i18n.h"
39 #include "libpspp/line-reader.h"
40 #include "libpspp/message.h"
41 #include "ui/gui/checkbox-treeview.h"
42 #include "ui/gui/dialog-common.h"
43 #include "ui/gui/executor.h"
44 #include "ui/gui/helper.h"
45 #include "ui/gui/builder-wrapper.h"
46 #include "ui/gui/psppire-data-window.h"
47 #include "ui/gui/psppire-dialog.h"
48 #include "ui/gui/psppire-encoding-selector.h"
49 #include "ui/gui/psppire-empty-list-store.h"
50 #include "ui/gui/psppire-var-sheet.h"
51 #include "ui/gui/psppire-var-store.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 /* Choosing a file and reading it. */
66
67 static char *choose_file (GtkWindow *parent_window, gchar **encodingp);
68
69 /* Obtains the file to import from the user and initializes IA's
70    file substructure.  PARENT_WINDOW must be the window to use
71    as the file chooser window's parent.
72
73    Returns true if successful, false if the file name could not
74    be obtained or the file could not be read. */
75 bool
76 init_file (struct import_assistant *ia, GtkWindow *parent_window)
77 {
78   struct file *file = &ia->file;
79   file->file_name = choose_file (parent_window, &file->encoding);
80   if (file->file_name == NULL)
81     return false;
82
83   struct dictionary *dict = NULL;
84   struct spreadsheet_read_info sri;
85
86   sri.sheet_name = NULL;
87   sri.file_name = file->file_name;
88   sri.cell_range = NULL;
89   sri.sheet_index = 1;
90   sri.read_names = true;
91   sri.asw = 0;
92
93   struct casereader *creader = gnumeric_open_reader (&sri, &dict);
94   printf ("%s:%d %s\n", __FILE__, __LINE__, sri.file_name);
95   ia->file.type = FTYPE_SPREADSHEET;
96
97   if (creader == NULL)
98   {
99     enum { MAX_PREVIEW_LINES = 1000 }; /* Max number of lines to read. */
100     enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
101
102     struct string input;
103     struct line_reader *reader = line_reader_for_file (file->encoding, file->file_name, O_RDONLY);
104     if (reader == NULL)
105       {
106         msg (ME, _("Could not open `%s': %s"),
107              file->file_name, strerror (errno));
108         return false;
109       }
110
111     ds_init_empty (&input);
112     file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines);
113     for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++)
114       {
115         ds_clear (&input);
116         if (!line_reader_read (reader, &input, MAX_LINE_LEN + 1)
117             || ds_length (&input) > MAX_LINE_LEN)
118           {
119             if (line_reader_eof (reader))
120               break;
121             else if (line_reader_error (reader))
122               msg (ME, _("Error reading `%s': %s"),
123                    file->file_name, strerror (line_reader_error (reader)));
124             else
125               msg (ME, _("Failed to read `%s', because it contains a line "
126                          "over %d bytes long and therefore appears not to be "
127                          "a text file."),
128                    file->file_name, MAX_LINE_LEN);
129             line_reader_close (reader);
130             destroy_file (ia);
131             ds_destroy (&input);
132             return false;
133           }
134
135         ds_init_cstr (&file->lines[file->line_cnt],
136                       recode_string ("UTF-8", line_reader_get_encoding (reader),
137                                      ds_cstr (&input), ds_length (&input)));
138       }
139     ds_destroy (&input);
140
141     if (file->line_cnt == 0)
142       {
143         msg (ME, _("`%s' is empty."), file->file_name);
144         line_reader_close (reader);
145         destroy_file (ia);
146         return false;
147       }
148
149     /* Estimate the number of lines in the file. */
150     if (file->line_cnt < MAX_PREVIEW_LINES)
151       file->total_lines = file->line_cnt;
152     else
153       {
154         struct stat s;
155         off_t position = line_reader_tell (reader);
156         if (fstat (line_reader_fileno (reader), &s) == 0 && position > 0)
157           file->total_lines = (double) file->line_cnt / position * s.st_size;
158         else
159           file->total_lines = 0;
160       }
161
162     line_reader_close (reader);
163     ia->file.type = FTYPE_TEXT;
164   }
165
166   return true;
167 }
168
169 /* Frees IA's file substructure. */
170 void
171 destroy_file (struct import_assistant *ia)
172 {
173   struct file *f = &ia->file;
174   size_t i;
175
176   for (i = 0; i < f->line_cnt; i++)
177     ds_destroy (&f->lines[i]);
178   free (f->lines);
179   g_free (f->file_name);
180   g_free (f->encoding);
181 }
182
183 /* Obtains the file to read from the user.  If successful, returns the name of
184    the file and stores the user's chosen encoding for the file into *ENCODINGP.
185    The caller must free each of these strings with g_free().
186
187    On failure, stores a null pointer and stores NULL in *ENCODINGP.
188
189    PARENT_WINDOW must be the window to use as the file chooser window's
190    parent. */
191 static char *
192 choose_file (GtkWindow *parent_window, gchar **encodingp)
193 {
194   char *file_name;
195   GtkFileFilter *filter = NULL;
196
197   GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
198                                         parent_window,
199                                         GTK_FILE_CHOOSER_ACTION_OPEN,
200                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
201                                         GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
202                                         NULL);
203
204   g_object_set (dialog, "local-only", FALSE, NULL);
205
206   filter = gtk_file_filter_new ();
207   gtk_file_filter_set_name (filter, _("Text files"));
208   gtk_file_filter_add_mime_type (filter, "text/*");
209   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
210
211   filter = gtk_file_filter_new ();
212   gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
213   gtk_file_filter_add_pattern (filter, "*.txt");
214   gtk_file_filter_add_pattern (filter, "*.TXT");
215   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
216
217   filter = gtk_file_filter_new ();
218   gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
219   gtk_file_filter_add_mime_type (filter, "text/plain");
220   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
221
222   filter = gtk_file_filter_new ();
223   gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
224   gtk_file_filter_add_mime_type (filter, "text/csv");
225   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
226
227   /* I've never encountered one of these, but it's listed here:
228      http://www.iana.org/assignments/media-types/text/tab-separated-values  */
229   filter = gtk_file_filter_new ();
230   gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
231   gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
232   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
233
234   filter = gtk_file_filter_new ();
235   gtk_file_filter_set_name (filter, _("Gnumeric Spreadsheet Files"));
236   gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
237   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
238
239   filter = gtk_file_filter_new ();
240   gtk_file_filter_set_name (filter, _("OpenOffice.Org Spreadsheet Files"));
241   gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
242   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
243
244   filter = gtk_file_filter_new ();
245   gtk_file_filter_set_name (filter, _("All Spreadsheet Files"));
246   gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
247   gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
248   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
249
250
251   gtk_file_chooser_set_extra_widget (
252     GTK_FILE_CHOOSER (dialog), psppire_encoding_selector_new ("Auto", true));
253
254   filter = gtk_file_filter_new ();
255   gtk_file_filter_set_name (filter, _("All Files"));
256   gtk_file_filter_add_pattern (filter, "*");
257   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
258
259   switch (gtk_dialog_run (GTK_DIALOG (dialog)))
260     {
261     case GTK_RESPONSE_ACCEPT:
262       file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
263       *encodingp = psppire_encoding_selector_get_encoding (
264         gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (dialog)));
265       break;
266     default:
267       file_name = NULL;
268       *encodingp = NULL;
269       break;
270     }
271   gtk_widget_destroy (dialog);
272
273   return file_name;
274 }