1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2008, 2009 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/>. */
23 #include "checkbox-treeview.h"
24 #include "descriptives-dialog.h"
28 #include <gtk-contrib/psppire-sheet.h>
33 #include <data/data-in.h>
34 #include <data/data-out.h>
35 #include <data/format-guesser.h>
36 #include <data/value-labels.h>
37 #include <language/data-io/data-parser.h>
38 #include <language/syntax-string-source.h>
39 #include <libpspp/assertion.h>
40 #include <libpspp/message.h>
41 #include <ui/syntax-gen.h>
42 #include <ui/gui/psppire-data-window.h>
43 #include <ui/gui/dialog-common.h>
44 #include <ui/gui/helper.h>
45 #include <ui/gui/psppire-dialog.h>
46 #include <ui/gui/psppire-var-sheet.h>
47 #include <ui/gui/psppire-var-store.h>
48 #include <ui/gui/helper.h>
54 #define _(msgid) gettext (msgid)
55 #define N_(msgid) msgid
58 /* TextImportModel, a GtkTreeModel used by the text data import
62 TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER, /* 1-based line number in file */
63 TEXT_IMPORT_MODEL_COLUMN_LINE, /* The line from the file. */
65 typedef struct TextImportModel TextImportModel;
66 typedef struct TextImportModelClass TextImportModelClass;
68 TextImportModel *text_import_model_new (struct string *lines, size_t line_cnt,
70 gint text_import_model_iter_to_row (const GtkTreeIter *);
72 struct import_assistant;
74 /* The file to be imported. */
77 char *file_name; /* File name. */
78 unsigned long int total_lines; /* Number of lines in file. */
79 bool total_is_exact; /* Is total_lines exact (or an estimate)? */
81 /* The first several lines of the file. */
85 static bool init_file (struct import_assistant *, GtkWindow *parent);
86 static void destroy_file (struct import_assistant *);
88 /* The main body of the GTK+ assistant and related data. */
92 GtkAssistant *assistant;
94 GtkWidget *paste_button;
95 GtkWidget *reset_button;
99 GtkCellRenderer *prop_renderer;
100 GtkCellRenderer *fixed_renderer;
102 static void init_assistant (struct import_assistant *, GtkWindow *);
103 static void destroy_assistant (struct import_assistant *);
104 static GtkWidget *add_page_to_assistant (struct import_assistant *,
106 GtkAssistantPageType);
108 /* The introduction page of the assistant. */
112 GtkWidget *all_cases_button;
113 GtkWidget *n_cases_button;
114 GtkWidget *n_cases_spin;
115 GtkWidget *percent_button;
116 GtkWidget *percent_spin;
118 static void init_intro_page (struct import_assistant *);
119 static void reset_intro_page (struct import_assistant *);
121 /* Page where the user chooses the first line of data. */
122 struct first_line_page
124 int skip_lines; /* Number of initial lines to skip? */
125 bool variable_names; /* Variable names above first line of data? */
128 GtkTreeView *tree_view;
129 GtkWidget *variable_names_cb;
131 static void init_first_line_page (struct import_assistant *);
132 static void reset_first_line_page (struct import_assistant *);
134 /* Page where the user chooses field separators. */
135 struct separators_page
137 /* How to break lines into columns. */
138 struct string separators; /* Field separators. */
139 struct string quotes; /* Quote characters. */
140 bool escape; /* Doubled quotes yield a quote mark? */
142 /* The columns produced thereby. */
143 struct column *columns; /* Information about each column. */
144 size_t column_cnt; /* Number of columns. */
147 GtkWidget *custom_cb;
148 GtkWidget *custom_entry;
150 GtkWidget *quote_combo;
151 GtkEntry *quote_entry;
152 GtkWidget *escape_cb;
153 GtkTreeView *fields_tree_view;
155 /* The columns that the separators divide the data into. */
158 /* Variable name for this column. This is the variable name
159 used on the separators page; it can be overridden by the
160 user on the formats page. */
163 /* Maximum length of any row in this column. */
166 /* Contents of this column: contents[row] is the contents for
169 A null substring indicates a missing column for that row
170 (because the line contains an insufficient number of
173 contents[] elements may be substrings of the lines[]
174 strings that represent the whole lines of the file, to
175 save memory. Other elements are dynamically allocated
176 with ss_alloc_substring. */
177 struct substring *contents;
179 static void init_separators_page (struct import_assistant *);
180 static void destroy_separators_page (struct import_assistant *);
181 static void prepare_separators_page (struct import_assistant *);
182 static void reset_separators_page (struct import_assistant *);
184 /* Page where the user verifies and adjusts input formats. */
187 struct dictionary *dict;
190 GtkTreeView *data_tree_view;
191 PsppireDict *psppire_dict;
192 struct variable **modified_vars;
193 size_t modified_var_cnt;
195 static void init_formats_page (struct import_assistant *);
196 static void destroy_formats_page (struct import_assistant *);
197 static void prepare_formats_page (struct import_assistant *);
198 static void reset_formats_page (struct import_assistant *);
200 struct import_assistant
203 struct assistant asst;
204 struct intro_page intro;
205 struct first_line_page first_line;
206 struct separators_page separators;
207 struct formats_page formats;
210 static void apply_dict (const struct dictionary *, struct string *);
211 static char *generate_syntax (const struct import_assistant *);
213 static gboolean get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
214 const struct import_assistant *,
215 size_t *row, size_t *column);
216 static void make_tree_view (const struct import_assistant *ia,
218 GtkTreeView **tree_view);
219 static void add_line_number_column (const struct import_assistant *,
221 static gint get_monospace_width (GtkTreeView *, GtkCellRenderer *,
223 static gint get_string_width (GtkTreeView *, GtkCellRenderer *,
225 static GtkTreeViewColumn *make_data_column (struct import_assistant *,
226 GtkTreeView *, bool input,
228 static GtkTreeView *create_data_tree_view (bool input, GtkContainer *parent,
229 struct import_assistant *);
230 static void escape_underscores (const char *in, char *out);
231 static void push_watch_cursor (struct import_assistant *);
232 static void pop_watch_cursor (struct import_assistant *);
234 /* Pops up the Text Data Import assistant. */
236 text_data_import_assistant (GObject *o, GtkWindow *parent_window)
238 struct import_assistant *ia;
240 ia = xzalloc (sizeof *ia);
241 if (!init_file (ia, parent_window))
247 init_assistant (ia, parent_window);
248 init_intro_page (ia);
249 init_first_line_page (ia);
250 init_separators_page (ia);
251 init_formats_page (ia);
253 gtk_widget_show_all (GTK_WIDGET (ia->asst.assistant));
255 ia->asst.main_loop = g_main_loop_new (NULL, false);
256 g_main_loop_run (ia->asst.main_loop);
257 g_main_loop_unref (ia->asst.main_loop);
259 switch (ia->asst.response)
261 case GTK_RESPONSE_APPLY:
263 char *syntax = generate_syntax (ia);
264 execute_syntax (create_syntax_string_source (syntax));
268 case PSPPIRE_RESPONSE_PASTE:
270 char *syntax = generate_syntax (ia);
271 paste_syntax_in_new_window (syntax);
279 destroy_formats_page (ia);
280 destroy_separators_page (ia);
281 destroy_assistant (ia);
286 /* Emits PSPP syntax to S that applies the dictionary attributes
287 (such as missing values and value labels) of the variables in
290 apply_dict (const struct dictionary *dict, struct string *s)
292 size_t var_cnt = dict_get_var_cnt (dict);
295 for (i = 0; i < var_cnt; i++)
297 struct variable *var = dict_get_var (dict, i);
298 const char *name = var_get_name (var);
299 enum val_type type = var_get_type (var);
300 int width = var_get_width (var);
301 enum measure measure = var_get_measure (var);
302 enum alignment alignment = var_get_alignment (var);
303 const struct fmt_spec *format = var_get_print_format (var);
305 if (var_has_missing_values (var))
307 const struct missing_values *mv = var_get_missing_values (var);
310 syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
311 for (j = 0; j < mv_n_values (mv); j++)
315 ds_put_cstr (s, ", ");
316 mv_get_value (mv, &value, j);
317 syntax_gen_value (s, &value, width, format);
320 if (mv_has_range (mv))
323 if (mv_has_value (mv))
324 ds_put_cstr (s, ", ");
325 mv_get_range (mv, &low, &high);
326 syntax_gen_num_range (s, low, high, format);
328 ds_put_cstr (s, ").\n");
330 if (var_has_value_labels (var))
332 const struct val_labs *vls = var_get_value_labels (var);
333 struct val_labs_iterator *iter;
336 syntax_gen_pspp (s, "VALUE LABELS %ss", name);
337 for (vl = val_labs_first_sorted (vls, &iter); vl != NULL;
338 vl = val_labs_next (vls, &iter))
340 ds_put_cstr (s, "\n ");
341 syntax_gen_value (s, &vl->value, width, format);
342 ds_put_char (s, ' ');
343 syntax_gen_string (s, ss_cstr (vl->label));
345 ds_put_cstr (s, ".\n");
347 if (var_has_label (var))
348 syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
349 name, var_get_label (var));
350 if (measure != var_default_measure (type))
351 syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
353 (measure == MEASURE_NOMINAL ? "NOMINAL"
354 : measure == MEASURE_ORDINAL ? "ORDINAL"
356 if (alignment != var_default_alignment (type))
357 syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
359 (alignment == ALIGN_LEFT ? "LEFT"
360 : alignment == ALIGN_CENTRE ? "CENTER"
362 if (var_get_display_width (var) != var_default_display_width (width))
363 syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
364 name, var_get_display_width (var));
368 /* Generates and returns PSPP syntax to execute the import
369 operation described by IA. The caller must free the syntax
372 generate_syntax (const struct import_assistant *ia)
374 struct string s = DS_EMPTY_INITIALIZER;
383 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
384 ia->intro.n_cases_button)))
385 ds_put_format (&s, " /IMPORTCASES=FIRST %d\n",
386 gtk_spin_button_get_value_as_int (
387 GTK_SPIN_BUTTON (ia->intro.n_cases_spin)));
388 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
389 ia->intro.percent_button)))
390 ds_put_format (&s, " /IMPORTCASES=PERCENT %d\n",
391 gtk_spin_button_get_value_as_int (
392 GTK_SPIN_BUTTON (ia->intro.percent_spin)));
394 ds_put_cstr (&s, " /IMPORTCASES=ALL\n");
396 " /ARRANGEMENT=DELIMITED\n"
398 if (ia->first_line.skip_lines > 0)
399 ds_put_format (&s, " /FIRSTCASE=%d\n", ia->first_line.skip_lines + 1);
400 ds_put_cstr (&s, " /DELIMITERS=\"");
401 if (ds_find_char (&ia->separators.separators, '\t') != SIZE_MAX)
402 ds_put_cstr (&s, "\\t");
403 if (ds_find_char (&ia->separators.separators, '\\') != SIZE_MAX)
404 ds_put_cstr (&s, "\\\\");
405 for (i = 0; i < ds_length (&ia->separators.separators); i++)
407 char c = ds_at (&ia->separators.separators, i);
409 ds_put_cstr (&s, "\"\"");
410 else if (c != '\t' && c != '\\')
413 ds_put_cstr (&s, "\"\n");
414 if (!ds_is_empty (&ia->separators.quotes))
415 syntax_gen_pspp (&s, " /QUALIFIER=%sq\n", ds_cstr (&ia->separators.quotes));
416 if (!ds_is_empty (&ia->separators.quotes) && ia->separators.escape)
417 ds_put_cstr (&s, " /ESCAPE\n");
418 ds_put_cstr (&s, " /VARIABLES=\n");
420 var_cnt = dict_get_var_cnt (ia->formats.dict);
421 for (i = 0; i < var_cnt; i++)
423 struct variable *var = dict_get_var (ia->formats.dict, i);
424 char format_string[FMT_STRING_LEN_MAX + 1];
425 fmt_to_string (var_get_print_format (var), format_string);
426 ds_put_format (&s, " %s %s%s\n",
427 var_get_name (var), format_string,
428 i == var_cnt - 1 ? "." : "");
431 apply_dict (ia->formats.dict, &s);
436 /* Choosing a file and reading it. */
438 static char *choose_file (GtkWindow *parent_window);
440 /* Obtains the file to import from the user and initializes IA's
441 file substructure. PARENT_WINDOW must be the window to use
442 as the file chooser window's parent.
444 Returns true if successful, false if the file name could not
445 be obtained or the file could not be read. */
447 init_file (struct import_assistant *ia, GtkWindow *parent_window)
449 struct file *file = &ia->file;
450 enum { MAX_PREVIEW_LINES = 1000 }; /* Max number of lines to read. */
451 enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
454 file->file_name = choose_file (parent_window);
455 if (file->file_name == NULL)
458 stream = fopen (file->file_name, "r");
461 msg (ME, _("Could not open \"%s\": %s"),
462 file->file_name, strerror (errno));
466 file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines);
467 for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++)
469 struct string *line = &file->lines[file->line_cnt];
471 ds_init_empty (line);
472 if (!ds_read_line (line, stream, MAX_LINE_LEN))
476 else if (ferror (stream))
477 msg (ME, _("Error reading \"%s\": %s"),
478 file->file_name, strerror (errno));
480 msg (ME, _("Failed to read \"%s\", because it contains a line "
481 "over %d bytes long and therefore appears not to be "
483 file->file_name, MAX_LINE_LEN);
488 ds_chomp (line, '\n');
489 ds_chomp (line, '\r');
492 if (file->line_cnt == 0)
494 msg (ME, _("\"%s\" is empty."), file->file_name);
500 /* Estimate the number of lines in the file. */
501 if (file->line_cnt < MAX_PREVIEW_LINES)
502 file->total_lines = file->line_cnt;
506 off_t position = ftello (stream);
507 if (fstat (fileno (stream), &s) == 0 && position > 0)
508 file->total_lines = (double) file->line_cnt / position * s.st_size;
510 file->total_lines = 0;
516 /* Frees IA's file substructure. */
518 destroy_file (struct import_assistant *ia)
520 struct file *f = &ia->file;
523 for (i = 0; i < f->line_cnt; i++)
524 ds_destroy (&f->lines[i]);
526 g_free (f->file_name);
529 /* Obtains the file to read from the user and returns the name of
530 the file as a string that must be freed with g_free if
531 successful, otherwise a null pointer. PARENT_WINDOW must be
532 the window to use as the file chooser window's parent. */
534 choose_file (GtkWindow *parent_window)
539 dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
541 GTK_FILE_CHOOSER_ACTION_OPEN,
542 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
543 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
546 switch (gtk_dialog_run (GTK_DIALOG (dialog)))
548 case GTK_RESPONSE_ACCEPT:
549 file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
555 gtk_widget_destroy (dialog);
562 static void close_assistant (struct import_assistant *, int response);
563 static void on_prepare (GtkAssistant *assistant, GtkWidget *page,
564 struct import_assistant *);
565 static void on_cancel (GtkAssistant *assistant, struct import_assistant *);
566 static void on_close (GtkAssistant *assistant, struct import_assistant *);
567 static void on_paste (GtkButton *button, struct import_assistant *);
568 static void on_reset (GtkButton *button, struct import_assistant *);
569 static void close_assistant (struct import_assistant *, int response);
571 /* Initializes IA's asst substructure. PARENT_WINDOW must be the
572 window to use as the assistant window's parent. */
574 init_assistant (struct import_assistant *ia, GtkWindow *parent_window)
576 struct assistant *a = &ia->asst;
578 a->builder = builder_new ("text-data-import.ui");
579 a->assistant = GTK_ASSISTANT (gtk_assistant_new ());
580 g_signal_connect (a->assistant, "prepare", G_CALLBACK (on_prepare), ia);
581 g_signal_connect (a->assistant, "cancel", G_CALLBACK (on_cancel), ia);
582 g_signal_connect (a->assistant, "close", G_CALLBACK (on_close), ia);
583 a->paste_button = gtk_button_new_from_stock (GTK_STOCK_PASTE);
584 gtk_assistant_add_action_widget (a->assistant, a->paste_button);
585 g_signal_connect (a->paste_button, "clicked", G_CALLBACK (on_paste), ia);
586 a->reset_button = gtk_button_new_from_stock ("pspp-stock-reset");
587 gtk_assistant_add_action_widget (a->assistant, a->reset_button);
588 g_signal_connect (a->reset_button, "clicked", G_CALLBACK (on_reset), ia);
589 gtk_window_set_title (GTK_WINDOW (a->assistant),
590 _("Importing Delimited Text Data"));
591 gtk_window_set_transient_for (GTK_WINDOW (a->assistant), parent_window);
592 gtk_window_set_icon_name (GTK_WINDOW (a->assistant), "psppicon");
594 a->prop_renderer = gtk_cell_renderer_text_new ();
595 g_object_ref_sink (a->prop_renderer);
596 a->fixed_renderer = gtk_cell_renderer_text_new ();
597 g_object_ref_sink (a->fixed_renderer);
598 g_object_set (G_OBJECT (a->fixed_renderer),
599 "family", "Monospace",
603 /* Frees IA's asst substructure. */
605 destroy_assistant (struct import_assistant *ia)
607 struct assistant *a = &ia->asst;
609 g_object_unref (a->prop_renderer);
610 g_object_unref (a->fixed_renderer);
611 g_object_unref (a->builder);
614 /* Appends a page of the given TYPE, with PAGE as its content, to
615 the GtkAssistant encapsulated by IA. Returns the GtkWidget
616 that represents the page. */
618 add_page_to_assistant (struct import_assistant *ia,
619 GtkWidget *page, GtkAssistantPageType type)
625 title = gtk_window_get_title (GTK_WINDOW (page));
626 title_copy = xstrdup (title ? title : "");
628 content = gtk_bin_get_child (GTK_BIN (page));
630 g_object_ref (content);
631 gtk_container_remove (GTK_CONTAINER (page), content);
633 gtk_widget_destroy (page);
635 gtk_assistant_append_page (ia->asst.assistant, content);
636 gtk_assistant_set_page_type (ia->asst.assistant, content, type);
637 gtk_assistant_set_page_title (ia->asst.assistant, content, title_copy);
638 gtk_assistant_set_page_complete (ia->asst.assistant, content, true);
645 /* Called just before PAGE is displayed as the current page of
646 ASSISTANT, this updates IA content according to the new
649 on_prepare (GtkAssistant *assistant, GtkWidget *page,
650 struct import_assistant *ia)
652 if (page == ia->separators.page)
653 prepare_separators_page (ia);
654 else if (page == ia->formats.page)
655 prepare_formats_page (ia);
657 gtk_widget_show (ia->asst.reset_button);
658 if (page == ia->formats.page)
659 gtk_widget_show (ia->asst.paste_button);
661 gtk_widget_hide (ia->asst.paste_button);
664 /* Called when the Cancel button in the assistant is clicked. */
666 on_cancel (GtkAssistant *assistant, struct import_assistant *ia)
668 close_assistant (ia, GTK_RESPONSE_CANCEL);
671 /* Called when the Apply button on the last page of the assistant
674 on_close (GtkAssistant *assistant, struct import_assistant *ia)
676 close_assistant (ia, GTK_RESPONSE_APPLY);
679 /* Called when the Paste button on the last page of the assistant
682 on_paste (GtkButton *button, struct import_assistant *ia)
684 close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
687 /* Called when the Reset button is clicked. */
689 on_reset (GtkButton *button, struct import_assistant *ia)
691 gint page_num = gtk_assistant_get_current_page (ia->asst.assistant);
692 GtkWidget *page = gtk_assistant_get_nth_page (ia->asst.assistant, page_num);
694 if (page == ia->intro.page)
695 reset_intro_page (ia);
696 else if (page == ia->first_line.page)
697 reset_first_line_page (ia);
698 else if (page == ia->separators.page)
699 reset_separators_page (ia);
700 else if (page == ia->formats.page)
701 reset_formats_page (ia);
704 /* Causes the assistant to close, returning RESPONSE for
705 interpretation by text_data_import_assistant. */
707 close_assistant (struct import_assistant *ia, int response)
709 ia->asst.response = response;
710 g_main_loop_quit (ia->asst.main_loop);
711 gtk_widget_hide (GTK_WIDGET (ia->asst.assistant));
714 /* The "intro" page of the assistant. */
716 static void on_intro_amount_changed (GtkToggleButton *button,
717 struct import_assistant *);
719 /* Initializes IA's intro substructure. */
721 init_intro_page (struct import_assistant *ia)
723 GtkBuilder *builder = ia->asst.builder;
724 struct intro_page *p = &ia->intro;
727 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Intro"),
728 GTK_ASSISTANT_PAGE_INTRO);
729 p->all_cases_button = get_widget_assert (builder, "import-all-cases");
730 p->n_cases_button = get_widget_assert (builder, "import-n-cases");
731 p->n_cases_spin = get_widget_assert (builder, "n-cases-spin");
732 p->percent_button = get_widget_assert (builder, "import-percent");
733 p->percent_spin = get_widget_assert (builder, "percent-spin");
734 g_signal_connect (p->all_cases_button, "toggled",
735 G_CALLBACK (on_intro_amount_changed), ia);
736 g_signal_connect (p->n_cases_button, "toggled",
737 G_CALLBACK (on_intro_amount_changed), ia);
738 g_signal_connect (p->percent_button, "toggled",
739 G_CALLBACK (on_intro_amount_changed), ia);
742 ds_put_cstr (&s, _("This assistant will guide you through the process of "
743 "importing data into PSPP from a text file with one line "
744 "per case, in which fields are separated by tabs, "
745 "commas, or other delimiters.\n\n"));
746 if (ia->file.total_is_exact)
748 &s, ngettext ("The selected file contains %zu line of text. ",
749 "The selected file contains %zu lines of text. ",
752 else if (ia->file.total_lines > 0)
756 "The selected file contains approximately %lu line of text. ",
757 "The selected file contains approximately %lu lines of text. ",
758 ia->file.total_lines),
759 ia->file.total_lines);
762 "Only the first %zu line of the file will be shown for "
763 "preview purposes in the following screens. ",
764 "Only the first %zu lines of the file will be shown for "
765 "preview purposes in the following screens. ",
769 ds_put_cstr (&s, _("You may choose below how much of the file should "
770 "actually be imported."));
771 gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
776 /* Resets IA's intro page to its initial state. */
778 reset_intro_page (struct import_assistant *ia)
780 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->intro.all_cases_button),
784 /* Called when one of the radio buttons is clicked. */
786 on_intro_amount_changed (GtkToggleButton *button UNUSED,
787 struct import_assistant *ia)
789 struct intro_page *p = &ia->intro;
791 gtk_widget_set_sensitive (p->n_cases_spin,
792 gtk_toggle_button_get_active (
793 GTK_TOGGLE_BUTTON (p->n_cases_button)));
795 gtk_widget_set_sensitive (ia->intro.percent_spin,
796 gtk_toggle_button_get_active (
797 GTK_TOGGLE_BUTTON (p->percent_button)));
800 /* The "first line" page of the assistant. */
802 static GtkTreeView *create_lines_tree_view (GtkContainer *parent_window,
803 struct import_assistant *);
804 static void on_first_line_change (GtkTreeSelection *,
805 struct import_assistant *);
806 static void on_variable_names_cb_toggle (GtkToggleButton *,
807 struct import_assistant *);
808 static void set_first_line (struct import_assistant *);
809 static void get_first_line (struct import_assistant *);
811 /* Initializes IA's first_line substructure. */
813 init_first_line_page (struct import_assistant *ia)
815 struct first_line_page *p = &ia->first_line;
816 GtkBuilder *builder = ia->asst.builder;
818 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "FirstLine"),
819 GTK_ASSISTANT_PAGE_CONTENT);
820 gtk_widget_destroy (get_widget_assert (builder, "first-line"));
821 p->tree_view = create_lines_tree_view (
822 GTK_CONTAINER (get_widget_assert (builder, "first-line-scroller")), ia);
823 p->variable_names_cb = get_widget_assert (builder, "variable-names");
824 gtk_tree_selection_set_mode (
825 gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
826 GTK_SELECTION_BROWSE);
828 g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
829 "changed", G_CALLBACK (on_first_line_change), ia);
830 g_signal_connect (p->variable_names_cb, "toggled",
831 G_CALLBACK (on_variable_names_cb_toggle), ia);
834 /* Resets the first_line page to its initial content. */
836 reset_first_line_page (struct import_assistant *ia)
838 ia->first_line.skip_lines = 0;
839 ia->first_line.variable_names = false;
843 /* Creates and returns a tree view that contains each of the
844 lines in IA's file as a row. */
846 create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia)
848 GtkTreeView *tree_view;
849 GtkTreeViewColumn *column;
850 size_t max_line_length;
851 gint content_width, header_width;
854 make_tree_view (ia, 0, &tree_view);
856 column = gtk_tree_view_column_new_with_attributes (
857 "Text", ia->asst.fixed_renderer,
858 "text", TEXT_IMPORT_MODEL_COLUMN_LINE,
860 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
863 for (i = 0; i < ia->file.line_cnt; i++)
865 size_t w = ds_length (&ia->file.lines[i]);
866 max_line_length = MAX (max_line_length, w);
869 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
871 header_width = get_string_width (tree_view, ia->asst.prop_renderer, "Text");
872 gtk_tree_view_column_set_fixed_width (column, MAX (content_width,
874 gtk_tree_view_append_column (tree_view, column);
876 gtk_tree_view_set_fixed_height_mode (tree_view, true);
878 gtk_container_add (parent, GTK_WIDGET (tree_view));
879 gtk_widget_show (GTK_WIDGET (tree_view));
884 /* Called when the line selected in the first_line tree view
887 on_first_line_change (GtkTreeSelection *selection UNUSED,
888 struct import_assistant *ia)
893 /* Called when the checkbox that indicates whether variable
894 names are in the row above the first line is toggled. */
896 on_variable_names_cb_toggle (GtkToggleButton *variable_names_cb UNUSED,
897 struct import_assistant *ia)
902 /* Sets the widgets to match IA's first_line substructure. */
904 set_first_line (struct import_assistant *ia)
908 path = gtk_tree_path_new_from_indices (ia->first_line.skip_lines, -1);
909 gtk_tree_view_set_cursor (GTK_TREE_VIEW (ia->first_line.tree_view),
911 gtk_tree_path_free (path);
913 gtk_toggle_button_set_active (
914 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb),
915 ia->first_line.variable_names);
916 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
917 ia->first_line.skip_lines > 0);
920 /* Sets IA's first_line substructure to match the widgets. */
922 get_first_line (struct import_assistant *ia)
924 GtkTreeSelection *selection;
928 selection = gtk_tree_view_get_selection (ia->first_line.tree_view);
929 if (gtk_tree_selection_get_selected (selection, &model, &iter))
931 GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
932 int row = gtk_tree_path_get_indices (path)[0];
933 gtk_tree_path_free (path);
935 ia->first_line.skip_lines = row;
936 ia->first_line.variable_names =
937 (ia->first_line.skip_lines > 0
938 && gtk_toggle_button_get_active (
939 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb)));
941 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
942 ia->first_line.skip_lines > 0);
945 /* The "separators" page of the assistant. */
947 static void revise_fields_preview (struct import_assistant *ia);
948 static void choose_likely_separators (struct import_assistant *ia);
949 static void find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
950 const char *targets, const char *def,
951 struct string *result);
952 static void clear_fields (struct import_assistant *ia);
953 static void revise_fields_preview (struct import_assistant *);
954 static void set_separators (struct import_assistant *);
955 static void get_separators (struct import_assistant *);
956 static void on_separators_custom_entry_notify (GObject *UNUSED,
958 struct import_assistant *);
959 static void on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
960 struct import_assistant *);
961 static void on_quote_combo_change (GtkComboBox *combo,
962 struct import_assistant *);
963 static void on_quote_cb_toggle (GtkToggleButton *quote_cb,
964 struct import_assistant *);
965 static void on_separator_toggle (GtkToggleButton *, struct import_assistant *);
966 static void render_input_cell (GtkTreeViewColumn *tree_column,
967 GtkCellRenderer *cell,
968 GtkTreeModel *model, GtkTreeIter *iter,
970 static gboolean on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
971 gboolean keyboard_mode UNUSED,
973 struct import_assistant *);
975 /* A common field separator and its identifying name. */
978 const char *name; /* Name (for use with get_widget_assert). */
979 int c; /* Separator character. */
982 /* All the separators in the dialog box. */
983 static const struct separator separators[] =
995 #define SEPARATOR_CNT (sizeof separators / sizeof *separators)
998 set_quote_list (GtkComboBoxEntry *cb)
1000 GtkListStore *list = gtk_list_store_new (1, G_TYPE_STRING);
1003 const gchar *seperator[3] = {"'\"", "\'", "\""};
1005 for (i = 0; i < 3; i++)
1007 const gchar *s = seperator[i];
1009 /* Add a new row to the model */
1010 gtk_list_store_append (list, &iter);
1011 gtk_list_store_set (list, &iter,
1017 gtk_combo_box_set_model (GTK_COMBO_BOX (cb), GTK_TREE_MODEL (list));
1019 gtk_combo_box_entry_set_text_column (cb, 0);
1022 /* Initializes IA's separators substructure. */
1024 init_separators_page (struct import_assistant *ia)
1026 GtkBuilder *builder = ia->asst.builder;
1027 struct separators_page *p = &ia->separators;
1030 choose_likely_separators (ia);
1032 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Separators"),
1033 GTK_ASSISTANT_PAGE_CONTENT);
1034 p->custom_cb = get_widget_assert (builder, "custom-cb");
1035 p->custom_entry = get_widget_assert (builder, "custom-entry");
1036 p->quote_combo = get_widget_assert (builder, "quote-combo");
1037 p->quote_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (p->quote_combo)));
1038 p->quote_cb = get_widget_assert (builder, "quote-cb");
1039 p->escape_cb = get_widget_assert (builder, "escape");
1041 set_separators (ia);
1042 set_quote_list (GTK_COMBO_BOX_ENTRY (p->quote_combo));
1043 p->fields_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "fields"));
1044 g_signal_connect (GTK_COMBO_BOX (p->quote_combo), "changed",
1045 G_CALLBACK (on_quote_combo_change), ia);
1046 g_signal_connect (p->quote_cb, "toggled",
1047 G_CALLBACK (on_quote_cb_toggle), ia);
1048 g_signal_connect (GTK_ENTRY (p->custom_entry), "notify::text",
1049 G_CALLBACK (on_separators_custom_entry_notify), ia);
1050 g_signal_connect (p->custom_cb, "toggled",
1051 G_CALLBACK (on_separators_custom_cb_toggle), ia);
1052 for (i = 0; i < SEPARATOR_CNT; i++)
1053 g_signal_connect (get_widget_assert (builder, separators[i].name),
1054 "toggled", G_CALLBACK (on_separator_toggle), ia);
1055 g_signal_connect (p->escape_cb, "toggled",
1056 G_CALLBACK (on_separator_toggle), ia);
1059 /* Frees IA's separators substructure. */
1061 destroy_separators_page (struct import_assistant *ia)
1063 struct separators_page *s = &ia->separators;
1065 ds_destroy (&s->separators);
1066 ds_destroy (&s->quotes);
1070 /* Called just before the separators page becomes visible in the
1073 prepare_separators_page (struct import_assistant *ia)
1075 revise_fields_preview (ia);
1078 /* Called when the Reset button is clicked on the separators
1079 page, resets the separators to the defaults. */
1081 reset_separators_page (struct import_assistant *ia)
1083 choose_likely_separators (ia);
1084 set_separators (ia);
1087 /* Frees and clears the column data in IA's separators
1090 clear_fields (struct import_assistant *ia)
1092 struct separators_page *s = &ia->separators;
1094 if (s->column_cnt > 0)
1099 for (row = 0; row < ia->file.line_cnt; row++)
1101 const struct string *line = &ia->file.lines[row];
1102 const char *line_start = ds_data (line);
1103 const char *line_end = ds_end (line);
1105 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1107 char *s = ss_data (col->contents[row]);
1108 if (!(s >= line_start && s <= line_end))
1109 ss_dealloc (&col->contents[row]);
1113 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1116 free (col->contents);
1125 /* Breaks the file data in IA into columns based on the
1126 separators set in IA's separators substructure. */
1128 split_fields (struct import_assistant *ia)
1130 struct separators_page *s = &ia->separators;
1131 size_t columns_allocated;
1137 /* Is space in the set of separators? */
1138 space_sep = ss_find_char (ds_ss (&s->separators), ' ') != SIZE_MAX;
1140 /* Split all the lines, not just those from
1141 ia->first_line.skip_lines on, so that we split the line that
1142 contains variables names if ia->first_line.variable_names is
1144 columns_allocated = 0;
1145 for (row = 0; row < ia->file.line_cnt; row++)
1147 struct string *line = &ia->file.lines[row];
1148 struct substring text = ds_ss (line);
1151 for (column_idx = 0; ; column_idx++)
1153 struct substring field;
1154 struct column *column;
1157 ss_ltrim (&text, ss_cstr (" "));
1158 if (ss_is_empty (text))
1160 if (column_idx != 0)
1164 else if (!ds_is_empty (&s->quotes)
1165 && ds_find_char (&s->quotes, text.string[0]) != SIZE_MAX)
1167 int quote = ss_get_char (&text);
1169 ss_get_until (&text, quote, &field);
1176 while ((c = ss_get_char (&text)) != EOF)
1178 ds_put_char (&s, c);
1179 else if (ss_match_char (&text, quote))
1180 ds_put_char (&s, quote);
1187 ss_get_chars (&text, ss_cspan (text, ds_ss (&s->separators)),
1190 if (column_idx >= s->column_cnt)
1192 struct column *column;
1194 if (s->column_cnt >= columns_allocated)
1195 s->columns = x2nrealloc (s->columns, &columns_allocated,
1196 sizeof *s->columns);
1197 column = &s->columns[s->column_cnt++];
1198 column->name = NULL;
1200 column->contents = xcalloc (ia->file.line_cnt,
1201 sizeof *column->contents);
1203 column = &s->columns[column_idx];
1204 column->contents[row] = field;
1205 if (ss_length (field) > column->width)
1206 column->width = ss_length (field);
1209 ss_ltrim (&text, ss_cstr (" "));
1210 if (ss_is_empty (text))
1212 if (ss_find_char (ds_ss (&s->separators), ss_first (text))
1214 ss_advance (&text, 1);
1219 /* Chooses a name for each column on the separators page */
1221 choose_column_names (struct import_assistant *ia)
1223 const struct first_line_page *f = &ia->first_line;
1224 struct separators_page *s = &ia->separators;
1225 struct dictionary *dict;
1226 unsigned long int generated_name_count = 0;
1230 dict = dict_create ();
1231 name_row = f->variable_names && f->skip_lines ? f->skip_lines : 0;
1232 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1234 char name[VAR_NAME_LEN + 1];
1237 hint = name_row ? ss_xstrdup (col->contents[name_row - 1]) : NULL;
1238 if (!dict_make_unique_var_name (dict, hint, &generated_name_count, name))
1242 col->name = xstrdup (name);
1243 dict_create_var_assert (dict, name, 0);
1245 dict_destroy (dict);
1248 /* Picks the most likely separator and quote characters based on
1251 choose_likely_separators (struct import_assistant *ia)
1253 unsigned long int histogram[UCHAR_MAX + 1] = { 0 };
1256 /* Construct a histogram of all the characters used in the
1258 for (row = 0; row < ia->file.line_cnt; row++)
1260 struct substring line = ds_ss (&ia->file.lines[row]);
1261 size_t length = ss_length (line);
1263 for (i = 0; i < length; i++)
1264 histogram[(unsigned char) line.string[i]]++;
1267 find_commonest_chars (histogram, "\"'", "", &ia->separators.quotes);
1268 find_commonest_chars (histogram, ",;:/|!\t-", ",",
1269 &ia->separators.separators);
1270 ia->separators.escape = true;
1273 /* Chooses the most common character among those in TARGETS,
1274 based on the frequency data in HISTOGRAM, and stores it in
1275 RESULT. If there is a tie for the most common character among
1276 those in TARGETS, the earliest character is chosen. If none
1277 of the TARGETS appear at all, then DEF is used as a
1280 find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
1281 const char *targets, const char *def,
1282 struct string *result)
1284 unsigned char max = 0;
1285 unsigned long int max_count = 0;
1287 for (; *targets != '\0'; targets++)
1289 unsigned char c = *targets;
1290 unsigned long int count = histogram[c];
1291 if (count > max_count)
1300 ds_put_char (result, max);
1303 ds_assign_cstr (result, def);
1306 /* Revises the contents of the fields tree view based on the
1307 currently chosen set of separators. */
1309 revise_fields_preview (struct import_assistant *ia)
1313 push_watch_cursor (ia);
1315 w = GTK_WIDGET (ia->separators.fields_tree_view);
1316 gtk_widget_destroy (w);
1317 get_separators (ia);
1319 choose_column_names (ia);
1320 ia->separators.fields_tree_view = create_data_tree_view (
1322 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "fields-scroller")),
1325 pop_watch_cursor (ia);
1328 /* Sets the widgets to match IA's separators substructure. */
1330 set_separators (struct import_assistant *ia)
1332 struct separators_page *s = &ia->separators;
1334 struct string custom;
1339 ds_init_empty (&custom);
1341 for (i = 0; i < ds_length (&s->separators); i++)
1343 unsigned char c = ds_at (&s->separators, i);
1346 for (j = 0; j < SEPARATOR_CNT; j++)
1348 const struct separator *s = &separators[j];
1356 ds_put_char (&custom, c);
1360 for (i = 0; i < SEPARATOR_CNT; i++)
1362 const struct separator *s = &separators[i];
1363 GtkWidget *button = get_widget_assert (ia->asst.builder, s->name);
1364 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
1365 (seps & (1u << i)) != 0);
1367 any_custom = !ds_is_empty (&custom);
1368 gtk_entry_set_text (GTK_ENTRY (s->custom_entry), ds_cstr (&custom));
1369 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->custom_cb),
1371 gtk_widget_set_sensitive (s->custom_entry, any_custom);
1372 ds_destroy (&custom);
1374 any_quotes = !ds_is_empty (&s->quotes);
1376 gtk_entry_set_text (s->quote_entry,
1377 any_quotes ? ds_cstr (&s->quotes) : "\"");
1378 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->quote_cb),
1380 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->escape_cb),
1382 gtk_widget_set_sensitive (s->quote_combo, any_quotes);
1383 gtk_widget_set_sensitive (s->escape_cb, any_quotes);
1386 /* Sets IA's separators substructure to match the widgets. */
1388 get_separators (struct import_assistant *ia)
1390 struct separators_page *s = &ia->separators;
1393 ds_clear (&s->separators);
1394 for (i = 0; i < SEPARATOR_CNT; i++)
1396 const struct separator *sep = &separators[i];
1397 GtkWidget *button = get_widget_assert (ia->asst.builder, sep->name);
1398 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1399 ds_put_char (&s->separators, sep->c);
1402 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->custom_cb)))
1403 ds_put_cstr (&s->separators,
1404 gtk_entry_get_text (GTK_ENTRY (s->custom_entry)));
1406 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->quote_cb)))
1408 gchar *text = gtk_combo_box_get_active_text (
1409 GTK_COMBO_BOX (s->quote_combo));
1410 ds_assign_cstr (&s->quotes, text);
1414 ds_clear (&s->quotes);
1415 s->escape = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->escape_cb));
1418 /* Called when the user changes the entry field for custom
1421 on_separators_custom_entry_notify (GObject *gobject UNUSED,
1422 GParamSpec *arg1 UNUSED,
1423 struct import_assistant *ia)
1425 revise_fields_preview (ia);
1428 /* Called when the user toggles the checkbox that enables custom
1431 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1432 struct import_assistant *ia)
1434 bool is_active = gtk_toggle_button_get_active (custom_cb);
1435 gtk_widget_set_sensitive (ia->separators.custom_entry, is_active);
1436 revise_fields_preview (ia);
1439 /* Called when the user changes the selection in the combo box
1440 that selects a quote character. */
1442 on_quote_combo_change (GtkComboBox *combo, struct import_assistant *ia)
1444 revise_fields_preview (ia);
1447 /* Called when the user toggles the checkbox that enables
1450 on_quote_cb_toggle (GtkToggleButton *quote_cb, struct import_assistant *ia)
1452 bool is_active = gtk_toggle_button_get_active (quote_cb);
1453 gtk_widget_set_sensitive (ia->separators.quote_combo, is_active);
1454 gtk_widget_set_sensitive (ia->separators.escape_cb, is_active);
1455 revise_fields_preview (ia);
1458 /* Called when the user toggles one of the separators
1461 on_separator_toggle (GtkToggleButton *toggle UNUSED,
1462 struct import_assistant *ia)
1464 revise_fields_preview (ia);
1467 /* Called to render one of the cells in the fields preview tree
1470 render_input_cell (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1471 GtkTreeModel *model, GtkTreeIter *iter,
1474 struct import_assistant *ia = ia_;
1475 struct substring field;
1479 column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1481 row = text_import_model_iter_to_row (iter) + ia->first_line.skip_lines;
1482 field = ia->separators.columns[column].contents[row];
1483 if (field.string != NULL)
1485 GValue text = {0, };
1486 g_value_init (&text, G_TYPE_STRING);
1487 g_value_take_string (&text, ss_xstrdup (field));
1488 g_object_set_property (G_OBJECT (cell), "text", &text);
1489 g_value_unset (&text);
1490 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1495 "background", "red",
1496 "background-set", TRUE,
1500 /* Called to render a tooltip on one of the cells in the fields
1501 preview tree view. */
1503 on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1504 gboolean keyboard_mode UNUSED,
1505 GtkTooltip *tooltip, struct import_assistant *ia)
1509 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1512 if (ia->separators.columns[column].contents[row].string != NULL)
1515 gtk_tooltip_set_text (tooltip,
1516 _("This input line has too few separators "
1517 "to fill in this field."));
1521 /* The "formats" page of the assistant. */
1523 static void on_variable_change (PsppireDict *dict, int idx,
1524 struct import_assistant *);
1525 static void clear_modified_vars (struct import_assistant *);
1527 /* Initializes IA's formats substructure. */
1529 init_formats_page (struct import_assistant *ia)
1531 GtkBuilder *builder = ia->asst.builder;
1532 struct formats_page *p = &ia->formats;
1534 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Formats"),
1535 GTK_ASSISTANT_PAGE_CONFIRM);
1536 p->data_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "data"));
1537 p->modified_vars = NULL;
1538 p->modified_var_cnt = 0;
1541 /* Frees IA's formats substructure. */
1543 destroy_formats_page (struct import_assistant *ia)
1545 struct formats_page *p = &ia->formats;
1547 if (p->psppire_dict != NULL)
1549 /* This destroys p->dict also. */
1550 g_object_unref (p->psppire_dict);
1552 clear_modified_vars (ia);
1555 /* Called just before the formats page of the assistant is
1558 prepare_formats_page (struct import_assistant *ia)
1560 struct dictionary *dict;
1561 PsppireDict *psppire_dict;
1562 PsppireVarStore *var_store;
1563 GtkBin *vars_scroller;
1564 GtkWidget *old_var_sheet;
1565 PsppireVarSheet *var_sheet;
1566 struct separators_page *s = &ia->separators;
1567 struct formats_page *p = &ia->formats;
1568 struct fmt_guesser *fg;
1569 unsigned long int number = 0;
1572 push_watch_cursor (ia);
1574 dict = dict_create ();
1575 fg = fmt_guesser_create ();
1576 for (column_idx = 0; column_idx < s->column_cnt; column_idx++)
1578 struct variable *modified_var;
1579 char name[VAR_NAME_LEN + 1];
1581 modified_var = (column_idx < p->modified_var_cnt
1582 ? p->modified_vars[column_idx] : NULL);
1583 if (modified_var == NULL)
1585 struct column *column = &s->columns[column_idx];
1586 struct variable *var;
1587 struct fmt_spec format;
1590 /* Choose variable name. */
1591 if (!dict_make_unique_var_name (dict, column->name, &number, name))
1594 /* Choose variable format. */
1595 fmt_guesser_clear (fg);
1596 for (row = ia->first_line.skip_lines; row < ia->file.line_cnt; row++)
1597 fmt_guesser_add (fg, column->contents[row]);
1598 fmt_guesser_guess (fg, &format);
1599 fmt_fix_input (&format);
1601 /* Create variable. */
1602 var = dict_create_var_assert (dict, name, fmt_var_width (&format));
1603 var_set_both_formats (var, &format);
1607 if (!dict_make_unique_var_name (dict, var_get_name (modified_var),
1610 dict_clone_var_assert (dict, modified_var, name);
1613 fmt_guesser_destroy (fg);
1615 psppire_dict = psppire_dict_new_from_dict (dict);
1616 g_signal_connect (psppire_dict, "variable_changed",
1617 G_CALLBACK (on_variable_change), ia);
1618 ia->formats.dict = dict;
1619 ia->formats.psppire_dict = psppire_dict;
1621 /* XXX: PsppireVarStore doesn't hold a reference to
1622 psppire_dict for now, but it should. After it does, we
1623 should g_object_ref the psppire_dict here, since we also
1624 hold a reference via ia->formats.dict. */
1625 var_store = psppire_var_store_new (psppire_dict);
1626 g_object_set (var_store,
1627 "format-type", PSPPIRE_VAR_STORE_INPUT_FORMATS,
1629 var_sheet = PSPPIRE_VAR_SHEET (psppire_var_sheet_new ());
1630 g_object_set (var_sheet,
1632 "may-create-vars", FALSE,
1635 vars_scroller = GTK_BIN (get_widget_assert (ia->asst.builder, "vars-scroller"));
1636 old_var_sheet = gtk_bin_get_child (vars_scroller);
1637 if (old_var_sheet != NULL)
1638 gtk_widget_destroy (old_var_sheet);
1639 gtk_container_add (GTK_CONTAINER (vars_scroller), GTK_WIDGET (var_sheet));
1640 gtk_widget_show (GTK_WIDGET (var_sheet));
1642 gtk_widget_destroy (GTK_WIDGET (ia->formats.data_tree_view));
1643 ia->formats.data_tree_view = create_data_tree_view (
1645 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "data-scroller")),
1648 pop_watch_cursor (ia);
1651 /* Clears the set of user-modified variables from IA's formats
1652 substructure. This discards user modifications to variable
1653 formats, thereby causing formats to revert to their
1656 clear_modified_vars (struct import_assistant *ia)
1658 struct formats_page *p = &ia->formats;
1661 for (i = 0; i < p->modified_var_cnt; i++)
1662 var_destroy (p->modified_vars[i]);
1663 free (p->modified_vars);
1664 p->modified_vars = NULL;
1665 p->modified_var_cnt = 0;
1668 /* Resets the formats page to its defaults, discarding user
1671 reset_formats_page (struct import_assistant *ia)
1673 clear_modified_vars (ia);
1674 prepare_formats_page (ia);
1677 /* Called when the user changes one of the variables in the
1680 on_variable_change (PsppireDict *dict, int dict_idx,
1681 struct import_assistant *ia)
1683 struct formats_page *p = &ia->formats;
1684 GtkTreeView *tv = ia->formats.data_tree_view;
1685 gint column_idx = dict_idx + 1;
1687 push_watch_cursor (ia);
1689 /* Remove previous column and replace with new column. */
1690 gtk_tree_view_remove_column (tv, gtk_tree_view_get_column (tv, column_idx));
1691 gtk_tree_view_insert_column (tv, make_data_column (ia, tv, false, dict_idx),
1694 /* Save a copy of the modified variable in modified_vars, so
1695 that its attributes will be preserved if we back up to the
1696 previous page with the Prev button and then come back
1698 if (dict_idx >= p->modified_var_cnt)
1701 p->modified_vars = xnrealloc (p->modified_vars, dict_idx + 1,
1702 sizeof *p->modified_vars);
1703 for (i = 0; i <= dict_idx; i++)
1704 p->modified_vars[i] = NULL;
1705 p->modified_var_cnt = dict_idx + 1;
1707 if (p->modified_vars[dict_idx])
1708 var_destroy (p->modified_vars[dict_idx]);
1709 p->modified_vars[dict_idx]
1710 = var_clone (psppire_dict_get_variable (dict, dict_idx));
1712 pop_watch_cursor (ia);
1715 /* Parses the contents of the field at (ROW,COLUMN) according to
1716 its variable format. If OUTPUTP is non-null, then *OUTPUTP
1717 receives the formatted output for that field (which must be
1718 freed with free). If TOOLTIPP is non-null, then *TOOLTIPP
1719 receives a message suitable for use in a tooltip, if one is
1720 needed, or a null pointer otherwise. Returns true if a
1721 tooltip message is needed, otherwise false. */
1723 parse_field (struct import_assistant *ia,
1724 size_t row, size_t column,
1725 char **outputp, char **tooltipp)
1727 struct substring field;
1729 struct variable *var;
1730 const struct fmt_spec *in;
1731 struct fmt_spec out;
1735 field = ia->separators.columns[column].contents[row];
1736 var = dict_get_var (ia->formats.dict, column);
1737 val = value_create (var_get_width (var));
1738 in = var_get_print_format (var);
1739 out = fmt_for_output_from_input (in);
1741 if (field.string != NULL)
1744 if (!data_in (field, LEGACY_NATIVE, in->type, 0, 0, 0,
1745 val, var_get_width (var)))
1747 char fmt_string[FMT_STRING_LEN_MAX + 1];
1748 fmt_to_string (in, fmt_string);
1749 tooltip = xasprintf (_("Field content \"%.*s\" cannot be parsed in "
1751 (int) field.length, field.string,
1758 tooltip = xstrdup (_("This input line has too few separators "
1759 "to fill in this field."));
1760 value_set_missing (val, var_get_width (var));
1762 if (outputp != NULL)
1764 char *output = xmalloc (out.w + 1);
1765 data_out (val, &out, output);
1766 output[out.w] = '\0';
1771 ok = tooltip == NULL;
1772 if (tooltipp != NULL)
1773 *tooltipp = tooltip;
1779 /* Called to render one of the cells in the data preview tree
1782 render_output_cell (GtkTreeViewColumn *tree_column,
1783 GtkCellRenderer *cell,
1784 GtkTreeModel *model,
1788 struct import_assistant *ia = ia_;
1790 GValue gvalue = { 0, };
1793 ok = parse_field (ia,
1794 (text_import_model_iter_to_row (iter)
1795 + ia->first_line.skip_lines),
1796 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1800 g_value_init (&gvalue, G_TYPE_STRING);
1801 g_value_take_string (&gvalue, output);
1802 g_object_set_property (G_OBJECT (cell), "text", &gvalue);
1803 g_value_unset (&gvalue);
1806 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1809 "background", "red",
1810 "background-set", TRUE,
1814 /* Called to render a tooltip for one of the cells in the data
1815 preview tree view. */
1817 on_query_output_tooltip (GtkWidget *widget, gint wx, gint wy,
1818 gboolean keyboard_mode UNUSED,
1819 GtkTooltip *tooltip, struct import_assistant *ia)
1824 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1827 if (parse_field (ia, row, column, NULL, &text))
1830 gtk_tooltip_set_text (tooltip, text);
1835 /* Utility functions used by multiple pages of the assistant. */
1838 get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
1839 const struct import_assistant *ia,
1840 size_t *row, size_t *column)
1842 GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
1846 GtkTreeViewColumn *tree_column;
1847 GtkTreeModel *tree_model;
1850 /* Check that WIDGET is really visible on the screen before we
1851 do anything else. This is a bug fix for a sticky situation:
1852 when text_data_import_assistant() returns, it frees the data
1853 necessary to compose the tool tip message, but there may be
1854 a tool tip under preparation at that point (even if there is
1855 no visible tool tip) that will call back into us a little
1856 bit later. Perhaps the correct solution to this problem is
1857 to make the data related to the tool tips part of a GObject
1858 that only gets destroyed when all references are released,
1859 but this solution appears to be effective too. */
1860 if (!GTK_WIDGET_MAPPED (widget))
1863 gtk_tree_view_convert_widget_to_bin_window_coords (tree_view,
1865 if (!gtk_tree_view_get_path_at_pos (tree_view, bx, by,
1866 &path, &tree_column, NULL, NULL))
1869 *column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1872 tree_model = gtk_tree_view_get_model (tree_view);
1873 ok = gtk_tree_model_get_iter (tree_model, &iter, path);
1874 gtk_tree_path_free (path);
1878 *row = text_import_model_iter_to_row (&iter) + ia->first_line.skip_lines;
1883 make_tree_view (const struct import_assistant *ia,
1885 GtkTreeView **tree_view)
1887 GtkTreeModel *model;
1889 *tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
1890 model = GTK_TREE_MODEL (text_import_model_new (
1891 ia->file.lines + first_line,
1892 ia->file.line_cnt - first_line, first_line));
1893 gtk_tree_view_set_model (*tree_view, model);
1895 add_line_number_column (ia, *tree_view);
1899 add_line_number_column (const struct import_assistant *ia,
1900 GtkTreeView *treeview)
1902 GtkTreeViewColumn *column;
1904 column = gtk_tree_view_column_new_with_attributes (
1905 "Line", ia->asst.prop_renderer,
1906 "text", TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER,
1908 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
1909 gtk_tree_view_column_set_fixed_width (
1910 column, get_monospace_width (treeview, ia->asst.prop_renderer, 5));
1911 gtk_tree_view_append_column (treeview, column);
1915 get_monospace_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
1922 ds_put_char_multiple (&s, '0', char_cnt);
1923 ds_put_char (&s, ' ');
1924 width = get_string_width (treeview, renderer, ds_cstr (&s));
1931 get_string_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
1935 g_object_set (G_OBJECT (renderer), "text", string, (void *) NULL);
1936 gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
1937 NULL, NULL, NULL, &width, NULL);
1941 static GtkTreeViewColumn *
1942 make_data_column (struct import_assistant *ia, GtkTreeView *tree_view,
1943 bool input, gint dict_idx)
1945 struct variable *var = NULL;
1946 struct column *column = NULL;
1947 char name[(VAR_NAME_LEN * 2) + 1];
1949 gint content_width, header_width;
1950 GtkTreeViewColumn *tree_column;
1953 column = &ia->separators.columns[dict_idx];
1955 var = dict_get_var (ia->formats.dict, dict_idx);
1957 escape_underscores (input ? column->name : var_get_name (var), name);
1958 char_cnt = input ? column->width : var_get_print_format (var)->w;
1959 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
1961 header_width = get_string_width (tree_view, ia->asst.prop_renderer,
1964 tree_column = gtk_tree_view_column_new ();
1965 g_object_set_data (G_OBJECT (tree_column), "column-number",
1966 GINT_TO_POINTER (dict_idx));
1967 gtk_tree_view_column_set_title (tree_column, name);
1968 gtk_tree_view_column_pack_start (tree_column, ia->asst.fixed_renderer,
1970 gtk_tree_view_column_set_cell_data_func (
1971 tree_column, ia->asst.fixed_renderer,
1972 input ? render_input_cell : render_output_cell, ia, NULL);
1973 gtk_tree_view_column_set_sizing (tree_column, GTK_TREE_VIEW_COLUMN_FIXED);
1974 gtk_tree_view_column_set_fixed_width (tree_column, MAX (content_width,
1980 static GtkTreeView *
1981 create_data_tree_view (bool input, GtkContainer *parent,
1982 struct import_assistant *ia)
1984 GtkTreeView *tree_view;
1987 make_tree_view (ia, ia->first_line.skip_lines, &tree_view);
1988 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (tree_view),
1989 GTK_SELECTION_NONE);
1991 for (i = 0; i < ia->separators.column_cnt; i++)
1992 gtk_tree_view_append_column (tree_view,
1993 make_data_column (ia, tree_view, input, i));
1995 g_object_set (G_OBJECT (tree_view), "has-tooltip", TRUE, (void *) NULL);
1996 g_signal_connect (tree_view, "query-tooltip",
1997 G_CALLBACK (input ? on_query_input_tooltip
1998 : on_query_output_tooltip), ia);
1999 gtk_tree_view_set_fixed_height_mode (tree_view, true);
2001 gtk_container_add (parent, GTK_WIDGET (tree_view));
2002 gtk_widget_show (GTK_WIDGET (tree_view));
2008 escape_underscores (const char *in, char *out)
2010 for (; *in != '\0'; in++)
2019 /* TextImportModel, a GtkTreeModel implementation used by some
2020 pages of the assistant. */
2022 #define G_TYPE_TEXT_IMPORT_MODEL (text_import_model_get_type ())
2023 #define TEXT_IMPORT_MODEL(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), G_TYPE_TEXT_IMPORT_MODEL, TextImportModel))
2024 #define TEXT_IMPORT_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), G_TYPE_TEXT_IMPORT_MODEL, TextImportModelClass))
2025 #define IS_TEXT_IMPORT_MODEL(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), G_TYPE_TEXT_IMPORT_MODEL))
2026 #define IS_TEXT_IMPORT_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), G_TYPE_TEXT_IMPORT_MODEL))
2027 #define TEXT_IMPORT_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_TEXT_IMPORT_MODEL, TextImportModelClass))
2029 /* Random number used in 'stamp' member of GtkTreeIter. */
2030 #define TREE_MODEL_STAMP 0x7efd67d3
2032 struct TextImportModel
2035 struct string *lines;
2040 struct TextImportModelClass
2042 GObjectClass parent_class;
2045 GType text_import_model_get_type (void);
2046 static void text_import_model_tree_model_init (gpointer iface, gpointer data);
2049 text_import_model_get_type (void)
2051 static GType object_type = 0;
2055 static const GTypeInfo object_info = {
2056 sizeof (TextImportModelClass),
2057 (GBaseInitFunc) NULL,
2058 (GBaseFinalizeFunc) NULL,
2059 NULL, /* class_init */
2060 NULL, /* class_finalize */
2061 NULL, /* class_data */
2062 sizeof (TextImportModel),
2063 0, /* n_preallocs */
2064 NULL, /* instance_init */
2067 static const GInterfaceInfo tree_model_info = {
2068 text_import_model_tree_model_init,
2073 object_type = g_type_register_static (G_TYPE_OBJECT,
2077 g_type_add_interface_static (object_type, GTK_TYPE_TREE_MODEL,
2087 /* Creates and returns a new TextImportModel that contains the
2088 LINE_CNT lines in LINES. The lines before FIRST_LINE in LINES
2089 are not part of the model, but they are included in the line
2090 numbers in the TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER column.
2092 The caller retains responsibility for freeing LINES and must
2093 ensure that its lifetime and that of the strings that it
2094 contains exceeds that of the TextImportModel. */
2096 text_import_model_new (struct string *lines, size_t line_cnt,
2099 TextImportModel *new_text_import_model
2100 = g_object_new (G_TYPE_TEXT_IMPORT_MODEL, NULL);
2101 new_text_import_model->lines = lines;
2102 new_text_import_model->line_cnt = line_cnt;
2103 new_text_import_model->first_line = first_line;
2104 return new_text_import_model;
2109 tree_model_iter_has_child (GtkTreeModel *tree_model,
2116 tree_model_iter_parent (GtkTreeModel *tree_model,
2123 static GtkTreeModelFlags
2124 tree_model_get_flags (GtkTreeModel *model)
2126 g_return_val_if_fail (IS_TEXT_IMPORT_MODEL (model), (GtkTreeModelFlags) 0);
2128 return GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST;
2133 tree_model_n_columns (GtkTreeModel *model)
2139 tree_model_column_type (GtkTreeModel *model, gint index)
2141 return (index == TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER ? G_TYPE_INT
2142 : index == TEXT_IMPORT_MODEL_COLUMN_LINE ? G_TYPE_STRING
2147 init_iter (TextImportModel *list, gint idx, GtkTreeIter *iter)
2149 if (idx < 0 || idx >= list->line_cnt)
2152 iter->user_data = GINT_TO_POINTER (-1);
2157 iter->stamp = TREE_MODEL_STAMP;
2158 iter->user_data = GINT_TO_POINTER (idx);
2164 tree_model_get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path)
2166 gint *indices, depth;
2168 TextImportModel *list = TEXT_IMPORT_MODEL (model);
2170 g_return_val_if_fail (path, FALSE);
2172 indices = gtk_tree_path_get_indices (path);
2173 depth = gtk_tree_path_get_depth (path);
2175 g_return_val_if_fail (depth == 1, FALSE);
2177 return init_iter (list, indices[0], iter);
2182 tree_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter)
2184 TextImportModel *list = TEXT_IMPORT_MODEL (model);
2187 assert (iter->stamp == TREE_MODEL_STAMP);
2189 idx = GPOINTER_TO_INT (iter->user_data);
2190 return init_iter (list, idx == -1 ? -1 : idx + 1, iter);
2193 static GtkTreePath *
2194 tree_model_get_path (GtkTreeModel *model, GtkTreeIter *iter)
2198 g_return_val_if_fail (iter->stamp == TREE_MODEL_STAMP, FALSE);
2200 path = gtk_tree_path_new ();
2201 gtk_tree_path_append_index (path, GPOINTER_TO_INT (iter->user_data));
2207 tree_model_get_value (GtkTreeModel *model, GtkTreeIter *iter,
2208 gint column, GValue *value)
2210 TextImportModel *list = TEXT_IMPORT_MODEL (model);
2213 g_return_if_fail (iter->stamp == TREE_MODEL_STAMP);
2215 idx = GPOINTER_TO_INT (iter->user_data);
2216 assert (idx >= 0 && idx < list->line_cnt);
2220 g_value_init (value, G_TYPE_INT);
2221 g_value_set_int (value, idx + list->first_line + 1);
2225 g_value_init (value, G_TYPE_STRING);
2226 g_value_set_static_string (value, ds_cstr (&list->lines[idx]));
2231 tree_model_iter_children (GtkTreeModel *tree_model,
2233 GtkTreeIter *parent)
2239 tree_model_n_children (GtkTreeModel *model, GtkTreeIter *iter)
2241 TextImportModel *list = TEXT_IMPORT_MODEL (model);
2243 return iter == NULL ? list->line_cnt : 0;
2247 tree_model_nth_child (GtkTreeModel *model, GtkTreeIter *iter,
2248 GtkTreeIter *parent, gint n)
2250 TextImportModel *list = TEXT_IMPORT_MODEL (model);
2251 g_return_val_if_fail (IS_TEXT_IMPORT_MODEL (model), FALSE);
2255 return init_iter (list, n, iter);
2259 text_import_model_tree_model_init (gpointer iface_, gpointer data UNUSED)
2261 GtkTreeModelIface *iface = (GtkTreeModelIface *) iface_;
2263 iface->get_flags = tree_model_get_flags;
2264 iface->get_n_columns = tree_model_n_columns;
2265 iface->get_column_type = tree_model_column_type;
2266 iface->get_iter = tree_model_get_iter;
2267 iface->iter_next = tree_model_iter_next;
2268 iface->get_path = tree_model_get_path;
2269 iface->get_value = tree_model_get_value;
2271 iface->iter_children = tree_model_iter_children;
2272 iface->iter_has_child = tree_model_iter_has_child;
2273 iface->iter_n_children = tree_model_n_children;
2274 iface->iter_nth_child = tree_model_nth_child;
2275 iface->iter_parent = tree_model_iter_parent;
2279 text_import_model_iter_to_row (const GtkTreeIter *iter)
2281 assert (iter->stamp == TREE_MODEL_STAMP);
2282 return GPOINTER_TO_INT (iter->user_data);
2285 /* Increments the "watch cursor" level, setting the cursor for
2286 the assistant window to a watch face to indicate to the user
2287 that the ongoing operation may take some time. */
2289 push_watch_cursor (struct import_assistant *ia)
2291 if (++ia->asst.watch_cursor == 1)
2293 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2294 GdkDisplay *display = gtk_widget_get_display (widget);
2295 GdkCursor *cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
2296 gdk_window_set_cursor (widget->window, cursor);
2297 gdk_cursor_unref (cursor);
2298 gdk_display_flush (display);
2302 /* Decrements the "watch cursor" level. If the level reaches
2303 zero, the cursor is reset to its default shape. */
2305 pop_watch_cursor (struct import_assistant *ia)
2307 if (--ia->asst.watch_cursor == 0)
2309 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2310 gdk_window_set_cursor (widget->window, NULL);