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