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