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