Rework the spreadsheet import feature of the grapic user interface
[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
35 #include "builder-wrapper.h"
36
37 #include "psppire-data-store.h"
38 #include "psppire-scanf.h"
39
40 #include "ui/syntax-gen.h"
41
42 #include <gettext.h>
43 #define _(msgid) gettext (msgid)
44 #define N_(msgid) msgid
45
46 /* Chooses a name for each column on the separators page */
47 static void choose_column_names (PsppireImportAssistant *ia);
48
49 /* Revises the contents of the fields tree view based on the
50    currently chosen set of separators. */
51 static void
52 revise_fields_preview (PsppireImportAssistant *ia)
53 {
54   choose_column_names (ia);
55 }
56
57
58 struct separator_count_node
59 {
60   struct hmap_node node;
61   int occurance; /* The number of times the separator occurs in a line */
62   int quantity;  /* The number of lines with this occurance */
63 };
64
65
66 /* Picks the most likely separator and quote characters based on
67    IA's file data. */
68 static void
69 choose_likely_separators (PsppireImportAssistant *ia)
70 {
71   gint first_line = 0;
72   g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
73
74   gboolean valid;
75   GtkTreeIter iter;
76   int j;
77
78   struct hmap count_map[SEPARATOR_CNT];
79   for (j = 0; j < SEPARATOR_CNT; ++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 = xzalloc (sizeof *counts * SEPARATOR_CNT);
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 < SEPARATOR_CNT; ++s)
102             {
103               if (character == separators[s].c)
104                 counts[s]++;
105             }
106         }
107
108       int j;
109       for (j = 0; j < SEPARATOR_CNT; ++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 (sizeof *new_cn);
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 (j = 0; j < SEPARATOR_CNT; ++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->line_cnt),
424                          ia->text_file->line_cnt);
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 < SEPARATOR_CNT; 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 < SEPARATOR_CNT; 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)
619 {
620   gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view),
621                            GTK_TREE_MODEL (ia->delimiters_model));
622
623   g_signal_connect_swapped (GTK_TREE_MODEL (ia->delimiters_model), "notify::delimiters",
624                         G_CALLBACK (reset_tree_view_model), ia);
625
626
627   reset_separators_page (ia);
628 }
629
630
631 /* Initializes IA's separators substructure. */
632 void
633 separators_page_create (PsppireImportAssistant *ia)
634 {
635   GtkBuilder *builder = ia->text_builder;
636
637   size_t i;
638
639   GtkWidget *w = get_widget_assert (builder, "Separators");
640
641   g_object_set_data (G_OBJECT (w), "on-entering", prepare_separators_page);
642   g_object_set_data (G_OBJECT (w), "on-reset", reset_separators_page);
643
644   add_page_to_assistant (ia, w,   GTK_ASSISTANT_PAGE_CONTENT, _("Choose Separators"));
645
646   ia->custom_cb = get_widget_assert (builder, "custom-cb");
647   ia->custom_entry = get_widget_assert (builder, "custom-entry");
648   ia->quote_combo = get_widget_assert (builder, "quote-combo");
649   ia->quote_cb = get_widget_assert (builder, "quote-cb");
650
651   gtk_widget_set_sensitive (ia->custom_entry,
652                             gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->custom_cb)));
653
654   gtk_combo_box_set_active (GTK_COMBO_BOX (ia->quote_combo), 0);
655
656   if (ia->fields_tree_view == NULL)
657     {
658       GtkWidget *scroller = get_widget_assert (ia->text_builder, "fields-scroller");
659       ia->fields_tree_view = gtk_tree_view_new ();
660       g_object_set (ia->fields_tree_view, "enable-search", FALSE, NULL);
661       gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ia->fields_tree_view));
662       gtk_widget_show_all (scroller);
663     }
664
665   g_signal_connect (ia->quote_combo, "changed",
666                     G_CALLBACK (on_quote_combo_change), ia);
667   g_signal_connect (ia->quote_cb, "toggled",
668                     G_CALLBACK (on_quote_cb_toggle), ia);
669   g_signal_connect (ia->custom_entry, "notify::text",
670                     G_CALLBACK (on_separators_custom_entry_notify), ia);
671   g_signal_connect (ia->custom_cb, "toggled",
672                     G_CALLBACK (on_separators_custom_cb_toggle), ia);
673   for (i = 0; i < SEPARATOR_CNT; i++)
674     g_signal_connect (get_widget_assert (builder, separators[i].name),
675                       "toggled", G_CALLBACK (on_separator_toggle), ia);
676
677   reset_separators_page (ia);
678 }
679
680 \f
681
682 static struct casereader_random_class my_casereader_class;
683
684 static struct ccase *
685 my_read (struct casereader *reader, void *aux, casenumber idx)
686 {
687   PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (aux);
688   GtkTreeModel *tm = GTK_TREE_MODEL (ia->delimiters_model);
689
690   GtkTreePath *tp = gtk_tree_path_new_from_indices (idx, -1);
691
692   const struct caseproto *proto = casereader_get_proto (reader);
693
694   GtkTreeIter iter;
695   struct ccase *c = NULL;
696   if (gtk_tree_model_get_iter (tm, &iter, tp))
697     {
698       c = case_create (proto);
699       int i;
700       for (i = 0 ; i < caseproto_get_n_widths (proto); ++i)
701         {
702           GValue value = {0};
703           gtk_tree_model_get_value (tm, &iter, i + 1, &value);
704
705           const struct variable *var = dict_get_var (ia->casereader_dict, i);
706
707           const gchar *ss = g_value_get_string (&value);
708           if (ss)
709             {
710               union value *v = case_data_rw (c, var);
711               /* In this reader we derive the union value from the
712                  string in the tree_model. We retrieve the width and format
713                  from a dictionary which is stored directly after
714                  the reader creation. Changes in ia->dict in the
715                  variable window are not reflected here and therefore
716                  this is always compatible with the width in the
717                  caseproto. See bug #58298 */
718               char *xx = data_in (ss_cstr (ss),
719                                   "UTF-8",
720                                   var_get_write_format (var)->type,
721                                   v, var_get_width (var), "UTF-8");
722
723               free (xx);
724             }
725           g_value_unset (&value);
726         }
727     }
728
729   gtk_tree_path_free (tp);
730
731   return c;
732 }
733
734 static void
735 my_destroy (struct casereader *reader, void *aux)
736 {
737   g_print ("%s:%d %p\n", __FILE__, __LINE__, reader);
738 }
739
740 static void
741 my_advance (struct casereader *reader, void *aux, casenumber cnt)
742 {
743   g_print ("%s:%d\n", __FILE__, __LINE__);
744 }
745
746 static struct casereader *
747 textfile_create_reader (PsppireImportAssistant *ia)
748 {
749   int n_vars = dict_get_var_cnt (ia->dict);
750
751   int i;
752
753   struct fmt_guesser **fg = XCALLOC (n_vars,  struct fmt_guesser *);
754   for (i = 0 ; i < n_vars; ++i)
755     {
756       fg[i] = fmt_guesser_create ();
757     }
758
759   gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
760
761   GtkTreeIter iter;
762   gboolean ok;
763   for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->delimiters_model), &iter);
764        ok;
765        ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->delimiters_model), &iter))
766     {
767       for (i = 0 ; i < n_vars; ++i)
768         {
769           gchar *s = NULL;
770           gtk_tree_model_get (GTK_TREE_MODEL (ia->delimiters_model), &iter, i+1, &s, -1);
771           if (s)
772             fmt_guesser_add (fg[i], ss_cstr (s));
773           free (s);
774         }
775     }
776
777   struct caseproto *proto = caseproto_create ();
778   for (i = 0 ; i < n_vars; ++i)
779     {
780       struct fmt_spec fs;
781       fmt_guesser_guess (fg[i], &fs);
782
783       fmt_fix (&fs, FMT_FOR_INPUT);
784
785       struct variable *var = dict_get_var (ia->dict, i);
786
787       int width = fmt_var_width (&fs);
788
789       var_set_width_and_formats (var, width,
790                                  &fs, &fs);
791
792       proto = caseproto_add_width (proto, width);
793       fmt_guesser_destroy (fg[i]);
794     }
795
796   free (fg);
797
798   struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class,  ia);
799   /* Store the dictionary at this point when the casereader is created.
800      my_read depends on the dictionary to interpret the strings in the treeview.
801      This guarantees that the union value is produced according to the
802      caseproto in the reader. */
803   ia->casereader_dict = dict_clone (ia->dict);
804   caseproto_unref (proto);
805   return cr;
806 }
807
808
809 /* When during import the variable type is changed, the reader is reinitialized
810    based on the new dictionary with a fresh caseprototype. The default behaviour
811    when a variable type is changed and the column is resized is that the union
812    value is interpreted with new variable type and an overlay for that column
813    is generated. Here we reinit to the original reader based on strings.
814    As a result you can switch from string to numeric to string without loosing
815    the string information. */
816 static void
817 ia_variable_changed_cb (GObject *obj, gint var_num, guint what,
818                         const struct variable *oldvar, gpointer data)
819 {
820   PsppireImportAssistant *ia  = PSPPIRE_IMPORT_ASSISTANT (data);
821
822   struct caseproto *proto = caseproto_create();
823   for (int i = 0; i < dict_get_var_cnt (ia->dict); i++)
824     {
825       const struct variable *var = dict_get_var (ia->dict, i);
826       int width = var_get_width (var);
827       proto = caseproto_add_width (proto, width);
828     }
829
830   gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
831
832   PsppireDataStore *store = NULL;
833   g_object_get (ia->data_sheet, "data-model", &store, NULL);
834
835   struct casereader *cr = casereader_create_random (proto, n_rows,
836                                                     &my_casereader_class, ia);
837   psppire_data_store_set_reader (store, cr);
838   dict_unref (ia->casereader_dict);
839   ia->casereader_dict = dict_clone (ia->dict);
840 }
841
842
843 /* Set the data model for both the data sheet and the variable sheet.  */
844 void
845 textfile_set_data_models (PsppireImportAssistant *ia)
846 {
847   my_casereader_class.read = my_read;
848   my_casereader_class.destroy = my_destroy;
849   my_casereader_class.advance = my_advance;
850
851   struct casereader *reader = textfile_create_reader (ia);
852
853   PsppireDict *dict = psppire_dict_new_from_dict (ia->dict);
854   PsppireDataStore *store = psppire_data_store_new (dict);
855   psppire_data_store_set_reader (store, reader);
856   g_signal_connect (dict, "variable-changed",
857                     G_CALLBACK (ia_variable_changed_cb),
858                     ia);
859
860   g_object_set (ia->data_sheet, "data-model", store, NULL);
861   g_object_set (ia->var_sheet, "data-model", dict, NULL);
862 }