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;
438 file->file_name = choose_file (parent_window, &file->encoding);
439 if (file->file_name == NULL)
442 reader = line_reader_for_file (file->encoding, file->file_name, O_RDONLY);
445 msg (ME, _("Could not open `%s': %s"),
446 file->file_name, strerror (errno));
450 file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines);
451 for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++)
453 struct string *line = &file->lines[file->line_cnt];
455 ds_init_empty (line);
456 if (!line_reader_read (reader, line, MAX_LINE_LEN + 1)
457 || ds_length (line) > MAX_LINE_LEN)
459 if (line_reader_eof (reader))
461 else if (line_reader_error (reader))
462 msg (ME, _("Error reading `%s': %s"),
463 file->file_name, strerror (line_reader_error (reader)));
465 msg (ME, _("Failed to read `%s', because it contains a line "
466 "over %d bytes long and therefore appears not to be "
468 file->file_name, MAX_LINE_LEN);
469 line_reader_close (reader);
475 if (file->line_cnt == 0)
477 msg (ME, _("`%s' is empty."), file->file_name);
478 line_reader_close (reader);
483 /* Estimate the number of lines in the file. */
484 if (file->line_cnt < MAX_PREVIEW_LINES)
485 file->total_lines = file->line_cnt;
489 off_t position = line_reader_tell (reader);
490 if (fstat (line_reader_fileno (reader), &s) == 0 && position > 0)
491 file->total_lines = (double) file->line_cnt / position * s.st_size;
493 file->total_lines = 0;
496 line_reader_close (reader);
501 /* Frees IA's file substructure. */
503 destroy_file (struct import_assistant *ia)
505 struct file *f = &ia->file;
508 for (i = 0; i < f->line_cnt; i++)
509 ds_destroy (&f->lines[i]);
511 g_free (f->file_name);
512 g_free (f->encoding);
515 /* Obtains the file to read from the user. If successful, returns the name of
516 the file and stores the user's chosen encoding for the file into *ENCODINGP.
517 The caller must free each of these strings with g_free().
519 On failure, stores a null pointer and stores NULL in *ENCODINGP.
521 PARENT_WINDOW must be the window to use as the file chooser window's
524 choose_file (GtkWindow *parent_window, gchar **encodingp)
527 GtkFileFilter *filter = NULL;
529 GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
531 GTK_FILE_CHOOSER_ACTION_OPEN,
532 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
533 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
536 g_object_set (dialog, "local-only", FALSE, NULL);
538 filter = gtk_file_filter_new ();
539 gtk_file_filter_set_name (filter, _("Text files"));
540 gtk_file_filter_add_mime_type (filter, "text/*");
541 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
543 filter = gtk_file_filter_new ();
544 gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
545 gtk_file_filter_add_pattern (filter, "*.txt");
546 gtk_file_filter_add_pattern (filter, "*.TXT");
547 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
549 filter = gtk_file_filter_new ();
550 gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
551 gtk_file_filter_add_mime_type (filter, "text/plain");
552 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
554 filter = gtk_file_filter_new ();
555 gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
556 gtk_file_filter_add_mime_type (filter, "text/csv");
557 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
559 /* I've never encountered one of these, but it's listed here:
560 http://www.iana.org/assignments/media-types/text/tab-separated-values */
561 filter = gtk_file_filter_new ();
562 gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
563 gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
564 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
566 gtk_file_chooser_set_extra_widget (
567 GTK_FILE_CHOOSER (dialog), psppire_encoding_selector_new ("Auto", true));
569 filter = gtk_file_filter_new ();
570 gtk_file_filter_set_name (filter, _("All Files"));
571 gtk_file_filter_add_pattern (filter, "*");
572 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
574 switch (gtk_dialog_run (GTK_DIALOG (dialog)))
576 case GTK_RESPONSE_ACCEPT:
577 file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
578 *encodingp = psppire_encoding_selector_get_encoding (
579 gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (dialog)));
586 gtk_widget_destroy (dialog);
593 static void close_assistant (struct import_assistant *, int response);
594 static void on_prepare (GtkAssistant *assistant, GtkWidget *page,
595 struct import_assistant *);
596 static void on_cancel (GtkAssistant *assistant, struct import_assistant *);
597 static void on_close (GtkAssistant *assistant, struct import_assistant *);
598 static void on_paste (GtkButton *button, struct import_assistant *);
599 static void on_reset (GtkButton *button, struct import_assistant *);
600 static void close_assistant (struct import_assistant *, int response);
602 /* Initializes IA's asst substructure. PARENT_WINDOW must be the
603 window to use as the assistant window's parent. */
605 init_assistant (struct import_assistant *ia, GtkWindow *parent_window)
607 struct assistant *a = &ia->asst;
609 a->builder = builder_new ("text-data-import.ui");
610 a->assistant = GTK_ASSISTANT (gtk_assistant_new ());
611 g_signal_connect (a->assistant, "prepare", G_CALLBACK (on_prepare), ia);
612 g_signal_connect (a->assistant, "cancel", G_CALLBACK (on_cancel), ia);
613 g_signal_connect (a->assistant, "close", G_CALLBACK (on_close), ia);
614 a->paste_button = gtk_button_new_from_stock (GTK_STOCK_PASTE);
615 gtk_assistant_add_action_widget (a->assistant, a->paste_button);
616 g_signal_connect (a->paste_button, "clicked", G_CALLBACK (on_paste), ia);
617 a->reset_button = gtk_button_new_from_stock ("pspp-stock-reset");
618 gtk_assistant_add_action_widget (a->assistant, a->reset_button);
619 g_signal_connect (a->reset_button, "clicked", G_CALLBACK (on_reset), ia);
620 gtk_window_set_title (GTK_WINDOW (a->assistant),
621 _("Importing Delimited Text Data"));
622 gtk_window_set_transient_for (GTK_WINDOW (a->assistant), parent_window);
623 gtk_window_set_icon_name (GTK_WINDOW (a->assistant), "pspp");
625 a->prop_renderer = gtk_cell_renderer_text_new ();
626 g_object_ref_sink (a->prop_renderer);
627 a->fixed_renderer = gtk_cell_renderer_text_new ();
628 g_object_ref_sink (a->fixed_renderer);
629 g_object_set (G_OBJECT (a->fixed_renderer),
630 "family", "Monospace",
634 /* Frees IA's asst substructure. */
636 destroy_assistant (struct import_assistant *ia)
638 struct assistant *a = &ia->asst;
640 g_object_unref (a->prop_renderer);
641 g_object_unref (a->fixed_renderer);
642 g_object_unref (a->builder);
645 /* Appends a page of the given TYPE, with PAGE as its content, to
646 the GtkAssistant encapsulated by IA. Returns the GtkWidget
647 that represents the page. */
649 add_page_to_assistant (struct import_assistant *ia,
650 GtkWidget *page, GtkAssistantPageType type)
656 title = gtk_window_get_title (GTK_WINDOW (page));
657 title_copy = xstrdup (title ? title : "");
659 content = gtk_bin_get_child (GTK_BIN (page));
661 g_object_ref (content);
662 gtk_container_remove (GTK_CONTAINER (page), content);
664 gtk_widget_destroy (page);
666 gtk_assistant_append_page (ia->asst.assistant, content);
667 gtk_assistant_set_page_type (ia->asst.assistant, content, type);
668 gtk_assistant_set_page_title (ia->asst.assistant, content, title_copy);
669 gtk_assistant_set_page_complete (ia->asst.assistant, content, true);
676 /* Called just before PAGE is displayed as the current page of
677 ASSISTANT, this updates IA content according to the new
680 on_prepare (GtkAssistant *assistant, GtkWidget *page,
681 struct import_assistant *ia)
684 if (gtk_assistant_get_page_type (assistant, page)
685 == GTK_ASSISTANT_PAGE_CONFIRM)
686 gtk_widget_grab_focus (assistant->apply);
688 gtk_widget_grab_focus (assistant->forward);
690 if (page == ia->separators.page)
691 prepare_separators_page (ia);
692 else if (page == ia->formats.page)
693 prepare_formats_page (ia);
695 gtk_widget_show (ia->asst.reset_button);
696 if (page == ia->formats.page)
697 gtk_widget_show (ia->asst.paste_button);
699 gtk_widget_hide (ia->asst.paste_button);
702 /* Called when the Cancel button in the assistant is clicked. */
704 on_cancel (GtkAssistant *assistant, struct import_assistant *ia)
706 close_assistant (ia, GTK_RESPONSE_CANCEL);
709 /* Called when the Apply button on the last page of the assistant
712 on_close (GtkAssistant *assistant, struct import_assistant *ia)
714 close_assistant (ia, GTK_RESPONSE_APPLY);
717 /* Called when the Paste button on the last page of the assistant
720 on_paste (GtkButton *button, struct import_assistant *ia)
722 close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
725 /* Called when the Reset button is clicked. */
727 on_reset (GtkButton *button, struct import_assistant *ia)
729 gint page_num = gtk_assistant_get_current_page (ia->asst.assistant);
730 GtkWidget *page = gtk_assistant_get_nth_page (ia->asst.assistant, page_num);
732 if (page == ia->intro.page)
733 reset_intro_page (ia);
734 else if (page == ia->first_line.page)
735 reset_first_line_page (ia);
736 else if (page == ia->separators.page)
737 reset_separators_page (ia);
738 else if (page == ia->formats.page)
739 reset_formats_page (ia);
742 /* Causes the assistant to close, returning RESPONSE for
743 interpretation by text_data_import_assistant. */
745 close_assistant (struct import_assistant *ia, int response)
747 ia->asst.response = response;
748 g_main_loop_quit (ia->asst.main_loop);
749 gtk_widget_hide (GTK_WIDGET (ia->asst.assistant));
752 /* The "intro" page of the assistant. */
754 static void on_intro_amount_changed (struct import_assistant *);
756 /* Initializes IA's intro substructure. */
758 init_intro_page (struct import_assistant *ia)
760 GtkBuilder *builder = ia->asst.builder;
761 struct intro_page *p = &ia->intro;
763 GtkWidget *hbox_n_cases ;
764 GtkWidget *hbox_percent ;
768 p->n_cases_spin = gtk_spin_button_new_with_range (0, INT_MAX, 100);
770 hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &p->n_cases_spin);
772 table = get_widget_assert (builder, "button-table");
774 gtk_table_attach_defaults (GTK_TABLE (table), hbox_n_cases,
778 p->percent_spin = gtk_spin_button_new_with_range (0, 100, 10);
780 hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"), &p->percent_spin);
782 gtk_table_attach_defaults (GTK_TABLE (table), hbox_percent,
786 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Intro"),
787 GTK_ASSISTANT_PAGE_INTRO);
789 p->all_cases_button = get_widget_assert (builder, "import-all-cases");
791 p->n_cases_button = get_widget_assert (builder, "import-n-cases");
793 p->percent_button = get_widget_assert (builder, "import-percent");
795 g_signal_connect_swapped (p->all_cases_button, "toggled",
796 G_CALLBACK (on_intro_amount_changed), ia);
797 g_signal_connect_swapped (p->n_cases_button, "toggled",
798 G_CALLBACK (on_intro_amount_changed), ia);
799 g_signal_connect_swapped (p->percent_button, "toggled",
800 G_CALLBACK (on_intro_amount_changed), ia);
802 on_intro_amount_changed (ia);
805 ds_put_cstr (&s, _("This assistant will guide you through the process of "
806 "importing data into PSPP from a text file with one line "
807 "per case, in which fields are separated by tabs, "
808 "commas, or other delimiters.\n\n"));
809 if (ia->file.total_is_exact)
811 &s, ngettext ("The selected file contains %zu line of text. ",
812 "The selected file contains %zu lines of text. ",
815 else if (ia->file.total_lines > 0)
819 "The selected file contains approximately %lu line of text. ",
820 "The selected file contains approximately %lu lines of text. ",
821 ia->file.total_lines),
822 ia->file.total_lines);
825 "Only the first %zu line of the file will be shown for "
826 "preview purposes in the following screens. ",
827 "Only the first %zu lines of the file will be shown for "
828 "preview purposes in the following screens. ",
832 ds_put_cstr (&s, _("You may choose below how much of the file should "
833 "actually be imported."));
834 gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
839 /* Resets IA's intro page to its initial state. */
841 reset_intro_page (struct import_assistant *ia)
843 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->intro.all_cases_button),
847 /* Called when one of the radio buttons is clicked. */
849 on_intro_amount_changed (struct import_assistant *ia)
851 struct intro_page *p = &ia->intro;
853 gtk_widget_set_sensitive (p->n_cases_spin,
854 gtk_toggle_button_get_active (
855 GTK_TOGGLE_BUTTON (p->n_cases_button)));
857 gtk_widget_set_sensitive (p->percent_spin,
858 gtk_toggle_button_get_active (
859 GTK_TOGGLE_BUTTON (p->percent_button)));
862 /* The "first line" page of the assistant. */
864 static PsppSheetView *create_lines_tree_view (GtkContainer *parent_window,
865 struct import_assistant *);
866 static void on_first_line_change (PsppSheetSelection *,
867 struct import_assistant *);
868 static void on_variable_names_cb_toggle (GtkToggleButton *,
869 struct import_assistant *);
870 static void set_first_line (struct import_assistant *);
871 static void get_first_line (struct import_assistant *);
873 /* Initializes IA's first_line substructure. */
875 init_first_line_page (struct import_assistant *ia)
877 struct first_line_page *p = &ia->first_line;
878 GtkBuilder *builder = ia->asst.builder;
880 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "FirstLine"),
881 GTK_ASSISTANT_PAGE_CONTENT);
882 gtk_widget_destroy (get_widget_assert (builder, "first-line"));
883 p->tree_view = create_lines_tree_view (
884 GTK_CONTAINER (get_widget_assert (builder, "first-line-scroller")), ia);
885 p->variable_names_cb = get_widget_assert (builder, "variable-names");
886 pspp_sheet_selection_set_mode (
887 pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (p->tree_view)),
888 PSPP_SHEET_SELECTION_BROWSE);
889 pspp_sheet_view_set_rubber_banding (PSPP_SHEET_VIEW (p->tree_view), TRUE);
891 g_signal_connect (pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (p->tree_view)),
892 "changed", G_CALLBACK (on_first_line_change), ia);
893 g_signal_connect (p->variable_names_cb, "toggled",
894 G_CALLBACK (on_variable_names_cb_toggle), ia);
897 /* Resets the first_line page to its initial content. */
899 reset_first_line_page (struct import_assistant *ia)
901 ia->first_line.skip_lines = 0;
902 ia->first_line.variable_names = false;
907 render_line (PsppSheetViewColumn *tree_column,
908 GtkCellRenderer *cell,
909 GtkTreeModel *tree_model,
913 gint row = empty_list_store_iter_to_row (iter);
914 struct string *lines;
916 lines = g_object_get_data (G_OBJECT (tree_model), "lines");
917 g_return_if_fail (lines != NULL);
919 g_object_set (cell, "text", ds_cstr (&lines[row]), NULL);
923 /* Creates and returns a tree view that contains each of the
924 lines in IA's file as a row. */
925 static PsppSheetView *
926 create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia)
928 PsppSheetView *tree_view;
929 PsppSheetViewColumn *column;
930 size_t max_line_length;
931 gint content_width, header_width;
933 const gchar *title = _("Text");
935 make_tree_view (ia, 0, &tree_view);
937 column = pspp_sheet_view_column_new_with_attributes (
938 title, ia->asst.fixed_renderer, (void *) NULL);
939 pspp_sheet_view_column_set_cell_data_func (column, ia->asst.fixed_renderer,
940 render_line, NULL, NULL);
941 pspp_sheet_view_column_set_resizable (column, TRUE);
944 for (i = 0; i < ia->file.line_cnt; i++)
946 size_t w = ds_length (&ia->file.lines[i]);
947 max_line_length = MAX (max_line_length, w);
950 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
952 header_width = get_string_width (tree_view, ia->asst.prop_renderer, title);
953 pspp_sheet_view_column_set_fixed_width (column, MAX (content_width,
955 pspp_sheet_view_append_column (tree_view, column);
957 gtk_container_add (parent, GTK_WIDGET (tree_view));
958 gtk_widget_show (GTK_WIDGET (tree_view));
963 /* Called when the line selected in the first_line tree view
966 on_first_line_change (PsppSheetSelection *selection UNUSED,
967 struct import_assistant *ia)
972 /* Called when the checkbox that indicates whether variable
973 names are in the row above the first line is toggled. */
975 on_variable_names_cb_toggle (GtkToggleButton *variable_names_cb UNUSED,
976 struct import_assistant *ia)
981 /* Sets the widgets to match IA's first_line substructure. */
983 set_first_line (struct import_assistant *ia)
987 path = gtk_tree_path_new_from_indices (ia->first_line.skip_lines, -1);
988 pspp_sheet_view_set_cursor (PSPP_SHEET_VIEW (ia->first_line.tree_view),
990 gtk_tree_path_free (path);
992 gtk_toggle_button_set_active (
993 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb),
994 ia->first_line.variable_names);
995 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
996 ia->first_line.skip_lines > 0);
999 /* Sets IA's first_line substructure to match the widgets. */
1001 get_first_line (struct import_assistant *ia)
1003 PsppSheetSelection *selection;
1005 GtkTreeModel *model;
1007 selection = pspp_sheet_view_get_selection (ia->first_line.tree_view);
1008 if (pspp_sheet_selection_get_selected (selection, &model, &iter))
1010 GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
1011 int row = gtk_tree_path_get_indices (path)[0];
1012 gtk_tree_path_free (path);
1014 ia->first_line.skip_lines = row;
1015 ia->first_line.variable_names =
1016 (ia->first_line.skip_lines > 0
1017 && gtk_toggle_button_get_active (
1018 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb)));
1020 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
1021 ia->first_line.skip_lines > 0);
1024 /* The "separators" page of the assistant. */
1026 static void revise_fields_preview (struct import_assistant *ia);
1027 static void choose_likely_separators (struct import_assistant *ia);
1028 static void find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
1029 const char *targets, const char *def,
1030 struct string *result);
1031 static void clear_fields (struct import_assistant *ia);
1032 static void revise_fields_preview (struct import_assistant *);
1033 static void set_separators (struct import_assistant *);
1034 static void get_separators (struct import_assistant *);
1035 static void on_separators_custom_entry_notify (GObject *UNUSED,
1037 struct import_assistant *);
1038 static void on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1039 struct import_assistant *);
1040 static void on_quote_combo_change (GtkComboBox *combo,
1041 struct import_assistant *);
1042 static void on_quote_cb_toggle (GtkToggleButton *quote_cb,
1043 struct import_assistant *);
1044 static void on_separator_toggle (GtkToggleButton *, struct import_assistant *);
1045 static void render_input_cell (PsppSheetViewColumn *tree_column,
1046 GtkCellRenderer *cell,
1047 GtkTreeModel *model, GtkTreeIter *iter,
1049 static gboolean on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1050 gboolean keyboard_mode UNUSED,
1051 GtkTooltip *tooltip,
1052 struct import_assistant *);
1054 /* A common field separator and its identifying name. */
1057 const char *name; /* Name (for use with get_widget_assert). */
1058 int c; /* Separator character. */
1061 /* All the separators in the dialog box. */
1062 static const struct separator separators[] =
1074 #define SEPARATOR_CNT (sizeof separators / sizeof *separators)
1077 set_quote_list (GtkComboBoxEntry *cb)
1079 GtkListStore *list = gtk_list_store_new (1, G_TYPE_STRING);
1082 const gchar *seperator[3] = {"'\"", "\'", "\""};
1084 for (i = 0; i < 3; i++)
1086 const gchar *s = seperator[i];
1088 /* Add a new row to the model */
1089 gtk_list_store_append (list, &iter);
1090 gtk_list_store_set (list, &iter,
1096 gtk_combo_box_set_model (GTK_COMBO_BOX (cb), GTK_TREE_MODEL (list));
1097 g_object_unref (list);
1099 gtk_combo_box_entry_set_text_column (cb, 0);
1102 /* Initializes IA's separators substructure. */
1104 init_separators_page (struct import_assistant *ia)
1106 GtkBuilder *builder = ia->asst.builder;
1107 struct separators_page *p = &ia->separators;
1110 choose_likely_separators (ia);
1112 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Separators"),
1113 GTK_ASSISTANT_PAGE_CONTENT);
1114 p->custom_cb = get_widget_assert (builder, "custom-cb");
1115 p->custom_entry = get_widget_assert (builder, "custom-entry");
1116 p->quote_combo = get_widget_assert (builder, "quote-combo");
1117 p->quote_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (p->quote_combo)));
1118 p->quote_cb = get_widget_assert (builder, "quote-cb");
1119 p->escape_cb = get_widget_assert (builder, "escape");
1121 set_separators (ia);
1122 set_quote_list (GTK_COMBO_BOX_ENTRY (p->quote_combo));
1123 p->fields_tree_view = PSPP_SHEET_VIEW (get_widget_assert (builder, "fields"));
1124 g_signal_connect (p->quote_combo, "changed",
1125 G_CALLBACK (on_quote_combo_change), ia);
1126 g_signal_connect (p->quote_cb, "toggled",
1127 G_CALLBACK (on_quote_cb_toggle), ia);
1128 g_signal_connect (p->custom_entry, "notify::text",
1129 G_CALLBACK (on_separators_custom_entry_notify), ia);
1130 g_signal_connect (p->custom_cb, "toggled",
1131 G_CALLBACK (on_separators_custom_cb_toggle), ia);
1132 for (i = 0; i < SEPARATOR_CNT; i++)
1133 g_signal_connect (get_widget_assert (builder, separators[i].name),
1134 "toggled", G_CALLBACK (on_separator_toggle), ia);
1135 g_signal_connect (p->escape_cb, "toggled",
1136 G_CALLBACK (on_separator_toggle), ia);
1139 /* Frees IA's separators substructure. */
1141 destroy_separators_page (struct import_assistant *ia)
1143 struct separators_page *s = &ia->separators;
1145 ds_destroy (&s->separators);
1146 ds_destroy (&s->quotes);
1150 /* Called just before the separators page becomes visible in the
1153 prepare_separators_page (struct import_assistant *ia)
1155 revise_fields_preview (ia);
1158 /* Called when the Reset button is clicked on the separators
1159 page, resets the separators to the defaults. */
1161 reset_separators_page (struct import_assistant *ia)
1163 choose_likely_separators (ia);
1164 set_separators (ia);
1167 /* Frees and clears the column data in IA's separators
1170 clear_fields (struct import_assistant *ia)
1172 struct separators_page *s = &ia->separators;
1174 if (s->column_cnt > 0)
1179 for (row = 0; row < ia->file.line_cnt; row++)
1181 const struct string *line = &ia->file.lines[row];
1182 const char *line_start = ds_data (line);
1183 const char *line_end = ds_end (line);
1185 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1187 char *s = ss_data (col->contents[row]);
1188 if (!(s >= line_start && s <= line_end))
1189 ss_dealloc (&col->contents[row]);
1193 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1196 free (col->contents);
1205 /* Breaks the file data in IA into columns based on the
1206 separators set in IA's separators substructure. */
1208 split_fields (struct import_assistant *ia)
1210 struct separators_page *s = &ia->separators;
1211 size_t columns_allocated;
1217 /* Is space in the set of separators? */
1218 space_sep = ss_find_byte (ds_ss (&s->separators), ' ') != SIZE_MAX;
1220 /* Split all the lines, not just those from
1221 ia->first_line.skip_lines on, so that we split the line that
1222 contains variables names if ia->first_line.variable_names is
1224 columns_allocated = 0;
1225 for (row = 0; row < ia->file.line_cnt; row++)
1227 struct string *line = &ia->file.lines[row];
1228 struct substring text = ds_ss (line);
1231 for (column_idx = 0; ; column_idx++)
1233 struct substring field;
1234 struct column *column;
1237 ss_ltrim (&text, ss_cstr (" "));
1238 if (ss_is_empty (text))
1240 if (column_idx != 0)
1244 else if (!ds_is_empty (&s->quotes)
1245 && ds_find_byte (&s->quotes, text.string[0]) != SIZE_MAX)
1247 int quote = ss_get_byte (&text);
1249 ss_get_until (&text, quote, &field);
1256 while ((c = ss_get_byte (&text)) != EOF)
1258 ds_put_byte (&s, c);
1259 else if (ss_match_byte (&text, quote))
1260 ds_put_byte (&s, quote);
1267 ss_get_bytes (&text, ss_cspan (text, ds_ss (&s->separators)),
1270 if (column_idx >= s->column_cnt)
1272 struct column *column;
1274 if (s->column_cnt >= columns_allocated)
1275 s->columns = x2nrealloc (s->columns, &columns_allocated,
1276 sizeof *s->columns);
1277 column = &s->columns[s->column_cnt++];
1278 column->name = NULL;
1280 column->contents = xcalloc (ia->file.line_cnt,
1281 sizeof *column->contents);
1283 column = &s->columns[column_idx];
1284 column->contents[row] = field;
1285 if (ss_length (field) > column->width)
1286 column->width = ss_length (field);
1289 ss_ltrim (&text, ss_cstr (" "));
1290 if (ss_is_empty (text))
1292 if (ss_find_byte (ds_ss (&s->separators), ss_first (text))
1294 ss_advance (&text, 1);
1299 /* Chooses a name for each column on the separators page */
1301 choose_column_names (struct import_assistant *ia)
1303 const struct first_line_page *f = &ia->first_line;
1304 struct separators_page *s = &ia->separators;
1305 struct dictionary *dict;
1306 unsigned long int generated_name_count = 0;
1310 dict = dict_create (get_default_encoding ());
1311 name_row = f->variable_names && f->skip_lines ? f->skip_lines : 0;
1312 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1316 hint = name_row ? ss_xstrdup (col->contents[name_row - 1]) : NULL;
1317 name = dict_make_unique_var_name (dict, hint, &generated_name_count);
1321 dict_create_var_assert (dict, name, 0);
1323 dict_destroy (dict);
1326 /* Picks the most likely separator and quote characters based on
1329 choose_likely_separators (struct import_assistant *ia)
1331 unsigned long int histogram[UCHAR_MAX + 1] = { 0 };
1334 /* Construct a histogram of all the characters used in the
1336 for (row = 0; row < ia->file.line_cnt; row++)
1338 struct substring line = ds_ss (&ia->file.lines[row]);
1339 size_t length = ss_length (line);
1341 for (i = 0; i < length; i++)
1342 histogram[(unsigned char) line.string[i]]++;
1345 find_commonest_chars (histogram, "\"'", "", &ia->separators.quotes);
1346 find_commonest_chars (histogram, ",;:/|!\t-", ",",
1347 &ia->separators.separators);
1348 ia->separators.escape = true;
1351 /* Chooses the most common character among those in TARGETS,
1352 based on the frequency data in HISTOGRAM, and stores it in
1353 RESULT. If there is a tie for the most common character among
1354 those in TARGETS, the earliest character is chosen. If none
1355 of the TARGETS appear at all, then DEF is used as a
1358 find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
1359 const char *targets, const char *def,
1360 struct string *result)
1362 unsigned char max = 0;
1363 unsigned long int max_count = 0;
1365 for (; *targets != '\0'; targets++)
1367 unsigned char c = *targets;
1368 unsigned long int count = histogram[c];
1369 if (count > max_count)
1378 ds_put_byte (result, max);
1381 ds_assign_cstr (result, def);
1384 /* Revises the contents of the fields tree view based on the
1385 currently chosen set of separators. */
1387 revise_fields_preview (struct import_assistant *ia)
1391 push_watch_cursor (ia);
1393 w = GTK_WIDGET (ia->separators.fields_tree_view);
1394 gtk_widget_destroy (w);
1395 get_separators (ia);
1397 choose_column_names (ia);
1398 ia->separators.fields_tree_view = create_data_tree_view (
1400 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "fields-scroller")),
1403 pop_watch_cursor (ia);
1406 /* Sets the widgets to match IA's separators substructure. */
1408 set_separators (struct import_assistant *ia)
1410 struct separators_page *s = &ia->separators;
1412 struct string custom;
1417 ds_init_empty (&custom);
1419 for (i = 0; i < ds_length (&s->separators); i++)
1421 unsigned char c = ds_at (&s->separators, i);
1424 for (j = 0; j < SEPARATOR_CNT; j++)
1426 const struct separator *s = &separators[j];
1434 ds_put_byte (&custom, c);
1438 for (i = 0; i < SEPARATOR_CNT; i++)
1440 const struct separator *s = &separators[i];
1441 GtkWidget *button = get_widget_assert (ia->asst.builder, s->name);
1442 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
1443 (seps & (1u << i)) != 0);
1445 any_custom = !ds_is_empty (&custom);
1446 gtk_entry_set_text (GTK_ENTRY (s->custom_entry), ds_cstr (&custom));
1447 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->custom_cb),
1449 gtk_widget_set_sensitive (s->custom_entry, any_custom);
1450 ds_destroy (&custom);
1452 any_quotes = !ds_is_empty (&s->quotes);
1454 gtk_entry_set_text (s->quote_entry,
1455 any_quotes ? ds_cstr (&s->quotes) : "\"");
1456 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->quote_cb),
1458 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->escape_cb),
1460 gtk_widget_set_sensitive (s->quote_combo, any_quotes);
1461 gtk_widget_set_sensitive (s->escape_cb, any_quotes);
1464 /* Sets IA's separators substructure to match the widgets. */
1466 get_separators (struct import_assistant *ia)
1468 struct separators_page *s = &ia->separators;
1471 ds_clear (&s->separators);
1472 for (i = 0; i < SEPARATOR_CNT; i++)
1474 const struct separator *sep = &separators[i];
1475 GtkWidget *button = get_widget_assert (ia->asst.builder, sep->name);
1476 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1477 ds_put_byte (&s->separators, sep->c);
1480 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->custom_cb)))
1481 ds_put_cstr (&s->separators,
1482 gtk_entry_get_text (GTK_ENTRY (s->custom_entry)));
1484 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->quote_cb)))
1486 gchar *text = gtk_combo_box_get_active_text (
1487 GTK_COMBO_BOX (s->quote_combo));
1488 ds_assign_cstr (&s->quotes, text);
1492 ds_clear (&s->quotes);
1493 s->escape = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->escape_cb));
1496 /* Called when the user changes the entry field for custom
1499 on_separators_custom_entry_notify (GObject *gobject UNUSED,
1500 GParamSpec *arg1 UNUSED,
1501 struct import_assistant *ia)
1503 revise_fields_preview (ia);
1506 /* Called when the user toggles the checkbox that enables custom
1509 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1510 struct import_assistant *ia)
1512 bool is_active = gtk_toggle_button_get_active (custom_cb);
1513 gtk_widget_set_sensitive (ia->separators.custom_entry, is_active);
1514 revise_fields_preview (ia);
1517 /* Called when the user changes the selection in the combo box
1518 that selects a quote character. */
1520 on_quote_combo_change (GtkComboBox *combo, struct import_assistant *ia)
1522 revise_fields_preview (ia);
1525 /* Called when the user toggles the checkbox that enables
1528 on_quote_cb_toggle (GtkToggleButton *quote_cb, struct import_assistant *ia)
1530 bool is_active = gtk_toggle_button_get_active (quote_cb);
1531 gtk_widget_set_sensitive (ia->separators.quote_combo, is_active);
1532 gtk_widget_set_sensitive (ia->separators.escape_cb, is_active);
1533 revise_fields_preview (ia);
1536 /* Called when the user toggles one of the separators
1539 on_separator_toggle (GtkToggleButton *toggle UNUSED,
1540 struct import_assistant *ia)
1542 revise_fields_preview (ia);
1545 /* Called to render one of the cells in the fields preview tree
1548 render_input_cell (PsppSheetViewColumn *tree_column, GtkCellRenderer *cell,
1549 GtkTreeModel *model, GtkTreeIter *iter,
1552 struct import_assistant *ia = ia_;
1553 struct substring field;
1557 column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1559 row = empty_list_store_iter_to_row (iter) + ia->first_line.skip_lines;
1560 field = ia->separators.columns[column].contents[row];
1561 if (field.string != NULL)
1563 GValue text = {0, };
1564 g_value_init (&text, G_TYPE_STRING);
1565 g_value_take_string (&text, ss_xstrdup (field));
1566 g_object_set_property (G_OBJECT (cell), "text", &text);
1567 g_value_unset (&text);
1568 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1573 "background", "red",
1574 "background-set", TRUE,
1578 /* Called to render a tooltip on one of the cells in the fields
1579 preview tree view. */
1581 on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1582 gboolean keyboard_mode UNUSED,
1583 GtkTooltip *tooltip, struct import_assistant *ia)
1587 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1590 if (ia->separators.columns[column].contents[row].string != NULL)
1593 gtk_tooltip_set_text (tooltip,
1594 _("This input line has too few separators "
1595 "to fill in this field."));
1599 /* The "formats" page of the assistant. */
1601 static void on_variable_change (PsppireDict *dict, int idx,
1602 struct import_assistant *);
1603 static void clear_modified_vars (struct import_assistant *);
1605 /* Initializes IA's formats substructure. */
1607 init_formats_page (struct import_assistant *ia)
1609 GtkBuilder *builder = ia->asst.builder;
1610 struct formats_page *p = &ia->formats;
1612 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Formats"),
1613 GTK_ASSISTANT_PAGE_CONFIRM);
1614 p->data_tree_view = PSPP_SHEET_VIEW (get_widget_assert (builder, "data"));
1615 p->modified_vars = NULL;
1616 p->modified_var_cnt = 0;
1620 /* Frees IA's formats substructure. */
1622 destroy_formats_page (struct import_assistant *ia)
1624 struct formats_page *p = &ia->formats;
1626 if (p->psppire_dict != NULL)
1628 dict_destroy (p->psppire_dict->dict);
1629 g_object_unref (p->psppire_dict);
1631 clear_modified_vars (ia);
1634 /* Called just before the formats page of the assistant is
1637 prepare_formats_page (struct import_assistant *ia)
1639 struct dictionary *dict;
1640 PsppireDict *psppire_dict;
1641 GtkBin *vars_scroller;
1642 GtkWidget *old_var_sheet;
1643 PsppireVarSheet *var_sheet;
1644 struct separators_page *s = &ia->separators;
1645 struct formats_page *p = &ia->formats;
1646 struct fmt_guesser *fg;
1647 unsigned long int number = 0;
1650 push_watch_cursor (ia);
1652 dict = dict_create (get_default_encoding ());
1653 fg = fmt_guesser_create ();
1654 for (column_idx = 0; column_idx < s->column_cnt; column_idx++)
1656 struct variable *modified_var;
1658 modified_var = (column_idx < p->modified_var_cnt
1659 ? p->modified_vars[column_idx] : NULL);
1660 if (modified_var == NULL)
1662 struct column *column = &s->columns[column_idx];
1663 struct variable *var;
1664 struct fmt_spec format;
1668 /* Choose variable name. */
1669 name = dict_make_unique_var_name (dict, column->name, &number);
1671 /* Choose variable format. */
1672 fmt_guesser_clear (fg);
1673 for (row = ia->first_line.skip_lines; row < ia->file.line_cnt; row++)
1674 fmt_guesser_add (fg, column->contents[row]);
1675 fmt_guesser_guess (fg, &format);
1676 fmt_fix_input (&format);
1678 /* Create variable. */
1679 var = dict_create_var_assert (dict, name, fmt_var_width (&format));
1680 var_set_both_formats (var, &format);
1688 name = dict_make_unique_var_name (dict, var_get_name (modified_var),
1690 dict_clone_var_as_assert (dict, modified_var, name);
1694 fmt_guesser_destroy (fg);
1696 psppire_dict = psppire_dict_new_from_dict (dict);
1697 g_signal_connect (psppire_dict, "variable_changed",
1698 G_CALLBACK (on_variable_change), ia);
1699 ia->formats.dict = dict;
1700 ia->formats.psppire_dict = psppire_dict;
1702 /* XXX: PsppireVarStore doesn't hold a reference to
1703 psppire_dict for now, but it should. After it does, we
1704 should g_object_ref the psppire_dict here, since we also
1705 hold a reference via ia->formats.dict. */
1706 var_sheet = PSPPIRE_VAR_SHEET (psppire_var_sheet_new ());
1707 g_object_set (var_sheet,
1708 "dictionary", psppire_dict,
1709 "may-create-vars", FALSE,
1710 "may-delete-vars", FALSE,
1711 "format-use", FMT_FOR_INPUT,
1712 "enable-grid-lines", PSPP_SHEET_VIEW_GRID_LINES_BOTH,
1715 vars_scroller = GTK_BIN (get_widget_assert (ia->asst.builder, "vars-scroller"));
1716 old_var_sheet = gtk_bin_get_child (vars_scroller);
1717 if (old_var_sheet != NULL)
1718 gtk_widget_destroy (old_var_sheet);
1719 gtk_container_add (GTK_CONTAINER (vars_scroller), GTK_WIDGET (var_sheet));
1720 gtk_widget_show (GTK_WIDGET (var_sheet));
1722 gtk_widget_destroy (GTK_WIDGET (ia->formats.data_tree_view));
1723 ia->formats.data_tree_view = create_data_tree_view (
1725 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "data-scroller")),
1728 pop_watch_cursor (ia);
1731 /* Clears the set of user-modified variables from IA's formats
1732 substructure. This discards user modifications to variable
1733 formats, thereby causing formats to revert to their
1736 clear_modified_vars (struct import_assistant *ia)
1738 struct formats_page *p = &ia->formats;
1741 for (i = 0; i < p->modified_var_cnt; i++)
1742 var_destroy (p->modified_vars[i]);
1743 free (p->modified_vars);
1744 p->modified_vars = NULL;
1745 p->modified_var_cnt = 0;
1748 /* Resets the formats page to its defaults, discarding user
1751 reset_formats_page (struct import_assistant *ia)
1753 clear_modified_vars (ia);
1754 prepare_formats_page (ia);
1757 /* Called when the user changes one of the variables in the
1760 on_variable_change (PsppireDict *dict, int dict_idx,
1761 struct import_assistant *ia)
1763 struct formats_page *p = &ia->formats;
1764 PsppSheetView *tv = ia->formats.data_tree_view;
1765 gint column_idx = dict_idx + 1;
1767 push_watch_cursor (ia);
1769 /* Remove previous column and replace with new column. */
1770 pspp_sheet_view_remove_column (tv, pspp_sheet_view_get_column (tv, column_idx));
1771 pspp_sheet_view_insert_column (tv, make_data_column (ia, tv, false, dict_idx),
1774 /* Save a copy of the modified variable in modified_vars, so
1775 that its attributes will be preserved if we back up to the
1776 previous page with the Prev button and then come back
1778 if (dict_idx >= p->modified_var_cnt)
1781 p->modified_vars = xnrealloc (p->modified_vars, dict_idx + 1,
1782 sizeof *p->modified_vars);
1783 for (i = 0; i <= dict_idx; i++)
1784 p->modified_vars[i] = NULL;
1785 p->modified_var_cnt = dict_idx + 1;
1787 if (p->modified_vars[dict_idx])
1788 var_destroy (p->modified_vars[dict_idx]);
1789 p->modified_vars[dict_idx]
1790 = var_clone (psppire_dict_get_variable (dict, dict_idx));
1792 pop_watch_cursor (ia);
1795 /* Parses the contents of the field at (ROW,COLUMN) according to
1796 its variable format. If OUTPUTP is non-null, then *OUTPUTP
1797 receives the formatted output for that field (which must be
1798 freed with free). If TOOLTIPP is non-null, then *TOOLTIPP
1799 receives a message suitable for use in a tooltip, if one is
1800 needed, or a null pointer otherwise. Returns true if a
1801 tooltip message is needed, otherwise false. */
1803 parse_field (struct import_assistant *ia,
1804 size_t row, size_t column,
1805 char **outputp, char **tooltipp)
1807 struct substring field;
1809 struct variable *var;
1810 const struct fmt_spec *in;
1811 struct fmt_spec out;
1815 field = ia->separators.columns[column].contents[row];
1816 var = dict_get_var (ia->formats.dict, column);
1817 value_init (&val, var_get_width (var));
1818 in = var_get_print_format (var);
1819 out = fmt_for_output_from_input (in);
1821 if (field.string != NULL)
1825 error = data_in (field, "UTF-8", in->type, &val, var_get_width (var),
1826 dict_get_encoding (ia->formats.dict));
1829 tooltip = xasprintf (_("Cannot parse field content `%.*s' as "
1831 (int) field.length, field.string,
1832 fmt_name (in->type), error);
1838 tooltip = xstrdup (_("This input line has too few separators "
1839 "to fill in this field."));
1840 value_set_missing (&val, var_get_width (var));
1842 if (outputp != NULL)
1844 *outputp = data_out (&val, dict_get_encoding (ia->formats.dict), &out);
1846 value_destroy (&val, var_get_width (var));
1848 ok = tooltip == NULL;
1849 if (tooltipp != NULL)
1850 *tooltipp = tooltip;
1856 /* Called to render one of the cells in the data preview tree
1859 render_output_cell (PsppSheetViewColumn *tree_column,
1860 GtkCellRenderer *cell,
1861 GtkTreeModel *model,
1865 struct import_assistant *ia = ia_;
1867 GValue gvalue = { 0, };
1870 ok = parse_field (ia,
1871 (empty_list_store_iter_to_row (iter)
1872 + ia->first_line.skip_lines),
1873 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1877 g_value_init (&gvalue, G_TYPE_STRING);
1878 g_value_take_string (&gvalue, output);
1879 g_object_set_property (G_OBJECT (cell), "text", &gvalue);
1880 g_value_unset (&gvalue);
1883 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1886 "background", "red",
1887 "background-set", TRUE,
1891 /* Called to render a tooltip for one of the cells in the data
1892 preview tree view. */
1894 on_query_output_tooltip (GtkWidget *widget, gint wx, gint wy,
1895 gboolean keyboard_mode UNUSED,
1896 GtkTooltip *tooltip, struct import_assistant *ia)
1901 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1904 if (parse_field (ia, row, column, NULL, &text))
1907 gtk_tooltip_set_text (tooltip, text);
1912 /* Utility functions used by multiple pages of the assistant. */
1915 get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
1916 const struct import_assistant *ia,
1917 size_t *row, size_t *column)
1919 PsppSheetView *tree_view = PSPP_SHEET_VIEW (widget);
1923 PsppSheetViewColumn *tree_column;
1924 GtkTreeModel *tree_model;
1927 /* Check that WIDGET is really visible on the screen before we
1928 do anything else. This is a bug fix for a sticky situation:
1929 when text_data_import_assistant() returns, it frees the data
1930 necessary to compose the tool tip message, but there may be
1931 a tool tip under preparation at that point (even if there is
1932 no visible tool tip) that will call back into us a little
1933 bit later. Perhaps the correct solution to this problem is
1934 to make the data related to the tool tips part of a GObject
1935 that only gets destroyed when all references are released,
1936 but this solution appears to be effective too. */
1937 if (!gtk_widget_get_mapped (widget))
1940 pspp_sheet_view_convert_widget_to_bin_window_coords (tree_view,
1942 if (!pspp_sheet_view_get_path_at_pos (tree_view, bx, by,
1943 &path, &tree_column, NULL, NULL))
1946 *column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1949 tree_model = pspp_sheet_view_get_model (tree_view);
1950 ok = gtk_tree_model_get_iter (tree_model, &iter, path);
1951 gtk_tree_path_free (path);
1955 *row = empty_list_store_iter_to_row (&iter) + ia->first_line.skip_lines;
1960 make_tree_view (const struct import_assistant *ia,
1962 PsppSheetView **tree_view)
1964 GtkTreeModel *model;
1966 *tree_view = PSPP_SHEET_VIEW (pspp_sheet_view_new ());
1967 pspp_sheet_view_set_grid_lines (*tree_view, PSPP_SHEET_VIEW_GRID_LINES_BOTH);
1968 model = GTK_TREE_MODEL (psppire_empty_list_store_new (
1969 ia->file.line_cnt - first_line));
1970 g_object_set_data (G_OBJECT (model), "lines", ia->file.lines + first_line);
1971 g_object_set_data (G_OBJECT (model), "first-line",
1972 GINT_TO_POINTER (first_line));
1973 pspp_sheet_view_set_model (*tree_view, model);
1974 g_object_unref (model);
1976 add_line_number_column (ia, *tree_view);
1980 render_line_number (PsppSheetViewColumn *tree_column,
1981 GtkCellRenderer *cell,
1982 GtkTreeModel *tree_model,
1986 gint row = empty_list_store_iter_to_row (iter);
1987 char s[INT_BUFSIZE_BOUND (int)];
1990 first_line = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_model),
1992 sprintf (s, "%d", first_line + row);
1993 g_object_set (cell, "text", s, NULL);
1997 add_line_number_column (const struct import_assistant *ia,
1998 PsppSheetView *treeview)
2000 PsppSheetViewColumn *column;
2002 column = pspp_sheet_view_column_new_with_attributes (
2003 _("Line"), ia->asst.prop_renderer, (void *) NULL);
2004 pspp_sheet_view_column_set_fixed_width (
2005 column, get_monospace_width (treeview, ia->asst.prop_renderer, 5));
2006 pspp_sheet_view_column_set_resizable (column, TRUE);
2007 pspp_sheet_view_column_set_cell_data_func (column, ia->asst.prop_renderer,
2008 render_line_number, NULL, NULL);
2009 pspp_sheet_view_append_column (treeview, column);
2013 get_monospace_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
2020 ds_put_byte_multiple (&s, '0', char_cnt);
2021 ds_put_byte (&s, ' ');
2022 width = get_string_width (treeview, renderer, ds_cstr (&s));
2029 get_string_width (PsppSheetView *treeview, GtkCellRenderer *renderer,
2033 g_object_set (G_OBJECT (renderer), "text", string, (void *) NULL);
2034 gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
2035 NULL, NULL, NULL, &width, NULL);
2039 static PsppSheetViewColumn *
2040 make_data_column (struct import_assistant *ia, PsppSheetView *tree_view,
2041 bool input, gint dict_idx)
2043 struct variable *var = NULL;
2044 struct column *column = NULL;
2046 gint content_width, header_width;
2047 PsppSheetViewColumn *tree_column;
2051 column = &ia->separators.columns[dict_idx];
2053 var = dict_get_var (ia->formats.dict, dict_idx);
2055 name = escape_underscores (input ? column->name : var_get_name (var));
2056 char_cnt = input ? column->width : var_get_print_format (var)->w;
2057 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
2059 header_width = get_string_width (tree_view, ia->asst.prop_renderer,
2062 tree_column = pspp_sheet_view_column_new ();
2063 g_object_set_data (G_OBJECT (tree_column), "column-number",
2064 GINT_TO_POINTER (dict_idx));
2065 pspp_sheet_view_column_set_title (tree_column, name);
2066 pspp_sheet_view_column_pack_start (tree_column, ia->asst.fixed_renderer,
2068 pspp_sheet_view_column_set_cell_data_func (
2069 tree_column, ia->asst.fixed_renderer,
2070 input ? render_input_cell : render_output_cell, ia, NULL);
2071 pspp_sheet_view_column_set_fixed_width (tree_column, MAX (content_width,
2073 pspp_sheet_view_column_set_resizable (tree_column, TRUE);
2080 static PsppSheetView *
2081 create_data_tree_view (bool input, GtkContainer *parent,
2082 struct import_assistant *ia)
2084 PsppSheetView *tree_view;
2087 make_tree_view (ia, ia->first_line.skip_lines, &tree_view);
2088 pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (tree_view),
2089 PSPP_SHEET_SELECTION_NONE);
2091 for (i = 0; i < ia->separators.column_cnt; i++)
2092 pspp_sheet_view_append_column (tree_view,
2093 make_data_column (ia, tree_view, input, i));
2095 g_object_set (G_OBJECT (tree_view), "has-tooltip", TRUE, (void *) NULL);
2096 g_signal_connect (tree_view, "query-tooltip",
2097 G_CALLBACK (input ? on_query_input_tooltip
2098 : on_query_output_tooltip), ia);
2100 gtk_container_add (parent, GTK_WIDGET (tree_view));
2101 gtk_widget_show (GTK_WIDGET (tree_view));
2106 /* Increments the "watch cursor" level, setting the cursor for
2107 the assistant window to a watch face to indicate to the user
2108 that the ongoing operation may take some time. */
2110 push_watch_cursor (struct import_assistant *ia)
2112 if (++ia->asst.watch_cursor == 1)
2114 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2115 GdkDisplay *display = gtk_widget_get_display (widget);
2116 GdkCursor *cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
2117 gdk_window_set_cursor (widget->window, cursor);
2118 gdk_cursor_unref (cursor);
2119 gdk_display_flush (display);
2123 /* Decrements the "watch cursor" level. If the level reaches
2124 zero, the cursor is reset to its default shape. */
2126 pop_watch_cursor (struct import_assistant *ia)
2128 if (--ia->asst.watch_cursor == 0)
2130 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2131 gdk_window_set_cursor (widget->window, NULL);