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"
23 #include <gtk-contrib/psppire-sheet.h>
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/lexer/lexer.h"
35 #include "libpspp/assertion.h"
36 #include "libpspp/i18n.h"
37 #include "libpspp/line-reader.h"
38 #include "libpspp/message.h"
39 #include "ui/gui/checkbox-treeview.h"
40 #include "ui/gui/dialog-common.h"
41 #include "ui/gui/executor.h"
42 #include "ui/gui/helper.h"
43 #include "ui/gui/builder-wrapper.h"
44 #include "ui/gui/psppire-data-window.h"
45 #include "ui/gui/psppire-dialog.h"
46 #include "ui/gui/psppire-encoding-selector.h"
47 #include "ui/gui/psppire-empty-list-store.h"
48 #include "ui/gui/psppire-var-sheet.h"
49 #include "ui/gui/psppire-var-store.h"
50 #include "ui/gui/psppire-scanf.h"
51 #include "ui/syntax-gen.h"
54 #include "gl/intprops.h"
55 #include "gl/xalloc.h"
58 #define _(msgid) gettext (msgid)
59 #define N_(msgid) msgid
61 struct import_assistant;
63 /* The file to be imported. */
66 char *file_name; /* File name. */
67 gchar *encoding; /* Encoding. */
68 unsigned long int total_lines; /* Number of lines in file. */
69 bool total_is_exact; /* Is total_lines exact (or an estimate)? */
71 /* The first several lines of the file. */
75 static bool init_file (struct import_assistant *, GtkWindow *parent);
76 static void destroy_file (struct import_assistant *);
78 /* The main body of the GTK+ assistant and related data. */
82 GtkAssistant *assistant;
84 GtkWidget *paste_button;
85 GtkWidget *reset_button;
89 GtkCellRenderer *prop_renderer;
90 GtkCellRenderer *fixed_renderer;
92 static void init_assistant (struct import_assistant *, GtkWindow *);
93 static void destroy_assistant (struct import_assistant *);
94 static GtkWidget *add_page_to_assistant (struct import_assistant *,
96 GtkAssistantPageType);
98 /* The introduction page of the assistant. */
102 GtkWidget *all_cases_button;
103 GtkWidget *n_cases_button;
104 GtkWidget *n_cases_spin;
105 GtkWidget *percent_button;
106 GtkWidget *percent_spin;
108 static void init_intro_page (struct import_assistant *);
109 static void reset_intro_page (struct import_assistant *);
111 /* Page where the user chooses the first line of data. */
112 struct first_line_page
114 int skip_lines; /* Number of initial lines to skip? */
115 bool variable_names; /* Variable names above first line of data? */
118 GtkTreeView *tree_view;
119 GtkWidget *variable_names_cb;
121 static void init_first_line_page (struct import_assistant *);
122 static void reset_first_line_page (struct import_assistant *);
124 /* Page where the user chooses field separators. */
125 struct separators_page
127 /* How to break lines into columns. */
128 struct string separators; /* Field separators. */
129 struct string quotes; /* Quote characters. */
130 bool escape; /* Doubled quotes yield a quote mark? */
132 /* The columns produced thereby. */
133 struct column *columns; /* Information about each column. */
134 size_t column_cnt; /* Number of columns. */
137 GtkWidget *custom_cb;
138 GtkWidget *custom_entry;
140 GtkWidget *quote_combo;
141 GtkEntry *quote_entry;
142 GtkWidget *escape_cb;
143 GtkTreeView *fields_tree_view;
145 /* The columns that the separators divide the data into. */
148 /* Variable name for this column. This is the variable name
149 used on the separators page; it can be overridden by the
150 user on the formats page. */
153 /* Maximum length of any row in this column. */
156 /* Contents of this column: contents[row] is the contents for
159 A null substring indicates a missing column for that row
160 (because the line contains an insufficient number of
163 contents[] elements may be substrings of the lines[]
164 strings that represent the whole lines of the file, to
165 save memory. Other elements are dynamically allocated
166 with ss_alloc_substring. */
167 struct substring *contents;
169 static void init_separators_page (struct import_assistant *);
170 static void destroy_separators_page (struct import_assistant *);
171 static void prepare_separators_page (struct import_assistant *);
172 static void reset_separators_page (struct import_assistant *);
174 /* Page where the user verifies and adjusts input formats. */
177 struct dictionary *dict;
180 GtkTreeView *data_tree_view;
181 PsppireDict *psppire_dict;
182 struct variable **modified_vars;
183 size_t modified_var_cnt;
185 static void init_formats_page (struct import_assistant *);
186 static void destroy_formats_page (struct import_assistant *);
187 static void prepare_formats_page (struct import_assistant *);
188 static void reset_formats_page (struct import_assistant *);
190 struct import_assistant
193 struct assistant asst;
194 struct intro_page intro;
195 struct first_line_page first_line;
196 struct separators_page separators;
197 struct formats_page formats;
200 static void apply_dict (const struct dictionary *, struct string *);
201 static char *generate_syntax (const struct import_assistant *);
203 static gboolean get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
204 const struct import_assistant *,
205 size_t *row, size_t *column);
206 static void make_tree_view (const struct import_assistant *ia,
208 GtkTreeView **tree_view);
209 static void add_line_number_column (const struct import_assistant *,
211 static gint get_monospace_width (GtkTreeView *, GtkCellRenderer *,
213 static gint get_string_width (GtkTreeView *, GtkCellRenderer *,
215 static GtkTreeViewColumn *make_data_column (struct import_assistant *,
216 GtkTreeView *, bool input,
218 static GtkTreeView *create_data_tree_view (bool input, GtkContainer *parent,
219 struct import_assistant *);
220 static void push_watch_cursor (struct import_assistant *);
221 static void pop_watch_cursor (struct import_assistant *);
223 /* Pops up the Text Data Import assistant. */
225 text_data_import_assistant (PsppireDataWindow *dw)
227 GtkWindow *parent_window = GTK_WINDOW (dw);
228 struct import_assistant *ia;
230 ia = xzalloc (sizeof *ia);
231 if (!init_file (ia, parent_window))
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);
243 gtk_widget_show_all (GTK_WIDGET (ia->asst.assistant));
245 ia->asst.main_loop = g_main_loop_new (NULL, false);
246 g_main_loop_run (ia->asst.main_loop);
247 g_main_loop_unref (ia->asst.main_loop);
249 switch (ia->asst.response)
251 case GTK_RESPONSE_APPLY:
252 free (execute_syntax_string (dw, generate_syntax (ia)));
254 case PSPPIRE_RESPONSE_PASTE:
255 free (paste_syntax_to_window (generate_syntax (ia)));
261 destroy_formats_page (ia);
262 destroy_separators_page (ia);
263 destroy_assistant (ia);
268 /* Emits PSPP syntax to S that applies the dictionary attributes
269 (such as missing values and value labels) of the variables in
272 apply_dict (const struct dictionary *dict, struct string *s)
274 size_t var_cnt = dict_get_var_cnt (dict);
277 for (i = 0; i < var_cnt; i++)
279 struct variable *var = dict_get_var (dict, i);
280 const char *name = var_get_name (var);
281 enum val_type type = var_get_type (var);
282 int width = var_get_width (var);
283 enum measure measure = var_get_measure (var);
284 enum alignment alignment = var_get_alignment (var);
285 const struct fmt_spec *format = var_get_print_format (var);
287 if (var_has_missing_values (var))
289 const struct missing_values *mv = var_get_missing_values (var);
292 syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
293 for (j = 0; j < mv_n_values (mv); j++)
296 ds_put_cstr (s, ", ");
297 syntax_gen_value (s, mv_get_value (mv, j), width, format);
300 if (mv_has_range (mv))
303 if (mv_has_value (mv))
304 ds_put_cstr (s, ", ");
305 mv_get_range (mv, &low, &high);
306 syntax_gen_num_range (s, low, high, format);
308 ds_put_cstr (s, ").\n");
310 if (var_has_value_labels (var))
312 const struct val_labs *vls = var_get_value_labels (var);
313 const struct val_lab **labels = val_labs_sorted (vls);
314 size_t n_labels = val_labs_count (vls);
317 syntax_gen_pspp (s, "VALUE LABELS %ss", name);
318 for (i = 0; i < n_labels; i++)
320 const struct val_lab *vl = labels[i];
321 ds_put_cstr (s, "\n ");
322 syntax_gen_value (s, &vl->value, width, format);
323 ds_put_byte (s, ' ');
324 syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl)));
327 ds_put_cstr (s, ".\n");
329 if (var_has_label (var))
330 syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
331 name, var_get_label (var));
332 if (measure != var_default_measure (type))
333 syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
335 (measure == MEASURE_NOMINAL ? "NOMINAL"
336 : measure == MEASURE_ORDINAL ? "ORDINAL"
338 if (alignment != var_default_alignment (type))
339 syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
341 (alignment == ALIGN_LEFT ? "LEFT"
342 : alignment == ALIGN_CENTRE ? "CENTER"
344 if (var_get_display_width (var) != var_default_display_width (width))
345 syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
346 name, var_get_display_width (var));
350 /* Generates and returns PSPP syntax to execute the import
351 operation described by IA. The caller must free the syntax
354 generate_syntax (const struct import_assistant *ia)
356 struct string s = DS_EMPTY_INITIALIZER;
365 if (ia->file.encoding && strcmp (ia->file.encoding, "Auto"))
366 syntax_gen_pspp (&s, " /ENCODING=%sq\n", ia->file.encoding);
367 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
368 ia->intro.n_cases_button)))
369 ds_put_format (&s, " /IMPORTCASES=FIRST %d\n",
370 gtk_spin_button_get_value_as_int (
371 GTK_SPIN_BUTTON (ia->intro.n_cases_spin)));
372 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
373 ia->intro.percent_button)))
374 ds_put_format (&s, " /IMPORTCASES=PERCENT %d\n",
375 gtk_spin_button_get_value_as_int (
376 GTK_SPIN_BUTTON (ia->intro.percent_spin)));
378 ds_put_cstr (&s, " /IMPORTCASES=ALL\n");
380 " /ARRANGEMENT=DELIMITED\n"
382 if (ia->first_line.skip_lines > 0)
383 ds_put_format (&s, " /FIRSTCASE=%d\n", ia->first_line.skip_lines + 1);
384 ds_put_cstr (&s, " /DELIMITERS=\"");
385 if (ds_find_byte (&ia->separators.separators, '\t') != SIZE_MAX)
386 ds_put_cstr (&s, "\\t");
387 if (ds_find_byte (&ia->separators.separators, '\\') != SIZE_MAX)
388 ds_put_cstr (&s, "\\\\");
389 for (i = 0; i < ds_length (&ia->separators.separators); i++)
391 char c = ds_at (&ia->separators.separators, i);
393 ds_put_cstr (&s, "\"\"");
394 else if (c != '\t' && c != '\\')
397 ds_put_cstr (&s, "\"\n");
398 if (!ds_is_empty (&ia->separators.quotes))
399 syntax_gen_pspp (&s, " /QUALIFIER=%sq\n", ds_cstr (&ia->separators.quotes));
400 if (!ds_is_empty (&ia->separators.quotes) && ia->separators.escape)
401 ds_put_cstr (&s, " /ESCAPE\n");
402 ds_put_cstr (&s, " /VARIABLES=\n");
404 var_cnt = dict_get_var_cnt (ia->formats.dict);
405 for (i = 0; i < var_cnt; i++)
407 struct variable *var = dict_get_var (ia->formats.dict, i);
408 char format_string[FMT_STRING_LEN_MAX + 1];
409 fmt_to_string (var_get_print_format (var), format_string);
410 ds_put_format (&s, " %s %s%s\n",
411 var_get_name (var), format_string,
412 i == var_cnt - 1 ? "." : "");
415 apply_dict (ia->formats.dict, &s);
420 /* Choosing a file and reading it. */
422 static char *choose_file (GtkWindow *parent_window, gchar **encodingp);
424 /* Obtains the file to import from the user and initializes IA's
425 file substructure. PARENT_WINDOW must be the window to use
426 as the file chooser window's parent.
428 Returns true if successful, false if the file name could not
429 be obtained or the file could not be read. */
431 init_file (struct import_assistant *ia, GtkWindow *parent_window)
433 struct file *file = &ia->file;
434 enum { MAX_PREVIEW_LINES = 1000 }; /* Max number of lines to read. */
435 enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
436 struct line_reader *reader;
438 file->file_name = choose_file (parent_window, &file->encoding);
439 if (file->file_name == NULL)
442 reader = line_reader_for_file (file->encoding, file->file_name, O_RDONLY);
445 msg (ME, _("Could not open `%s': %s"),
446 file->file_name, strerror (errno));
450 file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines);
451 for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++)
453 struct string *line = &file->lines[file->line_cnt];
455 ds_init_empty (line);
456 if (!line_reader_read (reader, line, MAX_LINE_LEN + 1)
457 || ds_length (line) > MAX_LINE_LEN)
459 if (line_reader_eof (reader))
461 else if (line_reader_error (reader))
462 msg (ME, _("Error reading `%s': %s"),
463 file->file_name, strerror (line_reader_error (reader)));
465 msg (ME, _("Failed to read `%s', because it contains a line "
466 "over %d bytes long and therefore appears not to be "
468 file->file_name, MAX_LINE_LEN);
469 line_reader_close (reader);
475 if (file->line_cnt == 0)
477 msg (ME, _("`%s' is empty."), file->file_name);
478 line_reader_close (reader);
483 /* Estimate the number of lines in the file. */
484 if (file->line_cnt < MAX_PREVIEW_LINES)
485 file->total_lines = file->line_cnt;
489 off_t position = line_reader_tell (reader);
490 if (fstat (line_reader_fileno (reader), &s) == 0 && position > 0)
491 file->total_lines = (double) file->line_cnt / position * s.st_size;
493 file->total_lines = 0;
496 line_reader_close (reader);
501 /* Frees IA's file substructure. */
503 destroy_file (struct import_assistant *ia)
505 struct file *f = &ia->file;
508 for (i = 0; i < f->line_cnt; i++)
509 ds_destroy (&f->lines[i]);
511 g_free (f->file_name);
512 g_free (f->encoding);
515 /* Obtains the file to read from the user. If successful, returns the name of
516 the file and stores the user's chosen encoding for the file into *ENCODINGP.
517 The caller must free each of these strings with g_free().
519 On failure, stores a null pointer and stores NULL in *ENCODINGP.
521 PARENT_WINDOW must be the window to use as the file chooser window's
524 choose_file (GtkWindow *parent_window, gchar **encodingp)
527 GtkFileFilter *filter = NULL;
529 GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
531 GTK_FILE_CHOOSER_ACTION_OPEN,
532 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
533 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
536 g_object_set (dialog, "local-only", FALSE, NULL);
538 filter = gtk_file_filter_new ();
539 gtk_file_filter_set_name (filter, _("Text files"));
540 gtk_file_filter_add_mime_type (filter, "text/*");
541 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
543 filter = gtk_file_filter_new ();
544 gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
545 gtk_file_filter_add_pattern (filter, "*.txt");
546 gtk_file_filter_add_pattern (filter, "*.TXT");
547 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
549 filter = gtk_file_filter_new ();
550 gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
551 gtk_file_filter_add_mime_type (filter, "text/plain");
552 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
554 filter = gtk_file_filter_new ();
555 gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
556 gtk_file_filter_add_mime_type (filter, "text/csv");
557 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
559 /* I've never encountered one of these, but it's listed here:
560 http://www.iana.org/assignments/media-types/text/tab-separated-values */
561 filter = gtk_file_filter_new ();
562 gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
563 gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
564 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
566 gtk_file_chooser_set_extra_widget (
567 GTK_FILE_CHOOSER (dialog), psppire_encoding_selector_new ("Auto", true));
569 filter = gtk_file_filter_new ();
570 gtk_file_filter_set_name (filter, _("All Files"));
571 gtk_file_filter_add_pattern (filter, "*");
572 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
574 switch (gtk_dialog_run (GTK_DIALOG (dialog)))
576 case GTK_RESPONSE_ACCEPT:
577 file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
578 *encodingp = psppire_encoding_selector_get_encoding (
579 gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (dialog)));
586 gtk_widget_destroy (dialog);
593 static void close_assistant (struct import_assistant *, int response);
594 static void on_prepare (GtkAssistant *assistant, GtkWidget *page,
595 struct import_assistant *);
596 static void on_cancel (GtkAssistant *assistant, struct import_assistant *);
597 static void on_close (GtkAssistant *assistant, struct import_assistant *);
598 static void on_paste (GtkButton *button, struct import_assistant *);
599 static void on_reset (GtkButton *button, struct import_assistant *);
600 static void close_assistant (struct import_assistant *, int response);
602 /* Initializes IA's asst substructure. PARENT_WINDOW must be the
603 window to use as the assistant window's parent. */
605 init_assistant (struct import_assistant *ia, GtkWindow *parent_window)
607 struct assistant *a = &ia->asst;
609 a->builder = builder_new ("text-data-import.ui");
610 a->assistant = GTK_ASSISTANT (gtk_assistant_new ());
611 g_signal_connect (a->assistant, "prepare", G_CALLBACK (on_prepare), ia);
612 g_signal_connect (a->assistant, "cancel", G_CALLBACK (on_cancel), ia);
613 g_signal_connect (a->assistant, "close", G_CALLBACK (on_close), ia);
614 a->paste_button = gtk_button_new_from_stock (GTK_STOCK_PASTE);
615 gtk_assistant_add_action_widget (a->assistant, a->paste_button);
616 g_signal_connect (a->paste_button, "clicked", G_CALLBACK (on_paste), ia);
617 a->reset_button = gtk_button_new_from_stock ("pspp-stock-reset");
618 gtk_assistant_add_action_widget (a->assistant, a->reset_button);
619 g_signal_connect (a->reset_button, "clicked", G_CALLBACK (on_reset), ia);
620 gtk_window_set_title (GTK_WINDOW (a->assistant),
621 _("Importing Delimited Text Data"));
622 gtk_window_set_transient_for (GTK_WINDOW (a->assistant), parent_window);
623 gtk_window_set_icon_name (GTK_WINDOW (a->assistant), "pspp");
625 a->prop_renderer = gtk_cell_renderer_text_new ();
626 g_object_ref_sink (a->prop_renderer);
627 a->fixed_renderer = gtk_cell_renderer_text_new ();
628 g_object_ref_sink (a->fixed_renderer);
629 g_object_set (G_OBJECT (a->fixed_renderer),
630 "family", "Monospace",
634 /* Frees IA's asst substructure. */
636 destroy_assistant (struct import_assistant *ia)
638 struct assistant *a = &ia->asst;
640 g_object_unref (a->prop_renderer);
641 g_object_unref (a->fixed_renderer);
642 g_object_unref (a->builder);
645 /* Appends a page of the given TYPE, with PAGE as its content, to
646 the GtkAssistant encapsulated by IA. Returns the GtkWidget
647 that represents the page. */
649 add_page_to_assistant (struct import_assistant *ia,
650 GtkWidget *page, GtkAssistantPageType type)
656 title = gtk_window_get_title (GTK_WINDOW (page));
657 title_copy = xstrdup (title ? title : "");
659 content = gtk_bin_get_child (GTK_BIN (page));
661 g_object_ref (content);
662 gtk_container_remove (GTK_CONTAINER (page), content);
664 gtk_widget_destroy (page);
666 gtk_assistant_append_page (ia->asst.assistant, content);
667 gtk_assistant_set_page_type (ia->asst.assistant, content, type);
668 gtk_assistant_set_page_title (ia->asst.assistant, content, title_copy);
669 gtk_assistant_set_page_complete (ia->asst.assistant, content, true);
676 /* Called just before PAGE is displayed as the current page of
677 ASSISTANT, this updates IA content according to the new
680 on_prepare (GtkAssistant *assistant, GtkWidget *page,
681 struct import_assistant *ia)
684 if (gtk_assistant_get_page_type (assistant, page)
685 == GTK_ASSISTANT_PAGE_CONFIRM)
686 gtk_widget_grab_focus (assistant->apply);
688 gtk_widget_grab_focus (assistant->forward);
690 if (page == ia->separators.page)
691 prepare_separators_page (ia);
692 else if (page == ia->formats.page)
693 prepare_formats_page (ia);
695 gtk_widget_show (ia->asst.reset_button);
696 if (page == ia->formats.page)
697 gtk_widget_show (ia->asst.paste_button);
699 gtk_widget_hide (ia->asst.paste_button);
702 /* Called when the Cancel button in the assistant is clicked. */
704 on_cancel (GtkAssistant *assistant, struct import_assistant *ia)
706 close_assistant (ia, GTK_RESPONSE_CANCEL);
709 /* Called when the Apply button on the last page of the assistant
712 on_close (GtkAssistant *assistant, struct import_assistant *ia)
714 close_assistant (ia, GTK_RESPONSE_APPLY);
717 /* Called when the Paste button on the last page of the assistant
720 on_paste (GtkButton *button, struct import_assistant *ia)
722 close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
725 /* Called when the Reset button is clicked. */
727 on_reset (GtkButton *button, struct import_assistant *ia)
729 gint page_num = gtk_assistant_get_current_page (ia->asst.assistant);
730 GtkWidget *page = gtk_assistant_get_nth_page (ia->asst.assistant, page_num);
732 if (page == ia->intro.page)
733 reset_intro_page (ia);
734 else if (page == ia->first_line.page)
735 reset_first_line_page (ia);
736 else if (page == ia->separators.page)
737 reset_separators_page (ia);
738 else if (page == ia->formats.page)
739 reset_formats_page (ia);
742 /* Causes the assistant to close, returning RESPONSE for
743 interpretation by text_data_import_assistant. */
745 close_assistant (struct import_assistant *ia, int response)
747 ia->asst.response = response;
748 g_main_loop_quit (ia->asst.main_loop);
749 gtk_widget_hide (GTK_WIDGET (ia->asst.assistant));
752 /* The "intro" page of the assistant. */
754 static void on_intro_amount_changed (struct import_assistant *);
756 /* Initializes IA's intro substructure. */
758 init_intro_page (struct import_assistant *ia)
760 GtkBuilder *builder = ia->asst.builder;
761 struct intro_page *p = &ia->intro;
763 GtkWidget *hbox_n_cases ;
764 GtkWidget *hbox_percent ;
768 p->n_cases_spin = gtk_spin_button_new_with_range (0, INT_MAX, 100);
770 hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &p->n_cases_spin);
772 table = get_widget_assert (builder, "button-table");
774 gtk_table_attach_defaults (GTK_TABLE (table), hbox_n_cases,
778 p->percent_spin = gtk_spin_button_new_with_range (0, 100, 10);
780 hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"), &p->percent_spin);
782 gtk_table_attach_defaults (GTK_TABLE (table), hbox_percent,
786 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Intro"),
787 GTK_ASSISTANT_PAGE_INTRO);
789 p->all_cases_button = get_widget_assert (builder, "import-all-cases");
791 p->n_cases_button = get_widget_assert (builder, "import-n-cases");
793 p->percent_button = get_widget_assert (builder, "import-percent");
795 g_signal_connect_swapped (p->all_cases_button, "toggled",
796 G_CALLBACK (on_intro_amount_changed), ia);
797 g_signal_connect_swapped (p->n_cases_button, "toggled",
798 G_CALLBACK (on_intro_amount_changed), ia);
799 g_signal_connect_swapped (p->percent_button, "toggled",
800 G_CALLBACK (on_intro_amount_changed), ia);
802 on_intro_amount_changed (ia);
805 ds_put_cstr (&s, _("This assistant will guide you through the process of "
806 "importing data into PSPP from a text file with one line "
807 "per case, in which fields are separated by tabs, "
808 "commas, or other delimiters.\n\n"));
809 if (ia->file.total_is_exact)
811 &s, ngettext ("The selected file contains %zu line of text. ",
812 "The selected file contains %zu lines of text. ",
815 else if (ia->file.total_lines > 0)
819 "The selected file contains approximately %lu line of text. ",
820 "The selected file contains approximately %lu lines of text. ",
821 ia->file.total_lines),
822 ia->file.total_lines);
825 "Only the first %zu line of the file will be shown for "
826 "preview purposes in the following screens. ",
827 "Only the first %zu lines of the file will be shown for "
828 "preview purposes in the following screens. ",
832 ds_put_cstr (&s, _("You may choose below how much of the file should "
833 "actually be imported."));
834 gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
839 /* Resets IA's intro page to its initial state. */
841 reset_intro_page (struct import_assistant *ia)
843 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->intro.all_cases_button),
847 /* Called when one of the radio buttons is clicked. */
849 on_intro_amount_changed (struct import_assistant *ia)
851 struct intro_page *p = &ia->intro;
853 gtk_widget_set_sensitive (p->n_cases_spin,
854 gtk_toggle_button_get_active (
855 GTK_TOGGLE_BUTTON (p->n_cases_button)));
857 gtk_widget_set_sensitive (p->percent_spin,
858 gtk_toggle_button_get_active (
859 GTK_TOGGLE_BUTTON (p->percent_button)));
862 /* The "first line" page of the assistant. */
864 static GtkTreeView *create_lines_tree_view (GtkContainer *parent_window,
865 struct import_assistant *);
866 static void on_first_line_change (GtkTreeSelection *,
867 struct import_assistant *);
868 static void on_variable_names_cb_toggle (GtkToggleButton *,
869 struct import_assistant *);
870 static void set_first_line (struct import_assistant *);
871 static void get_first_line (struct import_assistant *);
873 /* Initializes IA's first_line substructure. */
875 init_first_line_page (struct import_assistant *ia)
877 struct first_line_page *p = &ia->first_line;
878 GtkBuilder *builder = ia->asst.builder;
880 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "FirstLine"),
881 GTK_ASSISTANT_PAGE_CONTENT);
882 gtk_widget_destroy (get_widget_assert (builder, "first-line"));
883 p->tree_view = create_lines_tree_view (
884 GTK_CONTAINER (get_widget_assert (builder, "first-line-scroller")), ia);
885 p->variable_names_cb = get_widget_assert (builder, "variable-names");
886 gtk_tree_selection_set_mode (
887 gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
888 GTK_SELECTION_BROWSE);
890 g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
891 "changed", G_CALLBACK (on_first_line_change), ia);
892 g_signal_connect (p->variable_names_cb, "toggled",
893 G_CALLBACK (on_variable_names_cb_toggle), ia);
896 /* Resets the first_line page to its initial content. */
898 reset_first_line_page (struct import_assistant *ia)
900 ia->first_line.skip_lines = 0;
901 ia->first_line.variable_names = false;
906 render_line (GtkTreeViewColumn *tree_column,
907 GtkCellRenderer *cell,
908 GtkTreeModel *tree_model,
912 gint row = empty_list_store_iter_to_row (iter);
913 struct string *lines;
915 lines = g_object_get_data (G_OBJECT (tree_model), "lines");
916 g_return_if_fail (lines != NULL);
918 g_object_set (cell, "text", ds_cstr (&lines[row]), NULL);
922 /* Creates and returns a tree view that contains each of the
923 lines in IA's file as a row. */
925 create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia)
927 GtkTreeView *tree_view;
928 GtkTreeViewColumn *column;
929 size_t max_line_length;
930 gint content_width, header_width;
932 const gchar *title = _("Text");
934 make_tree_view (ia, 0, &tree_view);
936 column = gtk_tree_view_column_new_with_attributes (
937 title, ia->asst.fixed_renderer, (void *) NULL);
938 gtk_tree_view_column_set_cell_data_func (column, ia->asst.fixed_renderer,
939 render_line, NULL, NULL);
940 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
943 for (i = 0; i < ia->file.line_cnt; i++)
945 size_t w = ds_length (&ia->file.lines[i]);
946 max_line_length = MAX (max_line_length, w);
949 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
951 header_width = get_string_width (tree_view, ia->asst.prop_renderer, title);
952 gtk_tree_view_column_set_fixed_width (column, MAX (content_width,
954 gtk_tree_view_append_column (tree_view, column);
956 gtk_tree_view_set_fixed_height_mode (tree_view, true);
958 gtk_container_add (parent, GTK_WIDGET (tree_view));
959 gtk_widget_show (GTK_WIDGET (tree_view));
964 /* Called when the line selected in the first_line tree view
967 on_first_line_change (GtkTreeSelection *selection UNUSED,
968 struct import_assistant *ia)
973 /* Called when the checkbox that indicates whether variable
974 names are in the row above the first line is toggled. */
976 on_variable_names_cb_toggle (GtkToggleButton *variable_names_cb UNUSED,
977 struct import_assistant *ia)
982 /* Sets the widgets to match IA's first_line substructure. */
984 set_first_line (struct import_assistant *ia)
988 path = gtk_tree_path_new_from_indices (ia->first_line.skip_lines, -1);
989 gtk_tree_view_set_cursor (GTK_TREE_VIEW (ia->first_line.tree_view),
991 gtk_tree_path_free (path);
993 gtk_toggle_button_set_active (
994 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb),
995 ia->first_line.variable_names);
996 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
997 ia->first_line.skip_lines > 0);
1000 /* Sets IA's first_line substructure to match the widgets. */
1002 get_first_line (struct import_assistant *ia)
1004 GtkTreeSelection *selection;
1006 GtkTreeModel *model;
1008 selection = gtk_tree_view_get_selection (ia->first_line.tree_view);
1009 if (gtk_tree_selection_get_selected (selection, &model, &iter))
1011 GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
1012 int row = gtk_tree_path_get_indices (path)[0];
1013 gtk_tree_path_free (path);
1015 ia->first_line.skip_lines = row;
1016 ia->first_line.variable_names =
1017 (ia->first_line.skip_lines > 0
1018 && gtk_toggle_button_get_active (
1019 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb)));
1021 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
1022 ia->first_line.skip_lines > 0);
1025 /* The "separators" page of the assistant. */
1027 static void revise_fields_preview (struct import_assistant *ia);
1028 static void choose_likely_separators (struct import_assistant *ia);
1029 static void find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
1030 const char *targets, const char *def,
1031 struct string *result);
1032 static void clear_fields (struct import_assistant *ia);
1033 static void revise_fields_preview (struct import_assistant *);
1034 static void set_separators (struct import_assistant *);
1035 static void get_separators (struct import_assistant *);
1036 static void on_separators_custom_entry_notify (GObject *UNUSED,
1038 struct import_assistant *);
1039 static void on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1040 struct import_assistant *);
1041 static void on_quote_combo_change (GtkComboBox *combo,
1042 struct import_assistant *);
1043 static void on_quote_cb_toggle (GtkToggleButton *quote_cb,
1044 struct import_assistant *);
1045 static void on_separator_toggle (GtkToggleButton *, struct import_assistant *);
1046 static void render_input_cell (GtkTreeViewColumn *tree_column,
1047 GtkCellRenderer *cell,
1048 GtkTreeModel *model, GtkTreeIter *iter,
1050 static gboolean on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1051 gboolean keyboard_mode UNUSED,
1052 GtkTooltip *tooltip,
1053 struct import_assistant *);
1055 /* A common field separator and its identifying name. */
1058 const char *name; /* Name (for use with get_widget_assert). */
1059 int c; /* Separator character. */
1062 /* All the separators in the dialog box. */
1063 static const struct separator separators[] =
1075 #define SEPARATOR_CNT (sizeof separators / sizeof *separators)
1078 set_quote_list (GtkComboBoxEntry *cb)
1080 GtkListStore *list = gtk_list_store_new (1, G_TYPE_STRING);
1083 const gchar *seperator[3] = {"'\"", "\'", "\""};
1085 for (i = 0; i < 3; i++)
1087 const gchar *s = seperator[i];
1089 /* Add a new row to the model */
1090 gtk_list_store_append (list, &iter);
1091 gtk_list_store_set (list, &iter,
1097 gtk_combo_box_set_model (GTK_COMBO_BOX (cb), GTK_TREE_MODEL (list));
1098 g_object_unref (list);
1100 gtk_combo_box_entry_set_text_column (cb, 0);
1103 /* Initializes IA's separators substructure. */
1105 init_separators_page (struct import_assistant *ia)
1107 GtkBuilder *builder = ia->asst.builder;
1108 struct separators_page *p = &ia->separators;
1111 choose_likely_separators (ia);
1113 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Separators"),
1114 GTK_ASSISTANT_PAGE_CONTENT);
1115 p->custom_cb = get_widget_assert (builder, "custom-cb");
1116 p->custom_entry = get_widget_assert (builder, "custom-entry");
1117 p->quote_combo = get_widget_assert (builder, "quote-combo");
1118 p->quote_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (p->quote_combo)));
1119 p->quote_cb = get_widget_assert (builder, "quote-cb");
1120 p->escape_cb = get_widget_assert (builder, "escape");
1122 set_separators (ia);
1123 set_quote_list (GTK_COMBO_BOX_ENTRY (p->quote_combo));
1124 p->fields_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "fields"));
1125 g_signal_connect (p->quote_combo, "changed",
1126 G_CALLBACK (on_quote_combo_change), ia);
1127 g_signal_connect (p->quote_cb, "toggled",
1128 G_CALLBACK (on_quote_cb_toggle), ia);
1129 g_signal_connect (p->custom_entry, "notify::text",
1130 G_CALLBACK (on_separators_custom_entry_notify), ia);
1131 g_signal_connect (p->custom_cb, "toggled",
1132 G_CALLBACK (on_separators_custom_cb_toggle), ia);
1133 for (i = 0; i < SEPARATOR_CNT; i++)
1134 g_signal_connect (get_widget_assert (builder, separators[i].name),
1135 "toggled", G_CALLBACK (on_separator_toggle), ia);
1136 g_signal_connect (p->escape_cb, "toggled",
1137 G_CALLBACK (on_separator_toggle), ia);
1140 /* Frees IA's separators substructure. */
1142 destroy_separators_page (struct import_assistant *ia)
1144 struct separators_page *s = &ia->separators;
1146 ds_destroy (&s->separators);
1147 ds_destroy (&s->quotes);
1151 /* Called just before the separators page becomes visible in the
1154 prepare_separators_page (struct import_assistant *ia)
1156 revise_fields_preview (ia);
1159 /* Called when the Reset button is clicked on the separators
1160 page, resets the separators to the defaults. */
1162 reset_separators_page (struct import_assistant *ia)
1164 choose_likely_separators (ia);
1165 set_separators (ia);
1168 /* Frees and clears the column data in IA's separators
1171 clear_fields (struct import_assistant *ia)
1173 struct separators_page *s = &ia->separators;
1175 if (s->column_cnt > 0)
1180 for (row = 0; row < ia->file.line_cnt; row++)
1182 const struct string *line = &ia->file.lines[row];
1183 const char *line_start = ds_data (line);
1184 const char *line_end = ds_end (line);
1186 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1188 char *s = ss_data (col->contents[row]);
1189 if (!(s >= line_start && s <= line_end))
1190 ss_dealloc (&col->contents[row]);
1194 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1197 free (col->contents);
1206 /* Breaks the file data in IA into columns based on the
1207 separators set in IA's separators substructure. */
1209 split_fields (struct import_assistant *ia)
1211 struct separators_page *s = &ia->separators;
1212 size_t columns_allocated;
1218 /* Is space in the set of separators? */
1219 space_sep = ss_find_byte (ds_ss (&s->separators), ' ') != SIZE_MAX;
1221 /* Split all the lines, not just those from
1222 ia->first_line.skip_lines on, so that we split the line that
1223 contains variables names if ia->first_line.variable_names is
1225 columns_allocated = 0;
1226 for (row = 0; row < ia->file.line_cnt; row++)
1228 struct string *line = &ia->file.lines[row];
1229 struct substring text = ds_ss (line);
1232 for (column_idx = 0; ; column_idx++)
1234 struct substring field;
1235 struct column *column;
1238 ss_ltrim (&text, ss_cstr (" "));
1239 if (ss_is_empty (text))
1241 if (column_idx != 0)
1245 else if (!ds_is_empty (&s->quotes)
1246 && ds_find_byte (&s->quotes, text.string[0]) != SIZE_MAX)
1248 int quote = ss_get_byte (&text);
1250 ss_get_until (&text, quote, &field);
1257 while ((c = ss_get_byte (&text)) != EOF)
1259 ds_put_byte (&s, c);
1260 else if (ss_match_byte (&text, quote))
1261 ds_put_byte (&s, quote);
1268 ss_get_bytes (&text, ss_cspan (text, ds_ss (&s->separators)),
1271 if (column_idx >= s->column_cnt)
1273 struct column *column;
1275 if (s->column_cnt >= columns_allocated)
1276 s->columns = x2nrealloc (s->columns, &columns_allocated,
1277 sizeof *s->columns);
1278 column = &s->columns[s->column_cnt++];
1279 column->name = NULL;
1281 column->contents = xcalloc (ia->file.line_cnt,
1282 sizeof *column->contents);
1284 column = &s->columns[column_idx];
1285 column->contents[row] = field;
1286 if (ss_length (field) > column->width)
1287 column->width = ss_length (field);
1290 ss_ltrim (&text, ss_cstr (" "));
1291 if (ss_is_empty (text))
1293 if (ss_find_byte (ds_ss (&s->separators), ss_first (text))
1295 ss_advance (&text, 1);
1300 /* Chooses a name for each column on the separators page */
1302 choose_column_names (struct import_assistant *ia)
1304 const struct first_line_page *f = &ia->first_line;
1305 struct separators_page *s = &ia->separators;
1306 struct dictionary *dict;
1307 unsigned long int generated_name_count = 0;
1311 dict = dict_create (get_default_encoding ());
1312 name_row = f->variable_names && f->skip_lines ? f->skip_lines : 0;
1313 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1317 hint = name_row ? ss_xstrdup (col->contents[name_row - 1]) : NULL;
1318 name = dict_make_unique_var_name (dict, hint, &generated_name_count);
1322 dict_create_var_assert (dict, name, 0);
1324 dict_destroy (dict);
1327 /* Picks the most likely separator and quote characters based on
1330 choose_likely_separators (struct import_assistant *ia)
1332 unsigned long int histogram[UCHAR_MAX + 1] = { 0 };
1335 /* Construct a histogram of all the characters used in the
1337 for (row = 0; row < ia->file.line_cnt; row++)
1339 struct substring line = ds_ss (&ia->file.lines[row]);
1340 size_t length = ss_length (line);
1342 for (i = 0; i < length; i++)
1343 histogram[(unsigned char) line.string[i]]++;
1346 find_commonest_chars (histogram, "\"'", "", &ia->separators.quotes);
1347 find_commonest_chars (histogram, ",;:/|!\t-", ",",
1348 &ia->separators.separators);
1349 ia->separators.escape = true;
1352 /* Chooses the most common character among those in TARGETS,
1353 based on the frequency data in HISTOGRAM, and stores it in
1354 RESULT. If there is a tie for the most common character among
1355 those in TARGETS, the earliest character is chosen. If none
1356 of the TARGETS appear at all, then DEF is used as a
1359 find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
1360 const char *targets, const char *def,
1361 struct string *result)
1363 unsigned char max = 0;
1364 unsigned long int max_count = 0;
1366 for (; *targets != '\0'; targets++)
1368 unsigned char c = *targets;
1369 unsigned long int count = histogram[c];
1370 if (count > max_count)
1379 ds_put_byte (result, max);
1382 ds_assign_cstr (result, def);
1385 /* Revises the contents of the fields tree view based on the
1386 currently chosen set of separators. */
1388 revise_fields_preview (struct import_assistant *ia)
1392 push_watch_cursor (ia);
1394 w = GTK_WIDGET (ia->separators.fields_tree_view);
1395 gtk_widget_destroy (w);
1396 get_separators (ia);
1398 choose_column_names (ia);
1399 ia->separators.fields_tree_view = create_data_tree_view (
1401 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "fields-scroller")),
1404 pop_watch_cursor (ia);
1407 /* Sets the widgets to match IA's separators substructure. */
1409 set_separators (struct import_assistant *ia)
1411 struct separators_page *s = &ia->separators;
1413 struct string custom;
1418 ds_init_empty (&custom);
1420 for (i = 0; i < ds_length (&s->separators); i++)
1422 unsigned char c = ds_at (&s->separators, i);
1425 for (j = 0; j < SEPARATOR_CNT; j++)
1427 const struct separator *s = &separators[j];
1435 ds_put_byte (&custom, c);
1439 for (i = 0; i < SEPARATOR_CNT; i++)
1441 const struct separator *s = &separators[i];
1442 GtkWidget *button = get_widget_assert (ia->asst.builder, s->name);
1443 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
1444 (seps & (1u << i)) != 0);
1446 any_custom = !ds_is_empty (&custom);
1447 gtk_entry_set_text (GTK_ENTRY (s->custom_entry), ds_cstr (&custom));
1448 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->custom_cb),
1450 gtk_widget_set_sensitive (s->custom_entry, any_custom);
1451 ds_destroy (&custom);
1453 any_quotes = !ds_is_empty (&s->quotes);
1455 gtk_entry_set_text (s->quote_entry,
1456 any_quotes ? ds_cstr (&s->quotes) : "\"");
1457 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->quote_cb),
1459 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->escape_cb),
1461 gtk_widget_set_sensitive (s->quote_combo, any_quotes);
1462 gtk_widget_set_sensitive (s->escape_cb, any_quotes);
1465 /* Sets IA's separators substructure to match the widgets. */
1467 get_separators (struct import_assistant *ia)
1469 struct separators_page *s = &ia->separators;
1472 ds_clear (&s->separators);
1473 for (i = 0; i < SEPARATOR_CNT; i++)
1475 const struct separator *sep = &separators[i];
1476 GtkWidget *button = get_widget_assert (ia->asst.builder, sep->name);
1477 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1478 ds_put_byte (&s->separators, sep->c);
1481 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->custom_cb)))
1482 ds_put_cstr (&s->separators,
1483 gtk_entry_get_text (GTK_ENTRY (s->custom_entry)));
1485 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->quote_cb)))
1487 gchar *text = gtk_combo_box_get_active_text (
1488 GTK_COMBO_BOX (s->quote_combo));
1489 ds_assign_cstr (&s->quotes, text);
1493 ds_clear (&s->quotes);
1494 s->escape = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->escape_cb));
1497 /* Called when the user changes the entry field for custom
1500 on_separators_custom_entry_notify (GObject *gobject UNUSED,
1501 GParamSpec *arg1 UNUSED,
1502 struct import_assistant *ia)
1504 revise_fields_preview (ia);
1507 /* Called when the user toggles the checkbox that enables custom
1510 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1511 struct import_assistant *ia)
1513 bool is_active = gtk_toggle_button_get_active (custom_cb);
1514 gtk_widget_set_sensitive (ia->separators.custom_entry, is_active);
1515 revise_fields_preview (ia);
1518 /* Called when the user changes the selection in the combo box
1519 that selects a quote character. */
1521 on_quote_combo_change (GtkComboBox *combo, struct import_assistant *ia)
1523 revise_fields_preview (ia);
1526 /* Called when the user toggles the checkbox that enables
1529 on_quote_cb_toggle (GtkToggleButton *quote_cb, struct import_assistant *ia)
1531 bool is_active = gtk_toggle_button_get_active (quote_cb);
1532 gtk_widget_set_sensitive (ia->separators.quote_combo, is_active);
1533 gtk_widget_set_sensitive (ia->separators.escape_cb, is_active);
1534 revise_fields_preview (ia);
1537 /* Called when the user toggles one of the separators
1540 on_separator_toggle (GtkToggleButton *toggle UNUSED,
1541 struct import_assistant *ia)
1543 revise_fields_preview (ia);
1546 /* Called to render one of the cells in the fields preview tree
1549 render_input_cell (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1550 GtkTreeModel *model, GtkTreeIter *iter,
1553 struct import_assistant *ia = ia_;
1554 struct substring field;
1558 column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1560 row = empty_list_store_iter_to_row (iter) + ia->first_line.skip_lines;
1561 field = ia->separators.columns[column].contents[row];
1562 if (field.string != NULL)
1564 GValue text = {0, };
1565 g_value_init (&text, G_TYPE_STRING);
1566 g_value_take_string (&text, ss_xstrdup (field));
1567 g_object_set_property (G_OBJECT (cell), "text", &text);
1568 g_value_unset (&text);
1569 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1574 "background", "red",
1575 "background-set", TRUE,
1579 /* Called to render a tooltip on one of the cells in the fields
1580 preview tree view. */
1582 on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1583 gboolean keyboard_mode UNUSED,
1584 GtkTooltip *tooltip, struct import_assistant *ia)
1588 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1591 if (ia->separators.columns[column].contents[row].string != NULL)
1594 gtk_tooltip_set_text (tooltip,
1595 _("This input line has too few separators "
1596 "to fill in this field."));
1600 /* The "formats" page of the assistant. */
1602 static void on_variable_change (PsppireDict *dict, int idx,
1603 struct import_assistant *);
1604 static void clear_modified_vars (struct import_assistant *);
1606 /* Initializes IA's formats substructure. */
1608 init_formats_page (struct import_assistant *ia)
1610 GtkBuilder *builder = ia->asst.builder;
1611 struct formats_page *p = &ia->formats;
1613 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Formats"),
1614 GTK_ASSISTANT_PAGE_CONFIRM);
1615 p->data_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "data"));
1616 p->modified_vars = NULL;
1617 p->modified_var_cnt = 0;
1621 /* Frees IA's formats substructure. */
1623 destroy_formats_page (struct import_assistant *ia)
1625 struct formats_page *p = &ia->formats;
1627 if (p->psppire_dict != NULL)
1629 /* This destroys p->dict also. */
1630 g_object_unref (p->psppire_dict);
1632 clear_modified_vars (ia);
1635 /* Called just before the formats page of the assistant is
1638 prepare_formats_page (struct import_assistant *ia)
1640 struct dictionary *dict;
1641 PsppireDict *psppire_dict;
1642 PsppireVarStore *var_store;
1643 GtkBin *vars_scroller;
1644 GtkWidget *old_var_sheet;
1645 PsppireVarSheet *var_sheet;
1646 struct separators_page *s = &ia->separators;
1647 struct formats_page *p = &ia->formats;
1648 struct fmt_guesser *fg;
1649 unsigned long int number = 0;
1652 push_watch_cursor (ia);
1654 dict = dict_create (get_default_encoding ());
1655 fg = fmt_guesser_create ();
1656 for (column_idx = 0; column_idx < s->column_cnt; column_idx++)
1658 struct variable *modified_var;
1660 modified_var = (column_idx < p->modified_var_cnt
1661 ? p->modified_vars[column_idx] : NULL);
1662 if (modified_var == NULL)
1664 struct column *column = &s->columns[column_idx];
1665 struct variable *var;
1666 struct fmt_spec format;
1670 /* Choose variable name. */
1671 name = dict_make_unique_var_name (dict, column->name, &number);
1673 /* Choose variable format. */
1674 fmt_guesser_clear (fg);
1675 for (row = ia->first_line.skip_lines; row < ia->file.line_cnt; row++)
1676 fmt_guesser_add (fg, column->contents[row]);
1677 fmt_guesser_guess (fg, &format);
1678 fmt_fix_input (&format);
1680 /* Create variable. */
1681 var = dict_create_var_assert (dict, name, fmt_var_width (&format));
1682 var_set_both_formats (var, &format);
1690 name = dict_make_unique_var_name (dict, var_get_name (modified_var),
1692 dict_clone_var_as_assert (dict, modified_var, name);
1696 fmt_guesser_destroy (fg);
1698 psppire_dict = psppire_dict_new_from_dict (dict);
1699 g_signal_connect (psppire_dict, "variable_changed",
1700 G_CALLBACK (on_variable_change), ia);
1701 ia->formats.dict = dict;
1702 ia->formats.psppire_dict = psppire_dict;
1704 /* XXX: PsppireVarStore doesn't hold a reference to
1705 psppire_dict for now, but it should. After it does, we
1706 should g_object_ref the psppire_dict here, since we also
1707 hold a reference via ia->formats.dict. */
1708 var_store = psppire_var_store_new (psppire_dict);
1709 g_object_set (var_store,
1710 "format-type", PSPPIRE_VAR_STORE_INPUT_FORMATS,
1712 var_sheet = PSPPIRE_VAR_SHEET (psppire_var_sheet_new ());
1713 g_object_set (var_sheet,
1715 "may-create-vars", FALSE,
1718 vars_scroller = GTK_BIN (get_widget_assert (ia->asst.builder, "vars-scroller"));
1719 old_var_sheet = gtk_bin_get_child (vars_scroller);
1720 if (old_var_sheet != NULL)
1721 gtk_widget_destroy (old_var_sheet);
1722 gtk_container_add (GTK_CONTAINER (vars_scroller), GTK_WIDGET (var_sheet));
1723 gtk_widget_show (GTK_WIDGET (var_sheet));
1725 gtk_widget_destroy (GTK_WIDGET (ia->formats.data_tree_view));
1726 ia->formats.data_tree_view = create_data_tree_view (
1728 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "data-scroller")),
1731 pop_watch_cursor (ia);
1734 /* Clears the set of user-modified variables from IA's formats
1735 substructure. This discards user modifications to variable
1736 formats, thereby causing formats to revert to their
1739 clear_modified_vars (struct import_assistant *ia)
1741 struct formats_page *p = &ia->formats;
1744 for (i = 0; i < p->modified_var_cnt; i++)
1745 var_destroy (p->modified_vars[i]);
1746 free (p->modified_vars);
1747 p->modified_vars = NULL;
1748 p->modified_var_cnt = 0;
1751 /* Resets the formats page to its defaults, discarding user
1754 reset_formats_page (struct import_assistant *ia)
1756 clear_modified_vars (ia);
1757 prepare_formats_page (ia);
1760 /* Called when the user changes one of the variables in the
1763 on_variable_change (PsppireDict *dict, int dict_idx,
1764 struct import_assistant *ia)
1766 struct formats_page *p = &ia->formats;
1767 GtkTreeView *tv = ia->formats.data_tree_view;
1768 gint column_idx = dict_idx + 1;
1770 push_watch_cursor (ia);
1772 /* Remove previous column and replace with new column. */
1773 gtk_tree_view_remove_column (tv, gtk_tree_view_get_column (tv, column_idx));
1774 gtk_tree_view_insert_column (tv, make_data_column (ia, tv, false, dict_idx),
1777 /* Save a copy of the modified variable in modified_vars, so
1778 that its attributes will be preserved if we back up to the
1779 previous page with the Prev button and then come back
1781 if (dict_idx >= p->modified_var_cnt)
1784 p->modified_vars = xnrealloc (p->modified_vars, dict_idx + 1,
1785 sizeof *p->modified_vars);
1786 for (i = 0; i <= dict_idx; i++)
1787 p->modified_vars[i] = NULL;
1788 p->modified_var_cnt = dict_idx + 1;
1790 if (p->modified_vars[dict_idx])
1791 var_destroy (p->modified_vars[dict_idx]);
1792 p->modified_vars[dict_idx]
1793 = var_clone (psppire_dict_get_variable (dict, dict_idx));
1795 pop_watch_cursor (ia);
1798 /* Parses the contents of the field at (ROW,COLUMN) according to
1799 its variable format. If OUTPUTP is non-null, then *OUTPUTP
1800 receives the formatted output for that field (which must be
1801 freed with free). If TOOLTIPP is non-null, then *TOOLTIPP
1802 receives a message suitable for use in a tooltip, if one is
1803 needed, or a null pointer otherwise. Returns true if a
1804 tooltip message is needed, otherwise false. */
1806 parse_field (struct import_assistant *ia,
1807 size_t row, size_t column,
1808 char **outputp, char **tooltipp)
1810 struct substring field;
1812 struct variable *var;
1813 const struct fmt_spec *in;
1814 struct fmt_spec out;
1818 field = ia->separators.columns[column].contents[row];
1819 var = dict_get_var (ia->formats.dict, column);
1820 value_init (&val, var_get_width (var));
1821 in = var_get_print_format (var);
1822 out = fmt_for_output_from_input (in);
1824 if (field.string != NULL)
1828 error = data_in (field, "UTF-8", in->type, &val, var_get_width (var),
1829 dict_get_encoding (ia->formats.dict));
1832 tooltip = xasprintf (_("Cannot parse field content `%.*s' as "
1834 (int) field.length, field.string,
1835 fmt_name (in->type), error);
1841 tooltip = xstrdup (_("This input line has too few separators "
1842 "to fill in this field."));
1843 value_set_missing (&val, var_get_width (var));
1845 if (outputp != NULL)
1847 *outputp = data_out (&val, dict_get_encoding (ia->formats.dict), &out);
1849 value_destroy (&val, var_get_width (var));
1851 ok = tooltip == NULL;
1852 if (tooltipp != NULL)
1853 *tooltipp = tooltip;
1859 /* Called to render one of the cells in the data preview tree
1862 render_output_cell (GtkTreeViewColumn *tree_column,
1863 GtkCellRenderer *cell,
1864 GtkTreeModel *model,
1868 struct import_assistant *ia = ia_;
1870 GValue gvalue = { 0, };
1873 ok = parse_field (ia,
1874 (empty_list_store_iter_to_row (iter)
1875 + ia->first_line.skip_lines),
1876 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1880 g_value_init (&gvalue, G_TYPE_STRING);
1881 g_value_take_string (&gvalue, output);
1882 g_object_set_property (G_OBJECT (cell), "text", &gvalue);
1883 g_value_unset (&gvalue);
1886 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1889 "background", "red",
1890 "background-set", TRUE,
1894 /* Called to render a tooltip for one of the cells in the data
1895 preview tree view. */
1897 on_query_output_tooltip (GtkWidget *widget, gint wx, gint wy,
1898 gboolean keyboard_mode UNUSED,
1899 GtkTooltip *tooltip, struct import_assistant *ia)
1904 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1907 if (parse_field (ia, row, column, NULL, &text))
1910 gtk_tooltip_set_text (tooltip, text);
1915 /* Utility functions used by multiple pages of the assistant. */
1918 get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
1919 const struct import_assistant *ia,
1920 size_t *row, size_t *column)
1922 GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
1926 GtkTreeViewColumn *tree_column;
1927 GtkTreeModel *tree_model;
1930 /* Check that WIDGET is really visible on the screen before we
1931 do anything else. This is a bug fix for a sticky situation:
1932 when text_data_import_assistant() returns, it frees the data
1933 necessary to compose the tool tip message, but there may be
1934 a tool tip under preparation at that point (even if there is
1935 no visible tool tip) that will call back into us a little
1936 bit later. Perhaps the correct solution to this problem is
1937 to make the data related to the tool tips part of a GObject
1938 that only gets destroyed when all references are released,
1939 but this solution appears to be effective too. */
1940 if (!gtk_widget_get_mapped (widget))
1943 gtk_tree_view_convert_widget_to_bin_window_coords (tree_view,
1945 if (!gtk_tree_view_get_path_at_pos (tree_view, bx, by,
1946 &path, &tree_column, NULL, NULL))
1949 *column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1952 tree_model = gtk_tree_view_get_model (tree_view);
1953 ok = gtk_tree_model_get_iter (tree_model, &iter, path);
1954 gtk_tree_path_free (path);
1958 *row = empty_list_store_iter_to_row (&iter) + ia->first_line.skip_lines;
1963 make_tree_view (const struct import_assistant *ia,
1965 GtkTreeView **tree_view)
1967 GtkTreeModel *model;
1969 *tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
1970 model = GTK_TREE_MODEL (psppire_empty_list_store_new (
1971 ia->file.line_cnt - first_line));
1972 g_object_set_data (G_OBJECT (model), "lines", ia->file.lines + first_line);
1973 g_object_set_data (G_OBJECT (model), "first-line",
1974 GINT_TO_POINTER (first_line));
1975 gtk_tree_view_set_model (*tree_view, model);
1976 g_object_unref (model);
1978 add_line_number_column (ia, *tree_view);
1982 render_line_number (GtkTreeViewColumn *tree_column,
1983 GtkCellRenderer *cell,
1984 GtkTreeModel *tree_model,
1988 gint row = empty_list_store_iter_to_row (iter);
1989 char s[INT_BUFSIZE_BOUND (int)];
1992 first_line = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_model),
1994 sprintf (s, "%d", first_line + row);
1995 g_object_set (cell, "text", s, NULL);
1999 add_line_number_column (const struct import_assistant *ia,
2000 GtkTreeView *treeview)
2002 GtkTreeViewColumn *column;
2004 column = gtk_tree_view_column_new_with_attributes (
2005 _("Line"), ia->asst.prop_renderer, (void *) NULL);
2006 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
2007 gtk_tree_view_column_set_fixed_width (
2008 column, get_monospace_width (treeview, ia->asst.prop_renderer, 5));
2009 gtk_tree_view_column_set_resizable (column, TRUE);
2010 gtk_tree_view_column_set_cell_data_func (column, ia->asst.prop_renderer,
2011 render_line_number, NULL, NULL);
2012 gtk_tree_view_append_column (treeview, column);
2016 get_monospace_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
2023 ds_put_byte_multiple (&s, '0', char_cnt);
2024 ds_put_byte (&s, ' ');
2025 width = get_string_width (treeview, renderer, ds_cstr (&s));
2032 get_string_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
2036 g_object_set (G_OBJECT (renderer), "text", string, (void *) NULL);
2037 gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
2038 NULL, NULL, NULL, &width, NULL);
2042 static GtkTreeViewColumn *
2043 make_data_column (struct import_assistant *ia, GtkTreeView *tree_view,
2044 bool input, gint dict_idx)
2046 struct variable *var = NULL;
2047 struct column *column = NULL;
2049 gint content_width, header_width;
2050 GtkTreeViewColumn *tree_column;
2054 column = &ia->separators.columns[dict_idx];
2056 var = dict_get_var (ia->formats.dict, dict_idx);
2058 name = escape_underscores (input ? column->name : var_get_name (var));
2059 char_cnt = input ? column->width : var_get_print_format (var)->w;
2060 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
2062 header_width = get_string_width (tree_view, ia->asst.prop_renderer,
2065 tree_column = gtk_tree_view_column_new ();
2066 g_object_set_data (G_OBJECT (tree_column), "column-number",
2067 GINT_TO_POINTER (dict_idx));
2068 gtk_tree_view_column_set_title (tree_column, name);
2069 gtk_tree_view_column_pack_start (tree_column, ia->asst.fixed_renderer,
2071 gtk_tree_view_column_set_cell_data_func (
2072 tree_column, ia->asst.fixed_renderer,
2073 input ? render_input_cell : render_output_cell, ia, NULL);
2074 gtk_tree_view_column_set_sizing (tree_column, GTK_TREE_VIEW_COLUMN_FIXED);
2075 gtk_tree_view_column_set_fixed_width (tree_column, MAX (content_width,
2083 static GtkTreeView *
2084 create_data_tree_view (bool input, GtkContainer *parent,
2085 struct import_assistant *ia)
2087 GtkTreeView *tree_view;
2090 make_tree_view (ia, ia->first_line.skip_lines, &tree_view);
2091 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (tree_view),
2092 GTK_SELECTION_NONE);
2094 for (i = 0; i < ia->separators.column_cnt; i++)
2095 gtk_tree_view_append_column (tree_view,
2096 make_data_column (ia, tree_view, input, i));
2098 g_object_set (G_OBJECT (tree_view), "has-tooltip", TRUE, (void *) NULL);
2099 g_signal_connect (tree_view, "query-tooltip",
2100 G_CALLBACK (input ? on_query_input_tooltip
2101 : on_query_output_tooltip), ia);
2102 gtk_tree_view_set_fixed_height_mode (tree_view, true);
2104 gtk_container_add (parent, GTK_WIDGET (tree_view));
2105 gtk_widget_show (GTK_WIDGET (tree_view));
2110 /* Increments the "watch cursor" level, setting the cursor for
2111 the assistant window to a watch face to indicate to the user
2112 that the ongoing operation may take some time. */
2114 push_watch_cursor (struct import_assistant *ia)
2116 if (++ia->asst.watch_cursor == 1)
2118 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2119 GdkDisplay *display = gtk_widget_get_display (widget);
2120 GdkCursor *cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
2121 gdk_window_set_cursor (widget->window, cursor);
2122 gdk_cursor_unref (cursor);
2123 gdk_display_flush (display);
2127 /* Decrements the "watch cursor" level. If the level reaches
2128 zero, the cursor is reset to its default shape. */
2130 pop_watch_cursor (struct import_assistant *ia)
2132 if (--ia->asst.watch_cursor == 0)
2134 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2135 gdk_window_set_cursor (widget->window, NULL);