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