1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2008 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 <gtksheet/gtksheet.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/data-editor.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/syntax-editor.h>
54 #define _(msgid) gettext (msgid)
55 #define N_(msgid) msgid
58 #if !GTK_CHECK_VERSION (2, 10, 0)
61 text_data_import_assistant (GObject *o, gpointer de_)
63 struct data_editor *de = de_;
66 gtk_message_dialog_new (de->parent.window,
70 _("The text import assistant has not been "
71 "compiled into this build of PSPPIRE, because "
72 "GTK+ version 2.10.0 or later was not available."));
74 gtk_dialog_run (GTK_DIALOG (dialog));
76 gtk_widget_destroy (dialog);
81 /* TextImportModel, a GtkTreeModel used by the text data import
85 TEXT_IMPORT_MODEL_COLUMN_LINE_NUMBER, /* 1-based line number in file */
86 TEXT_IMPORT_MODEL_COLUMN_LINE, /* The line from the file. */
88 typedef struct TextImportModel TextImportModel;
89 typedef struct TextImportModelClass TextImportModelClass;
91 TextImportModel *text_import_model_new (struct string *lines, size_t line_cnt,
93 gint text_import_model_iter_to_row (const GtkTreeIter *);
95 struct import_assistant;
97 /* The file to be imported. */
100 char *file_name; /* File name. */
101 unsigned long int total_lines; /* Number of lines in file. */
102 bool total_is_exact; /* Is total_lines exact (or an estimate)? */
104 /* The first several lines of the file. */
105 struct string *lines;
108 static bool init_file (struct import_assistant *, GtkWindow *parent);
109 static void destroy_file (struct import_assistant *);
111 /* The main body of the GTK+ assistant and related data. */
115 GtkAssistant *assistant;
116 GMainLoop *main_loop;
117 GtkWidget *paste_button;
118 GtkWidget *reset_button;
122 GtkCellRenderer *prop_renderer;
123 GtkCellRenderer *fixed_renderer;
125 static void init_assistant (struct import_assistant *, GtkWindow *);
126 static void destroy_assistant (struct import_assistant *);
127 static GtkWidget *add_page_to_assistant (struct import_assistant *,
129 GtkAssistantPageType);
131 /* The introduction page of the assistant. */
135 GtkWidget *all_cases_button;
136 GtkWidget *n_cases_button;
137 GtkWidget *n_cases_spin;
138 GtkWidget *percent_button;
139 GtkWidget *percent_spin;
141 static void init_intro_page (struct import_assistant *);
142 static void reset_intro_page (struct import_assistant *);
144 /* Page where the user chooses the first line of data. */
145 struct first_line_page
147 int skip_lines; /* Number of initial lines to skip? */
148 bool variable_names; /* Variable names above first line of data? */
151 GtkTreeView *tree_view;
152 GtkWidget *variable_names_cb;
154 static void init_first_line_page (struct import_assistant *);
155 static void reset_first_line_page (struct import_assistant *);
157 /* Page where the user chooses field separators. */
158 struct separators_page
160 /* How to break lines into columns. */
161 struct string separators; /* Field separators. */
162 struct string quotes; /* Quote characters. */
163 bool escape; /* Doubled quotes yield a quote mark? */
165 /* The columns produced thereby. */
166 struct column *columns; /* Information about each column. */
167 size_t column_cnt; /* Number of columns. */
170 GtkWidget *custom_cb;
171 GtkWidget *custom_entry;
173 GtkWidget *quote_combo;
174 GtkEntry *quote_entry;
175 GtkWidget *escape_cb;
176 GtkTreeView *fields_tree_view;
178 /* The columns that the separators divide the data into. */
181 /* Variable name for this column. This is the variable name
182 used on the separators page; it can be overridden by the
183 user on the formats page. */
186 /* Maximum length of any row in this column. */
189 /* Contents of this column: contents[row] is the contents for
192 A null substring indicates a missing column for that row
193 (because the line contains an insufficient number of
196 contents[] elements may be substrings of the lines[]
197 strings that represent the whole lines of the file, to
198 save memory. Other elements are dynamically allocated
199 with ss_alloc_substring. */
200 struct substring *contents;
202 static void init_separators_page (struct import_assistant *);
203 static void destroy_separators_page (struct import_assistant *);
204 static void prepare_separators_page (struct import_assistant *);
205 static void reset_separators_page (struct import_assistant *);
207 /* Page where the user verifies and adjusts input formats. */
210 struct dictionary *dict;
213 GtkTreeView *data_tree_view;
214 PsppireDict *psppire_dict;
215 struct variable **modified_vars;
216 size_t modified_var_cnt;
218 static void init_formats_page (struct import_assistant *);
219 static void destroy_formats_page (struct import_assistant *);
220 static void prepare_formats_page (struct import_assistant *);
221 static void reset_formats_page (struct import_assistant *);
223 struct import_assistant
226 struct assistant asst;
227 struct intro_page intro;
228 struct first_line_page first_line;
229 struct separators_page separators;
230 struct formats_page formats;
233 static void apply_dict (const struct dictionary *, struct string *);
234 static char *generate_syntax (const struct import_assistant *);
236 static gboolean get_tooltip_location (GtkWidget *widget, gint wx, gint wy,
237 const struct import_assistant *,
238 size_t *row, size_t *column);
239 static void make_tree_view (const struct import_assistant *ia,
241 GtkTreeView **tree_view);
242 static void add_line_number_column (const struct import_assistant *,
244 static gint get_monospace_width (GtkTreeView *, GtkCellRenderer *,
246 static gint get_string_width (GtkTreeView *, GtkCellRenderer *,
248 static GtkTreeViewColumn *make_data_column (struct import_assistant *,
249 GtkTreeView *, bool input,
251 static GtkTreeView *create_data_tree_view (bool input, GtkContainer *parent,
252 struct import_assistant *);
253 static void escape_underscores (const char *in, char *out);
254 static void push_watch_cursor (struct import_assistant *);
255 static void pop_watch_cursor (struct import_assistant *);
257 /* Pops up the Text Data Import assistant. */
259 text_data_import_assistant (GObject *o, gpointer de_)
261 struct data_editor *de = de_;
262 GtkWindow *parent_window = de->parent.window;
263 struct import_assistant *ia;
265 ia = xzalloc (sizeof *ia);
266 if (!init_file (ia, parent_window))
272 init_assistant (ia, parent_window);
273 init_intro_page (ia);
274 init_first_line_page (ia);
275 init_separators_page (ia);
276 init_formats_page (ia);
278 gtk_widget_show_all (GTK_WIDGET (ia->asst.assistant));
280 ia->asst.main_loop = g_main_loop_new (NULL, false);
281 g_main_loop_run (ia->asst.main_loop);
282 g_main_loop_unref (ia->asst.main_loop);
284 switch (ia->asst.response)
286 case GTK_RESPONSE_APPLY:
288 char *syntax = generate_syntax (ia);
289 execute_syntax (create_syntax_string_source (syntax));
293 case PSPPIRE_RESPONSE_PASTE:
295 char *syntax = generate_syntax (ia);
296 struct syntax_editor *se =
297 (struct syntax_editor *) window_create (WINDOW_SYNTAX, NULL);
298 gtk_text_buffer_insert_at_cursor (se->buffer, syntax, -1);
306 destroy_formats_page (ia);
307 destroy_separators_page (ia);
308 destroy_assistant (ia);
313 /* Emits PSPP syntax to S that applies the dictionary attributes
314 (such as missing values and value labels) of the variables in
317 apply_dict (const struct dictionary *dict, struct string *s)
319 size_t var_cnt = dict_get_var_cnt (dict);
322 for (i = 0; i < var_cnt; i++)
324 struct variable *var = dict_get_var (dict, i);
325 const char *name = var_get_name (var);
326 enum val_type type = var_get_type (var);
327 int width = var_get_width (var);
328 enum measure measure = var_get_measure (var);
329 enum alignment alignment = var_get_alignment (var);
330 const struct fmt_spec *format = var_get_print_format (var);
332 if (var_has_missing_values (var))
334 const struct missing_values *mv = var_get_missing_values (var);
337 syntax_gen_pspp (s, "MISSING VALUES %ss (", name);
338 for (j = 0; j < mv_n_values (mv); j++)
342 ds_put_cstr (s, ", ");
343 mv_get_value (mv, &value, j);
344 syntax_gen_value (s, &value, width, format);
347 if (mv_has_range (mv))
350 if (mv_has_value (mv))
351 ds_put_cstr (s, ", ");
352 mv_get_range (mv, &low, &high);
353 syntax_gen_num_range (s, low, high, format);
355 ds_put_cstr (s, ").\n");
357 if (var_has_value_labels (var))
359 const struct val_labs *vls = var_get_value_labels (var);
360 struct val_labs_iterator *iter;
363 syntax_gen_pspp (s, "VALUE LABELS %ss", name);
364 for (vl = val_labs_first_sorted (vls, &iter); vl != NULL;
365 vl = val_labs_next (vls, &iter))
367 ds_put_cstr (s, "\n ");
368 syntax_gen_value (s, &vl->value, width, format);
369 ds_put_char (s, ' ');
370 syntax_gen_string (s, ss_cstr (vl->label));
372 ds_put_cstr (s, ".\n");
374 if (var_has_label (var))
375 syntax_gen_pspp (s, "VARIABLE LABELS %ss %sq.\n",
376 name, var_get_label (var));
377 if (measure != var_default_measure (type))
378 syntax_gen_pspp (s, "VARIABLE LEVEL %ss (%ss).\n",
380 (measure == MEASURE_NOMINAL ? "NOMINAL"
381 : measure == MEASURE_ORDINAL ? "ORDINAL"
383 if (alignment != var_default_alignment (type))
384 syntax_gen_pspp (s, "VARIABLE ALIGNMENT %ss (%ss).\n",
386 (alignment == ALIGN_LEFT ? "LEFT"
387 : alignment == ALIGN_CENTRE ? "CENTER"
389 if (var_get_display_width (var) != var_default_display_width (width))
390 syntax_gen_pspp (s, "VARIABLE WIDTH %ss (%d).\n",
391 name, var_get_display_width (var));
395 /* Generates and returns PSPP syntax to execute the import
396 operation described by IA. The caller must free the syntax
399 generate_syntax (const struct import_assistant *ia)
401 struct string s = DS_EMPTY_INITIALIZER;
410 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
411 ia->intro.n_cases_button)))
412 ds_put_format (&s, " /IMPORTCASES=FIRST %d\n",
413 gtk_spin_button_get_value_as_int (
414 GTK_SPIN_BUTTON (ia->intro.n_cases_spin)));
415 else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (
416 ia->intro.percent_button)))
417 ds_put_format (&s, " /IMPORTCASES=PERCENT %d\n",
418 gtk_spin_button_get_value_as_int (
419 GTK_SPIN_BUTTON (ia->intro.percent_spin)));
421 ds_put_cstr (&s, " /IMPORTCASES=ALL\n");
423 " /ARRANGEMENT=DELIMITED\n"
425 if (ia->first_line.skip_lines > 0)
426 ds_put_format (&s, " /FIRSTCASE=%d\n", ia->first_line.skip_lines + 1);
427 ds_put_cstr (&s, " /DELIMITERS=\"");
428 if (ds_find_char (&ia->separators.separators, '\t') != SIZE_MAX)
429 ds_put_cstr (&s, "\\t");
430 if (ds_find_char (&ia->separators.separators, '\\') != SIZE_MAX)
431 ds_put_cstr (&s, "\\\\");
432 for (i = 0; i < ds_length (&ia->separators.separators); i++)
434 char c = ds_at (&ia->separators.separators, i);
436 ds_put_cstr (&s, "\"\"");
437 else if (c != '\t' && c != '\\')
440 ds_put_cstr (&s, "\"\n");
441 if (!ds_is_empty (&ia->separators.quotes))
442 syntax_gen_pspp (&s, " /QUALIFIER=%sq\n", ds_cstr (&ia->separators.quotes));
443 if (!ds_is_empty (&ia->separators.quotes) && ia->separators.escape)
444 ds_put_cstr (&s, " /ESCAPE\n");
445 ds_put_cstr (&s, " /VARIABLES=\n");
447 var_cnt = dict_get_var_cnt (ia->formats.dict);
448 for (i = 0; i < var_cnt; i++)
450 struct variable *var = dict_get_var (ia->formats.dict, i);
451 char format_string[FMT_STRING_LEN_MAX + 1];
452 fmt_to_string (var_get_print_format (var), format_string);
453 ds_put_format (&s, " %s %s%s\n",
454 var_get_name (var), format_string,
455 i == var_cnt - 1 ? "." : "");
458 apply_dict (ia->formats.dict, &s);
463 /* Choosing a file and reading it. */
465 static char *choose_file (GtkWindow *parent_window);
467 /* Obtains the file to import from the user and initializes IA's
468 file substructure. PARENT_WINDOW must be the window to use
469 as the file chooser window's parent.
471 Returns true if successful, false if the file name could not
472 be obtained or the file could not be read. */
474 init_file (struct import_assistant *ia, GtkWindow *parent_window)
476 struct file *file = &ia->file;
477 enum { MAX_PREVIEW_LINES = 1000 }; /* Max number of lines to read. */
478 enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */
481 file->file_name = choose_file (parent_window);
482 if (file->file_name == NULL)
485 stream = fopen (file->file_name, "r");
488 msg (ME, _("Could not open \"%s\": %s"),
489 file->file_name, strerror (errno));
493 file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines);
494 for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++)
496 struct string *line = &file->lines[file->line_cnt];
498 ds_init_empty (line);
499 if (!ds_read_line (line, stream, MAX_LINE_LEN))
503 else if (ferror (stream))
504 msg (ME, _("Error reading \"%s\": %s"),
505 file->file_name, strerror (errno));
507 msg (ME, _("Failed to read \"%s\", because it contains a line "
508 "over %d bytes long and therefore appears not to be "
510 file->file_name, MAX_LINE_LEN);
515 ds_chomp (line, '\n');
516 ds_chomp (line, '\r');
519 if (file->line_cnt == 0)
521 msg (ME, _("\"%s\" is empty."), file->file_name);
527 /* Estimate the number of lines in the file. */
528 if (file->line_cnt < MAX_PREVIEW_LINES)
529 file->total_lines = file->line_cnt;
533 off_t position = ftello (stream);
534 if (fstat (fileno (stream), &s) == 0 && position > 0)
535 file->total_lines = (double) file->line_cnt / position * s.st_size;
537 file->total_lines = 0;
543 /* Frees IA's file substructure. */
545 destroy_file (struct import_assistant *ia)
547 struct file *f = &ia->file;
550 for (i = 0; i < f->line_cnt; i++)
551 ds_destroy (&f->lines[i]);
553 g_free (f->file_name);
556 /* Obtains the file to read from the user and returns the name of
557 the file as a string that must be freed with g_free if
558 successful, otherwise a null pointer. PARENT_WINDOW must be
559 the window to use as the file chooser window's parent. */
561 choose_file (GtkWindow *parent_window)
566 dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"),
568 GTK_FILE_CHOOSER_ACTION_OPEN,
569 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
570 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
573 switch (gtk_dialog_run (GTK_DIALOG (dialog)))
575 case GTK_RESPONSE_ACCEPT:
576 file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
582 gtk_widget_destroy (dialog);
589 static void close_assistant (struct import_assistant *, int response);
590 static void on_prepare (GtkAssistant *assistant, GtkWidget *page,
591 struct import_assistant *);
592 static void on_cancel (GtkAssistant *assistant, struct import_assistant *);
593 static void on_close (GtkAssistant *assistant, struct import_assistant *);
594 static void on_paste (GtkButton *button, struct import_assistant *);
595 static void on_reset (GtkButton *button, struct import_assistant *);
596 static void close_assistant (struct import_assistant *, int response);
598 /* Initializes IA's asst substructure. PARENT_WINDOW must be the
599 window to use as the assistant window's parent. */
601 init_assistant (struct import_assistant *ia, GtkWindow *parent_window)
603 struct assistant *a = &ia->asst;
605 a->xml = XML_NEW ("text-data-import.glade");
606 a->assistant = GTK_ASSISTANT (gtk_assistant_new ());
607 g_signal_connect (a->assistant, "prepare", G_CALLBACK (on_prepare), ia);
608 g_signal_connect (a->assistant, "cancel", G_CALLBACK (on_cancel), ia);
609 g_signal_connect (a->assistant, "close", G_CALLBACK (on_close), ia);
610 a->paste_button = gtk_button_new_from_stock (GTK_STOCK_PASTE);
611 gtk_assistant_add_action_widget (a->assistant, a->paste_button);
612 g_signal_connect (a->paste_button, "clicked", G_CALLBACK (on_paste), ia);
613 a->reset_button = gtk_button_new_from_stock ("pspp-stock-reset");
614 gtk_assistant_add_action_widget (a->assistant, a->reset_button);
615 g_signal_connect (a->reset_button, "clicked", G_CALLBACK (on_reset), ia);
616 gtk_window_set_title (GTK_WINDOW (a->assistant),
617 _("Importing Delimited Text Data"));
618 gtk_window_set_transient_for (GTK_WINDOW (a->assistant), parent_window);
620 a->prop_renderer = gtk_cell_renderer_text_new ();
621 g_object_ref_sink (a->prop_renderer);
622 a->fixed_renderer = gtk_cell_renderer_text_new ();
623 g_object_ref_sink (a->fixed_renderer);
624 g_object_set (G_OBJECT (a->fixed_renderer),
625 "family", "Monospace",
629 /* Frees IA's asst substructure. */
631 destroy_assistant (struct import_assistant *ia)
633 struct assistant *a = &ia->asst;
635 g_object_unref (a->prop_renderer);
636 g_object_unref (a->fixed_renderer);
637 g_object_unref (a->xml);
640 /* Appends a page of the given TYPE, with PAGE as its content, to
641 the GtkAssistant encapsulated by IA. Returns the GtkWidget
642 that represents the page. */
644 add_page_to_assistant (struct import_assistant *ia,
645 GtkWidget *page, GtkAssistantPageType type)
651 title = gtk_window_get_title (GTK_WINDOW (page));
652 title_copy = xstrdup (title ? title : "");
654 content = gtk_bin_get_child (GTK_BIN (page));
656 g_object_ref (content);
657 gtk_container_remove (GTK_CONTAINER (page), content);
659 gtk_widget_destroy (page);
661 gtk_assistant_append_page (ia->asst.assistant, content);
662 gtk_assistant_set_page_type (ia->asst.assistant, content, type);
663 gtk_assistant_set_page_title (ia->asst.assistant, content, title_copy);
664 gtk_assistant_set_page_complete (ia->asst.assistant, content, true);
671 /* Called just before PAGE is displayed as the current page of
672 ASSISTANT, this updates IA content according to the new
675 on_prepare (GtkAssistant *assistant, GtkWidget *page,
676 struct import_assistant *ia)
678 if (page == ia->separators.page)
679 prepare_separators_page (ia);
680 else if (page == ia->formats.page)
681 prepare_formats_page (ia);
683 gtk_widget_show (ia->asst.reset_button);
684 if (page == ia->formats.page)
685 gtk_widget_show (ia->asst.paste_button);
687 gtk_widget_hide (ia->asst.paste_button);
690 /* Called when the Cancel button in the assistant is clicked. */
692 on_cancel (GtkAssistant *assistant, struct import_assistant *ia)
694 close_assistant (ia, GTK_RESPONSE_CANCEL);
697 /* Called when the Apply button on the last page of the assistant
700 on_close (GtkAssistant *assistant, struct import_assistant *ia)
702 close_assistant (ia, GTK_RESPONSE_APPLY);
705 /* Called when the Paste button on the last page of the assistant
708 on_paste (GtkButton *button, struct import_assistant *ia)
710 close_assistant (ia, PSPPIRE_RESPONSE_PASTE);
713 /* Called when the Reset button is clicked. */
715 on_reset (GtkButton *button, struct import_assistant *ia)
717 gint page_num = gtk_assistant_get_current_page (ia->asst.assistant);
718 GtkWidget *page = gtk_assistant_get_nth_page (ia->asst.assistant, page_num);
720 if (page == ia->intro.page)
721 reset_intro_page (ia);
722 else if (page == ia->first_line.page)
723 reset_first_line_page (ia);
724 else if (page == ia->separators.page)
725 reset_separators_page (ia);
726 else if (page == ia->formats.page)
727 reset_formats_page (ia);
730 /* Causes the assistant to close, returning RESPONSE for
731 interpretation by text_data_import_assistant. */
733 close_assistant (struct import_assistant *ia, int response)
735 ia->asst.response = response;
736 g_main_loop_quit (ia->asst.main_loop);
737 gtk_widget_hide (GTK_WIDGET (ia->asst.assistant));
740 /* The "intro" page of the assistant. */
742 static void on_intro_amount_changed (GtkToggleButton *button,
743 struct import_assistant *);
745 /* Initializes IA's intro substructure. */
747 init_intro_page (struct import_assistant *ia)
749 GladeXML *xml = ia->asst.xml;
750 struct intro_page *p = &ia->intro;
753 p->page = add_page_to_assistant (ia, get_widget_assert (xml, "Intro"),
754 GTK_ASSISTANT_PAGE_INTRO);
755 p->all_cases_button = get_widget_assert (xml, "import-all-cases");
756 p->n_cases_button = get_widget_assert (xml, "import-n-cases");
757 p->n_cases_spin = get_widget_assert (xml, "n-cases-spin");
758 p->percent_button = get_widget_assert (xml, "import-percent");
759 p->percent_spin = get_widget_assert (xml, "percent-spin");
760 g_signal_connect (p->all_cases_button, "toggled",
761 G_CALLBACK (on_intro_amount_changed), ia);
762 g_signal_connect (p->n_cases_button, "toggled",
763 G_CALLBACK (on_intro_amount_changed), ia);
764 g_signal_connect (p->percent_button, "toggled",
765 G_CALLBACK (on_intro_amount_changed), ia);
768 ds_put_cstr (&s, _("This assistant will guide you through the process of "
769 "importing data into PSPP from a text file with one line "
770 "per case, in which fields are separated by tabs, "
771 "commas, or other delimiters.\n\n"));
772 if (ia->file.total_is_exact)
774 &s, ngettext ("The selected file contains %zu line of text. ",
775 "The selected file contains %zu lines of text. ",
778 else if (ia->file.total_lines > 0)
782 "The selected file contains approximately %lu line of text. ",
783 "The selected file contains approximately %lu lines of text. ",
784 ia->file.total_lines),
785 ia->file.total_lines);
788 "Only the first %zu line of the file will be shown for "
789 "preview purposes in the following screens. ",
790 "Only the first %zu lines of the file will be shown for "
791 "preview purposes in the following screens. ",
795 ds_put_cstr (&s, _("You may choose below how much of the file should "
796 "actually be imported."));
797 gtk_label_set_text (GTK_LABEL (get_widget_assert (xml, "intro-label")),
802 /* Resets IA's intro page to its initial state. */
804 reset_intro_page (struct import_assistant *ia)
806 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->intro.all_cases_button),
810 /* Called when one of the radio buttons is clicked. */
812 on_intro_amount_changed (GtkToggleButton *button UNUSED,
813 struct import_assistant *ia)
815 struct intro_page *p = &ia->intro;
817 gtk_widget_set_sensitive (p->n_cases_spin,
818 gtk_toggle_button_get_active (
819 GTK_TOGGLE_BUTTON (p->n_cases_button)));
821 gtk_widget_set_sensitive (ia->intro.percent_spin,
822 gtk_toggle_button_get_active (
823 GTK_TOGGLE_BUTTON (p->percent_button)));
826 /* The "first line" page of the assistant. */
828 static GtkTreeView *create_lines_tree_view (GtkContainer *parent_window,
829 struct import_assistant *);
830 static void on_first_line_change (GtkTreeSelection *,
831 struct import_assistant *);
832 static void on_variable_names_cb_toggle (GtkToggleButton *,
833 struct import_assistant *);
834 static void set_first_line (struct import_assistant *);
835 static void get_first_line (struct import_assistant *);
837 /* Initializes IA's first_line substructure. */
839 init_first_line_page (struct import_assistant *ia)
841 struct first_line_page *p = &ia->first_line;
842 GladeXML *xml = ia->asst.xml;
844 p->page = add_page_to_assistant (ia, get_widget_assert (xml, "FirstLine"),
845 GTK_ASSISTANT_PAGE_CONTENT);
846 gtk_widget_destroy (get_widget_assert (xml, "first-line"));
847 p->tree_view = create_lines_tree_view (
848 GTK_CONTAINER (get_widget_assert (xml, "first-line-scroller")), ia);
849 p->variable_names_cb = get_widget_assert (xml, "variable-names");
850 gtk_tree_selection_set_mode (
851 gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
852 GTK_SELECTION_BROWSE);
854 g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)),
855 "changed", G_CALLBACK (on_first_line_change), ia);
856 g_signal_connect (p->variable_names_cb, "toggled",
857 G_CALLBACK (on_variable_names_cb_toggle), ia);
860 /* Resets the first_line page to its initial content. */
862 reset_first_line_page (struct import_assistant *ia)
864 ia->first_line.skip_lines = 0;
865 ia->first_line.variable_names = false;
869 /* Creates and returns a tree view that contains each of the
870 lines in IA's file as a row. */
872 create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia)
874 GtkTreeView *tree_view;
875 GtkTreeViewColumn *column;
876 size_t max_line_length;
877 gint content_width, header_width;
880 make_tree_view (ia, 0, &tree_view);
882 column = gtk_tree_view_column_new_with_attributes (
883 "Text", ia->asst.fixed_renderer,
884 "text", TEXT_IMPORT_MODEL_COLUMN_LINE,
886 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
889 for (i = 0; i < ia->file.line_cnt; i++)
891 size_t w = ds_length (&ia->file.lines[i]);
892 max_line_length = MAX (max_line_length, w);
895 content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer,
897 header_width = get_string_width (tree_view, ia->asst.prop_renderer, "Text");
898 gtk_tree_view_column_set_fixed_width (column, MAX (content_width,
900 gtk_tree_view_append_column (tree_view, column);
902 gtk_tree_view_set_fixed_height_mode (tree_view, true);
904 gtk_container_add (parent, GTK_WIDGET (tree_view));
905 gtk_widget_show (GTK_WIDGET (tree_view));
910 /* Called when the line selected in the first_line tree view
913 on_first_line_change (GtkTreeSelection *selection UNUSED,
914 struct import_assistant *ia)
919 /* Called when the checkbox that indicates whether variable
920 names are in the row above the first line is toggled. */
922 on_variable_names_cb_toggle (GtkToggleButton *variable_names_cb UNUSED,
923 struct import_assistant *ia)
928 /* Sets the widgets to match IA's first_line substructure. */
930 set_first_line (struct import_assistant *ia)
934 path = gtk_tree_path_new_from_indices (ia->first_line.skip_lines, -1);
935 gtk_tree_view_set_cursor (GTK_TREE_VIEW (ia->first_line.tree_view),
937 gtk_tree_path_free (path);
939 gtk_toggle_button_set_active (
940 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb),
941 ia->first_line.variable_names);
942 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
943 ia->first_line.skip_lines > 0);
946 /* Sets IA's first_line substructure to match the widgets. */
948 get_first_line (struct import_assistant *ia)
950 GtkTreeSelection *selection;
954 selection = gtk_tree_view_get_selection (ia->first_line.tree_view);
955 if (gtk_tree_selection_get_selected (selection, &model, &iter))
957 GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
958 int row = gtk_tree_path_get_indices (path)[0];
959 gtk_tree_path_free (path);
961 ia->first_line.skip_lines = row;
962 ia->first_line.variable_names =
963 (ia->first_line.skip_lines > 0
964 && gtk_toggle_button_get_active (
965 GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb)));
967 gtk_widget_set_sensitive (ia->first_line.variable_names_cb,
968 ia->first_line.skip_lines > 0);
971 /* The "separators" page of the assistant. */
973 static void revise_fields_preview (struct import_assistant *ia);
974 static void choose_likely_separators (struct import_assistant *ia);
975 static void find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1],
976 const char *targets, const char *def,
977 struct string *result);
978 static void clear_fields (struct import_assistant *ia);
979 static void revise_fields_preview (struct import_assistant *);
980 static void set_separators (struct import_assistant *);
981 static void get_separators (struct import_assistant *);
982 static void on_separators_custom_entry_notify (GObject *UNUSED,
984 struct import_assistant *);
985 static void on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
986 struct import_assistant *);
987 static void on_quote_combo_change (GtkComboBox *combo,
988 struct import_assistant *);
989 static void on_quote_cb_toggle (GtkToggleButton *quote_cb,
990 struct import_assistant *);
991 static void on_separator_toggle (GtkToggleButton *, struct import_assistant *);
992 static void render_input_cell (GtkTreeViewColumn *tree_column,
993 GtkCellRenderer *cell,
994 GtkTreeModel *model, GtkTreeIter *iter,
996 static gboolean on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
997 gboolean keyboard_mode UNUSED,
999 struct import_assistant *);
1001 /* A common field separator and its identifying name. */
1004 const char *name; /* Name (for use with get_widget_assert). */
1005 int c; /* Separator character. */
1008 /* All the separators in the dialog box. */
1009 static const struct separator separators[] =
1021 #define SEPARATOR_CNT (sizeof separators / sizeof *separators)
1023 /* Initializes IA's separators substructure. */
1025 init_separators_page (struct import_assistant *ia)
1027 GladeXML *xml = ia->asst.xml;
1028 struct separators_page *p = &ia->separators;
1031 choose_likely_separators (ia);
1033 p->page = add_page_to_assistant (ia, get_widget_assert (xml, "Separators"),
1034 GTK_ASSISTANT_PAGE_CONTENT);
1035 p->custom_cb = get_widget_assert (xml, "custom-cb");
1036 p->custom_entry = get_widget_assert (xml, "custom-entry");
1037 p->quote_combo = get_widget_assert (xml, "quote-combo");
1038 p->quote_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (p->quote_combo)));
1039 p->quote_cb = get_widget_assert (xml, "quote-cb");
1040 p->escape_cb = get_widget_assert (xml, "escape");
1042 set_separators (ia);
1043 p->fields_tree_view = GTK_TREE_VIEW (get_widget_assert (xml, "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 (xml, 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.xml, "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.xml, 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);
1375 gtk_entry_set_text (s->quote_entry,
1376 any_quotes ? ds_cstr (&s->quotes) : "\"");
1377 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->quote_cb),
1379 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->escape_cb),
1381 gtk_widget_set_sensitive (s->quote_combo, any_quotes);
1382 gtk_widget_set_sensitive (s->escape_cb, any_quotes);
1385 /* Sets IA's separators substructure to match the widgets. */
1387 get_separators (struct import_assistant *ia)
1389 struct separators_page *s = &ia->separators;
1392 ds_clear (&s->separators);
1393 for (i = 0; i < SEPARATOR_CNT; i++)
1395 const struct separator *sep = &separators[i];
1396 GtkWidget *button = get_widget_assert (ia->asst.xml, sep->name);
1397 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)))
1398 ds_put_char (&s->separators, sep->c);
1401 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->custom_cb)))
1402 ds_put_cstr (&s->separators,
1403 gtk_entry_get_text (GTK_ENTRY (s->custom_entry)));
1405 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->quote_cb)))
1407 gchar *text = gtk_combo_box_get_active_text (
1408 GTK_COMBO_BOX (s->quote_combo));
1409 ds_assign_cstr (&s->quotes, text);
1413 ds_clear (&s->quotes);
1414 s->escape = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->escape_cb));
1417 /* Called when the user changes the entry field for custom
1420 on_separators_custom_entry_notify (GObject *gobject UNUSED,
1421 GParamSpec *arg1 UNUSED,
1422 struct import_assistant *ia)
1424 revise_fields_preview (ia);
1427 /* Called when the user toggles the checkbox that enables custom
1430 on_separators_custom_cb_toggle (GtkToggleButton *custom_cb,
1431 struct import_assistant *ia)
1433 bool is_active = gtk_toggle_button_get_active (custom_cb);
1434 gtk_widget_set_sensitive (ia->separators.custom_entry, is_active);
1435 revise_fields_preview (ia);
1438 /* Called when the user changes the selection in the combo box
1439 that selects a quote character. */
1441 on_quote_combo_change (GtkComboBox *combo, struct import_assistant *ia)
1443 revise_fields_preview (ia);
1446 /* Called when the user toggles the checkbox that enables
1449 on_quote_cb_toggle (GtkToggleButton *quote_cb, struct import_assistant *ia)
1451 bool is_active = gtk_toggle_button_get_active (quote_cb);
1452 gtk_widget_set_sensitive (ia->separators.quote_combo, is_active);
1453 gtk_widget_set_sensitive (ia->separators.escape_cb, is_active);
1454 revise_fields_preview (ia);
1457 /* Called when the user toggles one of the separators
1460 on_separator_toggle (GtkToggleButton *toggle UNUSED,
1461 struct import_assistant *ia)
1463 revise_fields_preview (ia);
1466 /* Called to render one of the cells in the fields preview tree
1469 render_input_cell (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
1470 GtkTreeModel *model, GtkTreeIter *iter,
1473 struct import_assistant *ia = ia_;
1474 struct substring field;
1478 column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column),
1480 row = text_import_model_iter_to_row (iter) + ia->first_line.skip_lines;
1481 field = ia->separators.columns[column].contents[row];
1482 if (field.string != NULL)
1484 GValue text = {0, };
1485 g_value_init (&text, G_TYPE_STRING);
1486 g_value_take_string (&text, ss_xstrdup (field));
1487 g_object_set_property (G_OBJECT (cell), "text", &text);
1488 g_value_unset (&text);
1489 g_object_set (cell, "background-set", FALSE, (void *) NULL);
1494 "background", "red",
1495 "background-set", TRUE,
1499 /* Called to render a tooltip on one of the cells in the fields
1500 preview tree view. */
1502 on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy,
1503 gboolean keyboard_mode UNUSED,
1504 GtkTooltip *tooltip, struct import_assistant *ia)
1508 if (!get_tooltip_location (widget, wx, wy, ia, &row, &column))
1511 if (ia->separators.columns[column].contents[row].string != NULL)
1514 gtk_tooltip_set_text (tooltip,
1515 _("This input line has too few separators "
1516 "to fill in this field."));
1520 /* The "formats" page of the assistant. */
1522 static void on_variable_change (PsppireDict *dict, int idx,
1523 struct import_assistant *);
1524 static void clear_modified_vars (struct import_assistant *);
1526 /* Initializes IA's formats substructure. */
1528 init_formats_page (struct import_assistant *ia)
1530 GladeXML *xml = ia->asst.xml;
1531 struct formats_page *p = &ia->formats;
1533 p->page = add_page_to_assistant (ia, get_widget_assert (xml, "Formats"),
1534 GTK_ASSISTANT_PAGE_CONFIRM);
1535 p->data_tree_view = GTK_TREE_VIEW (get_widget_assert (xml, "data"));
1536 p->modified_vars = NULL;
1537 p->modified_var_cnt = 0;
1540 /* Frees IA's formats substructure. */
1542 destroy_formats_page (struct import_assistant *ia)
1544 struct formats_page *p = &ia->formats;
1546 if (p->psppire_dict != NULL)
1548 /* This destroys p->dict also. */
1549 g_object_unref (p->psppire_dict);
1551 clear_modified_vars (ia);
1554 /* Called just before the formats page of the assistant is
1557 prepare_formats_page (struct import_assistant *ia)
1559 struct dictionary *dict;
1560 PsppireDict *psppire_dict;
1561 PsppireVarStore *var_store;
1562 GtkBin *vars_scroller;
1563 GtkWidget *old_var_sheet;
1564 PsppireVarSheet *var_sheet;
1565 struct separators_page *s = &ia->separators;
1566 struct formats_page *p = &ia->formats;
1567 struct fmt_guesser *fg;
1568 unsigned long int number = 0;
1571 push_watch_cursor (ia);
1573 dict = dict_create ();
1574 fg = fmt_guesser_create ();
1575 for (column_idx = 0; column_idx < s->column_cnt; column_idx++)
1577 struct variable *modified_var;
1578 char name[VAR_NAME_LEN + 1];
1580 modified_var = (column_idx < p->modified_var_cnt
1581 ? p->modified_vars[column_idx] : NULL);
1582 if (modified_var == NULL)
1584 struct column *column = &s->columns[column_idx];
1585 struct variable *var;
1586 struct fmt_spec format;
1589 /* Choose variable name. */
1590 if (!dict_make_unique_var_name (dict, column->name, &number, name))
1593 /* Choose variable format. */
1594 fmt_guesser_clear (fg);
1595 for (row = ia->first_line.skip_lines; row < ia->file.line_cnt; row++)
1596 fmt_guesser_add (fg, column->contents[row]);
1597 fmt_guesser_guess (fg, &format);
1598 fmt_fix_input (&format);
1600 /* Create variable. */
1601 var = dict_create_var_assert (dict, name, fmt_var_width (&format));
1602 var_set_both_formats (var, &format);
1606 if (!dict_make_unique_var_name (dict, var_get_name (modified_var),
1609 dict_clone_var_assert (dict, modified_var, name);
1612 fmt_guesser_destroy (fg);
1614 psppire_dict = psppire_dict_new_from_dict (dict);
1615 g_signal_connect (psppire_dict, "variable_changed",
1616 G_CALLBACK (on_variable_change), ia);
1617 ia->formats.dict = dict;
1618 ia->formats.psppire_dict = psppire_dict;
1620 /* XXX: PsppireVarStore doesn't hold a reference to
1621 psppire_dict for now, but it should. After it does, we
1622 should g_object_ref the psppire_dict here, since we also
1623 hold a reference via ia->formats.dict. */
1624 var_store = psppire_var_store_new (psppire_dict);
1625 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.xml, "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.xml, "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);