1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2008, 2009, 2010, 2011, 2012 Free Software Foundation
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.
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.
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/>. */
19 #include "ui/gui/text-data-import-dialog.h"
22 #include <gtk-contrib/psppire-sheet.h>
28 #include "data/data-in.h"
29 #include "data/data-out.h"
30 #include "data/format-guesser.h"
31 #include "data/value-labels.h"
32 #include "language/data-io/data-parser.h"
33 #include "language/lexer/lexer.h"
34 #include "libpspp/assertion.h"
35 #include "libpspp/i18n.h"
36 #include "libpspp/message.h"
37 #include "ui/gui/checkbox-treeview.h"
38 #include "ui/gui/dialog-common.h"
39 #include "ui/gui/executor.h"
40 #include "ui/gui/helper.h"
41 #include "ui/gui/builder-wrapper.h"
42 #include "ui/gui/psppire-data-window.h"
43 #include "ui/gui/psppire-dialog.h"
44 #include "ui/gui/psppire-empty-list-store.h"
45 #include "ui/gui/psppire-var-sheet.h"
46 #include "ui/gui/psppire-var-store.h"
47 #include "ui/gui/psppire-scanf.h"
48 #include "ui/syntax-gen.h"
51 #include "gl/intprops.h"
52 #include "gl/xalloc.h"
55 #define _(msgid) gettext (msgid)
56 #define N_(msgid) msgid
58 struct import_assistant;
60 /* The file to be imported. */
63 char *file_name; /* File name. */
64 unsigned long int total_lines; /* Number of lines in file. */
65 bool total_is_exact; /* Is total_lines exact (or an estimate)? */
67 /* The first several lines of the file. */
71 static bool init_file (struct import_assistant *, GtkWindow *parent);
72 static void destroy_file (struct import_assistant *);
74 /* The main body of the GTK+ assistant and related data. */
78 GtkAssistant *assistant;
80 GtkWidget *paste_button;
81 GtkWidget *reset_button;
85 GtkCellRenderer *prop_renderer;
86 GtkCellRenderer *fixed_renderer;
88 static void init_assistant (struct import_assistant *, GtkWindow *);
89 static void destroy_assistant (struct import_assistant *);
90 static GtkWidget *add_page_to_assistant (struct import_assistant *,
92 GtkAssistantPageType);
94 /* The introduction page of the assistant. */
98 GtkWidget *all_cases_button;
99 GtkWidget *n_cases_button;
100 GtkWidget *n_cases_spin;
101 GtkWidget *percent_button;
102 GtkWidget *percent_spin;
104 static void init_intro_page (struct import_assistant *);
105 static void reset_intro_page (struct import_assistant *);
107 /* Page where the user chooses the first line of data. */
108 struct first_line_page
110 int skip_lines; /* Number of initial lines to skip? */
111 bool variable_names; /* Variable names above first line of data? */
114 GtkTreeView *tree_view;
115 GtkWidget *variable_names_cb;
117 static void init_first_line_page (struct import_assistant *);
118 static void reset_first_line_page (struct import_assistant *);
120 /* Page where the user chooses field separators. */
121 struct separators_page
123 /* How to break lines into columns. */
124 struct string separators; /* Field separators. */
125 struct string quotes; /* Quote characters. */
126 bool escape; /* Doubled quotes yield a quote mark? */
128 /* The columns produced thereby. */
129 struct column *columns; /* Information about each column. */
130 size_t column_cnt; /* Number of columns. */
133 GtkWidget *custom_cb;
134 GtkWidget *custom_entry;
136 GtkWidget *quote_combo;
137 GtkEntry *quote_entry;
138 GtkWidget *escape_cb;
139 GtkTreeView *fields_tree_view;
141 /* The columns that the separators divide the data into. */
144 /* Variable name for this column. This is the variable name
145 used on the separators page; it can be overridden by the
146 user on the formats page. */
149 /* Maximum length of any row in this column. */
152 /* Contents of this column: contents[row] is the contents for
155 A null substring indicates a missing column for that row
156 (because the line contains an insufficient number of
159 contents[] elements may be substrings of the lines[]
160 strings that represent the whole lines of the file, to
161 save memory. Other elements are dynamically allocated
162 with ss_alloc_substring. */
163 struct substring *contents;
165 static void init_separators_page (struct import_assistant *);
166 static void destroy_separators_page (struct import_assistant *);
167 static void prepare_separators_page (struct import_assistant *);
168 static void reset_separators_page (struct import_assistant *);
170 /* Page where the user verifies and adjusts input formats. */
173 struct dictionary *dict;
176 GtkTreeView *data_tree_view;
177 PsppireDict *psppire_dict;
178 struct variable **modified_vars;
179 size_t modified_var_cnt;
181 static void init_formats_page (struct import_assistant *);
182 static void destroy_formats_page (struct import_assistant *);
183 static void prepare_formats_page (struct import_assistant *);
184 static void reset_formats_page (struct import_assistant *);
186 struct import_assistant
189 struct assistant asst;
190 struct intro_page intro;
191 struct first_line_page first_line;
192 struct separators_page separators;
193 struct formats_page formats;
196 static void apply_dict (const struct dictionary *, struct string *);
197 static char *generate_syntax (const struct import_assistant *);
199 static gboolean get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
200 const struct import_assistant *,
201 size_t *row, size_t *column);
202 static void make_tree_view (const struct import_assistant *ia,
204 GtkTreeView **tree_view);
205 static void add_line_number_column (const struct import_assistant *,
207 static gint get_monospace_width (GtkTreeView *, GtkCellRenderer *,
209 static gint get_string_width (GtkTreeView *, GtkCellRenderer *,
211 static GtkTreeViewColumn *make_data_column (struct import_assistant *,
212 GtkTreeView *, bool input,
214 static GtkTreeView *create_data_tree_view (bool input, GtkContainer *parent,
215 struct import_assistant *);
216 static char *escape_underscores (const char *in);
217 static void push_watch_cursor (struct import_assistant *);
218 static void pop_watch_cursor (struct import_assistant *);
220 /* Pops up the Text Data Import assistant. */
222 text_data_import_assistant (PsppireDataWindow *dw)
224 GtkWindow *parent_window = GTK_WINDOW (dw);
225 struct import_assistant *ia;
227 ia = xzalloc (sizeof *ia);
228 if (!init_file (ia, parent_window))
234 init_assistant (ia, parent_window);
235 init_intro_page (ia);
236 init_first_line_page (ia);
237 init_separators_page (ia);
238 init_formats_page (ia);
240 gtk_widget_show_all (GTK_WIDGET (ia->asst.assistant));
242 ia->asst.main_loop = g_main_loop_new (NULL, false);
243 g_main_loop_run (ia->asst.main_loop);
244 g_main_loop_unref (ia->asst.main_loop);
246 switch (ia->asst.response)
248 case GTK_RESPONSE_APPLY:
249 free (execute_syntax_string (dw, generate_syntax (ia)));
251 case PSPPIRE_RESPONSE_PASTE:
252 free (paste_syntax_to_window (generate_syntax (ia)));
258 destroy_formats_page (ia);
259 destroy_separators_page (ia);
260 destroy_assistant (ia);
265 /* Emits PSPP syntax to S that applies the dictionary attributes
266 (such as missing values and value labels) of the variables in
269 apply_dict (const struct dictionary *dict, struct string *s)
271 size_t var_cnt = dict_get_var_cnt (dict);
274 for (i = 0; i < var_cnt; i++)
276 struct variable *var = dict_get_var (dict, i);
277 const char *name = var_get_name (var);
278 enum val_type type = var_get_type (var);
279 int width = var_get_width (var);
280 enum measure measure = var_get_measure (var);
281 enum alignment alignment = var_get_alignment (var);
282 const struct fmt_spec *format = var_get_print_format (var);
284 if (var_has_missing_values (var))
286 const struct missing_values *mv = var_get_missing_values (var);
289 syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
290 for (j = 0; j < mv_n_values (mv); j++)
293 ds_put_cstr (s, ", ");
294 syntax_gen_value (s, mv_get_value (mv, j), width, format);
297 if (mv_has_range (mv))
300 if (mv_has_value (mv))
301 ds_put_cstr (s, ", ");
302 mv_get_range (mv, &low, &high);
303 syntax_gen_num_range (s, low, high, format);
305 ds_put_cstr (s, ").\n");
307 if (var_has_value_labels (var))
309 const struct val_labs *vls = var_get_value_labels (var);
310 const struct val_lab **labels = val_labs_sorted (vls);
311 size_t n_labels = val_labs_count (vls);
314 syntax_gen_pspp (s, "VALUE LABELS %ss", name);
315 for (i = 0; i < n_labels; i++)
317 const struct val_lab *vl = labels[i];
318 ds_put_cstr (s, "\n ");
319 syntax_gen_value (s, &vl->value, width, format);
320 ds_put_byte (s, ' ');
321 syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl)));
324 ds_put_cstr (s, ".\n");
326 if (var_has_label (var))
327 syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
328 name, var_get_label (var));
329 if (measure != var_default_measure (type))
330 syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
332 (measure == MEASURE_NOMINAL ? "NOMINAL"
333 : measure == MEASURE_ORDINAL ? "ORDINAL"
335 if (alignment != var_default_alignment (type))
336 syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
338 (alignment == ALIGN_LEFT ? "LEFT"
339 : alignment == ALIGN_CENTRE ? "CENTER"
341 if (var_get_display_width (var) != var_default_display_width (width))
342 syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
343 name, var_get_display_width (var));
347 /* Generates and returns PSPP syntax to execute the import
348 operation described by IA. The caller must free the syntax
351 generate_syntax (const struct import_assistant *ia)
353 struct string s = DS_EMPTY_INITIALIZER;
362 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
363 ia->intro.n_cases_button)))
364 ds_put_format (&s, " /IMPORTCASES=FIRST %d\n",
365 gtk_spin_button_get_value_as_int (
366 GTK_SPIN_BUTTON (ia->intro.n_cases_spin)));
367 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
368 ia->intro.percent_button)))
369 ds_put_format (&s, " /IMPORTCASES=PERCENT %d\n",
370 gtk_spin_button_get_value_as_int (
371 GTK_SPIN_BUTTON (ia->intro.percent_spin)));
373 ds_put_cstr (&s, " /IMPORTCASES=ALL\n");
375 " /ARRANGEMENT=DELIMITED\n"
377 if (ia->first_line.skip_lines > 0)
378 ds_put_format (&s, " /FIRSTCASE=%d\n", ia->first_line.skip_lines + 1);
379 ds_put_cstr (&s, " /DELIMITERS=\"");
380 if (ds_find_byte (&ia->separators.separators, '\t') != SIZE_MAX)
381 ds_put_cstr (&s, "\\t");
382 if (ds_find_byte (&ia->separators.separators, '\\') != SIZE_MAX)
383 ds_put_cstr (&s, "\\\\");
384 for (i = 0; i < ds_length (&ia->separators.separators); i++)
386 char c = ds_at (&ia->separators.separators, i);
388 ds_put_cstr (&s, "\"\"");
389 else if (c != '\t' && c != '\\')
392 ds_put_cstr (&s, "\"\n");
393 if (!ds_is_empty (&ia->separators.quotes))
394 syntax_gen_pspp (&s, " /QUALIFIER=%sq\n", ds_cstr (&ia->separators.quotes));
395 if (!ds_is_empty (&ia->separators.quotes) && ia->separators.escape)
396 ds_put_cstr (&s, " /ESCAPE\n");
397 ds_put_cstr (&s, " /VARIABLES=\n");
399 var_cnt = dict_get_var_cnt (ia->formats.dict);
400 for (i = 0; i < var_cnt; i++)
402 struct variable *var = dict_get_var (ia->formats.dict, i);
403 char format_string[FMT_STRING_LEN_MAX + 1];
404 fmt_to_string (var_get_print_format (var), format_string);
405 ds_put_format (&s, " %s %s%s\n",
406 var_get_name (var), format_string,
407 i == var_cnt - 1 ? "." : "");
410 apply_dict (ia->formats.dict, &s);
415 /* Choosing a file and reading it. */
417 static char *choose_file (GtkWindow *parent_window);
419 /* Obtains the file to import from the user and initializes IA's
420 file substructure. PARENT_WINDOW must be the window to use
421 as the file chooser window's parent.
423 Returns true if successful, false if the file name could not
424 be obtained or the file could not be read. */
426 init_file (struct import_assistant *ia, GtkWindow *parent_window)
428 struct file *file = &ia->file;
429 enum { MAX_PREVIEW_LINES = 1000 }; /* Max number of lines to read. */
430 enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
433 file->file_name = choose_file (parent_window);
434 if (file->file_name == NULL)
437 stream = fopen (file->file_name, "r");
440 msg (ME, _("Could not open `%s': %s"),
441 file->file_name, strerror (errno));
445 file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines);
446 for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++)
448 struct string *line = &file->lines[file->line_cnt];
450 ds_init_empty (line);
451 if (!ds_read_line (line, stream, MAX_LINE_LEN))
455 else if (ferror (stream))
456 msg (ME, _("Error reading `%s': %s"),
457 file->file_name, strerror (errno));
459 msg (ME, _("Failed to read `%s', because it contains a line "
460 "over %d bytes long and therefore appears not to be "
462 file->file_name, MAX_LINE_LEN);
467 ds_chomp_byte (line, '\n');
468 ds_chomp_byte (line, '\r');
471 if (file->line_cnt == 0)
473 msg (ME, _("`%s' is empty."), file->file_name);
479 /* Estimate the number of lines in the file. */
480 if (file->line_cnt < MAX_PREVIEW_LINES)
481 file->total_lines = file->line_cnt;
485 off_t position = ftello (stream);
486 if (fstat (fileno (stream), &s) == 0 && position > 0)
487 file->total_lines = (double) file->line_cnt / position * s.st_size;
489 file->total_lines = 0;
495 /* Frees IA's file substructure. */
497 destroy_file (struct import_assistant *ia)
499 struct file *f = &ia->file;
502 for (i = 0; i < f->line_cnt; i++)
503 ds_destroy (&f->lines[i]);
505 g_free (f->file_name);
508 /* Obtains the file to read from the user and returns the name of
509 the file as a string that must be freed with g_free if
510 successful, otherwise a null pointer. PARENT_WINDOW must be
511 the window to use as the file chooser window's parent. */
513 choose_file (GtkWindow *parent_window)
516 GtkFileFilter *filter = NULL;
518 GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
520 GTK_FILE_CHOOSER_ACTION_OPEN,
521 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
522 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
525 g_object_set (dialog, "local-only", FALSE, NULL);
527 filter = gtk_file_filter_new ();
528 gtk_file_filter_set_name (filter, _("Text files"));
529 gtk_file_filter_add_mime_type (filter, "text/*");
530 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
532 filter = gtk_file_filter_new ();
533 gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
534 gtk_file_filter_add_pattern (filter, "*.txt");
535 gtk_file_filter_add_pattern (filter, "*.TXT");
536 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
538 filter = gtk_file_filter_new ();
539 gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
540 gtk_file_filter_add_mime_type (filter, "text/plain");
541 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
543 filter = gtk_file_filter_new ();
544 gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
545 gtk_file_filter_add_mime_type (filter, "text/csv");
546 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
548 /* I've never encountered one of these, but it's listed here:
549 http://www.iana.org/assignments/media-types/text/tab-separated-values */
550 filter = gtk_file_filter_new ();
551 gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
552 gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
553 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
555 filter = gtk_file_filter_new ();
556 gtk_file_filter_set_name (filter, _("All Files"));
557 gtk_file_filter_add_pattern (filter, "*");
558 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
560 switch (gtk_dialog_run (GTK_DIALOG (dialog)))
562 case GTK_RESPONSE_ACCEPT:
563 file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
569 gtk_widget_destroy (dialog);
576 static void close_assistant (struct import_assistant *, int response);
577 static void on_prepare (GtkAssistant *assistant, GtkWidget *page,
578 struct import_assistant *);
579 static void on_cancel (GtkAssistant *assistant, struct import_assistant *);
580 static void on_close (GtkAssistant *assistant, struct import_assistant *);
581 static void on_paste (GtkButton *button, struct import_assistant *);
582 static void on_reset (GtkButton *button, struct import_assistant *);
583 static void close_assistant (struct import_assistant *, int response);
585 /* Initializes IA's asst substructure. PARENT_WINDOW must be the
586 window to use as the assistant window's parent. */
588 init_assistant (struct import_assistant *ia, GtkWindow *parent_window)
590 struct assistant *a = &ia->asst;
592 a->builder = builder_new ("text-data-import.ui");
593 a->assistant = GTK_ASSISTANT (gtk_assistant_new ());
594 g_signal_connect (a->assistant, "prepare", G_CALLBACK (on_prepare), ia);
595 g_signal_connect (a->assistant, "cancel", G_CALLBACK (on_cancel), ia);
596 g_signal_connect (a->assistant, "close", G_CALLBACK (on_close), ia);
597 a->paste_button = gtk_button_new_from_stock (GTK_STOCK_PASTE);
598 gtk_assistant_add_action_widget (a->assistant, a->paste_button);
599 g_signal_connect (a->paste_button, "clicked", G_CALLBACK (on_paste), ia);
600 a->reset_button = gtk_button_new_from_stock ("pspp-stock-reset");
601 gtk_assistant_add_action_widget (a->assistant, a->reset_button);
602 g_signal_connect (a->reset_button, "clicked", G_CALLBACK (on_reset), ia);
603 gtk_window_set_title (GTK_WINDOW (a->assistant),
604 _("Importing Delimited Text Data"));
605 gtk_window_set_transient_for (GTK_WINDOW (a->assistant), parent_window);
606 gtk_window_set_icon_name (GTK_WINDOW (a->assistant), "pspp");
608 a->prop_renderer = gtk_cell_renderer_text_new ();
609 g_object_ref_sink (a->prop_renderer);
610 a->fixed_renderer = gtk_cell_renderer_text_new ();
611 g_object_ref_sink (a->fixed_renderer);
612 g_object_set (G_OBJECT (a->fixed_renderer),
613 "family", "Monospace",
617 /* Frees IA's asst substructure. */
619 destroy_assistant (struct import_assistant *ia)
621 struct assistant *a = &ia->asst;
623 g_object_unref (a->prop_renderer);
624 g_object_unref (a->fixed_renderer);
625 g_object_unref (a->builder);
628 /* Appends a page of the given TYPE, with PAGE as its content, to
629 the GtkAssistant encapsulated by IA. Returns the GtkWidget
630 that represents the page. */
632 add_page_to_assistant (struct import_assistant *ia,
633 GtkWidget *page, GtkAssistantPageType type)
639 title = gtk_window_get_title (GTK_WINDOW (page));
640 title_copy = xstrdup (title ? title : "");
642 content = gtk_bin_get_child (GTK_BIN (page));
644 g_object_ref (content);
645 gtk_container_remove (GTK_CONTAINER (page), content);
647 gtk_widget_destroy (page);
649 gtk_assistant_append_page (ia->asst.assistant, content);
650 gtk_assistant_set_page_type (ia->asst.assistant, content, type);
651 gtk_assistant_set_page_title (ia->asst.assistant, content, title_copy);
652 gtk_assistant_set_page_complete (ia->asst.assistant, content, true);
659 /* Called just before PAGE is displayed as the current page of
660 ASSISTANT, this updates IA content according to the new
663 on_prepare (GtkAssistant *assistant, GtkWidget *page,
664 struct import_assistant *ia)
667 if (gtk_assistant_get_page_type (assistant, page)
668 == GTK_ASSISTANT_PAGE_CONFIRM)
669 gtk_widget_grab_focus (assistant->apply);
671 gtk_widget_grab_focus (assistant->forward);
673 if (page == ia->separators.page)
674 prepare_separators_page (ia);
675 else if (page == ia->formats.page)
676 prepare_formats_page (ia);
678 gtk_widget_show (ia->asst.reset_button);
679 if (page == ia->formats.page)
680 gtk_widget_show (ia->asst.paste_button);
682 gtk_widget_hide (ia->asst.paste_button);
685 /* Called when the Cancel button in the assistant is clicked. */
687 on_cancel (GtkAssistant *assistant, struct import_assistant *ia)
689 close_assistant (ia, GTK_RESPONSE_CANCEL);
692 /* Called when the Apply button on the last page of the assistant
695 on_close (GtkAssistant *assistant, struct import_assistant *ia)
697 close_assistant (ia, GTK_RESPONSE_APPLY);
700 /* Called when the Paste button on the last page of the assistant
703 on_paste (GtkButton *button, struct import_assistant *ia)
705 close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
708 /* Called when the Reset button is clicked. */
710 on_reset (GtkButton *button, struct import_assistant *ia)
712 gint page_num = gtk_assistant_get_current_page (ia->asst.assistant);
713 GtkWidget *page = gtk_assistant_get_nth_page (ia->asst.assistant, page_num);
715 if (page == ia->intro.page)
716 reset_intro_page (ia);
717 else if (page == ia->first_line.page)
718 reset_first_line_page (ia);
719 else if (page == ia->separators.page)
720 reset_separators_page (ia);
721 else if (page == ia->formats.page)
722 reset_formats_page (ia);
725 /* Causes the assistant to close, returning RESPONSE for
726 interpretation by text_data_import_assistant. */
728 close_assistant (struct import_assistant *ia, int response)
730 ia->asst.response = response;
731 g_main_loop_quit (ia->asst.main_loop);
732 gtk_widget_hide (GTK_WIDGET (ia->asst.assistant));
735 /* The "intro" page of the assistant. */
737 static void on_intro_amount_changed (struct import_assistant *);
739 /* Initializes IA's intro substructure. */
741 init_intro_page (struct import_assistant *ia)
743 GtkBuilder *builder = ia->asst.builder;
744 struct intro_page *p = &ia->intro;
746 GtkWidget *hbox_n_cases ;
747 GtkWidget *hbox_percent ;
751 p->n_cases_spin = gtk_spin_button_new_with_range (0, INT_MAX, 100);
753 hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &p->n_cases_spin);
755 table = get_widget_assert (builder, "button-table");
757 gtk_table_attach_defaults (GTK_TABLE (table), hbox_n_cases,
761 p->percent_spin = gtk_spin_button_new_with_range (0, 100, 10);
763 hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"), &p->percent_spin);
765 gtk_table_attach_defaults (GTK_TABLE (table), hbox_percent,
769 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Intro"),
770 GTK_ASSISTANT_PAGE_INTRO);
772 p->all_cases_button = get_widget_assert (builder, "import-all-cases");
774 p->n_cases_button = get_widget_assert (builder, "import-n-cases");
776 p->percent_button = get_widget_assert (builder, "import-percent");
778 g_signal_connect_swapped (p->all_cases_button, "toggled",
779 G_CALLBACK (on_intro_amount_changed), ia);
780 g_signal_connect_swapped (p->n_cases_button, "toggled",
781 G_CALLBACK (on_intro_amount_changed), ia);
782 g_signal_connect_swapped (p->percent_button, "toggled",
783 G_CALLBACK (on_intro_amount_changed), ia);
785 on_intro_amount_changed (ia);
788 ds_put_cstr (&s, _("This assistant will guide you through the process of "
789 "importing data into PSPP from a text file with one line "
790 "per case, in which fields are separated by tabs, "
791 "commas, or other delimiters.\n\n"));
792 if (ia->file.total_is_exact)
794 &s, ngettext ("The selected file contains %zu line of text. ",
795 "The selected file contains %zu lines of text. ",
798 else if (ia->file.total_lines > 0)
802 "The selected file contains approximately %lu line of text. ",
803 "The selected file contains approximately %lu lines of text. ",
804 ia->file.total_lines),
805 ia->file.total_lines);
808 "Only the first %zu line of the file will be shown for "
809 "preview purposes in the following screens. ",
810 "Only the first %zu lines of the file will be shown for "
811 "preview purposes in the following screens. ",
815 ds_put_cstr (&s, _("You may choose below how much of the file should "
816 "actually be imported."));
817 gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
822 /* Resets IA's intro page to its initial state. */
824 reset_intro_page (struct import_assistant *ia)
826 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->intro.all_cases_button),
830 /* Called when one of the radio buttons is clicked. */
832 on_intro_amount_changed (struct import_assistant *ia)
834 struct intro_page *p = &ia->intro;
836 gtk_widget_set_sensitive (p->n_cases_spin,
837 gtk_toggle_button_get_active (
838 GTK_TOGGLE_BUTTON (p->n_cases_button)));
840 gtk_widget_set_sensitive (p->percent_spin,
841 gtk_toggle_button_get_active (
842 GTK_TOGGLE_BUTTON (p->percent_button)));
845 /* The "first line" page of the assistant. */
847 static GtkTreeView *create_lines_tree_view (GtkContainer *parent_window,
848 struct import_assistant *);
849 static void on_first_line_change (GtkTreeSelection *,
850 struct import_assistant *);
851 static void on_variable_names_cb_toggle (GtkToggleButton *,
852 struct import_assistant *);
853 static void set_first_line (struct import_assistant *);
854 static void get_first_line (struct import_assistant *);
856 /* Initializes IA's first_line substructure. */
858 init_first_line_page (struct import_assistant *ia)
860 struct first_line_page *p = &ia->first_line;
861 GtkBuilder *builder = ia->asst.builder;
863 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "FirstLine"),
864 GTK_ASSISTANT_PAGE_CONTENT);
865 gtk_widget_destroy (get_widget_assert (builder, "first-line"));
866 p->tree_view = create_lines_tree_view (
867 GTK_CONTAINER (get_widget_assert (builder, "first-line-scroller")), ia);
868 p->variable_names_cb = get_widget_assert (builder, "variable-names");
869 gtk_tree_selection_set_mode (
870 gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
871 GTK_SELECTION_BROWSE);
873 g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
874 "changed", G_CALLBACK (on_first_line_change), ia);
875 g_signal_connect (p->variable_names_cb, "toggled",
876 G_CALLBACK (on_variable_names_cb_toggle), ia);
879 /* Resets the first_line page to its initial content. */
881 reset_first_line_page (struct import_assistant *ia)
883 ia->first_line.skip_lines = 0;
884 ia->first_line.variable_names = false;
889 render_line (GtkTreeViewColumn *tree_column,
890 GtkCellRenderer *cell,
891 GtkTreeModel *tree_model,
895 gint row = empty_list_store_iter_to_row (iter);
896 struct string *lines;
898 lines = g_object_get_data (G_OBJECT (tree_model), "lines");
899 g_return_if_fail (lines != NULL);
901 g_object_set (cell, "text", ds_cstr (&lines[row]), NULL);
905 /* Creates and returns a tree view that contains each of the
906 lines in IA's file as a row. */
908 create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia)
910 GtkTreeView *tree_view;
911 GtkTreeViewColumn *column;
912 size_t max_line_length;
913 gint content_width, header_width;
915 const gchar *title = _("Text");
917 make_tree_view (ia, 0, &tree_view);
919 column = gtk_tree_view_column_new_with_attributes (
920 title, ia->asst.fixed_renderer, (void *) NULL);
921 gtk_tree_view_column_set_cell_data_func (column, ia->asst.fixed_renderer,
922 render_line, NULL, NULL);
923 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
926 for (i = 0; i < ia->file.line_cnt; i++)
928 size_t w = ds_length (&ia->file.lines[i]);
929 max_line_length = MAX (max_line_length, w);
932 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
934 header_width = get_string_width (tree_view, ia->asst.prop_renderer, title);
935 gtk_tree_view_column_set_fixed_width (column, MAX (content_width,
937 gtk_tree_view_append_column (tree_view, column);
939 gtk_tree_view_set_fixed_height_mode (tree_view, true);
941 gtk_container_add (parent, GTK_WIDGET (tree_view));
942 gtk_widget_show (GTK_WIDGET (tree_view));
947 /* Called when the line selected in the first_line tree view
950 on_first_line_change (GtkTreeSelection *selection UNUSED,
951 struct import_assistant *ia)
956 /* Called when the checkbox that indicates whether variable
957 names are in the row above the first line is toggled. */
959 on_variable_names_cb_toggle (GtkToggleButton *variable_names_cb UNUSED,
960 struct import_assistant *ia)
965 /* Sets the widgets to match IA's first_line substructure. */
967 set_first_line (struct import_assistant *ia)
971 path = gtk_tree_path_new_from_indices (ia->first_line.skip_lines, -1);
972 gtk_tree_view_set_cursor (GTK_TREE_VIEW (ia->first_line.tree_view),
974 gtk_tree_path_free (path);
976 gtk_toggle_button_set_active (
977 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb),
978 ia->first_line.variable_names);
979 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
980 ia->first_line.skip_lines > 0);
983 /* Sets IA's first_line substructure to match the widgets. */
985 get_first_line (struct import_assistant *ia)
987 GtkTreeSelection *selection;
991 selection = gtk_tree_view_get_selection (ia->first_line.tree_view);
992 if (gtk_tree_selection_get_selected (selection, &model, &iter))
994 GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
995 int row = gtk_tree_path_get_indices (path)[0];
996 gtk_tree_path_free (path);
998 ia->first_line.skip_lines = row;
999 ia->first_line.variable_names =
1000 (ia->first_line.skip_lines > 0
1001 && gtk_toggle_button_get_active (
1002 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb)));
1004 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
1005 ia->first_line.skip_lines > 0);
1008 /* The "separators" page of the assistant. */
1010 static void revise_fields_preview (struct import_assistant *ia);
1011 static void choose_likely_separators (struct import_assistant *ia);
1012 static void find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
1013 const char *targets, const char *def,
1014 struct string *result);
1015 static void clear_fields (struct import_assistant *ia);
1016 static void revise_fields_preview (struct import_assistant *);
1017 static void set_separators (struct import_assistant *);
1018 static void get_separators (struct import_assistant *);
1019 static void on_separators_custom_entry_notify (GObject *UNUSED,
1021 struct import_assistant *);
1022 static void on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1023 struct import_assistant *);
1024 static void on_quote_combo_change (GtkComboBox *combo,
1025 struct import_assistant *);
1026 static void on_quote_cb_toggle (GtkToggleButton *quote_cb,
1027 struct import_assistant *);
1028 static void on_separator_toggle (GtkToggleButton *, struct import_assistant *);
1029 static void render_input_cell (GtkTreeViewColumn *tree_column,
1030 GtkCellRenderer *cell,
1031 GtkTreeModel *model, GtkTreeIter *iter,
1033 static gboolean on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1034 gboolean keyboard_mode UNUSED,
1035 GtkTooltip *tooltip,
1036 struct import_assistant *);
1038 /* A common field separator and its identifying name. */
1041 const char *name; /* Name (for use with get_widget_assert). */
1042 int c; /* Separator character. */
1045 /* All the separators in the dialog box. */
1046 static const struct separator separators[] =
1058 #define SEPARATOR_CNT (sizeof separators / sizeof *separators)
1061 set_quote_list (GtkComboBoxEntry *cb)
1063 GtkListStore *list = gtk_list_store_new (1, G_TYPE_STRING);
1066 const gchar *seperator[3] = {"'\"", "\'", "\""};
1068 for (i = 0; i < 3; i++)
1070 const gchar *s = seperator[i];
1072 /* Add a new row to the model */
1073 gtk_list_store_append (list, &iter);
1074 gtk_list_store_set (list, &iter,
1080 gtk_combo_box_set_model (GTK_COMBO_BOX (cb), GTK_TREE_MODEL (list));
1082 gtk_combo_box_entry_set_text_column (cb, 0);
1085 /* Initializes IA's separators substructure. */
1087 init_separators_page (struct import_assistant *ia)
1089 GtkBuilder *builder = ia->asst.builder;
1090 struct separators_page *p = &ia->separators;
1093 choose_likely_separators (ia);
1095 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Separators"),
1096 GTK_ASSISTANT_PAGE_CONTENT);
1097 p->custom_cb = get_widget_assert (builder, "custom-cb");
1098 p->custom_entry = get_widget_assert (builder, "custom-entry");
1099 p->quote_combo = get_widget_assert (builder, "quote-combo");
1100 p->quote_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (p->quote_combo)));
1101 p->quote_cb = get_widget_assert (builder, "quote-cb");
1102 p->escape_cb = get_widget_assert (builder, "escape");
1104 set_separators (ia);
1105 set_quote_list (GTK_COMBO_BOX_ENTRY (p->quote_combo));
1106 p->fields_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "fields"));
1107 g_signal_connect (p->quote_combo, "changed",
1108 G_CALLBACK (on_quote_combo_change), ia);
1109 g_signal_connect (p->quote_cb, "toggled",
1110 G_CALLBACK (on_quote_cb_toggle), ia);
1111 g_signal_connect (p->custom_entry, "notify::text",
1112 G_CALLBACK (on_separators_custom_entry_notify), ia);
1113 g_signal_connect (p->custom_cb, "toggled",
1114 G_CALLBACK (on_separators_custom_cb_toggle), ia);
1115 for (i = 0; i < SEPARATOR_CNT; i++)
1116 g_signal_connect (get_widget_assert (builder, separators[i].name),
1117 "toggled", G_CALLBACK (on_separator_toggle), ia);
1118 g_signal_connect (p->escape_cb, "toggled",
1119 G_CALLBACK (on_separator_toggle), ia);
1122 /* Frees IA's separators substructure. */
1124 destroy_separators_page (struct import_assistant *ia)
1126 struct separators_page *s = &ia->separators;
1128 ds_destroy (&s->separators);
1129 ds_destroy (&s->quotes);
1133 /* Called just before the separators page becomes visible in the
1136 prepare_separators_page (struct import_assistant *ia)
1138 revise_fields_preview (ia);
1141 /* Called when the Reset button is clicked on the separators
1142 page, resets the separators to the defaults. */
1144 reset_separators_page (struct import_assistant *ia)
1146 choose_likely_separators (ia);
1147 set_separators (ia);
1150 /* Frees and clears the column data in IA's separators
1153 clear_fields (struct import_assistant *ia)
1155 struct separators_page *s = &ia->separators;
1157 if (s->column_cnt > 0)
1162 for (row = 0; row < ia->file.line_cnt; row++)
1164 const struct string *line = &ia->file.lines[row];
1165 const char *line_start = ds_data (line);
1166 const char *line_end = ds_end (line);
1168 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1170 char *s = ss_data (col->contents[row]);
1171 if (!(s >= line_start && s <= line_end))
1172 ss_dealloc (&col->contents[row]);
1176 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1179 free (col->contents);
1188 /* Breaks the file data in IA into columns based on the
1189 separators set in IA's separators substructure. */
1191 split_fields (struct import_assistant *ia)
1193 struct separators_page *s = &ia->separators;
1194 size_t columns_allocated;
1200 /* Is space in the set of separators? */
1201 space_sep = ss_find_byte (ds_ss (&s->separators), ' ') != SIZE_MAX;
1203 /* Split all the lines, not just those from
1204 ia->first_line.skip_lines on, so that we split the line that
1205 contains variables names if ia->first_line.variable_names is
1207 columns_allocated = 0;
1208 for (row = 0; row < ia->file.line_cnt; row++)
1210 struct string *line = &ia->file.lines[row];
1211 struct substring text = ds_ss (line);
1214 for (column_idx = 0; ; column_idx++)
1216 struct substring field;
1217 struct column *column;
1220 ss_ltrim (&text, ss_cstr (" "));
1221 if (ss_is_empty (text))
1223 if (column_idx != 0)
1227 else if (!ds_is_empty (&s->quotes)
1228 && ds_find_byte (&s->quotes, text.string[0]) != SIZE_MAX)
1230 int quote = ss_get_byte (&text);
1232 ss_get_until (&text, quote, &field);
1239 while ((c = ss_get_byte (&text)) != EOF)
1241 ds_put_byte (&s, c);
1242 else if (ss_match_byte (&text, quote))
1243 ds_put_byte (&s, quote);
1250 ss_get_bytes (&text, ss_cspan (text, ds_ss (&s->separators)),
1253 if (column_idx >= s->column_cnt)
1255 struct column *column;
1257 if (s->column_cnt >= columns_allocated)
1258 s->columns = x2nrealloc (s->columns, &columns_allocated,
1259 sizeof *s->columns);
1260 column = &s->columns[s->column_cnt++];
1261 column->name = NULL;
1263 column->contents = xcalloc (ia->file.line_cnt,
1264 sizeof *column->contents);
1266 column = &s->columns[column_idx];
1267 column->contents[row] = field;
1268 if (ss_length (field) > column->width)
1269 column->width = ss_length (field);
1272 ss_ltrim (&text, ss_cstr (" "));
1273 if (ss_is_empty (text))
1275 if (ss_find_byte (ds_ss (&s->separators), ss_first (text))
1277 ss_advance (&text, 1);
1282 /* Chooses a name for each column on the separators page */
1284 choose_column_names (struct import_assistant *ia)
1286 const struct first_line_page *f = &ia->first_line;
1287 struct separators_page *s = &ia->separators;
1288 struct dictionary *dict;
1289 unsigned long int generated_name_count = 0;
1293 dict = dict_create (get_default_encoding ());
1294 name_row = f->variable_names && f->skip_lines ? f->skip_lines : 0;
1295 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1299 hint = name_row ? ss_xstrdup (col->contents[name_row - 1]) : NULL;
1300 name = dict_make_unique_var_name (dict, hint, &generated_name_count);
1304 dict_create_var_assert (dict, name, 0);
1306 dict_destroy (dict);
1309 /* Picks the most likely separator and quote characters based on
1312 choose_likely_separators (struct import_assistant *ia)
1314 unsigned long int histogram[UCHAR_MAX + 1] = { 0 };
1317 /* Construct a histogram of all the characters used in the
1319 for (row = 0; row < ia->file.line_cnt; row++)
1321 struct substring line = ds_ss (&ia->file.lines[row]);
1322 size_t length = ss_length (line);
1324 for (i = 0; i < length; i++)
1325 histogram[(unsigned char) line.string[i]]++;
1328 find_commonest_chars (histogram, "\"'", "", &ia->separators.quotes);
1329 find_commonest_chars (histogram, ",;:/|!\t-", ",",
1330 &ia->separators.separators);
1331 ia->separators.escape = true;
1334 /* Chooses the most common character among those in TARGETS,
1335 based on the frequency data in HISTOGRAM, and stores it in
1336 RESULT. If there is a tie for the most common character among
1337 those in TARGETS, the earliest character is chosen. If none
1338 of the TARGETS appear at all, then DEF is used as a
1341 find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
1342 const char *targets, const char *def,
1343 struct string *result)
1345 unsigned char max = 0;
1346 unsigned long int max_count = 0;
1348 for (; *targets != '\0'; targets++)
1350 unsigned char c = *targets;
1351 unsigned long int count = histogram[c];
1352 if (count > max_count)
1361 ds_put_byte (result, max);
1364 ds_assign_cstr (result, def);
1367 /* Revises the contents of the fields tree view based on the
1368 currently chosen set of separators. */
1370 revise_fields_preview (struct import_assistant *ia)
1374 push_watch_cursor (ia);
1376 w = GTK_WIDGET (ia->separators.fields_tree_view);
1377 gtk_widget_destroy (w);
1378 get_separators (ia);
1380 choose_column_names (ia);
1381 ia->separators.fields_tree_view = create_data_tree_view (
1383 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "fields-scroller")),
1386 pop_watch_cursor (ia);
1389 /* Sets the widgets to match IA's separators substructure. */
1391 set_separators (struct import_assistant *ia)
1393 struct separators_page *s = &ia->separators;
1395 struct string custom;
1400 ds_init_empty (&custom);
1402 for (i = 0; i < ds_length (&s->separators); i++)
1404 unsigned char c = ds_at (&s->separators, i);
1407 for (j = 0; j < SEPARATOR_CNT; j++)
1409 const struct separator *s = &separators[j];
1417 ds_put_byte (&custom, c);
1421 for (i = 0; i < SEPARATOR_CNT; i++)
1423 const struct separator *s = &separators[i];
1424 GtkWidget *button = get_widget_assert (ia->asst.builder, s->name);
1425 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
1426 (seps & (1u << i)) != 0);
1428 any_custom = !ds_is_empty (&custom);
1429 gtk_entry_set_text (GTK_ENTRY (s->custom_entry), ds_cstr (&custom));
1430 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->custom_cb),
1432 gtk_widget_set_sensitive (s->custom_entry, any_custom);
1433 ds_destroy (&custom);
1435 any_quotes = !ds_is_empty (&s->quotes);
1437 gtk_entry_set_text (s->quote_entry,
1438 any_quotes ? ds_cstr (&s->quotes) : "\"");
1439 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->quote_cb),
1441 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->escape_cb),
1443 gtk_widget_set_sensitive (s->quote_combo, any_quotes);
1444 gtk_widget_set_sensitive (s->escape_cb, any_quotes);
1447 /* Sets IA's separators substructure to match the widgets. */
1449 get_separators (struct import_assistant *ia)
1451 struct separators_page *s = &ia->separators;
1454 ds_clear (&s->separators);
1455 for (i = 0; i < SEPARATOR_CNT; i++)
1457 const struct separator *sep = &separators[i];
1458 GtkWidget *button = get_widget_assert (ia->asst.builder, sep->name);
1459 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1460 ds_put_byte (&s->separators, sep->c);
1463 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->custom_cb)))
1464 ds_put_cstr (&s->separators,
1465 gtk_entry_get_text (GTK_ENTRY (s->custom_entry)));
1467 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->quote_cb)))
1469 gchar *text = gtk_combo_box_get_active_text (
1470 GTK_COMBO_BOX (s->quote_combo));
1471 ds_assign_cstr (&s->quotes, text);
1475 ds_clear (&s->quotes);
1476 s->escape = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->escape_cb));
1479 /* Called when the user changes the entry field for custom
1482 on_separators_custom_entry_notify (GObject *gobject UNUSED,
1483 GParamSpec *arg1 UNUSED,
1484 struct import_assistant *ia)
1486 revise_fields_preview (ia);
1489 /* Called when the user toggles the checkbox that enables custom
1492 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1493 struct import_assistant *ia)
1495 bool is_active = gtk_toggle_button_get_active (custom_cb);
1496 gtk_widget_set_sensitive (ia->separators.custom_entry, is_active);
1497 revise_fields_preview (ia);
1500 /* Called when the user changes the selection in the combo box
1501 that selects a quote character. */
1503 on_quote_combo_change (GtkComboBox *combo, struct import_assistant *ia)
1505 revise_fields_preview (ia);
1508 /* Called when the user toggles the checkbox that enables
1511 on_quote_cb_toggle (GtkToggleButton *quote_cb, struct import_assistant *ia)
1513 bool is_active = gtk_toggle_button_get_active (quote_cb);
1514 gtk_widget_set_sensitive (ia->separators.quote_combo, is_active);
1515 gtk_widget_set_sensitive (ia->separators.escape_cb, is_active);
1516 revise_fields_preview (ia);
1519 /* Called when the user toggles one of the separators
1522 on_separator_toggle (GtkToggleButton *toggle UNUSED,
1523 struct import_assistant *ia)
1525 revise_fields_preview (ia);
1528 /* Called to render one of the cells in the fields preview tree
1531 render_input_cell (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1532 GtkTreeModel *model, GtkTreeIter *iter,
1535 struct import_assistant *ia = ia_;
1536 struct substring field;
1540 column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1542 row = empty_list_store_iter_to_row (iter) + ia->first_line.skip_lines;
1543 field = ia->separators.columns[column].contents[row];
1544 if (field.string != NULL)
1546 GValue text = {0, };
1547 g_value_init (&text, G_TYPE_STRING);
1548 g_value_take_string (&text, ss_xstrdup (field));
1549 g_object_set_property (G_OBJECT (cell), "text", &text);
1550 g_value_unset (&text);
1551 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1556 "background", "red",
1557 "background-set", TRUE,
1561 /* Called to render a tooltip on one of the cells in the fields
1562 preview tree view. */
1564 on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1565 gboolean keyboard_mode UNUSED,
1566 GtkTooltip *tooltip, struct import_assistant *ia)
1570 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1573 if (ia->separators.columns[column].contents[row].string != NULL)
1576 gtk_tooltip_set_text (tooltip,
1577 _("This input line has too few separators "
1578 "to fill in this field."));
1582 /* The "formats" page of the assistant. */
1584 static void on_variable_change (PsppireDict *dict, int idx,
1585 struct import_assistant *);
1586 static void clear_modified_vars (struct import_assistant *);
1588 /* Initializes IA's formats substructure. */
1590 init_formats_page (struct import_assistant *ia)
1592 GtkBuilder *builder = ia->asst.builder;
1593 struct formats_page *p = &ia->formats;
1595 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Formats"),
1596 GTK_ASSISTANT_PAGE_CONFIRM);
1597 p->data_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "data"));
1598 p->modified_vars = NULL;
1599 p->modified_var_cnt = 0;
1603 /* Frees IA's formats substructure. */
1605 destroy_formats_page (struct import_assistant *ia)
1607 struct formats_page *p = &ia->formats;
1609 if (p->psppire_dict != NULL)
1611 /* This destroys p->dict also. */
1612 g_object_unref (p->psppire_dict);
1614 clear_modified_vars (ia);
1617 /* Called just before the formats page of the assistant is
1620 prepare_formats_page (struct import_assistant *ia)
1622 struct dictionary *dict;
1623 PsppireDict *psppire_dict;
1624 PsppireVarStore *var_store;
1625 GtkBin *vars_scroller;
1626 GtkWidget *old_var_sheet;
1627 PsppireVarSheet *var_sheet;
1628 struct separators_page *s = &ia->separators;
1629 struct formats_page *p = &ia->formats;
1630 struct fmt_guesser *fg;
1631 unsigned long int number = 0;
1634 push_watch_cursor (ia);
1636 dict = dict_create (get_default_encoding ());
1637 fg = fmt_guesser_create ();
1638 for (column_idx = 0; column_idx < s->column_cnt; column_idx++)
1640 struct variable *modified_var;
1642 modified_var = (column_idx < p->modified_var_cnt
1643 ? p->modified_vars[column_idx] : NULL);
1644 if (modified_var == NULL)
1646 struct column *column = &s->columns[column_idx];
1647 struct variable *var;
1648 struct fmt_spec format;
1652 /* Choose variable name. */
1653 name = dict_make_unique_var_name (dict, column->name, &number);
1655 /* Choose variable format. */
1656 fmt_guesser_clear (fg);
1657 for (row = ia->first_line.skip_lines; row < ia->file.line_cnt; row++)
1658 fmt_guesser_add (fg, column->contents[row]);
1659 fmt_guesser_guess (fg, &format);
1660 fmt_fix_input (&format);
1662 /* Create variable. */
1663 var = dict_create_var_assert (dict, name, fmt_var_width (&format));
1664 var_set_both_formats (var, &format);
1672 name = dict_make_unique_var_name (dict, var_get_name (modified_var),
1674 dict_clone_var_as_assert (dict, modified_var, name);
1678 fmt_guesser_destroy (fg);
1680 psppire_dict = psppire_dict_new_from_dict (dict);
1681 g_signal_connect (psppire_dict, "variable_changed",
1682 G_CALLBACK (on_variable_change), ia);
1683 ia->formats.dict = dict;
1684 ia->formats.psppire_dict = psppire_dict;
1686 /* XXX: PsppireVarStore doesn't hold a reference to
1687 psppire_dict for now, but it should. After it does, we
1688 should g_object_ref the psppire_dict here, since we also
1689 hold a reference via ia->formats.dict. */
1690 var_store = psppire_var_store_new (psppire_dict);
1691 g_object_set (var_store,
1692 "format-type", PSPPIRE_VAR_STORE_INPUT_FORMATS,
1694 var_sheet = PSPPIRE_VAR_SHEET (psppire_var_sheet_new ());
1695 g_object_set (var_sheet,
1697 "may-create-vars", FALSE,
1700 vars_scroller = GTK_BIN (get_widget_assert (ia->asst.builder, "vars-scroller"));
1701 old_var_sheet = gtk_bin_get_child (vars_scroller);
1702 if (old_var_sheet != NULL)
1703 gtk_widget_destroy (old_var_sheet);
1704 gtk_container_add (GTK_CONTAINER (vars_scroller), GTK_WIDGET (var_sheet));
1705 gtk_widget_show (GTK_WIDGET (var_sheet));
1707 gtk_widget_destroy (GTK_WIDGET (ia->formats.data_tree_view));
1708 ia->formats.data_tree_view = create_data_tree_view (
1710 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "data-scroller")),
1713 pop_watch_cursor (ia);
1716 /* Clears the set of user-modified variables from IA's formats
1717 substructure. This discards user modifications to variable
1718 formats, thereby causing formats to revert to their
1721 clear_modified_vars (struct import_assistant *ia)
1723 struct formats_page *p = &ia->formats;
1726 for (i = 0; i < p->modified_var_cnt; i++)
1727 var_destroy (p->modified_vars[i]);
1728 free (p->modified_vars);
1729 p->modified_vars = NULL;
1730 p->modified_var_cnt = 0;
1733 /* Resets the formats page to its defaults, discarding user
1736 reset_formats_page (struct import_assistant *ia)
1738 clear_modified_vars (ia);
1739 prepare_formats_page (ia);
1742 /* Called when the user changes one of the variables in the
1745 on_variable_change (PsppireDict *dict, int dict_idx,
1746 struct import_assistant *ia)
1748 struct formats_page *p = &ia->formats;
1749 GtkTreeView *tv = ia->formats.data_tree_view;
1750 gint column_idx = dict_idx + 1;
1752 push_watch_cursor (ia);
1754 /* Remove previous column and replace with new column. */
1755 gtk_tree_view_remove_column (tv, gtk_tree_view_get_column (tv, column_idx));
1756 gtk_tree_view_insert_column (tv, make_data_column (ia, tv, false, dict_idx),
1759 /* Save a copy of the modified variable in modified_vars, so
1760 that its attributes will be preserved if we back up to the
1761 previous page with the Prev button and then come back
1763 if (dict_idx >= p->modified_var_cnt)
1766 p->modified_vars = xnrealloc (p->modified_vars, dict_idx + 1,
1767 sizeof *p->modified_vars);
1768 for (i = 0; i <= dict_idx; i++)
1769 p->modified_vars[i] = NULL;
1770 p->modified_var_cnt = dict_idx + 1;
1772 if (p->modified_vars[dict_idx])
1773 var_destroy (p->modified_vars[dict_idx]);
1774 p->modified_vars[dict_idx]
1775 = var_clone (psppire_dict_get_variable (dict, dict_idx));
1777 pop_watch_cursor (ia);
1780 /* Parses the contents of the field at (ROW,COLUMN) according to
1781 its variable format. If OUTPUTP is non-null, then *OUTPUTP
1782 receives the formatted output for that field (which must be
1783 freed with free). If TOOLTIPP is non-null, then *TOOLTIPP
1784 receives a message suitable for use in a tooltip, if one is
1785 needed, or a null pointer otherwise. Returns true if a
1786 tooltip message is needed, otherwise false. */
1788 parse_field (struct import_assistant *ia,
1789 size_t row, size_t column,
1790 char **outputp, char **tooltipp)
1792 struct substring field;
1794 struct variable *var;
1795 const struct fmt_spec *in;
1796 struct fmt_spec out;
1800 field = ia->separators.columns[column].contents[row];
1801 var = dict_get_var (ia->formats.dict, column);
1802 value_init (&val, var_get_width (var));
1803 in = var_get_print_format (var);
1804 out = fmt_for_output_from_input (in);
1806 if (field.string != NULL)
1810 error = data_in (field, C_ENCODING, in->type, &val, var_get_width (var),
1811 dict_get_encoding (ia->formats.dict));
1814 tooltip = xasprintf (_("Cannot parse field content `%.*s' as "
1816 (int) field.length, field.string,
1817 fmt_name (in->type), error);
1823 tooltip = xstrdup (_("This input line has too few separators "
1824 "to fill in this field."));
1825 value_set_missing (&val, var_get_width (var));
1827 if (outputp != NULL)
1829 *outputp = data_out (&val, dict_get_encoding (ia->formats.dict), &out);
1831 value_destroy (&val, var_get_width (var));
1833 ok = tooltip == NULL;
1834 if (tooltipp != NULL)
1835 *tooltipp = tooltip;
1841 /* Called to render one of the cells in the data preview tree
1844 render_output_cell (GtkTreeViewColumn *tree_column,
1845 GtkCellRenderer *cell,
1846 GtkTreeModel *model,
1850 struct import_assistant *ia = ia_;
1852 GValue gvalue = { 0, };
1855 ok = parse_field (ia,
1856 (empty_list_store_iter_to_row (iter)
1857 + ia->first_line.skip_lines),
1858 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1862 g_value_init (&gvalue, G_TYPE_STRING);
1863 g_value_take_string (&gvalue, output);
1864 g_object_set_property (G_OBJECT (cell), "text", &gvalue);
1865 g_value_unset (&gvalue);
1868 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1871 "background", "red",
1872 "background-set", TRUE,
1876 /* Called to render a tooltip for one of the cells in the data
1877 preview tree view. */
1879 on_query_output_tooltip (GtkWidget *widget, gint wx, gint wy,
1880 gboolean keyboard_mode UNUSED,
1881 GtkTooltip *tooltip, struct import_assistant *ia)
1886 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1889 if (parse_field (ia, row, column, NULL, &text))
1892 gtk_tooltip_set_text (tooltip, text);
1897 /* Utility functions used by multiple pages of the assistant. */
1900 get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
1901 const struct import_assistant *ia,
1902 size_t *row, size_t *column)
1904 GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
1908 GtkTreeViewColumn *tree_column;
1909 GtkTreeModel *tree_model;
1912 /* Check that WIDGET is really visible on the screen before we
1913 do anything else. This is a bug fix for a sticky situation:
1914 when text_data_import_assistant() returns, it frees the data
1915 necessary to compose the tool tip message, but there may be
1916 a tool tip under preparation at that point (even if there is
1917 no visible tool tip) that will call back into us a little
1918 bit later. Perhaps the correct solution to this problem is
1919 to make the data related to the tool tips part of a GObject
1920 that only gets destroyed when all references are released,
1921 but this solution appears to be effective too. */
1922 if (!gtk_widget_get_mapped (widget))
1925 gtk_tree_view_convert_widget_to_bin_window_coords (tree_view,
1927 if (!gtk_tree_view_get_path_at_pos (tree_view, bx, by,
1928 &path, &tree_column, NULL, NULL))
1931 *column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1934 tree_model = gtk_tree_view_get_model (tree_view);
1935 ok = gtk_tree_model_get_iter (tree_model, &iter, path);
1936 gtk_tree_path_free (path);
1940 *row = empty_list_store_iter_to_row (&iter) + ia->first_line.skip_lines;
1945 make_tree_view (const struct import_assistant *ia,
1947 GtkTreeView **tree_view)
1949 GtkTreeModel *model;
1951 *tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
1952 model = GTK_TREE_MODEL (psppire_empty_list_store_new (
1953 ia->file.line_cnt - first_line));
1954 g_object_set_data (G_OBJECT (model), "lines", ia->file.lines + first_line);
1955 g_object_set_data (G_OBJECT (model), "first-line",
1956 GINT_TO_POINTER (first_line));
1957 gtk_tree_view_set_model (*tree_view, model);
1959 add_line_number_column (ia, *tree_view);
1963 render_line_number (GtkTreeViewColumn *tree_column,
1964 GtkCellRenderer *cell,
1965 GtkTreeModel *tree_model,
1969 gint row = empty_list_store_iter_to_row (iter);
1970 char s[INT_BUFSIZE_BOUND (int)];
1973 first_line = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_model),
1975 sprintf (s, "%d", first_line + row);
1976 g_object_set (cell, "text", s, NULL);
1980 add_line_number_column (const struct import_assistant *ia,
1981 GtkTreeView *treeview)
1983 GtkTreeViewColumn *column;
1985 column = gtk_tree_view_column_new_with_attributes (
1986 _("Line"), ia->asst.prop_renderer, (void *) NULL);
1987 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
1988 gtk_tree_view_column_set_fixed_width (
1989 column, get_monospace_width (treeview, ia->asst.prop_renderer, 5));
1990 gtk_tree_view_column_set_resizable (column, TRUE);
1991 gtk_tree_view_column_set_cell_data_func (column, ia->asst.prop_renderer,
1992 render_line_number, NULL, NULL);
1993 gtk_tree_view_append_column (treeview, column);
1997 get_monospace_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
2004 ds_put_byte_multiple (&s, '0', char_cnt);
2005 ds_put_byte (&s, ' ');
2006 width = get_string_width (treeview, renderer, ds_cstr (&s));
2013 get_string_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
2017 g_object_set (G_OBJECT (renderer), "text", string, (void *) NULL);
2018 gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
2019 NULL, NULL, NULL, &width, NULL);
2023 static GtkTreeViewColumn *
2024 make_data_column (struct import_assistant *ia, GtkTreeView *tree_view,
2025 bool input, gint dict_idx)
2027 struct variable *var = NULL;
2028 struct column *column = NULL;
2030 gint content_width, header_width;
2031 GtkTreeViewColumn *tree_column;
2035 column = &ia->separators.columns[dict_idx];
2037 var = dict_get_var (ia->formats.dict, dict_idx);
2039 name = escape_underscores (input ? column->name : var_get_name (var));
2040 char_cnt = input ? column->width : var_get_print_format (var)->w;
2041 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
2043 header_width = get_string_width (tree_view, ia->asst.prop_renderer,
2046 tree_column = gtk_tree_view_column_new ();
2047 g_object_set_data (G_OBJECT (tree_column), "column-number",
2048 GINT_TO_POINTER (dict_idx));
2049 gtk_tree_view_column_set_title (tree_column, name);
2050 gtk_tree_view_column_pack_start (tree_column, ia->asst.fixed_renderer,
2052 gtk_tree_view_column_set_cell_data_func (
2053 tree_column, ia->asst.fixed_renderer,
2054 input ? render_input_cell : render_output_cell, ia, NULL);
2055 gtk_tree_view_column_set_sizing (tree_column, GTK_TREE_VIEW_COLUMN_FIXED);
2056 gtk_tree_view_column_set_fixed_width (tree_column, MAX (content_width,
2064 static GtkTreeView *
2065 create_data_tree_view (bool input, GtkContainer *parent,
2066 struct import_assistant *ia)
2068 GtkTreeView *tree_view;
2071 make_tree_view (ia, ia->first_line.skip_lines, &tree_view);
2072 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (tree_view),
2073 GTK_SELECTION_NONE);
2075 for (i = 0; i < ia->separators.column_cnt; i++)
2076 gtk_tree_view_append_column (tree_view,
2077 make_data_column (ia, tree_view, input, i));
2079 g_object_set (G_OBJECT (tree_view), "has-tooltip", TRUE, (void *) NULL);
2080 g_signal_connect (tree_view, "query-tooltip",
2081 G_CALLBACK (input ? on_query_input_tooltip
2082 : on_query_output_tooltip), ia);
2083 gtk_tree_view_set_fixed_height_mode (tree_view, true);
2085 gtk_container_add (parent, GTK_WIDGET (tree_view));
2086 gtk_widget_show (GTK_WIDGET (tree_view));
2092 escape_underscores (const char *in)
2094 char *out = xmalloc (2 * strlen (in) + 1);
2098 for (; *in != '\0'; in++)
2109 /* Increments the "watch cursor" level, setting the cursor for
2110 the assistant window to a watch face to indicate to the user
2111 that the ongoing operation may take some time. */
2113 push_watch_cursor (struct import_assistant *ia)
2115 if (++ia->asst.watch_cursor == 1)
2117 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2118 GdkDisplay *display = gtk_widget_get_display (widget);
2119 GdkCursor *cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
2120 gdk_window_set_cursor (widget->window, cursor);
2121 gdk_cursor_unref (cursor);
2122 gdk_display_flush (display);
2126 /* Decrements the "watch cursor" level. If the level reaches
2127 zero, the cursor is reset to its default shape. */
2129 pop_watch_cursor (struct import_assistant *ia)
2131 if (--ia->asst.watch_cursor == 0)
2133 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2134 gdk_window_set_cursor (widget->window, NULL);