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