From: John Darrington Date: Wed, 20 Mar 2013 17:28:44 +0000 (+0100) Subject: Reorganised the text-data import assistant into separate files for each page X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cf10258e2e58c352c555dd2579d2fe9f5b2fce50;p=pspp Reorganised the text-data import assistant into separate files for each page This will hopefully make it easier to add new functionality. Reviewed-by: Ben Pfaff --- diff --git a/src/ui/gui/automake.mk b/src/ui/gui/automake.mk index d7c3401d7b..e29e30bb71 100644 --- a/src/ui/gui/automake.mk +++ b/src/ui/gui/automake.mk @@ -318,6 +318,16 @@ src_ui_gui_psppire_SOURCES = \ src/ui/gui/select-cases-dialog.h \ src/ui/gui/split-file-dialog.c \ src/ui/gui/split-file-dialog.h \ + src/ui/gui/page-assistant.c \ + src/ui/gui/page-intro.c \ + src/ui/gui/page-intro.h \ + src/ui/gui/page-file.c \ + src/ui/gui/page-first-line.c \ + src/ui/gui/page-first-line.h \ + src/ui/gui/page-formats.c \ + src/ui/gui/page-formats.h \ + src/ui/gui/page-separators.c \ + src/ui/gui/page-separators.h \ src/ui/gui/text-data-import-dialog.c \ src/ui/gui/text-data-import-dialog.h \ src/ui/gui/transpose-dialog.c \ diff --git a/src/ui/gui/data-editor.ui b/src/ui/gui/data-editor.ui index da3a4a5e59..2a52894779 100644 --- a/src/ui/gui/data-editor.ui +++ b/src/ui/gui/data-editor.ui @@ -49,9 +49,9 @@ - - file_import-text - _Import Delimited Text Data... + + file_import + I_mport Data... gtk-convert @@ -565,7 +565,7 @@ - + diff --git a/src/ui/gui/page-assistant.c b/src/ui/gui/page-assistant.c new file mode 100644 index 0000000000..88314734e2 --- /dev/null +++ b/src/ui/gui/page-assistant.c @@ -0,0 +1,247 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include "ui/gui/text-data-import-dialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "data/data-in.h" +#include "data/data-out.h" +#include "data/format-guesser.h" +#include "data/value-labels.h" +#include "language/data-io/data-parser.h" +#include "language/lexer/lexer.h" +#include "libpspp/assertion.h" +#include "libpspp/i18n.h" +#include "libpspp/line-reader.h" +#include "libpspp/message.h" +#include "ui/gui/checkbox-treeview.h" +#include "ui/gui/dialog-common.h" +#include "ui/gui/executor.h" +#include "ui/gui/helper.h" +#include "ui/gui/builder-wrapper.h" +#include "ui/gui/psppire-data-window.h" +#include "ui/gui/psppire-dialog.h" +#include "ui/gui/psppire-encoding-selector.h" +#include "ui/gui/psppire-empty-list-store.h" +#include "ui/gui/psppire-var-sheet.h" +#include "ui/gui/psppire-var-store.h" + +#include "gl/error.h" +#include "gl/intprops.h" +#include "gl/xalloc.h" + +#include "gettext.h" +#define _(msgid) gettext (msgid) +#define N_(msgid) msgid + +/* Assistant. */ + +static void close_assistant (struct import_assistant *, int response); +static void on_prepare (GtkAssistant *assistant, GtkWidget *page, + struct import_assistant *); +static void on_cancel (GtkAssistant *assistant, struct import_assistant *); +static void on_close (GtkAssistant *assistant, struct import_assistant *); +static void on_paste (GtkButton *button, struct import_assistant *); +static void on_reset (GtkButton *button, struct import_assistant *); + +/* Initializes IA's asst substructure. PARENT_WINDOW must be the + window to use as the assistant window's parent. */ +struct import_assistant * +init_assistant (GtkWindow *parent_window) +{ + struct import_assistant *ia = xzalloc (sizeof *ia); + struct assistant *a = &ia->asst; + + a->builder = builder_new ("text-data-import.ui"); + a->assistant = GTK_ASSISTANT (gtk_assistant_new ()); + + a->prop_renderer = gtk_cell_renderer_text_new (); + g_object_ref_sink (a->prop_renderer); + a->fixed_renderer = gtk_cell_renderer_text_new (); + g_object_ref_sink (a->fixed_renderer); + g_object_set (G_OBJECT (a->fixed_renderer), + "family", "Monospace", + (void *) NULL); + + g_signal_connect (a->assistant, "prepare", G_CALLBACK (on_prepare), ia); + g_signal_connect (a->assistant, "cancel", G_CALLBACK (on_cancel), ia); + g_signal_connect (a->assistant, "close", G_CALLBACK (on_close), ia); + a->paste_button = gtk_button_new_from_stock (GTK_STOCK_PASTE); + gtk_assistant_add_action_widget (a->assistant, a->paste_button); + g_signal_connect (a->paste_button, "clicked", G_CALLBACK (on_paste), ia); + a->reset_button = gtk_button_new_from_stock ("pspp-stock-reset"); + gtk_assistant_add_action_widget (a->assistant, a->reset_button); + g_signal_connect (a->reset_button, "clicked", G_CALLBACK (on_reset), ia); + gtk_window_set_title (GTK_WINDOW (a->assistant), + _("Importing Delimited Text Data")); + gtk_window_set_transient_for (GTK_WINDOW (a->assistant), parent_window); + gtk_window_set_icon_name (GTK_WINDOW (a->assistant), "pspp"); + + + return ia; +} + +/* Frees IA's asst substructure. */ +void +destroy_assistant (struct import_assistant *ia) +{ + struct assistant *a = &ia->asst; + + g_object_unref (a->prop_renderer); + g_object_unref (a->fixed_renderer); + g_object_unref (a->builder); +} + +/* Appends a page of the given TYPE, with PAGE as its content, to + the GtkAssistant encapsulated by IA. Returns the GtkWidget + that represents the page. */ +GtkWidget * +add_page_to_assistant (struct import_assistant *ia, + GtkWidget *page, GtkAssistantPageType type) +{ + const char *title; + char *title_copy; + GtkWidget *content; + + title = gtk_window_get_title (GTK_WINDOW (page)); + title_copy = xstrdup (title ? title : ""); + + content = gtk_bin_get_child (GTK_BIN (page)); + assert (content); + g_object_ref (content); + gtk_container_remove (GTK_CONTAINER (page), content); + + gtk_widget_destroy (page); + + gtk_assistant_append_page (ia->asst.assistant, content); + gtk_assistant_set_page_type (ia->asst.assistant, content, type); + gtk_assistant_set_page_title (ia->asst.assistant, content, title_copy); + gtk_assistant_set_page_complete (ia->asst.assistant, content, true); + + free (title_copy); + + return content; +} + +/* Called just before PAGE is displayed as the current page of + ASSISTANT, this updates IA content according to the new + page. */ +static void +on_prepare (GtkAssistant *assistant, GtkWidget *page, + struct import_assistant *ia) +{ + int pn = gtk_assistant_get_current_page (assistant); + + gtk_widget_show (ia->asst.reset_button); + gtk_widget_hide (ia->asst.paste_button); + + if ( ia->spreadsheet) + { + if (pn == 0) + { + } + else if (pn == 1) + { + prepare_formats_page (ia); + } + } + else + { + if (pn == 0) + prepare_separators_page (ia); + else if (pn == 3) + prepare_formats_page (ia); + } + + + if (gtk_assistant_get_page_type (assistant, page) + == GTK_ASSISTANT_PAGE_CONFIRM) + gtk_widget_grab_focus (assistant->apply); + else + gtk_widget_grab_focus (assistant->forward); +} + +/* Called when the Cancel button in the assistant is clicked. */ +static void +on_cancel (GtkAssistant *assistant, struct import_assistant *ia) +{ + close_assistant (ia, GTK_RESPONSE_CANCEL); +} + +/* Called when the Apply button on the last page of the assistant + is clicked. */ +static void +on_close (GtkAssistant *assistant, struct import_assistant *ia) +{ + close_assistant (ia, GTK_RESPONSE_APPLY); +} + +/* Called when the Paste button on the last page of the assistant + is clicked. */ +static void +on_paste (GtkButton *button, struct import_assistant *ia) +{ + close_assistant (ia, PSPPIRE_RESPONSE_PASTE); +} + +static GtkWidget * +assist_get_page (struct assist_page *ap) +{ + if (ap == NULL) + return NULL; + + return ap->page; +} + +/* Called when the Reset button is clicked. */ +static void +on_reset (GtkButton *button, struct import_assistant *ia) +{ + gint page_num = gtk_assistant_get_current_page (ia->asst.assistant); + GtkWidget *page = gtk_assistant_get_nth_page (ia->asst.assistant, page_num); + + if (page == assist_get_page ((struct assist_page *) ia->intro)) + reset_intro_page (ia); + else if (page == assist_get_page ((struct assist_page *) ia->first_line)) + reset_first_line_page (ia); + else if (page == assist_get_page ((struct assist_page *) ia->separators)) + reset_separators_page (ia); + else if (page == assist_get_page ((struct assist_page *) ia->formats)) + reset_formats_page (ia); +} + +/* Causes the assistant to close, returning RESPONSE for + interpretation by text_data_import_assistant. */ +static void +close_assistant (struct import_assistant *ia, int response) +{ + ia->asst.response = response; + /* Use our loop_done variable until we find out + why g_main_loop_quit (ia->asst.main_loop); doesn't work. + */ + ia->asst.loop_done = true; + gtk_widget_hide (GTK_WIDGET (ia->asst.assistant)); +} + diff --git a/src/ui/gui/page-file.c b/src/ui/gui/page-file.c new file mode 100644 index 0000000000..a65b210538 --- /dev/null +++ b/src/ui/gui/page-file.c @@ -0,0 +1,266 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include "ui/gui/text-data-import-dialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "data/data-in.h" +#include "data/data-out.h" +#include "data/format-guesser.h" +#include "data/casereader.h" +#include "data/gnumeric-reader.h" +#include "data/ods-reader.h" +#include "data/spreadsheet-reader.h" +#include "data/value-labels.h" +#include "language/data-io/data-parser.h" +#include "language/lexer/lexer.h" +#include "libpspp/assertion.h" +#include "libpspp/i18n.h" +#include "libpspp/line-reader.h" +#include "libpspp/message.h" +#include "ui/gui/checkbox-treeview.h" +#include "ui/gui/dialog-common.h" +#include "ui/gui/executor.h" +#include "ui/gui/helper.h" +#include "ui/gui/builder-wrapper.h" +#include "ui/gui/psppire-data-window.h" +#include "ui/gui/psppire-dialog.h" +#include "ui/gui/psppire-encoding-selector.h" +#include "ui/gui/psppire-empty-list-store.h" +#include "ui/gui/psppire-var-sheet.h" +#include "ui/gui/psppire-var-store.h" +#include "ui/gui/psppire-scanf.h" +#include "ui/syntax-gen.h" + +#include "gl/error.h" +#include "gl/intprops.h" +#include "gl/xalloc.h" + +#include "gettext.h" +#define _(msgid) gettext (msgid) +#define N_(msgid) msgid + +struct import_assistant; + +/* Choose a file */ +static char *choose_file (GtkWindow *parent_window, gchar **encodingp); + + + + +/* Obtains the file to import from the user and initializes IA's + file substructure. PARENT_WINDOW must be the window to use + as the file chooser window's parent. + + Returns true if successful, false if the file name could not + be obtained or the file could not be read. */ +bool +init_file (struct import_assistant *ia, GtkWindow *parent_window) +{ + enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */ + struct file *file = &ia->file; + struct spreadsheet_read_options opts; + + file->lines = NULL; + file->file_name = choose_file (parent_window, &file->encoding); + if (file->file_name == NULL) + return false; + + opts.sheet_name = NULL; + opts.cell_range = NULL; + opts.sheet_index = 1; + + opts.read_names = true; + opts.asw = -1; + + + if (ia->spreadsheet == NULL) + ia->spreadsheet = gnumeric_probe (file->file_name, false); + + if (ia->spreadsheet == NULL) + ia->spreadsheet = ods_probe (file->file_name, false); + + if (ia->spreadsheet == NULL) + { + struct string input; + struct line_reader *reader = line_reader_for_file (file->encoding, file->file_name, O_RDONLY); + if (reader == NULL) + { + msg (ME, _("Could not open `%s': %s"), + file->file_name, strerror (errno)); + return false; + } + + ds_init_empty (&input); + file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines); + for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++) + { + ds_clear (&input); + if (!line_reader_read (reader, &input, MAX_LINE_LEN + 1) + || ds_length (&input) > MAX_LINE_LEN) + { + if (line_reader_eof (reader)) + break; + else if (line_reader_error (reader)) + msg (ME, _("Error reading `%s': %s"), + file->file_name, strerror (line_reader_error (reader))); + else + msg (ME, _("Failed to read `%s', because it contains a line " + "over %d bytes long and therefore appears not to be " + "a text file."), + file->file_name, MAX_LINE_LEN); + line_reader_close (reader); + destroy_file (ia); + ds_destroy (&input); + return false; + } + + ds_init_cstr (&file->lines[file->line_cnt], + recode_string ("UTF-8", line_reader_get_encoding (reader), + ds_cstr (&input), ds_length (&input))); + } + ds_destroy (&input); + + if (file->line_cnt == 0) + { + msg (ME, _("`%s' is empty."), file->file_name); + line_reader_close (reader); + destroy_file (ia); + return false; + } + + /* Estimate the number of lines in the file. */ + if (file->line_cnt < MAX_PREVIEW_LINES) + file->total_lines = file->line_cnt; + else + { + struct stat s; + off_t position = line_reader_tell (reader); + if (fstat (line_reader_fileno (reader), &s) == 0 && position > 0) + file->total_lines = (double) file->line_cnt / position * s.st_size; + else + file->total_lines = 0; + } + + line_reader_close (reader); + } + + return true; +} + +/* Frees IA's file substructure. */ +void +destroy_file (struct import_assistant *ia) +{ + struct file *f = &ia->file; + size_t i; + + if (f->lines) + { + for (i = 0; i < f->line_cnt; i++) + ds_destroy (&f->lines[i]); + free (f->lines); + } + + g_free (f->file_name); + g_free (f->encoding); +} + +/* Obtains the file to read from the user. If successful, returns the name of + the file and stores the user's chosen encoding for the file into *ENCODINGP. + The caller must free each of these strings with g_free(). + + On failure, stores a null pointer and stores NULL in *ENCODINGP. + + PARENT_WINDOW must be the window to use as the file chooser window's + parent. */ +static char * +choose_file (GtkWindow *parent_window, gchar **encodingp) +{ + char *file_name; + GtkFileFilter *filter = NULL; + + GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"), + parent_window, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + + g_object_set (dialog, "local-only", FALSE, NULL); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("Text files")); + gtk_file_filter_add_mime_type (filter, "text/*"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("Text (*.txt) Files")); + gtk_file_filter_add_pattern (filter, "*.txt"); + gtk_file_filter_add_pattern (filter, "*.TXT"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files")); + gtk_file_filter_add_mime_type (filter, "text/plain"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("Comma Separated Value Files")); + gtk_file_filter_add_mime_type (filter, "text/csv"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + /* I've never encountered one of these, but it's listed here: + http://www.iana.org/assignments/media-types/text/tab-separated-values */ + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("Tab Separated Value Files")); + gtk_file_filter_add_mime_type (filter, "text/tab-separated-values"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + + gtk_file_chooser_set_extra_widget ( + GTK_FILE_CHOOSER (dialog), psppire_encoding_selector_new ("Auto", true)); + + filter = gtk_file_filter_new (); + gtk_file_filter_set_name (filter, _("All Files")); + gtk_file_filter_add_pattern (filter, "*"); + gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); + + switch (gtk_dialog_run (GTK_DIALOG (dialog))) + { + case GTK_RESPONSE_ACCEPT: + file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + *encodingp = psppire_encoding_selector_get_encoding ( + gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (dialog))); + break; + default: + file_name = NULL; + *encodingp = NULL; + break; + } + gtk_widget_destroy (dialog); + + return file_name; +} diff --git a/src/ui/gui/page-first-line.c b/src/ui/gui/page-first-line.c new file mode 100644 index 0000000000..d3191e78ca --- /dev/null +++ b/src/ui/gui/page-first-line.c @@ -0,0 +1,244 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include "page-first-line.h" + +#include "ui/gui/text-data-import-dialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "data/data-in.h" +#include "data/data-out.h" +#include "data/format-guesser.h" +#include "data/value-labels.h" +#include "language/data-io/data-parser.h" +#include "language/lexer/lexer.h" +#include "libpspp/assertion.h" +#include "libpspp/i18n.h" +#include "libpspp/line-reader.h" +#include "libpspp/message.h" +#include "ui/gui/checkbox-treeview.h" +#include "ui/gui/dialog-common.h" +#include "ui/gui/executor.h" +#include "ui/gui/helper.h" +#include "ui/gui/builder-wrapper.h" +#include "ui/gui/psppire-data-window.h" +#include "ui/gui/psppire-dialog.h" +#include "ui/gui/psppire-encoding-selector.h" +#include "ui/gui/psppire-empty-list-store.h" +#include "ui/gui/psppire-var-sheet.h" +#include "ui/gui/psppire-var-store.h" +#include "ui/gui/psppire-scanf.h" + +#include "gl/error.h" +#include "gl/intprops.h" +#include "gl/xalloc.h" + +#include "gettext.h" +#define _(msgid) gettext (msgid) +#define N_(msgid) msgid + + + +/* The "first line" page of the assistant. */ + +/* Page where the user chooses the first line of data. */ +struct first_line_page + { + GtkWidget *page; + GtkTreeView *tree_view; + GtkWidget *variable_names_cb; + }; + +static GtkTreeView *create_lines_tree_view (GtkContainer *parent_window, + struct import_assistant *); +static void on_first_line_change (GtkTreeSelection *, + struct import_assistant *); +static void on_variable_names_cb_toggle (GtkToggleButton *, + struct import_assistant *); +static void set_first_line (struct import_assistant *); +static void get_first_line (struct import_assistant *); + +/* Initializes IA's first_line substructure. */ +struct first_line_page * +first_line_page_create (struct import_assistant *ia) +{ + struct first_line_page *p = xzalloc (sizeof *p); + + GtkBuilder *builder = ia->asst.builder; + + p->page = add_page_to_assistant (ia, get_widget_assert (builder, "FirstLine"), + GTK_ASSISTANT_PAGE_CONTENT); + + gtk_widget_destroy (get_widget_assert (builder, "first-line")); + p->tree_view = create_lines_tree_view ( + GTK_CONTAINER (get_widget_assert (builder, "first-line-scroller")), ia); + p->variable_names_cb = get_widget_assert (builder, "variable-names"); + gtk_tree_selection_set_mode ( + gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)), + GTK_SELECTION_BROWSE); + g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)), + "changed", G_CALLBACK (on_first_line_change), ia); + g_signal_connect (p->variable_names_cb, "toggled", + G_CALLBACK (on_variable_names_cb_toggle), ia); + return p; +} + +/* Resets the first_line page to its initial content. */ +void +reset_first_line_page (struct import_assistant *ia) +{ + ia->skip_lines = 0; + ia->variable_names = false; + set_first_line (ia); +} + +static void +render_line (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer data) +{ + gint row = empty_list_store_iter_to_row (iter); + struct string *lines; + + lines = g_object_get_data (G_OBJECT (tree_model), "lines"); + g_return_if_fail (lines != NULL); + + g_object_set (cell, "text", ds_cstr (&lines[row]), NULL); +} + + +/* Creates and returns a tree view that contains each of the + lines in IA's file as a row. */ +static GtkTreeView * +create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia) +{ + GtkTreeView *tree_view = NULL; + GtkTreeViewColumn *column; + size_t max_line_length; + gint content_width, header_width; + size_t i; + const gchar *title = _("Text"); + + make_tree_view (ia, 0, &tree_view); + + column = gtk_tree_view_column_new_with_attributes ( + title, ia->asst.fixed_renderer, (void *) NULL); + gtk_tree_view_column_set_cell_data_func (column, ia->asst.fixed_renderer, + render_line, NULL, NULL); + gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED); + + max_line_length = 0; + for (i = 0; i < ia->file.line_cnt; i++) + { + size_t w = ds_length (&ia->file.lines[i]); + max_line_length = MAX (max_line_length, w); + } + + content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer, + max_line_length); + header_width = get_string_width (tree_view, ia->asst.prop_renderer, title); + gtk_tree_view_column_set_fixed_width (column, MAX (content_width, + header_width)); + gtk_tree_view_append_column (tree_view, column); + + gtk_tree_view_set_fixed_height_mode (tree_view, true); + + gtk_container_add (parent, GTK_WIDGET (tree_view)); + gtk_widget_show (GTK_WIDGET (tree_view)); + + return tree_view; +} + +/* Called when the line selected in the first_line tree view + changes. */ +static void +on_first_line_change (GtkTreeSelection *selection UNUSED, + struct import_assistant *ia) +{ + get_first_line (ia); +} + +/* Called when the checkbox that indicates whether variable + names are in the row above the first line is toggled. */ +static void +on_variable_names_cb_toggle (GtkToggleButton *variable_names_cb UNUSED, + struct import_assistant *ia) +{ + get_first_line (ia); +} + +/* Sets the widgets to match IA's first_line substructure. */ +static void +set_first_line (struct import_assistant *ia) +{ + GtkTreePath *path; + + path = gtk_tree_path_new_from_indices (ia->skip_lines, -1); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (ia->first_line->tree_view), + path, NULL, false); + gtk_tree_path_free (path); + + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (ia->first_line->variable_names_cb), + ia->variable_names); + gtk_widget_set_sensitive (ia->first_line->variable_names_cb, + ia->skip_lines > 0); +} + +/* Sets IA's first_line substructure to match the widgets. */ +static void +get_first_line (struct import_assistant *ia) +{ + GtkTreeSelection *selection; + GtkTreeIter iter; + GtkTreeModel *model; + + selection = gtk_tree_view_get_selection (ia->first_line->tree_view); + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + GtkTreePath *path = gtk_tree_model_get_path (model, &iter); + int row = gtk_tree_path_get_indices (path)[0]; + gtk_tree_path_free (path); + + ia->skip_lines = row; + ia->variable_names = + (ia->skip_lines > 0 + && gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (ia->first_line->variable_names_cb))); + } + gtk_widget_set_sensitive (ia->first_line->variable_names_cb, + ia->skip_lines > 0); +} + + + +void +first_line_append_syntax (const struct import_assistant *ia, struct string *s) +{ + if (ia->skip_lines > 0) + ds_put_format (s, " /FIRSTCASE=%d\n", ia->skip_lines + 1); +} diff --git a/src/ui/gui/page-first-line.h b/src/ui/gui/page-first-line.h new file mode 100644 index 0000000000..70118b340a --- /dev/null +++ b/src/ui/gui/page-first-line.h @@ -0,0 +1,28 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2013 Free Software Foundation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#ifndef PAGE_FIRST_LINE_H +#define PAGE_FIRST_LINE_H + +struct first_line_page ; +struct import_assistant; +struct string; + +struct first_line_page *first_line_page_create (struct import_assistant *ia); +void first_line_append_syntax (const struct import_assistant *ia, struct string *s); + +#endif diff --git a/src/ui/gui/page-formats.c b/src/ui/gui/page-formats.c new file mode 100644 index 0000000000..6a2109d44a --- /dev/null +++ b/src/ui/gui/page-formats.c @@ -0,0 +1,292 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include "ui/gui/text-data-import-dialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "data/data-in.h" +#include "data/data-out.h" +#include "data/format-guesser.h" +#include "data/value-labels.h" +#include "language/data-io/data-parser.h" +#include "language/lexer/lexer.h" +#include "libpspp/assertion.h" +#include "libpspp/i18n.h" +#include "libpspp/line-reader.h" +#include "libpspp/message.h" +#include "ui/gui/checkbox-treeview.h" +#include "ui/gui/dialog-common.h" +#include "ui/gui/executor.h" +#include "ui/gui/helper.h" +#include "ui/gui/builder-wrapper.h" +#include "ui/gui/psppire-data-window.h" +#include "ui/gui/psppire-dialog.h" +#include "ui/gui/psppire-encoding-selector.h" +#include "ui/gui/psppire-empty-list-store.h" +#include "ui/gui/psppire-var-sheet.h" +#include "ui/gui/psppire-var-store.h" +#include "ui/gui/psppire-scanf.h" + +#include "gl/error.h" +#include "gl/intprops.h" +#include "gl/xalloc.h" + +#include "gettext.h" +#define _(msgid) gettext (msgid) +#define N_(msgid) msgid + +/* Page where the user verifies and adjusts input formats. */ +struct formats_page + { + GtkWidget *page; + GtkTreeView *data_tree_view; + PsppireDict *psppire_dict; + struct variable **modified_vars; + size_t modified_var_cnt; + }; + +/* The "formats" page of the assistant. */ + +static void on_variable_change (PsppireDict *dict, int idx, + struct import_assistant *); +static void clear_modified_vars (struct import_assistant *); + +/* Initializes IA's formats substructure. */ + +struct formats_page * +formats_page_create (struct import_assistant *ia) +{ + GtkBuilder *builder = ia->asst.builder; + struct formats_page *p = xzalloc (sizeof *p); + + p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Formats"), + GTK_ASSISTANT_PAGE_CONFIRM); + + p->data_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "data")); + p->modified_vars = NULL; + p->modified_var_cnt = 0; + + return p; +} + +/* Frees IA's formats substructure. */ +void +destroy_formats_page (struct import_assistant *ia) +{ + struct formats_page *p = ia->formats; + + if (p->psppire_dict != NULL) + { + dict_destroy (p->psppire_dict->dict); + g_object_unref (p->psppire_dict); + } + clear_modified_vars (ia); +} + +/* Called just before the formats page of the assistant is + displayed. */ +void +prepare_formats_page (struct import_assistant *ia) +{ + struct dictionary *dict; + PsppireDict *psppire_dict; + PsppireVarStore *var_store; + GtkBin *vars_scroller; + GtkWidget *old_var_sheet; + PsppireVarSheet *var_sheet; + struct formats_page *p = ia->formats; + struct fmt_guesser *fg; + unsigned long int number = 0; + size_t column_idx; + + push_watch_cursor (ia); + + dict = dict_create (get_default_encoding ()); + fg = fmt_guesser_create (); + for (column_idx = 0; column_idx < ia->column_cnt; column_idx++) + { + struct variable *modified_var = + (column_idx < p->modified_var_cnt ? p->modified_vars[column_idx] : NULL); + if (modified_var == NULL) + { + struct column *column = &ia->columns[column_idx]; + struct variable *var; + struct fmt_spec format; + char *name; + size_t row; + + /* Choose variable name. */ + name = dict_make_unique_var_name (dict, column->name, &number); + + /* Choose variable format. */ + fmt_guesser_clear (fg); + for (row = ia->skip_lines; row < ia->file.line_cnt; row++) + fmt_guesser_add (fg, column->contents[row]); + fmt_guesser_guess (fg, &format); + fmt_fix_input (&format); + + /* Create variable. */ + var = dict_create_var_assert (dict, name, fmt_var_width (&format)); + var_set_both_formats (var, &format); + + free (name); + } + else + { + char *name; + + name = dict_make_unique_var_name (dict, var_get_name (modified_var), + &number); + dict_clone_var_as_assert (dict, modified_var, name); + free (name); + } + } + fmt_guesser_destroy (fg); + + psppire_dict = psppire_dict_new_from_dict (dict); + g_signal_connect (psppire_dict, "variable_changed", + G_CALLBACK (on_variable_change), ia); + ia->dict = dict; + ia->formats->psppire_dict = psppire_dict; + + /* XXX: PsppireVarStore doesn't hold a reference to + psppire_dict for now, but it should. After it does, we + should g_object_ref the psppire_dict here, since we also + hold a reference via ia->formats->dict. */ + var_store = psppire_var_store_new (psppire_dict); + g_object_set (var_store, + "format-type", PSPPIRE_VAR_STORE_INPUT_FORMATS, + (void *) NULL); + var_sheet = PSPPIRE_VAR_SHEET (psppire_var_sheet_new ()); + g_object_set (var_sheet, + "model", var_store, + "may-create-vars", FALSE, + (void *) NULL); + + vars_scroller = GTK_BIN (get_widget_assert (ia->asst.builder, "vars-scroller")); + old_var_sheet = gtk_bin_get_child (vars_scroller); + if (old_var_sheet != NULL) + gtk_widget_destroy (old_var_sheet); + gtk_container_add (GTK_CONTAINER (vars_scroller), GTK_WIDGET (var_sheet)); + gtk_widget_show (GTK_WIDGET (var_sheet)); + + gtk_widget_destroy (GTK_WIDGET (ia->formats->data_tree_view)); + ia->formats->data_tree_view = create_data_tree_view ( + false, + GTK_CONTAINER (get_widget_assert (ia->asst.builder, "data-scroller")), + ia); + + gtk_widget_show (ia->asst.paste_button); + + pop_watch_cursor (ia); +} + +/* Clears the set of user-modified variables from IA's formats + substructure. This discards user modifications to variable + formats, thereby causing formats to revert to their + defaults. */ +static void +clear_modified_vars (struct import_assistant *ia) +{ + struct formats_page *p = ia->formats; + size_t i; + + for (i = 0; i < p->modified_var_cnt; i++) + var_destroy (p->modified_vars[i]); + free (p->modified_vars); + p->modified_vars = NULL; + p->modified_var_cnt = 0; +} + +/* Resets the formats page to its defaults, discarding user + modifications. */ +void +reset_formats_page (struct import_assistant *ia) +{ + clear_modified_vars (ia); + prepare_formats_page (ia); +} + + + +/* Called when the user changes one of the variables in the + dictionary. */ +static void +on_variable_change (PsppireDict *dict, int dict_idx, + struct import_assistant *ia) +{ + struct formats_page *p = ia->formats; + GtkTreeView *tv = ia->formats->data_tree_view; + gint column_idx = dict_idx + 1; + + push_watch_cursor (ia); + + /* Remove previous column and replace with new column. */ + gtk_tree_view_remove_column (tv, gtk_tree_view_get_column (tv, column_idx)); + gtk_tree_view_insert_column (tv, make_data_column (ia, tv, false, dict_idx), + column_idx); + + /* Save a copy of the modified variable in modified_vars, so + that its attributes will be preserved if we back up to the + previous page with the Prev button and then come back + here. */ + if (dict_idx >= p->modified_var_cnt) + { + size_t i; + p->modified_vars = xnrealloc (p->modified_vars, dict_idx + 1, + sizeof *p->modified_vars); + for (i = 0; i <= dict_idx; i++) + p->modified_vars[i] = NULL; + p->modified_var_cnt = dict_idx + 1; + } + if (p->modified_vars[dict_idx]) + var_destroy (p->modified_vars[dict_idx]); + p->modified_vars[dict_idx] + = var_clone (psppire_dict_get_variable (dict, dict_idx)); + + pop_watch_cursor (ia); +} + + + + +void +formats_append_syntax (const struct import_assistant *ia, struct string *s) +{ + int i; + int var_cnt; + ds_put_cstr (s, " /VARIABLES=\n"); + + var_cnt = dict_get_var_cnt (ia->dict); + for (i = 0; i < var_cnt; i++) + { + struct variable *var = dict_get_var (ia->dict, i); + char format_string[FMT_STRING_LEN_MAX + 1]; + fmt_to_string (var_get_print_format (var), format_string); + ds_put_format (s, " %s %s%s\n", + var_get_name (var), format_string, + i == var_cnt - 1 ? "." : ""); + } +} diff --git a/src/ui/gui/page-formats.h b/src/ui/gui/page-formats.h new file mode 100644 index 0000000000..750b6cbc52 --- /dev/null +++ b/src/ui/gui/page-formats.h @@ -0,0 +1,28 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2013 Free Software Foundation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef PAGE_FORMATS_H +#define PAGE_FORMATS_H + +struct formats_page; +struct import_assistant; +struct string; + +struct formats_page *formats_page_create (struct import_assistant *ia); +void formats_append_syntax (const struct import_assistant *ia, struct string *s); + + +#endif diff --git a/src/ui/gui/page-intro.c b/src/ui/gui/page-intro.c new file mode 100644 index 0000000000..e0d51f537c --- /dev/null +++ b/src/ui/gui/page-intro.c @@ -0,0 +1,200 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include "page-intro.h" + +#include "ui/gui/text-data-import-dialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "data/data-in.h" +#include "data/data-out.h" +#include "data/format-guesser.h" +#include "data/value-labels.h" +#include "language/data-io/data-parser.h" +#include "language/lexer/lexer.h" +#include "libpspp/assertion.h" +#include "libpspp/i18n.h" +#include "libpspp/line-reader.h" +#include "libpspp/message.h" +#include "ui/gui/checkbox-treeview.h" +#include "ui/gui/dialog-common.h" +#include "ui/gui/executor.h" +#include "ui/gui/helper.h" +#include "ui/gui/builder-wrapper.h" +#include "ui/gui/psppire-data-window.h" +#include "ui/gui/psppire-dialog.h" +#include "ui/gui/psppire-encoding-selector.h" +#include "ui/gui/psppire-empty-list-store.h" +#include "ui/gui/psppire-var-sheet.h" +#include "ui/gui/psppire-var-store.h" +#include "ui/gui/psppire-scanf.h" +#include "ui/syntax-gen.h" + +#include "gl/error.h" +#include "gl/intprops.h" +#include "gl/xalloc.h" + +#include "gettext.h" +#define _(msgid) gettext (msgid) +#define N_(msgid) msgid + +struct import_assistant; + + + +/* The "intro" page of the assistant. */ + +/* The introduction page of the assistant. */ +struct intro_page + { + GtkWidget *page; + GtkWidget *all_cases_button; + GtkWidget *n_cases_button; + GtkWidget *n_cases_spin; + GtkWidget *percent_button; + GtkWidget *percent_spin; + }; + +static void on_intro_amount_changed (struct intro_page *); + +/* Initializes IA's intro substructure. */ +struct intro_page * +intro_page_create (struct import_assistant *ia) +{ + GtkBuilder *builder = ia->asst.builder; + struct string s; + GtkWidget *hbox_n_cases ; + GtkWidget *hbox_percent ; + GtkWidget *table ; + + struct intro_page *p = xzalloc (sizeof (*p)); + + p->n_cases_spin = gtk_spin_button_new_with_range (0, INT_MAX, 100); + + hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &p->n_cases_spin); + + table = get_widget_assert (builder, "button-table"); + + gtk_table_attach_defaults (GTK_TABLE (table), hbox_n_cases, + 1, 2, + 1, 2); + + p->percent_spin = gtk_spin_button_new_with_range (0, 100, 10); + + hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"), &p->percent_spin); + + gtk_table_attach_defaults (GTK_TABLE (table), hbox_percent, + 1, 2, + 2, 3); + + p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Intro"), + GTK_ASSISTANT_PAGE_INTRO); + + p->all_cases_button = get_widget_assert (builder, "import-all-cases"); + + p->n_cases_button = get_widget_assert (builder, "import-n-cases"); + + p->percent_button = get_widget_assert (builder, "import-percent"); + + g_signal_connect_swapped (p->all_cases_button, "toggled", + G_CALLBACK (on_intro_amount_changed), p); + g_signal_connect_swapped (p->n_cases_button, "toggled", + G_CALLBACK (on_intro_amount_changed), p); + g_signal_connect_swapped (p->percent_button, "toggled", + G_CALLBACK (on_intro_amount_changed), p); + + on_intro_amount_changed (p); + + ds_init_empty (&s); + ds_put_cstr (&s, _("This assistant will guide you through the process of " + "importing data into PSPP from a text file with one line " + "per case, in which fields are separated by tabs, " + "commas, or other delimiters.\n\n")); + if (ia->file.total_is_exact) + ds_put_format ( + &s, ngettext ("The selected file contains %zu line of text. ", + "The selected file contains %zu lines of text. ", + ia->file.line_cnt), + ia->file.line_cnt); + else if (ia->file.total_lines > 0) + { + ds_put_format ( + &s, ngettext ( + "The selected file contains approximately %lu line of text. ", + "The selected file contains approximately %lu lines of text. ", + ia->file.total_lines), + ia->file.total_lines); + ds_put_format ( + &s, ngettext ( + "Only the first %zu line of the file will be shown for " + "preview purposes in the following screens. ", + "Only the first %zu lines of the file will be shown for " + "preview purposes in the following screens. ", + ia->file.line_cnt), + ia->file.line_cnt); + } + ds_put_cstr (&s, _("You may choose below how much of the file should " + "actually be imported.")); + gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")), + ds_cstr (&s)); + ds_destroy (&s); + + return p; +} + +/* Resets IA's intro page to its initial state. */ +void +reset_intro_page (struct import_assistant *ia) +{ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->intro->all_cases_button), + true); +} + +/* Called when one of the radio buttons is clicked. */ +static void +on_intro_amount_changed (struct intro_page *p) +{ + gtk_widget_set_sensitive (p->n_cases_spin, + gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (p->n_cases_button))); + + gtk_widget_set_sensitive (p->percent_spin, + gtk_toggle_button_get_active ( + GTK_TOGGLE_BUTTON (p->percent_button))); +} + + +void +intro_append_syntax (const struct intro_page *p, struct string *s) +{ + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (p->n_cases_button))) + ds_put_format (s, " /IMPORTCASES=FIRST %d\n", + gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (p->n_cases_spin))); + else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (p->percent_button))) + ds_put_format (s, " /IMPORTCASES=PERCENT %d\n", + gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (p->percent_spin))); + else + ds_put_cstr (s, " /IMPORTCASES=ALL\n"); +} diff --git a/src/ui/gui/page-intro.h b/src/ui/gui/page-intro.h new file mode 100644 index 0000000000..87848abb4c --- /dev/null +++ b/src/ui/gui/page-intro.h @@ -0,0 +1,28 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2013 Free Software Foundation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#ifndef PAGE_INTRO_H +#define PAGE_INTRO_H + +struct intro_page; +struct import_assistant; +struct string; + +struct intro_page *intro_page_create (struct import_assistant *ia); +void intro_append_syntax (const struct intro_page *p, struct string *s); + + +#endif diff --git a/src/ui/gui/page-separators.c b/src/ui/gui/page-separators.c new file mode 100644 index 0000000000..e147881730 --- /dev/null +++ b/src/ui/gui/page-separators.c @@ -0,0 +1,616 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include + +#include "page-separators.h" + +#include "ui/gui/text-data-import-dialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "data/data-in.h" +#include "data/data-out.h" +#include "data/format-guesser.h" +#include "data/value-labels.h" +#include "language/data-io/data-parser.h" +#include "language/lexer/lexer.h" +#include "libpspp/assertion.h" +#include "libpspp/i18n.h" +#include "libpspp/line-reader.h" +#include "libpspp/message.h" +#include "ui/gui/checkbox-treeview.h" +#include "ui/gui/dialog-common.h" +#include "ui/gui/executor.h" +#include "ui/gui/helper.h" +#include "ui/gui/builder-wrapper.h" +#include "ui/gui/psppire-data-window.h" +#include "ui/gui/psppire-dialog.h" +#include "ui/gui/psppire-encoding-selector.h" +#include "ui/gui/psppire-empty-list-store.h" +#include "ui/gui/psppire-var-sheet.h" +#include "ui/gui/psppire-var-store.h" +#include "ui/gui/psppire-scanf.h" +#include "ui/syntax-gen.h" + +#include "gl/error.h" +#include "gl/intprops.h" +#include "gl/xalloc.h" + +#include "gettext.h" +#define _(msgid) gettext (msgid) +#define N_(msgid) msgid + +/* Page where the user chooses field separators. */ +struct separators_page + { + /* How to break lines into columns. */ + struct string separators; /* Field separators. */ + struct string quotes; /* Quote characters. */ + bool escape; /* Doubled quotes yield a quote mark? */ + + GtkWidget *page; + GtkWidget *custom_cb; + GtkWidget *custom_entry; + GtkWidget *quote_cb; + GtkWidget *quote_combo; + GtkEntry *quote_entry; + GtkWidget *escape_cb; + GtkTreeView *fields_tree_view; + }; + +/* The "separators" page of the assistant. */ + +static void revise_fields_preview (struct import_assistant *ia); +static void choose_likely_separators (struct import_assistant *ia); +static void find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1], + const char *targets, const char *def, + struct string *result); +static void clear_fields (struct import_assistant *ia); +static void revise_fields_preview (struct import_assistant *); +static void set_separators (struct import_assistant *); +static void get_separators (struct import_assistant *); +static void on_separators_custom_entry_notify (GObject *UNUSED, + GParamSpec *UNUSED, + struct import_assistant *); +static void on_separators_custom_cb_toggle (GtkToggleButton *custom_cb, + struct import_assistant *); +static void on_quote_combo_change (GtkComboBox *combo, + struct import_assistant *); +static void on_quote_cb_toggle (GtkToggleButton *quote_cb, + struct import_assistant *); +static void on_separator_toggle (GtkToggleButton *, struct import_assistant *); + +/* A common field separator and its identifying name. */ +struct separator + { + const char *name; /* Name (for use with get_widget_assert). */ + int c; /* Separator character. */ + }; + +/* All the separators in the dialog box. */ +static const struct separator separators[] = + { + {"space", ' '}, + {"tab", '\t'}, + {"bang", '!'}, + {"colon", ':'}, + {"comma", ','}, + {"hyphen", '-'}, + {"pipe", '|'}, + {"semicolon", ';'}, + {"slash", '/'}, + }; +#define SEPARATOR_CNT (sizeof separators / sizeof *separators) + +static void +set_quote_list (GtkComboBoxEntry *cb) +{ + GtkListStore *list = gtk_list_store_new (1, G_TYPE_STRING); + GtkTreeIter iter; + gint i; + const gchar *seperator[3] = {"'\"", "\'", "\""}; + + for (i = 0; i < 3; i++) + { + const gchar *s = seperator[i]; + + /* Add a new row to the model */ + gtk_list_store_append (list, &iter); + gtk_list_store_set (list, &iter, + 0, s, + -1); + + } + + gtk_combo_box_set_model (GTK_COMBO_BOX (cb), GTK_TREE_MODEL (list)); + g_object_unref (list); + + gtk_combo_box_entry_set_text_column (cb, 0); +} + +/* Initializes IA's separators substructure. */ + +struct separators_page * +separators_page_create (struct import_assistant *ia) +{ + GtkBuilder *builder = ia->asst.builder; + + size_t i; + + struct separators_page *p = xzalloc (sizeof *p); + + p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Separators"), + GTK_ASSISTANT_PAGE_CONTENT); + + p->custom_cb = get_widget_assert (builder, "custom-cb"); + p->custom_entry = get_widget_assert (builder, "custom-entry"); + p->quote_combo = get_widget_assert (builder, "quote-combo"); + p->quote_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (p->quote_combo))); + p->quote_cb = get_widget_assert (builder, "quote-cb"); + p->escape_cb = get_widget_assert (builder, "escape"); + + set_quote_list (GTK_COMBO_BOX_ENTRY (p->quote_combo)); + p->fields_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "fields")); + g_signal_connect (p->quote_combo, "changed", + G_CALLBACK (on_quote_combo_change), ia); + g_signal_connect (p->quote_cb, "toggled", + G_CALLBACK (on_quote_cb_toggle), ia); + g_signal_connect (p->custom_entry, "notify::text", + G_CALLBACK (on_separators_custom_entry_notify), ia); + g_signal_connect (p->custom_cb, "toggled", + G_CALLBACK (on_separators_custom_cb_toggle), ia); + for (i = 0; i < SEPARATOR_CNT; i++) + g_signal_connect (get_widget_assert (builder, separators[i].name), + "toggled", G_CALLBACK (on_separator_toggle), ia); + g_signal_connect (p->escape_cb, "toggled", + G_CALLBACK (on_separator_toggle), ia); + + return p; +} + +/* Frees IA's separators substructure. */ +void +destroy_separators_page (struct import_assistant *ia) +{ + struct separators_page *s = ia->separators; + + ds_destroy (&s->separators); + ds_destroy (&s->quotes); + clear_fields (ia); +} + +/* Called just before the separators page becomes visible in the + assistant. */ +void +prepare_separators_page (struct import_assistant *ia) +{ + revise_fields_preview (ia); +} + +/* Called when the Reset button is clicked on the separators + page, resets the separators to the defaults. */ +void +reset_separators_page (struct import_assistant *ia) +{ + choose_likely_separators (ia); + set_separators (ia); +} + +/* Frees and clears the column data in IA's separators + substructure. */ +static void +clear_fields (struct import_assistant *ia) +{ + if (ia->column_cnt > 0) + { + struct column *col; + size_t row; + + for (row = 0; row < ia->file.line_cnt; row++) + { + const struct string *line = &ia->file.lines[row]; + const char *line_start = ds_data (line); + const char *line_end = ds_end (line); + + for (col = ia->columns; col < &ia->columns[ia->column_cnt]; col++) + { + char *s = ss_data (col->contents[row]); + if (!(s >= line_start && s <= line_end)) + ss_dealloc (&col->contents[row]); + } + } + + for (col = ia->columns; col < &ia->columns[ia->column_cnt]; col++) + { + free (col->name); + free (col->contents); + } + + free (ia->columns); + ia->columns = NULL; + ia->column_cnt = 0; + } +} + +/* Breaks the file data in IA into columns based on the + separators set in IA's separators substructure. */ +static void +split_fields (struct import_assistant *ia) +{ + struct separators_page *s = ia->separators; + size_t columns_allocated; + bool space_sep; + size_t row; + + clear_fields (ia); + + /* Is space in the set of separators? */ + space_sep = ss_find_byte (ds_ss (&s->separators), ' ') != SIZE_MAX; + + /* Split all the lines, not just those from + ia->first_line.skip_lines on, so that we split the line that + contains variables names if ia->first_line.variable_names is + true. */ + columns_allocated = 0; + for (row = 0; row < ia->file.line_cnt; row++) + { + struct string *line = &ia->file.lines[row]; + struct substring text = ds_ss (line); + size_t column_idx; + + for (column_idx = 0; ; column_idx++) + { + struct substring field; + struct column *column; + + if (space_sep) + ss_ltrim (&text, ss_cstr (" ")); + if (ss_is_empty (text)) + { + if (column_idx != 0) + break; + field = text; + } + else if (!ds_is_empty (&s->quotes) + && ds_find_byte (&s->quotes, text.string[0]) != SIZE_MAX) + { + int quote = ss_get_byte (&text); + if (!s->escape) + ss_get_until (&text, quote, &field); + else + { + struct string s; + int c; + + ds_init_empty (&s); + while ((c = ss_get_byte (&text)) != EOF) + if (c != quote) + ds_put_byte (&s, c); + else if (ss_match_byte (&text, quote)) + ds_put_byte (&s, quote); + else + break; + field = ds_ss (&s); + } + } + else + ss_get_bytes (&text, ss_cspan (text, ds_ss (&s->separators)), + &field); + + if (column_idx >= ia->column_cnt) + { + struct column *column; + + if (ia->column_cnt >= columns_allocated) + ia->columns = x2nrealloc (ia->columns, &columns_allocated, + sizeof *ia->columns); + column = &ia->columns[ia->column_cnt++]; + column->name = NULL; + column->width = 0; + column->contents = xcalloc (ia->file.line_cnt, + sizeof *column->contents); + } + column = &ia->columns[column_idx]; + column->contents[row] = field; + if (ss_length (field) > column->width) + column->width = ss_length (field); + + if (space_sep) + ss_ltrim (&text, ss_cstr (" ")); + if (ss_is_empty (text)) + break; + if (ss_find_byte (ds_ss (&s->separators), ss_first (text)) + != SIZE_MAX) + ss_advance (&text, 1); + } + } +} + +/* Chooses a name for each column on the separators page */ +static void +choose_column_names (struct import_assistant *ia) +{ + struct dictionary *dict; + unsigned long int generated_name_count = 0; + struct column *col; + size_t name_row; + + dict = dict_create (get_default_encoding ()); + name_row = ia->variable_names && ia->skip_lines ? ia->skip_lines : 0; + for (col = ia->columns; col < &ia->columns[ia->column_cnt]; col++) + { + char *hint, *name; + + hint = name_row ? ss_xstrdup (col->contents[name_row - 1]) : NULL; + name = dict_make_unique_var_name (dict, hint, &generated_name_count); + free (hint); + + col->name = name; + dict_create_var_assert (dict, name, 0); + } + dict_destroy (dict); +} + +/* Picks the most likely separator and quote characters based on + IA's file data. */ +static void +choose_likely_separators (struct import_assistant *ia) +{ + unsigned long int histogram[UCHAR_MAX + 1] = { 0 }; + size_t row; + + /* Construct a histogram of all the characters used in the + file. */ + for (row = 0; row < ia->file.line_cnt; row++) + { + struct substring line = ds_ss (&ia->file.lines[row]); + size_t length = ss_length (line); + size_t i; + for (i = 0; i < length; i++) + histogram[(unsigned char) line.string[i]]++; + } + + find_commonest_chars (histogram, "\"'", "", &ia->separators->quotes); + find_commonest_chars (histogram, ",;:/|!\t-", ",", &ia->separators->separators); + ia->separators->escape = true; +} + +/* Chooses the most common character among those in TARGETS, + based on the frequency data in HISTOGRAM, and stores it in + RESULT. If there is a tie for the most common character among + those in TARGETS, the earliest character is chosen. If none + of the TARGETS appear at all, then DEF is used as a + fallback. */ +static void +find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1], + const char *targets, const char *def, + struct string *result) +{ + unsigned char max = 0; + unsigned long int max_count = 0; + + for (; *targets != '\0'; targets++) + { + unsigned char c = *targets; + unsigned long int count = histogram[c]; + if (count > max_count) + { + max = c; + max_count = count; + } + } + if (max_count > 0) + { + ds_clear (result); + ds_put_byte (result, max); + } + else + ds_assign_cstr (result, def); +} + +/* Revises the contents of the fields tree view based on the + currently chosen set of separators. */ +static void +revise_fields_preview (struct import_assistant *ia) +{ + GtkWidget *w; + + push_watch_cursor (ia); + + w = GTK_WIDGET (ia->separators->fields_tree_view); + gtk_widget_destroy (w); + get_separators (ia); + split_fields (ia); + choose_column_names (ia); + ia->separators->fields_tree_view = create_data_tree_view ( + true, + GTK_CONTAINER (get_widget_assert (ia->asst.builder, "fields-scroller")), + ia); + + pop_watch_cursor (ia); +} + +/* Sets the widgets to match IA's separators substructure. */ +static void +set_separators (struct import_assistant *ia) +{ + struct separators_page *s = ia->separators; + unsigned int seps; + struct string custom; + bool any_custom; + bool any_quotes; + size_t i; + + ds_init_empty (&custom); + seps = 0; + for (i = 0; i < ds_length (&s->separators); i++) + { + unsigned char c = ds_at (&s->separators, i); + int j; + + for (j = 0; j < SEPARATOR_CNT; j++) + { + const struct separator *s = &separators[j]; + if (s->c == c) + { + seps += 1u << j; + goto next; + } + } + + ds_put_byte (&custom, c); + next:; + } + + for (i = 0; i < SEPARATOR_CNT; i++) + { + const struct separator *s = &separators[i]; + GtkWidget *button = get_widget_assert (ia->asst.builder, s->name); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), + (seps & (1u << i)) != 0); + } + any_custom = !ds_is_empty (&custom); + gtk_entry_set_text (GTK_ENTRY (s->custom_entry), ds_cstr (&custom)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->custom_cb), + any_custom); + gtk_widget_set_sensitive (s->custom_entry, any_custom); + ds_destroy (&custom); + + any_quotes = !ds_is_empty (&s->quotes); + + gtk_entry_set_text (s->quote_entry, + any_quotes ? ds_cstr (&s->quotes) : "\""); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->quote_cb), + any_quotes); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->escape_cb), + s->escape); + gtk_widget_set_sensitive (s->quote_combo, any_quotes); + gtk_widget_set_sensitive (s->escape_cb, any_quotes); +} + +/* Sets IA's separators substructure to match the widgets. */ +static void +get_separators (struct import_assistant *ia) +{ + struct separators_page *s = ia->separators; + int i; + + ds_clear (&s->separators); + for (i = 0; i < SEPARATOR_CNT; i++) + { + const struct separator *sep = &separators[i]; + GtkWidget *button = get_widget_assert (ia->asst.builder, sep->name); + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) + ds_put_byte (&s->separators, sep->c); + } + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->custom_cb))) + ds_put_cstr (&s->separators, + gtk_entry_get_text (GTK_ENTRY (s->custom_entry))); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->quote_cb))) + { + gchar *text = gtk_combo_box_get_active_text ( + GTK_COMBO_BOX (s->quote_combo)); + ds_assign_cstr (&s->quotes, text); + g_free (text); + } + else + ds_clear (&s->quotes); + s->escape = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->escape_cb)); +} + +/* Called when the user changes the entry field for custom + separators. */ +static void +on_separators_custom_entry_notify (GObject *gobject UNUSED, + GParamSpec *arg1 UNUSED, + struct import_assistant *ia) +{ + revise_fields_preview (ia); +} + +/* Called when the user toggles the checkbox that enables custom + separators. */ +static void +on_separators_custom_cb_toggle (GtkToggleButton *custom_cb, + struct import_assistant *ia) +{ + bool is_active = gtk_toggle_button_get_active (custom_cb); + gtk_widget_set_sensitive (ia->separators->custom_entry, is_active); + revise_fields_preview (ia); +} + +/* Called when the user changes the selection in the combo box + that selects a quote character. */ +static void +on_quote_combo_change (GtkComboBox *combo, struct import_assistant *ia) +{ + revise_fields_preview (ia); +} + +/* Called when the user toggles the checkbox that enables + quoting. */ +static void +on_quote_cb_toggle (GtkToggleButton *quote_cb, struct import_assistant *ia) +{ + bool is_active = gtk_toggle_button_get_active (quote_cb); + gtk_widget_set_sensitive (ia->separators->quote_combo, is_active); + gtk_widget_set_sensitive (ia->separators->escape_cb, is_active); + revise_fields_preview (ia); +} + +/* Called when the user toggles one of the separators + checkboxes. */ +static void +on_separator_toggle (GtkToggleButton *toggle UNUSED, + struct import_assistant *ia) +{ + revise_fields_preview (ia); +} + + + +void +separators_append_syntax (const struct import_assistant *ia, struct string *s) +{ + int i; + ds_put_cstr (s, " /DELIMITERS=\""); + if (ds_find_byte (&ia->separators->separators, '\t') != SIZE_MAX) + ds_put_cstr (s, "\\t"); + if (ds_find_byte (&ia->separators->separators, '\\') != SIZE_MAX) + ds_put_cstr (s, "\\\\"); + for (i = 0; i < ds_length (&ia->separators->separators); i++) + { + char c = ds_at (&ia->separators->separators, i); + if (c == '"') + ds_put_cstr (s, "\"\""); + else if (c != '\t' && c != '\\') + ds_put_byte (s, c); + } + ds_put_cstr (s, "\"\n"); + if (!ds_is_empty (&ia->separators->quotes)) + syntax_gen_pspp (s, " /QUALIFIER=%sq\n", ds_cstr (&ia->separators->quotes)); + if (!ds_is_empty (&ia->separators->quotes) && ia->separators->escape) + ds_put_cstr (s, " /ESCAPE\n"); +} diff --git a/src/ui/gui/page-separators.h b/src/ui/gui/page-separators.h new file mode 100644 index 0000000000..cbc32ee15e --- /dev/null +++ b/src/ui/gui/page-separators.h @@ -0,0 +1,29 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2013 Free Software Foundation + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PAGE_SEPARATORS +#define PAGE_SEPARATORS + +struct separators_page; +struct import_assistant; +struct string; + +struct separators_page *separators_page_create (struct import_assistant *ia); +void separators_append_syntax (const struct import_assistant *ia, struct string *s); + + +#endif diff --git a/src/ui/gui/psppire-data-window.c b/src/ui/gui/psppire-data-window.c index 9e5860b719..500877bdd3 100644 --- a/src/ui/gui/psppire-data-window.c +++ b/src/ui/gui/psppire-data-window.c @@ -1032,7 +1032,7 @@ psppire_data_window_finish_init (PsppireDataWindow *de, connect_action (de, "file_new_data", G_CALLBACK (create_data_window)); - connect_action (de, "file_import-text", G_CALLBACK (text_data_import_assistant)); + connect_action (de, "file_import", G_CALLBACK (text_data_import_assistant)); connect_action (de, "file_save", G_CALLBACK (psppire_window_save)); diff --git a/src/ui/gui/text-data-import-dialog.c b/src/ui/gui/text-data-import-dialog.c index f8fa9c3a46..d535734d08 100644 --- a/src/ui/gui/text-data-import-dialog.c +++ b/src/ui/gui/text-data-import-dialog.c @@ -1,5 +1,5 @@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2008, 2009, 2010, 2011, 2012 Free Software Foundation + Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,6 +18,11 @@ #include "ui/gui/text-data-import-dialog.h" +#include "page-intro.h" +#include "page-first-line.h" +#include "page-separators.h" +#include "page-formats.h" + #include #include #include @@ -60,190 +65,50 @@ struct import_assistant; -/* The file to be imported. */ -struct file - { - char *file_name; /* File name. */ - gchar *encoding; /* Encoding. */ - unsigned long int total_lines; /* Number of lines in file. */ - bool total_is_exact; /* Is total_lines exact (or an estimate)? */ - - /* The first several lines of the file. */ - struct string *lines; - size_t line_cnt; - }; -static bool init_file (struct import_assistant *, GtkWindow *parent); -static void destroy_file (struct import_assistant *); - -/* The main body of the GTK+ assistant and related data. */ -struct assistant - { - GtkBuilder *builder; - GtkAssistant *assistant; - GMainLoop *main_loop; - GtkWidget *paste_button; - GtkWidget *reset_button; - int response; - int watch_cursor; - - GtkCellRenderer *prop_renderer; - GtkCellRenderer *fixed_renderer; - }; -static void init_assistant (struct import_assistant *, GtkWindow *); -static void destroy_assistant (struct import_assistant *); -static GtkWidget *add_page_to_assistant (struct import_assistant *, - GtkWidget *page, - GtkAssistantPageType); - -/* The introduction page of the assistant. */ -struct intro_page - { - GtkWidget *page; - GtkWidget *all_cases_button; - GtkWidget *n_cases_button; - GtkWidget *n_cases_spin; - GtkWidget *percent_button; - GtkWidget *percent_spin; - }; -static void init_intro_page (struct import_assistant *); -static void reset_intro_page (struct import_assistant *); - -/* Page where the user chooses the first line of data. */ -struct first_line_page - { - int skip_lines; /* Number of initial lines to skip? */ - bool variable_names; /* Variable names above first line of data? */ - - GtkWidget *page; - GtkTreeView *tree_view; - GtkWidget *variable_names_cb; - }; -static void init_first_line_page (struct import_assistant *); -static void reset_first_line_page (struct import_assistant *); - -/* Page where the user chooses field separators. */ -struct separators_page - { - /* How to break lines into columns. */ - struct string separators; /* Field separators. */ - struct string quotes; /* Quote characters. */ - bool escape; /* Doubled quotes yield a quote mark? */ - - /* The columns produced thereby. */ - struct column *columns; /* Information about each column. */ - size_t column_cnt; /* Number of columns. */ - - GtkWidget *page; - GtkWidget *custom_cb; - GtkWidget *custom_entry; - GtkWidget *quote_cb; - GtkWidget *quote_combo; - GtkEntry *quote_entry; - GtkWidget *escape_cb; - GtkTreeView *fields_tree_view; - }; -/* The columns that the separators divide the data into. */ -struct column - { - /* Variable name for this column. This is the variable name - used on the separators page; it can be overridden by the - user on the formats page. */ - char *name; - - /* Maximum length of any row in this column. */ - size_t width; - - /* Contents of this column: contents[row] is the contents for - the given row. - - A null substring indicates a missing column for that row - (because the line contains an insufficient number of - separators). - - contents[] elements may be substrings of the lines[] - strings that represent the whole lines of the file, to - save memory. Other elements are dynamically allocated - with ss_alloc_substring. */ - struct substring *contents; - }; -static void init_separators_page (struct import_assistant *); -static void destroy_separators_page (struct import_assistant *); -static void prepare_separators_page (struct import_assistant *); -static void reset_separators_page (struct import_assistant *); - -/* Page where the user verifies and adjusts input formats. */ -struct formats_page - { - struct dictionary *dict; - - GtkWidget *page; - GtkTreeView *data_tree_view; - PsppireDict *psppire_dict; - struct variable **modified_vars; - size_t modified_var_cnt; - }; -static void init_formats_page (struct import_assistant *); -static void destroy_formats_page (struct import_assistant *); -static void prepare_formats_page (struct import_assistant *); -static void reset_formats_page (struct import_assistant *); - -struct import_assistant - { - struct file file; - struct assistant asst; - struct intro_page intro; - struct first_line_page first_line; - struct separators_page separators; - struct formats_page formats; - }; - static void apply_dict (const struct dictionary *, struct string *); static char *generate_syntax (const struct import_assistant *); -static gboolean get_tooltip_location (GtkWidget *widget, gint wx, gint wy, - const struct import_assistant *, - size_t *row, size_t *column); -static void make_tree_view (const struct import_assistant *ia, - size_t first_line, - GtkTreeView **tree_view); static void add_line_number_column (const struct import_assistant *, GtkTreeView *); -static gint get_monospace_width (GtkTreeView *, GtkCellRenderer *, - size_t char_cnt); -static gint get_string_width (GtkTreeView *, GtkCellRenderer *, - const char *string); -static GtkTreeViewColumn *make_data_column (struct import_assistant *, - GtkTreeView *, bool input, - gint column_idx); -static GtkTreeView *create_data_tree_view (bool input, GtkContainer *parent, - struct import_assistant *); -static void push_watch_cursor (struct import_assistant *); -static void pop_watch_cursor (struct import_assistant *); /* Pops up the Text Data Import assistant. */ void text_data_import_assistant (PsppireDataWindow *dw) { GtkWindow *parent_window = GTK_WINDOW (dw); - struct import_assistant *ia; + struct import_assistant *ia = init_assistant (parent_window); - ia = xzalloc (sizeof *ia); if (!init_file (ia, parent_window)) { free (ia); return; } - init_assistant (ia, parent_window); - init_intro_page (ia); - init_first_line_page (ia); - init_separators_page (ia); - init_formats_page (ia); + + { + ia->intro = intro_page_create (ia); + ia->separators = separators_page_create (ia); + ia->first_line = first_line_page_create (ia); + } + ia->formats = formats_page_create (ia); gtk_widget_show_all (GTK_WIDGET (ia->asst.assistant)); ia->asst.main_loop = g_main_loop_new (NULL, false); - g_main_loop_run (ia->asst.main_loop); + + { + /* + Instead of this block, + A simple g_main_loop_run (ia->asst.main_loop); should work here. But it seems to crash. + I have no idea why. + */ + GMainContext *ctx = g_main_loop_get_context (ia->asst.main_loop); + ia->asst.loop_done = false; + while (! ia->asst.loop_done) + { + g_main_context_iteration (ctx, TRUE); + } + } g_main_loop_unref (ia->asst.main_loop); switch (ia->asst.response) @@ -258,8 +123,11 @@ text_data_import_assistant (PsppireDataWindow *dw) break; } - destroy_formats_page (ia); - destroy_separators_page (ia); + { + destroy_formats_page (ia); + destroy_separators_page (ia); + } + destroy_assistant (ia); destroy_file (ia); free (ia); @@ -354,1200 +222,46 @@ static char * generate_syntax (const struct import_assistant *ia) { struct string s = DS_EMPTY_INITIALIZER; - size_t var_cnt; - size_t i; - - syntax_gen_pspp (&s, - "GET DATA\n" - " /TYPE=TXT\n" - " /FILE=%sq\n", - ia->file.file_name); - if (ia->file.encoding && strcmp (ia->file.encoding, "Auto")) - syntax_gen_pspp (&s, " /ENCODING=%sq\n", ia->file.encoding); - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON ( - ia->intro.n_cases_button))) - ds_put_format (&s, " /IMPORTCASES=FIRST %d\n", - gtk_spin_button_get_value_as_int ( - GTK_SPIN_BUTTON (ia->intro.n_cases_spin))); - else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON ( - ia->intro.percent_button))) - ds_put_format (&s, " /IMPORTCASES=PERCENT %d\n", - gtk_spin_button_get_value_as_int ( - GTK_SPIN_BUTTON (ia->intro.percent_spin))); - else - ds_put_cstr (&s, " /IMPORTCASES=ALL\n"); - ds_put_cstr (&s, - " /ARRANGEMENT=DELIMITED\n" - " /DELCASE=LINE\n"); - if (ia->first_line.skip_lines > 0) - ds_put_format (&s, " /FIRSTCASE=%d\n", ia->first_line.skip_lines + 1); - ds_put_cstr (&s, " /DELIMITERS=\""); - if (ds_find_byte (&ia->separators.separators, '\t') != SIZE_MAX) - ds_put_cstr (&s, "\\t"); - if (ds_find_byte (&ia->separators.separators, '\\') != SIZE_MAX) - ds_put_cstr (&s, "\\\\"); - for (i = 0; i < ds_length (&ia->separators.separators); i++) - { - char c = ds_at (&ia->separators.separators, i); - if (c == '"') - ds_put_cstr (&s, "\"\""); - else if (c != '\t' && c != '\\') - ds_put_byte (&s, c); - } - ds_put_cstr (&s, "\"\n"); - if (!ds_is_empty (&ia->separators.quotes)) - syntax_gen_pspp (&s, " /QUALIFIER=%sq\n", ds_cstr (&ia->separators.quotes)); - if (!ds_is_empty (&ia->separators.quotes) && ia->separators.escape) - ds_put_cstr (&s, " /ESCAPE\n"); - ds_put_cstr (&s, " /VARIABLES=\n"); - - var_cnt = dict_get_var_cnt (ia->formats.dict); - for (i = 0; i < var_cnt; i++) - { - struct variable *var = dict_get_var (ia->formats.dict, i); - char format_string[FMT_STRING_LEN_MAX + 1]; - fmt_to_string (var_get_print_format (var), format_string); - ds_put_format (&s, " %s %s%s\n", - var_get_name (var), format_string, - i == var_cnt - 1 ? "." : ""); - } - - apply_dict (ia->formats.dict, &s); - - return ds_cstr (&s); -} - -/* Choosing a file and reading it. */ - -static char *choose_file (GtkWindow *parent_window, gchar **encodingp); - -/* Obtains the file to import from the user and initializes IA's - file substructure. PARENT_WINDOW must be the window to use - as the file chooser window's parent. - - Returns true if successful, false if the file name could not - be obtained or the file could not be read. */ -static bool -init_file (struct import_assistant *ia, GtkWindow *parent_window) -{ - struct file *file = &ia->file; - enum { MAX_PREVIEW_LINES = 1000 }; /* Max number of lines to read. */ - enum { MAX_LINE_LEN = 16384 }; /* Max length of an acceptable line. */ - struct line_reader *reader; - struct string input; - - file->file_name = choose_file (parent_window, &file->encoding); - if (file->file_name == NULL) - return false; - - reader = line_reader_for_file (file->encoding, file->file_name, O_RDONLY); - if (reader == NULL) - { - msg (ME, _("Could not open `%s': %s"), - file->file_name, strerror (errno)); - return false; - } - - ds_init_empty (&input); - file->lines = xnmalloc (MAX_PREVIEW_LINES, sizeof *file->lines); - for (; file->line_cnt < MAX_PREVIEW_LINES; file->line_cnt++) - { - ds_clear (&input); - if (!line_reader_read (reader, &input, MAX_LINE_LEN + 1) - || ds_length (&input) > MAX_LINE_LEN) - { - if (line_reader_eof (reader)) - break; - else if (line_reader_error (reader)) - msg (ME, _("Error reading `%s': %s"), - file->file_name, strerror (line_reader_error (reader))); - else - msg (ME, _("Failed to read `%s', because it contains a line " - "over %d bytes long and therefore appears not to be " - "a text file."), - file->file_name, MAX_LINE_LEN); - line_reader_close (reader); - destroy_file (ia); - ds_destroy (&input); - return false; - } - - ds_init_cstr (&file->lines[file->line_cnt], - recode_string ("UTF-8", line_reader_get_encoding (reader), - ds_cstr (&input), ds_length (&input))); - } - ds_destroy (&input); - - if (file->line_cnt == 0) - { - msg (ME, _("`%s' is empty."), file->file_name); - line_reader_close (reader); - destroy_file (ia); - return false; - } - - /* Estimate the number of lines in the file. */ - if (file->line_cnt < MAX_PREVIEW_LINES) - file->total_lines = file->line_cnt; - else - { - struct stat s; - off_t position = line_reader_tell (reader); - if (fstat (line_reader_fileno (reader), &s) == 0 && position > 0) - file->total_lines = (double) file->line_cnt / position * s.st_size; - else - file->total_lines = 0; - } - - line_reader_close (reader); - - return true; -} - -/* Frees IA's file substructure. */ -static void -destroy_file (struct import_assistant *ia) -{ - struct file *f = &ia->file; - size_t i; - - for (i = 0; i < f->line_cnt; i++) - ds_destroy (&f->lines[i]); - free (f->lines); - g_free (f->file_name); - g_free (f->encoding); -} - -/* Obtains the file to read from the user. If successful, returns the name of - the file and stores the user's chosen encoding for the file into *ENCODINGP. - The caller must free each of these strings with g_free(). - - On failure, stores a null pointer and stores NULL in *ENCODINGP. - - PARENT_WINDOW must be the window to use as the file chooser window's - parent. */ -static char * -choose_file (GtkWindow *parent_window, gchar **encodingp) -{ - char *file_name; - GtkFileFilter *filter = NULL; - - GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Import Delimited Text Data"), - parent_window, - GTK_FILE_CHOOSER_ACTION_OPEN, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, - NULL); - - g_object_set (dialog, "local-only", FALSE, NULL); - - filter = gtk_file_filter_new (); - gtk_file_filter_set_name (filter, _("Text files")); - gtk_file_filter_add_mime_type (filter, "text/*"); - gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); - - filter = gtk_file_filter_new (); - gtk_file_filter_set_name (filter, _("Text (*.txt) Files")); - gtk_file_filter_add_pattern (filter, "*.txt"); - gtk_file_filter_add_pattern (filter, "*.TXT"); - gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); - - filter = gtk_file_filter_new (); - gtk_file_filter_set_name (filter, _("Plain Text (ASCII) Files")); - gtk_file_filter_add_mime_type (filter, "text/plain"); - gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); - - filter = gtk_file_filter_new (); - gtk_file_filter_set_name (filter, _("Comma Separated Value Files")); - gtk_file_filter_add_mime_type (filter, "text/csv"); - gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); - - /* I've never encountered one of these, but it's listed here: - http://www.iana.org/assignments/media-types/text/tab-separated-values */ - filter = gtk_file_filter_new (); - gtk_file_filter_set_name (filter, _("Tab Separated Value Files")); - gtk_file_filter_add_mime_type (filter, "text/tab-separated-values"); - gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); - - gtk_file_chooser_set_extra_widget ( - GTK_FILE_CHOOSER (dialog), psppire_encoding_selector_new ("Auto", true)); - - filter = gtk_file_filter_new (); - gtk_file_filter_set_name (filter, _("All Files")); - gtk_file_filter_add_pattern (filter, "*"); - gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter); - - switch (gtk_dialog_run (GTK_DIALOG (dialog))) - { - case GTK_RESPONSE_ACCEPT: - file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); - *encodingp = psppire_encoding_selector_get_encoding ( - gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (dialog))); - break; - default: - file_name = NULL; - *encodingp = NULL; - break; - } - gtk_widget_destroy (dialog); - - return file_name; -} - -/* Assistant. */ - -static void close_assistant (struct import_assistant *, int response); -static void on_prepare (GtkAssistant *assistant, GtkWidget *page, - struct import_assistant *); -static void on_cancel (GtkAssistant *assistant, struct import_assistant *); -static void on_close (GtkAssistant *assistant, struct import_assistant *); -static void on_paste (GtkButton *button, struct import_assistant *); -static void on_reset (GtkButton *button, struct import_assistant *); -static void close_assistant (struct import_assistant *, int response); - -/* Initializes IA's asst substructure. PARENT_WINDOW must be the - window to use as the assistant window's parent. */ -static void -init_assistant (struct import_assistant *ia, GtkWindow *parent_window) -{ - struct assistant *a = &ia->asst; - - a->builder = builder_new ("text-data-import.ui"); - a->assistant = GTK_ASSISTANT (gtk_assistant_new ()); - g_signal_connect (a->assistant, "prepare", G_CALLBACK (on_prepare), ia); - g_signal_connect (a->assistant, "cancel", G_CALLBACK (on_cancel), ia); - g_signal_connect (a->assistant, "close", G_CALLBACK (on_close), ia); - a->paste_button = gtk_button_new_from_stock (GTK_STOCK_PASTE); - gtk_assistant_add_action_widget (a->assistant, a->paste_button); - g_signal_connect (a->paste_button, "clicked", G_CALLBACK (on_paste), ia); - a->reset_button = gtk_button_new_from_stock ("pspp-stock-reset"); - gtk_assistant_add_action_widget (a->assistant, a->reset_button); - g_signal_connect (a->reset_button, "clicked", G_CALLBACK (on_reset), ia); - gtk_window_set_title (GTK_WINDOW (a->assistant), - _("Importing Delimited Text Data")); - gtk_window_set_transient_for (GTK_WINDOW (a->assistant), parent_window); - gtk_window_set_icon_name (GTK_WINDOW (a->assistant), "pspp"); - - a->prop_renderer = gtk_cell_renderer_text_new (); - g_object_ref_sink (a->prop_renderer); - a->fixed_renderer = gtk_cell_renderer_text_new (); - g_object_ref_sink (a->fixed_renderer); - g_object_set (G_OBJECT (a->fixed_renderer), - "family", "Monospace", - (void *) NULL); -} - -/* Frees IA's asst substructure. */ -static void -destroy_assistant (struct import_assistant *ia) -{ - struct assistant *a = &ia->asst; - - g_object_unref (a->prop_renderer); - g_object_unref (a->fixed_renderer); - g_object_unref (a->builder); -} - -/* Appends a page of the given TYPE, with PAGE as its content, to - the GtkAssistant encapsulated by IA. Returns the GtkWidget - that represents the page. */ -static GtkWidget * -add_page_to_assistant (struct import_assistant *ia, - GtkWidget *page, GtkAssistantPageType type) -{ - const char *title; - char *title_copy; - GtkWidget *content; - - title = gtk_window_get_title (GTK_WINDOW (page)); - title_copy = xstrdup (title ? title : ""); - - content = gtk_bin_get_child (GTK_BIN (page)); - assert (content); - g_object_ref (content); - gtk_container_remove (GTK_CONTAINER (page), content); - - gtk_widget_destroy (page); - - gtk_assistant_append_page (ia->asst.assistant, content); - gtk_assistant_set_page_type (ia->asst.assistant, content, type); - gtk_assistant_set_page_title (ia->asst.assistant, content, title_copy); - gtk_assistant_set_page_complete (ia->asst.assistant, content, true); - - free (title_copy); - - return content; -} - -/* Called just before PAGE is displayed as the current page of - ASSISTANT, this updates IA content according to the new - page. */ -static void -on_prepare (GtkAssistant *assistant, GtkWidget *page, - struct import_assistant *ia) -{ - - if (gtk_assistant_get_page_type (assistant, page) - == GTK_ASSISTANT_PAGE_CONFIRM) - gtk_widget_grab_focus (assistant->apply); - else - gtk_widget_grab_focus (assistant->forward); - - if (page == ia->separators.page) - prepare_separators_page (ia); - else if (page == ia->formats.page) - prepare_formats_page (ia); - - gtk_widget_show (ia->asst.reset_button); - if (page == ia->formats.page) - gtk_widget_show (ia->asst.paste_button); - else - gtk_widget_hide (ia->asst.paste_button); -} - -/* Called when the Cancel button in the assistant is clicked. */ -static void -on_cancel (GtkAssistant *assistant, struct import_assistant *ia) -{ - close_assistant (ia, GTK_RESPONSE_CANCEL); -} - -/* Called when the Apply button on the last page of the assistant - is clicked. */ -static void -on_close (GtkAssistant *assistant, struct import_assistant *ia) -{ - close_assistant (ia, GTK_RESPONSE_APPLY); -} - -/* Called when the Paste button on the last page of the assistant - is clicked. */ -static void -on_paste (GtkButton *button, struct import_assistant *ia) -{ - close_assistant (ia, PSPPIRE_RESPONSE_PASTE); -} - -/* Called when the Reset button is clicked. */ -static void -on_reset (GtkButton *button, struct import_assistant *ia) -{ - gint page_num = gtk_assistant_get_current_page (ia->asst.assistant); - GtkWidget *page = gtk_assistant_get_nth_page (ia->asst.assistant, page_num); - - if (page == ia->intro.page) - reset_intro_page (ia); - else if (page == ia->first_line.page) - reset_first_line_page (ia); - else if (page == ia->separators.page) - reset_separators_page (ia); - else if (page == ia->formats.page) - reset_formats_page (ia); -} - -/* Causes the assistant to close, returning RESPONSE for - interpretation by text_data_import_assistant. */ -static void -close_assistant (struct import_assistant *ia, int response) -{ - ia->asst.response = response; - g_main_loop_quit (ia->asst.main_loop); - gtk_widget_hide (GTK_WIDGET (ia->asst.assistant)); -} - -/* The "intro" page of the assistant. */ - -static void on_intro_amount_changed (struct import_assistant *); - -/* Initializes IA's intro substructure. */ -static void -init_intro_page (struct import_assistant *ia) -{ - GtkBuilder *builder = ia->asst.builder; - struct intro_page *p = &ia->intro; - struct string s; - GtkWidget *hbox_n_cases ; - GtkWidget *hbox_percent ; - GtkWidget *table ; - - - p->n_cases_spin = gtk_spin_button_new_with_range (0, INT_MAX, 100); - - hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &p->n_cases_spin); - - table = get_widget_assert (builder, "button-table"); - - gtk_table_attach_defaults (GTK_TABLE (table), hbox_n_cases, - 1, 2, - 1, 2); - - p->percent_spin = gtk_spin_button_new_with_range (0, 100, 10); - - hbox_percent = psppire_scanf_new (_("Only the first %3d %% of file (approximately)"), &p->percent_spin); - - gtk_table_attach_defaults (GTK_TABLE (table), hbox_percent, - 1, 2, - 2, 3); - - p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Intro"), - GTK_ASSISTANT_PAGE_INTRO); - - p->all_cases_button = get_widget_assert (builder, "import-all-cases"); - - p->n_cases_button = get_widget_assert (builder, "import-n-cases"); - - p->percent_button = get_widget_assert (builder, "import-percent"); - - g_signal_connect_swapped (p->all_cases_button, "toggled", - G_CALLBACK (on_intro_amount_changed), ia); - g_signal_connect_swapped (p->n_cases_button, "toggled", - G_CALLBACK (on_intro_amount_changed), ia); - g_signal_connect_swapped (p->percent_button, "toggled", - G_CALLBACK (on_intro_amount_changed), ia); - on_intro_amount_changed (ia); - - ds_init_empty (&s); - ds_put_cstr (&s, _("This assistant will guide you through the process of " - "importing data into PSPP from a text file with one line " - "per case, in which fields are separated by tabs, " - "commas, or other delimiters.\n\n")); - if (ia->file.total_is_exact) - ds_put_format ( - &s, ngettext ("The selected file contains %zu line of text. ", - "The selected file contains %zu lines of text. ", - ia->file.line_cnt), - ia->file.line_cnt); - else if (ia->file.total_lines > 0) { - ds_put_format ( - &s, ngettext ( - "The selected file contains approximately %lu line of text. ", - "The selected file contains approximately %lu lines of text. ", - ia->file.total_lines), - ia->file.total_lines); - ds_put_format ( - &s, ngettext ( - "Only the first %zu line of the file will be shown for " - "preview purposes in the following screens. ", - "Only the first %zu lines of the file will be shown for " - "preview purposes in the following screens. ", - ia->file.line_cnt), - ia->file.line_cnt); - } - ds_put_cstr (&s, _("You may choose below how much of the file should " - "actually be imported.")); - gtk_label_set_text (GTK_LABEL (get_widget_assert (builder, "intro-label")), - ds_cstr (&s)); - ds_destroy (&s); -} - -/* Resets IA's intro page to its initial state. */ -static void -reset_intro_page (struct import_assistant *ia) -{ - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ia->intro.all_cases_button), - true); -} - -/* Called when one of the radio buttons is clicked. */ -static void -on_intro_amount_changed (struct import_assistant *ia) -{ - struct intro_page *p = &ia->intro; - - gtk_widget_set_sensitive (p->n_cases_spin, - gtk_toggle_button_get_active ( - GTK_TOGGLE_BUTTON (p->n_cases_button))); + syntax_gen_pspp (&s, + "GET DATA" + "\n /TYPE=TXT" + "\n /FILE=%sq\n", + ia->file.file_name); + if (ia->file.encoding && strcmp (ia->file.encoding, "Auto")) + syntax_gen_pspp (&s, " /ENCODING=%sq\n", ia->file.encoding); - gtk_widget_set_sensitive (p->percent_spin, - gtk_toggle_button_get_active ( - GTK_TOGGLE_BUTTON (p->percent_button))); -} - -/* The "first line" page of the assistant. */ - -static GtkTreeView *create_lines_tree_view (GtkContainer *parent_window, - struct import_assistant *); -static void on_first_line_change (GtkTreeSelection *, - struct import_assistant *); -static void on_variable_names_cb_toggle (GtkToggleButton *, - struct import_assistant *); -static void set_first_line (struct import_assistant *); -static void get_first_line (struct import_assistant *); - -/* Initializes IA's first_line substructure. */ -static void -init_first_line_page (struct import_assistant *ia) -{ - struct first_line_page *p = &ia->first_line; - GtkBuilder *builder = ia->asst.builder; - - p->page = add_page_to_assistant (ia, get_widget_assert (builder, "FirstLine"), - GTK_ASSISTANT_PAGE_CONTENT); - gtk_widget_destroy (get_widget_assert (builder, "first-line")); - p->tree_view = create_lines_tree_view ( - GTK_CONTAINER (get_widget_assert (builder, "first-line-scroller")), ia); - p->variable_names_cb = get_widget_assert (builder, "variable-names"); - gtk_tree_selection_set_mode ( - gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)), - GTK_SELECTION_BROWSE); - set_first_line (ia); - g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (p->tree_view)), - "changed", G_CALLBACK (on_first_line_change), ia); - g_signal_connect (p->variable_names_cb, "toggled", - G_CALLBACK (on_variable_names_cb_toggle), ia); -} -/* Resets the first_line page to its initial content. */ -static void -reset_first_line_page (struct import_assistant *ia) -{ - ia->first_line.skip_lines = 0; - ia->first_line.variable_names = false; - set_first_line (ia); -} - -static void -render_line (GtkTreeViewColumn *tree_column, - GtkCellRenderer *cell, - GtkTreeModel *tree_model, - GtkTreeIter *iter, - gpointer data) -{ - gint row = empty_list_store_iter_to_row (iter); - struct string *lines; + intro_append_syntax (ia->intro, &s); - lines = g_object_get_data (G_OBJECT (tree_model), "lines"); - g_return_if_fail (lines != NULL); - g_object_set (cell, "text", ds_cstr (&lines[row]), NULL); -} + ds_put_cstr (&s, + " /ARRANGEMENT=DELIMITED\n" + " /DELCASE=LINE\n"); - -/* Creates and returns a tree view that contains each of the - lines in IA's file as a row. */ -static GtkTreeView * -create_lines_tree_view (GtkContainer *parent, struct import_assistant *ia) -{ - GtkTreeView *tree_view; - GtkTreeViewColumn *column; - size_t max_line_length; - gint content_width, header_width; - size_t i; - const gchar *title = _("Text"); - - make_tree_view (ia, 0, &tree_view); - - column = gtk_tree_view_column_new_with_attributes ( - title, ia->asst.fixed_renderer, (void *) NULL); - gtk_tree_view_column_set_cell_data_func (column, ia->asst.fixed_renderer, - render_line, NULL, NULL); - gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED); - - max_line_length = 0; - for (i = 0; i < ia->file.line_cnt; i++) - { - size_t w = ds_length (&ia->file.lines[i]); - max_line_length = MAX (max_line_length, w); + first_line_append_syntax (ia, &s); + separators_append_syntax (ia, &s); + formats_append_syntax (ia, &s); + apply_dict (ia->dict, &s); } - content_width = get_monospace_width (tree_view, ia->asst.fixed_renderer, - max_line_length); - header_width = get_string_width (tree_view, ia->asst.prop_renderer, title); - gtk_tree_view_column_set_fixed_width (column, MAX (content_width, - header_width)); - gtk_tree_view_append_column (tree_view, column); - - gtk_tree_view_set_fixed_height_mode (tree_view, true); - - gtk_container_add (parent, GTK_WIDGET (tree_view)); - gtk_widget_show (GTK_WIDGET (tree_view)); - - return tree_view; -} - -/* Called when the line selected in the first_line tree view - changes. */ -static void -on_first_line_change (GtkTreeSelection *selection UNUSED, - struct import_assistant *ia) -{ - get_first_line (ia); -} - -/* Called when the checkbox that indicates whether variable - names are in the row above the first line is toggled. */ -static void -on_variable_names_cb_toggle (GtkToggleButton *variable_names_cb UNUSED, - struct import_assistant *ia) -{ - get_first_line (ia); + return ds_cstr (&s); } -/* Sets the widgets to match IA's first_line substructure. */ -static void -set_first_line (struct import_assistant *ia) -{ - GtkTreePath *path; - path = gtk_tree_path_new_from_indices (ia->first_line.skip_lines, -1); - gtk_tree_view_set_cursor (GTK_TREE_VIEW (ia->first_line.tree_view), - path, NULL, false); - gtk_tree_path_free (path); - gtk_toggle_button_set_active ( - GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb), - ia->first_line.variable_names); - gtk_widget_set_sensitive (ia->first_line.variable_names_cb, - ia->first_line.skip_lines > 0); -} - -/* Sets IA's first_line substructure to match the widgets. */ -static void -get_first_line (struct import_assistant *ia) -{ - GtkTreeSelection *selection; - GtkTreeIter iter; - GtkTreeModel *model; - - selection = gtk_tree_view_get_selection (ia->first_line.tree_view); - if (gtk_tree_selection_get_selected (selection, &model, &iter)) - { - GtkTreePath *path = gtk_tree_model_get_path (model, &iter); - int row = gtk_tree_path_get_indices (path)[0]; - gtk_tree_path_free (path); - - ia->first_line.skip_lines = row; - ia->first_line.variable_names = - (ia->first_line.skip_lines > 0 - && gtk_toggle_button_get_active ( - GTK_TOGGLE_BUTTON (ia->first_line.variable_names_cb))); - } - gtk_widget_set_sensitive (ia->first_line.variable_names_cb, - ia->first_line.skip_lines > 0); -} - -/* The "separators" page of the assistant. */ - -static void revise_fields_preview (struct import_assistant *ia); -static void choose_likely_separators (struct import_assistant *ia); -static void find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1], - const char *targets, const char *def, - struct string *result); -static void clear_fields (struct import_assistant *ia); -static void revise_fields_preview (struct import_assistant *); -static void set_separators (struct import_assistant *); -static void get_separators (struct import_assistant *); -static void on_separators_custom_entry_notify (GObject *UNUSED, - GParamSpec *UNUSED, - struct import_assistant *); -static void on_separators_custom_cb_toggle (GtkToggleButton *custom_cb, - struct import_assistant *); -static void on_quote_combo_change (GtkComboBox *combo, - struct import_assistant *); -static void on_quote_cb_toggle (GtkToggleButton *quote_cb, - struct import_assistant *); -static void on_separator_toggle (GtkToggleButton *, struct import_assistant *); static void render_input_cell (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter, gpointer ia); + static gboolean on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy, gboolean keyboard_mode UNUSED, GtkTooltip *tooltip, struct import_assistant *); -/* A common field separator and its identifying name. */ -struct separator - { - const char *name; /* Name (for use with get_widget_assert). */ - int c; /* Separator character. */ - }; - -/* All the separators in the dialog box. */ -static const struct separator separators[] = - { - {"space", ' '}, - {"tab", '\t'}, - {"bang", '!'}, - {"colon", ':'}, - {"comma", ','}, - {"hyphen", '-'}, - {"pipe", '|'}, - {"semicolon", ';'}, - {"slash", '/'}, - }; -#define SEPARATOR_CNT (sizeof separators / sizeof *separators) - -static void -set_quote_list (GtkComboBoxEntry *cb) -{ - GtkListStore *list = gtk_list_store_new (1, G_TYPE_STRING); - GtkTreeIter iter; - gint i; - const gchar *seperator[3] = {"'\"", "\'", "\""}; - - for (i = 0; i < 3; i++) - { - const gchar *s = seperator[i]; - - /* Add a new row to the model */ - gtk_list_store_append (list, &iter); - gtk_list_store_set (list, &iter, - 0, s, - -1); - - } - - gtk_combo_box_set_model (GTK_COMBO_BOX (cb), GTK_TREE_MODEL (list)); - g_object_unref (list); - - gtk_combo_box_entry_set_text_column (cb, 0); -} - -/* Initializes IA's separators substructure. */ -static void -init_separators_page (struct import_assistant *ia) -{ - GtkBuilder *builder = ia->asst.builder; - struct separators_page *p = &ia->separators; - size_t i; - - choose_likely_separators (ia); - - p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Separators"), - GTK_ASSISTANT_PAGE_CONTENT); - p->custom_cb = get_widget_assert (builder, "custom-cb"); - p->custom_entry = get_widget_assert (builder, "custom-entry"); - p->quote_combo = get_widget_assert (builder, "quote-combo"); - p->quote_entry = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (p->quote_combo))); - p->quote_cb = get_widget_assert (builder, "quote-cb"); - p->escape_cb = get_widget_assert (builder, "escape"); - - set_separators (ia); - set_quote_list (GTK_COMBO_BOX_ENTRY (p->quote_combo)); - p->fields_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "fields")); - g_signal_connect (p->quote_combo, "changed", - G_CALLBACK (on_quote_combo_change), ia); - g_signal_connect (p->quote_cb, "toggled", - G_CALLBACK (on_quote_cb_toggle), ia); - g_signal_connect (p->custom_entry, "notify::text", - G_CALLBACK (on_separators_custom_entry_notify), ia); - g_signal_connect (p->custom_cb, "toggled", - G_CALLBACK (on_separators_custom_cb_toggle), ia); - for (i = 0; i < SEPARATOR_CNT; i++) - g_signal_connect (get_widget_assert (builder, separators[i].name), - "toggled", G_CALLBACK (on_separator_toggle), ia); - g_signal_connect (p->escape_cb, "toggled", - G_CALLBACK (on_separator_toggle), ia); -} - -/* Frees IA's separators substructure. */ -static void -destroy_separators_page (struct import_assistant *ia) -{ - struct separators_page *s = &ia->separators; - - ds_destroy (&s->separators); - ds_destroy (&s->quotes); - clear_fields (ia); -} - -/* Called just before the separators page becomes visible in the - assistant. */ -static void -prepare_separators_page (struct import_assistant *ia) -{ - revise_fields_preview (ia); -} - -/* Called when the Reset button is clicked on the separators - page, resets the separators to the defaults. */ -static void -reset_separators_page (struct import_assistant *ia) -{ - choose_likely_separators (ia); - set_separators (ia); -} - -/* Frees and clears the column data in IA's separators - substructure. */ -static void -clear_fields (struct import_assistant *ia) -{ - struct separators_page *s = &ia->separators; - - if (s->column_cnt > 0) - { - struct column *col; - size_t row; - - for (row = 0; row < ia->file.line_cnt; row++) - { - const struct string *line = &ia->file.lines[row]; - const char *line_start = ds_data (line); - const char *line_end = ds_end (line); - - for (col = s->columns; col < &s->columns[s->column_cnt]; col++) - { - char *s = ss_data (col->contents[row]); - if (!(s >= line_start && s <= line_end)) - ss_dealloc (&col->contents[row]); - } - } - - for (col = s->columns; col < &s->columns[s->column_cnt]; col++) - { - free (col->name); - free (col->contents); - } - - free (s->columns); - s->columns = NULL; - s->column_cnt = 0; - } -} - -/* Breaks the file data in IA into columns based on the - separators set in IA's separators substructure. */ -static void -split_fields (struct import_assistant *ia) -{ - struct separators_page *s = &ia->separators; - size_t columns_allocated; - bool space_sep; - size_t row; - - clear_fields (ia); - - /* Is space in the set of separators? */ - space_sep = ss_find_byte (ds_ss (&s->separators), ' ') != SIZE_MAX; - - /* Split all the lines, not just those from - ia->first_line.skip_lines on, so that we split the line that - contains variables names if ia->first_line.variable_names is - true. */ - columns_allocated = 0; - for (row = 0; row < ia->file.line_cnt; row++) - { - struct string *line = &ia->file.lines[row]; - struct substring text = ds_ss (line); - size_t column_idx; - - for (column_idx = 0; ; column_idx++) - { - struct substring field; - struct column *column; - - if (space_sep) - ss_ltrim (&text, ss_cstr (" ")); - if (ss_is_empty (text)) - { - if (column_idx != 0) - break; - field = text; - } - else if (!ds_is_empty (&s->quotes) - && ds_find_byte (&s->quotes, text.string[0]) != SIZE_MAX) - { - int quote = ss_get_byte (&text); - if (!s->escape) - ss_get_until (&text, quote, &field); - else - { - struct string s; - int c; - - ds_init_empty (&s); - while ((c = ss_get_byte (&text)) != EOF) - if (c != quote) - ds_put_byte (&s, c); - else if (ss_match_byte (&text, quote)) - ds_put_byte (&s, quote); - else - break; - field = ds_ss (&s); - } - } - else - ss_get_bytes (&text, ss_cspan (text, ds_ss (&s->separators)), - &field); - - if (column_idx >= s->column_cnt) - { - struct column *column; - - if (s->column_cnt >= columns_allocated) - s->columns = x2nrealloc (s->columns, &columns_allocated, - sizeof *s->columns); - column = &s->columns[s->column_cnt++]; - column->name = NULL; - column->width = 0; - column->contents = xcalloc (ia->file.line_cnt, - sizeof *column->contents); - } - column = &s->columns[column_idx]; - column->contents[row] = field; - if (ss_length (field) > column->width) - column->width = ss_length (field); - - if (space_sep) - ss_ltrim (&text, ss_cstr (" ")); - if (ss_is_empty (text)) - break; - if (ss_find_byte (ds_ss (&s->separators), ss_first (text)) - != SIZE_MAX) - ss_advance (&text, 1); - } - } -} - -/* Chooses a name for each column on the separators page */ -static void -choose_column_names (struct import_assistant *ia) -{ - const struct first_line_page *f = &ia->first_line; - struct separators_page *s = &ia->separators; - struct dictionary *dict; - unsigned long int generated_name_count = 0; - struct column *col; - size_t name_row; - - dict = dict_create (get_default_encoding ()); - name_row = f->variable_names && f->skip_lines ? f->skip_lines : 0; - for (col = s->columns; col < &s->columns[s->column_cnt]; col++) - { - char *hint, *name; - - hint = name_row ? ss_xstrdup (col->contents[name_row - 1]) : NULL; - name = dict_make_unique_var_name (dict, hint, &generated_name_count); - free (hint); - - col->name = name; - dict_create_var_assert (dict, name, 0); - } - dict_destroy (dict); -} - -/* Picks the most likely separator and quote characters based on - IA's file data. */ -static void -choose_likely_separators (struct import_assistant *ia) -{ - unsigned long int histogram[UCHAR_MAX + 1] = { 0 }; - size_t row; - - /* Construct a histogram of all the characters used in the - file. */ - for (row = 0; row < ia->file.line_cnt; row++) - { - struct substring line = ds_ss (&ia->file.lines[row]); - size_t length = ss_length (line); - size_t i; - for (i = 0; i < length; i++) - histogram[(unsigned char) line.string[i]]++; - } - - find_commonest_chars (histogram, "\"'", "", &ia->separators.quotes); - find_commonest_chars (histogram, ",;:/|!\t-", ",", - &ia->separators.separators); - ia->separators.escape = true; -} - -/* Chooses the most common character among those in TARGETS, - based on the frequency data in HISTOGRAM, and stores it in - RESULT. If there is a tie for the most common character among - those in TARGETS, the earliest character is chosen. If none - of the TARGETS appear at all, then DEF is used as a - fallback. */ -static void -find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1], - const char *targets, const char *def, - struct string *result) -{ - unsigned char max = 0; - unsigned long int max_count = 0; - - for (; *targets != '\0'; targets++) - { - unsigned char c = *targets; - unsigned long int count = histogram[c]; - if (count > max_count) - { - max = c; - max_count = count; - } - } - if (max_count > 0) - { - ds_clear (result); - ds_put_byte (result, max); - } - else - ds_assign_cstr (result, def); -} - -/* Revises the contents of the fields tree view based on the - currently chosen set of separators. */ -static void -revise_fields_preview (struct import_assistant *ia) -{ - GtkWidget *w; - - push_watch_cursor (ia); - - w = GTK_WIDGET (ia->separators.fields_tree_view); - gtk_widget_destroy (w); - get_separators (ia); - split_fields (ia); - choose_column_names (ia); - ia->separators.fields_tree_view = create_data_tree_view ( - true, - GTK_CONTAINER (get_widget_assert (ia->asst.builder, "fields-scroller")), - ia); - - pop_watch_cursor (ia); -} - -/* Sets the widgets to match IA's separators substructure. */ -static void -set_separators (struct import_assistant *ia) -{ - struct separators_page *s = &ia->separators; - unsigned int seps; - struct string custom; - bool any_custom; - bool any_quotes; - size_t i; - - ds_init_empty (&custom); - seps = 0; - for (i = 0; i < ds_length (&s->separators); i++) - { - unsigned char c = ds_at (&s->separators, i); - int j; - - for (j = 0; j < SEPARATOR_CNT; j++) - { - const struct separator *s = &separators[j]; - if (s->c == c) - { - seps += 1u << j; - goto next; - } - } - - ds_put_byte (&custom, c); - next:; - } - - for (i = 0; i < SEPARATOR_CNT; i++) - { - const struct separator *s = &separators[i]; - GtkWidget *button = get_widget_assert (ia->asst.builder, s->name); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), - (seps & (1u << i)) != 0); - } - any_custom = !ds_is_empty (&custom); - gtk_entry_set_text (GTK_ENTRY (s->custom_entry), ds_cstr (&custom)); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->custom_cb), - any_custom); - gtk_widget_set_sensitive (s->custom_entry, any_custom); - ds_destroy (&custom); - - any_quotes = !ds_is_empty (&s->quotes); - - gtk_entry_set_text (s->quote_entry, - any_quotes ? ds_cstr (&s->quotes) : "\""); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->quote_cb), - any_quotes); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (s->escape_cb), - s->escape); - gtk_widget_set_sensitive (s->quote_combo, any_quotes); - gtk_widget_set_sensitive (s->escape_cb, any_quotes); -} - -/* Sets IA's separators substructure to match the widgets. */ -static void -get_separators (struct import_assistant *ia) -{ - struct separators_page *s = &ia->separators; - int i; - - ds_clear (&s->separators); - for (i = 0; i < SEPARATOR_CNT; i++) - { - const struct separator *sep = &separators[i]; - GtkWidget *button = get_widget_assert (ia->asst.builder, sep->name); - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) - ds_put_byte (&s->separators, sep->c); - } - - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->custom_cb))) - ds_put_cstr (&s->separators, - gtk_entry_get_text (GTK_ENTRY (s->custom_entry))); - - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->quote_cb))) - { - gchar *text = gtk_combo_box_get_active_text ( - GTK_COMBO_BOX (s->quote_combo)); - ds_assign_cstr (&s->quotes, text); - g_free (text); - } - else - ds_clear (&s->quotes); - s->escape = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (s->escape_cb)); -} -/* Called when the user changes the entry field for custom - separators. */ -static void -on_separators_custom_entry_notify (GObject *gobject UNUSED, - GParamSpec *arg1 UNUSED, - struct import_assistant *ia) -{ - revise_fields_preview (ia); -} - -/* Called when the user toggles the checkbox that enables custom - separators. */ -static void -on_separators_custom_cb_toggle (GtkToggleButton *custom_cb, - struct import_assistant *ia) -{ - bool is_active = gtk_toggle_button_get_active (custom_cb); - gtk_widget_set_sensitive (ia->separators.custom_entry, is_active); - revise_fields_preview (ia); -} - -/* Called when the user changes the selection in the combo box - that selects a quote character. */ -static void -on_quote_combo_change (GtkComboBox *combo, struct import_assistant *ia) -{ - revise_fields_preview (ia); -} - -/* Called when the user toggles the checkbox that enables - quoting. */ -static void -on_quote_cb_toggle (GtkToggleButton *quote_cb, struct import_assistant *ia) -{ - bool is_active = gtk_toggle_button_get_active (quote_cb); - gtk_widget_set_sensitive (ia->separators.quote_combo, is_active); - gtk_widget_set_sensitive (ia->separators.escape_cb, is_active); - revise_fields_preview (ia); -} - -/* Called when the user toggles one of the separators - checkboxes. */ -static void -on_separator_toggle (GtkToggleButton *toggle UNUSED, - struct import_assistant *ia) -{ - revise_fields_preview (ia); -} /* Called to render one of the cells in the fields preview tree view. */ @@ -1563,8 +277,8 @@ render_input_cell (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, column = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column), "column-number")); - row = empty_list_store_iter_to_row (iter) + ia->first_line.skip_lines; - field = ia->separators.columns[column].contents[row]; + row = empty_list_store_iter_to_row (iter) + ia->skip_lines; + field = ia->columns[column].contents[row]; if (field.string != NULL) { GValue text = {0, }; @@ -1582,6 +296,12 @@ render_input_cell (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, (void *) NULL); } +static gboolean +get_tooltip_location (GtkWidget *widget, gint wx, gint wy, + const struct import_assistant *ia, + size_t *row, size_t *column); + + /* Called to render a tooltip on one of the cells in the fields preview tree view. */ static gboolean @@ -1594,7 +314,7 @@ on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy, if (!get_tooltip_location (widget, wx, wy, ia, &row, &column)) return FALSE; - if (ia->separators.columns[column].contents[row].string != NULL) + if (ia->columns[column].contents[row].string != NULL) return FALSE; gtk_tooltip_set_text (tooltip, @@ -1602,204 +322,7 @@ on_query_input_tooltip (GtkWidget *widget, gint wx, gint wy, "to fill in this field.")); return TRUE; } - -/* The "formats" page of the assistant. */ -static void on_variable_change (PsppireDict *dict, int idx, - struct import_assistant *); -static void clear_modified_vars (struct import_assistant *); - -/* Initializes IA's formats substructure. */ -static void -init_formats_page (struct import_assistant *ia) -{ - GtkBuilder *builder = ia->asst.builder; - struct formats_page *p = &ia->formats; - - p->page = add_page_to_assistant (ia, get_widget_assert (builder, "Formats"), - GTK_ASSISTANT_PAGE_CONFIRM); - p->data_tree_view = GTK_TREE_VIEW (get_widget_assert (builder, "data")); - p->modified_vars = NULL; - p->modified_var_cnt = 0; - p->dict = NULL; -} - -/* Frees IA's formats substructure. */ -static void -destroy_formats_page (struct import_assistant *ia) -{ - struct formats_page *p = &ia->formats; - - if (p->psppire_dict != NULL) - { - dict_destroy (p->psppire_dict->dict); - g_object_unref (p->psppire_dict); - } - clear_modified_vars (ia); -} - -/* Called just before the formats page of the assistant is - displayed. */ -static void -prepare_formats_page (struct import_assistant *ia) -{ - struct dictionary *dict; - PsppireDict *psppire_dict; - PsppireVarStore *var_store; - GtkBin *vars_scroller; - GtkWidget *old_var_sheet; - PsppireVarSheet *var_sheet; - struct separators_page *s = &ia->separators; - struct formats_page *p = &ia->formats; - struct fmt_guesser *fg; - unsigned long int number = 0; - size_t column_idx; - - push_watch_cursor (ia); - - dict = dict_create (get_default_encoding ()); - fg = fmt_guesser_create (); - for (column_idx = 0; column_idx < s->column_cnt; column_idx++) - { - struct variable *modified_var; - - modified_var = (column_idx < p->modified_var_cnt - ? p->modified_vars[column_idx] : NULL); - if (modified_var == NULL) - { - struct column *column = &s->columns[column_idx]; - struct variable *var; - struct fmt_spec format; - char *name; - size_t row; - - /* Choose variable name. */ - name = dict_make_unique_var_name (dict, column->name, &number); - - /* Choose variable format. */ - fmt_guesser_clear (fg); - for (row = ia->first_line.skip_lines; row < ia->file.line_cnt; row++) - fmt_guesser_add (fg, column->contents[row]); - fmt_guesser_guess (fg, &format); - fmt_fix_input (&format); - - /* Create variable. */ - var = dict_create_var_assert (dict, name, fmt_var_width (&format)); - var_set_both_formats (var, &format); - - free (name); - } - else - { - char *name; - - name = dict_make_unique_var_name (dict, var_get_name (modified_var), - &number); - dict_clone_var_as_assert (dict, modified_var, name); - free (name); - } - } - fmt_guesser_destroy (fg); - - psppire_dict = psppire_dict_new_from_dict (dict); - g_signal_connect (psppire_dict, "variable_changed", - G_CALLBACK (on_variable_change), ia); - ia->formats.dict = dict; - ia->formats.psppire_dict = psppire_dict; - - /* XXX: PsppireVarStore doesn't hold a reference to - psppire_dict for now, but it should. After it does, we - should g_object_ref the psppire_dict here, since we also - hold a reference via ia->formats.dict. */ - var_store = psppire_var_store_new (psppire_dict); - g_object_set (var_store, - "format-type", PSPPIRE_VAR_STORE_INPUT_FORMATS, - (void *) NULL); - var_sheet = PSPPIRE_VAR_SHEET (psppire_var_sheet_new ()); - g_object_set (var_sheet, - "model", var_store, - "may-create-vars", FALSE, - (void *) NULL); - - vars_scroller = GTK_BIN (get_widget_assert (ia->asst.builder, "vars-scroller")); - old_var_sheet = gtk_bin_get_child (vars_scroller); - if (old_var_sheet != NULL) - gtk_widget_destroy (old_var_sheet); - gtk_container_add (GTK_CONTAINER (vars_scroller), GTK_WIDGET (var_sheet)); - gtk_widget_show (GTK_WIDGET (var_sheet)); - - gtk_widget_destroy (GTK_WIDGET (ia->formats.data_tree_view)); - ia->formats.data_tree_view = create_data_tree_view ( - false, - GTK_CONTAINER (get_widget_assert (ia->asst.builder, "data-scroller")), - ia); - - pop_watch_cursor (ia); -} - -/* Clears the set of user-modified variables from IA's formats - substructure. This discards user modifications to variable - formats, thereby causing formats to revert to their - defaults. */ -static void -clear_modified_vars (struct import_assistant *ia) -{ - struct formats_page *p = &ia->formats; - size_t i; - - for (i = 0; i < p->modified_var_cnt; i++) - var_destroy (p->modified_vars[i]); - free (p->modified_vars); - p->modified_vars = NULL; - p->modified_var_cnt = 0; -} - -/* Resets the formats page to its defaults, discarding user - modifications. */ -static void -reset_formats_page (struct import_assistant *ia) -{ - clear_modified_vars (ia); - prepare_formats_page (ia); -} - -/* Called when the user changes one of the variables in the - dictionary. */ -static void -on_variable_change (PsppireDict *dict, int dict_idx, - struct import_assistant *ia) -{ - struct formats_page *p = &ia->formats; - GtkTreeView *tv = ia->formats.data_tree_view; - gint column_idx = dict_idx + 1; - - push_watch_cursor (ia); - - /* Remove previous column and replace with new column. */ - gtk_tree_view_remove_column (tv, gtk_tree_view_get_column (tv, column_idx)); - gtk_tree_view_insert_column (tv, make_data_column (ia, tv, false, dict_idx), - column_idx); - - /* Save a copy of the modified variable in modified_vars, so - that its attributes will be preserved if we back up to the - previous page with the Prev button and then come back - here. */ - if (dict_idx >= p->modified_var_cnt) - { - size_t i; - p->modified_vars = xnrealloc (p->modified_vars, dict_idx + 1, - sizeof *p->modified_vars); - for (i = 0; i <= dict_idx; i++) - p->modified_vars[i] = NULL; - p->modified_var_cnt = dict_idx + 1; - } - if (p->modified_vars[dict_idx]) - var_destroy (p->modified_vars[dict_idx]); - p->modified_vars[dict_idx] - = var_clone (psppire_dict_get_variable (dict, dict_idx)); - - pop_watch_cursor (ia); -} /* Parses the contents of the field at (ROW,COLUMN) according to its variable format. If OUTPUTP is non-null, then *OUTPUTP @@ -1813,16 +336,15 @@ parse_field (struct import_assistant *ia, size_t row, size_t column, char **outputp, char **tooltipp) { - struct substring field; - union value val; - struct variable *var; const struct fmt_spec *in; struct fmt_spec out; char *tooltip; bool ok; - field = ia->separators.columns[column].contents[row]; - var = dict_get_var (ia->formats.dict, column); + struct substring field = ia->columns[column].contents[row]; + struct variable *var = dict_get_var (ia->dict, column); + union value val; + value_init (&val, var_get_width (var)); in = var_get_print_format (var); out = fmt_for_output_from_input (in); @@ -1832,7 +354,7 @@ parse_field (struct import_assistant *ia, char *error; error = data_in (field, "UTF-8", in->type, &val, var_get_width (var), - dict_get_encoding (ia->formats.dict)); + dict_get_encoding (ia->dict)); if (error != NULL) { tooltip = xasprintf (_("Cannot parse field content `%.*s' as " @@ -1850,7 +372,7 @@ parse_field (struct import_assistant *ia, } if (outputp != NULL) { - *outputp = data_out (&val, dict_get_encoding (ia->formats.dict), &out); + *outputp = data_out (&val, dict_get_encoding (ia->dict), &out); } value_destroy (&val, var_get_width (var)); @@ -1878,7 +400,7 @@ render_output_cell (GtkTreeViewColumn *tree_column, ok = parse_field (ia, (empty_list_store_iter_to_row (iter) - + ia->first_line.skip_lines), + + ia->skip_lines), GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_column), "column-number")), &output, NULL); @@ -1901,8 +423,8 @@ render_output_cell (GtkTreeViewColumn *tree_column, preview tree view. */ static gboolean on_query_output_tooltip (GtkWidget *widget, gint wx, gint wy, - gboolean keyboard_mode UNUSED, - GtkTooltip *tooltip, struct import_assistant *ia) + gboolean keyboard_mode UNUSED, + GtkTooltip *tooltip, struct import_assistant *ia) { size_t row, column; char *text; @@ -1961,11 +483,11 @@ get_tooltip_location (GtkWidget *widget, gint wx, gint wy, if (!ok) return FALSE; - *row = empty_list_store_iter_to_row (&iter) + ia->first_line.skip_lines; + *row = empty_list_store_iter_to_row (&iter) + ia->skip_lines; return TRUE; } -static void +void make_tree_view (const struct import_assistant *ia, size_t first_line, GtkTreeView **tree_view) @@ -1974,7 +496,7 @@ make_tree_view (const struct import_assistant *ia, *tree_view = GTK_TREE_VIEW (gtk_tree_view_new ()); model = GTK_TREE_MODEL (psppire_empty_list_store_new ( - ia->file.line_cnt - first_line)); + ia->file.line_cnt - first_line)); g_object_set_data (G_OBJECT (model), "lines", ia->file.lines + first_line); g_object_set_data (G_OBJECT (model), "first-line", GINT_TO_POINTER (first_line)); @@ -2008,17 +530,17 @@ add_line_number_column (const struct import_assistant *ia, GtkTreeViewColumn *column; column = gtk_tree_view_column_new_with_attributes ( - _("Line"), ia->asst.prop_renderer, (void *) NULL); + _("Line"), ia->asst.prop_renderer, (void *) NULL); gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED); gtk_tree_view_column_set_fixed_width ( - column, get_monospace_width (treeview, ia->asst.prop_renderer, 5)); + column, get_monospace_width (treeview, ia->asst.prop_renderer, 5)); gtk_tree_view_column_set_resizable (column, TRUE); gtk_tree_view_column_set_cell_data_func (column, ia->asst.prop_renderer, render_line_number, NULL, NULL); gtk_tree_view_append_column (treeview, column); } -static gint +gint get_monospace_width (GtkTreeView *treeview, GtkCellRenderer *renderer, size_t char_cnt) { @@ -2034,7 +556,7 @@ get_monospace_width (GtkTreeView *treeview, GtkCellRenderer *renderer, return width; } -static gint +gint get_string_width (GtkTreeView *treeview, GtkCellRenderer *renderer, const char *string) { @@ -2045,7 +567,7 @@ get_string_width (GtkTreeView *treeview, GtkCellRenderer *renderer, return width; } -static GtkTreeViewColumn * +GtkTreeViewColumn * make_data_column (struct import_assistant *ia, GtkTreeView *tree_view, bool input, gint dict_idx) { @@ -2057,9 +579,9 @@ make_data_column (struct import_assistant *ia, GtkTreeView *tree_view, char *name; if (input) - column = &ia->separators.columns[dict_idx]; + column = &ia->columns[dict_idx]; else - var = dict_get_var (ia->formats.dict, dict_idx); + var = dict_get_var (ia->dict, dict_idx); name = escape_underscores (input ? column->name : var_get_name (var)); char_cnt = input ? column->width : var_get_print_format (var)->w; @@ -2075,8 +597,8 @@ make_data_column (struct import_assistant *ia, GtkTreeView *tree_view, gtk_tree_view_column_pack_start (tree_column, ia->asst.fixed_renderer, FALSE); gtk_tree_view_column_set_cell_data_func ( - tree_column, ia->asst.fixed_renderer, - input ? render_input_cell : render_output_cell, ia, NULL); + tree_column, ia->asst.fixed_renderer, + input ? render_input_cell : render_output_cell, ia, NULL); gtk_tree_view_column_set_sizing (tree_column, GTK_TREE_VIEW_COLUMN_FIXED); gtk_tree_view_column_set_fixed_width (tree_column, MAX (content_width, header_width)); @@ -2086,18 +608,18 @@ make_data_column (struct import_assistant *ia, GtkTreeView *tree_view, return tree_column; } -static GtkTreeView * +GtkTreeView * create_data_tree_view (bool input, GtkContainer *parent, struct import_assistant *ia) { GtkTreeView *tree_view; gint i; - make_tree_view (ia, ia->first_line.skip_lines, &tree_view); + make_tree_view (ia, ia->skip_lines, &tree_view); gtk_tree_selection_set_mode (gtk_tree_view_get_selection (tree_view), GTK_SELECTION_NONE); - for (i = 0; i < ia->separators.column_cnt; i++) + for (i = 0; i < ia->column_cnt; i++) gtk_tree_view_append_column (tree_view, make_data_column (ia, tree_view, input, i)); @@ -2116,7 +638,7 @@ create_data_tree_view (bool input, GtkContainer *parent, /* Increments the "watch cursor" level, setting the cursor for the assistant window to a watch face to indicate to the user that the ongoing operation may take some time. */ -static void +void push_watch_cursor (struct import_assistant *ia) { if (++ia->asst.watch_cursor == 1) @@ -2132,7 +654,7 @@ push_watch_cursor (struct import_assistant *ia) /* Decrements the "watch cursor" level. If the level reaches zero, the cursor is reset to its default shape. */ -static void +void pop_watch_cursor (struct import_assistant *ia) { if (--ia->asst.watch_cursor == 0) diff --git a/src/ui/gui/text-data-import-dialog.h b/src/ui/gui/text-data-import-dialog.h index 664249c964..0c026858fe 100644 --- a/src/ui/gui/text-data-import-dialog.h +++ b/src/ui/gui/text-data-import-dialog.h @@ -1,5 +1,5 @@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2008, 2010, 2011 Free Software Foundation + Copyright (C) 2008, 2010, 2011, 2013 Free Software Foundation This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,7 +19,149 @@ #include #include "ui/gui/psppire-data-window.h" +#include "data/spreadsheet-reader.h" + +#include "libpspp/str.h" + +enum { MAX_PREVIEW_LINES = 1000 }; /* Max number of lines to read. */ + +struct assist_page +{ + GtkWidget *page; +}; + +/* The file to be imported. */ +struct file + { + char *file_name; /* File name. */ + + /* Relevant only for text files */ + + gchar *encoding; /* Encoding. */ + unsigned long int total_lines; /* Number of lines in file. */ + bool total_is_exact; /* Is total_lines exact (or an estimate)? */ + + /* The first several lines of the file. */ + struct string *lines; + size_t line_cnt; + }; + +/* The main body of the GTK+ assistant and related data. */ +struct assistant + { + GtkBuilder *builder; + GtkAssistant *assistant; + GMainLoop *main_loop; + bool loop_done; + GtkWidget *paste_button; + GtkWidget *reset_button; + int response; + int watch_cursor; + + GtkCellRenderer *prop_renderer; + GtkCellRenderer *fixed_renderer; + }; + +struct import_assistant + { + struct file file; + struct assistant asst; + + struct intro_page *intro; + struct sheet_spec_page *sheet_spec; + struct first_line_page *first_line; + struct separators_page *separators; + struct formats_page *formats; + + /* The columns produced. */ + struct column *columns; /* Information about each column. */ + size_t column_cnt; /* Number of columns. */ + + int skip_lines; /* Number of initial lines to skip? */ + bool variable_names; /* Variable names above first line of data? */ + struct dictionary *dict; + + struct spreadsheet *spreadsheet; + }; + +struct column + { + /* Variable name for this column. This is the variable name + used on the separators page; it can be overridden by the + user on the formats page. */ + char *name; + + /* Maximum length of any row in this column. */ + size_t width; + + /* Contents of this column: contents[row] is the contents for + the given row. + + A null substring indicates a missing column for that row + (because the line contains an insufficient number of + separators). + + contents[] elements may be substrings of the lines[] + strings that represent the whole lines of the file, to + save memory. Other elements are dynamically allocated + with ss_alloc_substring. */ + struct substring *contents; + }; + + +GtkWidget * add_page_to_assistant (struct import_assistant *ia, + GtkWidget *page, GtkAssistantPageType type); void text_data_import_assistant (PsppireDataWindow *); +/* FIXME: Should this be private to first line page ? */ +void make_tree_view (const struct import_assistant *ia, + size_t first_line, + GtkTreeView **tree_view); + +gint get_monospace_width (GtkTreeView *, GtkCellRenderer *, + size_t char_cnt); +gint get_string_width (GtkTreeView *, GtkCellRenderer *, + const char *string); + + + +void push_watch_cursor (struct import_assistant *); +void pop_watch_cursor (struct import_assistant *); + + +GtkTreeView *create_data_tree_view (bool input, GtkContainer *parent, + struct import_assistant *); + +GtkTreeViewColumn *make_data_column (struct import_assistant *, + GtkTreeView *, bool input, + gint column_idx); + + +void update_assistant (struct import_assistant *ia); + +bool init_file (struct import_assistant *ia, GtkWindow *parent_window); +void destroy_file (struct import_assistant *ia); + +void prepare_sheet_spec_page (struct import_assistant *ia); +void reset_sheet_spec_page (struct import_assistant *); +void post_sheet_spec_page (struct import_assistant *ia); + +void prepare_first_line_page (struct import_assistant *ia); +void reset_first_line_page (struct import_assistant *); + +void reset_intro_page (struct import_assistant *ia); + +void prepare_separators_page (struct import_assistant *ia); +void reset_separators_page (struct import_assistant *); +void destroy_separators_page (struct import_assistant *ia); + +void prepare_formats_page (struct import_assistant *ia); +void reset_formats_page (struct import_assistant *); +void destroy_formats_page (struct import_assistant *ia); + +struct import_assistant * init_assistant (GtkWindow *); +void destroy_assistant (struct import_assistant *); + + #endif