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