3034ed02f637a26d657787b5abebd04411affdec
[pspp] / src / ui / gui / psppire-import-textfile.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 "psppire-import-textfile.h"
20 #include <gtk/gtk.h>
21
22 #include "libpspp/i18n.h"
23 #include "libpspp/line-reader.h"
24 #include "libpspp/message.h"
25 #include "libpspp/hmap.h"
26 #include "libpspp/hash-functions.h"
27 #include "libpspp/str.h"
28 #include "libpspp/misc.h"
29
30 #include "data/casereader.h"
31 #include "data/casereader-provider.h"
32 #include "data/data-in.h"
33 #include "data/format-guesser.h"
34 #include "data/value-labels.h"
35
36 #include "builder-wrapper.h"
37
38 #include "psppire-data-store.h"
39 #include "psppire-scanf.h"
40
41 #include "ui/syntax-gen.h"
42
43 #include <gettext.h>
44 #define _(msgid) gettext (msgid)
45 #define N_(msgid) msgid
46
47 /* Chooses a name for each column on the separators page */
48 static void choose_column_names (PsppireImportAssistant *ia);
49
50 /* Revises the contents of the fields tree view based on the
51    currently chosen set of separators and quotes. */
52 static void
53 revise_fields_preview (PsppireImportAssistant *ia)
54 {
55   GSList *delimiters = NULL;
56   for (int i = 0; i < N_SEPARATORS; i++)
57     {
58       const struct separator *s = &separators[i];
59       GtkWidget *button = get_widget_assert (ia->text_builder, s->name);
60       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
61         {
62           delimiters = g_slist_prepend (delimiters,  GINT_TO_POINTER (s->c));
63         }
64     }
65
66   GtkComboBoxText *cbt = GTK_COMBO_BOX_TEXT (ia->quote_combo);
67   GtkToggleButton *quote_cb = GTK_TOGGLE_BUTTON (ia->quote_cb);
68   const gchar *quotes = (gtk_toggle_button_get_active (quote_cb)
69                          ? gtk_combo_box_text_get_active_text (cbt)
70                          : "");
71
72   g_object_set (ia->delimiters_model,
73                 "delimiters", delimiters,
74                 "quotes", quotes,
75                 NULL);
76
77   choose_column_names (ia);
78 }
79
80
81 static void
82 repopulate_delimiter_columns (PsppireImportAssistant *ia)
83 {
84   /* Remove all the columns */
85   while (gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view)) > 0)
86     {
87       GtkTreeViewColumn *tvc = gtk_tree_view_get_column (GTK_TREE_VIEW (ia->fields_tree_view), 0);
88       gtk_tree_view_remove_column (GTK_TREE_VIEW (ia->fields_tree_view), tvc);
89     }
90
91   gint n_fields =
92     gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model));
93
94   /* ... and put them back again. */
95   gint f;
96   for (f = gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view));
97        f < n_fields; f++)
98     {
99       GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
100
101       const gchar *title = NULL;
102
103       if (f == 0)
104         title = _("line");
105       else
106         {
107           if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
108             {
109               title =
110                 psppire_delimited_text_get_header_title
111                 (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), f - 1);
112             }
113           if (title == NULL)
114             title = _("var");
115         }
116
117       GtkTreeViewColumn *column =
118         gtk_tree_view_column_new_with_attributes (title,
119                                                   renderer,
120                                                   "text", f,
121                                                   NULL);
122       g_object_set (column,
123                     "resizable", TRUE,
124                     "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE,
125                     NULL);
126
127       gtk_tree_view_append_column (GTK_TREE_VIEW (ia->fields_tree_view), column);
128     }
129 }
130
131 static void
132 reset_tree_view_model (PsppireImportAssistant *ia)
133 {
134   GtkTreeModel *tm = gtk_tree_view_get_model (GTK_TREE_VIEW (ia->fields_tree_view));
135   g_object_ref (tm);
136   gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), NULL);
137
138
139   repopulate_delimiter_columns (ia);
140
141   gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), tm);
142   //  gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ia->fields_tree_view));
143
144   g_object_unref (tm);
145 }
146
147 /* Resets IA's intro page to its initial state. */
148 static void
149 reset_intro_page (PsppireImportAssistant *ia)
150 {
151   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->n_cases_button),
152                                 TRUE);
153   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->percent_button),
154                                 TRUE);
155   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->all_cases_button),
156                                 TRUE);
157
158   gtk_spin_button_set_value (GTK_SPIN_BUTTON (ia->n_cases_spin), 1);
159   gtk_spin_button_set_value (GTK_SPIN_BUTTON (ia->percent_spin), 0);
160 }
161
162 /* Called when one of the radio buttons is clicked. */
163 static void
164 on_intro_amount_changed (PsppireImportAssistant *ia)
165 {
166   gtk_widget_set_sensitive (ia->n_cases_spin,
167                             gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)));
168
169   gtk_widget_set_sensitive (ia->percent_spin,
170                             gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)));
171 }
172
173 static void
174 on_treeview_selection_change (PsppireImportAssistant *ia)
175 {
176   GtkTreeSelection *selection =
177     gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view));
178   GtkTreeModel *model = NULL;
179   GtkTreeIter iter;
180   if (gtk_tree_selection_get_selected (selection, &model, &iter))
181     {
182       gint max_lines;
183       int n;
184       GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
185       gint *index = gtk_tree_path_get_indices (path);
186       n = *index;
187       gtk_tree_path_free (path);
188       g_object_get (model, "maximum-lines", &max_lines, NULL);
189       gtk_widget_set_sensitive (ia->variable_names_cb,
190                                 (n > 0 && n < max_lines));
191       ia->delimiters_model =
192         psppire_delimited_text_new (GTK_TREE_MODEL (ia->text_file));
193       g_object_set (ia->delimiters_model, "first-line", n, NULL);
194     }
195 }
196
197 static void
198 render_text_preview_line (GtkTreeViewColumn *tree_column,
199                 GtkCellRenderer *cell,
200                 GtkTreeModel *tree_model,
201                 GtkTreeIter *iter,
202                 gpointer data)
203 {
204   /*
205      Set the text  to a "insensitive" state if the row
206      is greater than what the user declared to be the maximum.
207   */
208   GtkTreePath *path = gtk_tree_model_get_path (tree_model, iter);
209   gint *ii = gtk_tree_path_get_indices (path);
210   gint max_lines;
211   g_object_get (tree_model, "maximum-lines", &max_lines, NULL);
212   g_object_set (cell, "sensitive", (*ii < max_lines), NULL);
213   gtk_tree_path_free (path);
214 }
215
216 /* Resets IA's "first line" page to its initial state. */
217 static void
218 reset_first_line_page (PsppireImportAssistant *ia)
219 {
220   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb), FALSE);
221
222   GtkTreeSelection *selection =
223     gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view));
224
225   gtk_tree_selection_unselect_all (selection);
226 }
227
228 /* Initializes IA's first_line substructure. */
229 void
230 first_line_page_create (PsppireImportAssistant *ia)
231 {
232   GtkWidget *w =  get_widget_assert (ia->text_builder, "FirstLine");
233
234   g_object_set_data (G_OBJECT (w), "on-entering", on_treeview_selection_change);
235   g_object_set_data (G_OBJECT (w), "on-reset", reset_first_line_page);
236
237   add_page_to_assistant (ia, w,
238                          GTK_ASSISTANT_PAGE_CONTENT, _("Select the First Line"));
239
240   GtkWidget *scrolled_window = get_widget_assert (ia->text_builder, "first-line-scroller");
241
242   if (ia->first_line_tree_view == NULL)
243     {
244       ia->first_line_tree_view = gtk_tree_view_new ();
245       g_object_set (ia->first_line_tree_view, "enable-search", FALSE, NULL);
246
247       gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ia->first_line_tree_view), TRUE);
248
249       GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
250       GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Line"), renderer,
251                                                                             "text", 0,
252                                                                             NULL);
253
254       gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
255       gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
256
257       renderer = gtk_cell_renderer_text_new ();
258       column = gtk_tree_view_column_new_with_attributes (_("Text"), renderer, "text", 1, NULL);
259       gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
260
261       gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
262
263       g_signal_connect_swapped (ia->first_line_tree_view, "cursor-changed",
264                                 G_CALLBACK (on_treeview_selection_change), ia);
265       gtk_container_add (GTK_CONTAINER (scrolled_window), ia->first_line_tree_view);
266     }
267
268   gtk_widget_show_all (scrolled_window);
269
270   ia->variable_names_cb = get_widget_assert (ia->text_builder, "variable-names");
271
272   reset_first_line_page (ia);
273 }
274
275 static void
276 intro_on_leave (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
277 {
278   if (dir != IMPORT_ASSISTANT_FORWARDS)
279     return;
280
281   gint lc = 0;
282   g_object_get (ia->text_file, "line-count", &lc, NULL);
283   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
284     {
285       gint max_lines = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin));
286       g_object_set (ia->text_file, "maximum-lines", max_lines, NULL);
287     }
288   else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
289     {
290       gdouble percent = gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin));
291       g_object_set (ia->text_file, "maximum-lines", (gint) (lc * percent / 100.0), NULL);
292     }
293   else
294     {
295       g_object_set (ia->text_file, "maximum-lines", lc, NULL);
296     }
297 }
298
299
300 static void
301 intro_on_enter (PsppireImportAssistant *ia, GtkWidget *page, enum IMPORT_ASSISTANT_DIRECTION dir)
302 {
303   GtkBuilder *builder = ia->text_builder;
304   GtkWidget *table  = get_widget_assert (builder, "button-table");
305
306   struct string s;
307
308   ds_init_empty (&s);
309   ds_put_cstr (&s, _("This assistant will guide you through the process of "
310                      "importing data into PSPP from a text file with one line "
311                      "per case,  in which fields are separated by tabs, "
312                      "commas, or other delimiters.\n\n"));
313
314   if (ia->text_file)
315     {
316       if (ia->text_file->total_is_exact)
317         {
318           ds_put_format (
319                          &s, ngettext ("The selected file contains %'lu line of text.  ",
320                                        "The selected file contains %'lu lines of text.  ",
321                                        ia->text_file->total_lines),
322                          ia->text_file->total_lines);
323         }
324       else if (ia->text_file->total_lines > 0)
325         {
326           ds_put_format (
327                          &s, ngettext (
328                                        "The selected file contains approximately %'lu line of text.  ",
329                                        "The selected file contains approximately %'lu lines of text.  ",
330                                        ia->text_file->total_lines),
331                          ia->text_file->total_lines);
332           ds_put_format (
333                          &s, ngettext (
334                                        "Only the first %zu line of the file will be shown for "
335                                        "preview purposes in the following screens.  ",
336                                        "Only the first %zu lines of the file will be shown for "
337                                        "preview purposes in the following screens.  ",
338                                        ia->text_file->n_lines),
339                          ia->text_file->n_lines);
340         }
341     }
342
343   ds_put_cstr (&s, _("You may choose below how much of the file should "
344                      "actually be imported."));
345
346   gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
347                       ds_cstr (&s));
348   ds_destroy (&s);
349
350   if (gtk_grid_get_child_at (GTK_GRID (table), 1, 1) == NULL)
351     {
352       GtkWidget *hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &ia->n_cases_spin);
353       gtk_grid_attach (GTK_GRID (table), hbox_n_cases,
354                        1, 1,
355                        1, 1);
356     }
357
358   GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (ia->n_cases_spin));
359   gtk_adjustment_set_lower (adj, 1.0);
360
361   if (gtk_grid_get_child_at (GTK_GRID (table), 1, 2) == NULL)
362     {
363       GtkWidget *hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"),
364                                                    &ia->percent_spin);
365
366       gtk_grid_attach (GTK_GRID (table), hbox_percent,
367                        1, 2,
368                        1, 1);
369     }
370
371   gtk_widget_show_all (table);
372
373
374   if (dir != IMPORT_ASSISTANT_FORWARDS)
375     return;
376
377   reset_intro_page (ia);
378   on_intro_amount_changed (ia);
379 }
380
381 /* Initializes IA's intro substructure. */
382 void
383 intro_page_create (PsppireImportAssistant *ia)
384 {
385   GtkBuilder *builder = ia->text_builder;
386
387   GtkWidget *w = get_widget_assert (builder, "Intro");
388
389   ia->percent_spin = gtk_spin_button_new_with_range (0, 100, 1);
390
391   add_page_to_assistant (ia, w,  GTK_ASSISTANT_PAGE_CONTENT, _("Select the Lines to Import"));
392
393   ia->all_cases_button = get_widget_assert (builder, "import-all-cases");
394   ia->n_cases_button = get_widget_assert (builder, "import-n-cases");
395   ia->percent_button = get_widget_assert (builder, "import-percent");
396
397   g_signal_connect_swapped (ia->all_cases_button, "toggled",
398                             G_CALLBACK (on_intro_amount_changed), ia);
399   g_signal_connect_swapped (ia->n_cases_button, "toggled",
400                             G_CALLBACK (on_intro_amount_changed), ia);
401   g_signal_connect_swapped (ia->percent_button, "toggled",
402                             G_CALLBACK (on_intro_amount_changed), ia);
403
404   g_object_set_data (G_OBJECT (w), "on-leaving", intro_on_leave);
405   g_object_set_data (G_OBJECT (w), "on-entering", intro_on_enter);
406   g_object_set_data (G_OBJECT (w), "on-reset", reset_intro_page);
407 }
408
409
410 \f
411
412 /* Chooses a name for each column on the separators page */
413 static void
414 choose_column_names (PsppireImportAssistant *ia)
415 {
416   int i;
417   unsigned long int generated_name_count = 0;
418   char *encoding = NULL;
419   g_object_get (ia->text_file, "encoding", &encoding, NULL);
420   if (ia->dict)
421     dict_unref (ia->dict);
422   ia->dict = dict_create (encoding ? encoding : UTF8);
423   g_free (encoding);
424
425   for (i = 0;
426        i < gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model)) - 1;
427        ++i)
428     {
429       const gchar *candidate_name = NULL;
430
431       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
432         {
433           candidate_name = psppire_delimited_text_get_header_title (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), i);
434         }
435
436       char *name = dict_make_unique_var_name (ia->dict,
437                                               candidate_name,
438                                               &generated_name_count);
439
440       dict_create_var_assert (ia->dict, name, 0);
441       free (name);
442     }
443 }
444
445 /* Called when the user toggles one of the separators
446    checkboxes. */
447 static void
448 on_separator_toggle (GtkToggleButton *toggle UNUSED,
449                      PsppireImportAssistant *ia)
450 {
451   revise_fields_preview (ia);
452 }
453
454
455 /* Called when the user changes the entry field for custom
456    separators. */
457 static void
458 on_separators_custom_entry_notify (GObject *gobject UNUSED,
459                                    GParamSpec *arg1 UNUSED,
460                                    PsppireImportAssistant *ia)
461 {
462   revise_fields_preview (ia);
463 }
464
465 /* Called when the user toggles the checkbox that enables custom
466    separators. */
467 static void
468 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
469                                 PsppireImportAssistant *ia)
470 {
471   bool is_active = gtk_toggle_button_get_active (custom_cb);
472   gtk_widget_set_sensitive (ia->custom_entry, is_active);
473   revise_fields_preview (ia);
474 }
475
476 /* Called when the user changes the selection in the combo box
477    that selects a quote character. */
478 static void
479 on_quote_combo_change (GtkComboBox *combo, PsppireImportAssistant *ia)
480 {
481   revise_fields_preview (ia);
482 }
483
484 /* Called when the user toggles the checkbox that enables
485    quoting. */
486 static void
487 on_quote_cb_toggle (GtkToggleButton *quote_cb, PsppireImportAssistant *ia)
488 {
489   bool is_active = gtk_toggle_button_get_active (quote_cb);
490   gtk_widget_set_sensitive (ia->quote_combo, is_active);
491   revise_fields_preview (ia);
492 }
493
494
495 /* Called when the Reset button is clicked. */
496 static void
497 reset_separators_page (PsppireImportAssistant *ia)
498 {
499   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->custom_cb), FALSE);
500   gtk_entry_set_text (GTK_ENTRY (ia->custom_entry), "");
501   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->quote_cb), TRUE);
502   gtk_combo_box_set_active (GTK_COMBO_BOX (ia->quote_combo), 0);
503
504   for (gint i = 0; i < N_SEPARATORS; i++)
505     {
506       const struct separator *s = &separators[i];
507       GtkWidget *button = get_widget_assert (ia->text_builder, s->name);
508       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), s->c == ',');
509     }
510
511   if (ia->delimiters_model)
512     {
513       repopulate_delimiter_columns (ia);
514
515       revise_fields_preview (ia);
516     }
517 }
518
519 /* Called just before the separators page becomes visible in the
520    assistant. */
521 static void
522 prepare_separators_page (PsppireImportAssistant *ia, GtkWidget *new_page, enum IMPORT_ASSISTANT_DIRECTION dir)
523 {
524   if (dir != IMPORT_ASSISTANT_FORWARDS)
525     return;
526
527   gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view),
528                            GTK_TREE_MODEL (ia->delimiters_model));
529
530   g_signal_connect_swapped (GTK_TREE_MODEL (ia->delimiters_model), "notify::delimiters",
531                         G_CALLBACK (reset_tree_view_model), ia);
532
533
534   reset_separators_page (ia);
535 }
536
537
538 /* Initializes IA's separators substructure. */
539 void
540 separators_page_create (PsppireImportAssistant *ia)
541 {
542   GtkBuilder *builder = ia->text_builder;
543
544   size_t i;
545
546   GtkWidget *w = get_widget_assert (builder, "Separators");
547
548   g_object_set_data (G_OBJECT (w), "on-entering", prepare_separators_page);
549   g_object_set_data (G_OBJECT (w), "on-reset", reset_separators_page);
550
551   add_page_to_assistant (ia, w,   GTK_ASSISTANT_PAGE_CONTENT, _("Choose Separators"));
552
553   ia->custom_cb = get_widget_assert (builder, "custom-cb");
554   ia->custom_entry = get_widget_assert (builder, "custom-entry");
555   ia->quote_combo = get_widget_assert (builder, "quote-combo");
556   ia->quote_cb = get_widget_assert (builder, "quote-cb");
557
558   gtk_widget_set_sensitive (ia->custom_entry,
559                             gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->custom_cb)));
560
561   gtk_entry_set_max_length (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (ia->quote_combo))), 1);
562
563   if (ia->fields_tree_view == NULL)
564     {
565       GtkWidget *scroller = get_widget_assert (ia->text_builder, "fields-scroller");
566       ia->fields_tree_view = gtk_tree_view_new ();
567       g_object_set (ia->fields_tree_view, "enable-search", FALSE, NULL);
568       gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ia->fields_tree_view));
569       gtk_widget_show_all (scroller);
570     }
571
572   g_signal_connect (ia->quote_combo, "changed",
573                     G_CALLBACK (on_quote_combo_change), ia);
574   g_signal_connect (ia->quote_cb, "toggled",
575                     G_CALLBACK (on_quote_cb_toggle), ia);
576   g_signal_connect (ia->custom_entry, "notify::text",
577                     G_CALLBACK (on_separators_custom_entry_notify), ia);
578   g_signal_connect (ia->custom_cb, "toggled",
579                     G_CALLBACK (on_separators_custom_cb_toggle), ia);
580   for (i = 0; i < N_SEPARATORS; i++)
581     g_signal_connect (get_widget_assert (builder, separators[i].name),
582                       "toggled", G_CALLBACK (on_separator_toggle), ia);
583
584   reset_separators_page (ia);
585 }
586
587 \f
588
589 static struct casereader_random_class my_casereader_class;
590
591 static struct ccase *
592 my_read (struct casereader *reader, void *aux, casenumber idx)
593 {
594   PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (aux);
595   GtkTreeModel *tm = GTK_TREE_MODEL (ia->delimiters_model);
596
597   GtkTreePath *tp = gtk_tree_path_new_from_indices (idx, -1);
598
599   const struct caseproto *proto = casereader_get_proto (reader);
600
601   GtkTreeIter iter;
602   struct ccase *c = NULL;
603   if (gtk_tree_model_get_iter (tm, &iter, tp))
604     {
605       c = case_create (proto);
606       int i;
607       for (i = 0 ; i < caseproto_get_n_widths (proto); ++i)
608         {
609           GValue value = {0};
610           gtk_tree_model_get_value (tm, &iter, i + 1, &value);
611
612           const struct variable *var = dict_get_var (ia->casereader_dict, i);
613
614           const gchar *ss = g_value_get_string (&value);
615           if (ss)
616             {
617               union value *v = case_data_rw (c, var);
618               /* In this reader we derive the union value from the
619                  string in the tree_model. We retrieve the width and format
620                  from a dictionary which is stored directly after
621                  the reader creation. Changes in ia->dict in the
622                  variable window are not reflected here and therefore
623                  this is always compatible with the width in the
624                  caseproto. See bug #58298 */
625               char *xx = data_in (ss_cstr (ss),
626                                   "UTF-8",
627                                   var_get_write_format (var)->type,
628                                   settings_get_fmt_settings (),
629                                   v, var_get_width (var), "UTF-8");
630
631               free (xx);
632             }
633           g_value_unset (&value);
634         }
635     }
636
637   gtk_tree_path_free (tp);
638
639   return c;
640 }
641
642 static void
643 my_destroy (struct casereader *reader, void *aux)
644 {
645   g_print ("%s:%d %p\n", __FILE__, __LINE__, reader);
646 }
647
648 static void
649 my_advance (struct casereader *reader, void *aux, casenumber n)
650 {
651   g_print ("%s:%d\n", __FILE__, __LINE__);
652 }
653
654 static struct casereader *
655 textfile_create_reader (PsppireImportAssistant *ia)
656 {
657   int n_vars = dict_get_n_vars (ia->dict);
658
659   int i;
660
661   struct fmt_guesser **fg = XCALLOC (n_vars,  struct fmt_guesser *);
662   for (i = 0 ; i < n_vars; ++i)
663     {
664       fg[i] = fmt_guesser_create ();
665     }
666
667   gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
668
669   GtkTreeIter iter;
670   gboolean ok;
671   for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->delimiters_model), &iter);
672        ok;
673        ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->delimiters_model), &iter))
674     {
675       for (i = 0 ; i < n_vars; ++i)
676         {
677           gchar *s = NULL;
678           gtk_tree_model_get (GTK_TREE_MODEL (ia->delimiters_model), &iter, i+1, &s, -1);
679           if (s)
680             fmt_guesser_add (fg[i], ss_cstr (s));
681           free (s);
682         }
683     }
684
685   struct caseproto *proto = caseproto_create ();
686   for (i = 0 ; i < n_vars; ++i)
687     {
688       struct fmt_spec fs;
689       fmt_guesser_guess (fg[i], &fs);
690
691       fmt_fix (&fs, FMT_FOR_INPUT);
692
693       struct variable *var = dict_get_var (ia->dict, i);
694
695       int width = fmt_var_width (&fs);
696
697       var_set_width_and_formats (var, width,
698                                  &fs, &fs);
699
700       proto = caseproto_add_width (proto, width);
701       fmt_guesser_destroy (fg[i]);
702     }
703
704   free (fg);
705
706   struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class,  ia);
707   /* Store the dictionary at this point when the casereader is created.
708      my_read depends on the dictionary to interpret the strings in the treeview.
709      This guarantees that the union value is produced according to the
710      caseproto in the reader. */
711   ia->casereader_dict = dict_clone (ia->dict);
712   caseproto_unref (proto);
713   return cr;
714 }
715
716
717 /* When during import the variable type is changed, the reader is reinitialized
718    based on the new dictionary with a fresh caseprototype. The default behaviour
719    when a variable type is changed and the column is resized is that the union
720    value is interpreted with new variable type and an overlay for that column
721    is generated. Here we reinit to the original reader based on strings.
722    As a result you can switch from string to numeric to string without loosing
723    the string information. */
724 static void
725 ia_variable_changed_cb (GObject *obj, gint var_num, guint what,
726                         const struct variable *oldvar, gpointer data)
727 {
728   PsppireImportAssistant *ia  = PSPPIRE_IMPORT_ASSISTANT (data);
729
730   struct caseproto *proto = caseproto_create();
731   for (int i = 0; i < dict_get_n_vars (ia->dict); i++)
732     {
733       const struct variable *var = dict_get_var (ia->dict, i);
734       int width = var_get_width (var);
735       proto = caseproto_add_width (proto, width);
736     }
737
738   gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
739
740   PsppireDataStore *store = NULL;
741   g_object_get (ia->data_sheet, "data-model", &store, NULL);
742
743   struct casereader *cr = casereader_create_random (proto, n_rows,
744                                                     &my_casereader_class, ia);
745   psppire_data_store_set_reader (store, cr);
746   dict_unref (ia->casereader_dict);
747   ia->casereader_dict = dict_clone (ia->dict);
748 }
749
750
751 /* Set the data model for both the data sheet and the variable sheet.  */
752 void
753 textfile_set_data_models (PsppireImportAssistant *ia)
754 {
755   my_casereader_class.read = my_read;
756   my_casereader_class.destroy = my_destroy;
757   my_casereader_class.advance = my_advance;
758
759   struct casereader *reader = textfile_create_reader (ia);
760
761   PsppireDict *dict = psppire_dict_new_from_dict (ia->dict);
762   PsppireDataStore *store = psppire_data_store_new (dict);
763   psppire_data_store_set_reader (store, reader);
764   g_signal_connect (dict, "variable-changed",
765                     G_CALLBACK (ia_variable_changed_cb),
766                     ia);
767
768   g_object_set (ia->data_sheet, "data-model", store, NULL);
769   g_object_set (ia->var_sheet, "data-model", dict, NULL);
770 }
771
772 static void
773 first_line_append_syntax (const PsppireImportAssistant *ia, struct string *s)
774 {
775   gint first_case = 0;
776   g_object_get (ia->delimiters_model, "first-line", &first_case, NULL);
777
778   if (first_case > 0)
779     ds_put_format (s, "  /FIRSTCASE=%d\n", first_case + 1);
780 }
781
782 /* Emits PSPP syntax to S that applies the dictionary attributes
783    (such as missing values and value labels) of the variables in
784    DICT.  */
785 static void
786 apply_dict (const struct dictionary *dict, struct string *s)
787 {
788   size_t n_vars = dict_get_n_vars (dict);
789
790   for (size_t i = 0; i < n_vars; i++)
791     {
792       struct variable *var = dict_get_var (dict, i);
793       const char *name = var_get_name (var);
794       enum val_type type = var_get_type (var);
795       int width = var_get_width (var);
796       enum measure measure = var_get_measure (var);
797       enum var_role role = var_get_role (var);
798       enum alignment alignment = var_get_alignment (var);
799       const struct fmt_spec *format = var_get_print_format (var);
800
801       if (var_has_missing_values (var))
802         {
803           const struct missing_values *mv = var_get_missing_values (var);
804           size_t j;
805
806           syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
807           for (j = 0; j < mv_n_values (mv); j++)
808             {
809               if (j)
810                 ds_put_cstr (s, ", ");
811               syntax_gen_value (s, mv_get_value (mv, j), width, format);
812             }
813
814           if (mv_has_range (mv))
815             {
816               double low, high;
817               if (mv_has_value (mv))
818                 ds_put_cstr (s, ", ");
819               mv_get_range (mv, &low, &high);
820               syntax_gen_num_range (s, low, high, format);
821             }
822           ds_put_cstr (s, ").\n");
823         }
824       if (var_has_value_labels (var))
825         {
826           const struct val_labs *vls = var_get_value_labels (var);
827           const struct val_lab **labels = val_labs_sorted (vls);
828           size_t n_labels = val_labs_count (vls);
829
830           syntax_gen_pspp (s, "VALUE LABELS %ss", name);
831           for (size_t j = 0; j < n_labels; j++)
832             {
833               const struct val_lab *vl = labels[j];
834               ds_put_cstr (s, "\n  ");
835               syntax_gen_value (s, &vl->value, width, format);
836               ds_put_byte (s, ' ');
837               syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl)));
838             }
839           free (labels);
840           ds_put_cstr (s, ".\n");
841         }
842       if (var_has_label (var))
843         syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
844                          name, var_get_label (var));
845       if (measure != var_default_measure (type))
846         syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
847                          name, measure_to_syntax (measure));
848       if (role != ROLE_INPUT)
849         syntax_gen_pspp (s, "VARIABLE ROLE /%ss %ss.\n",
850                          var_role_to_syntax (role), name);
851       if (alignment != var_default_alignment (type))
852         syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
853                          name, alignment_to_syntax (alignment));
854       if (var_get_display_width (var) != var_default_display_width (width))
855         syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
856                          name, var_get_display_width (var));
857     }
858 }
859
860
861 static void
862 intro_append_syntax (const PsppireImportAssistant *ia, struct string *s)
863 {
864   gint first_line = 0;
865   g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
866
867   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
868     ds_put_format (s, "SELECT IF ($CASENUM <= %d).\n",
869                    gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin)) - first_line);
870   else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
871     ds_put_format (s, "SAMPLE %.4g.\n",
872                    gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin)) / 100.0);
873 }
874
875
876 static void
877 formats_append_syntax (const PsppireImportAssistant *ia, struct string *s)
878 {
879   g_return_if_fail (ia->dict);
880
881   ds_put_cstr (s, "  /VARIABLES=\n");
882
883   int n_vars = dict_get_n_vars (ia->dict);
884   for (int i = 0; i < n_vars; i++)
885     {
886       struct variable *var = dict_get_var (ia->dict, i);
887       char format_string[FMT_STRING_LEN_MAX + 1];
888       fmt_to_string (var_get_print_format (var), format_string);
889       ds_put_format (s, "    %s %s%s\n",
890                      var_get_name (var), format_string,
891                      i == n_vars - 1 ? "." : "");
892     }
893 }
894
895 static void
896 separators_append_syntax (const PsppireImportAssistant *ia, struct string *s)
897 {
898   int i;
899
900   ds_put_cstr (s, "  /DELIMITERS=\"");
901
902   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->text_builder, "tab"))))
903     ds_put_cstr (s, "\\t");
904   for (i = 0; i < N_SEPARATORS; i++)
905     {
906       const struct separator *seps = &separators[i];
907       GtkWidget *button = get_widget_assert (ia->text_builder, seps->name);
908       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
909         {
910           if (seps->c == '\t')
911             continue;
912
913           ds_put_byte (s, seps->c);
914         }
915     }
916   ds_put_cstr (s, "\"\n");
917
918   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->quote_cb)))
919     {
920       GtkComboBoxText *cbt = GTK_COMBO_BOX_TEXT (ia->quote_combo);
921       gchar *quotes = gtk_combo_box_text_get_active_text (cbt);
922       if (quotes && *quotes)
923         syntax_gen_pspp (s, "  /QUALIFIER=%sq\n", quotes);
924       free (quotes);
925     }
926 }
927
928
929 void
930 text_spec_gen_syntax (PsppireImportAssistant *ia, struct string *s)
931 {
932   gchar *file_name = NULL;
933   gchar *encoding = NULL;
934   g_object_get (ia->text_file,
935                 "file-name", &file_name,
936                 "encoding", &encoding,
937                 NULL);
938
939   if (file_name == NULL)
940     return;
941
942   syntax_gen_pspp (s,
943                    "GET DATA"
944                    "\n  /TYPE=TXT"
945                    "\n  /FILE=%sq\n",
946                    file_name);
947   if (encoding && strcmp (encoding, "Auto"))
948     syntax_gen_pspp (s, "  /ENCODING=%sq\n", encoding);
949
950   ds_put_cstr (s,
951                "  /ARRANGEMENT=DELIMITED\n"
952                "  /DELCASE=LINE\n");
953
954   first_line_append_syntax (ia, s);
955   separators_append_syntax (ia, s);
956
957   formats_append_syntax (ia, s);
958   apply_dict (ia->dict, s);
959   intro_append_syntax (ia, s);
960 }