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