84de028c25a53351bbd24abaae745647b6faab68
[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
659 static void
660 chooser_page_create (PsppireImportAssistant *ia)
661 {
662   GtkFileFilter *filter = NULL;
663
664   GtkWidget *chooser = gtk_file_chooser_widget_new (GTK_FILE_CHOOSER_ACTION_OPEN);
665
666   g_object_set_data (G_OBJECT (chooser), "on-forward", chooser_page_leave);
667   g_object_set_data (G_OBJECT (chooser), "on-reset",   chooser_page_reset);
668   g_object_set_data (G_OBJECT (chooser), "on-entering",chooser_page_enter);
669
670   g_object_set (chooser, "local-only", FALSE, NULL);
671
672
673   ia->default_filter = gtk_file_filter_new ();
674   gtk_file_filter_set_name (ia->default_filter, _("All Files"));
675   gtk_file_filter_add_pattern (ia->default_filter, "*");
676   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), ia->default_filter);
677
678   filter = gtk_file_filter_new ();
679   gtk_file_filter_set_name (filter, _("Text Files"));
680   gtk_file_filter_add_mime_type (filter, "text/*");
681   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
682
683   filter = gtk_file_filter_new ();
684   gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
685   gtk_file_filter_add_pattern (filter, "*.txt");
686   gtk_file_filter_add_pattern (filter, "*.TXT");
687   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
688
689   filter = gtk_file_filter_new ();
690   gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
691   gtk_file_filter_add_mime_type (filter, "text/plain");
692   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
693
694   filter = gtk_file_filter_new ();
695   gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
696   gtk_file_filter_add_mime_type (filter, "text/csv");
697   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
698
699   /* I've never encountered one of these, but it's listed here:
700      http://www.iana.org/assignments/media-types/text/tab-separated-values  */
701   filter = gtk_file_filter_new ();
702   gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
703   gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
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, _("Gnumeric Spreadsheet Files"));
708   gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
709   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
710
711   filter = gtk_file_filter_new ();
712   gtk_file_filter_set_name (filter, _("OpenDocument Spreadsheet Files"));
713   gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
714   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
715
716   filter = gtk_file_filter_new ();
717   gtk_file_filter_set_name (filter, _("All Spreadsheet Files"));
718   gtk_file_filter_add_mime_type (filter, "application/x-gnumeric");
719   gtk_file_filter_add_mime_type (filter, "application/vnd.oasis.opendocument.spreadsheet");
720   gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
721
722   ia->encoding_selector = psppire_encoding_selector_new ("Auto", TRUE);
723   gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER (chooser), ia->encoding_selector);
724
725   add_page_to_assistant (ia, chooser,
726                          GTK_ASSISTANT_PAGE_INTRO, _("Select File to Import"));
727
728   g_signal_connect_swapped (chooser, "selection-changed", G_CALLBACK (on_chosen), ia);
729   g_signal_connect_swapped (chooser, "map", G_CALLBACK (on_map), ia);
730 }
731
732
733
734 static void
735 psppire_import_assistant_init (PsppireImportAssistant *ia)
736 {
737   ia->builder = builder_new ("text-data-import.ui");
738
739   ia->current_page = -1 ;
740   ia->file_name = NULL;
741
742   ia->spreadsheet = NULL;
743   ia->dict = NULL;
744   ia->casereader_dict = NULL;
745
746   ia->main_loop = g_main_loop_new (NULL, TRUE);
747
748   g_signal_connect (ia, "prepare", G_CALLBACK (on_prepare), ia);
749   g_signal_connect (ia, "cancel", G_CALLBACK (on_cancel), ia);
750   g_signal_connect (ia, "close", G_CALLBACK (on_close), ia);
751
752   ia->paste_button = gtk_button_new_with_label (_("Paste"));
753   ia->reset_button = gtk_button_new_with_label (_("Reset"));
754
755   gtk_assistant_add_action_widget (GTK_ASSISTANT(ia), ia->paste_button);
756
757   g_signal_connect (ia->paste_button, "clicked", G_CALLBACK (on_paste), ia);
758   g_signal_connect (ia->reset_button, "clicked", G_CALLBACK (on_reset), ia);
759
760   gtk_assistant_add_action_widget (GTK_ASSISTANT(ia), ia->reset_button);
761
762   gtk_window_set_title (GTK_WINDOW (ia),
763                         _("Importing Delimited Text Data"));
764
765   gtk_window_set_icon_name (GTK_WINDOW (ia), "pspp");
766
767   chooser_page_create (ia);
768
769   gtk_assistant_set_forward_page_func (GTK_ASSISTANT (ia), next_page_func, NULL, NULL);
770
771   gtk_window_maximize (GTK_WINDOW (ia));
772 }
773
774
775 /* Appends a page of the given TYPE, with PAGE as its content, to
776    the GtkAssistant encapsulated by IA.  Returns the GtkWidget
777    that represents the page. */
778 static GtkWidget *
779 add_page_to_assistant (PsppireImportAssistant *ia,
780                        GtkWidget *page, GtkAssistantPageType type, const gchar *title)
781 {
782   GtkWidget *content = page;
783
784   gtk_assistant_append_page (GTK_ASSISTANT (ia), content);
785   gtk_assistant_set_page_type (GTK_ASSISTANT(ia), content, type);
786   gtk_assistant_set_page_title (GTK_ASSISTANT(ia), content, title);
787   gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), content, TRUE);
788
789   return content;
790 }
791
792
793 /* Called when one of the radio buttons is clicked. */
794 static void
795 on_intro_amount_changed (PsppireImportAssistant *p)
796 {
797   gtk_widget_set_sensitive (p->n_cases_spin,
798                             gtk_toggle_button_get_active
799                             (GTK_TOGGLE_BUTTON (p->n_cases_button)));
800
801   gtk_widget_set_sensitive (p->percent_spin,
802                             gtk_toggle_button_get_active
803                             (GTK_TOGGLE_BUTTON (p->percent_button)));
804 }
805
806 static void
807 on_treeview_selection_change (PsppireImportAssistant *ia)
808 {
809   GtkTreeSelection *selection =
810     gtk_tree_view_get_selection (GTK_TREE_VIEW (ia->first_line_tree_view));
811   GtkTreeModel *model = NULL;
812   GtkTreeIter iter;
813   if (gtk_tree_selection_get_selected (selection, &model, &iter))
814     {
815       gint max_lines;
816       int n;
817       GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
818       gint *index = gtk_tree_path_get_indices (path);
819       n = *index;
820       gtk_tree_path_free (path);
821       g_object_get (model, "maximum-lines", &max_lines, NULL);
822       gtk_widget_set_sensitive (ia->variable_names_cb,
823                                 (n > 0 && n < max_lines));
824       ia->delimiters_model =
825         psppire_delimited_text_new (GTK_TREE_MODEL (ia->text_file));
826       g_object_set (ia->delimiters_model, "first-line", n, NULL);
827     }
828 }
829
830 static void
831 render_text_preview_line (GtkTreeViewColumn *tree_column,
832                 GtkCellRenderer *cell,
833                 GtkTreeModel *tree_model,
834                 GtkTreeIter *iter,
835                 gpointer data)
836 {
837   /*
838      Set the text  to a "insensitive" state if the row
839      is greater than what the user declared to be the maximum.
840   */
841   GtkTreePath *path = gtk_tree_model_get_path (tree_model, iter);
842   gint *ii = gtk_tree_path_get_indices (path);
843   gint max_lines;
844   g_object_get (tree_model, "maximum-lines", &max_lines, NULL);
845   g_object_set (cell, "sensitive", (*ii < max_lines), NULL);
846   gtk_tree_path_free (path);
847 }
848
849 /* Initializes IA's first_line substructure. */
850 static void
851 first_line_page_create (PsppireImportAssistant *ia)
852 {
853   GtkWidget *w =  get_widget_assert (ia->builder, "FirstLine");
854
855   g_object_set_data (G_OBJECT (w), "on-entering", on_treeview_selection_change);
856
857   add_page_to_assistant (ia, w,
858                          GTK_ASSISTANT_PAGE_CONTENT, _("Select the First Line"));
859
860   GtkWidget *scrolled_window = get_widget_assert (ia->builder, "first-line-scroller");
861
862   if (ia->first_line_tree_view == NULL)
863     {
864       ia->first_line_tree_view = gtk_tree_view_new ();
865       g_object_set (ia->first_line_tree_view, "enable-search", FALSE, NULL);
866
867       gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ia->first_line_tree_view), TRUE);
868
869       GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
870       GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Line"), renderer,
871                                                                             "text", 0,
872                                                                             NULL);
873
874       gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
875       gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
876
877       renderer = gtk_cell_renderer_text_new ();
878       column = gtk_tree_view_column_new_with_attributes (_("Text"), renderer, "text", 1, NULL);
879       gtk_tree_view_column_set_cell_data_func (column, renderer, render_text_preview_line, ia, 0);
880
881       gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column);
882
883       g_signal_connect_swapped (ia->first_line_tree_view, "cursor-changed",
884                                 G_CALLBACK (on_treeview_selection_change), ia);
885       gtk_container_add (GTK_CONTAINER (scrolled_window), ia->first_line_tree_view);
886     }
887
888   gtk_widget_show_all (scrolled_window);
889
890   ia->variable_names_cb = get_widget_assert (ia->builder, "variable-names");
891 }
892
893 static void
894 intro_on_leave (PsppireImportAssistant *ia)
895 {
896   gint lc = 0;
897   g_object_get (ia->text_file, "line-count", &lc, NULL);
898   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
899     {
900       gint max_lines = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin));
901       g_object_set (ia->text_file, "maximum-lines", max_lines, NULL);
902     }
903   else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
904     {
905       gdouble percent = gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin));
906       g_object_set (ia->text_file, "maximum-lines", (gint) (lc * percent / 100.0), NULL);
907     }
908   else
909     {
910       g_object_set (ia->text_file, "maximum-lines", lc, NULL);
911     }
912 }
913
914
915 static void
916 intro_on_enter (PsppireImportAssistant *ia)
917 {
918   GtkBuilder *builder = ia->builder;
919   GtkWidget *table  = get_widget_assert (builder, "button-table");
920
921   struct string s;
922
923   ds_init_empty (&s);
924   ds_put_cstr (&s, _("This assistant will guide you through the process of "
925                      "importing data into PSPP from a text file with one line "
926                      "per case,  in which fields are separated by tabs, "
927                      "commas, or other delimiters.\n\n"));
928
929   if (ia->text_file)
930     {
931       if (ia->text_file->total_is_exact)
932         {
933           ds_put_format (
934                          &s, ngettext ("The selected file contains %'lu line of text.  ",
935                                        "The selected file contains %'lu lines of text.  ",
936                                        ia->text_file->total_lines),
937                          ia->text_file->total_lines);
938         }
939       else if (ia->text_file->total_lines > 0)
940         {
941           ds_put_format (
942                          &s, ngettext (
943                                        "The selected file contains approximately %'lu line of text.  ",
944                                        "The selected file contains approximately %'lu lines of text.  ",
945                                        ia->text_file->total_lines),
946                          ia->text_file->total_lines);
947           ds_put_format (
948                          &s, ngettext (
949                                        "Only the first %zu line of the file will be shown for "
950                                        "preview purposes in the following screens.  ",
951                                        "Only the first %zu lines of the file will be shown for "
952                                        "preview purposes in the following screens.  ",
953                                        ia->text_file->line_cnt),
954                          ia->text_file->line_cnt);
955         }
956     }
957
958   ds_put_cstr (&s, _("You may choose below how much of the file should "
959                      "actually be imported."));
960
961   gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
962                       ds_cstr (&s));
963   ds_destroy (&s);
964
965   if (gtk_grid_get_child_at (GTK_GRID (table), 1, 1) == NULL)
966     {
967       GtkWidget *hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &ia->n_cases_spin);
968       gtk_grid_attach (GTK_GRID (table), hbox_n_cases,
969                        1, 1,
970                        1, 1);
971     }
972
973   GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (ia->n_cases_spin));
974   gtk_adjustment_set_lower (adj, 1.0);
975
976   if (gtk_grid_get_child_at (GTK_GRID (table), 1, 2) == NULL)
977     {
978       GtkWidget *hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"),
979                                                    &ia->percent_spin);
980
981       gtk_grid_attach (GTK_GRID (table), hbox_percent,
982                        1, 2,
983                        1, 1);
984     }
985
986   gtk_widget_show_all (table);
987
988   on_intro_amount_changed (ia);
989 }
990
991 /* Initializes IA's intro substructure. */
992 static void
993 intro_page_create (PsppireImportAssistant *ia)
994 {
995   GtkBuilder *builder = ia->builder;
996
997   GtkWidget *w =  get_widget_assert (builder, "Intro");
998
999   ia->percent_spin = gtk_spin_button_new_with_range (0, 100, 10);
1000
1001
1002   add_page_to_assistant (ia, w,  GTK_ASSISTANT_PAGE_CONTENT, _("Select the Lines to Import"));
1003
1004   ia->all_cases_button = get_widget_assert (builder, "import-all-cases");
1005
1006   ia->n_cases_button = get_widget_assert (builder, "import-n-cases");
1007
1008   ia->percent_button = get_widget_assert (builder, "import-percent");
1009
1010   g_signal_connect_swapped (ia->all_cases_button, "toggled",
1011                             G_CALLBACK (on_intro_amount_changed), ia);
1012   g_signal_connect_swapped (ia->n_cases_button, "toggled",
1013                             G_CALLBACK (on_intro_amount_changed), ia);
1014   g_signal_connect_swapped (ia->percent_button, "toggled",
1015                             G_CALLBACK (on_intro_amount_changed), ia);
1016
1017
1018   g_object_set_data (G_OBJECT (w), "on-forward", intro_on_leave);
1019   g_object_set_data (G_OBJECT (w), "on-entering", intro_on_enter);
1020   g_object_set_data (G_OBJECT (w), "on-reset", reset_intro_page);
1021 }
1022
1023
1024 GtkWidget *
1025 psppire_import_assistant_new (GtkWindow *toplevel)
1026 {
1027   return GTK_WIDGET (g_object_new (PSPPIRE_TYPE_IMPORT_ASSISTANT,
1028                                    /* Some window managers (notably ratpoison)
1029                                       ignore the maximise command when a window is
1030                                       transient.  This causes problems for this
1031                                       window. */
1032                                    /* "transient-for", toplevel, */
1033                                    NULL));
1034 }
1035
1036
1037
1038 \f
1039
1040 /* Chooses a name for each column on the separators page */
1041 static void
1042 choose_column_names (PsppireImportAssistant *ia)
1043 {
1044   int i;
1045   unsigned long int generated_name_count = 0;
1046   dict_clear (ia->dict);
1047
1048   for (i = 0;
1049        i < gtk_tree_model_get_n_columns (GTK_TREE_MODEL (ia->delimiters_model)) - 1;
1050        ++i)
1051     {
1052       const gchar *candidate_name = NULL;
1053
1054       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->variable_names_cb)))
1055         {
1056           candidate_name = psppire_delimited_text_get_header_title (PSPPIRE_DELIMITED_TEXT (ia->delimiters_model), i);
1057         }
1058
1059       char *name = dict_make_unique_var_name (ia->dict,
1060                                               candidate_name,
1061                                               &generated_name_count);
1062
1063       dict_create_var_assert (ia->dict, name, 0);
1064       free (name);
1065     }
1066 }
1067
1068 /* Called when the user toggles one of the separators
1069    checkboxes. */
1070 static void
1071 on_separator_toggle (GtkToggleButton *toggle UNUSED,
1072                      PsppireImportAssistant *ia)
1073 {
1074   int i;
1075   GSList *delimiters = NULL;
1076   for (i = 0; i < SEPARATOR_CNT; i++)
1077     {
1078       const struct separator *s = &separators[i];
1079       GtkWidget *button = get_widget_assert (ia->builder, s->name);
1080       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1081         {
1082           delimiters = g_slist_prepend (delimiters,  GINT_TO_POINTER (s->c));
1083         }
1084     }
1085
1086   g_object_set (ia->delimiters_model, "delimiters", delimiters, NULL);
1087
1088   revise_fields_preview (ia);
1089 }
1090
1091
1092 /* Called when the user changes the entry field for custom
1093    separators. */
1094 static void
1095 on_separators_custom_entry_notify (GObject *gobject UNUSED,
1096                                    GParamSpec *arg1 UNUSED,
1097                                    PsppireImportAssistant *ia)
1098 {
1099   revise_fields_preview (ia);
1100 }
1101
1102 /* Called when the user toggles the checkbox that enables custom
1103    separators. */
1104 static void
1105 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1106                                 PsppireImportAssistant *ia)
1107 {
1108   bool is_active = gtk_toggle_button_get_active (custom_cb);
1109   gtk_widget_set_sensitive (ia->custom_entry, is_active);
1110   revise_fields_preview (ia);
1111 }
1112
1113 /* Called when the user changes the selection in the combo box
1114    that selects a quote character. */
1115 static void
1116 on_quote_combo_change (GtkComboBox *combo, PsppireImportAssistant *ia)
1117 {
1118   //  revise_fields_preview (ia);
1119 }
1120
1121 /* Called when the user toggles the checkbox that enables
1122    quoting. */
1123 static void
1124 on_quote_cb_toggle (GtkToggleButton *quote_cb, PsppireImportAssistant *ia)
1125 {
1126   bool is_active = gtk_toggle_button_get_active (quote_cb);
1127   gtk_widget_set_sensitive (ia->quote_combo, is_active);
1128   revise_fields_preview (ia);
1129 }
1130
1131 /* Initializes IA's separators substructure. */
1132 static void
1133 separators_page_create (PsppireImportAssistant *ia)
1134 {
1135   GtkBuilder *builder = ia->builder;
1136
1137   size_t i;
1138
1139   GtkWidget *w = get_widget_assert (builder, "Separators");
1140
1141   g_object_set_data (G_OBJECT (w), "on-entering", prepare_separators_page);
1142   g_object_set_data (G_OBJECT (w), "on-reset", prepare_separators_page);
1143
1144   add_page_to_assistant (ia, w,   GTK_ASSISTANT_PAGE_CONTENT, _("Choose Separators"));
1145
1146   ia->custom_cb = get_widget_assert (builder, "custom-cb");
1147   ia->custom_entry = get_widget_assert (builder, "custom-entry");
1148   ia->quote_combo = get_widget_assert (builder, "quote-combo");
1149   ia->quote_cb = get_widget_assert (builder, "quote-cb");
1150
1151   gtk_combo_box_set_active (GTK_COMBO_BOX (ia->quote_combo), 0);
1152
1153   if (ia->fields_tree_view == NULL)
1154     {
1155       GtkWidget *scroller = get_widget_assert (ia->builder, "fields-scroller");
1156       ia->fields_tree_view = gtk_tree_view_new ();
1157       g_object_set (ia->fields_tree_view, "enable-search", FALSE, NULL);
1158       gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ia->fields_tree_view));
1159       gtk_widget_show_all (scroller);
1160     }
1161
1162   g_signal_connect (ia->quote_combo, "changed",
1163                     G_CALLBACK (on_quote_combo_change), ia);
1164   g_signal_connect (ia->quote_cb, "toggled",
1165                     G_CALLBACK (on_quote_cb_toggle), ia);
1166   g_signal_connect (ia->custom_entry, "notify::text",
1167                     G_CALLBACK (on_separators_custom_entry_notify), ia);
1168   g_signal_connect (ia->custom_cb, "toggled",
1169                     G_CALLBACK (on_separators_custom_cb_toggle), ia);
1170   for (i = 0; i < SEPARATOR_CNT; i++)
1171     g_signal_connect (get_widget_assert (builder, separators[i].name),
1172                       "toggled", G_CALLBACK (on_separator_toggle), ia);
1173
1174 }
1175
1176
1177
1178 \f
1179
1180
1181 static struct casereader_random_class my_casereader_class;
1182
1183 static struct ccase *
1184 my_read (struct casereader *reader, void *aux, casenumber idx)
1185 {
1186   PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (aux);
1187   GtkTreeModel *tm = GTK_TREE_MODEL (ia->delimiters_model);
1188
1189   GtkTreePath *tp = gtk_tree_path_new_from_indices (idx, -1);
1190
1191   const struct caseproto *proto = casereader_get_proto (reader);
1192
1193   GtkTreeIter iter;
1194   struct ccase *c = NULL;
1195   if (gtk_tree_model_get_iter (tm, &iter, tp))
1196     {
1197       c = case_create (proto);
1198       int i;
1199       for (i = 0 ; i < caseproto_get_n_widths (proto); ++i)
1200         {
1201           GValue value = {0};
1202           gtk_tree_model_get_value (tm, &iter, i + 1, &value);
1203
1204           const struct variable *var = dict_get_var (ia->casereader_dict, i);
1205
1206           const gchar *ss = g_value_get_string (&value);
1207           if (ss)
1208             {
1209               union value *v = case_data_rw (c, var);
1210               /* In this reader we derive the union value from the
1211                  string in the tree_model. We retrieve the width and format
1212                  from a dictionary which is stored directly after
1213                  the reader creation. Changes in ia->dict in the
1214                  variable window are not reflected here and therefore
1215                  this is always compatible with the width in the
1216                  caseproto. See bug #58298 */
1217               char *xx = data_in (ss_cstr (ss),
1218                                   "UTF-8",
1219                                   var_get_write_format (var)->type,
1220                                   v, var_get_width (var), "UTF-8");
1221
1222               /* if (xx) */
1223               /*   g_print ("%s:%d Err %s\n", __FILE__, __LINE__, xx); */
1224               free (xx);
1225             }
1226           g_value_unset (&value);
1227         }
1228     }
1229
1230   gtk_tree_path_free (tp);
1231
1232   return c;
1233 }
1234
1235 static void
1236 my_destroy (struct casereader *reader, void *aux)
1237 {
1238   g_print ("%s:%d %p\n", __FILE__, __LINE__, reader);
1239 }
1240
1241 static void
1242 my_advance (struct casereader *reader, void *aux, casenumber cnt)
1243 {
1244   g_print ("%s:%d\n", __FILE__, __LINE__);
1245 }
1246
1247 static struct casereader *
1248 textfile_create_reader (PsppireImportAssistant *ia)
1249 {
1250   int n_vars = dict_get_var_cnt (ia->dict);
1251
1252   int i;
1253
1254   struct fmt_guesser **fg = XCALLOC (n_vars,  struct fmt_guesser *);
1255   for (i = 0 ; i < n_vars; ++i)
1256     {
1257       fg[i] = fmt_guesser_create ();
1258     }
1259
1260   gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
1261
1262   GtkTreeIter iter;
1263   gboolean ok;
1264   for (ok = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->delimiters_model), &iter);
1265        ok;
1266        ok = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->delimiters_model), &iter))
1267     {
1268       for (i = 0 ; i < n_vars; ++i)
1269         {
1270           gchar *s = NULL;
1271           gtk_tree_model_get (GTK_TREE_MODEL (ia->delimiters_model), &iter, i+1, &s, -1);
1272           if (s)
1273             fmt_guesser_add (fg[i], ss_cstr (s));
1274           free (s);
1275         }
1276     }
1277
1278   struct caseproto *proto = caseproto_create ();
1279   for (i = 0 ; i < n_vars; ++i)
1280     {
1281       struct fmt_spec fs;
1282       fmt_guesser_guess (fg[i], &fs);
1283
1284       fmt_fix (&fs, FMT_FOR_INPUT);
1285
1286       struct variable *var = dict_get_var (ia->dict, i);
1287
1288       int width = fmt_var_width (&fs);
1289
1290       var_set_width_and_formats (var, width,
1291                                  &fs, &fs);
1292
1293       proto = caseproto_add_width (proto, width);
1294       fmt_guesser_destroy (fg[i]);
1295     }
1296
1297   free (fg);
1298
1299   struct casereader *cr = casereader_create_random (proto, n_rows, &my_casereader_class,  ia);
1300   /* Store the dictionary at this point when the casereader is created.
1301      my_read depends on the dictionary to interpret the strings in the treeview.
1302      This guarantees that the union value is produced according to the
1303      caseproto in the reader. */
1304   ia->casereader_dict = dict_clone (ia->dict);
1305   caseproto_unref (proto);
1306   return cr;
1307 }
1308
1309 /* When during import the variable type is changed, the reader is reinitialized
1310    based on the new dictionary with a fresh caseprototype. The default behaviour
1311    when a variable type is changed and the column is resized is that the union
1312    value is interpreted with new variable type and an overlay for that column
1313    is generated. Here we reinit to the original reader based on strings.
1314    As a result you can switch from string to numeric to string without loosing
1315    the string information. */
1316 static void
1317 ia_variable_changed_cb (GObject *obj, gint var_num, guint what,
1318                         const struct variable *oldvar, gpointer data)
1319 {
1320   PsppireImportAssistant *ia  = PSPPIRE_IMPORT_ASSISTANT (data);
1321
1322   struct caseproto *proto = caseproto_create();
1323   for (int i = 0; i < dict_get_var_cnt (ia->dict); i++)
1324     {
1325       const struct variable *var = dict_get_var (ia->dict, i);
1326       int width = var_get_width (var);
1327       proto = caseproto_add_width (proto, width);
1328     }
1329
1330   gint n_rows = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->delimiters_model), NULL);
1331
1332   PsppireDataStore *store = NULL;
1333   g_object_get (ia->data_sheet, "data-model", &store, NULL);
1334
1335   struct casereader *cr = casereader_create_random (proto, n_rows,
1336                                                     &my_casereader_class, ia);
1337   psppire_data_store_set_reader (store, cr);
1338   dict_unref (ia->casereader_dict);
1339   ia->casereader_dict = dict_clone (ia->dict);
1340 }
1341
1342 /* Called just before the formats page of the assistant is
1343    displayed. */
1344 static void
1345 prepare_formats_page (PsppireImportAssistant *ia)
1346 {
1347   my_casereader_class.read = my_read;
1348   my_casereader_class.destroy = my_destroy;
1349   my_casereader_class.advance = my_advance;
1350
1351   if (ia->spreadsheet)
1352     {
1353       GtkBuilder *builder = ia->builder;
1354       GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
1355       GtkWidget *rnc = get_widget_assert (builder, "readnames-checkbox");
1356       GtkWidget *combo_box = get_widget_assert (builder, "sheet-entry");
1357
1358       struct spreadsheet_read_options opts;
1359       opts.sheet_name = NULL;
1360       opts.sheet_index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo_box)) + 1;
1361       opts.read_names = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rnc));
1362       opts.cell_range = g_strdup (gtk_entry_get_text (GTK_ENTRY (range_entry)));
1363       opts.asw = 8;
1364
1365       struct casereader *reader = spreadsheet_make_reader (ia->spreadsheet, &opts);
1366
1367       PsppireDict *dict = psppire_dict_new_from_dict (ia->spreadsheet->dict);
1368       PsppireDataStore *store = psppire_data_store_new (dict);
1369       psppire_data_store_set_reader (store, reader);
1370       g_object_set (ia->data_sheet, "data-model", store, NULL);
1371       g_object_set (ia->var_sheet, "data-model", dict, NULL);
1372     }
1373   else
1374     {
1375       struct casereader *reader = textfile_create_reader (ia);
1376
1377       PsppireDict *dict = psppire_dict_new_from_dict (ia->dict);
1378       PsppireDataStore *store = psppire_data_store_new (dict);
1379       psppire_data_store_set_reader (store, reader);
1380       g_signal_connect (dict, "variable-changed",
1381                         G_CALLBACK (ia_variable_changed_cb),
1382                         ia);
1383
1384       g_object_set (ia->data_sheet, "data-model", store, NULL);
1385       g_object_set (ia->var_sheet, "data-model", dict, NULL);
1386     }
1387
1388   gint pmax;
1389   g_object_get (get_widget_assert (ia->builder, "vpaned1"),
1390                 "max-position", &pmax, NULL);
1391
1392
1393   g_object_set (get_widget_assert (ia->builder, "vpaned1"),
1394                 "position", pmax / 2, NULL);
1395
1396   gtk_widget_show (ia->paste_button);
1397 }
1398
1399 static void
1400 formats_page_create (PsppireImportAssistant *ia)
1401 {
1402   GtkBuilder *builder = ia->builder;
1403
1404   GtkWidget *w = get_widget_assert (builder, "Formats");
1405   g_object_set_data (G_OBJECT (w), "on-entering", prepare_formats_page);
1406   g_object_set_data (G_OBJECT (w), "on-reset", reset_formats_page);
1407
1408   GtkWidget *vars_scroller = get_widget_assert (builder, "vars-scroller");
1409   if (ia->var_sheet == NULL)
1410     {
1411       ia->var_sheet = psppire_variable_sheet_new ();
1412
1413       gtk_container_add (GTK_CONTAINER (vars_scroller), ia->var_sheet);
1414
1415       ia->dict = dict_create (get_default_encoding ());
1416
1417       gtk_widget_show_all (vars_scroller);
1418     }
1419   GtkWidget *data_scroller = get_widget_assert (builder, "data-scroller");
1420   if (ia->data_sheet == NULL)
1421     {
1422       ia->data_sheet = psppire_data_sheet_new ();
1423       g_object_set (ia->data_sheet, "editable", FALSE, NULL);
1424
1425       gtk_container_add (GTK_CONTAINER (data_scroller), ia->data_sheet);
1426
1427       gtk_widget_show_all (data_scroller);
1428     }
1429
1430   add_page_to_assistant (ia, w,
1431                          GTK_ASSISTANT_PAGE_CONFIRM, _("Adjust Variable Formats"));
1432 }
1433
1434
1435 \f
1436
1437 static void
1438 separators_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1439 {
1440   int i;
1441
1442   ds_put_cstr (s, "  /DELIMITERS=\"");
1443
1444   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (get_widget_assert (ia->builder, "tab"))))
1445     ds_put_cstr (s, "\\t");
1446   for (i = 0; i < SEPARATOR_CNT; i++)
1447     {
1448       const struct separator *seps = &separators[i];
1449       GtkWidget *button = get_widget_assert (ia->builder, seps->name);
1450       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1451         {
1452           if (seps->c == '\t')
1453             continue;
1454
1455           ds_put_byte (s, seps->c);
1456         }
1457     }
1458   ds_put_cstr (s, "\"\n");
1459
1460   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->quote_cb)))
1461     {
1462       GtkComboBoxText *cbt = GTK_COMBO_BOX_TEXT (ia->quote_combo);
1463       gchar *quotes = gtk_combo_box_text_get_active_text (cbt);
1464       if (quotes && *quotes)
1465         syntax_gen_pspp (s, "  /QUALIFIER=%sq\n", quotes);
1466       free (quotes);
1467     }
1468 }
1469
1470 static void
1471 formats_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1472 {
1473   int i;
1474   int var_cnt;
1475
1476   g_return_if_fail (ia->dict);
1477
1478   ds_put_cstr (s, "  /VARIABLES=\n");
1479
1480   var_cnt = dict_get_var_cnt (ia->dict);
1481   for (i = 0; i < var_cnt; i++)
1482     {
1483       struct variable *var = dict_get_var (ia->dict, i);
1484       char format_string[FMT_STRING_LEN_MAX + 1];
1485       fmt_to_string (var_get_print_format (var), format_string);
1486       ds_put_format (s, "    %s %s%s\n",
1487                      var_get_name (var), format_string,
1488                      i == var_cnt - 1 ? "." : "");
1489     }
1490 }
1491
1492 static void
1493 first_line_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1494 {
1495   gint first_case = 0;
1496   g_object_get (ia->delimiters_model, "first-line", &first_case, NULL);
1497
1498   if (first_case > 0)
1499     ds_put_format (s, "  /FIRSTCASE=%d\n", first_case + 1);
1500 }
1501
1502 static void
1503 intro_append_syntax (const PsppireImportAssistant *ia, struct string *s)
1504 {
1505   gint first_line = 0;
1506   g_object_get (ia->delimiters_model, "first-line", &first_line, NULL);
1507
1508   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->n_cases_button)))
1509     ds_put_format (s, "SELECT IF ($CASENUM <= %d).\n",
1510                    gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ia->n_cases_spin)) - first_line);
1511   else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ia->percent_button)))
1512     ds_put_format (s, "SAMPLE %.4g.\n",
1513                    gtk_spin_button_get_value (GTK_SPIN_BUTTON (ia->percent_spin)) / 100.0);
1514 }
1515
1516
1517 /* Emits PSPP syntax to S that applies the dictionary attributes
1518    (such as missing values and value labels) of the variables in
1519    DICT.  */
1520 static void
1521 apply_dict (const struct dictionary *dict, struct string *s)
1522 {
1523   size_t var_cnt = dict_get_var_cnt (dict);
1524   size_t i;
1525
1526   for (i = 0; i < var_cnt; i++)
1527     {
1528       struct variable *var = dict_get_var (dict, i);
1529       const char *name = var_get_name (var);
1530       enum val_type type = var_get_type (var);
1531       int width = var_get_width (var);
1532       enum measure measure = var_get_measure (var);
1533       enum var_role role = var_get_role (var);
1534       enum alignment alignment = var_get_alignment (var);
1535       const struct fmt_spec *format = var_get_print_format (var);
1536
1537       if (var_has_missing_values (var))
1538         {
1539           const struct missing_values *mv = var_get_missing_values (var);
1540           size_t j;
1541
1542           syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
1543           for (j = 0; j < mv_n_values (mv); j++)
1544             {
1545               if (j)
1546                 ds_put_cstr (s, ", ");
1547               syntax_gen_value (s, mv_get_value (mv, j), width, format);
1548             }
1549
1550           if (mv_has_range (mv))
1551             {
1552               double low, high;
1553               if (mv_has_value (mv))
1554                 ds_put_cstr (s, ", ");
1555               mv_get_range (mv, &low, &high);
1556               syntax_gen_num_range (s, low, high, format);
1557             }
1558           ds_put_cstr (s, ").\n");
1559         }
1560       if (var_has_value_labels (var))
1561         {
1562           const struct val_labs *vls = var_get_value_labels (var);
1563           const struct val_lab **labels = val_labs_sorted (vls);
1564           size_t n_labels = val_labs_count (vls);
1565           size_t i;
1566
1567           syntax_gen_pspp (s, "VALUE LABELS %ss", name);
1568           for (i = 0; i < n_labels; i++)
1569             {
1570               const struct val_lab *vl = labels[i];
1571               ds_put_cstr (s, "\n  ");
1572               syntax_gen_value (s, &vl->value, width, format);
1573               ds_put_byte (s, ' ');
1574               syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl)));
1575             }
1576           free (labels);
1577           ds_put_cstr (s, ".\n");
1578         }
1579       if (var_has_label (var))
1580         syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
1581                          name, var_get_label (var));
1582       if (measure != var_default_measure (type))
1583         syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
1584                          name, measure_to_syntax (measure));
1585       if (role != ROLE_INPUT)
1586         syntax_gen_pspp (s, "VARIABLE ROLE /%ss %ss.\n",
1587                          var_role_to_syntax (role), name);
1588       if (alignment != var_default_alignment (type))
1589         syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
1590                          name, alignment_to_syntax (alignment));
1591       if (var_get_display_width (var) != var_default_display_width (width))
1592         syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
1593                          name, var_get_display_width (var));
1594     }
1595 }
1596
1597
1598
1599 static void
1600 sheet_spec_gen_syntax (PsppireImportAssistant *ia, struct string *s)
1601 {
1602   GtkBuilder *builder = ia->builder;
1603   GtkWidget *range_entry = get_widget_assert (builder, "cell-range-entry");
1604   GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry");
1605   GtkWidget *rnc = get_widget_assert (builder, "readnames-checkbox");
1606   const gchar *range = gtk_entry_get_text (GTK_ENTRY (range_entry));
1607   int sheet_index = 1 + gtk_combo_box_get_active (GTK_COMBO_BOX (sheet_entry));
1608   gboolean read_names = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (rnc));
1609
1610
1611   char *filename;
1612   if (ia->spreadsheet)
1613     filename = ia->spreadsheet->file_name;
1614   else
1615     g_object_get (ia->text_file, "file-name", &filename, NULL);
1616   syntax_gen_pspp (s,
1617                    "GET DATA"
1618                    "\n  /TYPE=%ss"
1619                    "\n  /FILE=%sq"
1620                    "\n  /SHEET=index %d"
1621                    "\n  /READNAMES=%ss",
1622                    (ia->spreadsheet->type == SPREADSHEET_GNUMERIC) ? "GNM" : "ODS",
1623                    filename,
1624                    sheet_index,
1625                    read_names ? "ON" : "OFF");
1626
1627   if (range && 0 != strcmp ("", range))
1628     {
1629       syntax_gen_pspp (s,
1630                        "\n  /CELLRANGE=RANGE %sq", range);
1631     }
1632   else
1633     {
1634       syntax_gen_pspp (s,
1635                        "\n  /CELLRANGE=FULL");
1636     }
1637
1638
1639   syntax_gen_pspp (s, ".\n");
1640 }
1641
1642
1643 gchar *
1644 psppire_import_assistant_generate_syntax (PsppireImportAssistant *ia)
1645 {
1646   struct string s = DS_EMPTY_INITIALIZER;
1647
1648   if (!ia->spreadsheet)
1649     {
1650       gchar *file_name = NULL;
1651       gchar *encoding = NULL;
1652       g_object_get (ia->text_file,
1653                     "file-name", &file_name,
1654                     "encoding", &encoding,
1655                     NULL);
1656
1657       if (file_name == NULL)
1658         return NULL;
1659
1660       syntax_gen_pspp (&s,
1661                        "GET DATA"
1662                        "\n  /TYPE=TXT"
1663                        "\n  /FILE=%sq\n",
1664                        file_name);
1665       if (encoding && strcmp (encoding, "Auto"))
1666         syntax_gen_pspp (&s, "  /ENCODING=%sq\n", encoding);
1667
1668       ds_put_cstr (&s,
1669                    "  /ARRANGEMENT=DELIMITED\n"
1670                    "  /DELCASE=LINE\n");
1671
1672       first_line_append_syntax (ia, &s);
1673       separators_append_syntax (ia, &s);
1674
1675       formats_append_syntax (ia, &s);
1676       apply_dict (ia->dict, &s);
1677       intro_append_syntax (ia, &s);
1678     }
1679   else
1680     {
1681       sheet_spec_gen_syntax (ia, &s);
1682     }
1683
1684   return ds_cstr (&s);
1685 }
1686
1687
1688 int
1689 psppire_import_assistant_run (PsppireImportAssistant *asst)
1690 {
1691   g_main_loop_run (asst->main_loop);
1692   return asst->response;
1693 }