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,
1631 "row-geometry", var_store,
1633 "may-create-vars", FALSE,
1636 vars_scroller = GTK_BIN (get_widget_assert (ia->asst.xml, "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.xml, "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 GdkCursor *cursor = gdk_cursor_new (GDK_WATCH);
2296 gdk_window_set_cursor (widget->window, cursor);
2297 gdk_cursor_unref (cursor);
2298 gdk_display_flush (gtk_widget_get_display (widget));
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);