fixed goto case dialog
[pspp] / src / ui / gui / psppire-import-assistant.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 <gtk/gtk.h>
20
21 #include "data/casereader.h"
22 #include "data/data-in.h"
23 #include "data/data-out.h"
24 #include "data/dictionary.h"
25 #include "data/format-guesser.h"
26 #include "data/format.h"
27 #include "data/gnumeric-reader.h"
28 #include "data/ods-reader.h"
29 #include "data/spreadsheet-reader.h"
30 #include "data/value-labels.h"
31 #include "data/casereader-provider.h"
32
33 #include "libpspp/i18n.h"
34 #include "libpspp/line-reader.h"
35 #include "libpspp/message.h"
36 #include "libpspp/hmap.h"
37 #include "libpspp/hash-functions.h"
38 #include "libpspp/str.h"
39
40 #include "builder-wrapper.h"
41
42 #include "psppire-data-sheet.h"
43 #include "psppire-data-store.h"
44 #include "psppire-dialog.h"
45 #include "psppire-delimited-text.h"
46 #include "psppire-dict.h"
47 #include "psppire-encoding-selector.h"
48 #include "psppire-import-assistant.h"
49 #include "psppire-scanf.h"
50 #include "psppire-spreadsheet-model.h"
51 #include "psppire-text-file.h"
52 #include "psppire-variable-sheet.h"
53
54 #include "ui/syntax-gen.h"
55
56 #include <gettext.h>
57 #define _(msgid) gettext (msgid)
58 #define N_(msgid) msgid
59
60 enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
61
62
63 /* Chooses a name for each column on the separators page */
64 static void choose_column_names (PsppireImportAssistant *ia);
65
66 static void intro_page_create (PsppireImportAssistant *ia);
67 static void first_line_page_create (PsppireImportAssistant *ia);
68
69 static void separators_page_create (PsppireImportAssistant *ia);
70 static void formats_page_create (PsppireImportAssistant *ia);
71
72 static void psppire_import_assistant_init            (PsppireImportAssistant      *act);
73 static void psppire_import_assistant_class_init      (PsppireImportAssistantClass *class);
74
75 G_DEFINE_TYPE (PsppireImportAssistant, psppire_import_assistant, GTK_TYPE_ASSISTANT);
76
77
78 /* Properties */
79 enum
80   {
81     PROP_0,
82   };
83
84 static void
85 psppire_import_assistant_set_property (GObject         *object,
86                                        guint            prop_id,
87                                        const GValue    *value,
88                                        GParamSpec      *pspec)
89 {
90   //   PsppireImportAssistant *act = PSPPIRE_IMPORT_ASSISTANT (object);
91
92   switch (prop_id)
93     {
94     default:
95       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
96       break;
97     };
98 }
99
100
101 static void
102 psppire_import_assistant_get_property (GObject    *object,
103                                        guint            prop_id,
104                                        GValue          *value,
105                                        GParamSpec      *pspec)
106 {
107   //  PsppireImportAssistant *assistant = PSPPIRE_IMPORT_ASSISTANT (object);
108
109   switch (prop_id)
110     {
111     default:
112       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
113       break;
114     };
115 }
116
117 static GObjectClass * parent_class = NULL;
118
119
120 static void
121 psppire_import_assistant_finalize (GObject *object)
122 {
123   PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (object);
124
125   if (ia->spreadsheet)
126     spreadsheet_unref (ia->spreadsheet);
127
128   ds_destroy (&ia->quotes);
129
130   dict_unref (ia->dict);
131   dict_unref (ia->casereader_dict);
132
133   g_object_unref (ia->builder);
134
135   ia->response = -1;
136   g_main_loop_unref (ia->main_loop);
137
138   if (G_OBJECT_CLASS (parent_class)->finalize)
139     G_OBJECT_CLASS (parent_class)->finalize (object);
140 }
141
142
143 static void
144 psppire_import_assistant_class_init (PsppireImportAssistantClass *class)
145 {
146   GObjectClass *object_class = G_OBJECT_CLASS (class);
147
148   parent_class = g_type_class_peek_parent (class);
149
150   object_class->set_property = psppire_import_assistant_set_property;
151   object_class->get_property = psppire_import_assistant_get_property;
152
153   object_class->finalize = psppire_import_assistant_finalize;
154 }
155
156
157 /* Causes the assistant to close, returning RESPONSE for
158    interpretation by text_data_import_assistant. */
159 static void
160 close_assistant (PsppireImportAssistant *ia, int response)
161 {
162   ia->response = response;
163   g_main_loop_quit (ia->main_loop);
164   gtk_widget_hide (GTK_WIDGET (ia));
165 }
166
167
168 /* Called when the Paste button on the last page of the assistant
169    is clicked. */
170 static void
171 on_paste (GtkButton *button, PsppireImportAssistant *ia)
172 {
173   close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
174 }
175
176
177 /* Revises the contents of the fields tree view based on the
178    currently chosen set of separators. */
179 static void
180 revise_fields_preview (PsppireImportAssistant *ia)
181 {
182   choose_column_names (ia);
183 }
184
185
186 struct separator
187 {
188   const char *name;           /* Name (for use with get_widget_assert). */
189   gunichar c;                 /* Separator character. */
190 };
191
192 /* All the separators in the dialog box. */
193 static const struct separator separators[] =
194   {
195     {"space",     ' '},
196     {"tab",       '\t'},
197     {"bang",      '!'},
198     {"colon",     ':'},
199     {"comma",     ','},
200     {"hyphen",    '-'},
201     {"pipe",      '|'},
202     {"semicolon", ';'},
203     {"slash",     '/'},
204   };
205
206 #define SEPARATOR_CNT (sizeof separators / sizeof *separators)
207
208 struct separator_count_node
209 {
210   struct hmap_node node;
211   int occurance; /* The number of times the separator occurs in a line */
212   int quantity;  /* The number of lines with this occurance */
213 };
214
215
216 /* Picks the most likely separator and quote characters based on
217    IA's file data. */
218 static void
219 choose_likely_separators (PsppireImportAssistant *ia)
220 {
221   gint first_line = 0;
222   g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
223
224   gboolean valid;
225   GtkTreeIter iter;
226   int j;
227
228   struct hmap count_map[SEPARATOR_CNT];
229   for (j = 0; j < SEPARATOR_CNT; ++j)
230     hmap_init (count_map + j);
231
232   GtkTreePath *p = gtk_tree_path_new_from_indices (first_line, -1);
233
234   for (valid = gtk_tree_model_get_iter (GTK_TREE_MODEL (ia->text_file), &iter, p);
235        valid;
236        valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->text_file), &iter))
237     {
238       gchar *line_text = NULL;
239       gtk_tree_model_get (GTK_TREE_MODEL (ia->text_file), &iter, 1, &line_text, -1);
240
241       gint *counts = xzalloc (sizeof *counts * SEPARATOR_CNT);
242
243       struct substring cs = ss_cstr (line_text);
244       for (;
245            UINT32_MAX != ss_first_mb (cs);
246            ss_get_mb (&cs))
247         {
248           ucs4_t character = ss_first_mb (cs);
249
250           int s;
251           for (s = 0; s < SEPARATOR_CNT; ++s)
252             {
253               if (character == separators[s].c)
254                 counts[s]++;
255             }
256         }
257
258       int j;
259       for (j = 0; j < SEPARATOR_CNT; ++j)
260         {
261           if (counts[j] > 0)
262             {
263               struct separator_count_node *cn = NULL;
264               unsigned int hash = hash_int (counts[j], 0);
265               HMAP_FOR_EACH_WITH_HASH (cn, struct separator_count_node, node, hash, &count_map[j])
266                 {
267                   if (cn->occurance == counts[j])
268                     break;
269                 }
270
271               if (cn == NULL)
272                 {
273                   struct separator_count_node *new_cn = xzalloc (sizeof *new_cn);
274                   new_cn->occurance = counts[j];
275                   new_cn->quantity = 1;
276                   hmap_insert (&count_map[j], &new_cn->node, hash);
277                 }
278               else
279                 cn->quantity++;
280             }
281         }
282
283       free (line_text);
284       free (counts);
285     }
286   gtk_tree_path_free (p);
287
288   int most_frequent = -1;
289   int largest = 0;
290   for (j = 0; j < SEPARATOR_CNT; ++j)
291     {
292       struct separator_count_node *cn;
293       HMAP_FOR_EACH (cn, struct separator_count_node, node, &count_map[j])
294         {
295           if (largest < cn->quantity)
296             {
297               largest = cn->quantity;
298               most_frequent = j;
299             }
300         }
301       hmap_destroy (&count_map[j]);
302     }
303
304   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->builder, separators[most_frequent].name)), TRUE);
305 }
306
307 static void
308 repopulate_delimiter_columns (PsppireImportAssistant *ia)
309 {
310   /* Remove all the columns */
311   while (gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view)) > 0)
312     {
313       GtkTreeViewColumn *tvc = gtk_tree_view_get_column (GTK_TREE_VIEW (ia->fields_tree_view), 0);
314       gtk_tree_view_remove_column (GTK_TREE_VIEW (ia->fields_tree_view), tvc);
315     }
316
317   gint n_fields =
318     gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model));
319
320   /* ... and put them back again. */
321   gint f;
322   for (f = gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view));
323        f < n_fields; f++)
324     {
325       GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
326
327       const gchar *title = NULL;
328
329       if (f == 0)
330         title = _("line");
331       else
332         {
333           if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
334             {
335               title =
336                 psppire_delimited_text_get_header_title
337                 (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), f - 1);
338             }
339           if (title == NULL)
340             title = _("var");
341         }
342
343       GtkTreeViewColumn *column =
344         gtk_tree_view_column_new_with_attributes (title,
345                                                   renderer,
346                                                   "text", f,
347                                                   NULL);
348       g_object_set (column,
349                     "resizable", TRUE,
350                     "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE,
351                     NULL);
352
353       gtk_tree_view_append_column (GTK_TREE_VIEW (ia->fields_tree_view), column);
354     }
355 }
356
357 static void
358 reset_tree_view_model (PsppireImportAssistant *ia)
359 {
360   GtkTreeModel *tm = gtk_tree_view_get_model (GTK_TREE_VIEW (ia->fields_tree_view));
361   g_object_ref (tm);
362   gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), NULL);
363
364
365   repopulate_delimiter_columns (ia);
366
367   gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), tm);
368   //  gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ia->fields_tree_view));
369
370   g_object_unref (tm);
371 }
372
373 /* Called just before the separators page becomes visible in the
374    assistant, and when the Reset button is clicked. */
375 static void
376 prepare_separators_page (PsppireImportAssistant *ia, GtkWidget *page)
377 {
378   gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view),
379                            GTK_TREE_MODEL (ia->delimiters_model));
380
381   g_signal_connect_swapped (GTK_TREE_MODEL (ia->delimiters_model), "notify::delimiters",
382                         G_CALLBACK (reset_tree_view_model), ia);
383
384
385   repopulate_delimiter_columns (ia);
386
387   revise_fields_preview (ia);
388   choose_likely_separators (ia);
389 }
390
391 /* Resets IA's intro page to its initial state. */
392 static void
393 reset_intro_page (PsppireImportAssistant *ia)
394 {
395   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->all_cases_button),
396                                 TRUE);
397 }
398
399
400
401 /* Clears the set of user-modified variables from IA's formats
402    substructure.  This discards user modifications to variable
403    formats, thereby causing formats to revert to their
404    defaults.  */
405 static void
406 reset_formats_page (PsppireImportAssistant *ia, GtkWidget *page)
407 {
408 }
409
410 static void prepare_formats_page (PsppireImportAssistant *ia);
411
412 /* Called when the Reset button is clicked. */
413 static void
414 on_reset (GtkButton *button, PsppireImportAssistant *ia)
415 {
416   gint pn = gtk_assistant_get_current_page (GTK_ASSISTANT (ia));
417   {
418     GtkWidget *page =  gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), pn);
419
420     page_func *on_reset = g_object_get_data (G_OBJECT (page), "on-reset");
421
422     if (on_reset)
423       on_reset (ia, page);
424   }
425 }
426
427
428 static gint
429 next_page_func (gint old_page, gpointer data)
430 {
431   return old_page + 1;
432 }
433
434
435 /* Called just before PAGE is displayed as the current page of
436    IMPORT_ASSISTANT, this updates IA content according to the new
437    page. */
438 static void
439 on_prepare (GtkAssistant *assistant, GtkWidget *page, PsppireImportAssistant *ia)
440 {
441   gtk_widget_show (ia->reset_button);
442   gtk_widget_hide (ia->paste_button);
443
444   gint pn = gtk_assistant_get_current_page (assistant);
445   gint previous_page_index = ia->current_page;
446
447   if (previous_page_index >= 0)
448     {
449       GtkWidget *closing_page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), previous_page_index);
450
451       if (pn > previous_page_index)
452         {
453           page_func *on_forward = g_object_get_data (G_OBJECT (closing_page), "on-forward");
454
455           if (on_forward)
456             on_forward (ia, closing_page);
457         }
458       else
459         {
460           page_func *on_back = g_object_get_data (G_OBJECT (closing_page), "on-back");
461
462           if (on_back)
463             on_back (ia, closing_page);
464         }
465     }
466
467   {
468     GtkWidget *new_page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), pn);
469
470     page_func *on_entering = g_object_get_data (G_OBJECT (new_page), "on-entering");
471
472     if (on_entering)
473       on_entering (ia, new_page);
474   }
475
476   ia->current_page = pn;
477 }
478
479 /* Called when the Cancel button in the assistant is clicked. */
480 static void
481 on_cancel (GtkAssistant *assistant, PsppireImportAssistant *ia)
482 {
483   close_assistant (ia, GTK_RESPONSE_CANCEL);
484 }
485
486 /* Called when the Apply button on the last page of the assistant
487    is clicked. */
488 static void
489 on_close (GtkAssistant *assistant, PsppireImportAssistant *ia)
490 {
491   close_assistant (ia, GTK_RESPONSE_APPLY);
492 }
493
494
495 static GtkWidget *
496 add_page_to_assistant (PsppireImportAssistant *ia,
497                        GtkWidget *page, GtkAssistantPageType type, const gchar *);
498
499
500 static void
501 on_sheet_combo_changed (GtkComboBox *cb, PsppireImportAssistant *ia)
502 {
503   GtkTreeIter iter;
504   gchar *range = NULL;
505   GtkTreeModel *model = gtk_combo_box_get_model (cb);
506   GtkBuilder *builder = ia->builder;
507   GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
508
509   gtk_combo_box_get_active_iter (cb, &iter);
510   gtk_tree_model_get (model, &iter, PSPPIRE_SPREADSHEET_MODEL_COL_RANGE, &range, -1);
511   gtk_entry_set_text (GTK_ENTRY (range_entry), range ?  range : "");
512   g_free (range);
513 }
514
515 /* Prepares IA's sheet_spec page. */
516 static void
517 prepare_sheet_spec_page (PsppireImportAssistant *ia)
518 {
519   GtkBuilder *builder = ia->builder;
520   GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
521   GtkWidget *readnames_checkbox = get_widget_assert (builder, "readnames-checkbox");
522
523   gtk_combo_box_set_model (GTK_COMBO_BOX (sheet_entry),
524                            psppire_spreadsheet_model_new (ia->spreadsheet));
525
526   gtk_combo_box_set_active (GTK_COMBO_BOX (sheet_entry), 0);
527
528   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (readnames_checkbox), FALSE);
529 }
530
531
532 /* Initializes IA's sheet_spec substructure. */
533 static void
534 sheet_spec_page_create (PsppireImportAssistant *ia)
535 {
536   GtkBuilder *builder = ia->builder;
537   GtkWidget *page = get_widget_assert (builder, "Spreadsheet-Importer");
538
539   GtkWidget *combo_box = get_widget_assert (builder, "sheet-entry");
540   GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
541   gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo_box));
542   gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE);
543   gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), renderer,
544                                   "text", 0,
545                                   NULL);
546
547   g_signal_connect (combo_box, "changed", G_CALLBACK (on_sheet_combo_changed), ia);
548
549   add_page_to_assistant (ia, page,
550                          GTK_ASSISTANT_PAGE_CONTENT, _("Importing Spreadsheet Data"));
551
552   g_object_set_data (G_OBJECT (page), "on-entering", prepare_sheet_spec_page);
553 }
554
555 static void
556 on_chosen (PsppireImportAssistant *ia, GtkWidget *page)
557 {
558   GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
559   gchar *f = gtk_file_chooser_get_filename (fc);
560   int i;
561
562   for(i = gtk_assistant_get_n_pages (GTK_ASSISTANT (ia)); i > 0; --i)
563     gtk_assistant_remove_page (GTK_ASSISTANT (ia), i);
564
565   gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), GTK_WIDGET (fc), FALSE);
566
567   if (f && g_file_test (f, G_FILE_TEST_IS_REGULAR))
568     {
569       gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), GTK_WIDGET (fc), TRUE);
570
571       if (ia->spreadsheet)
572         spreadsheet_unref (ia->spreadsheet);
573
574       ia->spreadsheet = gnumeric_probe (f, FALSE);
575
576       if (!ia->spreadsheet)
577         ia->spreadsheet = ods_probe (f, FALSE);
578
579       if (ia->spreadsheet)
580         {
581           sheet_spec_page_create (ia);
582         }
583       else
584         {
585           intro_page_create (ia);
586           first_line_page_create (ia);
587           separators_page_create (ia);
588         }
589
590       formats_page_create (ia);
591     }
592
593   g_free (f);
594 }
595
596 /* This has to be done on a map signal callback,
597    because GtkFileChooserWidget resets everything when it is mapped. */
598 static void
599 on_map (PsppireImportAssistant *ia, GtkWidget *page)
600 {
601 #if TEXT_FILE
602   GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
603
604   if (ia->file_name)
605     gtk_file_chooser_set_filename (fc, ia->file_name);
606 #endif
607
608   on_chosen (ia, page);
609 }
610
611
612
613 static void
614 chooser_page_enter (PsppireImportAssistant *ia, GtkWidget *page)
615 {
616 }
617
618 static void
619 chooser_page_leave (PsppireImportAssistant *ia, GtkWidget *page)
620 {
621   g_free (ia->file_name);
622   ia->file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (page));
623   gchar *encoding = psppire_encoding_selector_get_encoding (ia->encoding_selector);
624
625   if (!ia->spreadsheet)
626     {
627       ia->text_file = psppire_text_file_new (ia->file_name, encoding);
628       gtk_tree_view_set_model (GTK_TREE_VIEW (ia->first_line_tree_view),
629                                GTK_TREE_MODEL (ia->text_file));
630     }
631
632
633   g_free (encoding);
634 }
635
636 static void
637 chooser_page_reset (PsppireImportAssistant *ia, GtkWidget *page)
638 {
639   GtkFileChooser *fc = GTK_FILE_CHOOSER (page);
640
641   gtk_file_chooser_set_filter (fc, ia->default_filter);
642   gtk_file_chooser_unselect_all (fc);
643
644   on_chosen (ia, page);
645 }
646
647
648
649 static void
650 chooser_page_create (PsppireImportAssistant *ia)
651 {
652   GtkFileFilter *filter = NULL;
653
654   GtkWidget *chooser = gtk_file_chooser_widget_new (GTK_FILE_CHOOSER_ACTION_OPEN);
655
656   g_object_set_data (G_OBJECT (chooser), "on-forward", chooser_page_leave);
657   g_object_set_data (G_OBJECT (chooser), "on-reset",   chooser_page_reset);
658   g_object_set_data (G_OBJECT (chooser), "on-entering",chooser_page_enter);
659
660   g_object_set (chooser, "local-only", FALSE, NULL);
661
662
663   ia->default_filter = gtk_file_filter_new ();
664   gtk_file_filter_set_name (ia->default_filter, _("All Files"));
665   gtk_file_filter_add_pattern (ia->default_filter, "*");
666   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), ia->default_filter);
667
668   filter = gtk_file_filter_new ();
669   gtk_file_filter_set_name (filter, _("Text Files"));
670   gtk_file_filter_add_mime_type (filter, "text/*");
671   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
672
673   filter = gtk_file_filter_new ();
674   gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
675   gtk_file_filter_add_pattern (filter, "*.txt");
676   gtk_file_filter_add_pattern (filter, "*.TXT");
677   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
678
679   filter = gtk_file_filter_new ();
680   gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
681   gtk_file_filter_add_mime_type (filter, "text/plain");
682   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
683
684   filter = gtk_file_filter_new ();
685   gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
686   gtk_file_filter_add_mime_type (filter, "text/csv");
687   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
688
689   /* I've never encountered one of these, but it's listed here:
690      http://www.iana.org/assignments/media-types/text/tab-separated-values  */
691   filter = gtk_file_filter_new ();
692   gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
693   gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
694   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
695
696   filter = gtk_file_filter_new ();
697   gtk_file_filter_set_name (filter, _("Gnumeric Spreadsheet Files"));
698   gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
699   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
700
701   filter = gtk_file_filter_new ();
702   gtk_file_filter_set_name (filter, _("OpenDocument Spreadsheet Files"));
703   gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
704   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
705
706   filter = gtk_file_filter_new ();
707   gtk_file_filter_set_name (filter, _("All Spreadsheet Files"));
708   gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
709   gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
710   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
711
712   ia->encoding_selector = psppire_encoding_selector_new ("Auto", TRUE);
713   gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (chooser), ia->encoding_selector);
714
715   add_page_to_assistant (ia, chooser,
716                          GTK_ASSISTANT_PAGE_INTRO, _("Select File to Import"));
717
718   g_signal_connect_swapped (chooser, "selection-changed", G_CALLBACK (on_chosen), ia);
719   g_signal_connect_swapped (chooser, "map", G_CALLBACK (on_map), ia);
720 }
721
722
723
724 static void
725 psppire_import_assistant_init (PsppireImportAssistant *ia)
726 {
727   ia->builder = builder_new ("text-data-import.ui");
728
729   ia->current_page = -1 ;
730   ia->file_name = NULL;
731
732   ia->spreadsheet = NULL;
733   ia->dict = NULL;
734   ia->casereader_dict = NULL;
735
736   ia->main_loop = g_main_loop_new (NULL, TRUE);
737
738   g_signal_connect (ia, "prepare", G_CALLBACK (on_prepare), ia);
739   g_signal_connect (ia, "cancel", G_CALLBACK (on_cancel), ia);
740   g_signal_connect (ia, "close", G_CALLBACK (on_close), ia);
741
742   ia->paste_button = gtk_button_new_with_label (_("Paste"));
743   ia->reset_button = gtk_button_new_with_label (_("Reset"));
744
745   gtk_assistant_add_action_widget (GTK_ASSISTANT(ia), ia->paste_button);
746
747   g_signal_connect (ia->paste_button, "clicked", G_CALLBACK (on_paste), ia);
748   g_signal_connect (ia->reset_button, "clicked", G_CALLBACK (on_reset), ia);
749
750   gtk_assistant_add_action_widget (GTK_ASSISTANT(ia), ia->reset_button);
751
752   gtk_window_set_title (GTK_WINDOW (ia),
753                         _("Importing Delimited Text Data"));
754
755   gtk_window_set_icon_name (GTK_WINDOW (ia), "pspp");
756
757   chooser_page_create (ia);
758
759   gtk_assistant_set_forward_page_func (GTK_ASSISTANT (ia), next_page_func, NULL, NULL);
760
761   gtk_window_maximize (GTK_WINDOW (ia));
762 }
763
764
765 /* Appends a page of the given TYPE, with PAGE as its content, to
766    the GtkAssistant encapsulated by IA.  Returns the GtkWidget
767    that represents the page. */
768 static GtkWidget *
769 add_page_to_assistant (PsppireImportAssistant *ia,
770                        GtkWidget *page, GtkAssistantPageType type, const gchar *title)
771 {
772   GtkWidget *content = page;
773
774   gtk_assistant_append_page (GTK_ASSISTANT (ia), content);
775   gtk_assistant_set_page_type (GTK_ASSISTANT(ia), content, type);
776   gtk_assistant_set_page_title (GTK_ASSISTANT(ia), content, title);
777   gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), content, TRUE);
778
779   return content;
780 }
781
782
783 /* Called when one of the radio buttons is clicked. */
784 static void
785 on_intro_amount_changed (PsppireImportAssistant *p)
786 {
787   gtk_widget_set_sensitive (p->n_cases_spin,
788                             gtk_toggle_button_get_active
789                             (GTK_TOGGLE_BUTTON (p->n_cases_button)));
790
791   gtk_widget_set_sensitive (p->percent_spin,
792                             gtk_toggle_button_get_active
793                             (GTK_TOGGLE_BUTTON (p->percent_button)));
794 }
795
796 static void
797 on_treeview_selection_change (PsppireImportAssistant *ia)
798 {
799   GtkTreeSelection *selection =
800     gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view));
801   GtkTreeModel *model = NULL;
802   GtkTreeIter iter;
803   if (gtk_tree_selection_get_selected (selection, &model, &iter))
804     {
805       gint max_lines;
806       int n;
807       GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
808       gint *index = gtk_tree_path_get_indices (path);
809       n = *index;
810       gtk_tree_path_free (path);
811       g_object_get (model, "maximum-lines", &max_lines, NULL);
812       gtk_widget_set_sensitive (ia->variable_names_cb,
813                                 (n > 0 && n < max_lines));
814       ia->delimiters_model =
815         psppire_delimited_text_new (GTK_TREE_MODEL (ia->text_file));
816       g_object_set (ia->delimiters_model, "first-line", n, NULL);
817     }
818 }
819
820 static void
821 render_text_preview_line (GtkTreeViewColumn *tree_column,
822                 GtkCellRenderer *cell,
823                 GtkTreeModel *tree_model,
824                 GtkTreeIter *iter,
825                 gpointer data)
826 {
827   /*
828      Set the text  to a "insensitive" state if the row
829      is greater than what the user declared to be the maximum.
830   */
831   GtkTreePath *path = gtk_tree_model_get_path (tree_model, iter);
832   gint *ii = gtk_tree_path_get_indices (path);
833   gint max_lines;
834   g_object_get (tree_model, "maximum-lines", &max_lines, NULL);
835   g_object_set (cell, "sensitive", (*ii < max_lines), NULL);
836   gtk_tree_path_free (path);
837 }
838
839 /* Initializes IA's first_line substructure. */
840 static void
841 first_line_page_create (PsppireImportAssistant *ia)
842 {
843   GtkWidget *w =  get_widget_assert (ia->builder, "FirstLine");
844
845   g_object_set_data (G_OBJECT (w), "on-entering", on_treeview_selection_change);
846
847   add_page_to_assistant (ia, w,
848                          GTK_ASSISTANT_PAGE_CONTENT, _("Select the First Line"));
849
850   GtkWidget *scrolled_window = get_widget_assert (ia->builder, "first-line-scroller");
851
852   if (ia->first_line_tree_view == NULL)
853     {
854       ia->first_line_tree_view = gtk_tree_view_new ();
855       g_object_set (ia->first_line_tree_view, "enable-search", FALSE, NULL);
856
857       gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ia->first_line_tree_view), TRUE);
858
859       GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
860       GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Line"), renderer,
861                                                                             "text", 0,
862                                                                             NULL);
863
864       gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
865       gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
866
867       renderer = gtk_cell_renderer_text_new ();
868       column = gtk_tree_view_column_new_with_attributes (_("Text"), renderer, "text", 1, NULL);
869       gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
870
871       gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
872
873       g_signal_connect_swapped (ia->first_line_tree_view, "cursor-changed",
874                                 G_CALLBACK (on_treeview_selection_change), ia);
875       gtk_container_add (GTK_CONTAINER (scrolled_window), ia->first_line_tree_view);
876     }
877
878   gtk_widget_show_all (scrolled_window);
879
880   ia->variable_names_cb = get_widget_assert (ia->builder, "variable-names");
881 }
882
883 static void
884 intro_on_leave (PsppireImportAssistant *ia)
885 {
886   gint lc = 0;
887   g_object_get (ia->text_file, "line-count", &lc, NULL);
888   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
889     {
890       gint max_lines = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin));
891       g_object_set (ia->text_file, "maximum-lines", max_lines, NULL);
892     }
893   else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
894     {
895       gdouble percent = gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin));
896       g_object_set (ia->text_file, "maximum-lines", (gint) (lc * percent / 100.0), NULL);
897     }
898   else
899     {
900       g_object_set (ia->text_file, "maximum-lines", lc, NULL);
901     }
902 }
903
904
905 static void
906 intro_on_enter (PsppireImportAssistant *ia)
907 {
908   GtkBuilder *builder = ia->builder;
909   GtkWidget *table  = get_widget_assert (builder, "button-table");
910
911   struct string s;
912
913   ds_init_empty (&s);
914   ds_put_cstr (&s, _("This assistant will guide you through the process of "
915                      "importing data into PSPP from a text file with one line "
916                      "per case,  in which fields are separated by tabs, "
917                      "commas, or other delimiters.\n\n"));
918
919   if (ia->text_file)
920     {
921       if (ia->text_file->total_is_exact)
922         {
923           ds_put_format (
924                          &s, ngettext ("The selected file contains %'lu line of text.  ",
925                                        "The selected file contains %'lu lines of text.  ",
926                                        ia->text_file->total_lines),
927                          ia->text_file->total_lines);
928         }
929       else if (ia->text_file->total_lines > 0)
930         {
931           ds_put_format (
932                          &s, ngettext (
933                                        "The selected file contains approximately %'lu line of text.  ",
934                                        "The selected file contains approximately %'lu lines of text.  ",
935                                        ia->text_file->total_lines),
936                          ia->text_file->total_lines);
937           ds_put_format (
938                          &s, ngettext (
939                                        "Only the first %zu line of the file will be shown for "
940                                        "preview purposes in the following screens.  ",
941                                        "Only the first %zu lines of the file will be shown for "
942                                        "preview purposes in the following screens.  ",
943                                        ia->text_file->line_cnt),
944                          ia->text_file->line_cnt);
945         }
946     }
947
948   ds_put_cstr (&s, _("You may choose below how much of the file should "
949                      "actually be imported."));
950
951   gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
952                       ds_cstr (&s));
953   ds_destroy (&s);
954
955   if (gtk_grid_get_child_at (GTK_GRID (table), 1, 1) == NULL)
956     {
957       GtkWidget *hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &ia->n_cases_spin);
958       gtk_grid_attach (GTK_GRID (table), hbox_n_cases,
959                        1, 1,
960                        1, 1);
961     }
962
963   GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (ia->n_cases_spin));
964   gtk_adjustment_set_lower (adj, 1.0);
965
966   if (gtk_grid_get_child_at (GTK_GRID (table), 1, 2) == NULL)
967     {
968       GtkWidget *hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"),
969                                                    &ia->percent_spin);
970
971       gtk_grid_attach (GTK_GRID (table), hbox_percent,
972                        1, 2,
973                        1, 1);
974     }
975
976   gtk_widget_show_all (table);
977
978   on_intro_amount_changed (ia);
979 }
980
981 /* Initializes IA's intro substructure. */
982 static void
983 intro_page_create (PsppireImportAssistant *ia)
984 {
985   GtkBuilder *builder = ia->builder;
986
987   GtkWidget *w =  get_widget_assert (builder, "Intro");
988
989   ia->percent_spin = gtk_spin_button_new_with_range (0, 100, 10);
990
991
992   add_page_to_assistant (ia, w,  GTK_ASSISTANT_PAGE_CONTENT, _("Select the Lines to Import"));
993
994   ia->all_cases_button = get_widget_assert (builder, "import-all-cases");
995
996   ia->n_cases_button = get_widget_assert (builder, "import-n-cases");
997
998   ia->percent_button = get_widget_assert (builder, "import-percent");
999
1000   g_signal_connect_swapped (ia->all_cases_button, "toggled",
1001                             G_CALLBACK (on_intro_amount_changed), ia);
1002   g_signal_connect_swapped (ia->n_cases_button, "toggled",
1003                             G_CALLBACK (on_intro_amount_changed), ia);
1004   g_signal_connect_swapped (ia->percent_button, "toggled",
1005                             G_CALLBACK (on_intro_amount_changed), ia);
1006
1007
1008   g_object_set_data (G_OBJECT (w), "on-forward", intro_on_leave);
1009   g_object_set_data (G_OBJECT (w), "on-entering", intro_on_enter);
1010   g_object_set_data (G_OBJECT (w), "on-reset", reset_intro_page);
1011 }
1012
1013
1014 GtkWidget *
1015 psppire_import_assistant_new (GtkWindow *toplevel)
1016 {
1017   return GTK_WIDGET (g_object_new (PSPPIRE_TYPE_IMPORT_ASSISTANT,
1018                                    /* Some window managers (notably ratpoison)
1019                                       ignore the maximise command when a window is
1020                                       transient.  This causes problems for this
1021                                       window. */
1022                                    /* "transient-for", toplevel, */
1023                                    NULL));
1024 }
1025
1026
1027
1028 \f
1029
1030 /* Chooses a name for each column on the separators page */
1031 static void
1032 choose_column_names (PsppireImportAssistant *ia)
1033 {
1034   int i;
1035   unsigned long int generated_name_count = 0;
1036   dict_clear (ia->dict);
1037
1038   for (i = 0;
1039        i < gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model)) - 1;
1040        ++i)
1041     {
1042       const gchar *candidate_name = NULL;
1043
1044       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
1045         {
1046           candidate_name = psppire_delimited_text_get_header_title (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), i);
1047         }
1048
1049       char *name = dict_make_unique_var_name (ia->dict,
1050                                               candidate_name,
1051                                               &generated_name_count);
1052
1053       dict_create_var_assert (ia->dict, name, 0);
1054       free (name);
1055     }
1056 }
1057
1058 /* Called when the user toggles one of the separators
1059    checkboxes. */
1060 static void
1061 on_separator_toggle (GtkToggleButton *toggle UNUSED,
1062                      PsppireImportAssistant *ia)
1063 {
1064   int i;
1065   GSList *delimiters = NULL;
1066   for (i = 0; i < SEPARATOR_CNT; i++)
1067     {
1068       const struct separator *s = &separators[i];
1069       GtkWidget *button = get_widget_assert (ia->builder, s->name);
1070       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1071         {
1072           delimiters = g_slist_prepend (delimiters,  GINT_TO_POINTER (s->c));
1073         }
1074     }
1075
1076   g_object_set (ia->delimiters_model, "delimiters", delimiters, NULL);
1077
1078   revise_fields_preview (ia);
1079 }
1080
1081
1082 /* Called when the user changes the entry field for custom
1083    separators. */
1084 static void
1085 on_separators_custom_entry_notify (GObject *gobject UNUSED,
1086                                    GParamSpec *arg1 UNUSED,
1087                                    PsppireImportAssistant *ia)
1088 {
1089   revise_fields_preview (ia);
1090 }
1091
1092 /* Called when the user toggles the checkbox that enables custom
1093    separators. */
1094 static void
1095 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1096                                 PsppireImportAssistant *ia)
1097 {
1098   bool is_active = gtk_toggle_button_get_active (custom_cb);
1099   gtk_widget_set_sensitive (ia->custom_entry, is_active);
1100   revise_fields_preview (ia);
1101 }
1102
1103 /* Called when the user changes the selection in the combo box
1104    that selects a quote character. */
1105 static void
1106 on_quote_combo_change (GtkComboBox *combo, PsppireImportAssistant *ia)
1107 {
1108   //  revise_fields_preview (ia);
1109 }
1110
1111 /* Called when the user toggles the checkbox that enables
1112    quoting. */
1113 static void
1114 on_quote_cb_toggle (GtkToggleButton *quote_cb, PsppireImportAssistant *ia)
1115 {
1116   bool is_active = gtk_toggle_button_get_active (quote_cb);
1117   gtk_widget_set_sensitive (ia->quote_combo, is_active);
1118   revise_fields_preview (ia);
1119 }
1120
1121 /* Initializes IA's separators substructure. */
1122 static void
1123 separators_page_create (PsppireImportAssistant *ia)
1124 {
1125   GtkBuilder *builder = ia->builder;
1126
1127   size_t i;
1128
1129   GtkWidget *w = get_widget_assert (builder, "Separators");
1130
1131   g_object_set_data (G_OBJECT (w), "on-entering", prepare_separators_page);
1132   g_object_set_data (G_OBJECT (w), "on-reset", prepare_separators_page);
1133
1134   add_page_to_assistant (ia, w,   GTK_ASSISTANT_PAGE_CONTENT, _("Choose Separators"));
1135
1136   ia->custom_cb = get_widget_assert (builder, "custom-cb");
1137   ia->custom_entry = get_widget_assert (builder, "custom-entry");
1138   ia->quote_combo = get_widget_assert (builder, "quote-combo");
1139   ia->quote_cb = get_widget_assert (builder, "quote-cb");
1140
1141   gtk_combo_box_set_active (GTK_COMBO_BOX (ia->quote_combo), 0);
1142
1143   if (ia->fields_tree_view == NULL)
1144     {
1145       GtkWidget *scroller = get_widget_assert (ia->builder, "fields-scroller");
1146       ia->fields_tree_view = gtk_tree_view_new ();
1147       g_object_set (ia->fields_tree_view, "enable-search", FALSE, NULL);
1148       gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ia->fields_tree_view));
1149       gtk_widget_show_all (scroller);
1150     }
1151
1152   g_signal_connect (ia->quote_combo, "changed",
1153                     G_CALLBACK (on_quote_combo_change), ia);
1154   g_signal_connect (ia->quote_cb, "toggled",
1155                     G_CALLBACK (on_quote_cb_toggle), ia);
1156   g_signal_connect (ia->custom_entry, "notify::text",
1157                     G_CALLBACK (on_separators_custom_entry_notify), ia);
1158   g_signal_connect (ia->custom_cb, "toggled",
1159                     G_CALLBACK (on_separators_custom_cb_toggle), ia);
1160   for (i = 0; i < SEPARATOR_CNT; i++)
1161     g_signal_connect (get_widget_assert (builder, separators[i].name),
1162                       "toggled", G_CALLBACK (on_separator_toggle), ia);
1163
1164 }
1165
1166
1167
1168 \f
1169
1170
1171 static struct casereader_random_class my_casereader_class;
1172
1173 static struct ccase *
1174 my_read (struct casereader *reader, void *aux, casenumber idx)
1175 {
1176   PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (aux);
1177   GtkTreeModel *tm = GTK_TREE_MODEL (ia->delimiters_model);
1178
1179   GtkTreePath *tp = gtk_tree_path_new_from_indices (idx, -1);
1180
1181   const struct caseproto *proto = casereader_get_proto (reader);
1182
1183   GtkTreeIter iter;
1184   struct ccase *c = NULL;
1185   if (gtk_tree_model_get_iter (tm, &iter, tp))
1186     {
1187       c = case_create (proto);
1188       int i;
1189       for (i = 0 ; i < caseproto_get_n_widths (proto); ++i)
1190         {
1191           GValue value = {0};
1192           gtk_tree_model_get_value (tm, &iter, i + 1, &value);
1193
1194           const struct variable *var = dict_get_var (ia->casereader_dict, i);
1195
1196           const gchar *ss = g_value_get_string (&value);
1197           if (ss)
1198             {
1199               union value *v = case_data_rw (c, var);
1200               /* In this reader we derive the union value from the
1201                  string in the tree_model. We retrieve the width and format
1202                  from a dictionary which is stored directly after
1203                  the reader creation. Changes in ia->dict in the
1204                  variable window are not reflected here and therefore
1205                  this is always compatible with the width in the
1206                  caseproto. See bug #58298 */
1207               char *xx = data_in (ss_cstr (ss),
1208                                   "UTF-8",
1209                                   var_get_write_format (var)->type,
1210                                   v, var_get_width (var), "UTF-8");
1211
1212               /* if (xx) */
1213               /*   g_print ("%s:%d Err %s\n", __FILE__, __LINE__, xx); */
1214               free (xx);
1215             }
1216           g_value_unset (&value);
1217         }
1218     }
1219
1220   gtk_tree_path_free (tp);
1221
1222   return c;
1223 }
1224
1225 static void
1226 my_destroy (struct casereader *reader, void *aux)
1227 {
1228   g_print ("%s:%d %p\n", __FILE__, __LINE__, reader);
1229 }
1230
1231 static void
1232 my_advance (struct casereader *reader, void *aux, casenumber cnt)
1233 {
1234   g_print ("%s:%d\n", __FILE__, __LINE__);
1235 }
1236
1237 static struct casereader *
1238 textfile_create_reader (PsppireImportAssistant *ia)
1239 {
1240   int n_vars = dict_get_var_cnt (ia->dict);
1241
1242   int i;
1243
1244   struct fmt_guesser **fg = xcalloc (sizeof *fg, n_vars);
1245   for (i = 0 ; i < n_vars; ++i)
1246     {
1247       fg[i] = fmt_guesser_create ();
1248     }
1249
1250   gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
1251
1252   GtkTreeIter iter;
1253   gboolean ok;
1254   for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->delimiters_model), &iter);
1255        ok;
1256        ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->delimiters_model), &iter))
1257     {
1258       for (i = 0 ; i < n_vars; ++i)
1259         {
1260           gchar *s = NULL;
1261           gtk_tree_model_get (GTK_TREE_MODEL (ia->delimiters_model), &iter, i+1, &s, -1);
1262           if (s)
1263             fmt_guesser_add (fg[i], ss_cstr (s));
1264           free (s);
1265         }
1266     }
1267
1268   struct caseproto *proto = caseproto_create ();
1269   for (i = 0 ; i < n_vars; ++i)
1270     {
1271       struct fmt_spec fs;
1272       fmt_guesser_guess (fg[i], &fs);
1273
1274       fmt_fix (&fs, FMT_FOR_INPUT);
1275
1276       struct variable *var = dict_get_var (ia->dict, i);
1277
1278       int width = fmt_var_width (&fs);
1279
1280       var_set_width_and_formats (var, width,
1281                                  &fs, &fs);
1282
1283       proto = caseproto_add_width (proto, width);
1284       fmt_guesser_destroy (fg[i]);
1285     }
1286
1287   free (fg);
1288
1289   struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class,  ia);
1290   /* Store the dictionary at this point when the casereader is created.
1291      my_read depends on the dictionary to interpret the strings in the treeview.
1292      This guarantees that the union value is produced according to the
1293      caseproto in the reader. */
1294   ia->casereader_dict = dict_clone (ia->dict);
1295   caseproto_unref (proto);
1296   return cr;
1297 }
1298
1299 /* When during import the variable type is changed, the reader is reinitialized
1300    based on the new dictionary with a fresh caseprototype. The default behaviour
1301    when a variable type is changed and the column is resized is that the union
1302    value is interpreted with new variable type and an overlay for that column
1303    is generated. Here we reinit to the original reader based on strings.
1304    As a result you can switch from string to numeric to string without loosing
1305    the string information. */
1306 static void
1307 ia_variable_changed_cb (GObject *obj, gint var_num, guint what,
1308                         const struct variable *oldvar, gpointer data)
1309 {
1310   PsppireImportAssistant *ia  = PSPPIRE_IMPORT_ASSISTANT (data);
1311
1312   struct caseproto *proto = caseproto_create();
1313   for (int i = 0; i < dict_get_var_cnt (ia->dict); i++)
1314     {
1315       const struct variable *var = dict_get_var (ia->dict, i);
1316       int width = var_get_width (var);
1317       proto = caseproto_add_width (proto, width);
1318     }
1319
1320   gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
1321
1322   PsppireDataStore *store = NULL;
1323   g_object_get (ia->data_sheet, "data-model", &store, NULL);
1324
1325   struct casereader *cr = casereader_create_random (proto, n_rows,
1326                                                     &my_casereader_class, ia);
1327   psppire_data_store_set_reader (store, cr);
1328   dict_unref (ia->casereader_dict);
1329   ia->casereader_dict = dict_clone (ia->dict);
1330 }
1331
1332 /* Called just before the formats page of the assistant is
1333    displayed. */
1334 static void
1335 prepare_formats_page (PsppireImportAssistant *ia)
1336 {
1337   my_casereader_class.read = my_read;
1338   my_casereader_class.destroy = my_destroy;
1339   my_casereader_class.advance = my_advance;
1340
1341   if (ia->spreadsheet)
1342     {
1343       GtkBuilder *builder = ia->builder;
1344       GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
1345       GtkWidget *rnc = get_widget_assert (builder, "readnames-checkbox");
1346       GtkWidget *combo_box = get_widget_assert (builder, "sheet-entry");
1347
1348       struct spreadsheet_read_options opts;
1349       opts.sheet_name = NULL;
1350       opts.sheet_index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box)) + 1;
1351       opts.read_names = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rnc));
1352       opts.cell_range = g_strdup (gtk_entry_get_text (GTK_ENTRY (range_entry)));
1353       opts.asw = 8;
1354
1355       struct casereader *reader = spreadsheet_make_reader (ia->spreadsheet, &opts);
1356
1357       PsppireDict *dict = psppire_dict_new_from_dict (ia->spreadsheet->dict);
1358       PsppireDataStore *store = psppire_data_store_new (dict);
1359       psppire_data_store_set_reader (store, reader);
1360       g_object_set (ia->data_sheet, "data-model", store, NULL);
1361       g_object_set (ia->var_sheet, "data-model", dict, NULL);
1362     }
1363   else
1364     {
1365       struct casereader *reader = textfile_create_reader (ia);
1366
1367       PsppireDict *dict = psppire_dict_new_from_dict (ia->dict);
1368       PsppireDataStore *store = psppire_data_store_new (dict);
1369       psppire_data_store_set_reader (store, reader);
1370       g_signal_connect (dict, "variable-changed",
1371                         G_CALLBACK (ia_variable_changed_cb),
1372                         ia);
1373
1374       g_object_set (ia->data_sheet, "data-model", store, NULL);
1375       g_object_set (ia->var_sheet, "data-model", dict, NULL);
1376     }
1377
1378   gint pmax;
1379   g_object_get (get_widget_assert (ia->builder, "vpaned1"),
1380                 "max-position", &pmax, NULL);
1381
1382
1383   g_object_set (get_widget_assert (ia->builder, "vpaned1"),
1384                 "position", pmax / 2, NULL);
1385
1386   gtk_widget_show (ia->paste_button);
1387 }
1388
1389 static void
1390 formats_page_create (PsppireImportAssistant *ia)
1391 {
1392   GtkBuilder *builder = ia->builder;
1393
1394   GtkWidget *w = get_widget_assert (builder, "Formats");
1395   g_object_set_data (G_OBJECT (w), "on-entering", prepare_formats_page);
1396   g_object_set_data (G_OBJECT (w), "on-reset", reset_formats_page);
1397
1398   GtkWidget *vars_scroller = get_widget_assert (builder, "vars-scroller");
1399   if (ia->var_sheet == NULL)
1400     {
1401       ia->var_sheet = psppire_variable_sheet_new ();
1402
1403       gtk_container_add (GTK_CONTAINER (vars_scroller), ia->var_sheet);
1404
1405       ia->dict = dict_create (get_default_encoding ());
1406
1407       gtk_widget_show_all (vars_scroller);
1408     }
1409   GtkWidget *data_scroller = get_widget_assert (builder, "data-scroller");
1410   if (ia->data_sheet == NULL)
1411     {
1412       ia->data_sheet = psppire_data_sheet_new ();
1413       g_object_set (ia->data_sheet, "editable", FALSE, NULL);
1414
1415       gtk_container_add (GTK_CONTAINER (data_scroller), ia->data_sheet);
1416
1417       gtk_widget_show_all (data_scroller);
1418     }
1419
1420   add_page_to_assistant (ia, w,
1421                          GTK_ASSISTANT_PAGE_CONFIRM, _("Adjust Variable Formats"));
1422 }
1423
1424
1425 \f
1426
1427 static void
1428 separators_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1429 {
1430   int i;
1431
1432   ds_put_cstr (s, "  /DELIMITERS=\"");
1433
1434   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->builder, "tab"))))
1435     ds_put_cstr (s, "\\t");
1436   for (i = 0; i < SEPARATOR_CNT; i++)
1437     {
1438       const struct separator *seps = &separators[i];
1439       GtkWidget *button = get_widget_assert (ia->builder, seps->name);
1440       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1441         {
1442           if (seps->c == '\t')
1443             continue;
1444
1445           ds_put_byte (s, seps->c);
1446         }
1447     }
1448   ds_put_cstr (s, "\"\n");
1449
1450   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->quote_cb)))
1451     {
1452       GtkComboBoxText *cbt = GTK_COMBO_BOX_TEXT (ia->quote_combo);
1453       gchar *quotes = gtk_combo_box_text_get_active_text (cbt);
1454       if (quotes && *quotes)
1455         syntax_gen_pspp (s, "  /QUALIFIER=%sq\n", quotes);
1456       free (quotes);
1457     }
1458 }
1459
1460 static void
1461 formats_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1462 {
1463   int i;
1464   int var_cnt;
1465
1466   g_return_if_fail (ia->dict);
1467
1468   ds_put_cstr (s, "  /VARIABLES=\n");
1469
1470   var_cnt = dict_get_var_cnt (ia->dict);
1471   for (i = 0; i < var_cnt; i++)
1472     {
1473       struct variable *var = dict_get_var (ia->dict, i);
1474       char format_string[FMT_STRING_LEN_MAX + 1];
1475       fmt_to_string (var_get_print_format (var), format_string);
1476       ds_put_format (s, "    %s %s%s\n",
1477                      var_get_name (var), format_string,
1478                      i == var_cnt - 1 ? "." : "");
1479     }
1480 }
1481
1482 static void
1483 first_line_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1484 {
1485   gint first_case = 0;
1486   g_object_get (ia->delimiters_model, "first-line", &first_case, NULL);
1487
1488   if (first_case > 0)
1489     ds_put_format (s, "  /FIRSTCASE=%d\n", first_case + 1);
1490 }
1491
1492 static void
1493 intro_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1494 {
1495   gint first_line = 0;
1496   g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
1497
1498   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
1499     ds_put_format (s, "SELECT IF ($CASENUM <= %d).\n",
1500                    gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin)) - first_line);
1501   else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
1502     ds_put_format (s, "SAMPLE %.4g.\n",
1503                    gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin)) / 100.0);
1504 }
1505
1506
1507 /* Emits PSPP syntax to S that applies the dictionary attributes
1508    (such as missing values and value labels) of the variables in
1509    DICT.  */
1510 static void
1511 apply_dict (const struct dictionary *dict, struct string *s)
1512 {
1513   size_t var_cnt = dict_get_var_cnt (dict);
1514   size_t i;
1515
1516   for (i = 0; i < var_cnt; i++)
1517     {
1518       struct variable *var = dict_get_var (dict, i);
1519       const char *name = var_get_name (var);
1520       enum val_type type = var_get_type (var);
1521       int width = var_get_width (var);
1522       enum measure measure = var_get_measure (var);
1523       enum var_role role = var_get_role (var);
1524       enum alignment alignment = var_get_alignment (var);
1525       const struct fmt_spec *format = var_get_print_format (var);
1526
1527       if (var_has_missing_values (var))
1528         {
1529           const struct missing_values *mv = var_get_missing_values (var);
1530           size_t j;
1531
1532           syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
1533           for (j = 0; j < mv_n_values (mv); j++)
1534             {
1535               if (j)
1536                 ds_put_cstr (s, ", ");
1537               syntax_gen_value (s, mv_get_value (mv, j), width, format);
1538             }
1539
1540           if (mv_has_range (mv))
1541             {
1542               double low, high;
1543               if (mv_has_value (mv))
1544                 ds_put_cstr (s, ", ");
1545               mv_get_range (mv, &low, &high);
1546               syntax_gen_num_range (s, low, high, format);
1547             }
1548           ds_put_cstr (s, ").\n");
1549         }
1550       if (var_has_value_labels (var))
1551         {
1552           const struct val_labs *vls = var_get_value_labels (var);
1553           const struct val_lab **labels = val_labs_sorted (vls);
1554           size_t n_labels = val_labs_count (vls);
1555           size_t i;
1556
1557           syntax_gen_pspp (s, "VALUE LABELS %ss", name);
1558           for (i = 0; i < n_labels; i++)
1559             {
1560               const struct val_lab *vl = labels[i];
1561               ds_put_cstr (s, "\n  ");
1562               syntax_gen_value (s, &vl->value, width, format);
1563               ds_put_byte (s, ' ');
1564               syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl)));
1565             }
1566           free (labels);
1567           ds_put_cstr (s, ".\n");
1568         }
1569       if (var_has_label (var))
1570         syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
1571                          name, var_get_label (var));
1572       if (measure != var_default_measure (type))
1573         syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
1574                          name, measure_to_syntax (measure));
1575       if (role != ROLE_INPUT)
1576         syntax_gen_pspp (s, "VARIABLE ROLE /%ss %ss.\n",
1577                          var_role_to_syntax (role), name);
1578       if (alignment != var_default_alignment (type))
1579         syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
1580                          name, alignment_to_syntax (alignment));
1581       if (var_get_display_width (var) != var_default_display_width (width))
1582         syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
1583                          name, var_get_display_width (var));
1584     }
1585 }
1586
1587
1588
1589 static void
1590 sheet_spec_gen_syntax (PsppireImportAssistant *ia, struct string *s)
1591 {
1592   GtkBuilder *builder = ia->builder;
1593   GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
1594   GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
1595   GtkWidget *rnc = get_widget_assert (builder, "readnames-checkbox");
1596   const gchar *range = gtk_entry_get_text (GTK_ENTRY (range_entry));
1597   int sheet_index = 1 + gtk_combo_box_get_active (GTK_COMBO_BOX (sheet_entry));
1598   gboolean read_names = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rnc));
1599
1600
1601   char *filename;
1602   if (ia->spreadsheet)
1603     filename = ia->spreadsheet->file_name;
1604   else
1605     g_object_get (ia->text_file, "file-name", &filename, NULL);
1606   syntax_gen_pspp (s,
1607                    "GET DATA"
1608                    "\n  /TYPE=%ss"
1609                    "\n  /FILE=%sq"
1610                    "\n  /SHEET=index %d"
1611                    "\n  /READNAMES=%ss",
1612                    (ia->spreadsheet->type == SPREADSHEET_GNUMERIC) ? "GNM" : "ODS",
1613                    filename,
1614                    sheet_index,
1615                    read_names ? "ON" : "OFF");
1616
1617   if (range && 0 != strcmp ("", range))
1618     {
1619       syntax_gen_pspp (s,
1620                        "\n  /CELLRANGE=RANGE %sq", range);
1621     }
1622   else
1623     {
1624       syntax_gen_pspp (s,
1625                        "\n  /CELLRANGE=FULL");
1626     }
1627
1628
1629   syntax_gen_pspp (s, ".\n");
1630 }
1631
1632
1633 gchar *
1634 psppire_import_assistant_generate_syntax (PsppireImportAssistant *ia)
1635 {
1636   struct string s = DS_EMPTY_INITIALIZER;
1637
1638   if (!ia->spreadsheet)
1639     {
1640       gchar *file_name = NULL;
1641       gchar *encoding = NULL;
1642       g_object_get (ia->text_file,
1643                     "file-name", &file_name,
1644                     "encoding", &encoding,
1645                     NULL);
1646
1647       if (file_name == NULL)
1648         return NULL;
1649
1650       syntax_gen_pspp (&s,
1651                        "GET DATA"
1652                        "\n  /TYPE=TXT"
1653                        "\n  /FILE=%sq\n",
1654                        file_name);
1655       if (encoding && strcmp (encoding, "Auto"))
1656         syntax_gen_pspp (&s, "  /ENCODING=%sq\n", encoding);
1657
1658       ds_put_cstr (&s,
1659                    "  /ARRANGEMENT=DELIMITED\n"
1660                    "  /DELCASE=LINE\n");
1661
1662       first_line_append_syntax (ia, &s);
1663       separators_append_syntax (ia, &s);
1664
1665       formats_append_syntax (ia, &s);
1666       apply_dict (ia->dict, &s);
1667       intro_append_syntax (ia, &s);
1668     }
1669   else
1670     {
1671       sheet_spec_gen_syntax (ia, &s);
1672     }
1673
1674   return ds_cstr (&s);
1675 }
1676
1677
1678 int
1679 psppire_import_assistant_run (PsppireImportAssistant *asst)
1680 {
1681   g_main_loop_run (asst->main_loop);
1682   return asst->response;
1683 }