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"
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/line-reader.h"
37 #include "libpspp/message.h"
38 #include "ui/gui/builder-wrapper.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/pspp-sheet-selection.h"
44 #include "ui/gui/pspp-sheet-view.h"
45 #include "ui/gui/psppire-data-window.h"
46 #include "ui/gui/psppire-dialog.h"
47 #include "ui/gui/psppire-encoding-selector.h"
48 #include "ui/gui/psppire-empty-list-store.h"
49 #include "ui/gui/psppire-scanf.h"
50 #include "ui/gui/psppire-var-sheet.h"
51 #include "ui/gui/psppire-var-store.h"
52 #include "ui/syntax-gen.h"
55 #include "gl/intprops.h"
56 #include "gl/xalloc.h"
59 #define _(msgid) gettext (msgid)
60 #define N_(msgid) msgid
62 struct import_assistant;
64 /* The file to be imported. */
67 char *file_name; /* File name. */
68 gchar *encoding; /* Encoding. */
69 unsigned long int total_lines; /* Number of lines in file. */
70 bool total_is_exact; /* Is total_lines exact (or an estimate)? */
72 /* The first several lines of the file. */
76 static bool init_file (struct import_assistant *, GtkWindow *parent);
77 static void destroy_file (struct import_assistant *);
79 /* The main body of the GTK+ assistant and related data. */
83 GtkAssistant *assistant;
85 GtkWidget *paste_button;
86 GtkWidget *reset_button;
90 GtkCellRenderer *prop_renderer;
91 GtkCellRenderer *fixed_renderer;
93 static void init_assistant (struct import_assistant *, GtkWindow *);
94 static void destroy_assistant (struct import_assistant *);
95 static GtkWidget *add_page_to_assistant (struct import_assistant *,
97 GtkAssistantPageType);
99 /* The introduction page of the assistant. */
103 GtkWidget *all_cases_button;
104 GtkWidget *n_cases_button;
105 GtkWidget *n_cases_spin;
106 GtkWidget *percent_button;
107 GtkWidget *percent_spin;
109 static void init_intro_page (struct import_assistant *);
110 static void reset_intro_page (struct import_assistant *);
112 /* Page where the user chooses the first line of data. */
113 struct first_line_page
115 int skip_lines; /* Number of initial lines to skip? */
116 bool variable_names; /* Variable names above first line of data? */
119 PsppSheetView *tree_view;
120 GtkWidget *variable_names_cb;
122 static void init_first_line_page (struct import_assistant *);
123 static void reset_first_line_page (struct import_assistant *);
125 /* Page where the user chooses field separators. */
126 struct separators_page
128 /* How to break lines into columns. */
129 struct string separators; /* Field separators. */
130 struct string quotes; /* Quote characters. */
131 bool escape; /* Doubled quotes yield a quote mark? */
133 /* The columns produced thereby. */
134 struct column *columns; /* Information about each column. */
135 size_t column_cnt; /* Number of columns. */
138 GtkWidget *custom_cb;
139 GtkWidget *custom_entry;
141 GtkWidget *quote_combo;
142 GtkEntry *quote_entry;
143 GtkWidget *escape_cb;
144 PsppSheetView *fields_tree_view;
146 /* The columns that the separators divide the data into. */
149 /* Variable name for this column. This is the variable name
150 used on the separators page; it can be overridden by the
151 user on the formats page. */
154 /* Maximum length of any row in this column. */
157 /* Contents of this column: contents[row] is the contents for
160 A null substring indicates a missing column for that row
161 (because the line contains an insufficient number of
164 contents[] elements may be substrings of the lines[]
165 strings that represent the whole lines of the file, to
166 save memory. Other elements are dynamically allocated
167 with ss_alloc_substring. */
168 struct substring *contents;
170 static void init_separators_page (struct import_assistant *);
171 static void destroy_separators_page (struct import_assistant *);
172 static void prepare_separators_page (struct import_assistant *);
173 static void reset_separators_page (struct import_assistant *);
175 /* Page where the user verifies and adjusts input formats. */
178 struct dictionary *dict;
181 PsppSheetView *data_tree_view;
182 PsppireDict *psppire_dict;
183 struct variable **modified_vars;
184 size_t modified_var_cnt;
186 static void init_formats_page (struct import_assistant *);
187 static void destroy_formats_page (struct import_assistant *);
188 static void prepare_formats_page (struct import_assistant *);
189 static void reset_formats_page (struct import_assistant *);
191 struct import_assistant
194 struct assistant asst;
195 struct intro_page intro;
196 struct first_line_page first_line;
197 struct separators_page separators;
198 struct formats_page formats;
201 static void apply_dict (const struct dictionary *, struct string *);
202 static char *generate_syntax (const struct import_assistant *);
204 static gboolean get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
205 const struct import_assistant *,
206 size_t *row, size_t *column);
207 static void make_tree_view (const struct import_assistant *ia,
209 PsppSheetView **tree_view);
210 static void add_line_number_column (const struct import_assistant *,
212 static gint get_monospace_width (PsppSheetView *, GtkCellRenderer *,
214 static gint get_string_width (PsppSheetView *, GtkCellRenderer *,
216 static PsppSheetViewColumn *make_data_column (struct import_assistant *,
217 PsppSheetView *, bool input,
219 static PsppSheetView *create_data_tree_view (bool input, GtkContainer *parent,
220 struct import_assistant *);
221 static void push_watch_cursor (struct import_assistant *);
222 static void pop_watch_cursor (struct import_assistant *);
224 /* Pops up the Text Data Import assistant. */
226 text_data_import_assistant (PsppireDataWindow *dw)
228 GtkWindow *parent_window = GTK_WINDOW (dw);
229 struct import_assistant *ia;
231 ia = xzalloc (sizeof *ia);
232 if (!init_file (ia, parent_window))
238 init_assistant (ia, parent_window);
239 init_intro_page (ia);
240 init_first_line_page (ia);
241 init_separators_page (ia);
242 init_formats_page (ia);
244 gtk_widget_show_all (GTK_WIDGET (ia->asst.assistant));
246 ia->asst.main_loop = g_main_loop_new (NULL, false);
247 g_main_loop_run (ia->asst.main_loop);
248 g_main_loop_unref (ia->asst.main_loop);
250 switch (ia->asst.response)
252 case GTK_RESPONSE_APPLY:
253 free (execute_syntax_string (dw, generate_syntax (ia)));
255 case PSPPIRE_RESPONSE_PASTE:
256 free (paste_syntax_to_window (generate_syntax (ia)));
262 destroy_formats_page (ia);
263 destroy_separators_page (ia);
264 destroy_assistant (ia);
269 /* Emits PSPP syntax to S that applies the dictionary attributes
270 (such as missing values and value labels) of the variables in
273 apply_dict (const struct dictionary *dict, struct string *s)
275 size_t var_cnt = dict_get_var_cnt (dict);
278 for (i = 0; i < var_cnt; i++)
280 struct variable *var = dict_get_var (dict, i);
281 const char *name = var_get_name (var);
282 enum val_type type = var_get_type (var);
283 int width = var_get_width (var);
284 enum measure measure = var_get_measure (var);
285 enum alignment alignment = var_get_alignment (var);
286 const struct fmt_spec *format = var_get_print_format (var);
288 if (var_has_missing_values (var))
290 const struct missing_values *mv = var_get_missing_values (var);
293 syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
294 for (j = 0; j < mv_n_values (mv); j++)
297 ds_put_cstr (s, ", ");
298 syntax_gen_value (s, mv_get_value (mv, j), width, format);
301 if (mv_has_range (mv))
304 if (mv_has_value (mv))
305 ds_put_cstr (s, ", ");
306 mv_get_range (mv, &low, &high);
307 syntax_gen_num_range (s, low, high, format);
309 ds_put_cstr (s, ").\n");
311 if (var_has_value_labels (var))
313 const struct val_labs *vls = var_get_value_labels (var);
314 const struct val_lab **labels = val_labs_sorted (vls);
315 size_t n_labels = val_labs_count (vls);
318 syntax_gen_pspp (s, "VALUE LABELS %ss", name);
319 for (i = 0; i < n_labels; i++)
321 const struct val_lab *vl = labels[i];
322 ds_put_cstr (s, "\n ");
323 syntax_gen_value (s, &vl->value, width, format);
324 ds_put_byte (s, ' ');
325 syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl)));
328 ds_put_cstr (s, ".\n");
330 if (var_has_label (var))
331 syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
332 name, var_get_label (var));
333 if (measure != var_default_measure (type))
334 syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
336 (measure == MEASURE_NOMINAL ? "NOMINAL"
337 : measure == MEASURE_ORDINAL ? "ORDINAL"
339 if (alignment != var_default_alignment (type))
340 syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
342 (alignment == ALIGN_LEFT ? "LEFT"
343 : alignment == ALIGN_CENTRE ? "CENTER"
345 if (var_get_display_width (var) != var_default_display_width (width))
346 syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
347 name, var_get_display_width (var));
351 /* Generates and returns PSPP syntax to execute the import
352 operation described by IA. The caller must free the syntax
355 generate_syntax (const struct import_assistant *ia)
357 struct string s = DS_EMPTY_INITIALIZER;
366 if (ia->file.encoding && strcmp (ia->file.encoding, "Auto"))
367 syntax_gen_pspp (&s, " /ENCODING=%sq\n", ia->file.encoding);
368 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
369 ia->intro.n_cases_button)))
370 ds_put_format (&s, " /IMPORTCASES=FIRST %d\n",
371 gtk_spin_button_get_value_as_int (
372 GTK_SPIN_BUTTON (ia->intro.n_cases_spin)));
373 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
374 ia->intro.percent_button)))
375 ds_put_format (&s, " /IMPORTCASES=PERCENT %d\n",
376 gtk_spin_button_get_value_as_int (
377 GTK_SPIN_BUTTON (ia->intro.percent_spin)));
379 ds_put_cstr (&s, " /IMPORTCASES=ALL\n");
381 " /ARRANGEMENT=DELIMITED\n"
383 if (ia->first_line.skip_lines > 0)
384 ds_put_format (&s, " /FIRSTCASE=%d\n", ia->first_line.skip_lines + 1);
385 ds_put_cstr (&s, " /DELIMITERS=\"");
386 if (ds_find_byte (&ia->separators.separators, '\t') != SIZE_MAX)
387 ds_put_cstr (&s, "\\t");
388 if (ds_find_byte (&ia->separators.separators, '\\') != SIZE_MAX)
389 ds_put_cstr (&s, "\\\\");
390 for (i = 0; i < ds_length (&ia->separators.separators); i++)
392 char c = ds_at (&ia->separators.separators, i);
394 ds_put_cstr (&s, "\"\"");
395 else if (c != '\t' && c != '\\')
398 ds_put_cstr (&s, "\"\n");
399 if (!ds_is_empty (&ia->separators.quotes))
400 syntax_gen_pspp (&s, " /QUALIFIER=%sq\n", ds_cstr (&ia->separators.quotes));
401 if (!ds_is_empty (&ia->separators.quotes) && ia->separators.escape)
402 ds_put_cstr (&s, " /ESCAPE\n");
403 ds_put_cstr (&s, " /VARIABLES=\n");
405 var_cnt = dict_get_var_cnt (ia->formats.dict);
406 for (i = 0; i < var_cnt; i++)
408 struct variable *var = dict_get_var (ia->formats.dict, i);
409 char format_string[FMT_STRING_LEN_MAX + 1];
410 fmt_to_string (var_get_print_format (var), format_string);
411 ds_put_format (&s, " %s %s%s\n",
412 var_get_name (var), format_string,
413 i == var_cnt - 1 ? "." : "");
416 apply_dict (ia->formats.dict, &s);
421 /* Choosing a file and reading it. */
423 static char *choose_file (GtkWindow *parent_window, gchar **encodingp);
425 /* Obtains the file to import from the user and initializes IA's
426 file substructure. PARENT_WINDOW must be the window to use
427 as the file chooser window's parent.
429 Returns true if successful, false if the file name could not
430 be obtained or the file could not be read. */
432 init_file (struct import_assistant *ia, GtkWindow *parent_window)
434 struct file *file = &ia->file;
435 enum { MAX_PREVIEW_LINES = 1000 }; /* Max number of lines to read. */
436 enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
437 struct line_reader *reader;
439 file->file_name = choose_file (parent_window, &file->encoding);
440 if (file->file_name == NULL)
443 reader = line_reader_for_file (file->encoding, file->file_name, O_RDONLY);
446 msg (ME, _("Could not open `%s': %s"),
447 file->file_name, strerror (errno));
451 file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines);
452 for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++)
454 struct string *line = &file->lines[file->line_cnt];
456 ds_init_empty (line);
457 if (!line_reader_read (reader, line, MAX_LINE_LEN + 1)
458 || ds_length (line) > MAX_LINE_LEN)
460 if (line_reader_eof (reader))
462 else if (line_reader_error (reader))
463 msg (ME, _("Error reading `%s': %s"),
464 file->file_name, strerror (line_reader_error (reader)));
466 msg (ME, _("Failed to read `%s', because it contains a line "
467 "over %d bytes long and therefore appears not to be "
469 file->file_name, MAX_LINE_LEN);
470 line_reader_close (reader);
476 if (file->line_cnt == 0)
478 msg (ME, _("`%s' is empty."), file->file_name);
479 line_reader_close (reader);
484 /* Estimate the number of lines in the file. */
485 if (file->line_cnt < MAX_PREVIEW_LINES)
486 file->total_lines = file->line_cnt;
490 off_t position = line_reader_tell (reader);
491 if (fstat (line_reader_fileno (reader), &s) == 0 && position > 0)
492 file->total_lines = (double) file->line_cnt / position * s.st_size;
494 file->total_lines = 0;
497 line_reader_close (reader);
502 /* Frees IA's file substructure. */
504 destroy_file (struct import_assistant *ia)
506 struct file *f = &ia->file;
509 for (i = 0; i < f->line_cnt; i++)
510 ds_destroy (&f->lines[i]);
512 g_free (f->file_name);
513 g_free (f->encoding);
516 /* Obtains the file to read from the user. If successful, returns the name of
517 the file and stores the user's chosen encoding for the file into *ENCODINGP.
518 The caller must free each of these strings with g_free().
520 On failure, stores a null pointer and stores NULL in *ENCODINGP.
522 PARENT_WINDOW must be the window to use as the file chooser window's
525 choose_file (GtkWindow *parent_window, gchar **encodingp)
528 GtkFileFilter *filter = NULL;
530 GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
532 GTK_FILE_CHOOSER_ACTION_OPEN,
533 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
534 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
537 g_object_set (dialog, "local-only", FALSE, NULL);
539 filter = gtk_file_filter_new ();
540 gtk_file_filter_set_name (filter, _("Text files"));
541 gtk_file_filter_add_mime_type (filter, "text/*");
542 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
544 filter = gtk_file_filter_new ();
545 gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
546 gtk_file_filter_add_pattern (filter, "*.txt");
547 gtk_file_filter_add_pattern (filter, "*.TXT");
548 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
550 filter = gtk_file_filter_new ();
551 gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
552 gtk_file_filter_add_mime_type (filter, "text/plain");
553 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
555 filter = gtk_file_filter_new ();
556 gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
557 gtk_file_filter_add_mime_type (filter, "text/csv");
558 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
560 /* I've never encountered one of these, but it's listed here:
561 http://www.iana.org/assignments/media-types/text/tab-separated-values */
562 filter = gtk_file_filter_new ();
563 gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
564 gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
565 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
567 gtk_file_chooser_set_extra_widget (
568 GTK_FILE_CHOOSER (dialog), psppire_encoding_selector_new ("Auto", true));
570 filter = gtk_file_filter_new ();
571 gtk_file_filter_set_name (filter, _("All Files"));
572 gtk_file_filter_add_pattern (filter, "*");
573 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
575 switch (gtk_dialog_run (GTK_DIALOG (dialog)))
577 case GTK_RESPONSE_ACCEPT:
578 file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
579 *encodingp = psppire_encoding_selector_get_encoding (
580 gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (dialog)));
587 gtk_widget_destroy (dialog);
594 static void close_assistant (struct import_assistant *, int response);
595 static void on_prepare (GtkAssistant *assistant, GtkWidget *page,
596 struct import_assistant *);
597 static void on_cancel (GtkAssistant *assistant, struct import_assistant *);
598 static void on_close (GtkAssistant *assistant, struct import_assistant *);
599 static void on_paste (GtkButton *button, struct import_assistant *);
600 static void on_reset (GtkButton *button, struct import_assistant *);
601 static void close_assistant (struct import_assistant *, int response);
603 /* Initializes IA's asst substructure. PARENT_WINDOW must be the
604 window to use as the assistant window's parent. */
606 init_assistant (struct import_assistant *ia, GtkWindow *parent_window)
608 struct assistant *a = &ia->asst;
610 a->builder = builder_new ("text-data-import.ui");
611 a->assistant = GTK_ASSISTANT (gtk_assistant_new ());
612 g_signal_connect (a->assistant, "prepare", G_CALLBACK (on_prepare), ia);
613 g_signal_connect (a->assistant, "cancel", G_CALLBACK (on_cancel), ia);
614 g_signal_connect (a->assistant, "close", G_CALLBACK (on_close), ia);
615 a->paste_button = gtk_button_new_from_stock (GTK_STOCK_PASTE);
616 gtk_assistant_add_action_widget (a->assistant, a->paste_button);
617 g_signal_connect (a->paste_button, "clicked", G_CALLBACK (on_paste), ia);
618 a->reset_button = gtk_button_new_from_stock ("pspp-stock-reset");
619 gtk_assistant_add_action_widget (a->assistant, a->reset_button);
620 g_signal_connect (a->reset_button, "clicked", G_CALLBACK (on_reset), ia);
621 gtk_window_set_title (GTK_WINDOW (a->assistant),
622 _("Importing Delimited Text Data"));
623 gtk_window_set_transient_for (GTK_WINDOW (a->assistant), parent_window);
624 gtk_window_set_icon_name (GTK_WINDOW (a->assistant), "pspp");
626 a->prop_renderer = gtk_cell_renderer_text_new ();
627 g_object_ref_sink (a->prop_renderer);
628 a->fixed_renderer = gtk_cell_renderer_text_new ();
629 g_object_ref_sink (a->fixed_renderer);
630 g_object_set (G_OBJECT (a->fixed_renderer),
631 "family", "Monospace",
635 /* Frees IA's asst substructure. */
637 destroy_assistant (struct import_assistant *ia)
639 struct assistant *a = &ia->asst;
641 g_object_unref (a->prop_renderer);
642 g_object_unref (a->fixed_renderer);
643 g_object_unref (a->builder);
646 /* Appends a page of the given TYPE, with PAGE as its content, to
647 the GtkAssistant encapsulated by IA. Returns the GtkWidget
648 that represents the page. */
650 add_page_to_assistant (struct import_assistant *ia,
651 GtkWidget *page, GtkAssistantPageType type)
657 title = gtk_window_get_title (GTK_WINDOW (page));
658 title_copy = xstrdup (title ? title : "");
660 content = gtk_bin_get_child (GTK_BIN (page));
662 g_object_ref (content);
663 gtk_container_remove (GTK_CONTAINER (page), content);
665 gtk_widget_destroy (page);
667 gtk_assistant_append_page (ia->asst.assistant, content);
668 gtk_assistant_set_page_type (ia->asst.assistant, content, type);
669 gtk_assistant_set_page_title (ia->asst.assistant, content, title_copy);
670 gtk_assistant_set_page_complete (ia->asst.assistant, content, true);
677 /* Called just before PAGE is displayed as the current page of
678 ASSISTANT, this updates IA content according to the new
681 on_prepare (GtkAssistant *assistant, GtkWidget *page,
682 struct import_assistant *ia)
685 if (gtk_assistant_get_page_type (assistant, page)
686 == GTK_ASSISTANT_PAGE_CONFIRM)
687 gtk_widget_grab_focus (assistant->apply);
689 gtk_widget_grab_focus (assistant->forward);
691 if (page == ia->separators.page)
692 prepare_separators_page (ia);
693 else if (page == ia->formats.page)
694 prepare_formats_page (ia);
696 gtk_widget_show (ia->asst.reset_button);
697 if (page == ia->formats.page)
698 gtk_widget_show (ia->asst.paste_button);
700 gtk_widget_hide (ia->asst.paste_button);
703 /* Called when the Cancel button in the assistant is clicked. */
705 on_cancel (GtkAssistant *assistant, struct import_assistant *ia)
707 close_assistant (ia, GTK_RESPONSE_CANCEL);
710 /* Called when the Apply button on the last page of the assistant
713 on_close (GtkAssistant *assistant, struct import_assistant *ia)
715 close_assistant (ia, GTK_RESPONSE_APPLY);
718 /* Called when the Paste button on the last page of the assistant
721 on_paste (GtkButton *button, struct import_assistant *ia)
723 close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
726 /* Called when the Reset button is clicked. */
728 on_reset (GtkButton *button, struct import_assistant *ia)
730 gint page_num = gtk_assistant_get_current_page (ia->asst.assistant);
731 GtkWidget *page = gtk_assistant_get_nth_page (ia->asst.assistant, page_num);
733 if (page == ia->intro.page)
734 reset_intro_page (ia);
735 else if (page == ia->first_line.page)
736 reset_first_line_page (ia);
737 else if (page == ia->separators.page)
738 reset_separators_page (ia);
739 else if (page == ia->formats.page)
740 reset_formats_page (ia);
743 /* Causes the assistant to close, returning RESPONSE for
744 interpretation by text_data_import_assistant. */
746 close_assistant (struct import_assistant *ia, int response)
748 ia->asst.response = response;
749 g_main_loop_quit (ia->asst.main_loop);
750 gtk_widget_hide (GTK_WIDGET (ia->asst.assistant));
753 /* The "intro" page of the assistant. */
755 static void on_intro_amount_changed (struct import_assistant *);
757 /* Initializes IA's intro substructure. */
759 init_intro_page (struct import_assistant *ia)
761 GtkBuilder *builder = ia->asst.builder;
762 struct intro_page *p = &ia->intro;
764 GtkWidget *hbox_n_cases ;
765 GtkWidget *hbox_percent ;
769 p->n_cases_spin = gtk_spin_button_new_with_range (0, INT_MAX, 100);
771 hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &p->n_cases_spin);
773 table = get_widget_assert (builder, "button-table");
775 gtk_table_attach_defaults (GTK_TABLE (table), hbox_n_cases,
779 p->percent_spin = gtk_spin_button_new_with_range (0, 100, 10);
781 hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"), &p->percent_spin);
783 gtk_table_attach_defaults (GTK_TABLE (table), hbox_percent,
787 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Intro"),
788 GTK_ASSISTANT_PAGE_INTRO);
790 p->all_cases_button = get_widget_assert (builder, "import-all-cases");
792 p->n_cases_button = get_widget_assert (builder, "import-n-cases");
794 p->percent_button = get_widget_assert (builder, "import-percent");
796 g_signal_connect_swapped (p->all_cases_button, "toggled",
797 G_CALLBACK (on_intro_amount_changed), ia);
798 g_signal_connect_swapped (p->n_cases_button, "toggled",
799 G_CALLBACK (on_intro_amount_changed), ia);
800 g_signal_connect_swapped (p->percent_button, "toggled",
801 G_CALLBACK (on_intro_amount_changed), ia);
803 on_intro_amount_changed (ia);
806 ds_put_cstr (&s, _("This assistant will guide you through the process of "
807 "importing data into PSPP from a text file with one line "
808 "per case, in which fields are separated by tabs, "
809 "commas, or other delimiters.\n\n"));
810 if (ia->file.total_is_exact)
812 &s, ngettext ("The selected file contains %zu line of text. ",
813 "The selected file contains %zu lines of text. ",
816 else if (ia->file.total_lines > 0)
820 "The selected file contains approximately %lu line of text. ",
821 "The selected file contains approximately %lu lines of text. ",
822 ia->file.total_lines),
823 ia->file.total_lines);
826 "Only the first %zu line of the file will be shown for "
827 "preview purposes in the following screens. ",
828 "Only the first %zu lines of the file will be shown for "
829 "preview purposes in the following screens. ",
833 ds_put_cstr (&s, _("You may choose below how much of the file should "
834 "actually be imported."));
835 gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
840 /* Resets IA's intro page to its initial state. */
842 reset_intro_page (struct import_assistant *ia)
844 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->intro.all_cases_button),
848 /* Called when one of the radio buttons is clicked. */
850 on_intro_amount_changed (struct import_assistant *ia)
852 struct intro_page *p = &ia->intro;
854 gtk_widget_set_sensitive (p->n_cases_spin,
855 gtk_toggle_button_get_active (
856 GTK_TOGGLE_BUTTON (p->n_cases_button)));
858 gtk_widget_set_sensitive (p->percent_spin,
859 gtk_toggle_button_get_active (
860 GTK_TOGGLE_BUTTON (p->percent_button)));
863 /* The "first line" page of the assistant. */
865 static PsppSheetView *create_lines_tree_view (GtkContainer *parent_window,
866 struct import_assistant *);
867 static void on_first_line_change (PsppSheetSelection *,
868 struct import_assistant *);
869 static void on_variable_names_cb_toggle (GtkToggleButton *,
870 struct import_assistant *);
871 static void set_first_line (struct import_assistant *);
872 static void get_first_line (struct import_assistant *);
874 /* Initializes IA's first_line substructure. */
876 init_first_line_page (struct import_assistant *ia)
878 struct first_line_page *p = &ia->first_line;
879 GtkBuilder *builder = ia->asst.builder;
881 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "FirstLine"),
882 GTK_ASSISTANT_PAGE_CONTENT);
883 gtk_widget_destroy (get_widget_assert (builder, "first-line"));
884 p->tree_view = create_lines_tree_view (
885 GTK_CONTAINER (get_widget_assert (builder, "first-line-scroller")), ia);
886 p->variable_names_cb = get_widget_assert (builder, "variable-names");
887 pspp_sheet_selection_set_mode (
888 pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (p->tree_view)),
889 PSPP_SHEET_SELECTION_BROWSE);
890 pspp_sheet_view_set_rubber_banding (PSPP_SHEET_VIEW (p->tree_view), TRUE);
892 g_signal_connect (pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (p->tree_view)),
893 "changed", G_CALLBACK (on_first_line_change), ia);
894 g_signal_connect (p->variable_names_cb, "toggled",
895 G_CALLBACK (on_variable_names_cb_toggle), ia);
898 /* Resets the first_line page to its initial content. */
900 reset_first_line_page (struct import_assistant *ia)
902 ia->first_line.skip_lines = 0;
903 ia->first_line.variable_names = false;
908 render_line (PsppSheetViewColumn *tree_column,
909 GtkCellRenderer *cell,
910 GtkTreeModel *tree_model,
914 gint row = empty_list_store_iter_to_row (iter);
915 struct string *lines;
917 lines = g_object_get_data (G_OBJECT (tree_model), "lines");
918 g_return_if_fail (lines != NULL);
920 g_object_set (cell, "text", ds_cstr (&lines[row]), NULL);
924 /* Creates and returns a tree view that contains each of the
925 lines in IA's file as a row. */
926 static PsppSheetView *
927 create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia)
929 PsppSheetView *tree_view;
930 PsppSheetViewColumn *column;
931 size_t max_line_length;
932 gint content_width, header_width;
934 const gchar *title = _("Text");
936 make_tree_view (ia, 0, &tree_view);
938 column = pspp_sheet_view_column_new_with_attributes (
939 title, ia->asst.fixed_renderer, (void *) NULL);
940 pspp_sheet_view_column_set_cell_data_func (column, ia->asst.fixed_renderer,
941 render_line, NULL, NULL);
942 pspp_sheet_view_column_set_resizable (column, TRUE);
945 for (i = 0; i < ia->file.line_cnt; i++)
947 size_t w = ds_length (&ia->file.lines[i]);
948 max_line_length = MAX (max_line_length, w);
951 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
953 header_width = get_string_width (tree_view, ia->asst.prop_renderer, title);
954 pspp_sheet_view_column_set_fixed_width (column, MAX (content_width,
956 pspp_sheet_view_append_column (tree_view, column);
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 (PsppSheetSelection *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 pspp_sheet_view_set_cursor (PSPP_SHEET_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 PsppSheetSelection *selection;
1006 GtkTreeModel *model;
1008 selection = pspp_sheet_view_get_selection (ia->first_line.tree_view);
1009 if (pspp_sheet_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 (PsppSheetViewColumn *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 = PSPP_SHEET_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 (PsppSheetViewColumn *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 = PSPP_SHEET_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 GtkBin *vars_scroller;
1643 GtkWidget *old_var_sheet;
1644 PsppireVarSheet *var_sheet;
1645 struct separators_page *s = &ia->separators;
1646 struct formats_page *p = &ia->formats;
1647 struct fmt_guesser *fg;
1648 unsigned long int number = 0;
1651 push_watch_cursor (ia);
1653 dict = dict_create (get_default_encoding ());
1654 fg = fmt_guesser_create ();
1655 for (column_idx = 0; column_idx < s->column_cnt; column_idx++)
1657 struct variable *modified_var;
1659 modified_var = (column_idx < p->modified_var_cnt
1660 ? p->modified_vars[column_idx] : NULL);
1661 if (modified_var == NULL)
1663 struct column *column = &s->columns[column_idx];
1664 struct variable *var;
1665 struct fmt_spec format;
1669 /* Choose variable name. */
1670 name = dict_make_unique_var_name (dict, column->name, &number);
1672 /* Choose variable format. */
1673 fmt_guesser_clear (fg);
1674 for (row = ia->first_line.skip_lines; row < ia->file.line_cnt; row++)
1675 fmt_guesser_add (fg, column->contents[row]);
1676 fmt_guesser_guess (fg, &format);
1677 fmt_fix_input (&format);
1679 /* Create variable. */
1680 var = dict_create_var_assert (dict, name, fmt_var_width (&format));
1681 var_set_both_formats (var, &format);
1689 name = dict_make_unique_var_name (dict, var_get_name (modified_var),
1691 dict_clone_var_as_assert (dict, modified_var, name);
1695 fmt_guesser_destroy (fg);
1697 psppire_dict = psppire_dict_new_from_dict (dict);
1698 g_signal_connect (psppire_dict, "variable_changed",
1699 G_CALLBACK (on_variable_change), ia);
1700 ia->formats.dict = dict;
1701 ia->formats.psppire_dict = psppire_dict;
1703 /* XXX: PsppireVarStore doesn't hold a reference to
1704 psppire_dict for now, but it should. After it does, we
1705 should g_object_ref the psppire_dict here, since we also
1706 hold a reference via ia->formats.dict. */
1707 var_sheet = PSPPIRE_VAR_SHEET (psppire_var_sheet_new ());
1708 g_object_set (var_sheet,
1709 "dictionary", psppire_dict,
1710 "may-create-vars", FALSE,
1711 "may-delete-vars", FALSE,
1712 "format-use", FMT_FOR_INPUT,
1713 "enable-grid-lines", PSPP_SHEET_VIEW_GRID_LINES_BOTH,
1716 vars_scroller = GTK_BIN (get_widget_assert (ia->asst.builder, "vars-scroller"));
1717 old_var_sheet = gtk_bin_get_child (vars_scroller);
1718 if (old_var_sheet != NULL)
1719 gtk_widget_destroy (old_var_sheet);
1720 gtk_container_add (GTK_CONTAINER (vars_scroller), GTK_WIDGET (var_sheet));
1721 gtk_widget_show (GTK_WIDGET (var_sheet));
1723 gtk_widget_destroy (GTK_WIDGET (ia->formats.data_tree_view));
1724 ia->formats.data_tree_view = create_data_tree_view (
1726 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "data-scroller")),
1729 pop_watch_cursor (ia);
1732 /* Clears the set of user-modified variables from IA's formats
1733 substructure. This discards user modifications to variable
1734 formats, thereby causing formats to revert to their
1737 clear_modified_vars (struct import_assistant *ia)
1739 struct formats_page *p = &ia->formats;
1742 for (i = 0; i < p->modified_var_cnt; i++)
1743 var_destroy (p->modified_vars[i]);
1744 free (p->modified_vars);
1745 p->modified_vars = NULL;
1746 p->modified_var_cnt = 0;
1749 /* Resets the formats page to its defaults, discarding user
1752 reset_formats_page (struct import_assistant *ia)
1754 clear_modified_vars (ia);
1755 prepare_formats_page (ia);
1758 /* Called when the user changes one of the variables in the
1761 on_variable_change (PsppireDict *dict, int dict_idx,
1762 struct import_assistant *ia)
1764 struct formats_page *p = &ia->formats;
1765 PsppSheetView *tv = ia->formats.data_tree_view;
1766 gint column_idx = dict_idx + 1;
1768 push_watch_cursor (ia);
1770 /* Remove previous column and replace with new column. */
1771 pspp_sheet_view_remove_column (tv, pspp_sheet_view_get_column (tv, column_idx));
1772 pspp_sheet_view_insert_column (tv, make_data_column (ia, tv, false, dict_idx),
1775 /* Save a copy of the modified variable in modified_vars, so
1776 that its attributes will be preserved if we back up to the
1777 previous page with the Prev button and then come back
1779 if (dict_idx >= p->modified_var_cnt)
1782 p->modified_vars = xnrealloc (p->modified_vars, dict_idx + 1,
1783 sizeof *p->modified_vars);
1784 for (i = 0; i <= dict_idx; i++)
1785 p->modified_vars[i] = NULL;
1786 p->modified_var_cnt = dict_idx + 1;
1788 if (p->modified_vars[dict_idx])
1789 var_destroy (p->modified_vars[dict_idx]);
1790 p->modified_vars[dict_idx]
1791 = var_clone (psppire_dict_get_variable (dict, dict_idx));
1793 pop_watch_cursor (ia);
1796 /* Parses the contents of the field at (ROW,COLUMN) according to
1797 its variable format. If OUTPUTP is non-null, then *OUTPUTP
1798 receives the formatted output for that field (which must be
1799 freed with free). If TOOLTIPP is non-null, then *TOOLTIPP
1800 receives a message suitable for use in a tooltip, if one is
1801 needed, or a null pointer otherwise. Returns true if a
1802 tooltip message is needed, otherwise false. */
1804 parse_field (struct import_assistant *ia,
1805 size_t row, size_t column,
1806 char **outputp, char **tooltipp)
1808 struct substring field;
1810 struct variable *var;
1811 const struct fmt_spec *in;
1812 struct fmt_spec out;
1816 field = ia->separators.columns[column].contents[row];
1817 var = dict_get_var (ia->formats.dict, column);
1818 value_init (&val, var_get_width (var));
1819 in = var_get_print_format (var);
1820 out = fmt_for_output_from_input (in);
1822 if (field.string != NULL)
1826 error = data_in (field, "UTF-8", in->type, &val, var_get_width (var),
1827 dict_get_encoding (ia->formats.dict));
1830 tooltip = xasprintf (_("Cannot parse field content `%.*s' as "
1832 (int) field.length, field.string,
1833 fmt_name (in->type), error);
1839 tooltip = xstrdup (_("This input line has too few separators "
1840 "to fill in this field."));
1841 value_set_missing (&val, var_get_width (var));
1843 if (outputp != NULL)
1845 *outputp = data_out (&val, dict_get_encoding (ia->formats.dict), &out);
1847 value_destroy (&val, var_get_width (var));
1849 ok = tooltip == NULL;
1850 if (tooltipp != NULL)
1851 *tooltipp = tooltip;
1857 /* Called to render one of the cells in the data preview tree
1860 render_output_cell (PsppSheetViewColumn *tree_column,
1861 GtkCellRenderer *cell,
1862 GtkTreeModel *model,
1866 struct import_assistant *ia = ia_;
1868 GValue gvalue = { 0, };
1871 ok = parse_field (ia,
1872 (empty_list_store_iter_to_row (iter)
1873 + ia->first_line.skip_lines),
1874 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1878 g_value_init (&gvalue, G_TYPE_STRING);
1879 g_value_take_string (&gvalue, output);
1880 g_object_set_property (G_OBJECT (cell), "text", &gvalue);
1881 g_value_unset (&gvalue);
1884 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1887 "background", "red",
1888 "background-set", TRUE,
1892 /* Called to render a tooltip for one of the cells in the data
1893 preview tree view. */
1895 on_query_output_tooltip (GtkWidget *widget, gint wx, gint wy,
1896 gboolean keyboard_mode UNUSED,
1897 GtkTooltip *tooltip, struct import_assistant *ia)
1902 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1905 if (parse_field (ia, row, column, NULL, &text))
1908 gtk_tooltip_set_text (tooltip, text);
1913 /* Utility functions used by multiple pages of the assistant. */
1916 get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
1917 const struct import_assistant *ia,
1918 size_t *row, size_t *column)
1920 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
1924 PsppSheetViewColumn *tree_column;
1925 GtkTreeModel *tree_model;
1928 /* Check that WIDGET is really visible on the screen before we
1929 do anything else. This is a bug fix for a sticky situation:
1930 when text_data_import_assistant() returns, it frees the data
1931 necessary to compose the tool tip message, but there may be
1932 a tool tip under preparation at that point (even if there is
1933 no visible tool tip) that will call back into us a little
1934 bit later. Perhaps the correct solution to this problem is
1935 to make the data related to the tool tips part of a GObject
1936 that only gets destroyed when all references are released,
1937 but this solution appears to be effective too. */
1938 if (!gtk_widget_get_mapped (widget))
1941 pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
1943 if (!pspp_sheet_view_get_path_at_pos (tree_view, bx, by,
1944 &path, &tree_column, NULL, NULL))
1947 *column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1950 tree_model = pspp_sheet_view_get_model (tree_view);
1951 ok = gtk_tree_model_get_iter (tree_model, &iter, path);
1952 gtk_tree_path_free (path);
1956 *row = empty_list_store_iter_to_row (&iter) + ia->first_line.skip_lines;
1961 make_tree_view (const struct import_assistant *ia,
1963 PsppSheetView **tree_view)
1965 GtkTreeModel *model;
1967 *tree_view = PSPP_SHEET_VIEW (pspp_sheet_view_new ());
1968 pspp_sheet_view_set_grid_lines (*tree_view, PSPP_SHEET_VIEW_GRID_LINES_BOTH);
1969 model = GTK_TREE_MODEL (psppire_empty_list_store_new (
1970 ia->file.line_cnt - first_line));
1971 g_object_set_data (G_OBJECT (model), "lines", ia->file.lines + first_line);
1972 g_object_set_data (G_OBJECT (model), "first-line",
1973 GINT_TO_POINTER (first_line));
1974 pspp_sheet_view_set_model (*tree_view, model);
1975 g_object_unref (model);
1977 add_line_number_column (ia, *tree_view);
1981 render_line_number (PsppSheetViewColumn *tree_column,
1982 GtkCellRenderer *cell,
1983 GtkTreeModel *tree_model,
1987 gint row = empty_list_store_iter_to_row (iter);
1988 char s[INT_BUFSIZE_BOUND (int)];
1991 first_line = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_model),
1993 sprintf (s, "%d", first_line + row);
1994 g_object_set (cell, "text", s, NULL);
1998 add_line_number_column (const struct import_assistant *ia,
1999 PsppSheetView *treeview)
2001 PsppSheetViewColumn *column;
2003 column = pspp_sheet_view_column_new_with_attributes (
2004 _("Line"), ia->asst.prop_renderer, (void *) NULL);
2005 pspp_sheet_view_column_set_fixed_width (
2006 column, get_monospace_width (treeview, ia->asst.prop_renderer, 5));
2007 pspp_sheet_view_column_set_resizable (column, TRUE);
2008 pspp_sheet_view_column_set_cell_data_func (column, ia->asst.prop_renderer,
2009 render_line_number, NULL, NULL);
2010 pspp_sheet_view_append_column (treeview, column);
2014 get_monospace_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
2021 ds_put_byte_multiple (&s, '0', char_cnt);
2022 ds_put_byte (&s, ' ');
2023 width = get_string_width (treeview, renderer, ds_cstr (&s));
2030 get_string_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
2034 g_object_set (G_OBJECT (renderer), "text", string, (void *) NULL);
2035 gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
2036 NULL, NULL, NULL, &width, NULL);
2040 static PsppSheetViewColumn *
2041 make_data_column (struct import_assistant *ia, PsppSheetView *tree_view,
2042 bool input, gint dict_idx)
2044 struct variable *var = NULL;
2045 struct column *column = NULL;
2047 gint content_width, header_width;
2048 PsppSheetViewColumn *tree_column;
2052 column = &ia->separators.columns[dict_idx];
2054 var = dict_get_var (ia->formats.dict, dict_idx);
2056 name = escape_underscores (input ? column->name : var_get_name (var));
2057 char_cnt = input ? column->width : var_get_print_format (var)->w;
2058 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
2060 header_width = get_string_width (tree_view, ia->asst.prop_renderer,
2063 tree_column = pspp_sheet_view_column_new ();
2064 g_object_set_data (G_OBJECT (tree_column), "column-number",
2065 GINT_TO_POINTER (dict_idx));
2066 pspp_sheet_view_column_set_title (tree_column, name);
2067 pspp_sheet_view_column_pack_start (tree_column, ia->asst.fixed_renderer,
2069 pspp_sheet_view_column_set_cell_data_func (
2070 tree_column, ia->asst.fixed_renderer,
2071 input ? render_input_cell : render_output_cell, ia, NULL);
2072 pspp_sheet_view_column_set_fixed_width (tree_column, MAX (content_width,
2074 pspp_sheet_view_column_set_resizable (tree_column, TRUE);
2081 static PsppSheetView *
2082 create_data_tree_view (bool input, GtkContainer *parent,
2083 struct import_assistant *ia)
2085 PsppSheetView *tree_view;
2088 make_tree_view (ia, ia->first_line.skip_lines, &tree_view);
2089 pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (tree_view),
2090 PSPP_SHEET_SELECTION_NONE);
2092 for (i = 0; i < ia->separators.column_cnt; i++)
2093 pspp_sheet_view_append_column (tree_view,
2094 make_data_column (ia, tree_view, input, i));
2096 g_object_set (G_OBJECT (tree_view), "has-tooltip", TRUE, (void *) NULL);
2097 g_signal_connect (tree_view, "query-tooltip",
2098 G_CALLBACK (input ? on_query_input_tooltip
2099 : on_query_output_tooltip), ia);
2101 gtk_container_add (parent, GTK_WIDGET (tree_view));
2102 gtk_widget_show (GTK_WIDGET (tree_view));
2107 /* Increments the "watch cursor" level, setting the cursor for
2108 the assistant window to a watch face to indicate to the user
2109 that the ongoing operation may take some time. */
2111 push_watch_cursor (struct import_assistant *ia)
2113 if (++ia->asst.watch_cursor == 1)
2115 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2116 GdkDisplay *display = gtk_widget_get_display (widget);
2117 GdkCursor *cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
2118 gdk_window_set_cursor (widget->window, cursor);
2119 gdk_cursor_unref (cursor);
2120 gdk_display_flush (display);
2124 /* Decrements the "watch cursor" level. If the level reaches
2125 zero, the cursor is reset to its default shape. */
2127 pop_watch_cursor (struct import_assistant *ia)
2129 if (--ia->asst.watch_cursor == 0)
2131 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2132 gdk_window_set_cursor (widget->window, NULL);