Patch #5368. Reviewed by John Darrington. Tested by Jason Stover.
[pspp-builds.git] / src / ui / gui / text-data-import-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2008  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 "checkbox-treeview.h"
20 #include "descriptives-dialog.h"
21
22 #include <errno.h>
23 #include <gtk/gtk.h>
24 #include <gtksheet/gtksheet.h>
25 #include <limits.h>
26 #include <stdlib.h>
27 #include <sys/stat.h>
28
29 #include <data/data-in.h>
30 #include <data/data-out.h>
31 #include <data/format-guesser.h>
32 #include <data/value-labels.h>
33 #include <language/data-io/data-parser.h>
34 #include <language/syntax-string-source.h>
35 #include <libpspp/assertion.h>
36 #include <libpspp/message.h>
37 #include <ui/syntax-gen.h>
38 #include <ui/gui/data-editor.h>
39 #include <ui/gui/dialog-common.h>
40 #include <ui/gui/helper.h>
41 #include <ui/gui/psppire-dialog.h>
42 #include <ui/gui/psppire-var-sheet.h>
43 #include <ui/gui/psppire-var-store.h>
44 #include <ui/gui/syntax-editor.h>
45
46 #include "error.h"
47 #include "xalloc.h"
48
49 #include "gettext.h"
50 #define _(msgid) gettext (msgid)
51 #define N_(msgid) msgid
52
53 /* TextImportModel, a GtkTreeModel used by the text data import
54    dialog. */
55 enum
56   {
57     TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER, /* 1-based line number in file */
58     TEXT_IMPORT_MODEL_COLUMN_LINE,        /* The line from the file. */
59   };
60 typedef struct TextImportModel TextImportModel;
61 typedef struct TextImportModelClass TextImportModelClass;
62
63 TextImportModel *text_import_model_new (struct string *lines, size_t line_cnt,
64                                         size_t first_line);
65 gint text_import_model_iter_to_row (const GtkTreeIter *);
66
67 struct import_assistant;
68
69 /* The file to be imported. */
70 struct file
71   {
72     char *file_name;        /* File name. */
73     unsigned long int total_lines; /* Number of lines in file. */
74     bool total_is_exact;    /* Is total_lines exact (or an estimate)? */
75
76     /* The first several lines of the file. */
77     struct string *lines;
78     size_t line_cnt;
79   };
80 static bool init_file (struct import_assistant *, GtkWindow *parent);
81 static void destroy_file (struct import_assistant *);
82
83 /* The main body of the GTK+ assistant and related data. */
84 struct assistant
85   {
86     GladeXML *xml;
87     GtkAssistant *assistant;
88     GMainLoop *main_loop;
89     GtkWidget *paste_button;
90     GtkWidget *reset_button;
91     int response;
92
93     GtkCellRenderer *prop_renderer;
94     GtkCellRenderer *fixed_renderer;
95   };
96 static void init_assistant (struct import_assistant *, GtkWindow *);
97 static void destroy_assistant (struct import_assistant *);
98 static GtkWidget *add_page_to_assistant (struct import_assistant *,
99                                          GtkWidget *page,
100                                          GtkAssistantPageType);
101
102 /* The introduction page of the assistant. */
103 struct intro_page
104   {
105     GtkWidget *page;
106     GtkWidget *all_cases_button;
107     GtkWidget *n_cases_button;
108     GtkWidget *n_cases_spin;
109     GtkWidget *percent_button;
110     GtkWidget *percent_spin;
111   };
112 static void init_intro_page (struct import_assistant *);
113 static void reset_intro_page (struct import_assistant *);
114
115 /* Page where the user chooses the first line of data. */
116 struct first_line_page
117   {
118     int skip_lines;    /* Number of initial lines to skip? */
119     bool variable_names; /* Variable names above first line of data? */
120
121     GtkWidget *page;
122     GtkTreeView *tree_view;
123     GtkWidget *variable_names_cb;
124   };
125 static void init_first_line_page (struct import_assistant *);
126 static void reset_first_line_page (struct import_assistant *);
127
128 /* Page where the user chooses field separators. */
129 struct separators_page
130   {
131     /* How to break lines into columns. */
132     struct string separators;   /* Field separators. */
133     struct string quotes;       /* Quote characters. */
134     bool escape;                /* Doubled quotes yield a quote mark? */
135
136     /* The columns produced thereby. */
137     struct column *columns;     /* Information about each column. */
138     size_t column_cnt;          /* Number of columns. */
139
140     GtkWidget *page;
141     GtkWidget *custom_cb;
142     GtkWidget *custom_entry;
143     GtkWidget *quote_cb;
144     GtkWidget *quote_combo;
145     GtkEntry *quote_entry;
146     GtkWidget *escape_cb;
147     GtkTreeView *fields_tree_view;
148   };
149 /* The columns that the separators divide the data into. */
150 struct column
151   {
152     /* Variable name for this column.  This is the variable name
153        used on the separators page; it can be overridden by the
154        user on the formats page. */
155     char *name;
156
157     /* Maximum length of any row in this column. */
158     size_t width;
159
160     /* Contents of this column: contents[row] is the contents for
161        the given row.
162
163        A null substring indicates a missing column for that row
164        (because the line contains an insufficient number of
165        separators).
166
167        contents[] elements may be substrings of the lines[]
168        strings that represent the whole lines of the file, to
169        save memory.  Other elements are dynamically allocated
170        with ss_alloc_substring. */
171     struct substring *contents;
172   };
173 static void init_separators_page (struct import_assistant *);
174 static void destroy_separators_page (struct import_assistant *);
175 static void prepare_separators_page (struct import_assistant *);
176 static void reset_separators_page (struct import_assistant *);
177
178 /* Page where the user verifies and adjusts input formats. */
179 struct formats_page
180   {
181     struct dictionary *dict;
182
183     GtkWidget *page;
184     GtkTreeView *data_tree_view;
185     PsppireDict *psppire_dict;
186     struct variable **modified_vars;
187     size_t modified_var_cnt;
188   };
189 static void init_formats_page (struct import_assistant *);
190 static void destroy_formats_page (struct import_assistant *);
191 static void prepare_formats_page (struct import_assistant *);
192 static void reset_formats_page (struct import_assistant *);
193
194 struct import_assistant
195   {
196     struct file file;
197     struct assistant asst;
198     struct intro_page intro;
199     struct first_line_page first_line;
200     struct separators_page separators;
201     struct formats_page formats;
202   };
203
204 static void apply_dict (const struct dictionary *, struct string *);
205 static char *generate_syntax (const struct import_assistant *);
206
207 static gboolean get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
208                                       const struct import_assistant *,
209                                       size_t *row, size_t *column);
210 static void make_tree_view (const struct import_assistant *ia,
211                             size_t first_line,
212                             GtkTreeView **tree_view);
213 static void add_line_number_column (const struct import_assistant *,
214                                     GtkTreeView *);
215 static gint get_monospace_width (GtkTreeView *, GtkCellRenderer *,
216                                  size_t char_cnt);
217 static gint get_string_width (GtkTreeView *, GtkCellRenderer *,
218                               const char *string);
219 static GtkTreeViewColumn *make_data_column (struct import_assistant *,
220                                             GtkTreeView *, bool input,
221                                             gint column_idx);
222 static GtkTreeView *create_data_tree_view (bool input, GtkContainer *parent,
223                                            struct import_assistant *);
224 static void escape_underscores (const char *in, char *out);
225
226 /* Pops up the Text Data Import assistant. */
227 void
228 text_data_import_assistant (GObject *o, gpointer de_)
229 {
230   struct data_editor *de = de_;
231   GtkWindow *parent_window = de->parent.window;
232   struct import_assistant ia = { { 0, } };
233
234   if (!init_file (&ia, parent_window))
235     return;
236
237   init_assistant (&ia, parent_window);
238   init_intro_page (&ia);
239   init_first_line_page (&ia);
240   init_separators_page (&ia);
241   init_formats_page (&ia);
242
243   gtk_widget_show_all (GTK_WIDGET (ia.asst.assistant));
244
245   ia.asst.main_loop = g_main_loop_new (NULL, false);
246   g_main_loop_run (ia.asst.main_loop);
247
248   switch (ia.asst.response)
249     {
250     case GTK_RESPONSE_APPLY:
251       {
252         char *syntax = generate_syntax (&ia);
253         execute_syntax (create_syntax_string_source (syntax));
254         free (syntax);
255       }
256       break;
257     case PSPPIRE_RESPONSE_PASTE:
258       {
259         char *syntax = generate_syntax (&ia);
260         struct syntax_editor *se =
261           (struct syntax_editor *) window_create (WINDOW_SYNTAX, NULL);
262         gtk_text_buffer_insert_at_cursor (se->buffer, syntax, -1);
263         free (syntax);
264       }
265       break;
266     default:
267       break;
268     }
269
270   destroy_formats_page (&ia);
271   destroy_separators_page (&ia);
272   destroy_assistant (&ia);
273   destroy_file (&ia);
274 }
275
276 /* Emits PSPP syntax to S that applies the dictionary attributes
277    (such as missing values and value labels) of the variables in
278    DICT.  */
279 static void
280 apply_dict (const struct dictionary *dict, struct string *s)
281 {
282   size_t var_cnt = dict_get_var_cnt (dict);
283   size_t i;
284
285   for (i = 0; i < var_cnt; i++)
286     {
287       struct variable *var = dict_get_var (dict, i);
288       const char *name = var_get_name (var);
289       enum val_type type = var_get_type (var);
290       int width = var_get_width (var);
291       enum measure measure = var_get_measure (var);
292       enum alignment alignment = var_get_alignment (var);
293       const struct fmt_spec *format = var_get_print_format (var);
294
295       if (var_has_missing_values (var))
296         {
297           const struct missing_values *mv = var_get_missing_values (var);
298           size_t j;
299
300           syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
301           for (j = 0; j < mv_n_values (mv); j++)
302             {
303               union value value;
304               if (j)
305                 ds_put_cstr (s, ", ");
306               mv_get_value (mv, &value, j);
307               syntax_gen_value (s, &value, width, format);
308             }
309
310           if (mv_has_range (mv))
311             {
312               double low, high;
313               if (mv_has_value (mv))
314                 ds_put_cstr (s, ", ");
315               mv_get_range (mv, &low, &high);
316               syntax_gen_num_range (s, low, high, format);
317             }
318           ds_put_cstr (s, ").\n");
319         }
320       if (var_has_value_labels (var))
321         {
322           const struct val_labs *vls = var_get_value_labels (var);
323           struct val_labs_iterator *iter;
324           struct val_lab *vl;
325
326           syntax_gen_pspp (s, "VALUE LABELS %ss", name);
327           for (vl = val_labs_first_sorted (vls, &iter); vl != NULL;
328                vl = val_labs_next (vls, &iter))
329             {
330               ds_put_cstr (s, "\n  ");
331               syntax_gen_value (s, &vl->value, width, format);
332               ds_put_char (s, ' ');
333               syntax_gen_string (s, ss_cstr (vl->label));
334             }
335           ds_put_cstr (s, ".\n");
336         }
337       if (var_has_label (var))
338         syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
339                          name, var_get_label (var));
340       if (measure != var_default_measure (type))
341         syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
342                          name,
343                          (measure == MEASURE_NOMINAL ? "NOMINAL"
344                           : measure == MEASURE_ORDINAL ? "ORDINAL"
345                           : "SCALE"));
346       if (alignment != var_default_alignment (type))
347         syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
348                          name,
349                          (alignment == ALIGN_LEFT ? "LEFT"
350                           : alignment == ALIGN_CENTRE ? "CENTER"
351                           : "RIGHT"));
352       if (var_get_display_width (var) != var_default_display_width (width))
353         syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
354                          name, var_get_display_width (var));
355     }
356 }
357
358 /* Generates and returns PSPP syntax to execute the import
359    operation described by IA.  The caller must free the syntax
360    with free(). */
361 static char *
362 generate_syntax (const struct import_assistant *ia)
363 {
364   struct string s = DS_EMPTY_INITIALIZER;
365   size_t var_cnt;
366   size_t i;
367
368   syntax_gen_pspp (&s,
369                    "GET DATA\n"
370                    "  /TYPE=TXT\n"
371                    "  /FILE=%sq\n",
372                    ia->file.file_name);
373   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
374                                       ia->intro.n_cases_button)))
375     ds_put_format (&s, "  /IMPORTCASES=FIRST %d\n",
376                    gtk_spin_button_get_value_as_int (
377                      GTK_SPIN_BUTTON (ia->intro.n_cases_spin)));
378   else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
379                                            ia->intro.percent_button)))
380     ds_put_format (&s, "  /IMPORTCASES=PERCENT %d\n",
381                    gtk_spin_button_get_value_as_int (
382                      GTK_SPIN_BUTTON (ia->intro.percent_spin)));
383   else
384     ds_put_cstr (&s, "  /IMPORTCASES=ALL\n");
385   ds_put_cstr (&s,
386                "  /ARRANGEMENT=DELIMITED\n"
387                "  /DELCASE=LINE\n");
388   if (ia->first_line.skip_lines > 0)
389     ds_put_format (&s, "  /FIRSTCASE=%d\n", ia->first_line.skip_lines + 1);
390   ds_put_cstr (&s, "  /DELIMITERS=\"");
391   if (ds_find_char (&ia->separators.separators, '\t') != SIZE_MAX)
392     ds_put_cstr (&s, "\\t");
393   if (ds_find_char (&ia->separators.separators, '\\') != SIZE_MAX)
394     ds_put_cstr (&s, "\\\\");
395   for (i = 0; i < ds_length (&ia->separators.separators); i++)
396     {
397       char c = ds_at (&ia->separators.separators, i);
398       if (c == '"')
399         ds_put_cstr (&s, "\"\"");
400       else if (c != '\t' && c != '\\')
401         ds_put_char (&s, c);
402     }
403   ds_put_cstr (&s, "\"\n");
404   if (!ds_is_empty (&ia->separators.quotes))
405     syntax_gen_pspp (&s, "  /QUALIFIER=%sq\n", ds_cstr (&ia->separators.quotes));
406   if (!ds_is_empty (&ia->separators.quotes) && ia->separators.escape)
407     ds_put_cstr (&s, "  /ESCAPE\n");
408   ds_put_cstr (&s, "  /VARIABLES=\n");
409
410   var_cnt = dict_get_var_cnt (ia->formats.dict);
411   for (i = 0; i < var_cnt; i++)
412     {
413       struct variable *var = dict_get_var (ia->formats.dict, i);
414       char format_string[FMT_STRING_LEN_MAX + 1];
415       fmt_to_string (var_get_print_format (var), format_string);
416       ds_put_format (&s, "    %s %s%s\n",
417                      var_get_name (var), format_string,
418                      i == var_cnt - 1 ? "." : "");
419     }
420
421   apply_dict (ia->formats.dict, &s);
422
423   return ds_cstr (&s);
424 }
425 \f
426 /* Choosing a file and reading it. */
427
428 static char *choose_file (GtkWindow *parent_window);
429
430 /* Obtains the file to import from the user and initializes IA's
431    file substructure.  PARENT_WINDOW must be the window to use
432    as the file chooser window's parent.
433
434    Returns true if successful, false if the file name could not
435    be obtained or the file could not be read. */
436 static bool
437 init_file (struct import_assistant *ia, GtkWindow *parent_window)
438 {
439   struct file *file = &ia->file;
440   enum { MAX_PREVIEW_LINES = 1000 }; /* Max number of lines to read. */
441   enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
442   FILE *stream;
443
444   file->file_name = choose_file (parent_window);
445   if (file->file_name == NULL)
446     return false;
447
448   stream = fopen (file->file_name, "r");
449   if (stream == NULL)
450     {
451       msg (ME, _("Could not open \"%s\": %s"),
452            file->file_name, strerror (errno));
453       return false;
454     }
455
456   file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines);
457   for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++)
458     {
459       struct string *line = &file->lines[file->line_cnt];
460
461       ds_init_empty (line);
462       if (!ds_read_line (line, stream, MAX_LINE_LEN))
463         {
464           if (feof (stream))
465             break;
466           else if (ferror (stream))
467             msg (ME, _("Error reading \"%s\": %s"),
468                  file->file_name, strerror (errno));
469           else
470             msg (ME, _("Failed to read \"%s\", because it contains a line "
471                        "over %d bytes long and therefore appears not to be "
472                        "a text file."),
473                  file->file_name, MAX_LINE_LEN);
474           fclose (stream);
475           destroy_file (ia);
476           return false;
477         }
478       ds_chomp (line, '\n');
479       ds_chomp (line, '\r');
480     }
481
482   if (file->line_cnt == 0)
483     {
484       msg (ME, _("\"%s\" is empty."), file->file_name);
485       fclose (stream);
486       destroy_file (ia);
487       return false;
488     }
489
490   /* Estimate the number of lines in the file. */
491   if (file->line_cnt < MAX_PREVIEW_LINES)
492     file->total_lines = file->line_cnt;
493   else
494     {
495       struct stat s;
496       off_t position = ftello (stream);
497       if (fstat (fileno (stream), &s) == 0 && position > 0)
498         file->total_lines = (double) file->line_cnt / position * s.st_size;
499       else
500         file->total_lines = 0;
501     }
502
503   return true;
504 }
505
506 /* Frees IA's file substructure. */
507 static void
508 destroy_file (struct import_assistant *ia)
509 {
510   struct file *f = &ia->file;
511   size_t i;
512
513   for (i = 0; i < f->line_cnt; i++)
514     ds_destroy (&f->lines[i]);
515   free (f->lines);
516   g_free (f->file_name);
517 }
518
519 /* Obtains the file to read from the user and returns the name of
520    the file as a string that must be freed with g_free if
521    successful, otherwise a null pointer.  PARENT_WINDOW must be
522    the window to use as the file chooser window's parent. */
523 static char *
524 choose_file (GtkWindow *parent_window)
525 {
526   GtkWidget *dialog;
527   char *file_name;
528
529   dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
530                                         parent_window,
531                                         GTK_FILE_CHOOSER_ACTION_OPEN,
532                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
533                                         GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
534                                         NULL);
535
536   switch (gtk_dialog_run (GTK_DIALOG (dialog)))
537     {
538     case GTK_RESPONSE_ACCEPT:
539       file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
540       break;
541     default:
542       file_name = NULL;
543       break;
544     }
545   gtk_widget_destroy (dialog);
546
547   return file_name;
548 }
549 \f
550 /* Assistant. */
551
552 static void close_assistant (struct import_assistant *, int response);
553 static void on_prepare (GtkAssistant *assistant, GtkWidget *page,
554                         struct import_assistant *);
555 static void on_cancel (GtkAssistant *assistant, struct import_assistant *);
556 static void on_close (GtkAssistant *assistant, struct import_assistant *);
557 static void on_paste (GtkButton *button, struct import_assistant *);
558 static void on_reset (GtkButton *button, struct import_assistant *);
559 static void close_assistant (struct import_assistant *, int response);
560
561 /* Initializes IA's asst substructure.  PARENT_WINDOW must be the
562    window to use as the assistant window's parent.  */
563 static void
564 init_assistant (struct import_assistant *ia, GtkWindow *parent_window)
565 {
566   struct assistant *a = &ia->asst;
567
568   a->xml = XML_NEW ("text-data-import.glade");
569   a->assistant = GTK_ASSISTANT (gtk_assistant_new ());
570   g_signal_connect (a->assistant, "prepare", G_CALLBACK (on_prepare), ia);
571   g_signal_connect (a->assistant, "cancel", G_CALLBACK (on_cancel), ia);
572   g_signal_connect (a->assistant, "close", G_CALLBACK (on_close), ia);
573   a->paste_button = gtk_button_new_from_stock (GTK_STOCK_PASTE);
574   gtk_assistant_add_action_widget (a->assistant, a->paste_button);
575   g_signal_connect (a->paste_button, "clicked", G_CALLBACK (on_paste), ia);
576   a->reset_button = gtk_button_new_from_stock ("pspp-stock-reset");
577   gtk_assistant_add_action_widget (a->assistant, a->reset_button);
578   g_signal_connect (a->reset_button, "clicked", G_CALLBACK (on_reset), ia);
579   gtk_window_set_title (GTK_WINDOW (a->assistant),
580                         _("Importing Delimited Text Data"));
581   gtk_window_set_transient_for (GTK_WINDOW (a->assistant), parent_window);
582
583   a->prop_renderer = gtk_cell_renderer_text_new ();
584   g_object_ref_sink (a->prop_renderer);
585   a->fixed_renderer = gtk_cell_renderer_text_new ();
586   g_object_ref_sink (a->fixed_renderer);
587   g_object_set (G_OBJECT (a->fixed_renderer),
588                 "family", "Monospace",
589                 (void *) NULL);
590 }
591
592 /* Frees IA's asst substructure. */
593 static void
594 destroy_assistant (struct import_assistant *ia)
595 {
596   struct assistant *a = &ia->asst;
597
598   g_object_unref (a->prop_renderer);
599   g_object_unref (a->fixed_renderer);
600   g_object_unref (a->xml);
601 }
602
603 /* Appends a page of the given TYPE, with PAGE as its content, to
604    the GtkAssistant encapsulated by IA.  Returns the GtkWidget
605    that represents the page. */
606 static GtkWidget *
607 add_page_to_assistant (struct import_assistant *ia,
608                        GtkWidget *page, GtkAssistantPageType type)
609 {
610   const char *title;
611   char *title_copy;
612   GtkWidget *content;
613
614   title = gtk_window_get_title (GTK_WINDOW (page));
615   title_copy = xstrdup (title ? title : "");
616
617   content = gtk_bin_get_child (GTK_BIN (page));
618   assert (content);
619   g_object_ref (content);
620   gtk_container_remove (GTK_CONTAINER (page), content);
621
622   gtk_widget_destroy (page);
623
624   gtk_assistant_append_page (ia->asst.assistant, content);
625   gtk_assistant_set_page_type (ia->asst.assistant, content, type);
626   gtk_assistant_set_page_title (ia->asst.assistant, content, title_copy);
627
628   free (title_copy);
629
630   return content;
631 }
632
633 /* Called just before PAGE is displayed as the current page of
634    ASSISTANT, this updates IA content according to the new
635    page. */
636 static void
637 on_prepare (GtkAssistant *assistant, GtkWidget *page,
638             struct import_assistant *ia)
639 {
640   if (page == ia->separators.page)
641     prepare_separators_page (ia);
642   else if (page == ia->formats.page)
643     prepare_formats_page (ia);
644
645   gtk_widget_show (ia->asst.reset_button);
646   if (page == ia->formats.page)
647     gtk_widget_show (ia->asst.paste_button);
648   else
649     gtk_widget_hide (ia->asst.paste_button);
650
651   /* Make the user visit each page in the assistant once.  Seems
652      like a reasonable user interface, plus visiting the final
653      page is what constructs the dictionary. */
654   gtk_assistant_set_page_complete (assistant, page, true);
655 }
656
657 /* Called when the Cancel button in the assistant is clicked. */
658 static void
659 on_cancel (GtkAssistant *assistant, struct import_assistant *ia)
660 {
661   close_assistant (ia, GTK_RESPONSE_CANCEL);
662 }
663
664 /* Called when the Apply button on the last page of the assistant
665    is clicked. */
666 static void
667 on_close (GtkAssistant *assistant, struct import_assistant *ia)
668 {
669   close_assistant (ia, GTK_RESPONSE_APPLY);
670 }
671
672 /* Called when the Paste button on the last page of the assistant
673    is clicked. */
674 static void
675 on_paste (GtkButton *button, struct import_assistant *ia)
676 {
677   close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
678 }
679
680 /* Called when the Reset button is clicked. */
681 static void
682 on_reset (GtkButton *button, struct import_assistant *ia)
683 {
684   gint page_num = gtk_assistant_get_current_page (ia->asst.assistant);
685   GtkWidget *page = gtk_assistant_get_nth_page (ia->asst.assistant, page_num);
686
687   if (page == ia->intro.page)
688     reset_intro_page (ia);
689   else if (page == ia->first_line.page)
690     reset_first_line_page (ia);
691   else if (page == ia->separators.page)
692     reset_separators_page (ia);
693   else if (page == ia->formats.page)
694     reset_formats_page (ia);
695 }
696
697 /* Causes the assistant to close, returning RESPONSE for
698    interpretation by text_data_import_assistant. */
699 static void
700 close_assistant (struct import_assistant *ia, int response)
701 {
702   ia->asst.response = response;
703   g_main_loop_quit (ia->asst.main_loop);
704   gtk_widget_hide (GTK_WIDGET (ia->asst.assistant));
705 }
706 \f
707 /* The "intro" page of the assistant. */
708
709 static void on_intro_amount_changed (GtkToggleButton *button,
710                                      struct import_assistant *);
711
712 /* Initializes IA's intro substructure. */
713 static void
714 init_intro_page (struct import_assistant *ia)
715 {
716   GladeXML *xml = ia->asst.xml;
717   struct intro_page *p = &ia->intro;
718   struct string s;
719
720   p->page = add_page_to_assistant (ia, get_widget_assert (xml, "Intro"),
721                                    GTK_ASSISTANT_PAGE_INTRO);
722   p->all_cases_button = get_widget_assert (xml, "import-all-cases");
723   p->n_cases_button = get_widget_assert (xml, "import-n-cases");
724   p->n_cases_spin = get_widget_assert (xml, "n-cases-spin");
725   p->percent_button = get_widget_assert (xml, "import-percent");
726   p->percent_spin = get_widget_assert (xml, "percent-spin");
727   g_signal_connect (p->all_cases_button, "toggled",
728                     G_CALLBACK (on_intro_amount_changed), ia);
729   g_signal_connect (p->n_cases_button, "toggled",
730                     G_CALLBACK (on_intro_amount_changed), ia);
731   g_signal_connect (p->percent_button, "toggled",
732                     G_CALLBACK (on_intro_amount_changed), ia);
733
734   ds_init_empty (&s);
735   ds_put_cstr (&s, _("This assistant will guide you through the process of "
736                      "importing data into PSPP from a text file with one line "
737                      "per case,  in which fields are separated by tabs, "
738                      "commas, or other delimiters.\n\n"));
739   if (ia->file.total_is_exact)
740     ds_put_format (
741       &s, ngettext ("The selected file contains %zu line of text.  ",
742                     "The selected file contains %zu lines of text.  ",
743                     ia->file.line_cnt),
744       ia->file.line_cnt);
745   else if (ia->file.total_lines > 0)
746     {
747       ds_put_format (
748         &s, ngettext (
749           "The selected file contains approximately %lu line of text.  ",
750           "The selected file contains approximately %lu lines of text.  ",
751           ia->file.total_lines),
752         ia->file.total_lines);
753       ds_put_format (
754         &s, ngettext (
755           "Only the first %zu line of the file will be shown for "
756           "preview purposes in the following screens.  ",
757           "Only the first %zu lines of the file will be shown for "
758           "preview purposes in the following screens.  ",
759           ia->file.line_cnt),
760         ia->file.line_cnt);
761     }
762   ds_put_cstr (&s, _("You may choose below how much of the file should "
763                      "actually be imported."));
764   gtk_label_set_text (GTK_LABEL (get_widget_assert (xml, "intro-label")),
765                       ds_cstr (&s));
766   ds_destroy (&s);
767 }
768
769 /* Resets IA's intro page to its initial state. */
770 static void
771 reset_intro_page (struct import_assistant *ia)
772 {
773   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->intro.all_cases_button),
774                                 true);
775 }
776
777 /* Called when one of the radio buttons is clicked. */
778 static void
779 on_intro_amount_changed (GtkToggleButton *button UNUSED,
780                          struct import_assistant *ia)
781 {
782   struct intro_page *p = &ia->intro;
783
784   gtk_widget_set_sensitive (p->n_cases_spin,
785                             gtk_toggle_button_get_active (
786                               GTK_TOGGLE_BUTTON (p->n_cases_button)));
787
788   gtk_widget_set_sensitive (ia->intro.percent_spin,
789                             gtk_toggle_button_get_active (
790                               GTK_TOGGLE_BUTTON (p->percent_button)));
791 }
792 \f
793 /* The "first line" page of the assistant. */
794
795 static GtkTreeView *create_lines_tree_view (GtkContainer *parent_window,
796                                             struct import_assistant *);
797 static void on_first_line_change (GtkTreeSelection *,
798                                   struct import_assistant *);
799 static void on_variable_names_cb_toggle (GtkToggleButton *,
800                                          struct import_assistant *);
801 static void set_first_line (struct import_assistant *);
802 static void get_first_line (struct import_assistant *);
803
804 /* Initializes IA's first_line substructure. */
805 static void
806 init_first_line_page (struct import_assistant *ia)
807 {
808   struct first_line_page *p = &ia->first_line;
809   GladeXML *xml = ia->asst.xml;
810
811   p->page = add_page_to_assistant (ia, get_widget_assert (xml, "FirstLine"),
812                                    GTK_ASSISTANT_PAGE_CONTENT);
813   gtk_widget_destroy (get_widget_assert (xml, "first-line"));
814   p->tree_view = create_lines_tree_view (
815     GTK_CONTAINER (get_widget_assert (xml, "first-line-scroller")), ia);
816   p->variable_names_cb = get_widget_assert (xml, "variable-names");
817   gtk_tree_selection_set_mode (
818     gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
819     GTK_SELECTION_BROWSE);
820   set_first_line (ia);
821   g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
822                     "changed", G_CALLBACK (on_first_line_change), ia);
823   g_signal_connect (p->variable_names_cb, "toggled",
824                     G_CALLBACK (on_variable_names_cb_toggle), ia);
825 }
826
827 /* Resets the first_line page to its initial content. */
828 static void
829 reset_first_line_page (struct import_assistant *ia)
830 {
831   ia->first_line.skip_lines = 0;
832   ia->first_line.variable_names = false;
833   set_first_line (ia);
834 }
835
836 /* Creates and returns a tree view that contains each of the
837    lines in IA's file as a row. */
838 static GtkTreeView *
839 create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia)
840 {
841   GtkTreeView *tree_view;
842   GtkTreeViewColumn *column;
843   size_t max_line_length;
844   gint content_width, header_width;
845   size_t i;
846
847   make_tree_view (ia, 0, &tree_view);
848
849   column = gtk_tree_view_column_new_with_attributes (
850     "Text", ia->asst.fixed_renderer,
851     "text", TEXT_IMPORT_MODEL_COLUMN_LINE,
852     (void *) NULL);
853   gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
854
855   max_line_length = 0;
856   for (i = 0; i < ia->file.line_cnt; i++)
857     {
858       size_t w = ds_length (&ia->file.lines[i]);
859       max_line_length = MAX (max_line_length, w);
860     }
861
862   content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
863                                        max_line_length);
864   header_width = get_string_width (tree_view, ia->asst.prop_renderer, "Text");
865   gtk_tree_view_column_set_fixed_width (column, MAX (content_width,
866                                                      header_width));
867   gtk_tree_view_append_column (tree_view, column);
868
869   gtk_tree_view_set_fixed_height_mode (tree_view, true);
870
871   gtk_container_add (parent, GTK_WIDGET (tree_view));
872   gtk_widget_show (GTK_WIDGET (tree_view));
873
874   return tree_view;
875 }
876
877 /* Called when the line selected in the first_line tree view
878    changes. */
879 static void
880 on_first_line_change (GtkTreeSelection *selection UNUSED,
881                       struct import_assistant *ia)
882 {
883   get_first_line (ia);
884 }
885
886 /* Called when the checkbox that indicates whether variable
887    names are in the row above the first line is toggled. */
888 static void
889 on_variable_names_cb_toggle (GtkToggleButton *variable_names_cb UNUSED,
890                              struct import_assistant *ia)
891 {
892   get_first_line (ia);
893 }
894
895 /* Sets the widgets to match IA's first_line substructure. */
896 static void
897 set_first_line (struct import_assistant *ia)
898 {
899   GtkTreePath *path;
900
901   path = gtk_tree_path_new_from_indices (ia->first_line.skip_lines, -1);
902   gtk_tree_view_set_cursor (GTK_TREE_VIEW (ia->first_line.tree_view),
903                             path, NULL, false);
904   gtk_tree_path_free (path);
905
906   gtk_toggle_button_set_active (
907     GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb),
908     ia->first_line.variable_names);
909   gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
910                             ia->first_line.skip_lines > 0);
911 }
912
913 /* Sets IA's first_line substructure to match the widgets. */
914 static void
915 get_first_line (struct import_assistant *ia)
916 {
917   GtkTreeSelection *selection;
918   GtkTreeIter iter;
919   GtkTreeModel *model;
920
921   selection = gtk_tree_view_get_selection (ia->first_line.tree_view);
922   if (gtk_tree_selection_get_selected (selection, &model, &iter))
923     {
924       GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
925       int row = gtk_tree_path_get_indices (path)[0];
926       gtk_tree_path_free (path);
927
928       ia->first_line.skip_lines = row;
929       ia->first_line.variable_names =
930         (ia->first_line.skip_lines > 0
931          && gtk_toggle_button_get_active (
932            GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb)));
933     }
934   gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
935                             ia->first_line.skip_lines > 0);
936 }
937 \f
938 /* The "separators" page of the assistant. */
939
940 static void revise_fields_preview (struct import_assistant *ia);
941 static void choose_likely_separators (struct import_assistant *ia);
942 static void find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
943                                   const char *targets, const char *def,
944                                   struct string *result);
945 static void clear_fields (struct import_assistant *ia);
946 static void revise_fields_preview (struct import_assistant *);
947 static void set_separators (struct import_assistant *);
948 static void get_separators (struct import_assistant *);
949 static void on_separators_custom_entry_notify (GObject *UNUSED,
950                                                GParamSpec *UNUSED,
951                                                struct import_assistant *);
952 static void on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
953                                             struct import_assistant *);
954 static void on_quote_combo_change (GtkComboBox *combo,
955                                    struct import_assistant *);
956 static void on_quote_cb_toggle (GtkToggleButton *quote_cb,
957                                 struct import_assistant *);
958 static void on_separator_toggle (GtkToggleButton *, struct import_assistant *);
959 static void render_input_cell (GtkTreeViewColumn *tree_column,
960                                GtkCellRenderer *cell,
961                                GtkTreeModel *model, GtkTreeIter *iter,
962                                gpointer ia);
963 static gboolean on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
964                                         gboolean keyboard_mode UNUSED,
965                                         GtkTooltip *tooltip,
966                                         struct import_assistant *);
967
968 /* A common field separator and its identifying name. */
969 struct separator
970   {
971     const char *name;           /* Name (for use with get_widget_assert). */
972     int c;                      /* Separator character. */
973   };
974
975 /* All the separators in the dialog box. */
976 static const struct separator separators[] =
977   {
978     {"space", ' '},
979     {"tab", '\t'},
980     {"bang", '!'},
981     {"colon", ':'},
982     {"comma", ','},
983     {"hyphen", '-'},
984     {"pipe", '|'},
985     {"semicolon", ';'},
986     {"slash", '/'},
987   };
988 #define SEPARATOR_CNT (sizeof separators / sizeof *separators)
989
990 /* Initializes IA's separators substructure. */
991 static void
992 init_separators_page (struct import_assistant *ia)
993 {
994   GladeXML *xml = ia->asst.xml;
995   struct separators_page *p = &ia->separators;
996   size_t i;
997
998   choose_likely_separators (ia);
999
1000   p->page = add_page_to_assistant (ia, get_widget_assert (xml, "Separators"),
1001                                    GTK_ASSISTANT_PAGE_CONTENT);
1002   p->custom_cb = get_widget_assert (xml, "custom-cb");
1003   p->custom_entry = get_widget_assert (xml, "custom-entry");
1004   p->quote_combo = get_widget_assert (xml, "quote-combo");
1005   p->quote_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (p->quote_combo)));
1006   p->quote_cb = get_widget_assert (xml, "quote-cb");
1007   p->escape_cb = get_widget_assert (xml, "escape");
1008
1009   set_separators (ia);
1010   p->fields_tree_view = GTK_TREE_VIEW (get_widget_assert (xml, "fields"));
1011   g_signal_connect (GTK_COMBO_BOX (p->quote_combo), "changed",
1012                     G_CALLBACK (on_quote_combo_change), ia);
1013   g_signal_connect (p->quote_cb, "toggled",
1014                     G_CALLBACK (on_quote_cb_toggle), ia);
1015   g_signal_connect (GTK_ENTRY (p->custom_entry), "notify::text",
1016                     G_CALLBACK (on_separators_custom_entry_notify), ia);
1017   g_signal_connect (p->custom_cb, "toggled",
1018                     G_CALLBACK (on_separators_custom_cb_toggle), ia);
1019   for (i = 0; i < SEPARATOR_CNT; i++)
1020     g_signal_connect (get_widget_assert (xml, separators[i].name),
1021                       "toggled", G_CALLBACK (on_separator_toggle), ia);
1022   g_signal_connect (p->escape_cb, "toggled",
1023                     G_CALLBACK (on_separator_toggle), ia);
1024 }
1025
1026 /* Frees IA's separators substructure. */
1027 static void
1028 destroy_separators_page (struct import_assistant *ia)
1029 {
1030   struct separators_page *s = &ia->separators;
1031
1032   ds_destroy (&s->separators);
1033   ds_destroy (&s->quotes);
1034   clear_fields (ia);
1035 }
1036
1037 /* Called just before the separators page becomes visible in the
1038    assistant. */
1039 static void
1040 prepare_separators_page (struct import_assistant *ia)
1041 {
1042   revise_fields_preview (ia);
1043 }
1044
1045 /* Called when the Reset button is clicked on the separators
1046    page, resets the separators to the defaults. */
1047 static void
1048 reset_separators_page (struct import_assistant *ia)
1049 {
1050   choose_likely_separators (ia);
1051   set_separators (ia);
1052 }
1053
1054 /* Frees and clears the column data in IA's separators
1055    substructure. */
1056 static void
1057 clear_fields (struct import_assistant *ia)
1058 {
1059   struct separators_page *s = &ia->separators;
1060
1061   if (s->column_cnt > 0)
1062     {
1063       struct column *col;
1064       size_t row;
1065
1066       for (row = 0; row < ia->file.line_cnt; row++)
1067         {
1068           const struct string *line = &ia->file.lines[row];
1069           const char *line_start = ds_data (line);
1070           const char *line_end = ds_end (line);
1071
1072           for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1073             {
1074               char *s = ss_data (col->contents[row]);
1075               if (!(s >= line_start && s <= line_end))
1076                 ss_dealloc (&col->contents[row]);
1077             }
1078         }
1079
1080       for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1081         {
1082           free (col->name);
1083           free (col->contents);
1084         }
1085
1086       free (s->columns);
1087       s->columns = NULL;
1088       s->column_cnt = 0;
1089     }
1090 }
1091
1092 /* Breaks the file data in IA into columns based on the
1093    separators set in IA's separators substructure. */
1094 static void
1095 split_fields (struct import_assistant *ia)
1096 {
1097   struct separators_page *s = &ia->separators;
1098   size_t columns_allocated;
1099   bool space_sep;
1100   size_t row;
1101
1102   clear_fields (ia);
1103
1104   /* Is space in the set of separators? */
1105   space_sep = ss_find_char (ds_ss (&s->separators), ' ') != SIZE_MAX;
1106
1107   /* Split all the lines, not just those from
1108      ia->first_line.skip_lines on, so that we split the line that
1109      contains variables names if ia->first_line.variable_names is
1110      true. */
1111   columns_allocated = 0;
1112   for (row = 0; row < ia->file.line_cnt; row++)
1113     {
1114       struct string *line = &ia->file.lines[row];
1115       struct substring text = ds_ss (line);
1116       size_t column_idx;
1117
1118       for (column_idx = 0; ; column_idx++)
1119         {
1120           struct substring field;
1121           struct column *column;
1122
1123           if (space_sep)
1124             ss_ltrim (&text, ss_cstr (" "));
1125           if (ss_is_empty (text))
1126             {
1127               if (column_idx != 0)
1128                 break;
1129               field = text;
1130             }
1131           else if (!ds_is_empty (&s->quotes)
1132                    && ds_find_char (&s->quotes, text.string[0]) != SIZE_MAX)
1133             {
1134               int quote = ss_get_char (&text);
1135               if (!s->escape)
1136                 ss_get_until (&text, quote, &field);
1137               else
1138                 {
1139                   struct string s;
1140                   int c;
1141
1142                   ds_init_empty (&s);
1143                   while ((c = ss_get_char (&text)) != EOF)
1144                     if (c != quote)
1145                       ds_put_char (&s, c);
1146                     else if (ss_match_char (&text, quote))
1147                       ds_put_char (&s, quote);
1148                     else
1149                       break;
1150                   field = ds_ss (&s);
1151                 }
1152             }
1153           else
1154             ss_get_chars (&text, ss_cspan (text, ds_ss (&s->separators)),
1155                           &field);
1156
1157           if (column_idx >= s->column_cnt)
1158             {
1159               struct column *column;
1160
1161               if (s->column_cnt >= columns_allocated)
1162                 s->columns = x2nrealloc (s->columns, &columns_allocated,
1163                                          sizeof *s->columns);
1164               column = &s->columns[s->column_cnt++];
1165               column->name = NULL;
1166               column->width = 0;
1167               column->contents = xcalloc (ia->file.line_cnt,
1168                                           sizeof *column->contents);
1169             }
1170           column = &s->columns[column_idx];
1171           column->contents[row] = field;
1172           if (ss_length (field) > column->width)
1173             column->width = ss_length (field);
1174
1175           if (space_sep)
1176             ss_ltrim (&text, ss_cstr (" "));
1177           if (ss_is_empty (text))
1178             break;
1179           if (ss_find_char (ds_ss (&s->separators), ss_first (text))
1180               != SIZE_MAX)
1181             ss_advance (&text, 1);
1182         }
1183     }
1184 }
1185
1186 /* Chooses a name for each column on the separators page */
1187 static void
1188 choose_column_names (struct import_assistant *ia)
1189 {
1190   const struct first_line_page *f = &ia->first_line;
1191   struct separators_page *s = &ia->separators;
1192   struct dictionary *dict;
1193   unsigned long int generated_name_count = 0;
1194   struct column *col;
1195   size_t name_row;
1196
1197   dict = dict_create ();
1198   name_row = f->variable_names && f->skip_lines ? f->skip_lines : 0;
1199   for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1200     {
1201       char name[VAR_NAME_LEN + 1];
1202       char *hint;
1203
1204       hint = name_row ? ss_xstrdup (col->contents[name_row - 1]) : NULL;
1205       if (!dict_make_unique_var_name (dict, hint, &generated_name_count, name))
1206         NOT_REACHED ();
1207       free (hint);
1208
1209       col->name = xstrdup (name);
1210       dict_create_var_assert (dict, name, 0);
1211     }
1212   dict_destroy (dict);
1213 }
1214
1215 /* Picks the most likely separator and quote characters based on
1216    IA's file data. */
1217 static void
1218 choose_likely_separators (struct import_assistant *ia)
1219 {
1220   unsigned long int histogram[UCHAR_MAX + 1] = { 0 };
1221   size_t row;
1222
1223   /* Construct a histogram of all the characters used in the
1224      file. */
1225   for (row = 0; row < ia->file.line_cnt; row++)
1226     {
1227       struct substring line = ds_ss (&ia->file.lines[row]);
1228       size_t length = ss_length (line);
1229       size_t i;
1230       for (i = 0; i < length; i++)
1231         histogram[(unsigned char) line.string[i]]++;
1232     }
1233
1234   find_commonest_chars (histogram, "\"'", "", &ia->separators.quotes);
1235   find_commonest_chars (histogram, ",;:/|!\t-", ",",
1236                         &ia->separators.separators);
1237   ia->separators.escape = true;
1238 }
1239
1240 /* Chooses the most common character among those in TARGETS,
1241    based on the frequency data in HISTOGRAM, and stores it in
1242    RESULT.  If there is a tie for the most common character among
1243    those in TARGETS, the earliest character is chosen.  If none
1244    of the TARGETS appear at all, then DEF is used as a
1245    fallback. */
1246 static void
1247 find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
1248                       const char *targets, const char *def,
1249                       struct string *result)
1250 {
1251   unsigned char max = 0;
1252   unsigned long int max_count = 0;
1253
1254   for (; *targets != '\0'; targets++)
1255     {
1256       unsigned char c = *targets;
1257       unsigned long int count = histogram[c];
1258       if (count > max_count)
1259         {
1260           max = c;
1261           max_count = count;
1262         }
1263     }
1264   if (max_count > 0)
1265     {
1266       ds_clear (result);
1267       ds_put_char (result, max);
1268     }
1269   else
1270     ds_assign_cstr (result, def);
1271 }
1272
1273 /* Revises the contents of the fields tree view based on the
1274    currently chosen set of separators. */
1275 static void
1276 revise_fields_preview (struct import_assistant *ia)
1277 {
1278   GtkWidget *w;
1279   w = GTK_WIDGET (ia->separators.fields_tree_view);
1280   gtk_widget_destroy (w);
1281   get_separators (ia);
1282   split_fields (ia);
1283   choose_column_names (ia);
1284   ia->separators.fields_tree_view = create_data_tree_view (
1285     true,
1286     GTK_CONTAINER (get_widget_assert (ia->asst.xml, "fields-scroller")),
1287     ia);
1288 }
1289
1290 /* Sets the widgets to match IA's separators substructure. */
1291 static void
1292 set_separators (struct import_assistant *ia)
1293 {
1294   struct separators_page *s = &ia->separators;
1295   unsigned int seps;
1296   struct string custom;
1297   bool any_custom;
1298   bool any_quotes;
1299   size_t i;
1300
1301   ds_init_empty (&custom);
1302   seps = 0;
1303   for (i = 0; i < ds_length (&s->separators); i++)
1304     {
1305       unsigned char c = ds_at (&s->separators, i);
1306       int j;
1307
1308       for (j = 0; j < SEPARATOR_CNT; j++)
1309         {
1310           const struct separator *s = &separators[j];
1311           if (s->c == c)
1312             {
1313               seps += 1u << j;
1314               goto next;
1315             }
1316         }
1317
1318       ds_put_char (&custom, c);
1319     next:;
1320     }
1321
1322   for (i = 0; i < SEPARATOR_CNT; i++)
1323     {
1324       const struct separator *s = &separators[i];
1325       GtkWidget *button = get_widget_assert (ia->asst.xml, s->name);
1326       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
1327                                     (seps & (1u << i)) != 0);
1328     }
1329   any_custom = !ds_is_empty (&custom);
1330   gtk_entry_set_text (GTK_ENTRY (s->custom_entry), ds_cstr (&custom));
1331   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->custom_cb),
1332                                 any_custom);
1333   gtk_widget_set_sensitive (s->custom_entry, any_custom);
1334   ds_destroy (&custom);
1335
1336   any_quotes = !ds_is_empty (&s->quotes);
1337   gtk_entry_set_text (s->quote_entry,
1338                       any_quotes ? ds_cstr (&s->quotes) : "\"");
1339   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->quote_cb),
1340                                 any_quotes);
1341   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->escape_cb),
1342                                 s->escape);
1343   gtk_widget_set_sensitive (s->quote_combo, any_quotes);
1344   gtk_widget_set_sensitive (s->escape_cb, any_quotes);
1345 }
1346
1347 /* Sets IA's separators substructure to match the widgets. */
1348 static void
1349 get_separators (struct import_assistant *ia)
1350 {
1351   struct separators_page *s = &ia->separators;
1352   int i;
1353
1354   ds_clear (&s->separators);
1355   for (i = 0; i < SEPARATOR_CNT; i++)
1356     {
1357       const struct separator *sep = &separators[i];
1358       GtkWidget *button = get_widget_assert (ia->asst.xml, sep->name);
1359       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1360         ds_put_char (&s->separators, sep->c);
1361     }
1362
1363   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->custom_cb)))
1364     ds_put_cstr (&s->separators,
1365                  gtk_entry_get_text (GTK_ENTRY (s->custom_entry)));
1366
1367   if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->quote_cb)))
1368     {
1369       gchar *text = gtk_combo_box_get_active_text (
1370                       GTK_COMBO_BOX (s->quote_combo));
1371       ds_assign_cstr (&s->quotes, text);
1372       g_free (text);
1373     }
1374   else
1375     ds_clear (&s->quotes);
1376   s->escape = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->escape_cb));
1377 }
1378
1379 /* Called when the user changes the entry field for custom
1380    separators. */
1381 static void
1382 on_separators_custom_entry_notify (GObject *gobject UNUSED,
1383                                    GParamSpec *arg1 UNUSED,
1384                                    struct import_assistant *ia)
1385 {
1386   revise_fields_preview (ia);
1387 }
1388
1389 /* Called when the user toggles the checkbox that enables custom
1390    separators. */
1391 static void
1392 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1393                                 struct import_assistant *ia)
1394 {
1395   bool is_active = gtk_toggle_button_get_active (custom_cb);
1396   gtk_widget_set_sensitive (ia->separators.custom_entry, is_active);
1397   revise_fields_preview (ia);
1398 }
1399
1400 /* Called when the user changes the selection in the combo box
1401    that selects a quote character. */
1402 static void
1403 on_quote_combo_change (GtkComboBox *combo, struct import_assistant *ia)
1404 {
1405   revise_fields_preview (ia);
1406 }
1407
1408 /* Called when the user toggles the checkbox that enables
1409    quoting. */
1410 static void
1411 on_quote_cb_toggle (GtkToggleButton *quote_cb, struct import_assistant *ia)
1412 {
1413   bool is_active = gtk_toggle_button_get_active (quote_cb);
1414   gtk_widget_set_sensitive (ia->separators.quote_combo, is_active);
1415   gtk_widget_set_sensitive (ia->separators.escape_cb, is_active);
1416   revise_fields_preview (ia);
1417 }
1418
1419 /* Called when the user toggles one of the separators
1420    checkboxes. */
1421 static void
1422 on_separator_toggle (GtkToggleButton *toggle UNUSED,
1423                      struct import_assistant *ia)
1424 {
1425   revise_fields_preview (ia);
1426 }
1427
1428 /* Called to render one of the cells in the fields preview tree
1429    view. */
1430 static void
1431 render_input_cell (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1432                    GtkTreeModel *model, GtkTreeIter *iter,
1433                    gpointer ia_)
1434 {
1435   struct import_assistant *ia = ia_;
1436   struct substring field;
1437   size_t row;
1438   gint column;
1439
1440   column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1441                                                "column-number"));
1442   row = text_import_model_iter_to_row (iter) + ia->first_line.skip_lines;
1443   field = ia->separators.columns[column].contents[row];
1444   if (field.string != NULL)
1445     {
1446       GValue text = {0, };
1447       g_value_init (&text, G_TYPE_STRING);
1448       g_value_take_string (&text, ss_xstrdup (field));
1449       g_object_set_property (G_OBJECT (cell), "text", &text);
1450       g_value_unset (&text);
1451       g_object_set (cell, "background-set", FALSE, (void *) NULL);
1452     }
1453   else
1454     g_object_set (cell,
1455                   "text", "",
1456                   "background", "red",
1457                   "background-set", TRUE,
1458                   (void *) NULL);
1459 }
1460
1461 /* Called to render a tooltip on one of the cells in the fields
1462    preview tree view. */
1463 static gboolean
1464 on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1465                         gboolean keyboard_mode UNUSED,
1466                         GtkTooltip *tooltip, struct import_assistant *ia)
1467 {
1468   size_t row, column;
1469
1470   if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1471     return FALSE;
1472
1473   if (ia->separators.columns[column].contents[row].string != NULL)
1474     return FALSE;
1475
1476   gtk_tooltip_set_text (tooltip,
1477                         _("This input line has too few separators "
1478                           "to fill in this field."));
1479   return TRUE;
1480 }
1481 \f
1482 /* The "formats" page of the assistant. */
1483
1484 static void on_variable_change (PsppireDict *dict, int idx,
1485                                 struct import_assistant *);
1486 static void clear_modified_vars (struct import_assistant *);
1487
1488 /* Initializes IA's formats substructure. */
1489 static void
1490 init_formats_page (struct import_assistant *ia)
1491 {
1492   GladeXML *xml = ia->asst.xml;
1493   struct formats_page *p = &ia->formats;
1494
1495   p->page = add_page_to_assistant (ia, get_widget_assert (xml, "Formats"),
1496                                    GTK_ASSISTANT_PAGE_CONFIRM);
1497   p->data_tree_view = GTK_TREE_VIEW (get_widget_assert (xml, "data"));
1498   p->modified_vars = NULL;
1499   p->modified_var_cnt = 0;
1500 }
1501
1502 /* Frees IA's formats substructure. */
1503 static void
1504 destroy_formats_page (struct import_assistant *ia)
1505 {
1506   struct formats_page *p = &ia->formats;
1507
1508   if (p->psppire_dict != NULL)
1509     {
1510       /* This destroys p->dict also. */
1511       g_object_unref (p->psppire_dict);
1512     }
1513   clear_modified_vars (ia);
1514 }
1515
1516 /* Called just before the formats page of the assistant is
1517    displayed. */
1518 static void
1519 prepare_formats_page (struct import_assistant *ia)
1520 {
1521   struct dictionary *dict;
1522   PsppireDict *psppire_dict;
1523   PsppireVarStore *var_store;
1524   GtkBin *vars_scroller;
1525   GtkWidget *old_var_sheet;
1526   PsppireVarSheet *var_sheet;
1527   struct separators_page *s = &ia->separators;
1528   struct formats_page *p = &ia->formats;
1529   struct fmt_guesser *fg;
1530   unsigned long int number = 0;
1531   size_t column_idx;
1532
1533   dict = dict_create ();
1534   fg = fmt_guesser_create ();
1535   for (column_idx = 0; column_idx < s->column_cnt; column_idx++)
1536     {
1537       struct variable *modified_var;
1538       char name[VAR_NAME_LEN + 1];
1539
1540       modified_var = (column_idx < p->modified_var_cnt
1541                       ? p->modified_vars[column_idx] : NULL);
1542       if (modified_var == NULL)
1543         {
1544           struct column *column = &s->columns[column_idx];
1545           struct variable *var;
1546           struct fmt_spec format;
1547           size_t row;
1548
1549           /* Choose variable name. */
1550           if (!dict_make_unique_var_name (dict, column->name, &number, name))
1551             NOT_REACHED ();
1552
1553           /* Choose variable format. */
1554           fmt_guesser_clear (fg);
1555           for (row = ia->first_line.skip_lines; row < ia->file.line_cnt; row++)
1556             fmt_guesser_add (fg, column->contents[row]);
1557           fmt_guesser_guess (fg, &format);
1558           fmt_fix_input (&format);
1559
1560           /* Create variable. */
1561           var = dict_create_var_assert (dict, name, fmt_var_width (&format));
1562           var_set_both_formats (var, &format);
1563         }
1564       else
1565         {
1566           if (!dict_make_unique_var_name (dict, var_get_name (modified_var),
1567                                           &number, name))
1568             NOT_REACHED ();
1569           dict_clone_var_assert (dict, modified_var, name);
1570         }
1571     }
1572   fmt_guesser_destroy (fg);
1573
1574   psppire_dict = psppire_dict_new_from_dict (dict);
1575   g_signal_connect (psppire_dict, "variable_changed",
1576                     G_CALLBACK (on_variable_change), ia);
1577   ia->formats.dict = dict;
1578   ia->formats.psppire_dict = psppire_dict;
1579
1580   /* XXX: PsppireVarStore doesn't hold a reference to
1581      psppire_dict for now, but it should.  After it does, we
1582      should g_object_ref the psppire_dict here, since we also
1583      hold a reference via ia->formats.dict. */
1584   var_store = psppire_var_store_new (psppire_dict);
1585   g_object_set (var_store,
1586                 "trailing-rows", 1,
1587                 "format-type", PSPPIRE_VAR_STORE_INPUT_FORMATS,
1588                 (void *) NULL);
1589   var_sheet = PSPPIRE_VAR_SHEET (psppire_var_sheet_new ());
1590   g_object_set (var_sheet,
1591                 "row-geometry", var_store,
1592                 "model", var_store,
1593                 "may-create-vars", FALSE,
1594                 (void *) NULL);
1595
1596   vars_scroller = GTK_BIN (get_widget_assert (ia->asst.xml, "vars-scroller"));
1597   old_var_sheet = gtk_bin_get_child (vars_scroller);
1598   if (old_var_sheet != NULL)
1599     gtk_widget_destroy (old_var_sheet);
1600   gtk_container_add (GTK_CONTAINER (vars_scroller), GTK_WIDGET (var_sheet));
1601   gtk_widget_show (GTK_WIDGET (var_sheet));
1602
1603   gtk_widget_destroy (GTK_WIDGET (ia->formats.data_tree_view));
1604   ia->formats.data_tree_view = create_data_tree_view (
1605     false,
1606     GTK_CONTAINER (get_widget_assert (ia->asst.xml, "data-scroller")),
1607     ia);
1608 }
1609
1610 /* Clears the set of user-modified variables from IA's formats
1611    substructure.  This discards user modifications to variable
1612    formats, thereby causing formats to revert to their
1613    defaults.  */
1614 static void
1615 clear_modified_vars (struct import_assistant *ia)
1616 {
1617   struct formats_page *p = &ia->formats;
1618   size_t i;
1619
1620   for (i = 0; i < p->modified_var_cnt; i++)
1621     var_destroy (p->modified_vars[i]);
1622   free (p->modified_vars);
1623   p->modified_vars = NULL;
1624   p->modified_var_cnt = 0;
1625 }
1626
1627 /* Resets the formats page to its defaults, discarding user
1628    modifications. */
1629 static void
1630 reset_formats_page (struct import_assistant *ia)
1631 {
1632   clear_modified_vars (ia);
1633   prepare_formats_page (ia);
1634 }
1635
1636 /* Called when the user changes one of the variables in the
1637    dictionary. */
1638 static void
1639 on_variable_change (PsppireDict *dict, int dict_idx,
1640                     struct import_assistant *ia)
1641 {
1642   struct formats_page *p = &ia->formats;
1643   GtkTreeView *tv = ia->formats.data_tree_view;
1644   gint column_idx = dict_idx + 1;
1645
1646   /* Remove previous column and replace with new column. */
1647   gtk_tree_view_remove_column (tv, gtk_tree_view_get_column (tv, column_idx));
1648   gtk_tree_view_insert_column (tv, make_data_column (ia, tv, false, dict_idx),
1649                                column_idx);
1650
1651   /* Save a copy of the modified variable in modified_vars, so
1652      that its attributes will be preserved if we back up to the
1653      previous page with the Prev button and then come back
1654      here. */
1655   if (dict_idx >= p->modified_var_cnt)
1656     {
1657       size_t i;
1658       p->modified_vars = xnrealloc (p->modified_vars, dict_idx + 1,
1659                                     sizeof *p->modified_vars);
1660       for (i = 0; i <= dict_idx; i++)
1661         p->modified_vars[i] = NULL;
1662       p->modified_var_cnt = dict_idx + 1;
1663     }
1664   if (p->modified_vars[dict_idx])
1665     var_destroy (p->modified_vars[dict_idx]);
1666   p->modified_vars[dict_idx]
1667     = var_clone (psppire_dict_get_variable (dict, dict_idx));
1668 }
1669
1670 /* Parses the contents of the field at (ROW,COLUMN) according to
1671    its variable format.  If OUTPUTP is non-null, then *OUTPUTP
1672    receives the formatted output for that field (which must be
1673    freed with free).  If TOOLTIPP is non-null, then *TOOLTIPP
1674    receives a message suitable for use in a tooltip, if one is
1675    needed, or a null pointer otherwise.  Returns true if a
1676    tooltip message is needed, otherwise false. */
1677 static bool
1678 parse_field (struct import_assistant *ia,
1679              size_t row, size_t column,
1680              char **outputp, char **tooltipp)
1681 {
1682   struct substring field;
1683   union value *val;
1684   struct variable *var;
1685   const struct fmt_spec *in;
1686   struct fmt_spec out;
1687   char *tooltip;
1688   bool ok;
1689
1690   field = ia->separators.columns[column].contents[row];
1691   var = dict_get_var (ia->formats.dict, column);
1692   val = value_create (var_get_width (var));
1693   in = var_get_print_format (var);
1694   out = fmt_for_output_from_input (in);
1695   tooltip = NULL;
1696   if (field.string != NULL)
1697     {
1698       msg_disable ();
1699       if (!data_in (field, LEGACY_NATIVE, in->type, 0, 0, 0,
1700                     val, var_get_width (var)))
1701         {
1702           char fmt_string[FMT_STRING_LEN_MAX + 1];
1703           fmt_to_string (in, fmt_string);
1704           tooltip = xasprintf (_("Field content \"%.*s\" cannot be parsed in "
1705                                  "format %s."),
1706                                (int) field.length, field.string,
1707                                fmt_string);
1708         }
1709       msg_enable ();
1710     }
1711   else
1712     {
1713       tooltip = xstrdup (_("This input line has too few separators "
1714                            "to fill in this field."));
1715       value_set_missing (val, var_get_width (var));
1716     }
1717   if (outputp != NULL)
1718     {
1719       char *output = xmalloc (out.w + 1);
1720       data_out (val, &out, output);
1721       output[out.w] = '\0';
1722       *outputp = output;
1723     }
1724   free (val);
1725
1726   ok = tooltip == NULL;
1727   if (tooltipp != NULL)
1728     *tooltipp = tooltip;
1729   else
1730     free (tooltip);
1731   return ok;
1732 }
1733
1734 /* Called to render one of the cells in the data preview tree
1735    view. */
1736 static void
1737 render_output_cell (GtkTreeViewColumn *tree_column,
1738                     GtkCellRenderer *cell,
1739                     GtkTreeModel *model,
1740                     GtkTreeIter *iter,
1741                     gpointer ia_)
1742 {
1743   struct import_assistant *ia = ia_;
1744   char *output;
1745   GValue gvalue = { 0, };
1746   bool ok;
1747
1748   ok = parse_field (ia,
1749                     (text_import_model_iter_to_row (iter)
1750                      + ia->first_line.skip_lines),
1751                     GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1752                                                         "column-number")),
1753                     &output, NULL);
1754
1755   g_value_init (&gvalue, G_TYPE_STRING);
1756   g_value_take_string (&gvalue, output);
1757   g_object_set_property (G_OBJECT (cell), "text", &gvalue);
1758   g_value_unset (&gvalue);
1759
1760   if (ok)
1761     g_object_set (cell, "background-set", FALSE, (void *) NULL);
1762   else
1763     g_object_set (cell,
1764                   "background", "red",
1765                   "background-set", TRUE,
1766                   (void *) NULL);
1767 }
1768
1769 /* Called to render a tooltip for one of the cells in the data
1770    preview tree view. */
1771 static gboolean
1772 on_query_output_tooltip (GtkWidget *widget, gint wx, gint wy,
1773                         gboolean keyboard_mode UNUSED,
1774                         GtkTooltip *tooltip, struct import_assistant *ia)
1775 {
1776   size_t row, column;
1777   char *text;
1778
1779   if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1780     return FALSE;
1781
1782   if (parse_field (ia, row, column, NULL, &text))
1783     return FALSE;
1784
1785   gtk_tooltip_set_text (tooltip, text);
1786   free (text);
1787   return TRUE;
1788 }
1789 \f
1790 /* Utility functions used by multiple pages of the assistant. */
1791
1792 static gboolean
1793 get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
1794                       const struct import_assistant *ia,
1795                       size_t *row, size_t *column)
1796 {
1797   GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
1798   gint bx, by;
1799   GtkTreePath *path;
1800   GtkTreeIter iter;
1801   GtkTreeViewColumn *tree_column;
1802   GtkTreeModel *tree_model;
1803   bool ok;
1804
1805   gtk_tree_view_convert_widget_to_bin_window_coords (tree_view,
1806                                                      wx, wy, &bx, &by);
1807   if (!gtk_tree_view_get_path_at_pos (tree_view, bx, by,
1808                                       &path, &tree_column, NULL, NULL))
1809     return FALSE;
1810
1811   *column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1812                                                 "column-number"));
1813
1814   tree_model = gtk_tree_view_get_model (tree_view);
1815   ok = gtk_tree_model_get_iter (tree_model, &iter, path);
1816   gtk_tree_path_free (path);
1817   if (!ok)
1818     return FALSE;
1819
1820   *row = text_import_model_iter_to_row (&iter) + ia->first_line.skip_lines;
1821   return TRUE;
1822 }
1823
1824 static void
1825 make_tree_view (const struct import_assistant *ia,
1826                 size_t first_line,
1827                 GtkTreeView **tree_view)
1828 {
1829   GtkTreeModel *model;
1830
1831   *tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
1832   model = GTK_TREE_MODEL (text_import_model_new (
1833                             ia->file.lines + first_line,
1834                             ia->file.line_cnt - first_line, first_line));
1835   gtk_tree_view_set_model (*tree_view, model);
1836
1837   add_line_number_column (ia, *tree_view);
1838 }
1839
1840 static void
1841 add_line_number_column (const struct import_assistant *ia,
1842                         GtkTreeView *treeview)
1843 {
1844   GtkTreeViewColumn *column;
1845
1846   column = gtk_tree_view_column_new_with_attributes (
1847     "Line", ia->asst.prop_renderer,
1848     "text", TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER,
1849     (void *) NULL);
1850   gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
1851   gtk_tree_view_column_set_fixed_width (
1852     column, get_monospace_width (treeview, ia->asst.prop_renderer, 5));
1853   gtk_tree_view_append_column (treeview, column);
1854 }
1855
1856 static gint
1857 get_monospace_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
1858                      size_t char_cnt)
1859 {
1860   struct string s;
1861   gint width;
1862
1863   ds_init_empty (&s);
1864   ds_put_char_multiple (&s, '0', char_cnt);
1865   ds_put_char (&s, ' ');
1866   width = get_string_width (treeview, renderer, ds_cstr (&s));
1867   ds_destroy (&s);
1868
1869   return width;
1870 }
1871
1872 static gint
1873 get_string_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
1874                   const char *string)
1875 {
1876   gint width;
1877   g_object_set (G_OBJECT (renderer), "text", string, (void *) NULL);
1878   gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
1879                               NULL, NULL, NULL, &width, NULL);
1880   return width;
1881 }
1882
1883 static GtkTreeViewColumn *
1884 make_data_column (struct import_assistant *ia, GtkTreeView *tree_view,
1885                   bool input, gint dict_idx)
1886 {
1887   struct variable *var = NULL;
1888   struct column *column = NULL;
1889   char name[(VAR_NAME_LEN * 2) + 1];
1890   size_t char_cnt;
1891   gint content_width, header_width;
1892   GtkTreeViewColumn *tree_column;
1893
1894   if (input)
1895     column = &ia->separators.columns[dict_idx];
1896   else
1897     var = dict_get_var (ia->formats.dict, dict_idx);
1898
1899   escape_underscores (input ? column->name : var_get_name (var), name);
1900   char_cnt = input ? column->width : var_get_print_format (var)->w;
1901   content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
1902                                        char_cnt);
1903   header_width = get_string_width (tree_view, ia->asst.prop_renderer,
1904                                    name);
1905
1906   tree_column = gtk_tree_view_column_new ();
1907   g_object_set_data (G_OBJECT (tree_column), "column-number",
1908                      GINT_TO_POINTER (dict_idx));
1909   gtk_tree_view_column_set_title (tree_column, name);
1910   gtk_tree_view_column_pack_start (tree_column, ia->asst.fixed_renderer,
1911                                    FALSE);
1912   gtk_tree_view_column_set_cell_data_func (
1913     tree_column, ia->asst.fixed_renderer,
1914     input ? render_input_cell : render_output_cell, ia, NULL);
1915   gtk_tree_view_column_set_sizing (tree_column, GTK_TREE_VIEW_COLUMN_FIXED);
1916   gtk_tree_view_column_set_fixed_width (tree_column, MAX (content_width,
1917                                                           header_width));
1918
1919   return tree_column;
1920 }
1921
1922 static GtkTreeView *
1923 create_data_tree_view (bool input, GtkContainer *parent,
1924                        struct import_assistant *ia)
1925 {
1926   GtkTreeView *tree_view;
1927   gint i;
1928
1929   make_tree_view (ia, ia->first_line.skip_lines, &tree_view);
1930   gtk_tree_selection_set_mode (gtk_tree_view_get_selection (tree_view),
1931                                GTK_SELECTION_NONE);
1932
1933   for (i = 0; i < ia->separators.column_cnt; i++)
1934     gtk_tree_view_append_column (tree_view,
1935                                  make_data_column (ia, tree_view, input, i));
1936
1937   g_object_set (G_OBJECT (tree_view), "has-tooltip", TRUE, (void *) NULL);
1938   g_signal_connect (tree_view, "query-tooltip",
1939                     G_CALLBACK (input ? on_query_input_tooltip
1940                                 : on_query_output_tooltip), ia);
1941   gtk_tree_view_set_fixed_height_mode (tree_view, true);
1942
1943   gtk_container_add (parent, GTK_WIDGET (tree_view));
1944   gtk_widget_show (GTK_WIDGET (tree_view));
1945
1946   return tree_view;
1947 }
1948
1949 static void
1950 escape_underscores (const char *in, char *out)
1951 {
1952   for (; *in != '\0'; in++)
1953     {
1954       if (*in == '_')
1955         *out++ = '_';
1956       *out++ = *in;
1957     }
1958   *out = '\0';
1959 }
1960 \f
1961 /* TextImportModel, a GtkTreeModel implementation used by some
1962    pages of the assistant. */
1963
1964 #define G_TYPE_TEXT_IMPORT_MODEL (text_import_model_get_type ())
1965 #define TEXT_IMPORT_MODEL(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), G_TYPE_TEXT_IMPORT_MODEL, TextImportModel))
1966 #define TEXT_IMPORT_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), G_TYPE_TEXT_IMPORT_MODEL, TextImportModelClass))
1967 #define IS_TEXT_IMPORT_MODEL(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), G_TYPE_TEXT_IMPORT_MODEL))
1968 #define IS_TEXT_IMPORT_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), G_TYPE_TEXT_IMPORT_MODEL))
1969 #define TEXT_IMPORT_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_TEXT_IMPORT_MODEL, TextImportModelClass))
1970
1971 /* Random number used in 'stamp' member of GtkTreeIter. */
1972 #define TREE_MODEL_STAMP 0x7efd67d3
1973
1974 struct TextImportModel
1975 {
1976   GObject             parent;
1977   struct string *lines;
1978   size_t line_cnt;
1979   size_t first_line;
1980 };
1981
1982 struct TextImportModelClass
1983 {
1984   GObjectClass parent_class;
1985 };
1986
1987 GType text_import_model_get_type (void);
1988 static void text_import_model_tree_model_init (gpointer iface, gpointer data);
1989
1990 GType
1991 text_import_model_get_type (void)
1992 {
1993   static GType object_type = 0;
1994
1995   if (!object_type)
1996     {
1997       static const GTypeInfo object_info = {
1998         sizeof (TextImportModelClass),
1999         (GBaseInitFunc) NULL,
2000         (GBaseFinalizeFunc) NULL,
2001         NULL,   /* class_init */
2002         NULL,   /* class_finalize */
2003         NULL,   /* class_data */
2004         sizeof (TextImportModel),
2005         0,      /* n_preallocs */
2006         NULL,   /* instance_init */
2007       };
2008
2009       static const GInterfaceInfo tree_model_info = {
2010         text_import_model_tree_model_init,
2011         NULL,
2012         NULL
2013       };
2014
2015       object_type = g_type_register_static (G_TYPE_OBJECT,
2016                                             "TextImportModel",
2017                                             &object_info, 0);
2018
2019       g_type_add_interface_static (object_type, GTK_TYPE_TREE_MODEL,
2020                                    &tree_model_info);
2021
2022
2023     }
2024
2025   return object_type;
2026 }
2027
2028
2029 /* Creates and returns a new TextImportModel that contains the
2030    LINE_CNT lines in LINES.  The lines before FIRST_LINE in LINES
2031    are not part of the model, but they are included in the line
2032    numbers in the TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER column.
2033
2034    The caller retains responsibility for freeing LINES and must
2035    ensure that its lifetime and that of the strings that it
2036    contains exceeds that of the TextImportModel. */
2037 TextImportModel *
2038 text_import_model_new (struct string *lines, size_t line_cnt,
2039                        size_t first_line)
2040 {
2041   TextImportModel *new_text_import_model
2042     = g_object_new (G_TYPE_TEXT_IMPORT_MODEL, NULL);
2043   new_text_import_model->lines = lines;
2044   new_text_import_model->line_cnt = line_cnt;
2045   new_text_import_model->first_line = first_line;
2046   return new_text_import_model;
2047 }
2048
2049
2050 static gboolean
2051 tree_model_iter_has_child  (GtkTreeModel *tree_model,
2052                             GtkTreeIter  *iter)
2053 {
2054   return FALSE;
2055 }
2056
2057 static gboolean
2058 tree_model_iter_parent (GtkTreeModel *tree_model,
2059                         GtkTreeIter *iter,
2060                         GtkTreeIter *child)
2061 {
2062   return TRUE;
2063 }
2064
2065 static GtkTreeModelFlags
2066 tree_model_get_flags (GtkTreeModel *model)
2067 {
2068   g_return_val_if_fail (IS_TEXT_IMPORT_MODEL (model), (GtkTreeModelFlags) 0);
2069
2070   return GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST;
2071 }
2072
2073
2074 static gint
2075 tree_model_n_columns (GtkTreeModel *model)
2076 {
2077   return 2;
2078 }
2079
2080 static GType
2081 tree_model_column_type (GtkTreeModel *model, gint index)
2082 {
2083   return (index == TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER ? G_TYPE_INT
2084           : index == TEXT_IMPORT_MODEL_COLUMN_LINE ? G_TYPE_STRING
2085           : -1);
2086 }
2087
2088 static gboolean
2089 init_iter (TextImportModel *list, gint idx, GtkTreeIter *iter)
2090 {
2091   if (idx < 0 || idx >= list->line_cnt)
2092     {
2093       iter->stamp = 0;
2094       iter->user_data = GINT_TO_POINTER (-1);
2095       return FALSE;
2096     }
2097   else
2098     {
2099       iter->stamp = TREE_MODEL_STAMP;
2100       iter->user_data = GINT_TO_POINTER (idx);
2101       return TRUE;
2102     }
2103 }
2104
2105 static gboolean
2106 tree_model_get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path)
2107 {
2108   gint *indices, depth;
2109
2110   TextImportModel *list = TEXT_IMPORT_MODEL (model);
2111
2112   g_return_val_if_fail (path, FALSE);
2113
2114   indices = gtk_tree_path_get_indices (path);
2115   depth = gtk_tree_path_get_depth (path);
2116
2117   g_return_val_if_fail (depth == 1, FALSE);
2118
2119   return init_iter (list, indices[0], iter);
2120 }
2121
2122
2123 static gboolean
2124 tree_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter)
2125 {
2126   TextImportModel *list = TEXT_IMPORT_MODEL (model);
2127   gint idx;
2128
2129   assert (iter->stamp == TREE_MODEL_STAMP);
2130
2131   idx = GPOINTER_TO_INT (iter->user_data);
2132   return init_iter (list, idx == -1 ? -1 : idx + 1, iter);
2133 }
2134
2135 static GtkTreePath *
2136 tree_model_get_path (GtkTreeModel *model, GtkTreeIter *iter)
2137 {
2138   GtkTreePath *path;
2139
2140   g_return_val_if_fail (iter->stamp == TREE_MODEL_STAMP, FALSE);
2141
2142   path = gtk_tree_path_new ();
2143   gtk_tree_path_append_index (path, GPOINTER_TO_INT (iter->user_data));
2144
2145   return path;
2146 }
2147
2148 static void
2149 tree_model_get_value (GtkTreeModel *model, GtkTreeIter *iter,
2150                       gint column, GValue *value)
2151 {
2152   TextImportModel *list = TEXT_IMPORT_MODEL (model);
2153   gint idx;
2154
2155   g_return_if_fail (iter->stamp == TREE_MODEL_STAMP);
2156
2157   idx = GPOINTER_TO_INT (iter->user_data);
2158   assert (idx >= 0 && idx < list->line_cnt);
2159
2160   if (column == 0)
2161     {
2162       g_value_init (value, G_TYPE_INT);
2163       g_value_set_int (value, idx + list->first_line + 1);
2164     }
2165   else
2166     {
2167       g_value_init (value, G_TYPE_STRING);
2168       g_value_set_static_string (value, ds_cstr (&list->lines[idx]));
2169     }
2170 }
2171
2172 static gboolean
2173 tree_model_iter_children (GtkTreeModel *tree_model,
2174                           GtkTreeIter *iter,
2175                           GtkTreeIter *parent)
2176 {
2177   return FALSE;
2178 }
2179
2180 static gint
2181 tree_model_n_children (GtkTreeModel *model, GtkTreeIter  *iter)
2182 {
2183   TextImportModel *list = TEXT_IMPORT_MODEL (model);
2184
2185   return iter == NULL ? list->line_cnt : 0;
2186 }
2187
2188 static gboolean
2189 tree_model_nth_child (GtkTreeModel *model, GtkTreeIter *iter,
2190                       GtkTreeIter *parent, gint n)
2191 {
2192   TextImportModel *list = TEXT_IMPORT_MODEL (model);
2193   g_return_val_if_fail (IS_TEXT_IMPORT_MODEL (model), FALSE);
2194
2195   if (parent)
2196     return FALSE;
2197   return init_iter (list, n, iter);
2198 }
2199
2200 static void
2201 text_import_model_tree_model_init (gpointer iface_, gpointer data UNUSED)
2202 {
2203   GtkTreeModelIface *iface = (GtkTreeModelIface *) iface_;
2204
2205   iface->get_flags = tree_model_get_flags;
2206   iface->get_n_columns = tree_model_n_columns;
2207   iface->get_column_type = tree_model_column_type;
2208   iface->get_iter = tree_model_get_iter;
2209   iface->iter_next = tree_model_iter_next;
2210   iface->get_path = tree_model_get_path;
2211   iface->get_value = tree_model_get_value;
2212
2213   iface->iter_children = tree_model_iter_children;
2214   iface->iter_has_child = tree_model_iter_has_child;
2215   iface->iter_n_children = tree_model_n_children;
2216   iface->iter_nth_child = tree_model_nth_child;
2217   iface->iter_parent = tree_model_iter_parent;
2218 }
2219
2220 gint
2221 text_import_model_iter_to_row (const GtkTreeIter *iter)
2222 {
2223   assert (iter->stamp == TREE_MODEL_STAMP);
2224   return GPOINTER_TO_INT (iter->user_data);
2225 }