Move syntax generation to the psppire-import-textfile module
[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)
620 {
621   gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view),
622                            GTK_TREE_MODEL (ia->delimiters_model));
623
624   g_signal_connect_swapped (GTK_TREE_MODEL (ia->delimiters_model), "notify::delimiters",
625                         G_CALLBACK (reset_tree_view_model), ia);
626
627
628   reset_separators_page (ia);
629 }
630
631
632 /* Initializes IA's separators substructure. */
633 void
634 separators_page_create (PsppireImportAssistant *ia)
635 {
636   GtkBuilder *builder = ia->text_builder;
637
638   size_t i;
639
640   GtkWidget *w = get_widget_assert (builder, "Separators");
641
642   g_object_set_data (G_OBJECT (w), "on-entering", prepare_separators_page);
643   g_object_set_data (G_OBJECT (w), "on-reset", reset_separators_page);
644
645   add_page_to_assistant (ia, w,   GTK_ASSISTANT_PAGE_CONTENT, _("Choose Separators"));
646
647   ia->custom_cb = get_widget_assert (builder, "custom-cb");
648   ia->custom_entry = get_widget_assert (builder, "custom-entry");
649   ia->quote_combo = get_widget_assert (builder, "quote-combo");
650   ia->quote_cb = get_widget_assert (builder, "quote-cb");
651
652   gtk_widget_set_sensitive (ia->custom_entry,
653                             gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->custom_cb)));
654
655   gtk_combo_box_set_active (GTK_COMBO_BOX (ia->quote_combo), 0);
656
657   if (ia->fields_tree_view == NULL)
658     {
659       GtkWidget *scroller = get_widget_assert (ia->text_builder, "fields-scroller");
660       ia->fields_tree_view = gtk_tree_view_new ();
661       g_object_set (ia->fields_tree_view, "enable-search", FALSE, NULL);
662       gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ia->fields_tree_view));
663       gtk_widget_show_all (scroller);
664     }
665
666   g_signal_connect (ia->quote_combo, "changed",
667                     G_CALLBACK (on_quote_combo_change), ia);
668   g_signal_connect (ia->quote_cb, "toggled",
669                     G_CALLBACK (on_quote_cb_toggle), ia);
670   g_signal_connect (ia->custom_entry, "notify::text",
671                     G_CALLBACK (on_separators_custom_entry_notify), ia);
672   g_signal_connect (ia->custom_cb, "toggled",
673                     G_CALLBACK (on_separators_custom_cb_toggle), ia);
674   for (i = 0; i < SEPARATOR_CNT; i++)
675     g_signal_connect (get_widget_assert (builder, separators[i].name),
676                       "toggled", G_CALLBACK (on_separator_toggle), ia);
677
678   reset_separators_page (ia);
679 }
680
681 \f
682
683 static struct casereader_random_class my_casereader_class;
684
685 static struct ccase *
686 my_read (struct casereader *reader, void *aux, casenumber idx)
687 {
688   PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (aux);
689   GtkTreeModel *tm = GTK_TREE_MODEL (ia->delimiters_model);
690
691   GtkTreePath *tp = gtk_tree_path_new_from_indices (idx, -1);
692
693   const struct caseproto *proto = casereader_get_proto (reader);
694
695   GtkTreeIter iter;
696   struct ccase *c = NULL;
697   if (gtk_tree_model_get_iter (tm, &iter, tp))
698     {
699       c = case_create (proto);
700       int i;
701       for (i = 0 ; i < caseproto_get_n_widths (proto); ++i)
702         {
703           GValue value = {0};
704           gtk_tree_model_get_value (tm, &iter, i + 1, &value);
705
706           const struct variable *var = dict_get_var (ia->casereader_dict, i);
707
708           const gchar *ss = g_value_get_string (&value);
709           if (ss)
710             {
711               union value *v = case_data_rw (c, var);
712               /* In this reader we derive the union value from the
713                  string in the tree_model. We retrieve the width and format
714                  from a dictionary which is stored directly after
715                  the reader creation. Changes in ia->dict in the
716                  variable window are not reflected here and therefore
717                  this is always compatible with the width in the
718                  caseproto. See bug #58298 */
719               char *xx = data_in (ss_cstr (ss),
720                                   "UTF-8",
721                                   var_get_write_format (var)->type,
722                                   v, var_get_width (var), "UTF-8");
723
724               free (xx);
725             }
726           g_value_unset (&value);
727         }
728     }
729
730   gtk_tree_path_free (tp);
731
732   return c;
733 }
734
735 static void
736 my_destroy (struct casereader *reader, void *aux)
737 {
738   g_print ("%s:%d %p\n", __FILE__, __LINE__, reader);
739 }
740
741 static void
742 my_advance (struct casereader *reader, void *aux, casenumber cnt)
743 {
744   g_print ("%s:%d\n", __FILE__, __LINE__);
745 }
746
747 static struct casereader *
748 textfile_create_reader (PsppireImportAssistant *ia)
749 {
750   int n_vars = dict_get_var_cnt (ia->dict);
751
752   int i;
753
754   struct fmt_guesser **fg = XCALLOC (n_vars,  struct fmt_guesser *);
755   for (i = 0 ; i < n_vars; ++i)
756     {
757       fg[i] = fmt_guesser_create ();
758     }
759
760   gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
761
762   GtkTreeIter iter;
763   gboolean ok;
764   for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->delimiters_model), &iter);
765        ok;
766        ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->delimiters_model), &iter))
767     {
768       for (i = 0 ; i < n_vars; ++i)
769         {
770           gchar *s = NULL;
771           gtk_tree_model_get (GTK_TREE_MODEL (ia->delimiters_model), &iter, i+1, &s, -1);
772           if (s)
773             fmt_guesser_add (fg[i], ss_cstr (s));
774           free (s);
775         }
776     }
777
778   struct caseproto *proto = caseproto_create ();
779   for (i = 0 ; i < n_vars; ++i)
780     {
781       struct fmt_spec fs;
782       fmt_guesser_guess (fg[i], &fs);
783
784       fmt_fix (&fs, FMT_FOR_INPUT);
785
786       struct variable *var = dict_get_var (ia->dict, i);
787
788       int width = fmt_var_width (&fs);
789
790       var_set_width_and_formats (var, width,
791                                  &fs, &fs);
792
793       proto = caseproto_add_width (proto, width);
794       fmt_guesser_destroy (fg[i]);
795     }
796
797   free (fg);
798
799   struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class,  ia);
800   /* Store the dictionary at this point when the casereader is created.
801      my_read depends on the dictionary to interpret the strings in the treeview.
802      This guarantees that the union value is produced according to the
803      caseproto in the reader. */
804   ia->casereader_dict = dict_clone (ia->dict);
805   caseproto_unref (proto);
806   return cr;
807 }
808
809
810 /* When during import the variable type is changed, the reader is reinitialized
811    based on the new dictionary with a fresh caseprototype. The default behaviour
812    when a variable type is changed and the column is resized is that the union
813    value is interpreted with new variable type and an overlay for that column
814    is generated. Here we reinit to the original reader based on strings.
815    As a result you can switch from string to numeric to string without loosing
816    the string information. */
817 static void
818 ia_variable_changed_cb (GObject *obj, gint var_num, guint what,
819                         const struct variable *oldvar, gpointer data)
820 {
821   PsppireImportAssistant *ia  = PSPPIRE_IMPORT_ASSISTANT (data);
822
823   struct caseproto *proto = caseproto_create();
824   for (int i = 0; i < dict_get_var_cnt (ia->dict); i++)
825     {
826       const struct variable *var = dict_get_var (ia->dict, i);
827       int width = var_get_width (var);
828       proto = caseproto_add_width (proto, width);
829     }
830
831   gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
832
833   PsppireDataStore *store = NULL;
834   g_object_get (ia->data_sheet, "data-model", &store, NULL);
835
836   struct casereader *cr = casereader_create_random (proto, n_rows,
837                                                     &my_casereader_class, ia);
838   psppire_data_store_set_reader (store, cr);
839   dict_unref (ia->casereader_dict);
840   ia->casereader_dict = dict_clone (ia->dict);
841 }
842
843
844 /* Set the data model for both the data sheet and the variable sheet.  */
845 void
846 textfile_set_data_models (PsppireImportAssistant *ia)
847 {
848   my_casereader_class.read = my_read;
849   my_casereader_class.destroy = my_destroy;
850   my_casereader_class.advance = my_advance;
851
852   struct casereader *reader = textfile_create_reader (ia);
853
854   PsppireDict *dict = psppire_dict_new_from_dict (ia->dict);
855   PsppireDataStore *store = psppire_data_store_new (dict);
856   psppire_data_store_set_reader (store, reader);
857   g_signal_connect (dict, "variable-changed",
858                     G_CALLBACK (ia_variable_changed_cb),
859                     ia);
860
861   g_object_set (ia->data_sheet, "data-model", store, NULL);
862   g_object_set (ia->var_sheet, "data-model", dict, NULL);
863 }
864
865 static void
866 first_line_append_syntax (const PsppireImportAssistant *ia, struct string *s)
867 {
868   gint first_case = 0;
869   g_object_get (ia->delimiters_model, "first-line", &first_case, NULL);
870
871   if (first_case > 0)
872     ds_put_format (s, "  /FIRSTCASE=%d\n", first_case + 1);
873 }
874
875 /* Emits PSPP syntax to S that applies the dictionary attributes
876    (such as missing values and value labels) of the variables in
877    DICT.  */
878 static void
879 apply_dict (const struct dictionary *dict, struct string *s)
880 {
881   size_t var_cnt = dict_get_var_cnt (dict);
882   size_t i;
883
884   for (i = 0; i < var_cnt; i++)
885     {
886       struct variable *var = dict_get_var (dict, i);
887       const char *name = var_get_name (var);
888       enum val_type type = var_get_type (var);
889       int width = var_get_width (var);
890       enum measure measure = var_get_measure (var);
891       enum var_role role = var_get_role (var);
892       enum alignment alignment = var_get_alignment (var);
893       const struct fmt_spec *format = var_get_print_format (var);
894
895       if (var_has_missing_values (var))
896         {
897           const struct missing_values *mv = var_get_missing_values (var);
898           size_t j;
899
900           syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
901           for (j = 0; j < mv_n_values (mv); j++)
902             {
903               if (j)
904                 ds_put_cstr (s, ", ");
905               syntax_gen_value (s, mv_get_value (mv, j), width, format);
906             }
907
908           if (mv_has_range (mv))
909             {
910               double low, high;
911               if (mv_has_value (mv))
912                 ds_put_cstr (s, ", ");
913               mv_get_range (mv, &low, &high);
914               syntax_gen_num_range (s, low, high, format);
915             }
916           ds_put_cstr (s, ").\n");
917         }
918       if (var_has_value_labels (var))
919         {
920           const struct val_labs *vls = var_get_value_labels (var);
921           const struct val_lab **labels = val_labs_sorted (vls);
922           size_t n_labels = val_labs_count (vls);
923           size_t i;
924
925           syntax_gen_pspp (s, "VALUE LABELS %ss", name);
926           for (i = 0; i < n_labels; i++)
927             {
928               const struct val_lab *vl = labels[i];
929               ds_put_cstr (s, "\n  ");
930               syntax_gen_value (s, &vl->value, width, format);
931               ds_put_byte (s, ' ');
932               syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl)));
933             }
934           free (labels);
935           ds_put_cstr (s, ".\n");
936         }
937       if (var_has_label (var))
938         syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
939                          name, var_get_label (var));
940       if (measure != var_default_measure (type))
941         syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
942                          name, measure_to_syntax (measure));
943       if (role != ROLE_INPUT)
944         syntax_gen_pspp (s, "VARIABLE ROLE /%ss %ss.\n",
945                          var_role_to_syntax (role), name);
946       if (alignment != var_default_alignment (type))
947         syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
948                          name, alignment_to_syntax (alignment));
949       if (var_get_display_width (var) != var_default_display_width (width))
950         syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
951                          name, var_get_display_width (var));
952     }
953 }
954
955
956 static void
957 intro_append_syntax (const PsppireImportAssistant *ia, struct string *s)
958 {
959   gint first_line = 0;
960   g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
961
962   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
963     ds_put_format (s, "SELECT IF ($CASENUM <= %d).\n",
964                    gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin)) - first_line);
965   else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
966     ds_put_format (s, "SAMPLE %.4g.\n",
967                    gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin)) / 100.0);
968 }
969
970
971 static void
972 formats_append_syntax (const PsppireImportAssistant *ia, struct string *s)
973 {
974   int i;
975   int var_cnt;
976
977   g_return_if_fail (ia->dict);
978
979   ds_put_cstr (s, "  /VARIABLES=\n");
980
981   var_cnt = dict_get_var_cnt (ia->dict);
982   for (i = 0; i < var_cnt; i++)
983     {
984       struct variable *var = dict_get_var (ia->dict, i);
985       char format_string[FMT_STRING_LEN_MAX + 1];
986       fmt_to_string (var_get_print_format (var), format_string);
987       ds_put_format (s, "    %s %s%s\n",
988                      var_get_name (var), format_string,
989                      i == var_cnt - 1 ? "." : "");
990     }
991 }
992
993 static void
994 separators_append_syntax (const PsppireImportAssistant *ia, struct string *s)
995 {
996   int i;
997
998   ds_put_cstr (s, "  /DELIMITERS=\"");
999
1000   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->text_builder, "tab"))))
1001     ds_put_cstr (s, "\\t");
1002   for (i = 0; i < SEPARATOR_CNT; i++)
1003     {
1004       const struct separator *seps = &separators[i];
1005       GtkWidget *button = get_widget_assert (ia->text_builder, seps->name);
1006       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1007         {
1008           if (seps->c == '\t')
1009             continue;
1010
1011           ds_put_byte (s, seps->c);
1012         }
1013     }
1014   ds_put_cstr (s, "\"\n");
1015
1016   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->quote_cb)))
1017     {
1018       GtkComboBoxText *cbt = GTK_COMBO_BOX_TEXT (ia->quote_combo);
1019       gchar *quotes = gtk_combo_box_text_get_active_text (cbt);
1020       if (quotes && *quotes)
1021         syntax_gen_pspp (s, "  /QUALIFIER=%sq\n", quotes);
1022       free (quotes);
1023     }
1024 }
1025
1026
1027 void
1028 text_spec_gen_syntax (PsppireImportAssistant *ia, struct string *s)
1029 {
1030   gchar *file_name = NULL;
1031   gchar *encoding = NULL;
1032   g_object_get (ia->text_file,
1033                 "file-name", &file_name,
1034                 "encoding", &encoding,
1035                 NULL);
1036
1037   if (file_name == NULL)
1038     return;
1039
1040   syntax_gen_pspp (s,
1041                    "GET DATA"
1042                    "\n  /TYPE=TXT"
1043                    "\n  /FILE=%sq\n",
1044                    file_name);
1045   if (encoding && strcmp (encoding, "Auto"))
1046     syntax_gen_pspp (s, "  /ENCODING=%sq\n", encoding);
1047
1048   ds_put_cstr (s,
1049                "  /ARRANGEMENT=DELIMITED\n"
1050                "  /DELCASE=LINE\n");
1051
1052   first_line_append_syntax (ia, s);
1053   separators_append_syntax (ia, s);
1054
1055   formats_append_syntax (ia, s);
1056   apply_dict (ia->dict, s);
1057   intro_append_syntax (ia, s);
1058 }