psppire-import: fixed bug with short csv file import
[pspp] / src / ui / gui / psppire-import-assistant.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2015, 2016, 2017, 2018, 2020  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 <gtk/gtk.h>
20
21 #include "data/casereader.h"
22 #include "data/data-in.h"
23 #include "data/data-out.h"
24 #include "data/dictionary.h"
25 #include "data/format-guesser.h"
26 #include "data/format.h"
27 #include "data/gnumeric-reader.h"
28 #include "data/ods-reader.h"
29 #include "data/spreadsheet-reader.h"
30 #include "data/value-labels.h"
31 #include "data/casereader-provider.h"
32
33 #include "libpspp/i18n.h"
34 #include "libpspp/line-reader.h"
35 #include "libpspp/message.h"
36 #include "libpspp/hmap.h"
37 #include "libpspp/hash-functions.h"
38 #include "libpspp/str.h"
39
40 #include "builder-wrapper.h"
41
42 #include "psppire-data-sheet.h"
43 #include "psppire-data-store.h"
44 #include "psppire-dialog.h"
45 #include "psppire-delimited-text.h"
46 #include "psppire-dict.h"
47 #include "psppire-encoding-selector.h"
48 #include "psppire-import-assistant.h"
49 #include "psppire-scanf.h"
50 #include "psppire-spreadsheet-model.h"
51 #include "psppire-text-file.h"
52 #include "psppire-variable-sheet.h"
53
54 #include "ui/syntax-gen.h"
55
56 #include <gettext.h>
57 #define _(msgid) gettext (msgid)
58 #define N_(msgid) msgid
59
60 enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
61
62
63 /* Chooses a name for each column on the separators page */
64 static void choose_column_names (PsppireImportAssistant *ia);
65
66 static void intro_page_create (PsppireImportAssistant *ia);
67 static void first_line_page_create (PsppireImportAssistant *ia);
68
69 static void separators_page_create (PsppireImportAssistant *ia);
70 static void formats_page_create (PsppireImportAssistant *ia);
71
72 static void psppire_import_assistant_init            (PsppireImportAssistant      *act);
73 static void psppire_import_assistant_class_init      (PsppireImportAssistantClass *class);
74
75 G_DEFINE_TYPE (PsppireImportAssistant, psppire_import_assistant, GTK_TYPE_ASSISTANT);
76
77
78 /* Properties */
79 enum
80   {
81     PROP_0,
82   };
83
84 static void
85 psppire_import_assistant_set_property (GObject         *object,
86                                        guint            prop_id,
87                                        const GValue    *value,
88                                        GParamSpec      *pspec)
89 {
90   //   PsppireImportAssistant *act = PSPPIRE_IMPORT_ASSISTANT (object);
91
92   switch (prop_id)
93     {
94     default:
95       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
96       break;
97     };
98 }
99
100
101 static void
102 psppire_import_assistant_get_property (GObject    *object,
103                                        guint            prop_id,
104                                        GValue          *value,
105                                        GParamSpec      *pspec)
106 {
107   //  PsppireImportAssistant *assistant = PSPPIRE_IMPORT_ASSISTANT (object);
108
109   switch (prop_id)
110     {
111     default:
112       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
113       break;
114     };
115 }
116
117 static GObjectClass * parent_class = NULL;
118
119
120 static void
121 psppire_import_assistant_finalize (GObject *object)
122 {
123   PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (object);
124
125   if (ia->spreadsheet)
126     spreadsheet_unref (ia->spreadsheet);
127
128   ds_destroy (&ia->quotes);
129
130   dict_unref (ia->dict);
131   dict_unref (ia->casereader_dict);
132
133   g_object_unref (ia->builder);
134
135   ia->response = -1;
136   g_main_loop_unref (ia->main_loop);
137
138   if (G_OBJECT_CLASS (parent_class)->finalize)
139     G_OBJECT_CLASS (parent_class)->finalize (object);
140 }
141
142
143 static void
144 psppire_import_assistant_class_init (PsppireImportAssistantClass *class)
145 {
146   GObjectClass *object_class = G_OBJECT_CLASS (class);
147
148   parent_class = g_type_class_peek_parent (class);
149
150   object_class->set_property = psppire_import_assistant_set_property;
151   object_class->get_property = psppire_import_assistant_get_property;
152
153   object_class->finalize = psppire_import_assistant_finalize;
154 }
155
156
157 /* Causes the assistant to close, returning RESPONSE for
158    interpretation by text_data_import_assistant. */
159 static void
160 close_assistant (PsppireImportAssistant *ia, int response)
161 {
162   ia->response = response;
163   g_main_loop_quit (ia->main_loop);
164   gtk_widget_hide (GTK_WIDGET (ia));
165 }
166
167
168 /* Called when the Paste button on the last page of the assistant
169    is clicked. */
170 static void
171 on_paste (GtkButton *button, PsppireImportAssistant *ia)
172 {
173   close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
174 }
175
176
177 /* Revises the contents of the fields tree view based on the
178    currently chosen set of separators. */
179 static void
180 revise_fields_preview (PsppireImportAssistant *ia)
181 {
182   choose_column_names (ia);
183 }
184
185
186 struct separator
187 {
188   const char *name;           /* Name (for use with get_widget_assert). */
189   gunichar c;                 /* Separator character. */
190 };
191
192 /* All the separators in the dialog box. */
193 static const struct separator separators[] =
194   {
195     {"space",     ' '},
196     {"tab",       '\t'},
197     {"bang",      '!'},
198     {"colon",     ':'},
199     {"comma",     ','},
200     {"hyphen",    '-'},
201     {"pipe",      '|'},
202     {"semicolon", ';'},
203     {"slash",     '/'},
204   };
205
206 #define SEPARATOR_CNT (sizeof separators / sizeof *separators)
207
208 struct separator_count_node
209 {
210   struct hmap_node node;
211   int occurance; /* The number of times the separator occurs in a line */
212   int quantity;  /* The number of lines with this occurance */
213 };
214
215
216 /* Picks the most likely separator and quote characters based on
217    IA's file data. */
218 static void
219 choose_likely_separators (PsppireImportAssistant *ia)
220 {
221   gint first_line = 0;
222   g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
223
224   gboolean valid;
225   GtkTreeIter iter;
226   int j;
227
228   struct hmap count_map[SEPARATOR_CNT];
229   for (j = 0; j < SEPARATOR_CNT; ++j)
230     hmap_init (count_map + j);
231
232   GtkTreePath *p = gtk_tree_path_new_from_indices (first_line, -1);
233
234   for (valid = gtk_tree_model_get_iter (GTK_TREE_MODEL (ia->text_file), &iter, p);
235        valid;
236        valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->text_file), &iter))
237     {
238       gchar *line_text = NULL;
239       gtk_tree_model_get (GTK_TREE_MODEL (ia->text_file), &iter, 1, &line_text, -1);
240
241       gint *counts = xzalloc (sizeof *counts * SEPARATOR_CNT);
242
243       struct substring cs = ss_cstr (line_text);
244       for (;
245            UINT32_MAX != ss_first_mb (cs);
246            ss_get_mb (&cs))
247         {
248           ucs4_t character = ss_first_mb (cs);
249
250           int s;
251           for (s = 0; s < SEPARATOR_CNT; ++s)
252             {
253               if (character == separators[s].c)
254                 counts[s]++;
255             }
256         }
257
258       int j;
259       for (j = 0; j < SEPARATOR_CNT; ++j)
260         {
261           if (counts[j] > 0)
262             {
263               struct separator_count_node *cn = NULL;
264               unsigned int hash = hash_int (counts[j], 0);
265               HMAP_FOR_EACH_WITH_HASH (cn, struct separator_count_node, node, hash, &count_map[j])
266                 {
267                   if (cn->occurance == counts[j])
268                     break;
269                 }
270
271               if (cn == NULL)
272                 {
273                   struct separator_count_node *new_cn = xzalloc (sizeof *new_cn);
274                   new_cn->occurance = counts[j];
275                   new_cn->quantity = 1;
276                   hmap_insert (&count_map[j], &new_cn->node, hash);
277                 }
278               else
279                 cn->quantity++;
280             }
281         }
282
283       free (line_text);
284       free (counts);
285     }
286   gtk_tree_path_free (p);
287
288   int most_frequent = -1;
289   int largest = 0;
290   for (j = 0; j < SEPARATOR_CNT; ++j)
291     {
292       struct separator_count_node *cn;
293       HMAP_FOR_EACH (cn, struct separator_count_node, node, &count_map[j])
294         {
295           if (largest < cn->quantity)
296             {
297               largest = cn->quantity;
298               most_frequent = j;
299             }
300         }
301       hmap_destroy (&count_map[j]);
302     }
303
304   const char* sepname = separators[MAX (0,most_frequent)].name;
305   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->builder, sepname)), TRUE);
306 }
307
308 static void
309 repopulate_delimiter_columns (PsppireImportAssistant *ia)
310 {
311   /* Remove all the columns */
312   while (gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view)) > 0)
313     {
314       GtkTreeViewColumn *tvc = gtk_tree_view_get_column (GTK_TREE_VIEW (ia->fields_tree_view), 0);
315       gtk_tree_view_remove_column (GTK_TREE_VIEW (ia->fields_tree_view), tvc);
316     }
317
318   gint n_fields =
319     gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model));
320
321   /* ... and put them back again. */
322   gint f;
323   for (f = gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view));
324        f < n_fields; f++)
325     {
326       GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
327
328       const gchar *title = NULL;
329
330       if (f == 0)
331         title = _("line");
332       else
333         {
334           if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
335             {
336               title =
337                 psppire_delimited_text_get_header_title
338                 (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), f - 1);
339             }
340           if (title == NULL)
341             title = _("var");
342         }
343
344       GtkTreeViewColumn *column =
345         gtk_tree_view_column_new_with_attributes (title,
346                                                   renderer,
347                                                   "text", f,
348                                                   NULL);
349       g_object_set (column,
350                     "resizable", TRUE,
351                     "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE,
352                     NULL);
353
354       gtk_tree_view_append_column (GTK_TREE_VIEW (ia->fields_tree_view), column);
355     }
356 }
357
358 static void
359 reset_tree_view_model (PsppireImportAssistant *ia)
360 {
361   GtkTreeModel *tm = gtk_tree_view_get_model (GTK_TREE_VIEW (ia->fields_tree_view));
362   g_object_ref (tm);
363   gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), NULL);
364
365
366   repopulate_delimiter_columns (ia);
367
368   gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), tm);
369   //  gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ia->fields_tree_view));
370
371   g_object_unref (tm);
372 }
373
374 /* Called just before the separators page becomes visible in the
375    assistant, and when the Reset button is clicked. */
376 static void
377 prepare_separators_page (PsppireImportAssistant *ia, GtkWidget *page)
378 {
379   gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view),
380                            GTK_TREE_MODEL (ia->delimiters_model));
381
382   g_signal_connect_swapped (GTK_TREE_MODEL (ia->delimiters_model), "notify::delimiters",
383                         G_CALLBACK (reset_tree_view_model), ia);
384
385
386   repopulate_delimiter_columns (ia);
387
388   revise_fields_preview (ia);
389   choose_likely_separators (ia);
390 }
391
392 /* Resets IA's intro page to its initial state. */
393 static void
394 reset_intro_page (PsppireImportAssistant *ia)
395 {
396   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->all_cases_button),
397                                 TRUE);
398 }
399
400
401
402 /* Clears the set of user-modified variables from IA's formats
403    substructure.  This discards user modifications to variable
404    formats, thereby causing formats to revert to their
405    defaults.  */
406 static void
407 reset_formats_page (PsppireImportAssistant *ia, GtkWidget *page)
408 {
409 }
410
411 static void prepare_formats_page (PsppireImportAssistant *ia);
412
413 /* Called when the Reset button is clicked. */
414 static void
415 on_reset (GtkButton *button, PsppireImportAssistant *ia)
416 {
417   gint pn = gtk_assistant_get_current_page (GTK_ASSISTANT (ia));
418   {
419     GtkWidget *page =  gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), pn);
420
421     page_func *on_reset = g_object_get_data (G_OBJECT (page), "on-reset");
422
423     if (on_reset)
424       on_reset (ia, page);
425   }
426 }
427
428
429 static gint
430 next_page_func (gint old_page, gpointer data)
431 {
432   return old_page + 1;
433 }
434
435
436 /* Called just before PAGE is displayed as the current page of
437    IMPORT_ASSISTANT, this updates IA content according to the new
438    page. */
439 static void
440 on_prepare (GtkAssistant *assistant, GtkWidget *page, PsppireImportAssistant *ia)
441 {
442   gtk_widget_show (ia->reset_button);
443   gtk_widget_hide (ia->paste_button);
444
445   gint pn = gtk_assistant_get_current_page (assistant);
446   gint previous_page_index = ia->current_page;
447
448   if (previous_page_index >= 0)
449     {
450       GtkWidget *closing_page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), previous_page_index);
451
452       if (pn > previous_page_index)
453         {
454           page_func *on_forward = g_object_get_data (G_OBJECT (closing_page), "on-forward");
455
456           if (on_forward)
457             on_forward (ia, closing_page);
458         }
459       else
460         {
461           page_func *on_back = g_object_get_data (G_OBJECT (closing_page), "on-back");
462
463           if (on_back)
464             on_back (ia, closing_page);
465         }
466     }
467
468   {
469     GtkWidget *new_page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), pn);
470
471     page_func *on_entering = g_object_get_data (G_OBJECT (new_page), "on-entering");
472
473     if (on_entering)
474       on_entering (ia, new_page);
475   }
476
477   ia->current_page = pn;
478 }
479
480 /* Called when the Cancel button in the assistant is clicked. */
481 static void
482 on_cancel (GtkAssistant *assistant, PsppireImportAssistant *ia)
483 {
484   close_assistant (ia, GTK_RESPONSE_CANCEL);
485 }
486
487 /* Called when the Apply button on the last page of the assistant
488    is clicked. */
489 static void
490 on_close (GtkAssistant *assistant, PsppireImportAssistant *ia)
491 {
492   close_assistant (ia, GTK_RESPONSE_APPLY);
493 }
494
495
496 static GtkWidget *
497 add_page_to_assistant (PsppireImportAssistant *ia,
498                        GtkWidget *page, GtkAssistantPageType type, const gchar *);
499
500
501 static void
502 on_sheet_combo_changed (GtkComboBox *cb, PsppireImportAssistant *ia)
503 {
504   GtkTreeIter iter;
505   gchar *range = NULL;
506   GtkTreeModel *model = gtk_combo_box_get_model (cb);
507   GtkBuilder *builder = ia->builder;
508   GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
509
510   gtk_combo_box_get_active_iter (cb, &iter);
511   gtk_tree_model_get (model, &iter, PSPPIRE_SPREADSHEET_MODEL_COL_RANGE, &range, -1);
512   gtk_entry_set_text (GTK_ENTRY (range_entry), range ?  range : "");
513   g_free (range);
514 }
515
516 /* Prepares IA's sheet_spec page. */
517 static void
518 prepare_sheet_spec_page (PsppireImportAssistant *ia)
519 {
520   GtkBuilder *builder = ia->builder;
521   GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
522   GtkWidget *readnames_checkbox = get_widget_assert (builder, "readnames-checkbox");
523
524   gtk_combo_box_set_model (GTK_COMBO_BOX (sheet_entry),
525                            psppire_spreadsheet_model_new (ia->spreadsheet));
526
527   gtk_combo_box_set_active (GTK_COMBO_BOX (sheet_entry), 0);
528
529   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (readnames_checkbox), FALSE);
530 }
531
532
533 /* Initializes IA's sheet_spec substructure. */
534 static void
535 sheet_spec_page_create (PsppireImportAssistant *ia)
536 {
537   GtkBuilder *builder = ia->builder;
538   GtkWidget *page = get_widget_assert (builder, "Spreadsheet-Importer");
539
540   GtkWidget *combo_box = get_widget_assert (builder, "sheet-entry");
541   GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
542   gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo_box));
543   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
544   gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), renderer,
545                                   "text", 0,
546                                   NULL);
547
548   g_signal_connect (combo_box, "changed", G_CALLBACK (on_sheet_combo_changed), ia);
549
550   add_page_to_assistant (ia, page,
551                          GTK_ASSISTANT_PAGE_CONTENT, _("Importing Spreadsheet Data"));
552
553   g_object_set_data (G_OBJECT (page), "on-entering", prepare_sheet_spec_page);
554 }
555
556 static void
557 on_chosen (PsppireImportAssistant *ia, GtkWidget *page)
558 {
559   GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
560   gchar *f = gtk_file_chooser_get_filename (fc);
561   int i;
562
563   for(i = gtk_assistant_get_n_pages (GTK_ASSISTANT (ia)); i > 0; --i)
564     gtk_assistant_remove_page (GTK_ASSISTANT (ia), i);
565
566   gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), GTK_WIDGET (fc), FALSE);
567
568   if (f && g_file_test (f, G_FILE_TEST_IS_REGULAR))
569     {
570       gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), GTK_WIDGET (fc), TRUE);
571
572       if (ia->spreadsheet)
573         spreadsheet_unref (ia->spreadsheet);
574
575       ia->spreadsheet = gnumeric_probe (f, FALSE);
576
577       if (!ia->spreadsheet)
578         ia->spreadsheet = ods_probe (f, FALSE);
579
580       if (ia->spreadsheet)
581         {
582           sheet_spec_page_create (ia);
583         }
584       else
585         {
586           intro_page_create (ia);
587           first_line_page_create (ia);
588           separators_page_create (ia);
589         }
590
591       formats_page_create (ia);
592     }
593
594   g_free (f);
595 }
596
597 /* This has to be done on a map signal callback,
598    because GtkFileChooserWidget resets everything when it is mapped. */
599 static void
600 on_map (PsppireImportAssistant *ia, GtkWidget *page)
601 {
602 #if TEXT_FILE
603   GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
604
605   if (ia->file_name)
606     gtk_file_chooser_set_filename (fc, ia->file_name);
607 #endif
608
609   on_chosen (ia, page);
610 }
611
612
613
614 static void
615 chooser_page_enter (PsppireImportAssistant *ia, GtkWidget *page)
616 {
617 }
618
619 static void
620 chooser_page_leave (PsppireImportAssistant *ia, GtkWidget *page)
621 {
622   g_free (ia->file_name);
623   ia->file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (page));
624   gchar *encoding = psppire_encoding_selector_get_encoding (ia->encoding_selector);
625
626   if (!ia->spreadsheet)
627     {
628       ia->text_file = psppire_text_file_new (ia->file_name, encoding);
629       gtk_tree_view_set_model (GTK_TREE_VIEW (ia->first_line_tree_view),
630                                GTK_TREE_MODEL (ia->text_file));
631     }
632
633
634   g_free (encoding);
635 }
636
637 static void
638 chooser_page_reset (PsppireImportAssistant *ia, GtkWidget *page)
639 {
640   GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
641
642   gtk_file_chooser_set_filter (fc, ia->default_filter);
643   gtk_file_chooser_unselect_all (fc);
644
645   on_chosen (ia, page);
646 }
647
648
649
650 static void
651 chooser_page_create (PsppireImportAssistant *ia)
652 {
653   GtkFileFilter *filter = NULL;
654
655   GtkWidget *chooser = gtk_file_chooser_widget_new (GTK_FILE_CHOOSER_ACTION_OPEN);
656
657   g_object_set_data (G_OBJECT (chooser), "on-forward", chooser_page_leave);
658   g_object_set_data (G_OBJECT (chooser), "on-reset",   chooser_page_reset);
659   g_object_set_data (G_OBJECT (chooser), "on-entering",chooser_page_enter);
660
661   g_object_set (chooser, "local-only", FALSE, NULL);
662
663
664   ia->default_filter = gtk_file_filter_new ();
665   gtk_file_filter_set_name (ia->default_filter, _("All Files"));
666   gtk_file_filter_add_pattern (ia->default_filter, "*");
667   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), ia->default_filter);
668
669   filter = gtk_file_filter_new ();
670   gtk_file_filter_set_name (filter, _("Text Files"));
671   gtk_file_filter_add_mime_type (filter, "text/*");
672   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
673
674   filter = gtk_file_filter_new ();
675   gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
676   gtk_file_filter_add_pattern (filter, "*.txt");
677   gtk_file_filter_add_pattern (filter, "*.TXT");
678   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
679
680   filter = gtk_file_filter_new ();
681   gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
682   gtk_file_filter_add_mime_type (filter, "text/plain");
683   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
684
685   filter = gtk_file_filter_new ();
686   gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
687   gtk_file_filter_add_mime_type (filter, "text/csv");
688   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
689
690   /* I've never encountered one of these, but it's listed here:
691      http://www.iana.org/assignments/media-types/text/tab-separated-values  */
692   filter = gtk_file_filter_new ();
693   gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
694   gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
695   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
696
697   filter = gtk_file_filter_new ();
698   gtk_file_filter_set_name (filter, _("Gnumeric Spreadsheet Files"));
699   gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
700   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
701
702   filter = gtk_file_filter_new ();
703   gtk_file_filter_set_name (filter, _("OpenDocument Spreadsheet Files"));
704   gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
705   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
706
707   filter = gtk_file_filter_new ();
708   gtk_file_filter_set_name (filter, _("All Spreadsheet Files"));
709   gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
710   gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
711   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
712
713   ia->encoding_selector = psppire_encoding_selector_new ("Auto", TRUE);
714   gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (chooser), ia->encoding_selector);
715
716   add_page_to_assistant (ia, chooser,
717                          GTK_ASSISTANT_PAGE_INTRO, _("Select File to Import"));
718
719   g_signal_connect_swapped (chooser, "selection-changed", G_CALLBACK (on_chosen), ia);
720   g_signal_connect_swapped (chooser, "map", G_CALLBACK (on_map), ia);
721 }
722
723
724
725 static void
726 psppire_import_assistant_init (PsppireImportAssistant *ia)
727 {
728   ia->builder = builder_new ("text-data-import.ui");
729
730   ia->current_page = -1 ;
731   ia->file_name = NULL;
732
733   ia->spreadsheet = NULL;
734   ia->dict = NULL;
735   ia->casereader_dict = NULL;
736
737   ia->main_loop = g_main_loop_new (NULL, TRUE);
738
739   g_signal_connect (ia, "prepare", G_CALLBACK (on_prepare), ia);
740   g_signal_connect (ia, "cancel", G_CALLBACK (on_cancel), ia);
741   g_signal_connect (ia, "close", G_CALLBACK (on_close), ia);
742
743   ia->paste_button = gtk_button_new_with_label (_("Paste"));
744   ia->reset_button = gtk_button_new_with_label (_("Reset"));
745
746   gtk_assistant_add_action_widget (GTK_ASSISTANT(ia), ia->paste_button);
747
748   g_signal_connect (ia->paste_button, "clicked", G_CALLBACK (on_paste), ia);
749   g_signal_connect (ia->reset_button, "clicked", G_CALLBACK (on_reset), ia);
750
751   gtk_assistant_add_action_widget (GTK_ASSISTANT(ia), ia->reset_button);
752
753   gtk_window_set_title (GTK_WINDOW (ia),
754                         _("Importing Delimited Text Data"));
755
756   gtk_window_set_icon_name (GTK_WINDOW (ia), "pspp");
757
758   chooser_page_create (ia);
759
760   gtk_assistant_set_forward_page_func (GTK_ASSISTANT (ia), next_page_func, NULL, NULL);
761
762   gtk_window_maximize (GTK_WINDOW (ia));
763 }
764
765
766 /* Appends a page of the given TYPE, with PAGE as its content, to
767    the GtkAssistant encapsulated by IA.  Returns the GtkWidget
768    that represents the page. */
769 static GtkWidget *
770 add_page_to_assistant (PsppireImportAssistant *ia,
771                        GtkWidget *page, GtkAssistantPageType type, const gchar *title)
772 {
773   GtkWidget *content = page;
774
775   gtk_assistant_append_page (GTK_ASSISTANT (ia), content);
776   gtk_assistant_set_page_type (GTK_ASSISTANT(ia), content, type);
777   gtk_assistant_set_page_title (GTK_ASSISTANT(ia), content, title);
778   gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), content, TRUE);
779
780   return content;
781 }
782
783
784 /* Called when one of the radio buttons is clicked. */
785 static void
786 on_intro_amount_changed (PsppireImportAssistant *p)
787 {
788   gtk_widget_set_sensitive (p->n_cases_spin,
789                             gtk_toggle_button_get_active
790                             (GTK_TOGGLE_BUTTON (p->n_cases_button)));
791
792   gtk_widget_set_sensitive (p->percent_spin,
793                             gtk_toggle_button_get_active
794                             (GTK_TOGGLE_BUTTON (p->percent_button)));
795 }
796
797 static void
798 on_treeview_selection_change (PsppireImportAssistant *ia)
799 {
800   GtkTreeSelection *selection =
801     gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view));
802   GtkTreeModel *model = NULL;
803   GtkTreeIter iter;
804   if (gtk_tree_selection_get_selected (selection, &model, &iter))
805     {
806       gint max_lines;
807       int n;
808       GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
809       gint *index = gtk_tree_path_get_indices (path);
810       n = *index;
811       gtk_tree_path_free (path);
812       g_object_get (model, "maximum-lines", &max_lines, NULL);
813       gtk_widget_set_sensitive (ia->variable_names_cb,
814                                 (n > 0 && n < max_lines));
815       ia->delimiters_model =
816         psppire_delimited_text_new (GTK_TREE_MODEL (ia->text_file));
817       g_object_set (ia->delimiters_model, "first-line", n, NULL);
818     }
819 }
820
821 static void
822 render_text_preview_line (GtkTreeViewColumn *tree_column,
823                 GtkCellRenderer *cell,
824                 GtkTreeModel *tree_model,
825                 GtkTreeIter *iter,
826                 gpointer data)
827 {
828   /*
829      Set the text  to a "insensitive" state if the row
830      is greater than what the user declared to be the maximum.
831   */
832   GtkTreePath *path = gtk_tree_model_get_path (tree_model, iter);
833   gint *ii = gtk_tree_path_get_indices (path);
834   gint max_lines;
835   g_object_get (tree_model, "maximum-lines", &max_lines, NULL);
836   g_object_set (cell, "sensitive", (*ii < max_lines), NULL);
837   gtk_tree_path_free (path);
838 }
839
840 /* Initializes IA's first_line substructure. */
841 static void
842 first_line_page_create (PsppireImportAssistant *ia)
843 {
844   GtkWidget *w =  get_widget_assert (ia->builder, "FirstLine");
845
846   g_object_set_data (G_OBJECT (w), "on-entering", on_treeview_selection_change);
847
848   add_page_to_assistant (ia, w,
849                          GTK_ASSISTANT_PAGE_CONTENT, _("Select the First Line"));
850
851   GtkWidget *scrolled_window = get_widget_assert (ia->builder, "first-line-scroller");
852
853   if (ia->first_line_tree_view == NULL)
854     {
855       ia->first_line_tree_view = gtk_tree_view_new ();
856       g_object_set (ia->first_line_tree_view, "enable-search", FALSE, NULL);
857
858       gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ia->first_line_tree_view), TRUE);
859
860       GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
861       GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Line"), renderer,
862                                                                             "text", 0,
863                                                                             NULL);
864
865       gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
866       gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
867
868       renderer = gtk_cell_renderer_text_new ();
869       column = gtk_tree_view_column_new_with_attributes (_("Text"), renderer, "text", 1, NULL);
870       gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
871
872       gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
873
874       g_signal_connect_swapped (ia->first_line_tree_view, "cursor-changed",
875                                 G_CALLBACK (on_treeview_selection_change), ia);
876       gtk_container_add (GTK_CONTAINER (scrolled_window), ia->first_line_tree_view);
877     }
878
879   gtk_widget_show_all (scrolled_window);
880
881   ia->variable_names_cb = get_widget_assert (ia->builder, "variable-names");
882 }
883
884 static void
885 intro_on_leave (PsppireImportAssistant *ia)
886 {
887   gint lc = 0;
888   g_object_get (ia->text_file, "line-count", &lc, NULL);
889   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
890     {
891       gint max_lines = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin));
892       g_object_set (ia->text_file, "maximum-lines", max_lines, NULL);
893     }
894   else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
895     {
896       gdouble percent = gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin));
897       g_object_set (ia->text_file, "maximum-lines", (gint) (lc * percent / 100.0), NULL);
898     }
899   else
900     {
901       g_object_set (ia->text_file, "maximum-lines", lc, NULL);
902     }
903 }
904
905
906 static void
907 intro_on_enter (PsppireImportAssistant *ia)
908 {
909   GtkBuilder *builder = ia->builder;
910   GtkWidget *table  = get_widget_assert (builder, "button-table");
911
912   struct string s;
913
914   ds_init_empty (&s);
915   ds_put_cstr (&s, _("This assistant will guide you through the process of "
916                      "importing data into PSPP from a text file with one line "
917                      "per case,  in which fields are separated by tabs, "
918                      "commas, or other delimiters.\n\n"));
919
920   if (ia->text_file)
921     {
922       if (ia->text_file->total_is_exact)
923         {
924           ds_put_format (
925                          &s, ngettext ("The selected file contains %'lu line of text.  ",
926                                        "The selected file contains %'lu lines of text.  ",
927                                        ia->text_file->total_lines),
928                          ia->text_file->total_lines);
929         }
930       else if (ia->text_file->total_lines > 0)
931         {
932           ds_put_format (
933                          &s, ngettext (
934                                        "The selected file contains approximately %'lu line of text.  ",
935                                        "The selected file contains approximately %'lu lines of text.  ",
936                                        ia->text_file->total_lines),
937                          ia->text_file->total_lines);
938           ds_put_format (
939                          &s, ngettext (
940                                        "Only the first %zu line of the file will be shown for "
941                                        "preview purposes in the following screens.  ",
942                                        "Only the first %zu lines of the file will be shown for "
943                                        "preview purposes in the following screens.  ",
944                                        ia->text_file->line_cnt),
945                          ia->text_file->line_cnt);
946         }
947     }
948
949   ds_put_cstr (&s, _("You may choose below how much of the file should "
950                      "actually be imported."));
951
952   gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
953                       ds_cstr (&s));
954   ds_destroy (&s);
955
956   if (gtk_grid_get_child_at (GTK_GRID (table), 1, 1) == NULL)
957     {
958       GtkWidget *hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &ia->n_cases_spin);
959       gtk_grid_attach (GTK_GRID (table), hbox_n_cases,
960                        1, 1,
961                        1, 1);
962     }
963
964   GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (ia->n_cases_spin));
965   gtk_adjustment_set_lower (adj, 1.0);
966
967   if (gtk_grid_get_child_at (GTK_GRID (table), 1, 2) == NULL)
968     {
969       GtkWidget *hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"),
970                                                    &ia->percent_spin);
971
972       gtk_grid_attach (GTK_GRID (table), hbox_percent,
973                        1, 2,
974                        1, 1);
975     }
976
977   gtk_widget_show_all (table);
978
979   on_intro_amount_changed (ia);
980 }
981
982 /* Initializes IA's intro substructure. */
983 static void
984 intro_page_create (PsppireImportAssistant *ia)
985 {
986   GtkBuilder *builder = ia->builder;
987
988   GtkWidget *w =  get_widget_assert (builder, "Intro");
989
990   ia->percent_spin = gtk_spin_button_new_with_range (0, 100, 10);
991
992
993   add_page_to_assistant (ia, w,  GTK_ASSISTANT_PAGE_CONTENT, _("Select the Lines to Import"));
994
995   ia->all_cases_button = get_widget_assert (builder, "import-all-cases");
996
997   ia->n_cases_button = get_widget_assert (builder, "import-n-cases");
998
999   ia->percent_button = get_widget_assert (builder, "import-percent");
1000
1001   g_signal_connect_swapped (ia->all_cases_button, "toggled",
1002                             G_CALLBACK (on_intro_amount_changed), ia);
1003   g_signal_connect_swapped (ia->n_cases_button, "toggled",
1004                             G_CALLBACK (on_intro_amount_changed), ia);
1005   g_signal_connect_swapped (ia->percent_button, "toggled",
1006                             G_CALLBACK (on_intro_amount_changed), ia);
1007
1008
1009   g_object_set_data (G_OBJECT (w), "on-forward", intro_on_leave);
1010   g_object_set_data (G_OBJECT (w), "on-entering", intro_on_enter);
1011   g_object_set_data (G_OBJECT (w), "on-reset", reset_intro_page);
1012 }
1013
1014
1015 GtkWidget *
1016 psppire_import_assistant_new (GtkWindow *toplevel)
1017 {
1018   return GTK_WIDGET (g_object_new (PSPPIRE_TYPE_IMPORT_ASSISTANT,
1019                                    /* Some window managers (notably ratpoison)
1020                                       ignore the maximise command when a window is
1021                                       transient.  This causes problems for this
1022                                       window. */
1023                                    /* "transient-for", toplevel, */
1024                                    NULL));
1025 }
1026
1027
1028
1029 \f
1030
1031 /* Chooses a name for each column on the separators page */
1032 static void
1033 choose_column_names (PsppireImportAssistant *ia)
1034 {
1035   int i;
1036   unsigned long int generated_name_count = 0;
1037   dict_clear (ia->dict);
1038
1039   for (i = 0;
1040        i < gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model)) - 1;
1041        ++i)
1042     {
1043       const gchar *candidate_name = NULL;
1044
1045       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
1046         {
1047           candidate_name = psppire_delimited_text_get_header_title (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), i);
1048         }
1049
1050       char *name = dict_make_unique_var_name (ia->dict,
1051                                               candidate_name,
1052                                               &generated_name_count);
1053
1054       dict_create_var_assert (ia->dict, name, 0);
1055       free (name);
1056     }
1057 }
1058
1059 /* Called when the user toggles one of the separators
1060    checkboxes. */
1061 static void
1062 on_separator_toggle (GtkToggleButton *toggle UNUSED,
1063                      PsppireImportAssistant *ia)
1064 {
1065   int i;
1066   GSList *delimiters = NULL;
1067   for (i = 0; i < SEPARATOR_CNT; i++)
1068     {
1069       const struct separator *s = &separators[i];
1070       GtkWidget *button = get_widget_assert (ia->builder, s->name);
1071       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1072         {
1073           delimiters = g_slist_prepend (delimiters,  GINT_TO_POINTER (s->c));
1074         }
1075     }
1076
1077   g_object_set (ia->delimiters_model, "delimiters", delimiters, NULL);
1078
1079   revise_fields_preview (ia);
1080 }
1081
1082
1083 /* Called when the user changes the entry field for custom
1084    separators. */
1085 static void
1086 on_separators_custom_entry_notify (GObject *gobject UNUSED,
1087                                    GParamSpec *arg1 UNUSED,
1088                                    PsppireImportAssistant *ia)
1089 {
1090   revise_fields_preview (ia);
1091 }
1092
1093 /* Called when the user toggles the checkbox that enables custom
1094    separators. */
1095 static void
1096 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1097                                 PsppireImportAssistant *ia)
1098 {
1099   bool is_active = gtk_toggle_button_get_active (custom_cb);
1100   gtk_widget_set_sensitive (ia->custom_entry, is_active);
1101   revise_fields_preview (ia);
1102 }
1103
1104 /* Called when the user changes the selection in the combo box
1105    that selects a quote character. */
1106 static void
1107 on_quote_combo_change (GtkComboBox *combo, PsppireImportAssistant *ia)
1108 {
1109   //  revise_fields_preview (ia);
1110 }
1111
1112 /* Called when the user toggles the checkbox that enables
1113    quoting. */
1114 static void
1115 on_quote_cb_toggle (GtkToggleButton *quote_cb, PsppireImportAssistant *ia)
1116 {
1117   bool is_active = gtk_toggle_button_get_active (quote_cb);
1118   gtk_widget_set_sensitive (ia->quote_combo, is_active);
1119   revise_fields_preview (ia);
1120 }
1121
1122 /* Initializes IA's separators substructure. */
1123 static void
1124 separators_page_create (PsppireImportAssistant *ia)
1125 {
1126   GtkBuilder *builder = ia->builder;
1127
1128   size_t i;
1129
1130   GtkWidget *w = get_widget_assert (builder, "Separators");
1131
1132   g_object_set_data (G_OBJECT (w), "on-entering", prepare_separators_page);
1133   g_object_set_data (G_OBJECT (w), "on-reset", prepare_separators_page);
1134
1135   add_page_to_assistant (ia, w,   GTK_ASSISTANT_PAGE_CONTENT, _("Choose Separators"));
1136
1137   ia->custom_cb = get_widget_assert (builder, "custom-cb");
1138   ia->custom_entry = get_widget_assert (builder, "custom-entry");
1139   ia->quote_combo = get_widget_assert (builder, "quote-combo");
1140   ia->quote_cb = get_widget_assert (builder, "quote-cb");
1141
1142   gtk_combo_box_set_active (GTK_COMBO_BOX (ia->quote_combo), 0);
1143
1144   if (ia->fields_tree_view == NULL)
1145     {
1146       GtkWidget *scroller = get_widget_assert (ia->builder, "fields-scroller");
1147       ia->fields_tree_view = gtk_tree_view_new ();
1148       g_object_set (ia->fields_tree_view, "enable-search", FALSE, NULL);
1149       gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ia->fields_tree_view));
1150       gtk_widget_show_all (scroller);
1151     }
1152
1153   g_signal_connect (ia->quote_combo, "changed",
1154                     G_CALLBACK (on_quote_combo_change), ia);
1155   g_signal_connect (ia->quote_cb, "toggled",
1156                     G_CALLBACK (on_quote_cb_toggle), ia);
1157   g_signal_connect (ia->custom_entry, "notify::text",
1158                     G_CALLBACK (on_separators_custom_entry_notify), ia);
1159   g_signal_connect (ia->custom_cb, "toggled",
1160                     G_CALLBACK (on_separators_custom_cb_toggle), ia);
1161   for (i = 0; i < SEPARATOR_CNT; i++)
1162     g_signal_connect (get_widget_assert (builder, separators[i].name),
1163                       "toggled", G_CALLBACK (on_separator_toggle), ia);
1164
1165 }
1166
1167
1168
1169 \f
1170
1171
1172 static struct casereader_random_class my_casereader_class;
1173
1174 static struct ccase *
1175 my_read (struct casereader *reader, void *aux, casenumber idx)
1176 {
1177   PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (aux);
1178   GtkTreeModel *tm = GTK_TREE_MODEL (ia->delimiters_model);
1179
1180   GtkTreePath *tp = gtk_tree_path_new_from_indices (idx, -1);
1181
1182   const struct caseproto *proto = casereader_get_proto (reader);
1183
1184   GtkTreeIter iter;
1185   struct ccase *c = NULL;
1186   if (gtk_tree_model_get_iter (tm, &iter, tp))
1187     {
1188       c = case_create (proto);
1189       int i;
1190       for (i = 0 ; i < caseproto_get_n_widths (proto); ++i)
1191         {
1192           GValue value = {0};
1193           gtk_tree_model_get_value (tm, &iter, i + 1, &value);
1194
1195           const struct variable *var = dict_get_var (ia->casereader_dict, i);
1196
1197           const gchar *ss = g_value_get_string (&value);
1198           if (ss)
1199             {
1200               union value *v = case_data_rw (c, var);
1201               /* In this reader we derive the union value from the
1202                  string in the tree_model. We retrieve the width and format
1203                  from a dictionary which is stored directly after
1204                  the reader creation. Changes in ia->dict in the
1205                  variable window are not reflected here and therefore
1206                  this is always compatible with the width in the
1207                  caseproto. See bug #58298 */
1208               char *xx = data_in (ss_cstr (ss),
1209                                   "UTF-8",
1210                                   var_get_write_format (var)->type,
1211                                   v, var_get_width (var), "UTF-8");
1212
1213               /* if (xx) */
1214               /*   g_print ("%s:%d Err %s\n", __FILE__, __LINE__, xx); */
1215               free (xx);
1216             }
1217           g_value_unset (&value);
1218         }
1219     }
1220
1221   gtk_tree_path_free (tp);
1222
1223   return c;
1224 }
1225
1226 static void
1227 my_destroy (struct casereader *reader, void *aux)
1228 {
1229   g_print ("%s:%d %p\n", __FILE__, __LINE__, reader);
1230 }
1231
1232 static void
1233 my_advance (struct casereader *reader, void *aux, casenumber cnt)
1234 {
1235   g_print ("%s:%d\n", __FILE__, __LINE__);
1236 }
1237
1238 static struct casereader *
1239 textfile_create_reader (PsppireImportAssistant *ia)
1240 {
1241   int n_vars = dict_get_var_cnt (ia->dict);
1242
1243   int i;
1244
1245   struct fmt_guesser **fg = xcalloc (n_vars, sizeof *fg);
1246   for (i = 0 ; i < n_vars; ++i)
1247     {
1248       fg[i] = fmt_guesser_create ();
1249     }
1250
1251   gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
1252
1253   GtkTreeIter iter;
1254   gboolean ok;
1255   for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->delimiters_model), &iter);
1256        ok;
1257        ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->delimiters_model), &iter))
1258     {
1259       for (i = 0 ; i < n_vars; ++i)
1260         {
1261           gchar *s = NULL;
1262           gtk_tree_model_get (GTK_TREE_MODEL (ia->delimiters_model), &iter, i+1, &s, -1);
1263           if (s)
1264             fmt_guesser_add (fg[i], ss_cstr (s));
1265           free (s);
1266         }
1267     }
1268
1269   struct caseproto *proto = caseproto_create ();
1270   for (i = 0 ; i < n_vars; ++i)
1271     {
1272       struct fmt_spec fs;
1273       fmt_guesser_guess (fg[i], &fs);
1274
1275       fmt_fix (&fs, FMT_FOR_INPUT);
1276
1277       struct variable *var = dict_get_var (ia->dict, i);
1278
1279       int width = fmt_var_width (&fs);
1280
1281       var_set_width_and_formats (var, width,
1282                                  &fs, &fs);
1283
1284       proto = caseproto_add_width (proto, width);
1285       fmt_guesser_destroy (fg[i]);
1286     }
1287
1288   free (fg);
1289
1290   struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class,  ia);
1291   /* Store the dictionary at this point when the casereader is created.
1292      my_read depends on the dictionary to interpret the strings in the treeview.
1293      This guarantees that the union value is produced according to the
1294      caseproto in the reader. */
1295   ia->casereader_dict = dict_clone (ia->dict);
1296   caseproto_unref (proto);
1297   return cr;
1298 }
1299
1300 /* When during import the variable type is changed, the reader is reinitialized
1301    based on the new dictionary with a fresh caseprototype. The default behaviour
1302    when a variable type is changed and the column is resized is that the union
1303    value is interpreted with new variable type and an overlay for that column
1304    is generated. Here we reinit to the original reader based on strings.
1305    As a result you can switch from string to numeric to string without loosing
1306    the string information. */
1307 static void
1308 ia_variable_changed_cb (GObject *obj, gint var_num, guint what,
1309                         const struct variable *oldvar, gpointer data)
1310 {
1311   PsppireImportAssistant *ia  = PSPPIRE_IMPORT_ASSISTANT (data);
1312
1313   struct caseproto *proto = caseproto_create();
1314   for (int i = 0; i < dict_get_var_cnt (ia->dict); i++)
1315     {
1316       const struct variable *var = dict_get_var (ia->dict, i);
1317       int width = var_get_width (var);
1318       proto = caseproto_add_width (proto, width);
1319     }
1320
1321   gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
1322
1323   PsppireDataStore *store = NULL;
1324   g_object_get (ia->data_sheet, "data-model", &store, NULL);
1325
1326   struct casereader *cr = casereader_create_random (proto, n_rows,
1327                                                     &my_casereader_class, ia);
1328   psppire_data_store_set_reader (store, cr);
1329   dict_unref (ia->casereader_dict);
1330   ia->casereader_dict = dict_clone (ia->dict);
1331 }
1332
1333 /* Called just before the formats page of the assistant is
1334    displayed. */
1335 static void
1336 prepare_formats_page (PsppireImportAssistant *ia)
1337 {
1338   my_casereader_class.read = my_read;
1339   my_casereader_class.destroy = my_destroy;
1340   my_casereader_class.advance = my_advance;
1341
1342   if (ia->spreadsheet)
1343     {
1344       GtkBuilder *builder = ia->builder;
1345       GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
1346       GtkWidget *rnc = get_widget_assert (builder, "readnames-checkbox");
1347       GtkWidget *combo_box = get_widget_assert (builder, "sheet-entry");
1348
1349       struct spreadsheet_read_options opts;
1350       opts.sheet_name = NULL;
1351       opts.sheet_index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box)) + 1;
1352       opts.read_names = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rnc));
1353       opts.cell_range = g_strdup (gtk_entry_get_text (GTK_ENTRY (range_entry)));
1354       opts.asw = 8;
1355
1356       struct casereader *reader = spreadsheet_make_reader (ia->spreadsheet, &opts);
1357
1358       PsppireDict *dict = psppire_dict_new_from_dict (ia->spreadsheet->dict);
1359       PsppireDataStore *store = psppire_data_store_new (dict);
1360       psppire_data_store_set_reader (store, reader);
1361       g_object_set (ia->data_sheet, "data-model", store, NULL);
1362       g_object_set (ia->var_sheet, "data-model", dict, NULL);
1363     }
1364   else
1365     {
1366       struct casereader *reader = textfile_create_reader (ia);
1367
1368       PsppireDict *dict = psppire_dict_new_from_dict (ia->dict);
1369       PsppireDataStore *store = psppire_data_store_new (dict);
1370       psppire_data_store_set_reader (store, reader);
1371       g_signal_connect (dict, "variable-changed",
1372                         G_CALLBACK (ia_variable_changed_cb),
1373                         ia);
1374
1375       g_object_set (ia->data_sheet, "data-model", store, NULL);
1376       g_object_set (ia->var_sheet, "data-model", dict, NULL);
1377     }
1378
1379   gint pmax;
1380   g_object_get (get_widget_assert (ia->builder, "vpaned1"),
1381                 "max-position", &pmax, NULL);
1382
1383
1384   g_object_set (get_widget_assert (ia->builder, "vpaned1"),
1385                 "position", pmax / 2, NULL);
1386
1387   gtk_widget_show (ia->paste_button);
1388 }
1389
1390 static void
1391 formats_page_create (PsppireImportAssistant *ia)
1392 {
1393   GtkBuilder *builder = ia->builder;
1394
1395   GtkWidget *w = get_widget_assert (builder, "Formats");
1396   g_object_set_data (G_OBJECT (w), "on-entering", prepare_formats_page);
1397   g_object_set_data (G_OBJECT (w), "on-reset", reset_formats_page);
1398
1399   GtkWidget *vars_scroller = get_widget_assert (builder, "vars-scroller");
1400   if (ia->var_sheet == NULL)
1401     {
1402       ia->var_sheet = psppire_variable_sheet_new ();
1403
1404       gtk_container_add (GTK_CONTAINER (vars_scroller), ia->var_sheet);
1405
1406       ia->dict = dict_create (get_default_encoding ());
1407
1408       gtk_widget_show_all (vars_scroller);
1409     }
1410   GtkWidget *data_scroller = get_widget_assert (builder, "data-scroller");
1411   if (ia->data_sheet == NULL)
1412     {
1413       ia->data_sheet = psppire_data_sheet_new ();
1414       g_object_set (ia->data_sheet, "editable", FALSE, NULL);
1415
1416       gtk_container_add (GTK_CONTAINER (data_scroller), ia->data_sheet);
1417
1418       gtk_widget_show_all (data_scroller);
1419     }
1420
1421   add_page_to_assistant (ia, w,
1422                          GTK_ASSISTANT_PAGE_CONFIRM, _("Adjust Variable Formats"));
1423 }
1424
1425
1426 \f
1427
1428 static void
1429 separators_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1430 {
1431   int i;
1432
1433   ds_put_cstr (s, "  /DELIMITERS=\"");
1434
1435   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->builder, "tab"))))
1436     ds_put_cstr (s, "\\t");
1437   for (i = 0; i < SEPARATOR_CNT; i++)
1438     {
1439       const struct separator *seps = &separators[i];
1440       GtkWidget *button = get_widget_assert (ia->builder, seps->name);
1441       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1442         {
1443           if (seps->c == '\t')
1444             continue;
1445
1446           ds_put_byte (s, seps->c);
1447         }
1448     }
1449   ds_put_cstr (s, "\"\n");
1450
1451   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->quote_cb)))
1452     {
1453       GtkComboBoxText *cbt = GTK_COMBO_BOX_TEXT (ia->quote_combo);
1454       gchar *quotes = gtk_combo_box_text_get_active_text (cbt);
1455       if (quotes && *quotes)
1456         syntax_gen_pspp (s, "  /QUALIFIER=%sq\n", quotes);
1457       free (quotes);
1458     }
1459 }
1460
1461 static void
1462 formats_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1463 {
1464   int i;
1465   int var_cnt;
1466
1467   g_return_if_fail (ia->dict);
1468
1469   ds_put_cstr (s, "  /VARIABLES=\n");
1470
1471   var_cnt = dict_get_var_cnt (ia->dict);
1472   for (i = 0; i < var_cnt; i++)
1473     {
1474       struct variable *var = dict_get_var (ia->dict, i);
1475       char format_string[FMT_STRING_LEN_MAX + 1];
1476       fmt_to_string (var_get_print_format (var), format_string);
1477       ds_put_format (s, "    %s %s%s\n",
1478                      var_get_name (var), format_string,
1479                      i == var_cnt - 1 ? "." : "");
1480     }
1481 }
1482
1483 static void
1484 first_line_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1485 {
1486   gint first_case = 0;
1487   g_object_get (ia->delimiters_model, "first-line", &first_case, NULL);
1488
1489   if (first_case > 0)
1490     ds_put_format (s, "  /FIRSTCASE=%d\n", first_case + 1);
1491 }
1492
1493 static void
1494 intro_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1495 {
1496   gint first_line = 0;
1497   g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
1498
1499   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
1500     ds_put_format (s, "SELECT IF ($CASENUM <= %d).\n",
1501                    gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin)) - first_line);
1502   else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
1503     ds_put_format (s, "SAMPLE %.4g.\n",
1504                    gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin)) / 100.0);
1505 }
1506
1507
1508 /* Emits PSPP syntax to S that applies the dictionary attributes
1509    (such as missing values and value labels) of the variables in
1510    DICT.  */
1511 static void
1512 apply_dict (const struct dictionary *dict, struct string *s)
1513 {
1514   size_t var_cnt = dict_get_var_cnt (dict);
1515   size_t i;
1516
1517   for (i = 0; i < var_cnt; i++)
1518     {
1519       struct variable *var = dict_get_var (dict, i);
1520       const char *name = var_get_name (var);
1521       enum val_type type = var_get_type (var);
1522       int width = var_get_width (var);
1523       enum measure measure = var_get_measure (var);
1524       enum var_role role = var_get_role (var);
1525       enum alignment alignment = var_get_alignment (var);
1526       const struct fmt_spec *format = var_get_print_format (var);
1527
1528       if (var_has_missing_values (var))
1529         {
1530           const struct missing_values *mv = var_get_missing_values (var);
1531           size_t j;
1532
1533           syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
1534           for (j = 0; j < mv_n_values (mv); j++)
1535             {
1536               if (j)
1537                 ds_put_cstr (s, ", ");
1538               syntax_gen_value (s, mv_get_value (mv, j), width, format);
1539             }
1540
1541           if (mv_has_range (mv))
1542             {
1543               double low, high;
1544               if (mv_has_value (mv))
1545                 ds_put_cstr (s, ", ");
1546               mv_get_range (mv, &low, &high);
1547               syntax_gen_num_range (s, low, high, format);
1548             }
1549           ds_put_cstr (s, ").\n");
1550         }
1551       if (var_has_value_labels (var))
1552         {
1553           const struct val_labs *vls = var_get_value_labels (var);
1554           const struct val_lab **labels = val_labs_sorted (vls);
1555           size_t n_labels = val_labs_count (vls);
1556           size_t i;
1557
1558           syntax_gen_pspp (s, "VALUE LABELS %ss", name);
1559           for (i = 0; i < n_labels; i++)
1560             {
1561               const struct val_lab *vl = labels[i];
1562               ds_put_cstr (s, "\n  ");
1563               syntax_gen_value (s, &vl->value, width, format);
1564               ds_put_byte (s, ' ');
1565               syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl)));
1566             }
1567           free (labels);
1568           ds_put_cstr (s, ".\n");
1569         }
1570       if (var_has_label (var))
1571         syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
1572                          name, var_get_label (var));
1573       if (measure != var_default_measure (type))
1574         syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
1575                          name, measure_to_syntax (measure));
1576       if (role != ROLE_INPUT)
1577         syntax_gen_pspp (s, "VARIABLE ROLE /%ss %ss.\n",
1578                          var_role_to_syntax (role), name);
1579       if (alignment != var_default_alignment (type))
1580         syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
1581                          name, alignment_to_syntax (alignment));
1582       if (var_get_display_width (var) != var_default_display_width (width))
1583         syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
1584                          name, var_get_display_width (var));
1585     }
1586 }
1587
1588
1589
1590 static void
1591 sheet_spec_gen_syntax (PsppireImportAssistant *ia, struct string *s)
1592 {
1593   GtkBuilder *builder = ia->builder;
1594   GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
1595   GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
1596   GtkWidget *rnc = get_widget_assert (builder, "readnames-checkbox");
1597   const gchar *range = gtk_entry_get_text (GTK_ENTRY (range_entry));
1598   int sheet_index = 1 + gtk_combo_box_get_active (GTK_COMBO_BOX (sheet_entry));
1599   gboolean read_names = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rnc));
1600
1601
1602   char *filename;
1603   if (ia->spreadsheet)
1604     filename = ia->spreadsheet->file_name;
1605   else
1606     g_object_get (ia->text_file, "file-name", &filename, NULL);
1607   syntax_gen_pspp (s,
1608                    "GET DATA"
1609                    "\n  /TYPE=%ss"
1610                    "\n  /FILE=%sq"
1611                    "\n  /SHEET=index %d"
1612                    "\n  /READNAMES=%ss",
1613                    (ia->spreadsheet->type == SPREADSHEET_GNUMERIC) ? "GNM" : "ODS",
1614                    filename,
1615                    sheet_index,
1616                    read_names ? "ON" : "OFF");
1617
1618   if (range && 0 != strcmp ("", range))
1619     {
1620       syntax_gen_pspp (s,
1621                        "\n  /CELLRANGE=RANGE %sq", range);
1622     }
1623   else
1624     {
1625       syntax_gen_pspp (s,
1626                        "\n  /CELLRANGE=FULL");
1627     }
1628
1629
1630   syntax_gen_pspp (s, ".\n");
1631 }
1632
1633
1634 gchar *
1635 psppire_import_assistant_generate_syntax (PsppireImportAssistant *ia)
1636 {
1637   struct string s = DS_EMPTY_INITIALIZER;
1638
1639   if (!ia->spreadsheet)
1640     {
1641       gchar *file_name = NULL;
1642       gchar *encoding = NULL;
1643       g_object_get (ia->text_file,
1644                     "file-name", &file_name,
1645                     "encoding", &encoding,
1646                     NULL);
1647
1648       if (file_name == NULL)
1649         return NULL;
1650
1651       syntax_gen_pspp (&s,
1652                        "GET DATA"
1653                        "\n  /TYPE=TXT"
1654                        "\n  /FILE=%sq\n",
1655                        file_name);
1656       if (encoding && strcmp (encoding, "Auto"))
1657         syntax_gen_pspp (&s, "  /ENCODING=%sq\n", encoding);
1658
1659       ds_put_cstr (&s,
1660                    "  /ARRANGEMENT=DELIMITED\n"
1661                    "  /DELCASE=LINE\n");
1662
1663       first_line_append_syntax (ia, &s);
1664       separators_append_syntax (ia, &s);
1665
1666       formats_append_syntax (ia, &s);
1667       apply_dict (ia->dict, &s);
1668       intro_append_syntax (ia, &s);
1669     }
1670   else
1671     {
1672       sheet_spec_gen_syntax (ia, &s);
1673     }
1674
1675   return ds_cstr (&s);
1676 }
1677
1678
1679 int
1680 psppire_import_assistant_run (PsppireImportAssistant *asst)
1681 {
1682   g_main_loop_run (asst->main_loop);
1683   return asst->response;
1684 }