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, gpointer de_)
238 struct data_editor *de = de_;
239 GtkWindow *parent_window = GTK_WINDOW (de);
240 struct import_assistant *ia;
242 ia = xzalloc (sizeof *ia);
243 if (!init_file (ia, parent_window))
249 init_assistant (ia, parent_window);
250 init_intro_page (ia);
251 init_first_line_page (ia);
252 init_separators_page (ia);
253 init_formats_page (ia);
255 gtk_widget_show_all (GTK_WIDGET (ia->asst.assistant));
257 ia->asst.main_loop = g_main_loop_new (NULL, false);
258 g_main_loop_run (ia->asst.main_loop);
259 g_main_loop_unref (ia->asst.main_loop);
261 switch (ia->asst.response)
263 case GTK_RESPONSE_APPLY:
265 char *syntax = generate_syntax (ia);
266 execute_syntax (create_syntax_string_source (syntax));
270 case PSPPIRE_RESPONSE_PASTE:
272 char *syntax = generate_syntax (ia);
273 paste_syntax_in_new_window (syntax);
281 destroy_formats_page (ia);
282 destroy_separators_page (ia);
283 destroy_assistant (ia);
288 /* Emits PSPP syntax to S that applies the dictionary attributes
289 (such as missing values and value labels) of the variables in
292 apply_dict (const struct dictionary *dict, struct string *s)
294 size_t var_cnt = dict_get_var_cnt (dict);
297 for (i = 0; i < var_cnt; i++)
299 struct variable *var = dict_get_var (dict, i);
300 const char *name = var_get_name (var);
301 enum val_type type = var_get_type (var);
302 int width = var_get_width (var);
303 enum measure measure = var_get_measure (var);
304 enum alignment alignment = var_get_alignment (var);
305 const struct fmt_spec *format = var_get_print_format (var);
307 if (var_has_missing_values (var))
309 const struct missing_values *mv = var_get_missing_values (var);
312 syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
313 for (j = 0; j < mv_n_values (mv); j++)
317 ds_put_cstr (s, ", ");
318 mv_get_value (mv, &value, j);
319 syntax_gen_value (s, &value, width, format);
322 if (mv_has_range (mv))
325 if (mv_has_value (mv))
326 ds_put_cstr (s, ", ");
327 mv_get_range (mv, &low, &high);
328 syntax_gen_num_range (s, low, high, format);
330 ds_put_cstr (s, ").\n");
332 if (var_has_value_labels (var))
334 const struct val_labs *vls = var_get_value_labels (var);
335 struct val_labs_iterator *iter;
338 syntax_gen_pspp (s, "VALUE LABELS %ss", name);
339 for (vl = val_labs_first_sorted (vls, &iter); vl != NULL;
340 vl = val_labs_next (vls, &iter))
342 ds_put_cstr (s, "\n ");
343 syntax_gen_value (s, &vl->value, width, format);
344 ds_put_char (s, ' ');
345 syntax_gen_string (s, ss_cstr (vl->label));
347 ds_put_cstr (s, ".\n");
349 if (var_has_label (var))
350 syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
351 name, var_get_label (var));
352 if (measure != var_default_measure (type))
353 syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
355 (measure == MEASURE_NOMINAL ? "NOMINAL"
356 : measure == MEASURE_ORDINAL ? "ORDINAL"
358 if (alignment != var_default_alignment (type))
359 syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
361 (alignment == ALIGN_LEFT ? "LEFT"
362 : alignment == ALIGN_CENTRE ? "CENTER"
364 if (var_get_display_width (var) != var_default_display_width (width))
365 syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
366 name, var_get_display_width (var));
370 /* Generates and returns PSPP syntax to execute the import
371 operation described by IA. The caller must free the syntax
374 generate_syntax (const struct import_assistant *ia)
376 struct string s = DS_EMPTY_INITIALIZER;
385 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
386 ia->intro.n_cases_button)))
387 ds_put_format (&s, " /IMPORTCASES=FIRST %d\n",
388 gtk_spin_button_get_value_as_int (
389 GTK_SPIN_BUTTON (ia->intro.n_cases_spin)));
390 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
391 ia->intro.percent_button)))
392 ds_put_format (&s, " /IMPORTCASES=PERCENT %d\n",
393 gtk_spin_button_get_value_as_int (
394 GTK_SPIN_BUTTON (ia->intro.percent_spin)));
396 ds_put_cstr (&s, " /IMPORTCASES=ALL\n");
398 " /ARRANGEMENT=DELIMITED\n"
400 if (ia->first_line.skip_lines > 0)
401 ds_put_format (&s, " /FIRSTCASE=%d\n", ia->first_line.skip_lines + 1);
402 ds_put_cstr (&s, " /DELIMITERS=\"");
403 if (ds_find_char (&ia->separators.separators, '\t') != SIZE_MAX)
404 ds_put_cstr (&s, "\\t");
405 if (ds_find_char (&ia->separators.separators, '\\') != SIZE_MAX)
406 ds_put_cstr (&s, "\\\\");
407 for (i = 0; i < ds_length (&ia->separators.separators); i++)
409 char c = ds_at (&ia->separators.separators, i);
411 ds_put_cstr (&s, "\"\"");
412 else if (c != '\t' && c != '\\')
415 ds_put_cstr (&s, "\"\n");
416 if (!ds_is_empty (&ia->separators.quotes))
417 syntax_gen_pspp (&s, " /QUALIFIER=%sq\n", ds_cstr (&ia->separators.quotes));
418 if (!ds_is_empty (&ia->separators.quotes) && ia->separators.escape)
419 ds_put_cstr (&s, " /ESCAPE\n");
420 ds_put_cstr (&s, " /VARIABLES=\n");
422 var_cnt = dict_get_var_cnt (ia->formats.dict);
423 for (i = 0; i < var_cnt; i++)
425 struct variable *var = dict_get_var (ia->formats.dict, i);
426 char format_string[FMT_STRING_LEN_MAX + 1];
427 fmt_to_string (var_get_print_format (var), format_string);
428 ds_put_format (&s, " %s %s%s\n",
429 var_get_name (var), format_string,
430 i == var_cnt - 1 ? "." : "");
433 apply_dict (ia->formats.dict, &s);
438 /* Choosing a file and reading it. */
440 static char *choose_file (GtkWindow *parent_window);
442 /* Obtains the file to import from the user and initializes IA's
443 file substructure. PARENT_WINDOW must be the window to use
444 as the file chooser window's parent.
446 Returns true if successful, false if the file name could not
447 be obtained or the file could not be read. */
449 init_file (struct import_assistant *ia, GtkWindow *parent_window)
451 struct file *file = &ia->file;
452 enum { MAX_PREVIEW_LINES = 1000 }; /* Max number of lines to read. */
453 enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
456 file->file_name = choose_file (parent_window);
457 if (file->file_name == NULL)
460 stream = fopen (file->file_name, "r");
463 msg (ME, _("Could not open \"%s\": %s"),
464 file->file_name, strerror (errno));
468 file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines);
469 for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++)
471 struct string *line = &file->lines[file->line_cnt];
473 ds_init_empty (line);
474 if (!ds_read_line (line, stream, MAX_LINE_LEN))
478 else if (ferror (stream))
479 msg (ME, _("Error reading \"%s\": %s"),
480 file->file_name, strerror (errno));
482 msg (ME, _("Failed to read \"%s\", because it contains a line "
483 "over %d bytes long and therefore appears not to be "
485 file->file_name, MAX_LINE_LEN);
490 ds_chomp (line, '\n');
491 ds_chomp (line, '\r');
494 if (file->line_cnt == 0)
496 msg (ME, _("\"%s\" is empty."), file->file_name);
502 /* Estimate the number of lines in the file. */
503 if (file->line_cnt < MAX_PREVIEW_LINES)
504 file->total_lines = file->line_cnt;
508 off_t position = ftello (stream);
509 if (fstat (fileno (stream), &s) == 0 && position > 0)
510 file->total_lines = (double) file->line_cnt / position * s.st_size;
512 file->total_lines = 0;
518 /* Frees IA's file substructure. */
520 destroy_file (struct import_assistant *ia)
522 struct file *f = &ia->file;
525 for (i = 0; i < f->line_cnt; i++)
526 ds_destroy (&f->lines[i]);
528 g_free (f->file_name);
531 /* Obtains the file to read from the user and returns the name of
532 the file as a string that must be freed with g_free if
533 successful, otherwise a null pointer. PARENT_WINDOW must be
534 the window to use as the file chooser window's parent. */
536 choose_file (GtkWindow *parent_window)
541 dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
543 GTK_FILE_CHOOSER_ACTION_OPEN,
544 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
545 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
548 switch (gtk_dialog_run (GTK_DIALOG (dialog)))
550 case GTK_RESPONSE_ACCEPT:
551 file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
557 gtk_widget_destroy (dialog);
564 static void close_assistant (struct import_assistant *, int response);
565 static void on_prepare (GtkAssistant *assistant, GtkWidget *page,
566 struct import_assistant *);
567 static void on_cancel (GtkAssistant *assistant, struct import_assistant *);
568 static void on_close (GtkAssistant *assistant, struct import_assistant *);
569 static void on_paste (GtkButton *button, struct import_assistant *);
570 static void on_reset (GtkButton *button, struct import_assistant *);
571 static void close_assistant (struct import_assistant *, int response);
573 /* Initializes IA's asst substructure. PARENT_WINDOW must be the
574 window to use as the assistant window's parent. */
576 init_assistant (struct import_assistant *ia, GtkWindow *parent_window)
578 struct assistant *a = &ia->asst;
580 a->builder = builder_new ("text-data-import.ui");
581 a->assistant = GTK_ASSISTANT (gtk_assistant_new ());
582 g_signal_connect (a->assistant, "prepare", G_CALLBACK (on_prepare), ia);
583 g_signal_connect (a->assistant, "cancel", G_CALLBACK (on_cancel), ia);
584 g_signal_connect (a->assistant, "close", G_CALLBACK (on_close), ia);
585 a->paste_button = gtk_button_new_from_stock (GTK_STOCK_PASTE);
586 gtk_assistant_add_action_widget (a->assistant, a->paste_button);
587 g_signal_connect (a->paste_button, "clicked", G_CALLBACK (on_paste), ia);
588 a->reset_button = gtk_button_new_from_stock ("pspp-stock-reset");
589 gtk_assistant_add_action_widget (a->assistant, a->reset_button);
590 g_signal_connect (a->reset_button, "clicked", G_CALLBACK (on_reset), ia);
591 gtk_window_set_title (GTK_WINDOW (a->assistant),
592 _("Importing Delimited Text Data"));
593 gtk_window_set_transient_for (GTK_WINDOW (a->assistant), parent_window);
595 a->prop_renderer = gtk_cell_renderer_text_new ();
596 g_object_ref_sink (a->prop_renderer);
597 a->fixed_renderer = gtk_cell_renderer_text_new ();
598 g_object_ref_sink (a->fixed_renderer);
599 g_object_set (G_OBJECT (a->fixed_renderer),
600 "family", "Monospace",
604 /* Frees IA's asst substructure. */
606 destroy_assistant (struct import_assistant *ia)
608 struct assistant *a = &ia->asst;
610 g_object_unref (a->prop_renderer);
611 g_object_unref (a->fixed_renderer);
612 g_object_unref (a->builder);
615 /* Appends a page of the given TYPE, with PAGE as its content, to
616 the GtkAssistant encapsulated by IA. Returns the GtkWidget
617 that represents the page. */
619 add_page_to_assistant (struct import_assistant *ia,
620 GtkWidget *page, GtkAssistantPageType type)
626 title = gtk_window_get_title (GTK_WINDOW (page));
627 title_copy = xstrdup (title ? title : "");
629 content = gtk_bin_get_child (GTK_BIN (page));
631 g_object_ref (content);
632 gtk_container_remove (GTK_CONTAINER (page), content);
634 gtk_widget_destroy (page);
636 gtk_assistant_append_page (ia->asst.assistant, content);
637 gtk_assistant_set_page_type (ia->asst.assistant, content, type);
638 gtk_assistant_set_page_title (ia->asst.assistant, content, title_copy);
639 gtk_assistant_set_page_complete (ia->asst.assistant, content, true);
646 /* Called just before PAGE is displayed as the current page of
647 ASSISTANT, this updates IA content according to the new
650 on_prepare (GtkAssistant *assistant, GtkWidget *page,
651 struct import_assistant *ia)
653 if (page == ia->separators.page)
654 prepare_separators_page (ia);
655 else if (page == ia->formats.page)
656 prepare_formats_page (ia);
658 gtk_widget_show (ia->asst.reset_button);
659 if (page == ia->formats.page)
660 gtk_widget_show (ia->asst.paste_button);
662 gtk_widget_hide (ia->asst.paste_button);
665 /* Called when the Cancel button in the assistant is clicked. */
667 on_cancel (GtkAssistant *assistant, struct import_assistant *ia)
669 close_assistant (ia, GTK_RESPONSE_CANCEL);
672 /* Called when the Apply button on the last page of the assistant
675 on_close (GtkAssistant *assistant, struct import_assistant *ia)
677 close_assistant (ia, GTK_RESPONSE_APPLY);
680 /* Called when the Paste button on the last page of the assistant
683 on_paste (GtkButton *button, struct import_assistant *ia)
685 close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
688 /* Called when the Reset button is clicked. */
690 on_reset (GtkButton *button, struct import_assistant *ia)
692 gint page_num = gtk_assistant_get_current_page (ia->asst.assistant);
693 GtkWidget *page = gtk_assistant_get_nth_page (ia->asst.assistant, page_num);
695 if (page == ia->intro.page)
696 reset_intro_page (ia);
697 else if (page == ia->first_line.page)
698 reset_first_line_page (ia);
699 else if (page == ia->separators.page)
700 reset_separators_page (ia);
701 else if (page == ia->formats.page)
702 reset_formats_page (ia);
705 /* Causes the assistant to close, returning RESPONSE for
706 interpretation by text_data_import_assistant. */
708 close_assistant (struct import_assistant *ia, int response)
710 ia->asst.response = response;
711 g_main_loop_quit (ia->asst.main_loop);
712 gtk_widget_hide (GTK_WIDGET (ia->asst.assistant));
715 /* The "intro" page of the assistant. */
717 static void on_intro_amount_changed (GtkToggleButton *button,
718 struct import_assistant *);
720 /* Initializes IA's intro substructure. */
722 init_intro_page (struct import_assistant *ia)
724 GtkBuilder *builder = ia->asst.builder;
725 struct intro_page *p = &ia->intro;
728 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Intro"),
729 GTK_ASSISTANT_PAGE_INTRO);
730 p->all_cases_button = get_widget_assert (builder, "import-all-cases");
731 p->n_cases_button = get_widget_assert (builder, "import-n-cases");
732 p->n_cases_spin = get_widget_assert (builder, "n-cases-spin");
733 p->percent_button = get_widget_assert (builder, "import-percent");
734 p->percent_spin = get_widget_assert (builder, "percent-spin");
735 g_signal_connect (p->all_cases_button, "toggled",
736 G_CALLBACK (on_intro_amount_changed), ia);
737 g_signal_connect (p->n_cases_button, "toggled",
738 G_CALLBACK (on_intro_amount_changed), ia);
739 g_signal_connect (p->percent_button, "toggled",
740 G_CALLBACK (on_intro_amount_changed), ia);
743 ds_put_cstr (&s, _("This assistant will guide you through the process of "
744 "importing data into PSPP from a text file with one line "
745 "per case, in which fields are separated by tabs, "
746 "commas, or other delimiters.\n\n"));
747 if (ia->file.total_is_exact)
749 &s, ngettext ("The selected file contains %zu line of text. ",
750 "The selected file contains %zu lines of text. ",
753 else if (ia->file.total_lines > 0)
757 "The selected file contains approximately %lu line of text. ",
758 "The selected file contains approximately %lu lines of text. ",
759 ia->file.total_lines),
760 ia->file.total_lines);
763 "Only the first %zu line of the file will be shown for "
764 "preview purposes in the following screens. ",
765 "Only the first %zu lines of the file will be shown for "
766 "preview purposes in the following screens. ",
770 ds_put_cstr (&s, _("You may choose below how much of the file should "
771 "actually be imported."));
772 gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")),
777 /* Resets IA's intro page to its initial state. */
779 reset_intro_page (struct import_assistant *ia)
781 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->intro.all_cases_button),
785 /* Called when one of the radio buttons is clicked. */
787 on_intro_amount_changed (GtkToggleButton *button UNUSED,
788 struct import_assistant *ia)
790 struct intro_page *p = &ia->intro;
792 gtk_widget_set_sensitive (p->n_cases_spin,
793 gtk_toggle_button_get_active (
794 GTK_TOGGLE_BUTTON (p->n_cases_button)));
796 gtk_widget_set_sensitive (ia->intro.percent_spin,
797 gtk_toggle_button_get_active (
798 GTK_TOGGLE_BUTTON (p->percent_button)));
801 /* The "first line" page of the assistant. */
803 static GtkTreeView *create_lines_tree_view (GtkContainer *parent_window,
804 struct import_assistant *);
805 static void on_first_line_change (GtkTreeSelection *,
806 struct import_assistant *);
807 static void on_variable_names_cb_toggle (GtkToggleButton *,
808 struct import_assistant *);
809 static void set_first_line (struct import_assistant *);
810 static void get_first_line (struct import_assistant *);
812 /* Initializes IA's first_line substructure. */
814 init_first_line_page (struct import_assistant *ia)
816 struct first_line_page *p = &ia->first_line;
817 GtkBuilder *builder = ia->asst.builder;
819 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "FirstLine"),
820 GTK_ASSISTANT_PAGE_CONTENT);
821 gtk_widget_destroy (get_widget_assert (builder, "first-line"));
822 p->tree_view = create_lines_tree_view (
823 GTK_CONTAINER (get_widget_assert (builder, "first-line-scroller")), ia);
824 p->variable_names_cb = get_widget_assert (builder, "variable-names");
825 gtk_tree_selection_set_mode (
826 gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
827 GTK_SELECTION_BROWSE);
829 g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
830 "changed", G_CALLBACK (on_first_line_change), ia);
831 g_signal_connect (p->variable_names_cb, "toggled",
832 G_CALLBACK (on_variable_names_cb_toggle), ia);
835 /* Resets the first_line page to its initial content. */
837 reset_first_line_page (struct import_assistant *ia)
839 ia->first_line.skip_lines = 0;
840 ia->first_line.variable_names = false;
844 /* Creates and returns a tree view that contains each of the
845 lines in IA's file as a row. */
847 create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia)
849 GtkTreeView *tree_view;
850 GtkTreeViewColumn *column;
851 size_t max_line_length;
852 gint content_width, header_width;
855 make_tree_view (ia, 0, &tree_view);
857 column = gtk_tree_view_column_new_with_attributes (
858 "Text", ia->asst.fixed_renderer,
859 "text", TEXT_IMPORT_MODEL_COLUMN_LINE,
861 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
864 for (i = 0; i < ia->file.line_cnt; i++)
866 size_t w = ds_length (&ia->file.lines[i]);
867 max_line_length = MAX (max_line_length, w);
870 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
872 header_width = get_string_width (tree_view, ia->asst.prop_renderer, "Text");
873 gtk_tree_view_column_set_fixed_width (column, MAX (content_width,
875 gtk_tree_view_append_column (tree_view, column);
877 gtk_tree_view_set_fixed_height_mode (tree_view, true);
879 gtk_container_add (parent, GTK_WIDGET (tree_view));
880 gtk_widget_show (GTK_WIDGET (tree_view));
885 /* Called when the line selected in the first_line tree view
888 on_first_line_change (GtkTreeSelection *selection UNUSED,
889 struct import_assistant *ia)
894 /* Called when the checkbox that indicates whether variable
895 names are in the row above the first line is toggled. */
897 on_variable_names_cb_toggle (GtkToggleButton *variable_names_cb UNUSED,
898 struct import_assistant *ia)
903 /* Sets the widgets to match IA's first_line substructure. */
905 set_first_line (struct import_assistant *ia)
909 path = gtk_tree_path_new_from_indices (ia->first_line.skip_lines, -1);
910 gtk_tree_view_set_cursor (GTK_TREE_VIEW (ia->first_line.tree_view),
912 gtk_tree_path_free (path);
914 gtk_toggle_button_set_active (
915 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb),
916 ia->first_line.variable_names);
917 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
918 ia->first_line.skip_lines > 0);
921 /* Sets IA's first_line substructure to match the widgets. */
923 get_first_line (struct import_assistant *ia)
925 GtkTreeSelection *selection;
929 selection = gtk_tree_view_get_selection (ia->first_line.tree_view);
930 if (gtk_tree_selection_get_selected (selection, &model, &iter))
932 GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
933 int row = gtk_tree_path_get_indices (path)[0];
934 gtk_tree_path_free (path);
936 ia->first_line.skip_lines = row;
937 ia->first_line.variable_names =
938 (ia->first_line.skip_lines > 0
939 && gtk_toggle_button_get_active (
940 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb)));
942 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
943 ia->first_line.skip_lines > 0);
946 /* The "separators" page of the assistant. */
948 static void revise_fields_preview (struct import_assistant *ia);
949 static void choose_likely_separators (struct import_assistant *ia);
950 static void find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
951 const char *targets, const char *def,
952 struct string *result);
953 static void clear_fields (struct import_assistant *ia);
954 static void revise_fields_preview (struct import_assistant *);
955 static void set_separators (struct import_assistant *);
956 static void get_separators (struct import_assistant *);
957 static void on_separators_custom_entry_notify (GObject *UNUSED,
959 struct import_assistant *);
960 static void on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
961 struct import_assistant *);
962 static void on_quote_combo_change (GtkComboBox *combo,
963 struct import_assistant *);
964 static void on_quote_cb_toggle (GtkToggleButton *quote_cb,
965 struct import_assistant *);
966 static void on_separator_toggle (GtkToggleButton *, struct import_assistant *);
967 static void render_input_cell (GtkTreeViewColumn *tree_column,
968 GtkCellRenderer *cell,
969 GtkTreeModel *model, GtkTreeIter *iter,
971 static gboolean on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
972 gboolean keyboard_mode UNUSED,
974 struct import_assistant *);
976 /* A common field separator and its identifying name. */
979 const char *name; /* Name (for use with get_widget_assert). */
980 int c; /* Separator character. */
983 /* All the separators in the dialog box. */
984 static const struct separator separators[] =
996 #define SEPARATOR_CNT (sizeof separators / sizeof *separators)
999 set_quote_list (GtkComboBoxEntry *cb)
1001 GtkListStore *list = gtk_list_store_new (1, G_TYPE_STRING);
1004 const gchar *seperator[3] = {"'\"", "\'", "\""};
1006 for (i = 0; i < 3; i++)
1008 const gchar *s = seperator[i];
1010 /* Add a new row to the model */
1011 gtk_list_store_append (list, &iter);
1012 gtk_list_store_set (list, &iter,
1018 gtk_combo_box_set_model (GTK_COMBO_BOX (cb), GTK_TREE_MODEL (list));
1020 gtk_combo_box_entry_set_text_column (cb, 0);
1023 /* Initializes IA's separators substructure. */
1025 init_separators_page (struct import_assistant *ia)
1027 GtkBuilder *builder = ia->asst.builder;
1028 struct separators_page *p = &ia->separators;
1031 choose_likely_separators (ia);
1033 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Separators"),
1034 GTK_ASSISTANT_PAGE_CONTENT);
1035 p->custom_cb = get_widget_assert (builder, "custom-cb");
1036 p->custom_entry = get_widget_assert (builder, "custom-entry");
1037 p->quote_combo = get_widget_assert (builder, "quote-combo");
1038 p->quote_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (p->quote_combo)));
1039 p->quote_cb = get_widget_assert (builder, "quote-cb");
1040 p->escape_cb = get_widget_assert (builder, "escape");
1042 set_separators (ia);
1043 set_quote_list (GTK_COMBO_BOX_ENTRY (p->quote_combo));
1044 p->fields_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "fields"));
1045 g_signal_connect (GTK_COMBO_BOX (p->quote_combo), "changed",
1046 G_CALLBACK (on_quote_combo_change), ia);
1047 g_signal_connect (p->quote_cb, "toggled",
1048 G_CALLBACK (on_quote_cb_toggle), ia);
1049 g_signal_connect (GTK_ENTRY (p->custom_entry), "notify::text",
1050 G_CALLBACK (on_separators_custom_entry_notify), ia);
1051 g_signal_connect (p->custom_cb, "toggled",
1052 G_CALLBACK (on_separators_custom_cb_toggle), ia);
1053 for (i = 0; i < SEPARATOR_CNT; i++)
1054 g_signal_connect (get_widget_assert (builder, separators[i].name),
1055 "toggled", G_CALLBACK (on_separator_toggle), ia);
1056 g_signal_connect (p->escape_cb, "toggled",
1057 G_CALLBACK (on_separator_toggle), ia);
1060 /* Frees IA's separators substructure. */
1062 destroy_separators_page (struct import_assistant *ia)
1064 struct separators_page *s = &ia->separators;
1066 ds_destroy (&s->separators);
1067 ds_destroy (&s->quotes);
1071 /* Called just before the separators page becomes visible in the
1074 prepare_separators_page (struct import_assistant *ia)
1076 revise_fields_preview (ia);
1079 /* Called when the Reset button is clicked on the separators
1080 page, resets the separators to the defaults. */
1082 reset_separators_page (struct import_assistant *ia)
1084 choose_likely_separators (ia);
1085 set_separators (ia);
1088 /* Frees and clears the column data in IA's separators
1091 clear_fields (struct import_assistant *ia)
1093 struct separators_page *s = &ia->separators;
1095 if (s->column_cnt > 0)
1100 for (row = 0; row < ia->file.line_cnt; row++)
1102 const struct string *line = &ia->file.lines[row];
1103 const char *line_start = ds_data (line);
1104 const char *line_end = ds_end (line);
1106 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1108 char *s = ss_data (col->contents[row]);
1109 if (!(s >= line_start && s <= line_end))
1110 ss_dealloc (&col->contents[row]);
1114 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1117 free (col->contents);
1126 /* Breaks the file data in IA into columns based on the
1127 separators set in IA's separators substructure. */
1129 split_fields (struct import_assistant *ia)
1131 struct separators_page *s = &ia->separators;
1132 size_t columns_allocated;
1138 /* Is space in the set of separators? */
1139 space_sep = ss_find_char (ds_ss (&s->separators), ' ') != SIZE_MAX;
1141 /* Split all the lines, not just those from
1142 ia->first_line.skip_lines on, so that we split the line that
1143 contains variables names if ia->first_line.variable_names is
1145 columns_allocated = 0;
1146 for (row = 0; row < ia->file.line_cnt; row++)
1148 struct string *line = &ia->file.lines[row];
1149 struct substring text = ds_ss (line);
1152 for (column_idx = 0; ; column_idx++)
1154 struct substring field;
1155 struct column *column;
1158 ss_ltrim (&text, ss_cstr (" "));
1159 if (ss_is_empty (text))
1161 if (column_idx != 0)
1165 else if (!ds_is_empty (&s->quotes)
1166 && ds_find_char (&s->quotes, text.string[0]) != SIZE_MAX)
1168 int quote = ss_get_char (&text);
1170 ss_get_until (&text, quote, &field);
1177 while ((c = ss_get_char (&text)) != EOF)
1179 ds_put_char (&s, c);
1180 else if (ss_match_char (&text, quote))
1181 ds_put_char (&s, quote);
1188 ss_get_chars (&text, ss_cspan (text, ds_ss (&s->separators)),
1191 if (column_idx >= s->column_cnt)
1193 struct column *column;
1195 if (s->column_cnt >= columns_allocated)
1196 s->columns = x2nrealloc (s->columns, &columns_allocated,
1197 sizeof *s->columns);
1198 column = &s->columns[s->column_cnt++];
1199 column->name = NULL;
1201 column->contents = xcalloc (ia->file.line_cnt,
1202 sizeof *column->contents);
1204 column = &s->columns[column_idx];
1205 column->contents[row] = field;
1206 if (ss_length (field) > column->width)
1207 column->width = ss_length (field);
1210 ss_ltrim (&text, ss_cstr (" "));
1211 if (ss_is_empty (text))
1213 if (ss_find_char (ds_ss (&s->separators), ss_first (text))
1215 ss_advance (&text, 1);
1220 /* Chooses a name for each column on the separators page */
1222 choose_column_names (struct import_assistant *ia)
1224 const struct first_line_page *f = &ia->first_line;
1225 struct separators_page *s = &ia->separators;
1226 struct dictionary *dict;
1227 unsigned long int generated_name_count = 0;
1231 dict = dict_create ();
1232 name_row = f->variable_names && f->skip_lines ? f->skip_lines : 0;
1233 for (col = s->columns; col < &s->columns[s->column_cnt]; col++)
1235 char name[VAR_NAME_LEN + 1];
1238 hint = name_row ? ss_xstrdup (col->contents[name_row - 1]) : NULL;
1239 if (!dict_make_unique_var_name (dict, hint, &generated_name_count, name))
1243 col->name = xstrdup (name);
1244 dict_create_var_assert (dict, name, 0);
1246 dict_destroy (dict);
1249 /* Picks the most likely separator and quote characters based on
1252 choose_likely_separators (struct import_assistant *ia)
1254 unsigned long int histogram[UCHAR_MAX + 1] = { 0 };
1257 /* Construct a histogram of all the characters used in the
1259 for (row = 0; row < ia->file.line_cnt; row++)
1261 struct substring line = ds_ss (&ia->file.lines[row]);
1262 size_t length = ss_length (line);
1264 for (i = 0; i < length; i++)
1265 histogram[(unsigned char) line.string[i]]++;
1268 find_commonest_chars (histogram, "\"'", "", &ia->separators.quotes);
1269 find_commonest_chars (histogram, ",;:/|!\t-", ",",
1270 &ia->separators.separators);
1271 ia->separators.escape = true;
1274 /* Chooses the most common character among those in TARGETS,
1275 based on the frequency data in HISTOGRAM, and stores it in
1276 RESULT. If there is a tie for the most common character among
1277 those in TARGETS, the earliest character is chosen. If none
1278 of the TARGETS appear at all, then DEF is used as a
1281 find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
1282 const char *targets, const char *def,
1283 struct string *result)
1285 unsigned char max = 0;
1286 unsigned long int max_count = 0;
1288 for (; *targets != '\0'; targets++)
1290 unsigned char c = *targets;
1291 unsigned long int count = histogram[c];
1292 if (count > max_count)
1301 ds_put_char (result, max);
1304 ds_assign_cstr (result, def);
1307 /* Revises the contents of the fields tree view based on the
1308 currently chosen set of separators. */
1310 revise_fields_preview (struct import_assistant *ia)
1314 push_watch_cursor (ia);
1316 w = GTK_WIDGET (ia->separators.fields_tree_view);
1317 gtk_widget_destroy (w);
1318 get_separators (ia);
1320 choose_column_names (ia);
1321 ia->separators.fields_tree_view = create_data_tree_view (
1323 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "fields-scroller")),
1326 pop_watch_cursor (ia);
1329 /* Sets the widgets to match IA's separators substructure. */
1331 set_separators (struct import_assistant *ia)
1333 struct separators_page *s = &ia->separators;
1335 struct string custom;
1340 ds_init_empty (&custom);
1342 for (i = 0; i < ds_length (&s->separators); i++)
1344 unsigned char c = ds_at (&s->separators, i);
1347 for (j = 0; j < SEPARATOR_CNT; j++)
1349 const struct separator *s = &separators[j];
1357 ds_put_char (&custom, c);
1361 for (i = 0; i < SEPARATOR_CNT; i++)
1363 const struct separator *s = &separators[i];
1364 GtkWidget *button = get_widget_assert (ia->asst.builder, s->name);
1365 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
1366 (seps & (1u << i)) != 0);
1368 any_custom = !ds_is_empty (&custom);
1369 gtk_entry_set_text (GTK_ENTRY (s->custom_entry), ds_cstr (&custom));
1370 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->custom_cb),
1372 gtk_widget_set_sensitive (s->custom_entry, any_custom);
1373 ds_destroy (&custom);
1375 any_quotes = !ds_is_empty (&s->quotes);
1377 gtk_entry_set_text (s->quote_entry,
1378 any_quotes ? ds_cstr (&s->quotes) : "\"");
1379 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->quote_cb),
1381 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->escape_cb),
1383 gtk_widget_set_sensitive (s->quote_combo, any_quotes);
1384 gtk_widget_set_sensitive (s->escape_cb, any_quotes);
1387 /* Sets IA's separators substructure to match the widgets. */
1389 get_separators (struct import_assistant *ia)
1391 struct separators_page *s = &ia->separators;
1394 ds_clear (&s->separators);
1395 for (i = 0; i < SEPARATOR_CNT; i++)
1397 const struct separator *sep = &separators[i];
1398 GtkWidget *button = get_widget_assert (ia->asst.builder, sep->name);
1399 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1400 ds_put_char (&s->separators, sep->c);
1403 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->custom_cb)))
1404 ds_put_cstr (&s->separators,
1405 gtk_entry_get_text (GTK_ENTRY (s->custom_entry)));
1407 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->quote_cb)))
1409 gchar *text = gtk_combo_box_get_active_text (
1410 GTK_COMBO_BOX (s->quote_combo));
1411 ds_assign_cstr (&s->quotes, text);
1415 ds_clear (&s->quotes);
1416 s->escape = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->escape_cb));
1419 /* Called when the user changes the entry field for custom
1422 on_separators_custom_entry_notify (GObject *gobject UNUSED,
1423 GParamSpec *arg1 UNUSED,
1424 struct import_assistant *ia)
1426 revise_fields_preview (ia);
1429 /* Called when the user toggles the checkbox that enables custom
1432 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1433 struct import_assistant *ia)
1435 bool is_active = gtk_toggle_button_get_active (custom_cb);
1436 gtk_widget_set_sensitive (ia->separators.custom_entry, is_active);
1437 revise_fields_preview (ia);
1440 /* Called when the user changes the selection in the combo box
1441 that selects a quote character. */
1443 on_quote_combo_change (GtkComboBox *combo, struct import_assistant *ia)
1445 revise_fields_preview (ia);
1448 /* Called when the user toggles the checkbox that enables
1451 on_quote_cb_toggle (GtkToggleButton *quote_cb, struct import_assistant *ia)
1453 bool is_active = gtk_toggle_button_get_active (quote_cb);
1454 gtk_widget_set_sensitive (ia->separators.quote_combo, is_active);
1455 gtk_widget_set_sensitive (ia->separators.escape_cb, is_active);
1456 revise_fields_preview (ia);
1459 /* Called when the user toggles one of the separators
1462 on_separator_toggle (GtkToggleButton *toggle UNUSED,
1463 struct import_assistant *ia)
1465 revise_fields_preview (ia);
1468 /* Called to render one of the cells in the fields preview tree
1471 render_input_cell (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1472 GtkTreeModel *model, GtkTreeIter *iter,
1475 struct import_assistant *ia = ia_;
1476 struct substring field;
1480 column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1482 row = text_import_model_iter_to_row (iter) + ia->first_line.skip_lines;
1483 field = ia->separators.columns[column].contents[row];
1484 if (field.string != NULL)
1486 GValue text = {0, };
1487 g_value_init (&text, G_TYPE_STRING);
1488 g_value_take_string (&text, ss_xstrdup (field));
1489 g_object_set_property (G_OBJECT (cell), "text", &text);
1490 g_value_unset (&text);
1491 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1496 "background", "red",
1497 "background-set", TRUE,
1501 /* Called to render a tooltip on one of the cells in the fields
1502 preview tree view. */
1504 on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1505 gboolean keyboard_mode UNUSED,
1506 GtkTooltip *tooltip, struct import_assistant *ia)
1510 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1513 if (ia->separators.columns[column].contents[row].string != NULL)
1516 gtk_tooltip_set_text (tooltip,
1517 _("This input line has too few separators "
1518 "to fill in this field."));
1522 /* The "formats" page of the assistant. */
1524 static void on_variable_change (PsppireDict *dict, int idx,
1525 struct import_assistant *);
1526 static void clear_modified_vars (struct import_assistant *);
1528 /* Initializes IA's formats substructure. */
1530 init_formats_page (struct import_assistant *ia)
1532 GtkBuilder *builder = ia->asst.builder;
1533 struct formats_page *p = &ia->formats;
1535 p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Formats"),
1536 GTK_ASSISTANT_PAGE_CONFIRM);
1537 p->data_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "data"));
1538 p->modified_vars = NULL;
1539 p->modified_var_cnt = 0;
1542 /* Frees IA's formats substructure. */
1544 destroy_formats_page (struct import_assistant *ia)
1546 struct formats_page *p = &ia->formats;
1548 if (p->psppire_dict != NULL)
1550 /* This destroys p->dict also. */
1551 g_object_unref (p->psppire_dict);
1553 clear_modified_vars (ia);
1556 /* Called just before the formats page of the assistant is
1559 prepare_formats_page (struct import_assistant *ia)
1561 struct dictionary *dict;
1562 PsppireDict *psppire_dict;
1563 PsppireVarStore *var_store;
1564 GtkBin *vars_scroller;
1565 GtkWidget *old_var_sheet;
1566 PsppireVarSheet *var_sheet;
1567 struct separators_page *s = &ia->separators;
1568 struct formats_page *p = &ia->formats;
1569 struct fmt_guesser *fg;
1570 unsigned long int number = 0;
1573 push_watch_cursor (ia);
1575 dict = dict_create ();
1576 fg = fmt_guesser_create ();
1577 for (column_idx = 0; column_idx < s->column_cnt; column_idx++)
1579 struct variable *modified_var;
1580 char name[VAR_NAME_LEN + 1];
1582 modified_var = (column_idx < p->modified_var_cnt
1583 ? p->modified_vars[column_idx] : NULL);
1584 if (modified_var == NULL)
1586 struct column *column = &s->columns[column_idx];
1587 struct variable *var;
1588 struct fmt_spec format;
1591 /* Choose variable name. */
1592 if (!dict_make_unique_var_name (dict, column->name, &number, name))
1595 /* Choose variable format. */
1596 fmt_guesser_clear (fg);
1597 for (row = ia->first_line.skip_lines; row < ia->file.line_cnt; row++)
1598 fmt_guesser_add (fg, column->contents[row]);
1599 fmt_guesser_guess (fg, &format);
1600 fmt_fix_input (&format);
1602 /* Create variable. */
1603 var = dict_create_var_assert (dict, name, fmt_var_width (&format));
1604 var_set_both_formats (var, &format);
1608 if (!dict_make_unique_var_name (dict, var_get_name (modified_var),
1611 dict_clone_var_assert (dict, modified_var, name);
1614 fmt_guesser_destroy (fg);
1616 psppire_dict = psppire_dict_new_from_dict (dict);
1617 g_signal_connect (psppire_dict, "variable_changed",
1618 G_CALLBACK (on_variable_change), ia);
1619 ia->formats.dict = dict;
1620 ia->formats.psppire_dict = psppire_dict;
1622 /* XXX: PsppireVarStore doesn't hold a reference to
1623 psppire_dict for now, but it should. After it does, we
1624 should g_object_ref the psppire_dict here, since we also
1625 hold a reference via ia->formats.dict. */
1626 var_store = psppire_var_store_new (psppire_dict);
1627 g_object_set (var_store,
1628 "format-type", PSPPIRE_VAR_STORE_INPUT_FORMATS,
1630 var_sheet = PSPPIRE_VAR_SHEET (psppire_var_sheet_new ());
1631 g_object_set (var_sheet,
1633 "may-create-vars", FALSE,
1636 vars_scroller = GTK_BIN (get_widget_assert (ia->asst.builder, "vars-scroller"));
1637 old_var_sheet = gtk_bin_get_child (vars_scroller);
1638 if (old_var_sheet != NULL)
1639 gtk_widget_destroy (old_var_sheet);
1640 gtk_container_add (GTK_CONTAINER (vars_scroller), GTK_WIDGET (var_sheet));
1641 gtk_widget_show (GTK_WIDGET (var_sheet));
1643 gtk_widget_destroy (GTK_WIDGET (ia->formats.data_tree_view));
1644 ia->formats.data_tree_view = create_data_tree_view (
1646 GTK_CONTAINER (get_widget_assert (ia->asst.builder, "data-scroller")),
1649 pop_watch_cursor (ia);
1652 /* Clears the set of user-modified variables from IA's formats
1653 substructure. This discards user modifications to variable
1654 formats, thereby causing formats to revert to their
1657 clear_modified_vars (struct import_assistant *ia)
1659 struct formats_page *p = &ia->formats;
1662 for (i = 0; i < p->modified_var_cnt; i++)
1663 var_destroy (p->modified_vars[i]);
1664 free (p->modified_vars);
1665 p->modified_vars = NULL;
1666 p->modified_var_cnt = 0;
1669 /* Resets the formats page to its defaults, discarding user
1672 reset_formats_page (struct import_assistant *ia)
1674 clear_modified_vars (ia);
1675 prepare_formats_page (ia);
1678 /* Called when the user changes one of the variables in the
1681 on_variable_change (PsppireDict *dict, int dict_idx,
1682 struct import_assistant *ia)
1684 struct formats_page *p = &ia->formats;
1685 GtkTreeView *tv = ia->formats.data_tree_view;
1686 gint column_idx = dict_idx + 1;
1688 push_watch_cursor (ia);
1690 /* Remove previous column and replace with new column. */
1691 gtk_tree_view_remove_column (tv, gtk_tree_view_get_column (tv, column_idx));
1692 gtk_tree_view_insert_column (tv, make_data_column (ia, tv, false, dict_idx),
1695 /* Save a copy of the modified variable in modified_vars, so
1696 that its attributes will be preserved if we back up to the
1697 previous page with the Prev button and then come back
1699 if (dict_idx >= p->modified_var_cnt)
1702 p->modified_vars = xnrealloc (p->modified_vars, dict_idx + 1,
1703 sizeof *p->modified_vars);
1704 for (i = 0; i <= dict_idx; i++)
1705 p->modified_vars[i] = NULL;
1706 p->modified_var_cnt = dict_idx + 1;
1708 if (p->modified_vars[dict_idx])
1709 var_destroy (p->modified_vars[dict_idx]);
1710 p->modified_vars[dict_idx]
1711 = var_clone (psppire_dict_get_variable (dict, dict_idx));
1713 pop_watch_cursor (ia);
1716 /* Parses the contents of the field at (ROW,COLUMN) according to
1717 its variable format. If OUTPUTP is non-null, then *OUTPUTP
1718 receives the formatted output for that field (which must be
1719 freed with free). If TOOLTIPP is non-null, then *TOOLTIPP
1720 receives a message suitable for use in a tooltip, if one is
1721 needed, or a null pointer otherwise. Returns true if a
1722 tooltip message is needed, otherwise false. */
1724 parse_field (struct import_assistant *ia,
1725 size_t row, size_t column,
1726 char **outputp, char **tooltipp)
1728 struct substring field;
1730 struct variable *var;
1731 const struct fmt_spec *in;
1732 struct fmt_spec out;
1736 field = ia->separators.columns[column].contents[row];
1737 var = dict_get_var (ia->formats.dict, column);
1738 val = value_create (var_get_width (var));
1739 in = var_get_print_format (var);
1740 out = fmt_for_output_from_input (in);
1742 if (field.string != NULL)
1745 if (!data_in (field, LEGACY_NATIVE, in->type, 0, 0, 0,
1746 val, var_get_width (var)))
1748 char fmt_string[FMT_STRING_LEN_MAX + 1];
1749 fmt_to_string (in, fmt_string);
1750 tooltip = xasprintf (_("Field content \"%.*s\" cannot be parsed in "
1752 (int) field.length, field.string,
1759 tooltip = xstrdup (_("This input line has too few separators "
1760 "to fill in this field."));
1761 value_set_missing (val, var_get_width (var));
1763 if (outputp != NULL)
1765 char *output = xmalloc (out.w + 1);
1766 data_out (val, &out, output);
1767 output[out.w] = '\0';
1772 ok = tooltip == NULL;
1773 if (tooltipp != NULL)
1774 *tooltipp = tooltip;
1780 /* Called to render one of the cells in the data preview tree
1783 render_output_cell (GtkTreeViewColumn *tree_column,
1784 GtkCellRenderer *cell,
1785 GtkTreeModel *model,
1789 struct import_assistant *ia = ia_;
1791 GValue gvalue = { 0, };
1794 ok = parse_field (ia,
1795 (text_import_model_iter_to_row (iter)
1796 + ia->first_line.skip_lines),
1797 GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1801 g_value_init (&gvalue, G_TYPE_STRING);
1802 g_value_take_string (&gvalue, output);
1803 g_object_set_property (G_OBJECT (cell), "text", &gvalue);
1804 g_value_unset (&gvalue);
1807 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1810 "background", "red",
1811 "background-set", TRUE,
1815 /* Called to render a tooltip for one of the cells in the data
1816 preview tree view. */
1818 on_query_output_tooltip (GtkWidget *widget, gint wx, gint wy,
1819 gboolean keyboard_mode UNUSED,
1820 GtkTooltip *tooltip, struct import_assistant *ia)
1825 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1828 if (parse_field (ia, row, column, NULL, &text))
1831 gtk_tooltip_set_text (tooltip, text);
1836 /* Utility functions used by multiple pages of the assistant. */
1839 get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
1840 const struct import_assistant *ia,
1841 size_t *row, size_t *column)
1843 GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
1847 GtkTreeViewColumn *tree_column;
1848 GtkTreeModel *tree_model;
1851 /* Check that WIDGET is really visible on the screen before we
1852 do anything else. This is a bug fix for a sticky situation:
1853 when text_data_import_assistant() returns, it frees the data
1854 necessary to compose the tool tip message, but there may be
1855 a tool tip under preparation at that point (even if there is
1856 no visible tool tip) that will call back into us a little
1857 bit later. Perhaps the correct solution to this problem is
1858 to make the data related to the tool tips part of a GObject
1859 that only gets destroyed when all references are released,
1860 but this solution appears to be effective too. */
1861 if (!GTK_WIDGET_MAPPED (widget))
1864 gtk_tree_view_convert_widget_to_bin_window_coords (tree_view,
1866 if (!gtk_tree_view_get_path_at_pos (tree_view, bx, by,
1867 &path, &tree_column, NULL, NULL))
1870 *column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1873 tree_model = gtk_tree_view_get_model (tree_view);
1874 ok = gtk_tree_model_get_iter (tree_model, &iter, path);
1875 gtk_tree_path_free (path);
1879 *row = text_import_model_iter_to_row (&iter) + ia->first_line.skip_lines;
1884 make_tree_view (const struct import_assistant *ia,
1886 GtkTreeView **tree_view)
1888 GtkTreeModel *model;
1890 *tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
1891 model = GTK_TREE_MODEL (text_import_model_new (
1892 ia->file.lines + first_line,
1893 ia->file.line_cnt - first_line, first_line));
1894 gtk_tree_view_set_model (*tree_view, model);
1896 add_line_number_column (ia, *tree_view);
1900 add_line_number_column (const struct import_assistant *ia,
1901 GtkTreeView *treeview)
1903 GtkTreeViewColumn *column;
1905 column = gtk_tree_view_column_new_with_attributes (
1906 "Line", ia->asst.prop_renderer,
1907 "text", TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER,
1909 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
1910 gtk_tree_view_column_set_fixed_width (
1911 column, get_monospace_width (treeview, ia->asst.prop_renderer, 5));
1912 gtk_tree_view_append_column (treeview, column);
1916 get_monospace_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
1923 ds_put_char_multiple (&s, '0', char_cnt);
1924 ds_put_char (&s, ' ');
1925 width = get_string_width (treeview, renderer, ds_cstr (&s));
1932 get_string_width (GtkTreeView *treeview, GtkCellRenderer *renderer,
1936 g_object_set (G_OBJECT (renderer), "text", string, (void *) NULL);
1937 gtk_cell_renderer_get_size (renderer, GTK_WIDGET (treeview),
1938 NULL, NULL, NULL, &width, NULL);
1942 static GtkTreeViewColumn *
1943 make_data_column (struct import_assistant *ia, GtkTreeView *tree_view,
1944 bool input, gint dict_idx)
1946 struct variable *var = NULL;
1947 struct column *column = NULL;
1948 char name[(VAR_NAME_LEN * 2) + 1];
1950 gint content_width, header_width;
1951 GtkTreeViewColumn *tree_column;
1954 column = &ia->separators.columns[dict_idx];
1956 var = dict_get_var (ia->formats.dict, dict_idx);
1958 escape_underscores (input ? column->name : var_get_name (var), name);
1959 char_cnt = input ? column->width : var_get_print_format (var)->w;
1960 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
1962 header_width = get_string_width (tree_view, ia->asst.prop_renderer,
1965 tree_column = gtk_tree_view_column_new ();
1966 g_object_set_data (G_OBJECT (tree_column), "column-number",
1967 GINT_TO_POINTER (dict_idx));
1968 gtk_tree_view_column_set_title (tree_column, name);
1969 gtk_tree_view_column_pack_start (tree_column, ia->asst.fixed_renderer,
1971 gtk_tree_view_column_set_cell_data_func (
1972 tree_column, ia->asst.fixed_renderer,
1973 input ? render_input_cell : render_output_cell, ia, NULL);
1974 gtk_tree_view_column_set_sizing (tree_column, GTK_TREE_VIEW_COLUMN_FIXED);
1975 gtk_tree_view_column_set_fixed_width (tree_column, MAX (content_width,
1981 static GtkTreeView *
1982 create_data_tree_view (bool input, GtkContainer *parent,
1983 struct import_assistant *ia)
1985 GtkTreeView *tree_view;
1988 make_tree_view (ia, ia->first_line.skip_lines, &tree_view);
1989 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (tree_view),
1990 GTK_SELECTION_NONE);
1992 for (i = 0; i < ia->separators.column_cnt; i++)
1993 gtk_tree_view_append_column (tree_view,
1994 make_data_column (ia, tree_view, input, i));
1996 g_object_set (G_OBJECT (tree_view), "has-tooltip", TRUE, (void *) NULL);
1997 g_signal_connect (tree_view, "query-tooltip",
1998 G_CALLBACK (input ? on_query_input_tooltip
1999 : on_query_output_tooltip), ia);
2000 gtk_tree_view_set_fixed_height_mode (tree_view, true);
2002 gtk_container_add (parent, GTK_WIDGET (tree_view));
2003 gtk_widget_show (GTK_WIDGET (tree_view));
2009 escape_underscores (const char *in, char *out)
2011 for (; *in != '\0'; in++)
2020 /* TextImportModel, a GtkTreeModel implementation used by some
2021 pages of the assistant. */
2023 #define G_TYPE_TEXT_IMPORT_MODEL (text_import_model_get_type ())
2024 #define TEXT_IMPORT_MODEL(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), G_TYPE_TEXT_IMPORT_MODEL, TextImportModel))
2025 #define TEXT_IMPORT_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), G_TYPE_TEXT_IMPORT_MODEL, TextImportModelClass))
2026 #define IS_TEXT_IMPORT_MODEL(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), G_TYPE_TEXT_IMPORT_MODEL))
2027 #define IS_TEXT_IMPORT_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), G_TYPE_TEXT_IMPORT_MODEL))
2028 #define TEXT_IMPORT_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_TEXT_IMPORT_MODEL, TextImportModelClass))
2030 /* Random number used in 'stamp' member of GtkTreeIter. */
2031 #define TREE_MODEL_STAMP 0x7efd67d3
2033 struct TextImportModel
2036 struct string *lines;
2041 struct TextImportModelClass
2043 GObjectClass parent_class;
2046 GType text_import_model_get_type (void);
2047 static void text_import_model_tree_model_init (gpointer iface, gpointer data);
2050 text_import_model_get_type (void)
2052 static GType object_type = 0;
2056 static const GTypeInfo object_info = {
2057 sizeof (TextImportModelClass),
2058 (GBaseInitFunc) NULL,
2059 (GBaseFinalizeFunc) NULL,
2060 NULL, /* class_init */
2061 NULL, /* class_finalize */
2062 NULL, /* class_data */
2063 sizeof (TextImportModel),
2064 0, /* n_preallocs */
2065 NULL, /* instance_init */
2068 static const GInterfaceInfo tree_model_info = {
2069 text_import_model_tree_model_init,
2074 object_type = g_type_register_static (G_TYPE_OBJECT,
2078 g_type_add_interface_static (object_type, GTK_TYPE_TREE_MODEL,
2088 /* Creates and returns a new TextImportModel that contains the
2089 LINE_CNT lines in LINES. The lines before FIRST_LINE in LINES
2090 are not part of the model, but they are included in the line
2091 numbers in the TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER column.
2093 The caller retains responsibility for freeing LINES and must
2094 ensure that its lifetime and that of the strings that it
2095 contains exceeds that of the TextImportModel. */
2097 text_import_model_new (struct string *lines, size_t line_cnt,
2100 TextImportModel *new_text_import_model
2101 = g_object_new (G_TYPE_TEXT_IMPORT_MODEL, NULL);
2102 new_text_import_model->lines = lines;
2103 new_text_import_model->line_cnt = line_cnt;
2104 new_text_import_model->first_line = first_line;
2105 return new_text_import_model;
2110 tree_model_iter_has_child (GtkTreeModel *tree_model,
2117 tree_model_iter_parent (GtkTreeModel *tree_model,
2124 static GtkTreeModelFlags
2125 tree_model_get_flags (GtkTreeModel *model)
2127 g_return_val_if_fail (IS_TEXT_IMPORT_MODEL (model), (GtkTreeModelFlags) 0);
2129 return GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST;
2134 tree_model_n_columns (GtkTreeModel *model)
2140 tree_model_column_type (GtkTreeModel *model, gint index)
2142 return (index == TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER ? G_TYPE_INT
2143 : index == TEXT_IMPORT_MODEL_COLUMN_LINE ? G_TYPE_STRING
2148 init_iter (TextImportModel *list, gint idx, GtkTreeIter *iter)
2150 if (idx < 0 || idx >= list->line_cnt)
2153 iter->user_data = GINT_TO_POINTER (-1);
2158 iter->stamp = TREE_MODEL_STAMP;
2159 iter->user_data = GINT_TO_POINTER (idx);
2165 tree_model_get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path)
2167 gint *indices, depth;
2169 TextImportModel *list = TEXT_IMPORT_MODEL (model);
2171 g_return_val_if_fail (path, FALSE);
2173 indices = gtk_tree_path_get_indices (path);
2174 depth = gtk_tree_path_get_depth (path);
2176 g_return_val_if_fail (depth == 1, FALSE);
2178 return init_iter (list, indices[0], iter);
2183 tree_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter)
2185 TextImportModel *list = TEXT_IMPORT_MODEL (model);
2188 assert (iter->stamp == TREE_MODEL_STAMP);
2190 idx = GPOINTER_TO_INT (iter->user_data);
2191 return init_iter (list, idx == -1 ? -1 : idx + 1, iter);
2194 static GtkTreePath *
2195 tree_model_get_path (GtkTreeModel *model, GtkTreeIter *iter)
2199 g_return_val_if_fail (iter->stamp == TREE_MODEL_STAMP, FALSE);
2201 path = gtk_tree_path_new ();
2202 gtk_tree_path_append_index (path, GPOINTER_TO_INT (iter->user_data));
2208 tree_model_get_value (GtkTreeModel *model, GtkTreeIter *iter,
2209 gint column, GValue *value)
2211 TextImportModel *list = TEXT_IMPORT_MODEL (model);
2214 g_return_if_fail (iter->stamp == TREE_MODEL_STAMP);
2216 idx = GPOINTER_TO_INT (iter->user_data);
2217 assert (idx >= 0 && idx < list->line_cnt);
2221 g_value_init (value, G_TYPE_INT);
2222 g_value_set_int (value, idx + list->first_line + 1);
2226 g_value_init (value, G_TYPE_STRING);
2227 g_value_set_static_string (value, ds_cstr (&list->lines[idx]));
2232 tree_model_iter_children (GtkTreeModel *tree_model,
2234 GtkTreeIter *parent)
2240 tree_model_n_children (GtkTreeModel *model, GtkTreeIter *iter)
2242 TextImportModel *list = TEXT_IMPORT_MODEL (model);
2244 return iter == NULL ? list->line_cnt : 0;
2248 tree_model_nth_child (GtkTreeModel *model, GtkTreeIter *iter,
2249 GtkTreeIter *parent, gint n)
2251 TextImportModel *list = TEXT_IMPORT_MODEL (model);
2252 g_return_val_if_fail (IS_TEXT_IMPORT_MODEL (model), FALSE);
2256 return init_iter (list, n, iter);
2260 text_import_model_tree_model_init (gpointer iface_, gpointer data UNUSED)
2262 GtkTreeModelIface *iface = (GtkTreeModelIface *) iface_;
2264 iface->get_flags = tree_model_get_flags;
2265 iface->get_n_columns = tree_model_n_columns;
2266 iface->get_column_type = tree_model_column_type;
2267 iface->get_iter = tree_model_get_iter;
2268 iface->iter_next = tree_model_iter_next;
2269 iface->get_path = tree_model_get_path;
2270 iface->get_value = tree_model_get_value;
2272 iface->iter_children = tree_model_iter_children;
2273 iface->iter_has_child = tree_model_iter_has_child;
2274 iface->iter_n_children = tree_model_n_children;
2275 iface->iter_nth_child = tree_model_nth_child;
2276 iface->iter_parent = tree_model_iter_parent;
2280 text_import_model_iter_to_row (const GtkTreeIter *iter)
2282 assert (iter->stamp == TREE_MODEL_STAMP);
2283 return GPOINTER_TO_INT (iter->user_data);
2286 /* Increments the "watch cursor" level, setting the cursor for
2287 the assistant window to a watch face to indicate to the user
2288 that the ongoing operation may take some time. */
2290 push_watch_cursor (struct import_assistant *ia)
2292 if (++ia->asst.watch_cursor == 1)
2294 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2295 GdkDisplay *display = gtk_widget_get_display (widget);
2296 GdkCursor *cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
2297 gdk_window_set_cursor (widget->window, cursor);
2298 gdk_cursor_unref (cursor);
2299 gdk_display_flush (display);
2303 /* Decrements the "watch cursor" level. If the level reaches
2304 zero, the cursor is reset to its default shape. */
2306 pop_watch_cursor (struct import_assistant *ia)
2308 if (--ia->asst.watch_cursor == 0)
2310 GtkWidget *widget = GTK_WIDGET (ia->asst.assistant);
2311 gdk_window_set_cursor (widget->window, NULL);