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));
1080 g_object_unref (list);
1082 gtk_combo_box_entry_set_text_column (cb, 0);
1085 /* Initializes IA's separators substructure. */
1087 init_separators_page (struct import_assistant *ia)
1089 GtkBuilder *builder = ia->asst.builder;
1090 struct separators_page *p = &ia->separators;
1093 choose_likely_separators (ia);
1095 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Separators"),
1096 GTK_ASSISTANT_PAGE_CONTENT);
1097 p->custom_cb = get_widget_assert (builder, "custom-cb");
1098 p->custom_entry = get_widget_assert (builder, "custom-entry");
1099 p->quote_combo = get_widget_assert (builder, "quote-combo");
1100 p->quote_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (p->quote_combo)));
1101 p->quote_cb = get_widget_assert (builder, "quote-cb");
1102 p->escape_cb = get_widget_assert (builder, "escape");
1104 set_separators (ia);
1105 set_quote_list (GTK_COMBO_BOX_ENTRY (p->quote_combo));
1106 p->fields_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "fields"));
1107 g_signal_connect (p->quote_combo, "changed",
1108 G_CALLBACK (on_quote_combo_change), ia);
1109 g_signal_connect (p->quote_cb, "toggled",
1110 G_CALLBACK (on_quote_cb_toggle), ia);
1111 g_signal_connect (p->custom_entry, "notify::text",
1112 G_CALLBACK (on_separators_custom_entry_notify), ia);
1113 g_signal_connect (p->custom_cb, "toggled",
1114 G_CALLBACK (on_separators_custom_cb_toggle), ia);
1115 for (i = 0; i < SEPARATOR_CNT; i++)
1116 g_signal_connect (get_widget_assert (builder, separators[i].name),
1117 "toggled", G_CALLBACK (on_separator_toggle), ia);
1118 g_signal_connect (p->escape_cb, "toggled",
1119 G_CALLBACK (on_separator_toggle), ia);
1122 /* Frees IA's separators substructure. */
1124 destroy_separators_page (struct import_assistant *ia)
1126 struct separators_page *s = &ia->separators;
1128 ds_destroy (&s->separators);
1129 ds_destroy (&s->quotes);
1133 /* Called just before the separators page becomes visible in the
1136 prepare_separators_page (struct import_assistant *ia)
1138 revise_fields_preview (ia);
1141 /* Called when the Reset button is clicked on the separators
1142 page, resets the separators to the defaults. */
1144 reset_separators_page (struct import_assistant *ia)
1146 choose_likely_separators (ia);
1147 set_separators (ia);
1150 /* Frees and clears the column data in IA's separators
1153 clear_fields (struct import_assistant *ia)
1155 struct separators_page *s = &ia->separators;
1157 if (s->column_cnt > 0)
1162 for (row = 0; row < ia->file.line_cnt; row++)
1164 const struct string *line = &ia->file.lines[row];
1165 const char *line_start = ds_data (line);
1166 const char *line_end = ds_end (line);
1168 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1170 char *s = ss_data (col->contents[row]);
1171 if (!(s >= line_start && s <= line_end))
1172 ss_dealloc (&col->contents[row]);
1176 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1179 free (col->contents);
1188 /* Breaks the file data in IA into columns based on the
1189 separators set in IA's separators substructure. */
1191 split_fields (struct import_assistant *ia)
1193 struct separators_page *s = &ia->separators;
1194 size_t columns_allocated;
1200 /* Is space in the set of separators? */
1201 space_sep = ss_find_byte (ds_ss (&s->separators), ' ') != SIZE_MAX;
1203 /* Split all the lines, not just those from
1204 ia->first_line.skip_lines on, so that we split the line that
1205 contains variables names if ia->first_line.variable_names is
1207 columns_allocated = 0;
1208 for (row = 0; row < ia->file.line_cnt; row++)
1210 struct string *line = &ia->file.lines[row];
1211 struct substring text = ds_ss (line);
1214 for (column_idx = 0; ; column_idx++)
1216 struct substring field;
1217 struct column *column;
1220 ss_ltrim (&text, ss_cstr (" "));
1221 if (ss_is_empty (text))
1223 if (column_idx != 0)
1227 else if (!ds_is_empty (&s->quotes)
1228 && ds_find_byte (&s->quotes, text.string[0]) != SIZE_MAX)
1230 int quote = ss_get_byte (&text);
1232 ss_get_until (&text, quote, &field);
1239 while ((c = ss_get_byte (&text)) != EOF)
1241 ds_put_byte (&s, c);
1242 else if (ss_match_byte (&text, quote))
1243 ds_put_byte (&s, quote);
1250 ss_get_bytes (&text, ss_cspan (text, ds_ss (&s->separators)),
1253 if (column_idx >= s->column_cnt)
1255 struct column *column;
1257 if (s->column_cnt >= columns_allocated)
1258 s->columns = x2nrealloc (s->columns, &columns_allocated,
1259 sizeof *s->columns);
1260 column = &s->columns[s->column_cnt++];
1261 column->name = NULL;
1263 column->contents = xcalloc (ia->file.line_cnt,
1264 sizeof *column->contents);
1266 column = &s->columns[column_idx];
1267 column->contents[row] = field;
1268 if (ss_length (field) > column->width)
1269 column->width = ss_length (field);
1272 ss_ltrim (&text, ss_cstr (" "));
1273 if (ss_is_empty (text))
1275 if (ss_find_byte (ds_ss (&s->separators), ss_first (text))
1277 ss_advance (&text, 1);
1282 /* Chooses a name for each column on the separators page */
1284 choose_column_names (struct import_assistant *ia)
1286 const struct first_line_page *f = &ia->first_line;
1287 struct separators_page *s = &ia->separators;
1288 struct dictionary *dict;
1289 unsigned long int generated_name_count = 0;
1293 dict = dict_create (get_default_encoding ());
1294 name_row = f->variable_names && f->skip_lines ? f->skip_lines : 0;
1295 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1299 hint = name_row ? ss_xstrdup (col->contents[name_row - 1]) : NULL;
1300 name = dict_make_unique_var_name (dict, hint, &generated_name_count);
1304 dict_create_var_assert (dict, name, 0);
1306 dict_destroy (dict);
1309 /* Picks the most likely separator and quote characters based on
1312 choose_likely_separators (struct import_assistant *ia)
1314 unsigned long int histogram[UCHAR_MAX + 1] = { 0 };
1317 /* Construct a histogram of all the characters used in the
1319 for (row = 0; row < ia->file.line_cnt; row++)
1321 struct substring line = ds_ss (&ia->file.lines[row]);
1322 size_t length = ss_length (line);
1324 for (i = 0; i < length; i++)
1325 histogram[(unsigned char) line.string[i]]++;
1328 find_commonest_chars (histogram, "\"'", "", &ia->separators.quotes);
1329 find_commonest_chars (histogram, ",;:/|!\t-", ",",
1330 &ia->separators.separators);
1331 ia->separators.escape = true;
1334 /* Chooses the most common character among those in TARGETS,
1335 based on the frequency data in HISTOGRAM, and stores it in
1336 RESULT. If there is a tie for the most common character among
1337 those in TARGETS, the earliest character is chosen. If none
1338 of the TARGETS appear at all, then DEF is used as a
1341 find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
1342 const char *targets, const char *def,
1343 struct string *result)
1345 unsigned char max = 0;
1346 unsigned long int max_count = 0;
1348 for (; *targets != '\0'; targets++)
1350 unsigned char c = *targets;
1351 unsigned long int count = histogram[c];
1352 if (count > max_count)
1361 ds_put_byte (result, max);
1364 ds_assign_cstr (result, def);
1367 /* Revises the contents of the fields tree view based on the
1368 currently chosen set of separators. */
1370 revise_fields_preview (struct import_assistant *ia)
1374 push_watch_cursor (ia);
1376 w = GTK_WIDGET (ia->separators.fields_tree_view);
1377 gtk_widget_destroy (w);
1378 get_separators (ia);
1380 choose_column_names (ia);
1381 ia->separators.fields_tree_view = create_data_tree_view (
1383 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "fields-scroller")),
1386 pop_watch_cursor (ia);
1389 /* Sets the widgets to match IA's separators substructure. */
1391 set_separators (struct import_assistant *ia)
1393 struct separators_page *s = &ia->separators;
1395 struct string custom;
1400 ds_init_empty (&custom);
1402 for (i = 0; i < ds_length (&s->separators); i++)
1404 unsigned char c = ds_at (&s->separators, i);
1407 for (j = 0; j < SEPARATOR_CNT; j++)
1409 const struct separator *s = &separators[j];
1417 ds_put_byte (&custom, c);
1421 for (i = 0; i < SEPARATOR_CNT; i++)
1423 const struct separator *s = &separators[i];
1424 GtkWidget *button = get_widget_assert (ia->asst.builder, s->name);
1425 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
1426 (seps & (1u << i)) != 0);
1428 any_custom = !ds_is_empty (&custom);
1429 gtk_entry_set_text (GTK_ENTRY (s->custom_entry), ds_cstr (&custom));
1430 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->custom_cb),
1432 gtk_widget_set_sensitive (s->custom_entry, any_custom);
1433 ds_destroy (&custom);
1435 any_quotes = !ds_is_empty (&s->quotes);
1437 gtk_entry_set_text (s->quote_entry,
1438 any_quotes ? ds_cstr (&s->quotes) : "\"");
1439 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->quote_cb),
1441 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->escape_cb),
1443 gtk_widget_set_sensitive (s->quote_combo, any_quotes);
1444 gtk_widget_set_sensitive (s->escape_cb, any_quotes);
1447 /* Sets IA's separators substructure to match the widgets. */
1449 get_separators (struct import_assistant *ia)
1451 struct separators_page *s = &ia->separators;
1454 ds_clear (&s->separators);
1455 for (i = 0; i < SEPARATOR_CNT; i++)
1457 const struct separator *sep = &separators[i];
1458 GtkWidget *button = get_widget_assert (ia->asst.builder, sep->name);
1459 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1460 ds_put_byte (&s->separators, sep->c);
1463 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->custom_cb)))
1464 ds_put_cstr (&s->separators,
1465 gtk_entry_get_text (GTK_ENTRY (s->custom_entry)));
1467 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->quote_cb)))
1469 gchar *text = gtk_combo_box_get_active_text (
1470 GTK_COMBO_BOX (s->quote_combo));
1471 ds_assign_cstr (&s->quotes, text);
1475 ds_clear (&s->quotes);
1476 s->escape = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->escape_cb));
1479 /* Called when the user changes the entry field for custom
1482 on_separators_custom_entry_notify (GObject *gobject UNUSED,
1483 GParamSpec *arg1 UNUSED,
1484 struct import_assistant *ia)
1486 revise_fields_preview (ia);
1489 /* Called when the user toggles the checkbox that enables custom
1492 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1493 struct import_assistant *ia)
1495 bool is_active = gtk_toggle_button_get_active (custom_cb);
1496 gtk_widget_set_sensitive (ia->separators.custom_entry, is_active);
1497 revise_fields_preview (ia);
1500 /* Called when the user changes the selection in the combo box
1501 that selects a quote character. */
1503 on_quote_combo_change (GtkComboBox *combo, struct import_assistant *ia)
1505 revise_fields_preview (ia);
1508 /* Called when the user toggles the checkbox that enables
1511 on_quote_cb_toggle (GtkToggleButton *quote_cb, struct import_assistant *ia)
1513 bool is_active = gtk_toggle_button_get_active (quote_cb);
1514 gtk_widget_set_sensitive (ia->separators.quote_combo, is_active);
1515 gtk_widget_set_sensitive (ia->separators.escape_cb, is_active);
1516 revise_fields_preview (ia);
1519 /* Called when the user toggles one of the separators
1522 on_separator_toggle (GtkToggleButton *toggle UNUSED,
1523 struct import_assistant *ia)
1525 revise_fields_preview (ia);
1528 /* Called to render one of the cells in the fields preview tree
1531 render_input_cell (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1532 GtkTreeModel *model, GtkTreeIter *iter,
1535 struct import_assistant *ia = ia_;
1536 struct substring field;
1540 column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1542 row = empty_list_store_iter_to_row (iter) + ia->first_line.skip_lines;
1543 field = ia->separators.columns[column].contents[row];
1544 if (field.string != NULL)
1546 GValue text = {0, };
1547 g_value_init (&text, G_TYPE_STRING);
1548 g_value_take_string (&text, ss_xstrdup (field));
1549 g_object_set_property (G_OBJECT (cell), "text", &text);
1550 g_value_unset (&text);
1551 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1556 "background", "red",
1557 "background-set", TRUE,
1561 /* Called to render a tooltip on one of the cells in the fields
1562 preview tree view. */
1564 on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1565 gboolean keyboard_mode UNUSED,
1566 GtkTooltip *tooltip, struct import_assistant *ia)
1570 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1573 if (ia->separators.columns[column].contents[row].string != NULL)
1576 gtk_tooltip_set_text (tooltip,
1577 _("This input line has too few separators "
1578 "to fill in this field."));
1582 /* The "formats" page of the assistant. */
1584 static void on_variable_change (PsppireDict *dict, int idx,
1585 struct import_assistant *);
1586 static void clear_modified_vars (struct import_assistant *);
1588 /* Initializes IA's formats substructure. */
1590 init_formats_page (struct import_assistant *ia)
1592 GtkBuilder *builder = ia->asst.builder;
1593 struct formats_page *p = &ia->formats;
1595 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Formats"),
1596 GTK_ASSISTANT_PAGE_CONFIRM);
1597 p->data_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "data"));
1598 p->modified_vars = NULL;
1599 p->modified_var_cnt = 0;
1603 /* Frees IA's formats substructure. */
1605 destroy_formats_page (struct import_assistant *ia)
1607 struct formats_page *p = &ia->formats;
1609 if (p->psppire_dict != NULL)
1611 /* This destroys p->dict also. */
1612 g_object_unref (p->psppire_dict);
1614 clear_modified_vars (ia);
1617 /* Called just before the formats page of the assistant is
1620 prepare_formats_page (struct import_assistant *ia)
1622 struct dictionary *dict;
1623 PsppireDict *psppire_dict;
1624 PsppireVarStore *var_store;
1625 GtkBin *vars_scroller;
1626 GtkWidget *old_var_sheet;
1627 PsppireVarSheet *var_sheet;
1628 struct separators_page *s = &ia->separators;
1629 struct formats_page *p = &ia->formats;
1630 struct fmt_guesser *fg;
1631 unsigned long int number = 0;
1634 push_watch_cursor (ia);
1636 dict = dict_create (get_default_encoding ());
1637 fg = fmt_guesser_create ();
1638 for (column_idx = 0; column_idx < s->column_cnt; column_idx++)
1640 struct variable *modified_var;
1642 modified_var = (column_idx < p->modified_var_cnt
1643 ? p->modified_vars[column_idx] : NULL);
1644 if (modified_var == NULL)
1646 struct column *column = &s->columns[column_idx];
1647 struct variable *var;
1648 struct fmt_spec format;
1652 /* Choose variable name. */
1653 name = dict_make_unique_var_name (dict, column->name, &number);
1655 /* Choose variable format. */
1656 fmt_guesser_clear (fg);
1657 for (row = ia->first_line.skip_lines; row < ia->file.line_cnt; row++)
1658 fmt_guesser_add (fg, column->contents[row]);
1659 fmt_guesser_guess (fg, &format);
1660 fmt_fix_input (&format);
1662 /* Create variable. */
1663 var = dict_create_var_assert (dict, name, fmt_var_width (&format));
1664 var_set_both_formats (var, &format);
1672 name = dict_make_unique_var_name (dict, var_get_name (modified_var),
1674 dict_clone_var_as_assert (dict, modified_var, name);
1678 fmt_guesser_destroy (fg);
1680 psppire_dict = psppire_dict_new_from_dict (dict);
1681 g_signal_connect (psppire_dict, "variable_changed",
1682 G_CALLBACK (on_variable_change), ia);
1683 ia->formats.dict = dict;
1684 ia->formats.psppire_dict = psppire_dict;
1686 /* XXX: PsppireVarStore doesn't hold a reference to
1687 psppire_dict for now, but it should. After it does, we
1688 should g_object_ref the psppire_dict here, since we also
1689 hold a reference via ia->formats.dict. */
1690 var_store = psppire_var_store_new (psppire_dict);
1691 g_object_set (var_store,
1692 "format-type", PSPPIRE_VAR_STORE_INPUT_FORMATS,
1694 var_sheet = PSPPIRE_VAR_SHEET (psppire_var_sheet_new ());
1695 g_object_set (var_sheet,
1697 "may-create-vars", FALSE,
1700 vars_scroller = GTK_BIN (get_widget_assert (ia->asst.builder, "vars-scroller"));
1701 old_var_sheet = gtk_bin_get_child (vars_scroller);
1702 if (old_var_sheet != NULL)
1703 gtk_widget_destroy (old_var_sheet);
1704 gtk_container_add (GTK_CONTAINER (vars_scroller), GTK_WIDGET (var_sheet));
1705 gtk_widget_show (GTK_WIDGET (var_sheet));
1707 gtk_widget_destroy (GTK_WIDGET (ia->formats.data_tree_view));
1708 ia->formats.data_tree_view = create_data_tree_view (
1710 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "data-scroller")),
1713 pop_watch_cursor (ia);
1716 /* Clears the set of user-modified variables from IA's formats
1717 substructure. This discards user modifications to variable
1718 formats, thereby causing formats to revert to their
1721 clear_modified_vars (struct import_assistant *ia)
1723 struct formats_page *p = &ia->formats;
1726 for (i = 0; i < p->modified_var_cnt; i++)
1727 var_destroy (p->modified_vars[i]);
1728 free (p->modified_vars);
1729 p->modified_vars = NULL;
1730 p->modified_var_cnt = 0;
1733 /* Resets the formats page to its defaults, discarding user
1736 reset_formats_page (struct import_assistant *ia)
1738 clear_modified_vars (ia);
1739 prepare_formats_page (ia);
1742 /* Called when the user changes one of the variables in the
1745 on_variable_change (PsppireDict *dict, int dict_idx,
1746 struct import_assistant *ia)
1748 struct formats_page *p = &ia->formats;
1749 GtkTreeView *tv = ia->formats.data_tree_view;
1750 gint column_idx = dict_idx + 1;
1752 push_watch_cursor (ia);
1754 /* Remove previous column and replace with new column. */
1755 gtk_tree_view_remove_column (tv, gtk_tree_view_get_column (tv, column_idx));
1756 gtk_tree_view_insert_column (tv, make_data_column (ia, tv, false, dict_idx),
1759 /* Save a copy of the modified variable in modified_vars, so
1760 that its attributes will be preserved if we back up to the
1761 previous page with the Prev button and then come back
1763 if (dict_idx >= p->modified_var_cnt)
1766 p->modified_vars = xnrealloc (p->modified_vars, dict_idx + 1,
1767 sizeof *p->modified_vars);
1768 for (i = 0; i <= dict_idx; i++)
1769 p->modified_vars[i] = NULL;
1770 p->modified_var_cnt = dict_idx + 1;
1772 if (p->modified_vars[dict_idx])
1773 var_destroy (p->modified_vars[dict_idx]);
1774 p->modified_vars[dict_idx]
1775 = var_clone (psppire_dict_get_variable (dict, dict_idx));
1777 pop_watch_cursor (ia);
1780 /* Parses the contents of the field at (ROW,COLUMN) according to
1781 its variable format. If OUTPUTP is non-null, then *OUTPUTP
1782 receives the formatted output for that field (which must be
1783 freed with free). If TOOLTIPP is non-null, then *TOOLTIPP
1784 receives a message suitable for use in a tooltip, if one is
1785 needed, or a null pointer otherwise. Returns true if a
1786 tooltip message is needed, otherwise false. */
1788 parse_field (struct import_assistant *ia,
1789 size_t row, size_t column,
1790 char **outputp, char **tooltipp)
1792 struct substring field;
1794 struct variable *var;
1795 const struct fmt_spec *in;
1796 struct fmt_spec out;
1800 field = ia->separators.columns[column].contents[row];
1801 var = dict_get_var (ia->formats.dict, column);
1802 value_init (&val, var_get_width (var));
1803 in = var_get_print_format (var);
1804 out = fmt_for_output_from_input (in);
1806 if (field.string != NULL)
1810 error = data_in (field, C_ENCODING, in->type, &val, var_get_width (var),
1811 dict_get_encoding (ia->formats.dict));
1814 tooltip = xasprintf (_("Cannot parse field content `%.*s' as "
1816 (int) field.length, field.string,
1817 fmt_name (in->type), error);
1823 tooltip = xstrdup (_("This input line has too few separators "
1824 "to fill in this field."));
1825 value_set_missing (&val, var_get_width (var));
1827 if (outputp != NULL)
1829 *outputp = data_out (&val, dict_get_encoding (ia->formats.dict), &out);
1831 value_destroy (&val, var_get_width (var));
1833 ok = tooltip == NULL;
1834 if (tooltipp != NULL)
1835 *tooltipp = tooltip;
1841 /* Called to render one of the cells in the data preview tree
1844 render_output_cell (GtkTreeViewColumn *tree_column,
1845 GtkCellRenderer *cell,
1846 GtkTreeModel *model,
1850 struct import_assistant *ia = ia_;
1852 GValue gvalue = { 0, };
1855 ok = parse_field (ia,
1856 (empty_list_store_iter_to_row (iter)
1857 + ia->first_line.skip_lines),
1858 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1862 g_value_init (&gvalue, G_TYPE_STRING);
1863 g_value_take_string (&gvalue, output);
1864 g_object_set_property (G_OBJECT (cell), "text", &gvalue);
1865 g_value_unset (&gvalue);
1868 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1871 "background", "red",
1872 "background-set", TRUE,
1876 /* Called to render a tooltip for one of the cells in the data
1877 preview tree view. */
1879 on_query_output_tooltip (GtkWidget *widget, gint wx, gint wy,
1880 gboolean keyboard_mode UNUSED,
1881 GtkTooltip *tooltip, struct import_assistant *ia)
1886 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1889 if (parse_field (ia, row, column, NULL, &text))
1892 gtk_tooltip_set_text (tooltip, text);
1897 /* Utility functions used by multiple pages of the assistant. */
1900 get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
1901 const struct import_assistant *ia,
1902 size_t *row, size_t *column)
1904 GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
1908 GtkTreeViewColumn *tree_column;
1909 GtkTreeModel *tree_model;
1912 /* Check that WIDGET is really visible on the screen before we
1913 do anything else. This is a bug fix for a sticky situation:
1914 when text_data_import_assistant() returns, it frees the data
1915 necessary to compose the tool tip message, but there may be
1916 a tool tip under preparation at that point (even if there is
1917 no visible tool tip) that will call back into us a little
1918 bit later. Perhaps the correct solution to this problem is
1919 to make the data related to the tool tips part of a GObject
1920 that only gets destroyed when all references are released,
1921 but this solution appears to be effective too. */
1922 if (!gtk_widget_get_mapped (widget))
1925 gtk_tree_view_convert_widget_to_bin_window_coords (tree_view,
1927 if (!gtk_tree_view_get_path_at_pos (tree_view, bx, by,
1928 &path, &tree_column, NULL, NULL))
1931 *column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1934 tree_model = gtk_tree_view_get_model (tree_view);
1935 ok = gtk_tree_model_get_iter (tree_model, &iter, path);
1936 gtk_tree_path_free (path);
1940 *row = empty_list_store_iter_to_row (&iter) + ia->first_line.skip_lines;
1945 make_tree_view (const struct import_assistant *ia,
1947 GtkTreeView **tree_view)
1949 GtkTreeModel *model;
1951 *tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
1952 model = GTK_TREE_MODEL (psppire_empty_list_store_new (
1953 ia->file.line_cnt - first_line));
1954 g_object_set_data (G_OBJECT (model), "lines", ia->file.lines + first_line);
1955 g_object_set_data (G_OBJECT (model), "first-line",
1956 GINT_TO_POINTER (first_line));
1957 gtk_tree_view_set_model (*tree_view, model);
1958 g_object_unref (model);
1960 add_line_number_column (ia, *tree_view);
1964 render_line_number (GtkTreeViewColumn *tree_column,
1965 GtkCellRenderer *cell,
1966 GtkTreeModel *tree_model,
1970 gint row = empty_list_store_iter_to_row (iter);
1971 char s[INT_BUFSIZE_BOUND (int)];
1974 first_line = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_model),
1976 sprintf (s, "%d", first_line + row);
1977 g_object_set (cell, "text", s, NULL);
1981 add_line_number_column (const struct import_assistant *ia,
1982 GtkTreeView *treeview)
1984 GtkTreeViewColumn *column;
1986 column = gtk_tree_view_column_new_with_attributes (
1987 _("Line"), ia->asst.prop_renderer, (void *) NULL);
1988 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
1989 gtk_tree_view_column_set_fixed_width (
1990 column, get_monospace_width (treeview, ia->asst.prop_renderer, 5));
1991 gtk_tree_view_column_set_resizable (column, TRUE);
1992 gtk_tree_view_column_set_cell_data_func (column, ia->asst.prop_renderer,
1993 render_line_number, NULL, NULL);
1994 gtk_tree_view_append_column (treeview, column);
1998 get_monospace_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
2005 ds_put_byte_multiple (&s, '0', char_cnt);
2006 ds_put_byte (&s, ' ');
2007 width = get_string_width (treeview, renderer, ds_cstr (&s));
2014 get_string_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
2018 g_object_set (G_OBJECT (renderer), "text", string, (void *) NULL);
2019 gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
2020 NULL, NULL, NULL, &width, NULL);
2024 static GtkTreeViewColumn *
2025 make_data_column (struct import_assistant *ia, GtkTreeView *tree_view,
2026 bool input, gint dict_idx)
2028 struct variable *var = NULL;
2029 struct column *column = NULL;
2031 gint content_width, header_width;
2032 GtkTreeViewColumn *tree_column;
2036 column = &ia->separators.columns[dict_idx];
2038 var = dict_get_var (ia->formats.dict, dict_idx);
2040 name = escape_underscores (input ? column->name : var_get_name (var));
2041 char_cnt = input ? column->width : var_get_print_format (var)->w;
2042 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
2044 header_width = get_string_width (tree_view, ia->asst.prop_renderer,
2047 tree_column = gtk_tree_view_column_new ();
2048 g_object_set_data (G_OBJECT (tree_column), "column-number",
2049 GINT_TO_POINTER (dict_idx));
2050 gtk_tree_view_column_set_title (tree_column, name);
2051 gtk_tree_view_column_pack_start (tree_column, ia->asst.fixed_renderer,
2053 gtk_tree_view_column_set_cell_data_func (
2054 tree_column, ia->asst.fixed_renderer,
2055 input ? render_input_cell : render_output_cell, ia, NULL);
2056 gtk_tree_view_column_set_sizing (tree_column, GTK_TREE_VIEW_COLUMN_FIXED);
2057 gtk_tree_view_column_set_fixed_width (tree_column, MAX (content_width,
2065 static GtkTreeView *
2066 create_data_tree_view (bool input, GtkContainer *parent,
2067 struct import_assistant *ia)
2069 GtkTreeView *tree_view;
2072 make_tree_view (ia, ia->first_line.skip_lines, &tree_view);
2073 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (tree_view),
2074 GTK_SELECTION_NONE);
2076 for (i = 0; i < ia->separators.column_cnt; i++)
2077 gtk_tree_view_append_column (tree_view,
2078 make_data_column (ia, tree_view, input, i));
2080 g_object_set (G_OBJECT (tree_view), "has-tooltip", TRUE, (void *) NULL);
2081 g_signal_connect (tree_view, "query-tooltip",
2082 G_CALLBACK (input ? on_query_input_tooltip
2083 : on_query_output_tooltip), ia);
2084 gtk_tree_view_set_fixed_height_mode (tree_view, true);
2086 gtk_container_add (parent, GTK_WIDGET (tree_view));
2087 gtk_widget_show (GTK_WIDGET (tree_view));
2092 /* Increments the "watch cursor" level, setting the cursor for
2093 the assistant window to a watch face to indicate to the user
2094 that the ongoing operation may take some time. */
2096 push_watch_cursor (struct import_assistant *ia)
2098 if (++ia->asst.watch_cursor == 1)
2100 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2101 GdkDisplay *display = gtk_widget_get_display (widget);
2102 GdkCursor *cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
2103 gdk_window_set_cursor (widget->window, cursor);
2104 gdk_cursor_unref (cursor);
2105 gdk_display_flush (display);
2109 /* Decrements the "watch cursor" level. If the level reaches
2110 zero, the cursor is reset to its default shape. */
2112 pop_watch_cursor (struct import_assistant *ia)
2114 if (--ia->asst.watch_cursor == 0)
2116 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2117 gdk_window_set_cursor (widget->window, NULL);