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/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 PsppSheetView *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 PsppSheetView *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 PsppSheetView *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 PsppSheetView **tree_view);
209 static void add_line_number_column (const struct import_assistant *,
211 static gint get_monospace_width (PsppSheetView *, GtkCellRenderer *,
213 static gint get_string_width (PsppSheetView *, GtkCellRenderer *,
215 static PsppSheetViewColumn *make_data_column (struct import_assistant *,
216 PsppSheetView *, bool input,
218 static PsppSheetView *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;
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 ds_init_empty (&input);
452 file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines);
453 for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++)
456 if (!line_reader_read (reader, &input, MAX_LINE_LEN + 1)
457 || ds_length (&input) > 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 ds_init_cstr (&file->lines[file->line_cnt],
476 recode_string ("UTF-8", line_reader_get_encoding (reader),
477 ds_cstr (&input), ds_length (&input)));
481 if (file->line_cnt == 0)
483 msg (ME, _("`%s' is empty."), file->file_name);
484 line_reader_close (reader);
489 /* Estimate the number of lines in the file. */
490 if (file->line_cnt < MAX_PREVIEW_LINES)
491 file->total_lines = file->line_cnt;
495 off_t position = line_reader_tell (reader);
496 if (fstat (line_reader_fileno (reader), &s) == 0 && position > 0)
497 file->total_lines = (double) file->line_cnt / position * s.st_size;
499 file->total_lines = 0;
502 line_reader_close (reader);
507 /* Frees IA's file substructure. */
509 destroy_file (struct import_assistant *ia)
511 struct file *f = &ia->file;
514 for (i = 0; i < f->line_cnt; i++)
515 ds_destroy (&f->lines[i]);
517 g_free (f->file_name);
518 g_free (f->encoding);
521 /* Obtains the file to read from the user. If successful, returns the name of
522 the file and stores the user's chosen encoding for the file into *ENCODINGP.
523 The caller must free each of these strings with g_free().
525 On failure, stores a null pointer and stores NULL in *ENCODINGP.
527 PARENT_WINDOW must be the window to use as the file chooser window's
530 choose_file (GtkWindow *parent_window, gchar **encodingp)
533 GtkFileFilter *filter = NULL;
535 GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
537 GTK_FILE_CHOOSER_ACTION_OPEN,
538 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
539 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
542 g_object_set (dialog, "local-only", FALSE, NULL);
544 filter = gtk_file_filter_new ();
545 gtk_file_filter_set_name (filter, _("Text files"));
546 gtk_file_filter_add_mime_type (filter, "text/*");
547 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
549 filter = gtk_file_filter_new ();
550 gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
551 gtk_file_filter_add_pattern (filter, "*.txt");
552 gtk_file_filter_add_pattern (filter, "*.TXT");
553 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
555 filter = gtk_file_filter_new ();
556 gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
557 gtk_file_filter_add_mime_type (filter, "text/plain");
558 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
560 filter = gtk_file_filter_new ();
561 gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
562 gtk_file_filter_add_mime_type (filter, "text/csv");
563 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
565 /* I've never encountered one of these, but it's listed here:
566 http://www.iana.org/assignments/media-types/text/tab-separated-values */
567 filter = gtk_file_filter_new ();
568 gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
569 gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
570 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
572 gtk_file_chooser_set_extra_widget (
573 GTK_FILE_CHOOSER (dialog), psppire_encoding_selector_new ("Auto", true));
575 filter = gtk_file_filter_new ();
576 gtk_file_filter_set_name (filter, _("All Files"));
577 gtk_file_filter_add_pattern (filter, "*");
578 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
580 switch (gtk_dialog_run (GTK_DIALOG (dialog)))
582 case GTK_RESPONSE_ACCEPT:
583 file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
584 *encodingp = psppire_encoding_selector_get_encoding (
585 gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (dialog)));
592 gtk_widget_destroy (dialog);
599 static void close_assistant (struct import_assistant *, int response);
600 static void on_prepare (GtkAssistant *assistant, GtkWidget *page,
601 struct import_assistant *);
602 static void on_cancel (GtkAssistant *assistant, struct import_assistant *);
603 static void on_close (GtkAssistant *assistant, struct import_assistant *);
604 static void on_paste (GtkButton *button, struct import_assistant *);
605 static void on_reset (GtkButton *button, struct import_assistant *);
606 static void close_assistant (struct import_assistant *, int response);
608 /* Initializes IA's asst substructure. PARENT_WINDOW must be the
609 window to use as the assistant window's parent. */
611 init_assistant (struct import_assistant *ia, GtkWindow *parent_window)
613 struct assistant *a = &ia->asst;
615 a->builder = builder_new ("text-data-import.ui");
616 a->assistant = GTK_ASSISTANT (gtk_assistant_new ());
617 g_signal_connect (a->assistant, "prepare", G_CALLBACK (on_prepare), ia);
618 g_signal_connect (a->assistant, "cancel", G_CALLBACK (on_cancel), ia);
619 g_signal_connect (a->assistant, "close", G_CALLBACK (on_close), ia);
620 a->paste_button = gtk_button_new_from_stock (GTK_STOCK_PASTE);
621 gtk_assistant_add_action_widget (a->assistant, a->paste_button);
622 g_signal_connect (a->paste_button, "clicked", G_CALLBACK (on_paste), ia);
623 a->reset_button = gtk_button_new_from_stock ("pspp-stock-reset");
624 gtk_assistant_add_action_widget (a->assistant, a->reset_button);
625 g_signal_connect (a->reset_button, "clicked", G_CALLBACK (on_reset), ia);
626 gtk_window_set_title (GTK_WINDOW (a->assistant),
627 _("Importing Delimited Text Data"));
628 gtk_window_set_transient_for (GTK_WINDOW (a->assistant), parent_window);
629 gtk_window_set_icon_name (GTK_WINDOW (a->assistant), "pspp");
631 a->prop_renderer = gtk_cell_renderer_text_new ();
632 g_object_ref_sink (a->prop_renderer);
633 a->fixed_renderer = gtk_cell_renderer_text_new ();
634 g_object_ref_sink (a->fixed_renderer);
635 g_object_set (G_OBJECT (a->fixed_renderer),
636 "family", "Monospace",
640 /* Frees IA's asst substructure. */
642 destroy_assistant (struct import_assistant *ia)
644 struct assistant *a = &ia->asst;
646 g_object_unref (a->prop_renderer);
647 g_object_unref (a->fixed_renderer);
648 g_object_unref (a->builder);
651 /* Appends a page of the given TYPE, with PAGE as its content, to
652 the GtkAssistant encapsulated by IA. Returns the GtkWidget
653 that represents the page. */
655 add_page_to_assistant (struct import_assistant *ia,
656 GtkWidget *page, GtkAssistantPageType type)
662 title = gtk_window_get_title (GTK_WINDOW (page));
663 title_copy = xstrdup (title ? title : "");
665 content = gtk_bin_get_child (GTK_BIN (page));
667 g_object_ref (content);
668 gtk_container_remove (GTK_CONTAINER (page), content);
670 gtk_widget_destroy (page);
672 gtk_assistant_append_page (ia->asst.assistant, content);
673 gtk_assistant_set_page_type (ia->asst.assistant, content, type);
674 gtk_assistant_set_page_title (ia->asst.assistant, content, title_copy);
675 gtk_assistant_set_page_complete (ia->asst.assistant, content, true);
682 /* Called just before PAGE is displayed as the current page of
683 ASSISTANT, this updates IA content according to the new
686 on_prepare (GtkAssistant *assistant, GtkWidget *page,
687 struct import_assistant *ia)
690 if (gtk_assistant_get_page_type (assistant, page)
691 == GTK_ASSISTANT_PAGE_CONFIRM)
692 gtk_widget_grab_focus (assistant->apply);
694 gtk_widget_grab_focus (assistant->forward);
696 if (page == ia->separators.page)
697 prepare_separators_page (ia);
698 else if (page == ia->formats.page)
699 prepare_formats_page (ia);
701 gtk_widget_show (ia->asst.reset_button);
702 if (page == ia->formats.page)
703 gtk_widget_show (ia->asst.paste_button);
705 gtk_widget_hide (ia->asst.paste_button);
708 /* Called when the Cancel button in the assistant is clicked. */
710 on_cancel (GtkAssistant *assistant, struct import_assistant *ia)
712 close_assistant (ia, GTK_RESPONSE_CANCEL);
715 /* Called when the Apply button on the last page of the assistant
718 on_close (GtkAssistant *assistant, struct import_assistant *ia)
720 close_assistant (ia, GTK_RESPONSE_APPLY);
723 /* Called when the Paste button on the last page of the assistant
726 on_paste (GtkButton *button, struct import_assistant *ia)
728 close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
731 /* Called when the Reset button is clicked. */
733 on_reset (GtkButton *button, struct import_assistant *ia)
735 gint page_num = gtk_assistant_get_current_page (ia->asst.assistant);
736 GtkWidget *page = gtk_assistant_get_nth_page (ia->asst.assistant, page_num);
738 if (page == ia->intro.page)
739 reset_intro_page (ia);
740 else if (page == ia->first_line.page)
741 reset_first_line_page (ia);
742 else if (page == ia->separators.page)
743 reset_separators_page (ia);
744 else if (page == ia->formats.page)
745 reset_formats_page (ia);
748 /* Causes the assistant to close, returning RESPONSE for
749 interpretation by text_data_import_assistant. */
751 close_assistant (struct import_assistant *ia, int response)
753 ia->asst.response = response;
754 g_main_loop_quit (ia->asst.main_loop);
755 gtk_widget_hide (GTK_WIDGET (ia->asst.assistant));
758 /* The "intro" page of the assistant. */
760 static void on_intro_amount_changed (struct import_assistant *);
762 /* Initializes IA's intro substructure. */
764 init_intro_page (struct import_assistant *ia)
766 GtkBuilder *builder = ia->asst.builder;
767 struct intro_page *p = &ia->intro;
769 GtkWidget *hbox_n_cases ;
770 GtkWidget *hbox_percent ;
774 p->n_cases_spin = gtk_spin_button_new_with_range (0, INT_MAX, 100);
776 hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &p->n_cases_spin);
778 table = get_widget_assert (builder, "button-table");
780 gtk_table_attach_defaults (GTK_TABLE (table), hbox_n_cases,
784 p->percent_spin = gtk_spin_button_new_with_range (0, 100, 10);
786 hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"), &p->percent_spin);
788 gtk_table_attach_defaults (GTK_TABLE (table), hbox_percent,
792 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Intro"),
793 GTK_ASSISTANT_PAGE_INTRO);
795 p->all_cases_button = get_widget_assert (builder, "import-all-cases");
797 p->n_cases_button = get_widget_assert (builder, "import-n-cases");
799 p->percent_button = get_widget_assert (builder, "import-percent");
801 g_signal_connect_swapped (p->all_cases_button, "toggled",
802 G_CALLBACK (on_intro_amount_changed), ia);
803 g_signal_connect_swapped (p->n_cases_button, "toggled",
804 G_CALLBACK (on_intro_amount_changed), ia);
805 g_signal_connect_swapped (p->percent_button, "toggled",
806 G_CALLBACK (on_intro_amount_changed), ia);
808 on_intro_amount_changed (ia);
811 ds_put_cstr (&s, _("This assistant will guide you through the process of "
812 "importing data into PSPP from a text file with one line "
813 "per case, in which fields are separated by tabs, "
814 "commas, or other delimiters.\n\n"));
815 if (ia->file.total_is_exact)
817 &s, ngettext ("The selected file contains %zu line of text. ",
818 "The selected file contains %zu lines of text. ",
821 else if (ia->file.total_lines > 0)
825 "The selected file contains approximately %lu line of text. ",
826 "The selected file contains approximately %lu lines of text. ",
827 ia->file.total_lines),
828 ia->file.total_lines);
831 "Only the first %zu line of the file will be shown for "
832 "preview purposes in the following screens. ",
833 "Only the first %zu lines of the file will be shown for "
834 "preview purposes in the following screens. ",
838 ds_put_cstr (&s, _("You may choose below how much of the file should "
839 "actually be imported."));
840 gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
845 /* Resets IA's intro page to its initial state. */
847 reset_intro_page (struct import_assistant *ia)
849 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->intro.all_cases_button),
853 /* Called when one of the radio buttons is clicked. */
855 on_intro_amount_changed (struct import_assistant *ia)
857 struct intro_page *p = &ia->intro;
859 gtk_widget_set_sensitive (p->n_cases_spin,
860 gtk_toggle_button_get_active (
861 GTK_TOGGLE_BUTTON (p->n_cases_button)));
863 gtk_widget_set_sensitive (p->percent_spin,
864 gtk_toggle_button_get_active (
865 GTK_TOGGLE_BUTTON (p->percent_button)));
868 /* The "first line" page of the assistant. */
870 static PsppSheetView *create_lines_tree_view (GtkContainer *parent_window,
871 struct import_assistant *);
872 static void on_first_line_change (PsppSheetSelection *,
873 struct import_assistant *);
874 static void on_variable_names_cb_toggle (GtkToggleButton *,
875 struct import_assistant *);
876 static void set_first_line (struct import_assistant *);
877 static void get_first_line (struct import_assistant *);
879 /* Initializes IA's first_line substructure. */
881 init_first_line_page (struct import_assistant *ia)
883 struct first_line_page *p = &ia->first_line;
884 GtkBuilder *builder = ia->asst.builder;
886 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "FirstLine"),
887 GTK_ASSISTANT_PAGE_CONTENT);
888 gtk_widget_destroy (get_widget_assert (builder, "first-line"));
889 p->tree_view = create_lines_tree_view (
890 GTK_CONTAINER (get_widget_assert (builder, "first-line-scroller")), ia);
891 p->variable_names_cb = get_widget_assert (builder, "variable-names");
892 pspp_sheet_selection_set_mode (
893 pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (p->tree_view)),
894 PSPP_SHEET_SELECTION_BROWSE);
895 pspp_sheet_view_set_rubber_banding (PSPP_SHEET_VIEW (p->tree_view), TRUE);
897 g_signal_connect (pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (p->tree_view)),
898 "changed", G_CALLBACK (on_first_line_change), ia);
899 g_signal_connect (p->variable_names_cb, "toggled",
900 G_CALLBACK (on_variable_names_cb_toggle), ia);
903 /* Resets the first_line page to its initial content. */
905 reset_first_line_page (struct import_assistant *ia)
907 ia->first_line.skip_lines = 0;
908 ia->first_line.variable_names = false;
913 render_line (PsppSheetViewColumn *tree_column,
914 GtkCellRenderer *cell,
915 GtkTreeModel *tree_model,
919 gint row = empty_list_store_iter_to_row (iter);
920 struct string *lines;
922 lines = g_object_get_data (G_OBJECT (tree_model), "lines");
923 g_return_if_fail (lines != NULL);
925 g_object_set (cell, "text", ds_cstr (&lines[row]), NULL);
929 /* Creates and returns a tree view that contains each of the
930 lines in IA's file as a row. */
931 static PsppSheetView *
932 create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia)
934 PsppSheetView *tree_view;
935 PsppSheetViewColumn *column;
936 size_t max_line_length;
937 gint content_width, header_width;
939 const gchar *title = _("Text");
941 make_tree_view (ia, 0, &tree_view);
943 column = pspp_sheet_view_column_new_with_attributes (
944 title, ia->asst.fixed_renderer, (void *) NULL);
945 pspp_sheet_view_column_set_cell_data_func (column, ia->asst.fixed_renderer,
946 render_line, NULL, NULL);
947 pspp_sheet_view_column_set_resizable (column, TRUE);
950 for (i = 0; i < ia->file.line_cnt; i++)
952 size_t w = ds_length (&ia->file.lines[i]);
953 max_line_length = MAX (max_line_length, w);
956 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
958 header_width = get_string_width (tree_view, ia->asst.prop_renderer, title);
959 pspp_sheet_view_column_set_fixed_width (column, MAX (content_width,
961 pspp_sheet_view_append_column (tree_view, column);
963 gtk_container_add (parent, GTK_WIDGET (tree_view));
964 gtk_widget_show (GTK_WIDGET (tree_view));
969 /* Called when the line selected in the first_line tree view
972 on_first_line_change (PsppSheetSelection *selection UNUSED,
973 struct import_assistant *ia)
978 /* Called when the checkbox that indicates whether variable
979 names are in the row above the first line is toggled. */
981 on_variable_names_cb_toggle (GtkToggleButton *variable_names_cb UNUSED,
982 struct import_assistant *ia)
987 /* Sets the widgets to match IA's first_line substructure. */
989 set_first_line (struct import_assistant *ia)
993 path = gtk_tree_path_new_from_indices (ia->first_line.skip_lines, -1);
994 pspp_sheet_view_set_cursor (PSPP_SHEET_VIEW (ia->first_line.tree_view),
996 gtk_tree_path_free (path);
998 gtk_toggle_button_set_active (
999 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb),
1000 ia->first_line.variable_names);
1001 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
1002 ia->first_line.skip_lines > 0);
1005 /* Sets IA's first_line substructure to match the widgets. */
1007 get_first_line (struct import_assistant *ia)
1009 PsppSheetSelection *selection;
1011 GtkTreeModel *model;
1013 selection = pspp_sheet_view_get_selection (ia->first_line.tree_view);
1014 if (pspp_sheet_selection_get_selected (selection, &model, &iter))
1016 GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
1017 int row = gtk_tree_path_get_indices (path)[0];
1018 gtk_tree_path_free (path);
1020 ia->first_line.skip_lines = row;
1021 ia->first_line.variable_names =
1022 (ia->first_line.skip_lines > 0
1023 && gtk_toggle_button_get_active (
1024 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb)));
1026 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
1027 ia->first_line.skip_lines > 0);
1030 /* The "separators" page of the assistant. */
1032 static void revise_fields_preview (struct import_assistant *ia);
1033 static void choose_likely_separators (struct import_assistant *ia);
1034 static void find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
1035 const char *targets, const char *def,
1036 struct string *result);
1037 static void clear_fields (struct import_assistant *ia);
1038 static void revise_fields_preview (struct import_assistant *);
1039 static void set_separators (struct import_assistant *);
1040 static void get_separators (struct import_assistant *);
1041 static void on_separators_custom_entry_notify (GObject *UNUSED,
1043 struct import_assistant *);
1044 static void on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1045 struct import_assistant *);
1046 static void on_quote_combo_change (GtkComboBox *combo,
1047 struct import_assistant *);
1048 static void on_quote_cb_toggle (GtkToggleButton *quote_cb,
1049 struct import_assistant *);
1050 static void on_separator_toggle (GtkToggleButton *, struct import_assistant *);
1051 static void render_input_cell (PsppSheetViewColumn *tree_column,
1052 GtkCellRenderer *cell,
1053 GtkTreeModel *model, GtkTreeIter *iter,
1055 static gboolean on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1056 gboolean keyboard_mode UNUSED,
1057 GtkTooltip *tooltip,
1058 struct import_assistant *);
1060 /* A common field separator and its identifying name. */
1063 const char *name; /* Name (for use with get_widget_assert). */
1064 int c; /* Separator character. */
1067 /* All the separators in the dialog box. */
1068 static const struct separator separators[] =
1080 #define SEPARATOR_CNT (sizeof separators / sizeof *separators)
1083 set_quote_list (GtkComboBoxEntry *cb)
1085 GtkListStore *list = gtk_list_store_new (1, G_TYPE_STRING);
1088 const gchar *seperator[3] = {"'\"", "\'", "\""};
1090 for (i = 0; i < 3; i++)
1092 const gchar *s = seperator[i];
1094 /* Add a new row to the model */
1095 gtk_list_store_append (list, &iter);
1096 gtk_list_store_set (list, &iter,
1102 gtk_combo_box_set_model (GTK_COMBO_BOX (cb), GTK_TREE_MODEL (list));
1103 g_object_unref (list);
1105 gtk_combo_box_entry_set_text_column (cb, 0);
1108 /* Initializes IA's separators substructure. */
1110 init_separators_page (struct import_assistant *ia)
1112 GtkBuilder *builder = ia->asst.builder;
1113 struct separators_page *p = &ia->separators;
1116 choose_likely_separators (ia);
1118 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Separators"),
1119 GTK_ASSISTANT_PAGE_CONTENT);
1120 p->custom_cb = get_widget_assert (builder, "custom-cb");
1121 p->custom_entry = get_widget_assert (builder, "custom-entry");
1122 p->quote_combo = get_widget_assert (builder, "quote-combo");
1123 p->quote_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (p->quote_combo)));
1124 p->quote_cb = get_widget_assert (builder, "quote-cb");
1125 p->escape_cb = get_widget_assert (builder, "escape");
1127 set_separators (ia);
1128 set_quote_list (GTK_COMBO_BOX_ENTRY (p->quote_combo));
1129 p->fields_tree_view = PSPP_SHEET_VIEW (get_widget_assert (builder, "fields"));
1130 g_signal_connect (p->quote_combo, "changed",
1131 G_CALLBACK (on_quote_combo_change), ia);
1132 g_signal_connect (p->quote_cb, "toggled",
1133 G_CALLBACK (on_quote_cb_toggle), ia);
1134 g_signal_connect (p->custom_entry, "notify::text",
1135 G_CALLBACK (on_separators_custom_entry_notify), ia);
1136 g_signal_connect (p->custom_cb, "toggled",
1137 G_CALLBACK (on_separators_custom_cb_toggle), ia);
1138 for (i = 0; i < SEPARATOR_CNT; i++)
1139 g_signal_connect (get_widget_assert (builder, separators[i].name),
1140 "toggled", G_CALLBACK (on_separator_toggle), ia);
1141 g_signal_connect (p->escape_cb, "toggled",
1142 G_CALLBACK (on_separator_toggle), ia);
1145 /* Frees IA's separators substructure. */
1147 destroy_separators_page (struct import_assistant *ia)
1149 struct separators_page *s = &ia->separators;
1151 ds_destroy (&s->separators);
1152 ds_destroy (&s->quotes);
1156 /* Called just before the separators page becomes visible in the
1159 prepare_separators_page (struct import_assistant *ia)
1161 revise_fields_preview (ia);
1164 /* Called when the Reset button is clicked on the separators
1165 page, resets the separators to the defaults. */
1167 reset_separators_page (struct import_assistant *ia)
1169 choose_likely_separators (ia);
1170 set_separators (ia);
1173 /* Frees and clears the column data in IA's separators
1176 clear_fields (struct import_assistant *ia)
1178 struct separators_page *s = &ia->separators;
1180 if (s->column_cnt > 0)
1185 for (row = 0; row < ia->file.line_cnt; row++)
1187 const struct string *line = &ia->file.lines[row];
1188 const char *line_start = ds_data (line);
1189 const char *line_end = ds_end (line);
1191 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1193 char *s = ss_data (col->contents[row]);
1194 if (!(s >= line_start && s <= line_end))
1195 ss_dealloc (&col->contents[row]);
1199 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1202 free (col->contents);
1211 /* Breaks the file data in IA into columns based on the
1212 separators set in IA's separators substructure. */
1214 split_fields (struct import_assistant *ia)
1216 struct separators_page *s = &ia->separators;
1217 size_t columns_allocated;
1223 /* Is space in the set of separators? */
1224 space_sep = ss_find_byte (ds_ss (&s->separators), ' ') != SIZE_MAX;
1226 /* Split all the lines, not just those from
1227 ia->first_line.skip_lines on, so that we split the line that
1228 contains variables names if ia->first_line.variable_names is
1230 columns_allocated = 0;
1231 for (row = 0; row < ia->file.line_cnt; row++)
1233 struct string *line = &ia->file.lines[row];
1234 struct substring text = ds_ss (line);
1237 for (column_idx = 0; ; column_idx++)
1239 struct substring field;
1240 struct column *column;
1243 ss_ltrim (&text, ss_cstr (" "));
1244 if (ss_is_empty (text))
1246 if (column_idx != 0)
1250 else if (!ds_is_empty (&s->quotes)
1251 && ds_find_byte (&s->quotes, text.string[0]) != SIZE_MAX)
1253 int quote = ss_get_byte (&text);
1255 ss_get_until (&text, quote, &field);
1262 while ((c = ss_get_byte (&text)) != EOF)
1264 ds_put_byte (&s, c);
1265 else if (ss_match_byte (&text, quote))
1266 ds_put_byte (&s, quote);
1273 ss_get_bytes (&text, ss_cspan (text, ds_ss (&s->separators)),
1276 if (column_idx >= s->column_cnt)
1278 struct column *column;
1280 if (s->column_cnt >= columns_allocated)
1281 s->columns = x2nrealloc (s->columns, &columns_allocated,
1282 sizeof *s->columns);
1283 column = &s->columns[s->column_cnt++];
1284 column->name = NULL;
1286 column->contents = xcalloc (ia->file.line_cnt,
1287 sizeof *column->contents);
1289 column = &s->columns[column_idx];
1290 column->contents[row] = field;
1291 if (ss_length (field) > column->width)
1292 column->width = ss_length (field);
1295 ss_ltrim (&text, ss_cstr (" "));
1296 if (ss_is_empty (text))
1298 if (ss_find_byte (ds_ss (&s->separators), ss_first (text))
1300 ss_advance (&text, 1);
1305 /* Chooses a name for each column on the separators page */
1307 choose_column_names (struct import_assistant *ia)
1309 const struct first_line_page *f = &ia->first_line;
1310 struct separators_page *s = &ia->separators;
1311 struct dictionary *dict;
1312 unsigned long int generated_name_count = 0;
1316 dict = dict_create (get_default_encoding ());
1317 name_row = f->variable_names && f->skip_lines ? f->skip_lines : 0;
1318 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1322 hint = name_row ? ss_xstrdup (col->contents[name_row - 1]) : NULL;
1323 name = dict_make_unique_var_name (dict, hint, &generated_name_count);
1327 dict_create_var_assert (dict, name, 0);
1329 dict_destroy (dict);
1332 /* Picks the most likely separator and quote characters based on
1335 choose_likely_separators (struct import_assistant *ia)
1337 unsigned long int histogram[UCHAR_MAX + 1] = { 0 };
1340 /* Construct a histogram of all the characters used in the
1342 for (row = 0; row < ia->file.line_cnt; row++)
1344 struct substring line = ds_ss (&ia->file.lines[row]);
1345 size_t length = ss_length (line);
1347 for (i = 0; i < length; i++)
1348 histogram[(unsigned char) line.string[i]]++;
1351 find_commonest_chars (histogram, "\"'", "", &ia->separators.quotes);
1352 find_commonest_chars (histogram, ",;:/|!\t-", ",",
1353 &ia->separators.separators);
1354 ia->separators.escape = true;
1357 /* Chooses the most common character among those in TARGETS,
1358 based on the frequency data in HISTOGRAM, and stores it in
1359 RESULT. If there is a tie for the most common character among
1360 those in TARGETS, the earliest character is chosen. If none
1361 of the TARGETS appear at all, then DEF is used as a
1364 find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
1365 const char *targets, const char *def,
1366 struct string *result)
1368 unsigned char max = 0;
1369 unsigned long int max_count = 0;
1371 for (; *targets != '\0'; targets++)
1373 unsigned char c = *targets;
1374 unsigned long int count = histogram[c];
1375 if (count > max_count)
1384 ds_put_byte (result, max);
1387 ds_assign_cstr (result, def);
1390 /* Revises the contents of the fields tree view based on the
1391 currently chosen set of separators. */
1393 revise_fields_preview (struct import_assistant *ia)
1397 push_watch_cursor (ia);
1399 w = GTK_WIDGET (ia->separators.fields_tree_view);
1400 gtk_widget_destroy (w);
1401 get_separators (ia);
1403 choose_column_names (ia);
1404 ia->separators.fields_tree_view = create_data_tree_view (
1406 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "fields-scroller")),
1409 pop_watch_cursor (ia);
1412 /* Sets the widgets to match IA's separators substructure. */
1414 set_separators (struct import_assistant *ia)
1416 struct separators_page *s = &ia->separators;
1418 struct string custom;
1423 ds_init_empty (&custom);
1425 for (i = 0; i < ds_length (&s->separators); i++)
1427 unsigned char c = ds_at (&s->separators, i);
1430 for (j = 0; j < SEPARATOR_CNT; j++)
1432 const struct separator *s = &separators[j];
1440 ds_put_byte (&custom, c);
1444 for (i = 0; i < SEPARATOR_CNT; i++)
1446 const struct separator *s = &separators[i];
1447 GtkWidget *button = get_widget_assert (ia->asst.builder, s->name);
1448 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
1449 (seps & (1u << i)) != 0);
1451 any_custom = !ds_is_empty (&custom);
1452 gtk_entry_set_text (GTK_ENTRY (s->custom_entry), ds_cstr (&custom));
1453 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->custom_cb),
1455 gtk_widget_set_sensitive (s->custom_entry, any_custom);
1456 ds_destroy (&custom);
1458 any_quotes = !ds_is_empty (&s->quotes);
1460 gtk_entry_set_text (s->quote_entry,
1461 any_quotes ? ds_cstr (&s->quotes) : "\"");
1462 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->quote_cb),
1464 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->escape_cb),
1466 gtk_widget_set_sensitive (s->quote_combo, any_quotes);
1467 gtk_widget_set_sensitive (s->escape_cb, any_quotes);
1470 /* Sets IA's separators substructure to match the widgets. */
1472 get_separators (struct import_assistant *ia)
1474 struct separators_page *s = &ia->separators;
1477 ds_clear (&s->separators);
1478 for (i = 0; i < SEPARATOR_CNT; i++)
1480 const struct separator *sep = &separators[i];
1481 GtkWidget *button = get_widget_assert (ia->asst.builder, sep->name);
1482 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1483 ds_put_byte (&s->separators, sep->c);
1486 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->custom_cb)))
1487 ds_put_cstr (&s->separators,
1488 gtk_entry_get_text (GTK_ENTRY (s->custom_entry)));
1490 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->quote_cb)))
1492 gchar *text = gtk_combo_box_get_active_text (
1493 GTK_COMBO_BOX (s->quote_combo));
1494 ds_assign_cstr (&s->quotes, text);
1498 ds_clear (&s->quotes);
1499 s->escape = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->escape_cb));
1502 /* Called when the user changes the entry field for custom
1505 on_separators_custom_entry_notify (GObject *gobject UNUSED,
1506 GParamSpec *arg1 UNUSED,
1507 struct import_assistant *ia)
1509 revise_fields_preview (ia);
1512 /* Called when the user toggles the checkbox that enables custom
1515 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1516 struct import_assistant *ia)
1518 bool is_active = gtk_toggle_button_get_active (custom_cb);
1519 gtk_widget_set_sensitive (ia->separators.custom_entry, is_active);
1520 revise_fields_preview (ia);
1523 /* Called when the user changes the selection in the combo box
1524 that selects a quote character. */
1526 on_quote_combo_change (GtkComboBox *combo, struct import_assistant *ia)
1528 revise_fields_preview (ia);
1531 /* Called when the user toggles the checkbox that enables
1534 on_quote_cb_toggle (GtkToggleButton *quote_cb, struct import_assistant *ia)
1536 bool is_active = gtk_toggle_button_get_active (quote_cb);
1537 gtk_widget_set_sensitive (ia->separators.quote_combo, is_active);
1538 gtk_widget_set_sensitive (ia->separators.escape_cb, is_active);
1539 revise_fields_preview (ia);
1542 /* Called when the user toggles one of the separators
1545 on_separator_toggle (GtkToggleButton *toggle UNUSED,
1546 struct import_assistant *ia)
1548 revise_fields_preview (ia);
1551 /* Called to render one of the cells in the fields preview tree
1554 render_input_cell (PsppSheetViewColumn *tree_column, GtkCellRenderer *cell,
1555 GtkTreeModel *model, GtkTreeIter *iter,
1558 struct import_assistant *ia = ia_;
1559 struct substring field;
1563 column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1565 row = empty_list_store_iter_to_row (iter) + ia->first_line.skip_lines;
1566 field = ia->separators.columns[column].contents[row];
1567 if (field.string != NULL)
1569 GValue text = {0, };
1570 g_value_init (&text, G_TYPE_STRING);
1571 g_value_take_string (&text, ss_xstrdup (field));
1572 g_object_set_property (G_OBJECT (cell), "text", &text);
1573 g_value_unset (&text);
1574 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1579 "background", "red",
1580 "background-set", TRUE,
1584 /* Called to render a tooltip on one of the cells in the fields
1585 preview tree view. */
1587 on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1588 gboolean keyboard_mode UNUSED,
1589 GtkTooltip *tooltip, struct import_assistant *ia)
1593 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1596 if (ia->separators.columns[column].contents[row].string != NULL)
1599 gtk_tooltip_set_text (tooltip,
1600 _("This input line has too few separators "
1601 "to fill in this field."));
1605 /* The "formats" page of the assistant. */
1607 static void on_variable_change (PsppireDict *dict, int idx,
1608 struct import_assistant *);
1609 static void clear_modified_vars (struct import_assistant *);
1611 /* Initializes IA's formats substructure. */
1613 init_formats_page (struct import_assistant *ia)
1615 GtkBuilder *builder = ia->asst.builder;
1616 struct formats_page *p = &ia->formats;
1618 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Formats"),
1619 GTK_ASSISTANT_PAGE_CONFIRM);
1620 p->data_tree_view = PSPP_SHEET_VIEW (get_widget_assert (builder, "data"));
1621 p->modified_vars = NULL;
1622 p->modified_var_cnt = 0;
1626 /* Frees IA's formats substructure. */
1628 destroy_formats_page (struct import_assistant *ia)
1630 struct formats_page *p = &ia->formats;
1632 if (p->psppire_dict != NULL)
1634 dict_destroy (p->psppire_dict->dict);
1635 g_object_unref (p->psppire_dict);
1637 clear_modified_vars (ia);
1640 /* Called just before the formats page of the assistant is
1643 prepare_formats_page (struct import_assistant *ia)
1645 struct dictionary *dict;
1646 PsppireDict *psppire_dict;
1647 GtkBin *vars_scroller;
1648 GtkWidget *old_var_sheet;
1649 PsppireVarSheet *var_sheet;
1650 struct separators_page *s = &ia->separators;
1651 struct formats_page *p = &ia->formats;
1652 struct fmt_guesser *fg;
1653 unsigned long int number = 0;
1656 push_watch_cursor (ia);
1658 dict = dict_create (get_default_encoding ());
1659 fg = fmt_guesser_create ();
1660 for (column_idx = 0; column_idx < s->column_cnt; column_idx++)
1662 struct variable *modified_var;
1664 modified_var = (column_idx < p->modified_var_cnt
1665 ? p->modified_vars[column_idx] : NULL);
1666 if (modified_var == NULL)
1668 struct column *column = &s->columns[column_idx];
1669 struct variable *var;
1670 struct fmt_spec format;
1674 /* Choose variable name. */
1675 name = dict_make_unique_var_name (dict, column->name, &number);
1677 /* Choose variable format. */
1678 fmt_guesser_clear (fg);
1679 for (row = ia->first_line.skip_lines; row < ia->file.line_cnt; row++)
1680 fmt_guesser_add (fg, column->contents[row]);
1681 fmt_guesser_guess (fg, &format);
1682 fmt_fix_input (&format);
1684 /* Create variable. */
1685 var = dict_create_var_assert (dict, name, fmt_var_width (&format));
1686 var_set_both_formats (var, &format);
1694 name = dict_make_unique_var_name (dict, var_get_name (modified_var),
1696 dict_clone_var_as_assert (dict, modified_var, name);
1700 fmt_guesser_destroy (fg);
1702 psppire_dict = psppire_dict_new_from_dict (dict);
1703 g_signal_connect (psppire_dict, "variable_changed",
1704 G_CALLBACK (on_variable_change), ia);
1705 ia->formats.dict = dict;
1706 ia->formats.psppire_dict = psppire_dict;
1708 /* XXX: PsppireVarStore doesn't hold a reference to
1709 psppire_dict for now, but it should. After it does, we
1710 should g_object_ref the psppire_dict here, since we also
1711 hold a reference via ia->formats.dict. */
1712 var_sheet = PSPPIRE_VAR_SHEET (psppire_var_sheet_new ());
1713 g_object_set (var_sheet,
1714 "dictionary", psppire_dict,
1715 "may-create-vars", FALSE,
1716 "may-delete-vars", FALSE,
1717 "format-use", FMT_FOR_INPUT,
1718 "enable-grid-lines", PSPP_SHEET_VIEW_GRID_LINES_BOTH,
1721 vars_scroller = GTK_BIN (get_widget_assert (ia->asst.builder, "vars-scroller"));
1722 old_var_sheet = gtk_bin_get_child (vars_scroller);
1723 if (old_var_sheet != NULL)
1724 gtk_widget_destroy (old_var_sheet);
1725 gtk_container_add (GTK_CONTAINER (vars_scroller), GTK_WIDGET (var_sheet));
1726 gtk_widget_show (GTK_WIDGET (var_sheet));
1728 gtk_widget_destroy (GTK_WIDGET (ia->formats.data_tree_view));
1729 ia->formats.data_tree_view = create_data_tree_view (
1731 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "data-scroller")),
1734 pop_watch_cursor (ia);
1737 /* Clears the set of user-modified variables from IA's formats
1738 substructure. This discards user modifications to variable
1739 formats, thereby causing formats to revert to their
1742 clear_modified_vars (struct import_assistant *ia)
1744 struct formats_page *p = &ia->formats;
1747 for (i = 0; i < p->modified_var_cnt; i++)
1748 var_destroy (p->modified_vars[i]);
1749 free (p->modified_vars);
1750 p->modified_vars = NULL;
1751 p->modified_var_cnt = 0;
1754 /* Resets the formats page to its defaults, discarding user
1757 reset_formats_page (struct import_assistant *ia)
1759 clear_modified_vars (ia);
1760 prepare_formats_page (ia);
1763 /* Called when the user changes one of the variables in the
1766 on_variable_change (PsppireDict *dict, int dict_idx,
1767 struct import_assistant *ia)
1769 struct formats_page *p = &ia->formats;
1770 PsppSheetView *tv = ia->formats.data_tree_view;
1771 gint column_idx = dict_idx + 1;
1773 push_watch_cursor (ia);
1775 /* Remove previous column and replace with new column. */
1776 pspp_sheet_view_remove_column (tv, pspp_sheet_view_get_column (tv, column_idx));
1777 pspp_sheet_view_insert_column (tv, make_data_column (ia, tv, false, dict_idx),
1780 /* Save a copy of the modified variable in modified_vars, so
1781 that its attributes will be preserved if we back up to the
1782 previous page with the Prev button and then come back
1784 if (dict_idx >= p->modified_var_cnt)
1787 p->modified_vars = xnrealloc (p->modified_vars, dict_idx + 1,
1788 sizeof *p->modified_vars);
1789 for (i = 0; i <= dict_idx; i++)
1790 p->modified_vars[i] = NULL;
1791 p->modified_var_cnt = dict_idx + 1;
1793 if (p->modified_vars[dict_idx])
1794 var_destroy (p->modified_vars[dict_idx]);
1795 p->modified_vars[dict_idx]
1796 = var_clone (psppire_dict_get_variable (dict, dict_idx));
1798 pop_watch_cursor (ia);
1801 /* Parses the contents of the field at (ROW,COLUMN) according to
1802 its variable format. If OUTPUTP is non-null, then *OUTPUTP
1803 receives the formatted output for that field (which must be
1804 freed with free). If TOOLTIPP is non-null, then *TOOLTIPP
1805 receives a message suitable for use in a tooltip, if one is
1806 needed, or a null pointer otherwise. Returns true if a
1807 tooltip message is needed, otherwise false. */
1809 parse_field (struct import_assistant *ia,
1810 size_t row, size_t column,
1811 char **outputp, char **tooltipp)
1813 struct substring field;
1815 struct variable *var;
1816 const struct fmt_spec *in;
1817 struct fmt_spec out;
1821 field = ia->separators.columns[column].contents[row];
1822 var = dict_get_var (ia->formats.dict, column);
1823 value_init (&val, var_get_width (var));
1824 in = var_get_print_format (var);
1825 out = fmt_for_output_from_input (in);
1827 if (field.string != NULL)
1831 error = data_in (field, "UTF-8", in->type, &val, var_get_width (var),
1832 dict_get_encoding (ia->formats.dict));
1835 tooltip = xasprintf (_("Cannot parse field content `%.*s' as "
1837 (int) field.length, field.string,
1838 fmt_name (in->type), error);
1844 tooltip = xstrdup (_("This input line has too few separators "
1845 "to fill in this field."));
1846 value_set_missing (&val, var_get_width (var));
1848 if (outputp != NULL)
1850 *outputp = data_out (&val, dict_get_encoding (ia->formats.dict), &out);
1852 value_destroy (&val, var_get_width (var));
1854 ok = tooltip == NULL;
1855 if (tooltipp != NULL)
1856 *tooltipp = tooltip;
1862 /* Called to render one of the cells in the data preview tree
1865 render_output_cell (PsppSheetViewColumn *tree_column,
1866 GtkCellRenderer *cell,
1867 GtkTreeModel *model,
1871 struct import_assistant *ia = ia_;
1873 GValue gvalue = { 0, };
1876 ok = parse_field (ia,
1877 (empty_list_store_iter_to_row (iter)
1878 + ia->first_line.skip_lines),
1879 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1883 g_value_init (&gvalue, G_TYPE_STRING);
1884 g_value_take_string (&gvalue, output);
1885 g_object_set_property (G_OBJECT (cell), "text", &gvalue);
1886 g_value_unset (&gvalue);
1889 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1892 "background", "red",
1893 "background-set", TRUE,
1897 /* Called to render a tooltip for one of the cells in the data
1898 preview tree view. */
1900 on_query_output_tooltip (GtkWidget *widget, gint wx, gint wy,
1901 gboolean keyboard_mode UNUSED,
1902 GtkTooltip *tooltip, struct import_assistant *ia)
1907 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1910 if (parse_field (ia, row, column, NULL, &text))
1913 gtk_tooltip_set_text (tooltip, text);
1918 /* Utility functions used by multiple pages of the assistant. */
1921 get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
1922 const struct import_assistant *ia,
1923 size_t *row, size_t *column)
1925 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
1929 PsppSheetViewColumn *tree_column;
1930 GtkTreeModel *tree_model;
1933 /* Check that WIDGET is really visible on the screen before we
1934 do anything else. This is a bug fix for a sticky situation:
1935 when text_data_import_assistant() returns, it frees the data
1936 necessary to compose the tool tip message, but there may be
1937 a tool tip under preparation at that point (even if there is
1938 no visible tool tip) that will call back into us a little
1939 bit later. Perhaps the correct solution to this problem is
1940 to make the data related to the tool tips part of a GObject
1941 that only gets destroyed when all references are released,
1942 but this solution appears to be effective too. */
1943 if (!gtk_widget_get_mapped (widget))
1946 pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
1948 if (!pspp_sheet_view_get_path_at_pos (tree_view, bx, by,
1949 &path, &tree_column, NULL, NULL))
1952 *column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1955 tree_model = pspp_sheet_view_get_model (tree_view);
1956 ok = gtk_tree_model_get_iter (tree_model, &iter, path);
1957 gtk_tree_path_free (path);
1961 *row = empty_list_store_iter_to_row (&iter) + ia->first_line.skip_lines;
1966 make_tree_view (const struct import_assistant *ia,
1968 PsppSheetView **tree_view)
1970 GtkTreeModel *model;
1972 *tree_view = PSPP_SHEET_VIEW (pspp_sheet_view_new ());
1973 pspp_sheet_view_set_grid_lines (*tree_view, PSPP_SHEET_VIEW_GRID_LINES_BOTH);
1974 model = GTK_TREE_MODEL (psppire_empty_list_store_new (
1975 ia->file.line_cnt - first_line));
1976 g_object_set_data (G_OBJECT (model), "lines", ia->file.lines + first_line);
1977 g_object_set_data (G_OBJECT (model), "first-line",
1978 GINT_TO_POINTER (first_line));
1979 pspp_sheet_view_set_model (*tree_view, model);
1980 g_object_unref (model);
1982 add_line_number_column (ia, *tree_view);
1986 render_line_number (PsppSheetViewColumn *tree_column,
1987 GtkCellRenderer *cell,
1988 GtkTreeModel *tree_model,
1992 gint row = empty_list_store_iter_to_row (iter);
1993 char s[INT_BUFSIZE_BOUND (int)];
1996 first_line = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_model),
1998 sprintf (s, "%d", first_line + row);
1999 g_object_set (cell, "text", s, NULL);
2003 add_line_number_column (const struct import_assistant *ia,
2004 PsppSheetView *treeview)
2006 PsppSheetViewColumn *column;
2008 column = pspp_sheet_view_column_new_with_attributes (
2009 _("Line"), ia->asst.prop_renderer, (void *) NULL);
2010 pspp_sheet_view_column_set_fixed_width (
2011 column, get_monospace_width (treeview, ia->asst.prop_renderer, 5));
2012 pspp_sheet_view_column_set_resizable (column, TRUE);
2013 pspp_sheet_view_column_set_cell_data_func (column, ia->asst.prop_renderer,
2014 render_line_number, NULL, NULL);
2015 pspp_sheet_view_append_column (treeview, column);
2019 get_monospace_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
2026 ds_put_byte_multiple (&s, '0', char_cnt);
2027 ds_put_byte (&s, ' ');
2028 width = get_string_width (treeview, renderer, ds_cstr (&s));
2035 get_string_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
2039 g_object_set (G_OBJECT (renderer), "text", string, (void *) NULL);
2040 gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
2041 NULL, NULL, NULL, &width, NULL);
2045 static PsppSheetViewColumn *
2046 make_data_column (struct import_assistant *ia, PsppSheetView *tree_view,
2047 bool input, gint dict_idx)
2049 struct variable *var = NULL;
2050 struct column *column = NULL;
2052 gint content_width, header_width;
2053 PsppSheetViewColumn *tree_column;
2057 column = &ia->separators.columns[dict_idx];
2059 var = dict_get_var (ia->formats.dict, dict_idx);
2061 name = escape_underscores (input ? column->name : var_get_name (var));
2062 char_cnt = input ? column->width : var_get_print_format (var)->w;
2063 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
2065 header_width = get_string_width (tree_view, ia->asst.prop_renderer,
2068 tree_column = pspp_sheet_view_column_new ();
2069 g_object_set_data (G_OBJECT (tree_column), "column-number",
2070 GINT_TO_POINTER (dict_idx));
2071 pspp_sheet_view_column_set_title (tree_column, name);
2072 pspp_sheet_view_column_pack_start (tree_column, ia->asst.fixed_renderer,
2074 pspp_sheet_view_column_set_cell_data_func (
2075 tree_column, ia->asst.fixed_renderer,
2076 input ? render_input_cell : render_output_cell, ia, NULL);
2077 pspp_sheet_view_column_set_fixed_width (tree_column, MAX (content_width,
2079 pspp_sheet_view_column_set_resizable (tree_column, TRUE);
2086 static PsppSheetView *
2087 create_data_tree_view (bool input, GtkContainer *parent,
2088 struct import_assistant *ia)
2090 PsppSheetView *tree_view;
2093 make_tree_view (ia, ia->first_line.skip_lines, &tree_view);
2094 pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (tree_view),
2095 PSPP_SHEET_SELECTION_NONE);
2097 for (i = 0; i < ia->separators.column_cnt; i++)
2098 pspp_sheet_view_append_column (tree_view,
2099 make_data_column (ia, tree_view, input, i));
2101 g_object_set (G_OBJECT (tree_view), "has-tooltip", TRUE, (void *) NULL);
2102 g_signal_connect (tree_view, "query-tooltip",
2103 G_CALLBACK (input ? on_query_input_tooltip
2104 : on_query_output_tooltip), ia);
2106 gtk_container_add (parent, GTK_WIDGET (tree_view));
2107 gtk_widget_show (GTK_WIDGET (tree_view));
2112 /* Increments the "watch cursor" level, setting the cursor for
2113 the assistant window to a watch face to indicate to the user
2114 that the ongoing operation may take some time. */
2116 push_watch_cursor (struct import_assistant *ia)
2118 if (++ia->asst.watch_cursor == 1)
2120 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2121 GdkDisplay *display = gtk_widget_get_display (widget);
2122 GdkCursor *cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
2123 gdk_window_set_cursor (widget->window, cursor);
2124 gdk_cursor_unref (cursor);
2125 gdk_display_flush (display);
2129 /* Decrements the "watch cursor" level. If the level reaches
2130 zero, the cursor is reset to its default shape. */
2132 pop_watch_cursor (struct import_assistant *ia)
2134 if (--ia->asst.watch_cursor == 0)
2136 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2137 gdk_window_set_cursor (widget->window, NULL);