1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2008, 2009, 2010, 2011, 2012 Free Software Foundation
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
19 #include "ui/gui/text-data-import-dialog.h"
22 #include <gtk-contrib/psppire-sheet.h>
28 #include "data/data-in.h"
29 #include "data/data-out.h"
30 #include "data/format-guesser.h"
31 #include "data/value-labels.h"
32 #include "language/data-io/data-parser.h"
33 #include "language/lexer/lexer.h"
34 #include "libpspp/assertion.h"
35 #include "libpspp/i18n.h"
36 #include "libpspp/message.h"
37 #include "ui/gui/checkbox-treeview.h"
38 #include "ui/gui/dialog-common.h"
39 #include "ui/gui/executor.h"
40 #include "ui/gui/helper.h"
41 #include "ui/gui/builder-wrapper.h"
42 #include "ui/gui/psppire-data-window.h"
43 #include "ui/gui/psppire-dialog.h"
44 #include "ui/gui/psppire-empty-list-store.h"
45 #include "ui/gui/psppire-var-sheet.h"
46 #include "ui/gui/psppire-var-store.h"
47 #include "ui/gui/psppire-scanf.h"
48 #include "ui/syntax-gen.h"
51 #include "gl/intprops.h"
52 #include "gl/xalloc.h"
55 #define _(msgid) gettext (msgid)
56 #define N_(msgid) msgid
58 struct import_assistant;
60 /* The file to be imported. */
63 char *file_name; /* File name. */
64 unsigned long int total_lines; /* Number of lines in file. */
65 bool total_is_exact; /* Is total_lines exact (or an estimate)? */
67 /* The first several lines of the file. */
71 static bool init_file (struct import_assistant *, GtkWindow *parent);
72 static void destroy_file (struct import_assistant *);
74 /* The main body of the GTK+ assistant and related data. */
78 GtkAssistant *assistant;
80 GtkWidget *paste_button;
81 GtkWidget *reset_button;
85 GtkCellRenderer *prop_renderer;
86 GtkCellRenderer *fixed_renderer;
88 static void init_assistant (struct import_assistant *, GtkWindow *);
89 static void destroy_assistant (struct import_assistant *);
90 static GtkWidget *add_page_to_assistant (struct import_assistant *,
92 GtkAssistantPageType);
94 /* The introduction page of the assistant. */
98 GtkWidget *all_cases_button;
99 GtkWidget *n_cases_button;
100 GtkWidget *n_cases_spin;
101 GtkWidget *percent_button;
102 GtkWidget *percent_spin;
104 static void init_intro_page (struct import_assistant *);
105 static void reset_intro_page (struct import_assistant *);
107 /* Page where the user chooses the first line of data. */
108 struct first_line_page
110 int skip_lines; /* Number of initial lines to skip? */
111 bool variable_names; /* Variable names above first line of data? */
114 GtkTreeView *tree_view;
115 GtkWidget *variable_names_cb;
117 static void init_first_line_page (struct import_assistant *);
118 static void reset_first_line_page (struct import_assistant *);
120 /* Page where the user chooses field separators. */
121 struct separators_page
123 /* How to break lines into columns. */
124 struct string separators; /* Field separators. */
125 struct string quotes; /* Quote characters. */
126 bool escape; /* Doubled quotes yield a quote mark? */
128 /* The columns produced thereby. */
129 struct column *columns; /* Information about each column. */
130 size_t column_cnt; /* Number of columns. */
133 GtkWidget *custom_cb;
134 GtkWidget *custom_entry;
136 GtkWidget *quote_combo;
137 GtkEntry *quote_entry;
138 GtkWidget *escape_cb;
139 GtkTreeView *fields_tree_view;
141 /* The columns that the separators divide the data into. */
144 /* Variable name for this column. This is the variable name
145 used on the separators page; it can be overridden by the
146 user on the formats page. */
149 /* Maximum length of any row in this column. */
152 /* Contents of this column: contents[row] is the contents for
155 A null substring indicates a missing column for that row
156 (because the line contains an insufficient number of
159 contents[] elements may be substrings of the lines[]
160 strings that represent the whole lines of the file, to
161 save memory. Other elements are dynamically allocated
162 with ss_alloc_substring. */
163 struct substring *contents;
165 static void init_separators_page (struct import_assistant *);
166 static void destroy_separators_page (struct import_assistant *);
167 static void prepare_separators_page (struct import_assistant *);
168 static void reset_separators_page (struct import_assistant *);
170 /* Page where the user verifies and adjusts input formats. */
173 struct dictionary *dict;
176 GtkTreeView *data_tree_view;
177 PsppireDict *psppire_dict;
178 struct variable **modified_vars;
179 size_t modified_var_cnt;
181 static void init_formats_page (struct import_assistant *);
182 static void destroy_formats_page (struct import_assistant *);
183 static void prepare_formats_page (struct import_assistant *);
184 static void reset_formats_page (struct import_assistant *);
186 struct import_assistant
189 struct assistant asst;
190 struct intro_page intro;
191 struct first_line_page first_line;
192 struct separators_page separators;
193 struct formats_page formats;
196 static void apply_dict (const struct dictionary *, struct string *);
197 static char *generate_syntax (const struct import_assistant *);
199 static gboolean get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
200 const struct import_assistant *,
201 size_t *row, size_t *column);
202 static void make_tree_view (const struct import_assistant *ia,
204 GtkTreeView **tree_view);
205 static void add_line_number_column (const struct import_assistant *,
207 static gint get_monospace_width (GtkTreeView *, GtkCellRenderer *,
209 static gint get_string_width (GtkTreeView *, GtkCellRenderer *,
211 static GtkTreeViewColumn *make_data_column (struct import_assistant *,
212 GtkTreeView *, bool input,
214 static GtkTreeView *create_data_tree_view (bool input, GtkContainer *parent,
215 struct import_assistant *);
216 static void push_watch_cursor (struct import_assistant *);
217 static void pop_watch_cursor (struct import_assistant *);
219 /* Pops up the Text Data Import assistant. */
221 text_data_import_assistant (PsppireDataWindow *dw)
223 GtkWindow *parent_window = GTK_WINDOW (dw);
224 struct import_assistant *ia;
226 ia = xzalloc (sizeof *ia);
227 if (!init_file (ia, parent_window))
233 init_assistant (ia, parent_window);
234 init_intro_page (ia);
235 init_first_line_page (ia);
236 init_separators_page (ia);
237 init_formats_page (ia);
239 gtk_widget_show_all (GTK_WIDGET (ia->asst.assistant));
241 ia->asst.main_loop = g_main_loop_new (NULL, false);
242 g_main_loop_run (ia->asst.main_loop);
243 g_main_loop_unref (ia->asst.main_loop);
245 switch (ia->asst.response)
247 case GTK_RESPONSE_APPLY:
248 free (execute_syntax_string (dw, generate_syntax (ia)));
250 case PSPPIRE_RESPONSE_PASTE:
251 free (paste_syntax_to_window (generate_syntax (ia)));
257 destroy_formats_page (ia);
258 destroy_separators_page (ia);
259 destroy_assistant (ia);
264 /* Emits PSPP syntax to S that applies the dictionary attributes
265 (such as missing values and value labels) of the variables in
268 apply_dict (const struct dictionary *dict, struct string *s)
270 size_t var_cnt = dict_get_var_cnt (dict);
273 for (i = 0; i < var_cnt; i++)
275 struct variable *var = dict_get_var (dict, i);
276 const char *name = var_get_name (var);
277 enum val_type type = var_get_type (var);
278 int width = var_get_width (var);
279 enum measure measure = var_get_measure (var);
280 enum alignment alignment = var_get_alignment (var);
281 const struct fmt_spec *format = var_get_print_format (var);
283 if (var_has_missing_values (var))
285 const struct missing_values *mv = var_get_missing_values (var);
288 syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
289 for (j = 0; j < mv_n_values (mv); j++)
292 ds_put_cstr (s, ", ");
293 syntax_gen_value (s, mv_get_value (mv, j), width, format);
296 if (mv_has_range (mv))
299 if (mv_has_value (mv))
300 ds_put_cstr (s, ", ");
301 mv_get_range (mv, &low, &high);
302 syntax_gen_num_range (s, low, high, format);
304 ds_put_cstr (s, ").\n");
306 if (var_has_value_labels (var))
308 const struct val_labs *vls = var_get_value_labels (var);
309 const struct val_lab **labels = val_labs_sorted (vls);
310 size_t n_labels = val_labs_count (vls);
313 syntax_gen_pspp (s, "VALUE LABELS %ss", name);
314 for (i = 0; i < n_labels; i++)
316 const struct val_lab *vl = labels[i];
317 ds_put_cstr (s, "\n ");
318 syntax_gen_value (s, &vl->value, width, format);
319 ds_put_byte (s, ' ');
320 syntax_gen_string (s, ss_cstr (val_lab_get_escaped_label (vl)));
323 ds_put_cstr (s, ".\n");
325 if (var_has_label (var))
326 syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
327 name, var_get_label (var));
328 if (measure != var_default_measure (type))
329 syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
331 (measure == MEASURE_NOMINAL ? "NOMINAL"
332 : measure == MEASURE_ORDINAL ? "ORDINAL"
334 if (alignment != var_default_alignment (type))
335 syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
337 (alignment == ALIGN_LEFT ? "LEFT"
338 : alignment == ALIGN_CENTRE ? "CENTER"
340 if (var_get_display_width (var) != var_default_display_width (width))
341 syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
342 name, var_get_display_width (var));
346 /* Generates and returns PSPP syntax to execute the import
347 operation described by IA. The caller must free the syntax
350 generate_syntax (const struct import_assistant *ia)
352 struct string s = DS_EMPTY_INITIALIZER;
361 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
362 ia->intro.n_cases_button)))
363 ds_put_format (&s, " /IMPORTCASES=FIRST %d\n",
364 gtk_spin_button_get_value_as_int (
365 GTK_SPIN_BUTTON (ia->intro.n_cases_spin)));
366 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
367 ia->intro.percent_button)))
368 ds_put_format (&s, " /IMPORTCASES=PERCENT %d\n",
369 gtk_spin_button_get_value_as_int (
370 GTK_SPIN_BUTTON (ia->intro.percent_spin)));
372 ds_put_cstr (&s, " /IMPORTCASES=ALL\n");
374 " /ARRANGEMENT=DELIMITED\n"
376 if (ia->first_line.skip_lines > 0)
377 ds_put_format (&s, " /FIRSTCASE=%d\n", ia->first_line.skip_lines + 1);
378 ds_put_cstr (&s, " /DELIMITERS=\"");
379 if (ds_find_byte (&ia->separators.separators, '\t') != SIZE_MAX)
380 ds_put_cstr (&s, "\\t");
381 if (ds_find_byte (&ia->separators.separators, '\\') != SIZE_MAX)
382 ds_put_cstr (&s, "\\\\");
383 for (i = 0; i < ds_length (&ia->separators.separators); i++)
385 char c = ds_at (&ia->separators.separators, i);
387 ds_put_cstr (&s, "\"\"");
388 else if (c != '\t' && c != '\\')
391 ds_put_cstr (&s, "\"\n");
392 if (!ds_is_empty (&ia->separators.quotes))
393 syntax_gen_pspp (&s, " /QUALIFIER=%sq\n", ds_cstr (&ia->separators.quotes));
394 if (!ds_is_empty (&ia->separators.quotes) && ia->separators.escape)
395 ds_put_cstr (&s, " /ESCAPE\n");
396 ds_put_cstr (&s, " /VARIABLES=\n");
398 var_cnt = dict_get_var_cnt (ia->formats.dict);
399 for (i = 0; i < var_cnt; i++)
401 struct variable *var = dict_get_var (ia->formats.dict, i);
402 char format_string[FMT_STRING_LEN_MAX + 1];
403 fmt_to_string (var_get_print_format (var), format_string);
404 ds_put_format (&s, " %s %s%s\n",
405 var_get_name (var), format_string,
406 i == var_cnt - 1 ? "." : "");
409 apply_dict (ia->formats.dict, &s);
414 /* Choosing a file and reading it. */
416 static char *choose_file (GtkWindow *parent_window);
418 /* Obtains the file to import from the user and initializes IA's
419 file substructure. PARENT_WINDOW must be the window to use
420 as the file chooser window's parent.
422 Returns true if successful, false if the file name could not
423 be obtained or the file could not be read. */
425 init_file (struct import_assistant *ia, GtkWindow *parent_window)
427 struct file *file = &ia->file;
428 enum { MAX_PREVIEW_LINES = 1000 }; /* Max number of lines to read. */
429 enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
432 file->file_name = choose_file (parent_window);
433 if (file->file_name == NULL)
436 stream = fopen (file->file_name, "r");
439 msg (ME, _("Could not open `%s': %s"),
440 file->file_name, strerror (errno));
444 file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines);
445 for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++)
447 struct string *line = &file->lines[file->line_cnt];
449 ds_init_empty (line);
450 if (!ds_read_line (line, stream, MAX_LINE_LEN))
454 else if (ferror (stream))
455 msg (ME, _("Error reading `%s': %s"),
456 file->file_name, strerror (errno));
458 msg (ME, _("Failed to read `%s', because it contains a line "
459 "over %d bytes long and therefore appears not to be "
461 file->file_name, MAX_LINE_LEN);
466 ds_chomp_byte (line, '\n');
467 ds_chomp_byte (line, '\r');
470 if (file->line_cnt == 0)
472 msg (ME, _("`%s' is empty."), file->file_name);
478 /* Estimate the number of lines in the file. */
479 if (file->line_cnt < MAX_PREVIEW_LINES)
480 file->total_lines = file->line_cnt;
484 off_t position = ftello (stream);
485 if (fstat (fileno (stream), &s) == 0 && position > 0)
486 file->total_lines = (double) file->line_cnt / position * s.st_size;
488 file->total_lines = 0;
494 /* Frees IA's file substructure. */
496 destroy_file (struct import_assistant *ia)
498 struct file *f = &ia->file;
501 for (i = 0; i < f->line_cnt; i++)
502 ds_destroy (&f->lines[i]);
504 g_free (f->file_name);
507 /* Obtains the file to read from the user and returns the name of
508 the file as a string that must be freed with g_free if
509 successful, otherwise a null pointer. PARENT_WINDOW must be
510 the window to use as the file chooser window's parent. */
512 choose_file (GtkWindow *parent_window)
515 GtkFileFilter *filter = NULL;
517 GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
519 GTK_FILE_CHOOSER_ACTION_OPEN,
520 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
521 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
524 g_object_set (dialog, "local-only", FALSE, NULL);
526 filter = gtk_file_filter_new ();
527 gtk_file_filter_set_name (filter, _("Text files"));
528 gtk_file_filter_add_mime_type (filter, "text/*");
529 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
531 filter = gtk_file_filter_new ();
532 gtk_file_filter_set_name (filter, _("Text (*.txt) Files"));
533 gtk_file_filter_add_pattern (filter, "*.txt");
534 gtk_file_filter_add_pattern (filter, "*.TXT");
535 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
537 filter = gtk_file_filter_new ();
538 gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files"));
539 gtk_file_filter_add_mime_type (filter, "text/plain");
540 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
542 filter = gtk_file_filter_new ();
543 gtk_file_filter_set_name (filter, _("Comma Separated Value Files"));
544 gtk_file_filter_add_mime_type (filter, "text/csv");
545 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
547 /* I've never encountered one of these, but it's listed here:
548 http://www.iana.org/assignments/media-types/text/tab-separated-values */
549 filter = gtk_file_filter_new ();
550 gtk_file_filter_set_name (filter, _("Tab Separated Value Files"));
551 gtk_file_filter_add_mime_type (filter, "text/tab-separated-values");
552 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
554 filter = gtk_file_filter_new ();
555 gtk_file_filter_set_name (filter, _("All Files"));
556 gtk_file_filter_add_pattern (filter, "*");
557 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
559 switch (gtk_dialog_run (GTK_DIALOG (dialog)))
561 case GTK_RESPONSE_ACCEPT:
562 file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
568 gtk_widget_destroy (dialog);
575 static void close_assistant (struct import_assistant *, int response);
576 static void on_prepare (GtkAssistant *assistant, GtkWidget *page,
577 struct import_assistant *);
578 static void on_cancel (GtkAssistant *assistant, struct import_assistant *);
579 static void on_close (GtkAssistant *assistant, struct import_assistant *);
580 static void on_paste (GtkButton *button, struct import_assistant *);
581 static void on_reset (GtkButton *button, struct import_assistant *);
582 static void close_assistant (struct import_assistant *, int response);
584 /* Initializes IA's asst substructure. PARENT_WINDOW must be the
585 window to use as the assistant window's parent. */
587 init_assistant (struct import_assistant *ia, GtkWindow *parent_window)
589 struct assistant *a = &ia->asst;
591 a->builder = builder_new ("text-data-import.ui");
592 a->assistant = GTK_ASSISTANT (gtk_assistant_new ());
593 g_signal_connect (a->assistant, "prepare", G_CALLBACK (on_prepare), ia);
594 g_signal_connect (a->assistant, "cancel", G_CALLBACK (on_cancel), ia);
595 g_signal_connect (a->assistant, "close", G_CALLBACK (on_close), ia);
596 a->paste_button = gtk_button_new_from_stock (GTK_STOCK_PASTE);
597 gtk_assistant_add_action_widget (a->assistant, a->paste_button);
598 g_signal_connect (a->paste_button, "clicked", G_CALLBACK (on_paste), ia);
599 a->reset_button = gtk_button_new_from_stock ("pspp-stock-reset");
600 gtk_assistant_add_action_widget (a->assistant, a->reset_button);
601 g_signal_connect (a->reset_button, "clicked", G_CALLBACK (on_reset), ia);
602 gtk_window_set_title (GTK_WINDOW (a->assistant),
603 _("Importing Delimited Text Data"));
604 gtk_window_set_transient_for (GTK_WINDOW (a->assistant), parent_window);
605 gtk_window_set_icon_name (GTK_WINDOW (a->assistant), "pspp");
607 a->prop_renderer = gtk_cell_renderer_text_new ();
608 g_object_ref_sink (a->prop_renderer);
609 a->fixed_renderer = gtk_cell_renderer_text_new ();
610 g_object_ref_sink (a->fixed_renderer);
611 g_object_set (G_OBJECT (a->fixed_renderer),
612 "family", "Monospace",
616 /* Frees IA's asst substructure. */
618 destroy_assistant (struct import_assistant *ia)
620 struct assistant *a = &ia->asst;
622 g_object_unref (a->prop_renderer);
623 g_object_unref (a->fixed_renderer);
624 g_object_unref (a->builder);
627 /* Appends a page of the given TYPE, with PAGE as its content, to
628 the GtkAssistant encapsulated by IA. Returns the GtkWidget
629 that represents the page. */
631 add_page_to_assistant (struct import_assistant *ia,
632 GtkWidget *page, GtkAssistantPageType type)
638 title = gtk_window_get_title (GTK_WINDOW (page));
639 title_copy = xstrdup (title ? title : "");
641 content = gtk_bin_get_child (GTK_BIN (page));
643 g_object_ref (content);
644 gtk_container_remove (GTK_CONTAINER (page), content);
646 gtk_widget_destroy (page);
648 gtk_assistant_append_page (ia->asst.assistant, content);
649 gtk_assistant_set_page_type (ia->asst.assistant, content, type);
650 gtk_assistant_set_page_title (ia->asst.assistant, content, title_copy);
651 gtk_assistant_set_page_complete (ia->asst.assistant, content, true);
658 /* Called just before PAGE is displayed as the current page of
659 ASSISTANT, this updates IA content according to the new
662 on_prepare (GtkAssistant *assistant, GtkWidget *page,
663 struct import_assistant *ia)
666 if (gtk_assistant_get_page_type (assistant, page)
667 == GTK_ASSISTANT_PAGE_CONFIRM)
668 gtk_widget_grab_focus (assistant->apply);
670 gtk_widget_grab_focus (assistant->forward);
672 if (page == ia->separators.page)
673 prepare_separators_page (ia);
674 else if (page == ia->formats.page)
675 prepare_formats_page (ia);
677 gtk_widget_show (ia->asst.reset_button);
678 if (page == ia->formats.page)
679 gtk_widget_show (ia->asst.paste_button);
681 gtk_widget_hide (ia->asst.paste_button);
684 /* Called when the Cancel button in the assistant is clicked. */
686 on_cancel (GtkAssistant *assistant, struct import_assistant *ia)
688 close_assistant (ia, GTK_RESPONSE_CANCEL);
691 /* Called when the Apply button on the last page of the assistant
694 on_close (GtkAssistant *assistant, struct import_assistant *ia)
696 close_assistant (ia, GTK_RESPONSE_APPLY);
699 /* Called when the Paste button on the last page of the assistant
702 on_paste (GtkButton *button, struct import_assistant *ia)
704 close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
707 /* Called when the Reset button is clicked. */
709 on_reset (GtkButton *button, struct import_assistant *ia)
711 gint page_num = gtk_assistant_get_current_page (ia->asst.assistant);
712 GtkWidget *page = gtk_assistant_get_nth_page (ia->asst.assistant, page_num);
714 if (page == ia->intro.page)
715 reset_intro_page (ia);
716 else if (page == ia->first_line.page)
717 reset_first_line_page (ia);
718 else if (page == ia->separators.page)
719 reset_separators_page (ia);
720 else if (page == ia->formats.page)
721 reset_formats_page (ia);
724 /* Causes the assistant to close, returning RESPONSE for
725 interpretation by text_data_import_assistant. */
727 close_assistant (struct import_assistant *ia, int response)
729 ia->asst.response = response;
730 g_main_loop_quit (ia->asst.main_loop);
731 gtk_widget_hide (GTK_WIDGET (ia->asst.assistant));
734 /* The "intro" page of the assistant. */
736 static void on_intro_amount_changed (struct import_assistant *);
738 /* Initializes IA's intro substructure. */
740 init_intro_page (struct import_assistant *ia)
742 GtkBuilder *builder = ia->asst.builder;
743 struct intro_page *p = &ia->intro;
745 GtkWidget *hbox_n_cases ;
746 GtkWidget *hbox_percent ;
750 p->n_cases_spin = gtk_spin_button_new_with_range (0, INT_MAX, 100);
752 hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &p->n_cases_spin);
754 table = get_widget_assert (builder, "button-table");
756 gtk_table_attach_defaults (GTK_TABLE (table), hbox_n_cases,
760 p->percent_spin = gtk_spin_button_new_with_range (0, 100, 10);
762 hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"), &p->percent_spin);
764 gtk_table_attach_defaults (GTK_TABLE (table), hbox_percent,
768 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Intro"),
769 GTK_ASSISTANT_PAGE_INTRO);
771 p->all_cases_button = get_widget_assert (builder, "import-all-cases");
773 p->n_cases_button = get_widget_assert (builder, "import-n-cases");
775 p->percent_button = get_widget_assert (builder, "import-percent");
777 g_signal_connect_swapped (p->all_cases_button, "toggled",
778 G_CALLBACK (on_intro_amount_changed), ia);
779 g_signal_connect_swapped (p->n_cases_button, "toggled",
780 G_CALLBACK (on_intro_amount_changed), ia);
781 g_signal_connect_swapped (p->percent_button, "toggled",
782 G_CALLBACK (on_intro_amount_changed), ia);
784 on_intro_amount_changed (ia);
787 ds_put_cstr (&s, _("This assistant will guide you through the process of "
788 "importing data into PSPP from a text file with one line "
789 "per case, in which fields are separated by tabs, "
790 "commas, or other delimiters.\n\n"));
791 if (ia->file.total_is_exact)
793 &s, ngettext ("The selected file contains %zu line of text. ",
794 "The selected file contains %zu lines of text. ",
797 else if (ia->file.total_lines > 0)
801 "The selected file contains approximately %lu line of text. ",
802 "The selected file contains approximately %lu lines of text. ",
803 ia->file.total_lines),
804 ia->file.total_lines);
807 "Only the first %zu line of the file will be shown for "
808 "preview purposes in the following screens. ",
809 "Only the first %zu lines of the file will be shown for "
810 "preview purposes in the following screens. ",
814 ds_put_cstr (&s, _("You may choose below how much of the file should "
815 "actually be imported."));
816 gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
821 /* Resets IA's intro page to its initial state. */
823 reset_intro_page (struct import_assistant *ia)
825 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->intro.all_cases_button),
829 /* Called when one of the radio buttons is clicked. */
831 on_intro_amount_changed (struct import_assistant *ia)
833 struct intro_page *p = &ia->intro;
835 gtk_widget_set_sensitive (p->n_cases_spin,
836 gtk_toggle_button_get_active (
837 GTK_TOGGLE_BUTTON (p->n_cases_button)));
839 gtk_widget_set_sensitive (p->percent_spin,
840 gtk_toggle_button_get_active (
841 GTK_TOGGLE_BUTTON (p->percent_button)));
844 /* The "first line" page of the assistant. */
846 static GtkTreeView *create_lines_tree_view (GtkContainer *parent_window,
847 struct import_assistant *);
848 static void on_first_line_change (GtkTreeSelection *,
849 struct import_assistant *);
850 static void on_variable_names_cb_toggle (GtkToggleButton *,
851 struct import_assistant *);
852 static void set_first_line (struct import_assistant *);
853 static void get_first_line (struct import_assistant *);
855 /* Initializes IA's first_line substructure. */
857 init_first_line_page (struct import_assistant *ia)
859 struct first_line_page *p = &ia->first_line;
860 GtkBuilder *builder = ia->asst.builder;
862 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "FirstLine"),
863 GTK_ASSISTANT_PAGE_CONTENT);
864 gtk_widget_destroy (get_widget_assert (builder, "first-line"));
865 p->tree_view = create_lines_tree_view (
866 GTK_CONTAINER (get_widget_assert (builder, "first-line-scroller")), ia);
867 p->variable_names_cb = get_widget_assert (builder, "variable-names");
868 gtk_tree_selection_set_mode (
869 gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
870 GTK_SELECTION_BROWSE);
872 g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
873 "changed", G_CALLBACK (on_first_line_change), ia);
874 g_signal_connect (p->variable_names_cb, "toggled",
875 G_CALLBACK (on_variable_names_cb_toggle), ia);
878 /* Resets the first_line page to its initial content. */
880 reset_first_line_page (struct import_assistant *ia)
882 ia->first_line.skip_lines = 0;
883 ia->first_line.variable_names = false;
888 render_line (GtkTreeViewColumn *tree_column,
889 GtkCellRenderer *cell,
890 GtkTreeModel *tree_model,
894 gint row = empty_list_store_iter_to_row (iter);
895 struct string *lines;
897 lines = g_object_get_data (G_OBJECT (tree_model), "lines");
898 g_return_if_fail (lines != NULL);
900 g_object_set (cell, "text", ds_cstr (&lines[row]), NULL);
904 /* Creates and returns a tree view that contains each of the
905 lines in IA's file as a row. */
907 create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia)
909 GtkTreeView *tree_view;
910 GtkTreeViewColumn *column;
911 size_t max_line_length;
912 gint content_width, header_width;
914 const gchar *title = _("Text");
916 make_tree_view (ia, 0, &tree_view);
918 column = gtk_tree_view_column_new_with_attributes (
919 title, ia->asst.fixed_renderer, (void *) NULL);
920 gtk_tree_view_column_set_cell_data_func (column, ia->asst.fixed_renderer,
921 render_line, NULL, NULL);
922 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
925 for (i = 0; i < ia->file.line_cnt; i++)
927 size_t w = ds_length (&ia->file.lines[i]);
928 max_line_length = MAX (max_line_length, w);
931 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
933 header_width = get_string_width (tree_view, ia->asst.prop_renderer, title);
934 gtk_tree_view_column_set_fixed_width (column, MAX (content_width,
936 gtk_tree_view_append_column (tree_view, column);
938 gtk_tree_view_set_fixed_height_mode (tree_view, true);
940 gtk_container_add (parent, GTK_WIDGET (tree_view));
941 gtk_widget_show (GTK_WIDGET (tree_view));
946 /* Called when the line selected in the first_line tree view
949 on_first_line_change (GtkTreeSelection *selection UNUSED,
950 struct import_assistant *ia)
955 /* Called when the checkbox that indicates whether variable
956 names are in the row above the first line is toggled. */
958 on_variable_names_cb_toggle (GtkToggleButton *variable_names_cb UNUSED,
959 struct import_assistant *ia)
964 /* Sets the widgets to match IA's first_line substructure. */
966 set_first_line (struct import_assistant *ia)
970 path = gtk_tree_path_new_from_indices (ia->first_line.skip_lines, -1);
971 gtk_tree_view_set_cursor (GTK_TREE_VIEW (ia->first_line.tree_view),
973 gtk_tree_path_free (path);
975 gtk_toggle_button_set_active (
976 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb),
977 ia->first_line.variable_names);
978 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
979 ia->first_line.skip_lines > 0);
982 /* Sets IA's first_line substructure to match the widgets. */
984 get_first_line (struct import_assistant *ia)
986 GtkTreeSelection *selection;
990 selection = gtk_tree_view_get_selection (ia->first_line.tree_view);
991 if (gtk_tree_selection_get_selected (selection, &model, &iter))
993 GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
994 int row = gtk_tree_path_get_indices (path)[0];
995 gtk_tree_path_free (path);
997 ia->first_line.skip_lines = row;
998 ia->first_line.variable_names =
999 (ia->first_line.skip_lines > 0
1000 && gtk_toggle_button_get_active (
1001 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb)));
1003 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
1004 ia->first_line.skip_lines > 0);
1007 /* The "separators" page of the assistant. */
1009 static void revise_fields_preview (struct import_assistant *ia);
1010 static void choose_likely_separators (struct import_assistant *ia);
1011 static void find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
1012 const char *targets, const char *def,
1013 struct string *result);
1014 static void clear_fields (struct import_assistant *ia);
1015 static void revise_fields_preview (struct import_assistant *);
1016 static void set_separators (struct import_assistant *);
1017 static void get_separators (struct import_assistant *);
1018 static void on_separators_custom_entry_notify (GObject *UNUSED,
1020 struct import_assistant *);
1021 static void on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1022 struct import_assistant *);
1023 static void on_quote_combo_change (GtkComboBox *combo,
1024 struct import_assistant *);
1025 static void on_quote_cb_toggle (GtkToggleButton *quote_cb,
1026 struct import_assistant *);
1027 static void on_separator_toggle (GtkToggleButton *, struct import_assistant *);
1028 static void render_input_cell (GtkTreeViewColumn *tree_column,
1029 GtkCellRenderer *cell,
1030 GtkTreeModel *model, GtkTreeIter *iter,
1032 static gboolean on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1033 gboolean keyboard_mode UNUSED,
1034 GtkTooltip *tooltip,
1035 struct import_assistant *);
1037 /* A common field separator and its identifying name. */
1040 const char *name; /* Name (for use with get_widget_assert). */
1041 int c; /* Separator character. */
1044 /* All the separators in the dialog box. */
1045 static const struct separator separators[] =
1057 #define SEPARATOR_CNT (sizeof separators / sizeof *separators)
1060 set_quote_list (GtkComboBoxEntry *cb)
1062 GtkListStore *list = gtk_list_store_new (1, G_TYPE_STRING);
1065 const gchar *seperator[3] = {"'\"", "\'", "\""};
1067 for (i = 0; i < 3; i++)
1069 const gchar *s = seperator[i];
1071 /* Add a new row to the model */
1072 gtk_list_store_append (list, &iter);
1073 gtk_list_store_set (list, &iter,
1079 gtk_combo_box_set_model (GTK_COMBO_BOX (cb), GTK_TREE_MODEL (list));
1081 gtk_combo_box_entry_set_text_column (cb, 0);
1084 /* Initializes IA's separators substructure. */
1086 init_separators_page (struct import_assistant *ia)
1088 GtkBuilder *builder = ia->asst.builder;
1089 struct separators_page *p = &ia->separators;
1092 choose_likely_separators (ia);
1094 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Separators"),
1095 GTK_ASSISTANT_PAGE_CONTENT);
1096 p->custom_cb = get_widget_assert (builder, "custom-cb");
1097 p->custom_entry = get_widget_assert (builder, "custom-entry");
1098 p->quote_combo = get_widget_assert (builder, "quote-combo");
1099 p->quote_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (p->quote_combo)));
1100 p->quote_cb = get_widget_assert (builder, "quote-cb");
1101 p->escape_cb = get_widget_assert (builder, "escape");
1103 set_separators (ia);
1104 set_quote_list (GTK_COMBO_BOX_ENTRY (p->quote_combo));
1105 p->fields_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "fields"));
1106 g_signal_connect (p->quote_combo, "changed",
1107 G_CALLBACK (on_quote_combo_change), ia);
1108 g_signal_connect (p->quote_cb, "toggled",
1109 G_CALLBACK (on_quote_cb_toggle), ia);
1110 g_signal_connect (p->custom_entry, "notify::text",
1111 G_CALLBACK (on_separators_custom_entry_notify), ia);
1112 g_signal_connect (p->custom_cb, "toggled",
1113 G_CALLBACK (on_separators_custom_cb_toggle), ia);
1114 for (i = 0; i < SEPARATOR_CNT; i++)
1115 g_signal_connect (get_widget_assert (builder, separators[i].name),
1116 "toggled", G_CALLBACK (on_separator_toggle), ia);
1117 g_signal_connect (p->escape_cb, "toggled",
1118 G_CALLBACK (on_separator_toggle), ia);
1121 /* Frees IA's separators substructure. */
1123 destroy_separators_page (struct import_assistant *ia)
1125 struct separators_page *s = &ia->separators;
1127 ds_destroy (&s->separators);
1128 ds_destroy (&s->quotes);
1132 /* Called just before the separators page becomes visible in the
1135 prepare_separators_page (struct import_assistant *ia)
1137 revise_fields_preview (ia);
1140 /* Called when the Reset button is clicked on the separators
1141 page, resets the separators to the defaults. */
1143 reset_separators_page (struct import_assistant *ia)
1145 choose_likely_separators (ia);
1146 set_separators (ia);
1149 /* Frees and clears the column data in IA's separators
1152 clear_fields (struct import_assistant *ia)
1154 struct separators_page *s = &ia->separators;
1156 if (s->column_cnt > 0)
1161 for (row = 0; row < ia->file.line_cnt; row++)
1163 const struct string *line = &ia->file.lines[row];
1164 const char *line_start = ds_data (line);
1165 const char *line_end = ds_end (line);
1167 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1169 char *s = ss_data (col->contents[row]);
1170 if (!(s >= line_start && s <= line_end))
1171 ss_dealloc (&col->contents[row]);
1175 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1178 free (col->contents);
1187 /* Breaks the file data in IA into columns based on the
1188 separators set in IA's separators substructure. */
1190 split_fields (struct import_assistant *ia)
1192 struct separators_page *s = &ia->separators;
1193 size_t columns_allocated;
1199 /* Is space in the set of separators? */
1200 space_sep = ss_find_byte (ds_ss (&s->separators), ' ') != SIZE_MAX;
1202 /* Split all the lines, not just those from
1203 ia->first_line.skip_lines on, so that we split the line that
1204 contains variables names if ia->first_line.variable_names is
1206 columns_allocated = 0;
1207 for (row = 0; row < ia->file.line_cnt; row++)
1209 struct string *line = &ia->file.lines[row];
1210 struct substring text = ds_ss (line);
1213 for (column_idx = 0; ; column_idx++)
1215 struct substring field;
1216 struct column *column;
1219 ss_ltrim (&text, ss_cstr (" "));
1220 if (ss_is_empty (text))
1222 if (column_idx != 0)
1226 else if (!ds_is_empty (&s->quotes)
1227 && ds_find_byte (&s->quotes, text.string[0]) != SIZE_MAX)
1229 int quote = ss_get_byte (&text);
1231 ss_get_until (&text, quote, &field);
1238 while ((c = ss_get_byte (&text)) != EOF)
1240 ds_put_byte (&s, c);
1241 else if (ss_match_byte (&text, quote))
1242 ds_put_byte (&s, quote);
1249 ss_get_bytes (&text, ss_cspan (text, ds_ss (&s->separators)),
1252 if (column_idx >= s->column_cnt)
1254 struct column *column;
1256 if (s->column_cnt >= columns_allocated)
1257 s->columns = x2nrealloc (s->columns, &columns_allocated,
1258 sizeof *s->columns);
1259 column = &s->columns[s->column_cnt++];
1260 column->name = NULL;
1262 column->contents = xcalloc (ia->file.line_cnt,
1263 sizeof *column->contents);
1265 column = &s->columns[column_idx];
1266 column->contents[row] = field;
1267 if (ss_length (field) > column->width)
1268 column->width = ss_length (field);
1271 ss_ltrim (&text, ss_cstr (" "));
1272 if (ss_is_empty (text))
1274 if (ss_find_byte (ds_ss (&s->separators), ss_first (text))
1276 ss_advance (&text, 1);
1281 /* Chooses a name for each column on the separators page */
1283 choose_column_names (struct import_assistant *ia)
1285 const struct first_line_page *f = &ia->first_line;
1286 struct separators_page *s = &ia->separators;
1287 struct dictionary *dict;
1288 unsigned long int generated_name_count = 0;
1292 dict = dict_create (get_default_encoding ());
1293 name_row = f->variable_names && f->skip_lines ? f->skip_lines : 0;
1294 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1298 hint = name_row ? ss_xstrdup (col->contents[name_row - 1]) : NULL;
1299 name = dict_make_unique_var_name (dict, hint, &generated_name_count);
1303 dict_create_var_assert (dict, name, 0);
1305 dict_destroy (dict);
1308 /* Picks the most likely separator and quote characters based on
1311 choose_likely_separators (struct import_assistant *ia)
1313 unsigned long int histogram[UCHAR_MAX + 1] = { 0 };
1316 /* Construct a histogram of all the characters used in the
1318 for (row = 0; row < ia->file.line_cnt; row++)
1320 struct substring line = ds_ss (&ia->file.lines[row]);
1321 size_t length = ss_length (line);
1323 for (i = 0; i < length; i++)
1324 histogram[(unsigned char) line.string[i]]++;
1327 find_commonest_chars (histogram, "\"'", "", &ia->separators.quotes);
1328 find_commonest_chars (histogram, ",;:/|!\t-", ",",
1329 &ia->separators.separators);
1330 ia->separators.escape = true;
1333 /* Chooses the most common character among those in TARGETS,
1334 based on the frequency data in HISTOGRAM, and stores it in
1335 RESULT. If there is a tie for the most common character among
1336 those in TARGETS, the earliest character is chosen. If none
1337 of the TARGETS appear at all, then DEF is used as a
1340 find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
1341 const char *targets, const char *def,
1342 struct string *result)
1344 unsigned char max = 0;
1345 unsigned long int max_count = 0;
1347 for (; *targets != '\0'; targets++)
1349 unsigned char c = *targets;
1350 unsigned long int count = histogram[c];
1351 if (count > max_count)
1360 ds_put_byte (result, max);
1363 ds_assign_cstr (result, def);
1366 /* Revises the contents of the fields tree view based on the
1367 currently chosen set of separators. */
1369 revise_fields_preview (struct import_assistant *ia)
1373 push_watch_cursor (ia);
1375 w = GTK_WIDGET (ia->separators.fields_tree_view);
1376 gtk_widget_destroy (w);
1377 get_separators (ia);
1379 choose_column_names (ia);
1380 ia->separators.fields_tree_view = create_data_tree_view (
1382 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "fields-scroller")),
1385 pop_watch_cursor (ia);
1388 /* Sets the widgets to match IA's separators substructure. */
1390 set_separators (struct import_assistant *ia)
1392 struct separators_page *s = &ia->separators;
1394 struct string custom;
1399 ds_init_empty (&custom);
1401 for (i = 0; i < ds_length (&s->separators); i++)
1403 unsigned char c = ds_at (&s->separators, i);
1406 for (j = 0; j < SEPARATOR_CNT; j++)
1408 const struct separator *s = &separators[j];
1416 ds_put_byte (&custom, c);
1420 for (i = 0; i < SEPARATOR_CNT; i++)
1422 const struct separator *s = &separators[i];
1423 GtkWidget *button = get_widget_assert (ia->asst.builder, s->name);
1424 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
1425 (seps & (1u << i)) != 0);
1427 any_custom = !ds_is_empty (&custom);
1428 gtk_entry_set_text (GTK_ENTRY (s->custom_entry), ds_cstr (&custom));
1429 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->custom_cb),
1431 gtk_widget_set_sensitive (s->custom_entry, any_custom);
1432 ds_destroy (&custom);
1434 any_quotes = !ds_is_empty (&s->quotes);
1436 gtk_entry_set_text (s->quote_entry,
1437 any_quotes ? ds_cstr (&s->quotes) : "\"");
1438 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->quote_cb),
1440 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->escape_cb),
1442 gtk_widget_set_sensitive (s->quote_combo, any_quotes);
1443 gtk_widget_set_sensitive (s->escape_cb, any_quotes);
1446 /* Sets IA's separators substructure to match the widgets. */
1448 get_separators (struct import_assistant *ia)
1450 struct separators_page *s = &ia->separators;
1453 ds_clear (&s->separators);
1454 for (i = 0; i < SEPARATOR_CNT; i++)
1456 const struct separator *sep = &separators[i];
1457 GtkWidget *button = get_widget_assert (ia->asst.builder, sep->name);
1458 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1459 ds_put_byte (&s->separators, sep->c);
1462 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->custom_cb)))
1463 ds_put_cstr (&s->separators,
1464 gtk_entry_get_text (GTK_ENTRY (s->custom_entry)));
1466 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->quote_cb)))
1468 gchar *text = gtk_combo_box_get_active_text (
1469 GTK_COMBO_BOX (s->quote_combo));
1470 ds_assign_cstr (&s->quotes, text);
1474 ds_clear (&s->quotes);
1475 s->escape = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->escape_cb));
1478 /* Called when the user changes the entry field for custom
1481 on_separators_custom_entry_notify (GObject *gobject UNUSED,
1482 GParamSpec *arg1 UNUSED,
1483 struct import_assistant *ia)
1485 revise_fields_preview (ia);
1488 /* Called when the user toggles the checkbox that enables custom
1491 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1492 struct import_assistant *ia)
1494 bool is_active = gtk_toggle_button_get_active (custom_cb);
1495 gtk_widget_set_sensitive (ia->separators.custom_entry, is_active);
1496 revise_fields_preview (ia);
1499 /* Called when the user changes the selection in the combo box
1500 that selects a quote character. */
1502 on_quote_combo_change (GtkComboBox *combo, struct import_assistant *ia)
1504 revise_fields_preview (ia);
1507 /* Called when the user toggles the checkbox that enables
1510 on_quote_cb_toggle (GtkToggleButton *quote_cb, struct import_assistant *ia)
1512 bool is_active = gtk_toggle_button_get_active (quote_cb);
1513 gtk_widget_set_sensitive (ia->separators.quote_combo, is_active);
1514 gtk_widget_set_sensitive (ia->separators.escape_cb, is_active);
1515 revise_fields_preview (ia);
1518 /* Called when the user toggles one of the separators
1521 on_separator_toggle (GtkToggleButton *toggle UNUSED,
1522 struct import_assistant *ia)
1524 revise_fields_preview (ia);
1527 /* Called to render one of the cells in the fields preview tree
1530 render_input_cell (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1531 GtkTreeModel *model, GtkTreeIter *iter,
1534 struct import_assistant *ia = ia_;
1535 struct substring field;
1539 column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1541 row = empty_list_store_iter_to_row (iter) + ia->first_line.skip_lines;
1542 field = ia->separators.columns[column].contents[row];
1543 if (field.string != NULL)
1545 GValue text = {0, };
1546 g_value_init (&text, G_TYPE_STRING);
1547 g_value_take_string (&text, ss_xstrdup (field));
1548 g_object_set_property (G_OBJECT (cell), "text", &text);
1549 g_value_unset (&text);
1550 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1555 "background", "red",
1556 "background-set", TRUE,
1560 /* Called to render a tooltip on one of the cells in the fields
1561 preview tree view. */
1563 on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1564 gboolean keyboard_mode UNUSED,
1565 GtkTooltip *tooltip, struct import_assistant *ia)
1569 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1572 if (ia->separators.columns[column].contents[row].string != NULL)
1575 gtk_tooltip_set_text (tooltip,
1576 _("This input line has too few separators "
1577 "to fill in this field."));
1581 /* The "formats" page of the assistant. */
1583 static void on_variable_change (PsppireDict *dict, int idx,
1584 struct import_assistant *);
1585 static void clear_modified_vars (struct import_assistant *);
1587 /* Initializes IA's formats substructure. */
1589 init_formats_page (struct import_assistant *ia)
1591 GtkBuilder *builder = ia->asst.builder;
1592 struct formats_page *p = &ia->formats;
1594 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Formats"),
1595 GTK_ASSISTANT_PAGE_CONFIRM);
1596 p->data_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "data"));
1597 p->modified_vars = NULL;
1598 p->modified_var_cnt = 0;
1602 /* Frees IA's formats substructure. */
1604 destroy_formats_page (struct import_assistant *ia)
1606 struct formats_page *p = &ia->formats;
1608 if (p->psppire_dict != NULL)
1610 /* This destroys p->dict also. */
1611 g_object_unref (p->psppire_dict);
1613 clear_modified_vars (ia);
1616 /* Called just before the formats page of the assistant is
1619 prepare_formats_page (struct import_assistant *ia)
1621 struct dictionary *dict;
1622 PsppireDict *psppire_dict;
1623 PsppireVarStore *var_store;
1624 GtkBin *vars_scroller;
1625 GtkWidget *old_var_sheet;
1626 PsppireVarSheet *var_sheet;
1627 struct separators_page *s = &ia->separators;
1628 struct formats_page *p = &ia->formats;
1629 struct fmt_guesser *fg;
1630 unsigned long int number = 0;
1633 push_watch_cursor (ia);
1635 dict = dict_create (get_default_encoding ());
1636 fg = fmt_guesser_create ();
1637 for (column_idx = 0; column_idx < s->column_cnt; column_idx++)
1639 struct variable *modified_var;
1641 modified_var = (column_idx < p->modified_var_cnt
1642 ? p->modified_vars[column_idx] : NULL);
1643 if (modified_var == NULL)
1645 struct column *column = &s->columns[column_idx];
1646 struct variable *var;
1647 struct fmt_spec format;
1651 /* Choose variable name. */
1652 name = dict_make_unique_var_name (dict, column->name, &number);
1654 /* Choose variable format. */
1655 fmt_guesser_clear (fg);
1656 for (row = ia->first_line.skip_lines; row < ia->file.line_cnt; row++)
1657 fmt_guesser_add (fg, column->contents[row]);
1658 fmt_guesser_guess (fg, &format);
1659 fmt_fix_input (&format);
1661 /* Create variable. */
1662 var = dict_create_var_assert (dict, name, fmt_var_width (&format));
1663 var_set_both_formats (var, &format);
1671 name = dict_make_unique_var_name (dict, var_get_name (modified_var),
1673 dict_clone_var_as_assert (dict, modified_var, name);
1677 fmt_guesser_destroy (fg);
1679 psppire_dict = psppire_dict_new_from_dict (dict);
1680 g_signal_connect (psppire_dict, "variable_changed",
1681 G_CALLBACK (on_variable_change), ia);
1682 ia->formats.dict = dict;
1683 ia->formats.psppire_dict = psppire_dict;
1685 /* XXX: PsppireVarStore doesn't hold a reference to
1686 psppire_dict for now, but it should. After it does, we
1687 should g_object_ref the psppire_dict here, since we also
1688 hold a reference via ia->formats.dict. */
1689 var_store = psppire_var_store_new (psppire_dict);
1690 g_object_set (var_store,
1691 "format-type", PSPPIRE_VAR_STORE_INPUT_FORMATS,
1693 var_sheet = PSPPIRE_VAR_SHEET (psppire_var_sheet_new ());
1694 g_object_set (var_sheet,
1696 "may-create-vars", FALSE,
1699 vars_scroller = GTK_BIN (get_widget_assert (ia->asst.builder, "vars-scroller"));
1700 old_var_sheet = gtk_bin_get_child (vars_scroller);
1701 if (old_var_sheet != NULL)
1702 gtk_widget_destroy (old_var_sheet);
1703 gtk_container_add (GTK_CONTAINER (vars_scroller), GTK_WIDGET (var_sheet));
1704 gtk_widget_show (GTK_WIDGET (var_sheet));
1706 gtk_widget_destroy (GTK_WIDGET (ia->formats.data_tree_view));
1707 ia->formats.data_tree_view = create_data_tree_view (
1709 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "data-scroller")),
1712 pop_watch_cursor (ia);
1715 /* Clears the set of user-modified variables from IA's formats
1716 substructure. This discards user modifications to variable
1717 formats, thereby causing formats to revert to their
1720 clear_modified_vars (struct import_assistant *ia)
1722 struct formats_page *p = &ia->formats;
1725 for (i = 0; i < p->modified_var_cnt; i++)
1726 var_destroy (p->modified_vars[i]);
1727 free (p->modified_vars);
1728 p->modified_vars = NULL;
1729 p->modified_var_cnt = 0;
1732 /* Resets the formats page to its defaults, discarding user
1735 reset_formats_page (struct import_assistant *ia)
1737 clear_modified_vars (ia);
1738 prepare_formats_page (ia);
1741 /* Called when the user changes one of the variables in the
1744 on_variable_change (PsppireDict *dict, int dict_idx,
1745 struct import_assistant *ia)
1747 struct formats_page *p = &ia->formats;
1748 GtkTreeView *tv = ia->formats.data_tree_view;
1749 gint column_idx = dict_idx + 1;
1751 push_watch_cursor (ia);
1753 /* Remove previous column and replace with new column. */
1754 gtk_tree_view_remove_column (tv, gtk_tree_view_get_column (tv, column_idx));
1755 gtk_tree_view_insert_column (tv, make_data_column (ia, tv, false, dict_idx),
1758 /* Save a copy of the modified variable in modified_vars, so
1759 that its attributes will be preserved if we back up to the
1760 previous page with the Prev button and then come back
1762 if (dict_idx >= p->modified_var_cnt)
1765 p->modified_vars = xnrealloc (p->modified_vars, dict_idx + 1,
1766 sizeof *p->modified_vars);
1767 for (i = 0; i <= dict_idx; i++)
1768 p->modified_vars[i] = NULL;
1769 p->modified_var_cnt = dict_idx + 1;
1771 if (p->modified_vars[dict_idx])
1772 var_destroy (p->modified_vars[dict_idx]);
1773 p->modified_vars[dict_idx]
1774 = var_clone (psppire_dict_get_variable (dict, dict_idx));
1776 pop_watch_cursor (ia);
1779 /* Parses the contents of the field at (ROW,COLUMN) according to
1780 its variable format. If OUTPUTP is non-null, then *OUTPUTP
1781 receives the formatted output for that field (which must be
1782 freed with free). If TOOLTIPP is non-null, then *TOOLTIPP
1783 receives a message suitable for use in a tooltip, if one is
1784 needed, or a null pointer otherwise. Returns true if a
1785 tooltip message is needed, otherwise false. */
1787 parse_field (struct import_assistant *ia,
1788 size_t row, size_t column,
1789 char **outputp, char **tooltipp)
1791 struct substring field;
1793 struct variable *var;
1794 const struct fmt_spec *in;
1795 struct fmt_spec out;
1799 field = ia->separators.columns[column].contents[row];
1800 var = dict_get_var (ia->formats.dict, column);
1801 value_init (&val, var_get_width (var));
1802 in = var_get_print_format (var);
1803 out = fmt_for_output_from_input (in);
1805 if (field.string != NULL)
1809 error = data_in (field, C_ENCODING, in->type, &val, var_get_width (var),
1810 dict_get_encoding (ia->formats.dict));
1813 tooltip = xasprintf (_("Cannot parse field content `%.*s' as "
1815 (int) field.length, field.string,
1816 fmt_name (in->type), error);
1822 tooltip = xstrdup (_("This input line has too few separators "
1823 "to fill in this field."));
1824 value_set_missing (&val, var_get_width (var));
1826 if (outputp != NULL)
1828 *outputp = data_out (&val, dict_get_encoding (ia->formats.dict), &out);
1830 value_destroy (&val, var_get_width (var));
1832 ok = tooltip == NULL;
1833 if (tooltipp != NULL)
1834 *tooltipp = tooltip;
1840 /* Called to render one of the cells in the data preview tree
1843 render_output_cell (GtkTreeViewColumn *tree_column,
1844 GtkCellRenderer *cell,
1845 GtkTreeModel *model,
1849 struct import_assistant *ia = ia_;
1851 GValue gvalue = { 0, };
1854 ok = parse_field (ia,
1855 (empty_list_store_iter_to_row (iter)
1856 + ia->first_line.skip_lines),
1857 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1861 g_value_init (&gvalue, G_TYPE_STRING);
1862 g_value_take_string (&gvalue, output);
1863 g_object_set_property (G_OBJECT (cell), "text", &gvalue);
1864 g_value_unset (&gvalue);
1867 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1870 "background", "red",
1871 "background-set", TRUE,
1875 /* Called to render a tooltip for one of the cells in the data
1876 preview tree view. */
1878 on_query_output_tooltip (GtkWidget *widget, gint wx, gint wy,
1879 gboolean keyboard_mode UNUSED,
1880 GtkTooltip *tooltip, struct import_assistant *ia)
1885 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1888 if (parse_field (ia, row, column, NULL, &text))
1891 gtk_tooltip_set_text (tooltip, text);
1896 /* Utility functions used by multiple pages of the assistant. */
1899 get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
1900 const struct import_assistant *ia,
1901 size_t *row, size_t *column)
1903 GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
1907 GtkTreeViewColumn *tree_column;
1908 GtkTreeModel *tree_model;
1911 /* Check that WIDGET is really visible on the screen before we
1912 do anything else. This is a bug fix for a sticky situation:
1913 when text_data_import_assistant() returns, it frees the data
1914 necessary to compose the tool tip message, but there may be
1915 a tool tip under preparation at that point (even if there is
1916 no visible tool tip) that will call back into us a little
1917 bit later. Perhaps the correct solution to this problem is
1918 to make the data related to the tool tips part of a GObject
1919 that only gets destroyed when all references are released,
1920 but this solution appears to be effective too. */
1921 if (!gtk_widget_get_mapped (widget))
1924 gtk_tree_view_convert_widget_to_bin_window_coords (tree_view,
1926 if (!gtk_tree_view_get_path_at_pos (tree_view, bx, by,
1927 &path, &tree_column, NULL, NULL))
1930 *column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1933 tree_model = gtk_tree_view_get_model (tree_view);
1934 ok = gtk_tree_model_get_iter (tree_model, &iter, path);
1935 gtk_tree_path_free (path);
1939 *row = empty_list_store_iter_to_row (&iter) + ia->first_line.skip_lines;
1944 make_tree_view (const struct import_assistant *ia,
1946 GtkTreeView **tree_view)
1948 GtkTreeModel *model;
1950 *tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
1951 model = GTK_TREE_MODEL (psppire_empty_list_store_new (
1952 ia->file.line_cnt - first_line));
1953 g_object_set_data (G_OBJECT (model), "lines", ia->file.lines + first_line);
1954 g_object_set_data (G_OBJECT (model), "first-line",
1955 GINT_TO_POINTER (first_line));
1956 gtk_tree_view_set_model (*tree_view, model);
1958 add_line_number_column (ia, *tree_view);
1962 render_line_number (GtkTreeViewColumn *tree_column,
1963 GtkCellRenderer *cell,
1964 GtkTreeModel *tree_model,
1968 gint row = empty_list_store_iter_to_row (iter);
1969 char s[INT_BUFSIZE_BOUND (int)];
1972 first_line = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_model),
1974 sprintf (s, "%d", first_line + row);
1975 g_object_set (cell, "text", s, NULL);
1979 add_line_number_column (const struct import_assistant *ia,
1980 GtkTreeView *treeview)
1982 GtkTreeViewColumn *column;
1984 column = gtk_tree_view_column_new_with_attributes (
1985 _("Line"), ia->asst.prop_renderer, (void *) NULL);
1986 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
1987 gtk_tree_view_column_set_fixed_width (
1988 column, get_monospace_width (treeview, ia->asst.prop_renderer, 5));
1989 gtk_tree_view_column_set_resizable (column, TRUE);
1990 gtk_tree_view_column_set_cell_data_func (column, ia->asst.prop_renderer,
1991 render_line_number, NULL, NULL);
1992 gtk_tree_view_append_column (treeview, column);
1996 get_monospace_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
2003 ds_put_byte_multiple (&s, '0', char_cnt);
2004 ds_put_byte (&s, ' ');
2005 width = get_string_width (treeview, renderer, ds_cstr (&s));
2012 get_string_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
2016 g_object_set (G_OBJECT (renderer), "text", string, (void *) NULL);
2017 gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
2018 NULL, NULL, NULL, &width, NULL);
2022 static GtkTreeViewColumn *
2023 make_data_column (struct import_assistant *ia, GtkTreeView *tree_view,
2024 bool input, gint dict_idx)
2026 struct variable *var = NULL;
2027 struct column *column = NULL;
2029 gint content_width, header_width;
2030 GtkTreeViewColumn *tree_column;
2034 column = &ia->separators.columns[dict_idx];
2036 var = dict_get_var (ia->formats.dict, dict_idx);
2038 name = escape_underscores (input ? column->name : var_get_name (var));
2039 char_cnt = input ? column->width : var_get_print_format (var)->w;
2040 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
2042 header_width = get_string_width (tree_view, ia->asst.prop_renderer,
2045 tree_column = gtk_tree_view_column_new ();
2046 g_object_set_data (G_OBJECT (tree_column), "column-number",
2047 GINT_TO_POINTER (dict_idx));
2048 gtk_tree_view_column_set_title (tree_column, name);
2049 gtk_tree_view_column_pack_start (tree_column, ia->asst.fixed_renderer,
2051 gtk_tree_view_column_set_cell_data_func (
2052 tree_column, ia->asst.fixed_renderer,
2053 input ? render_input_cell : render_output_cell, ia, NULL);
2054 gtk_tree_view_column_set_sizing (tree_column, GTK_TREE_VIEW_COLUMN_FIXED);
2055 gtk_tree_view_column_set_fixed_width (tree_column, MAX (content_width,
2063 static GtkTreeView *
2064 create_data_tree_view (bool input, GtkContainer *parent,
2065 struct import_assistant *ia)
2067 GtkTreeView *tree_view;
2070 make_tree_view (ia, ia->first_line.skip_lines, &tree_view);
2071 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (tree_view),
2072 GTK_SELECTION_NONE);
2074 for (i = 0; i < ia->separators.column_cnt; i++)
2075 gtk_tree_view_append_column (tree_view,
2076 make_data_column (ia, tree_view, input, i));
2078 g_object_set (G_OBJECT (tree_view), "has-tooltip", TRUE, (void *) NULL);
2079 g_signal_connect (tree_view, "query-tooltip",
2080 G_CALLBACK (input ? on_query_input_tooltip
2081 : on_query_output_tooltip), ia);
2082 gtk_tree_view_set_fixed_height_mode (tree_view, true);
2084 gtk_container_add (parent, GTK_WIDGET (tree_view));
2085 gtk_widget_show (GTK_WIDGET (tree_view));
2090 /* Increments the "watch cursor" level, setting the cursor for
2091 the assistant window to a watch face to indicate to the user
2092 that the ongoing operation may take some time. */
2094 push_watch_cursor (struct import_assistant *ia)
2096 if (++ia->asst.watch_cursor == 1)
2098 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2099 GdkDisplay *display = gtk_widget_get_display (widget);
2100 GdkCursor *cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
2101 gdk_window_set_cursor (widget->window, cursor);
2102 gdk_cursor_unref (cursor);
2103 gdk_display_flush (display);
2107 /* Decrements the "watch cursor" level. If the level reaches
2108 zero, the cursor is reset to its default shape. */
2110 pop_watch_cursor (struct import_assistant *ia)
2112 if (--ia->asst.watch_cursor == 0)
2114 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2115 gdk_window_set_cursor (widget->window, NULL);