From 50feac63e2fb9776f53e432f137311b4f5563ccf Mon Sep 17 00:00:00 2001 From: John Darrington Date: Thu, 20 Apr 2017 16:29:56 +0200 Subject: [PATCH] New objects PsppireTextFile and PsppireDelimitedText. These objects take care of the spliting of delimited text data and allow the Import assistant to just do the job of presenting dialog boxes. --- src/ui/gui/automake.mk | 4 + src/ui/gui/psppire-data-window.c | 4 +- src/ui/gui/psppire-delimited-text.c | 535 +++++++++++++++++++ src/ui/gui/psppire-delimited-text.h | 89 ++++ src/ui/gui/psppire-import-assistant.c | 707 ++++++++++---------------- src/ui/gui/psppire-import-assistant.h | 25 +- src/ui/gui/psppire-text-file.c | 519 +++++++++++++++++++ src/ui/gui/psppire-text-file.h | 88 ++++ 8 files changed, 1501 insertions(+), 470 deletions(-) create mode 100644 src/ui/gui/psppire-delimited-text.c create mode 100644 src/ui/gui/psppire-delimited-text.h create mode 100644 src/ui/gui/psppire-text-file.c create mode 100644 src/ui/gui/psppire-text-file.h diff --git a/src/ui/gui/automake.mk b/src/ui/gui/automake.mk index f23102b74d..906679c4e2 100644 --- a/src/ui/gui/automake.mk +++ b/src/ui/gui/automake.mk @@ -283,6 +283,10 @@ src_ui_gui_psppire_SOURCES = \ src/ui/gui/psppire-select-dest.h \ src/ui/gui/psppire-syntax-window.c \ src/ui/gui/psppire-syntax-window.h \ + src/ui/gui/psppire-delimited-text.c \ + src/ui/gui/psppire-delimited-text.h \ + src/ui/gui/psppire-text-file.c \ + src/ui/gui/psppire-text-file.h \ src/ui/gui/psppire-val-chooser.c \ src/ui/gui/psppire-val-chooser.h \ src/ui/gui/psppire-value-entry.c \ diff --git a/src/ui/gui/psppire-data-window.c b/src/ui/gui/psppire-data-window.c index 091af6503e..c0da89e92f 100644 --- a/src/ui/gui/psppire-data-window.c +++ b/src/ui/gui/psppire-data-window.c @@ -974,8 +974,10 @@ file_import (PsppireDataWindow *dw) g_main_loop_run (asst->main_loop); g_main_loop_unref (asst->main_loop); +#if TEXT_FILE if (!asst->file_name) goto end; + switch (asst->response) { @@ -992,7 +994,7 @@ file_import (PsppireDataWindow *dw) default: break; } - +#endif end: gtk_widget_destroy (GTK_WIDGET (asst)); } diff --git a/src/ui/gui/psppire-delimited-text.c b/src/ui/gui/psppire-delimited-text.c new file mode 100644 index 0000000000..306fb69bfd --- /dev/null +++ b/src/ui/gui/psppire-delimited-text.c @@ -0,0 +1,535 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2017 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 +#define _(msgid) gettext (msgid) +#define P_(msgid) msgid + +#include "psppire-delimited-text.h" +#include "psppire-text-file.h" +#include "libpspp/str.h" +#include "libpspp/i18n.h" + +#include + +/* Properties */ +enum + { + PROP_0, + PROP_CHILD, + PROP_DELIMITERS, + PROP_FIRST_LINE + }; + +static void +count_delims (PsppireDelimitedText *tf) +{ + if (tf->child) + { + tf->max_delimiters = 0; + GtkTreeIter iter; + gboolean valid; + for (valid = gtk_tree_model_get_iter_first (tf->child, &iter); + valid; + valid = gtk_tree_model_iter_next (tf->child, &iter)) + { + // FIXME: Box these lines to avoid constant allocation/deallocation + gchar *foo = 0; + gtk_tree_model_get (tf->child, &iter, 1, &foo, -1); + { + char *line = foo; + gint count = 0; + while (*line) + { + GSList *del; + for (del = tf->delimiters; del; del = g_slist_next (del)) + { + if (*line == GPOINTER_TO_INT (del->data)) + count++; + } + line++; + } + tf->max_delimiters = MAX (tf->max_delimiters, count); + } + g_free (foo); + } + } + // g_print ("Max Number of delimiters per row: %d\n", tf->max_delimiters); +} + +static void +psppire_delimited_text_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + PsppireDelimitedText *tf = PSPPIRE_DELIMITED_TEXT (object); + + switch (prop_id) + { + case PROP_FIRST_LINE: + tf->first_line = g_value_get_int (value); + if (tf->const_cache.string) + { + ss_dealloc (&tf->const_cache); + tf->cache_row = -1; + } + break; + case PROP_CHILD: + tf->child = g_value_get_object (value); + break; + case PROP_DELIMITERS: + g_slist_free (tf->delimiters); + tf->delimiters = g_slist_copy (g_value_get_pointer (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + }; + + if (tf->child) + count_delims (tf); +} + +static void +psppire_delimited_text_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + PsppireDelimitedText *text_file = PSPPIRE_DELIMITED_TEXT (object); + + switch (prop_id) + { + case PROP_FIRST_LINE: + g_value_set_int (value, text_file->first_line); + break; + case PROP_DELIMITERS: + g_value_set_pointer (value, text_file->delimiters); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + }; +} + + +static void psppire_delimited_text_init (PsppireDelimitedText *text_file); +static void psppire_delimited_text_class_init (PsppireDelimitedTextClass *class); + +static void psppire_delimited_text_finalize (GObject *object); +static void psppire_delimited_text_dispose (GObject *object); + +static GObjectClass *parent_class = NULL; + + +static gboolean +__tree_get_iter (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreePath *path) +{ + PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model); + if (path == NULL) + return FALSE; + + // g_print ("%s:%d %s %s\n", __FILE__, __LINE__, __FUNCTION__, gtk_tree_path_to_string (path)); + + gint *indices = gtk_tree_path_get_indices (path); + + if (!indices) + return FALSE; + + gint n = *indices; + + gint children = gtk_tree_model_iter_n_children (file->child, NULL); + + if (n >= children - file->first_line) + return FALSE; + + // g_print ("%s:%d %s %d Children: %d\n", __FILE__, __LINE__, __FUNCTION__, n, children); + + iter->user_data = GINT_TO_POINTER (n); + iter->stamp = file->stamp; + + return TRUE; +} + + +static gboolean +__tree_iter_next (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model); + g_return_val_if_fail (file->stamp == iter->stamp, FALSE); + + gint n = GPOINTER_TO_INT (iter->user_data); + + // g_print ("%s:%d %s %d\n", __FILE__, __LINE__, __FUNCTION__, n); + + gint children = gtk_tree_model_iter_n_children (file->child, NULL); + + if (n + 1 >= children - file->first_line) + return FALSE; + + iter->user_data = GINT_TO_POINTER (n + 1); + + return TRUE; +} + + +static GType +__tree_get_column_type (GtkTreeModel *tree_model, + gint index) +{ + // g_print ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + if (index == 0) + return G_TYPE_INT; + + return G_TYPE_STRING; +} + +static gboolean +__iter_has_child (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + g_print ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + return 0; +} + + +static gboolean +__iter_parent (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *child) +{ + g_print ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + return 0; +} + +static GtkTreePath * +__tree_get_path (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + // g_print ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model); + g_return_val_if_fail (file->stamp == iter->stamp, FALSE); + + gint n = GPOINTER_TO_INT (iter->user_data); + + gint children = gtk_tree_model_iter_n_children (file->child, NULL); + + if (n >= children - file->first_line) + return NULL; + + return gtk_tree_path_new_from_indices (n, -1); +} + + +static gboolean +__iter_children (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent) +{ + g_print ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + return 0; +} + + +static gint +__tree_model_iter_n_children (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + g_print ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model); + g_assert (iter == NULL); + return 0; +} + +static GtkTreeModelFlags +__tree_model_get_flags (GtkTreeModel *model) +{ + // g_print ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + g_return_val_if_fail (PSPPIRE_IS_DELIMITED_TEXT (model), (GtkTreeModelFlags) 0); + + return GTK_TREE_MODEL_LIST_ONLY; +} + +static gint +__tree_model_get_n_columns (GtkTreeModel *tree_model) +{ + // g_print ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + PsppireDelimitedText *tf = PSPPIRE_DELIMITED_TEXT (tree_model); + + /* + 1 for the trailing field and +1 for the leading line number column */ + return tf->max_delimiters + 1 + 1; +} + + +static gboolean +__iter_nth_child (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n) +{ + // g_print ("%s:%d %s %d\n", __FILE__, __LINE__, __FUNCTION__, n); + PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model); + + g_assert (parent == NULL); + + g_return_val_if_fail (file, FALSE); + + gint children = gtk_tree_model_iter_n_children (file->child, NULL); + + if (n >= children - file->first_line) + { + iter->stamp = -1; + iter->user_data = NULL; + return FALSE; + } + + iter->user_data = GINT_TO_POINTER (n); + iter->stamp = file->stamp; + + return TRUE; +} + + +static void +__get_value (GtkTreeModel *tree_model, + GtkTreeIter *iter, + gint column, + GValue *value) +{ + // g_print ("%s:%d %s Col: %d\n", __FILE__, __LINE__, __FUNCTION__, column); + PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model); + + g_return_if_fail (iter->stamp == file->stamp); + + gint n = GPOINTER_TO_INT (iter->user_data) + file->first_line; + + // g_print ("%s:%d Row: %d\n", __FILE__, __LINE__, n); + + if (column == 0) + { + g_value_init (value, G_TYPE_INT); + g_value_set_int (value, n + 1); + return; + } + + g_value_init (value, G_TYPE_STRING); + + if (n != file->cache_row) + { + if (file->const_cache.string) + { + ss_dealloc (&file->const_cache); + } + ss_alloc_substring (&file->const_cache, PSPPIRE_TEXT_FILE (file->child)->lines[n]); + file->cache = file->const_cache; + int field = 0; + file->cache_starts[0] = file->cache.string; + for (; + UINT32_MAX != ss_first_mb (file->cache); + ss_get_mb (&file->cache)) + { + ucs4_t xx = ss_first_mb (file->cache); + GSList *del; + for (del = file->delimiters; del; del = g_slist_next (del)) + { + if (xx == GPOINTER_TO_INT (del->data)) + { + field++; + int char_len = ss_first_mblen (file->cache); + file->cache_starts[field] = file->cache.string + char_len; + while (char_len > 0) + { + file->cache.string[char_len - 1] = '\0'; + char_len--; + } + break; + } + } + } + + file->cache_row = n; + } + + g_value_set_string (value, file->cache_starts [column - 1]); +} + + +static void +__tree_model_init (GtkTreeModelIface *iface) +{ + iface->get_flags = __tree_model_get_flags; + iface->get_n_columns = __tree_model_get_n_columns ; + iface->get_column_type = __tree_get_column_type; + iface->get_iter = __tree_get_iter; + iface->iter_next = __tree_iter_next; + iface->get_path = __tree_get_path; + iface->get_value = __get_value; + + iface->iter_children = __iter_children; + iface->iter_has_child = __iter_has_child; + iface->iter_n_children = __tree_model_iter_n_children; + iface->iter_nth_child = __iter_nth_child; + iface->iter_parent = __iter_parent; +} + + +GType +psppire_delimited_text_get_type (void) +{ + static GType text_file_type = 0; + + if (!text_file_type) + { + static const GTypeInfo text_file_info = + { + sizeof (PsppireDelimitedTextClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) psppire_delimited_text_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (PsppireDelimitedText), + 0, + (GInstanceInitFunc) psppire_delimited_text_init, + }; + + static const GInterfaceInfo tree_model_info = { + (GInterfaceInitFunc) __tree_model_init, + NULL, + NULL + }; + + text_file_type = g_type_register_static (G_TYPE_OBJECT, + "PsppireDelimitedText", + &text_file_info, 0); + + g_type_add_interface_static (text_file_type, GTK_TYPE_TREE_MODEL, + &tree_model_info); + } + + return text_file_type; +} + + +static void +psppire_delimited_text_class_init (PsppireDelimitedTextClass *class) +{ + GObjectClass *object_class; + + parent_class = g_type_class_peek_parent (class); + object_class = (GObjectClass*) class; + + GParamSpec *first_line_spec = + g_param_spec_int ("first-line", + "First Line", + P_("The first line to be considered."), + 0, 1000, 0, + G_PARAM_READWRITE); + + GParamSpec *delimiters_spec = + g_param_spec_pointer ("delimiters", + "Field Delimiters", + P_("A GSList of gunichars which delimit the fields."), + G_PARAM_READWRITE); + + GParamSpec *child_spec = + g_param_spec_object ("child", + "Child Model", + P_("The GtkTextModel which this object wraps."), + GTK_TYPE_TREE_MODEL, + G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE); + + object_class->set_property = psppire_delimited_text_set_property; + object_class->get_property = psppire_delimited_text_get_property; + + g_object_class_install_property (object_class, + PROP_CHILD, + child_spec); + + g_object_class_install_property (object_class, + PROP_DELIMITERS, + delimiters_spec); + + g_object_class_install_property (object_class, + PROP_FIRST_LINE, + first_line_spec); + + object_class->finalize = psppire_delimited_text_finalize; + object_class->dispose = psppire_delimited_text_dispose; +} + + +static void +psppire_delimited_text_init (PsppireDelimitedText *text_file) +{ + text_file->child = NULL; + text_file->first_line = 0; + text_file->delimiters = g_slist_prepend (NULL, GINT_TO_POINTER (':')); + + text_file->const_cache.string = NULL; + text_file->const_cache.length = 0; + text_file->cache_row = -1; + + text_file->max_delimiters = 0; + + text_file->dispose_has_run = FALSE; + text_file->stamp = g_random_int (); +} + + +PsppireDelimitedText * +psppire_delimited_text_new (GtkTreeModel *child) +{ + PsppireDelimitedText *retval = + g_object_new (PSPPIRE_TYPE_DELIMITED_TEXT, + "child", child, + NULL); + + return retval; +} + +static void +psppire_delimited_text_finalize (GObject *object) +{ + PsppireDelimitedText *tf = PSPPIRE_DELIMITED_TEXT (object); + + g_slist_free (tf->delimiters); + + ss_dealloc (&tf->const_cache); + + /* must chain up */ + (* parent_class->finalize) (object); +} + + +static void +psppire_delimited_text_dispose (GObject *object) +{ + PsppireDelimitedText *ds = PSPPIRE_DELIMITED_TEXT (object); + + if (ds->dispose_has_run) + return; + + /* must chain up */ + (* parent_class->dispose) (object); + + ds->dispose_has_run = TRUE; +} diff --git a/src/ui/gui/psppire-delimited-text.h b/src/ui/gui/psppire-delimited-text.h new file mode 100644 index 0000000000..6465f10ae3 --- /dev/null +++ b/src/ui/gui/psppire-delimited-text.h @@ -0,0 +1,89 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2017 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 __PSPPIRE_DELIMITED_TEXT_H__ +#define __PSPPIRE_DELIMITED_TEXT_H__ + +#include "libpspp/str.h" + +#include +#include + +G_BEGIN_DECLS + + + +#define PSPPIRE_TYPE_DELIMITED_TEXT (psppire_delimited_text_get_type ()) + +#define PSPPIRE_DELIMITED_TEXT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + PSPPIRE_TYPE_DELIMITED_TEXT, PsppireDelimitedText)) + +#define PSPPIRE_DELIMITED_TEXT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + PSPPIRE_TYPE_DELIMITED_TEXT, \ + PsppireDelimitedTextClass)) + + +#define PSPPIRE_IS_DELIMITED_TEXT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PSPPIRE_TYPE_DELIMITED_TEXT)) + +#define PSPPIRE_IS_DELIMITED_TEXT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), PSPPIRE_TYPE_DELIMITED_TEXT)) + +#define PSPPIRE_DELIMITED_TEXT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + PSPPIRE_TYPE_DELIMITED_TEXT, \ + PsppireDelimitedTextClass)) + +struct _PsppireDelimitedText +{ + GObject parent; + + GtkTreeModel *child; + + /* The first line of the file to be modelled */ + gint first_line; + + GSList *delimiters; + gint max_delimiters; + + /*< private >*/ + gboolean dispose_has_run ; + gint stamp; + + /* caching */ + const char *cache_starts[512]; + struct substring cache; + struct substring const_cache; + int cache_row; +}; + +struct _PsppireDelimitedTextClass +{ + GObjectClass parent_class; +}; + + +typedef struct _PsppireDelimitedText PsppireDelimitedText; +typedef struct _PsppireDelimitedTextClass PsppireDelimitedTextClass; + +GType psppire_delimited_text_get_type (void) G_GNUC_CONST; +PsppireDelimitedText *psppire_delimited_text_new (GtkTreeModel *); + +G_END_DECLS + +#endif /* __PSPPIRE_DELIMITED_TEXT_H__ */ diff --git a/src/ui/gui/psppire-import-assistant.c b/src/ui/gui/psppire-import-assistant.c index 2eb5e89acb..d8695706a3 100644 --- a/src/ui/gui/psppire-import-assistant.c +++ b/src/ui/gui/psppire-import-assistant.c @@ -1,5 +1,5 @@ /* PSPPIRE - a graphical user interface for PSPP. - Copyright (C) 2015, 2016 Free Software Foundation + Copyright (C) 2015, 2016, 2017 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 @@ -48,6 +48,9 @@ #include "psppire-empty-list-store.h" #include "psppire-encoding-selector.h" #include "psppire-spreadsheet-model.h" +#include "psppire-text-file.h" +#include "psppire-delimited-text.h" + #include "ui/syntax-gen.h" #include @@ -65,21 +68,10 @@ static void split_fields (PsppireImportAssistant *ia); static void choose_column_names (PsppireImportAssistant *ia); -/* Frees IA's file substructure. */ -static void destroy_file (PsppireImportAssistant *ia); - -static void clear_fields (PsppireImportAssistant *ia); - static void intro_page_create (PsppireImportAssistant *ia); static void first_line_page_create (PsppireImportAssistant *ia); -static gboolean process_file (PsppireImportAssistant *ia); - - -static GtkWidget * create_data_tree_view (gboolean input, GtkContainer *parent, - PsppireImportAssistant *ia); - static void separators_page_create (PsppireImportAssistant *ia); static void formats_page_create (PsppireImportAssistant *ia); @@ -147,16 +139,13 @@ psppire_import_assistant_finalize (GObject *object) if (ia->spreadsheet) spreadsheet_unref (ia->spreadsheet); - // clear_fields (ia); destroy_columns (ia); - + ds_destroy (&ia->separators); ds_destroy (&ia->quotes); g_object_unref (ia->builder); - destroy_file (ia); - g_object_unref (ia->prop_renderer); g_object_unref (ia->fixed_renderer); @@ -207,12 +196,8 @@ revise_fields_preview (PsppireImportAssistant *ia) push_watch_cursor (ia); get_separators (ia); - split_fields (ia); + // split_fields (ia); choose_column_names (ia); - ia->fields_tree_view = - GTK_WIDGET (create_data_tree_view (TRUE, - GTK_CONTAINER (get_widget_assert (ia->builder, "fields-scroller")), - ia)); pop_watch_cursor (ia); } @@ -250,24 +235,29 @@ find_commonest_chars (unsigned long int histogram[UCHAR_MAX + 1], ds_assign_cstr (result, def); } - /* Picks the most likely separator and quote characters based on IA's file data. */ static void choose_likely_separators (PsppireImportAssistant *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->line_cnt; row++) + gboolean valid; + GtkTreeIter iter; + for (valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->text_file), &iter); + valid; + valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->text_file), &iter)) { - struct substring line = ds_ss (&ia->lines[row]); + gchar *xxx = 0; + gtk_tree_model_get (GTK_TREE_MODEL (ia->text_file), &iter, 1, &xxx, -1); + struct substring line = ss_cstr (xxx); size_t length = ss_length (line); size_t i; for (i = 0; i < length; i++) histogram[(unsigned char) line.string[i]]++; + g_free (xxx); } find_commonest_chars (histogram, "\"'", "", &ia->quotes); @@ -277,11 +267,67 @@ choose_likely_separators (PsppireImportAssistant *ia) static void set_separators (PsppireImportAssistant *ia); + +static void +repopulate_delimiter_columns (PsppireImportAssistant *ia) +{ + /* Remove all the columns */ + while (gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view)) > 0) + { + GtkTreeViewColumn *tvc = gtk_tree_view_get_column (GTK_TREE_VIEW (ia->fields_tree_view), 0); + gtk_tree_view_remove_column (GTK_TREE_VIEW (ia->fields_tree_view), tvc); + } + + gint n_fields = gtk_tree_model_get_n_columns (ia->delimiters_model); + + /* ... and put them back again. */ + gint f; + for (f = gtk_tree_view_get_n_columns (GTK_TREE_VIEW (ia->fields_tree_view)); + f < n_fields; f++) + { + GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); + GtkTreeViewColumn *column = + gtk_tree_view_column_new_with_attributes ("var", renderer, + "text", f, + NULL); + g_object_set (column, + "resizable", TRUE, + "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, + NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (ia->fields_tree_view), column); + } +} + +static void +reset_tree_view_model (PsppireImportAssistant *ia) +{ + GtkTreeModel *tm = gtk_tree_view_get_model (GTK_TREE_VIEW (ia->fields_tree_view)); + g_object_ref (tm); + gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), NULL); + + + repopulate_delimiter_columns (ia); + + gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), tm); + // gtk_tree_view_columns_autosize (GTK_TREE_VIEW (ia->fields_tree_view)); + + g_object_unref (tm); +} + /* Called just before the separators page becomes visible in the assistant, and when the Reset button is clicked. */ static void prepare_separators_page (PsppireImportAssistant *ia, GtkWidget *page) { + gtk_tree_view_set_model (GTK_TREE_VIEW (ia->fields_tree_view), ia->delimiters_model); + + g_signal_connect_swapped (ia->delimiters_model, "notify::delimiters", + G_CALLBACK (reset_tree_view_model), ia); + + + repopulate_delimiter_columns (ia); + revise_fields_preview (ia); choose_likely_separators (ia); set_separators (ia); @@ -290,7 +336,7 @@ prepare_separators_page (PsppireImportAssistant *ia, GtkWidget *page) struct separator { const char *name; /* Name (for use with get_widget_assert). */ - int c; /* Separator character. */ + gunichar c; /* Separator character. */ }; /* All the separators in the dialog box. */ @@ -400,7 +446,7 @@ on_reset (GtkButton *button, PsppireImportAssistant *ia) gint pn = gtk_assistant_get_current_page (GTK_ASSISTANT (ia)); { GtkWidget *page = gtk_assistant_get_nth_page (GTK_ASSISTANT (ia), pn); - + page_func *on_reset = g_object_get_data (G_OBJECT (page), "on-reset"); if (on_reset) @@ -456,7 +502,7 @@ on_prepare (GtkAssistant *assistant, GtkWidget *page, PsppireImportAssistant *ia if (on_entering) on_entering (ia, new_page); } - + ia->current_page = pn; } @@ -476,23 +522,6 @@ on_close (GtkAssistant *assistant, PsppireImportAssistant *ia) } -/* Frees IA's file substructure. */ -static void -destroy_file (PsppireImportAssistant *ia) -{ - size_t i; - - for (i = 0; i < ia->line_cnt; i++) - ds_destroy (&ia->lines[i]); - - g_free (ia->file_name); - ia->file_name = NULL; - - g_free (ia->encoding); - ia->encoding = NULL; -} - - /* 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. */ @@ -522,99 +551,7 @@ pop_watch_cursor (PsppireImportAssistant *ia) } } - -static gboolean -process_file (PsppireImportAssistant *ia) -{ - struct string input; - struct line_reader *reader = line_reader_for_file (ia->encoding, ia->file_name, O_RDONLY); - if (reader == NULL) - { - msg_error (errno, _("Could not open `%s'"), - ia->file_name); - return FALSE; - } - - ds_init_empty (&input); - for (ia->line_cnt = 0; ia->line_cnt < MAX_PREVIEW_LINES; ia->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"), - ia->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."), - ia->file_name, MAX_LINE_LEN); - line_reader_close (reader); - destroy_file (ia); - ds_destroy (&input); - return FALSE; - } - - char *s = recode_string ("UTF-8", line_reader_get_encoding (reader), ds_cstr (&input), ds_length (&input)); - ds_init_cstr (&ia->lines[ia->line_cnt], s); - free (s); - } - ds_destroy (&input); - if (ia->line_cnt == 0) - { - msg (ME, _("`%s' is empty."), ia->file_name); - line_reader_close (reader); - destroy_file (ia); - return FALSE; - } - - /* Estimate the number of lines in the file. */ - if (ia->line_cnt < MAX_PREVIEW_LINES) - { - ia->total_lines = ia->line_cnt; - ia->total_is_exact = true; - } - else - { - struct stat s; - off_t position = line_reader_tell (reader); - if (fstat (line_reader_fileno (reader), &s) == 0 && position > 0) - { - ia->total_lines = (double) ia->line_cnt / position * s.st_size; - ia->total_is_exact = false; - } - else - { - ia->total_lines = 0; - ia->total_is_exact = true; - } - } - line_reader_close (reader); - return TRUE; -} - #if SHEET_MERGE - - -static void -render_line_number (PsppSheetViewColumn *tree_column, - GtkCellRenderer *cell, - GtkTreeModel *tree_model, - GtkTreeIter *iter, - gpointer data) -{ - gint row = empty_list_store_iter_to_row (iter); - char s[INT_BUFSIZE_BOUND (int)]; - int first_line = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tree_model), - "first-line")); - sprintf (s, "%d", first_line + row); - g_object_set (cell, "text", s, NULL); -} - - static gint get_string_width (GtkWidget *treeview, GtkCellRenderer *renderer, const char *string) @@ -626,7 +563,6 @@ get_string_width (GtkWidget *treeview, GtkCellRenderer *renderer, return width; } - static gint get_monospace_width (GtkWidget *treeview, GtkCellRenderer *renderer, size_t char_cnt) @@ -643,24 +579,6 @@ get_monospace_width (GtkWidget *treeview, GtkCellRenderer *renderer, return width; } -static void -add_line_number_column (const PsppireImportAssistant *ia, - GtkWidget *treeview) -{ - PsppSheetViewColumn *column = - pspp_sheet_view_column_new_with_attributes (_("Line"), ia->prop_renderer, (void *) NULL); - - pspp_sheet_view_column_set_fixed_width (column, get_monospace_width (treeview, ia->prop_renderer, 5)); - - pspp_sheet_view_column_set_resizable (column, TRUE); - - pspp_sheet_view_column_set_cell_data_func (column, ia->prop_renderer, - render_line_number, NULL, NULL); - - pspp_sheet_view_append_column (PSPP_SHEET_VIEW (treeview), column); -} - - static void set_model_on_treeview (PsppireImportAssistant *ia, GtkWidget *tree_view, size_t first_line) { @@ -668,13 +586,12 @@ set_model_on_treeview (PsppireImportAssistant *ia, GtkWidget *tree_view, size_t g_object_set_data (G_OBJECT (model), "lines", &ia->lines + first_line); g_object_set_data (G_OBJECT (model), "first-line", GINT_TO_POINTER (first_line)); - + pspp_sheet_view_set_model (PSPP_SHEET_VIEW (tree_view), model); - + g_object_unref (model); } - static GtkWidget * make_tree_view (const PsppireImportAssistant *ia) { @@ -685,7 +602,6 @@ make_tree_view (const PsppireImportAssistant *ia) return tree_view; } - #endif static GtkWidget * @@ -693,7 +609,7 @@ add_page_to_assistant (PsppireImportAssistant *ia, GtkWidget *page, GtkAssistantPageType type, const gchar *); -static void +static void on_sheet_combo_changed (GtkComboBox *cb, PsppireImportAssistant *ia) { GtkTreeIter iter; @@ -716,7 +632,7 @@ prepare_sheet_spec_page (PsppireImportAssistant *ia) GtkWidget *sheet_entry = get_widget_assert (builder, "sheet-entry"); GtkWidget *readnames_checkbox = get_widget_assert (builder, "readnames-checkbox"); - gtk_combo_box_set_model (GTK_COMBO_BOX (sheet_entry), + gtk_combo_box_set_model (GTK_COMBO_BOX (sheet_entry), psppire_spreadsheet_model_new (ia->spreadsheet)); gtk_combo_box_set_active (GTK_COMBO_BOX (sheet_entry), 0); @@ -731,7 +647,7 @@ sheet_spec_page_create (PsppireImportAssistant *ia) { GtkBuilder *builder = ia->builder; GtkWidget *page = get_widget_assert (builder, "Spreadsheet-Importer"); - + GtkWidget *combo_box = get_widget_assert (builder, "sheet-entry"); GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo_box)); @@ -748,7 +664,7 @@ sheet_spec_page_create (PsppireImportAssistant *ia) g_object_set_data (G_OBJECT (page), "on-entering", prepare_sheet_spec_page); } -static void +static void on_chosen (PsppireImportAssistant *ia, GtkWidget *page) { GtkFileChooser *fc = GTK_FILE_CHOOSER (page); @@ -763,10 +679,10 @@ on_chosen (PsppireImportAssistant *ia, GtkWidget *page) if (f && !g_file_test (f, G_FILE_TEST_IS_DIR)) { gtk_assistant_set_page_complete (GTK_ASSISTANT(ia), GTK_WIDGET (fc), TRUE); - + if (ia->spreadsheet) spreadsheet_unref (ia->spreadsheet); - + ia->spreadsheet = gnumeric_probe (f, FALSE); if (!ia->spreadsheet) @@ -782,22 +698,24 @@ on_chosen (PsppireImportAssistant *ia, GtkWidget *page) { sheet_spec_page_create (ia); } - + formats_page_create (ia); } - g_free (f); + g_free (f); } /* This has to be done on a map signal callback, because GtkFileChooserWidget resets everything when it is mapped. */ -static void +static void on_map (PsppireImportAssistant *ia, GtkWidget *page) { GtkFileChooser *fc = GTK_FILE_CHOOSER (page); +#if TEXT_FILE if (ia->file_name) gtk_file_chooser_set_filename (fc, ia->file_name); +#endif on_chosen (ia, page); } @@ -812,17 +730,19 @@ chooser_page_enter (PsppireImportAssistant *ia, GtkWidget *page) static void chooser_page_leave (PsppireImportAssistant *ia, GtkWidget *page) { - - if (ia->file_name) - g_free (ia->file_name); - ia->file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (page)); - - if (ia->encoding) - g_free (ia->encoding); - ia->encoding = psppire_encoding_selector_get_encoding (ia->encoding_selector); + g_print ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); + gchar *file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (page)); + gchar *encoding = psppire_encoding_selector_get_encoding (ia->encoding_selector); if (!ia->spreadsheet) - process_file (ia); + { + ia->text_file = psppire_text_file_new (file_name, encoding); + gtk_tree_view_set_model (GTK_TREE_VIEW (ia->first_line_tree_view), + GTK_TREE_MODEL (ia->text_file)); + } + + g_free (file_name); + g_free (encoding); } static void @@ -856,7 +776,7 @@ chooser_page_create (PsppireImportAssistant *ia) gtk_file_filter_set_name (ia->default_filter, _("All Files")); gtk_file_filter_add_pattern (ia->default_filter, "*"); gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), ia->default_filter); - + filter = gtk_file_filter_new (); gtk_file_filter_set_name (filter, _("Text Files")); gtk_file_filter_add_mime_type (filter, "text/*"); @@ -918,15 +838,13 @@ psppire_import_assistant_init (PsppireImportAssistant *ia) { ia->builder = builder_new ("text-data-import.ui"); - ia->current_page = -1 ; + ia->current_page = -1 ; ia->column_cnt = 0; ia->columns = NULL; - ia->file_name = NULL; - ia->encoding = NULL; ia->spreadsheet = NULL; ia->watch_cursor = 0; - + ia->prop_renderer = gtk_cell_renderer_text_new (); g_object_ref_sink (ia->prop_renderer); ia->fixed_renderer = gtk_cell_renderer_text_new (); @@ -938,10 +856,10 @@ psppire_import_assistant_init (PsppireImportAssistant *ia) g_signal_connect (ia, "prepare", G_CALLBACK (on_prepare), ia); g_signal_connect (ia, "cancel", G_CALLBACK (on_cancel), ia); g_signal_connect (ia, "close", G_CALLBACK (on_close), ia); - + ia->paste_button = gtk_button_new_with_label (_("Paste")); ia->reset_button = gtk_button_new_with_label (_("Reset")); - + gtk_assistant_add_action_widget (GTK_ASSISTANT(ia), ia->paste_button); g_signal_connect (ia->paste_button, "clicked", G_CALLBACK (on_paste), ia); @@ -951,7 +869,7 @@ psppire_import_assistant_init (PsppireImportAssistant *ia) gtk_window_set_title (GTK_WINDOW (ia), _("Importing Delimited Text Data")); - + gtk_window_set_icon_name (GTK_WINDOW (ia), "pspp"); chooser_page_create (ia); @@ -993,38 +911,12 @@ on_intro_amount_changed (PsppireImportAssistant *p) GTK_TOGGLE_BUTTON (p->percent_button))); } - -#if SHEET_MERGE - -static void -render_line (PsppSheetViewColumn *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); -} - -#endif - /* Sets the widgets to match IA's first_line substructure. */ static void set_first_line (PsppireImportAssistant *ia) { GtkTreePath *path = gtk_tree_path_new_from_indices (ia->skip_lines, -1); -#if SHEET_MERGE - set_model_on_treeview (ia, ia->tree_view, 0); - pspp_sheet_view_set_cursor (PSPP_SHEET_VIEW (ia->tree_view), - path, NULL, false); -#endif gtk_tree_path_free (path); gtk_toggle_button_set_active ( @@ -1036,50 +928,6 @@ set_first_line (PsppireImportAssistant *ia) #if SHEET_MERGE -/* Creates and returns a tree view that contains each of the - lines in IA's file as a row. */ -static GtkWidget * -create_lines_tree_view (GtkContainer *parent, PsppireImportAssistant *ia) -{ - size_t max_line_length; - gint content_width, header_width; - size_t i; - const gchar *title = _("Text"); - GtkWidget *tree_view = make_tree_view (ia); - PsppSheetViewColumn *column = - pspp_sheet_view_column_new_with_attributes (title, - ia->fixed_renderer, (void *) NULL); - - pspp_sheet_view_column_set_cell_data_func (column, ia->fixed_renderer, - render_line, NULL, NULL); - pspp_sheet_view_column_set_resizable (column, TRUE); - pspp_sheet_view_column_set_expand (column, TRUE); - - max_line_length = 0; - for (i = 0; i < ia->line_cnt; i++) - { - size_t w = ds_length (&ia->lines[i]); - max_line_length = MAX (max_line_length, w); - } - - content_width = get_monospace_width (tree_view, ia->fixed_renderer, - max_line_length); - header_width = get_string_width (tree_view, ia->prop_renderer, title); - pspp_sheet_view_column_set_fixed_width (column, MAX (content_width, - header_width)); - pspp_sheet_view_append_column (PSPP_SHEET_VIEW (tree_view), column); - - GtkWidget *oldtv = gtk_bin_get_child (GTK_BIN (parent)); - if (oldtv) - gtk_container_remove (parent, oldtv); - - gtk_container_add (parent, tree_view); - gtk_widget_show (tree_view); - - return tree_view; -} - - /* Sets IA's first_line substructure to match the widgets. */ static void @@ -1116,21 +964,73 @@ reset_first_line_page (PsppireImportAssistant *ia) } #endif + +static void +on_cursor_change (GtkTreeView *treeview, gpointer user_data) +{ + PsppireImportAssistant *ia = PSPPIRE_IMPORT_ASSISTANT (user_data); + GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview); + GtkTreeModel *model = NULL; + GtkTreeIter iter; + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + int n; + PsppireTextFile *tf = PSPPIRE_TEXT_FILE (model); + GtkTreePath *path = gtk_tree_model_get_path (model, &iter); + gint *index = gtk_tree_path_get_indices (path); + + n = *index; + + gtk_tree_path_free (path); + + g_print ("%s:%d Setting first line to %d\n", __FILE__, __LINE__, n); + + ia->delimiters_model = psppire_delimited_text_new (ia->text_file); + g_object_set (ia->delimiters_model, "first-line", n, NULL); + } +} + + /* Initializes IA's first_line substructure. */ static void first_line_page_create (PsppireImportAssistant *ia) { + g_print ("%s:%d %s\n", __FILE__, __LINE__, __FUNCTION__); GtkWidget *w = get_widget_assert (ia->builder, "FirstLine"); g_object_set_data (G_OBJECT (w), "on-entering", set_first_line); - + add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Select the First Line")); + GtkWidget *scrolled_window = get_widget_assert (ia->builder, "first-line-scroller"); + + if (ia->first_line_tree_view == NULL) + { + ia->first_line_tree_view = gtk_tree_view_new (); + + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ia->first_line_tree_view), TRUE); + + GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); + GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (_("Line"), renderer, + "text", 0, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Text"), renderer, "text", 1, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (ia->first_line_tree_view), column); + + gtk_container_add (GTK_CONTAINER (scrolled_window), ia->first_line_tree_view); + + g_signal_connect (ia->first_line_tree_view, "cursor-changed", + G_CALLBACK (on_cursor_change), ia); + } + gtk_widget_show_all (scrolled_window); + + + #if SHEET_MERGE - - ia->tree_view = GTK_WIDGET (create_lines_tree_view ( - GTK_CONTAINER (get_widget_assert (ia->builder, "first-line-scroller")), ia)); ia->variable_names_cb = get_widget_assert (ia->builder, "variable-names"); pspp_sheet_selection_set_mode ( pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (ia->tree_view)), @@ -1160,39 +1060,39 @@ intro_on_enter (PsppireImportAssistant *ia) struct string s; - if (ia->line_cnt > MAX_PREVIEW_LINES) - ia->line_cnt = MAX_PREVIEW_LINES; - 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->total_is_exact) + if (ia->text_file) { - ds_put_format ( - &s, ngettext ("The selected file contains %'lu line of text. ", - "The selected file contains %'lu lines of text. ", - ia->total_lines), - ia->total_lines); - } - else if (ia->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->total_lines), - ia->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->line_cnt), - ia->line_cnt); + if (ia->text_file->total_is_exact) + { + ds_put_format ( + &s, ngettext ("The selected file contains %'lu line of text. ", + "The selected file contains %'lu lines of text. ", + ia->text_file->total_lines), + ia->text_file->total_lines); + } + else if (ia->text_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->text_file->total_lines), + ia->text_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->text_file->line_cnt), + ia->text_file->line_cnt); + } } ds_put_cstr (&s, _("You may choose below how much of the file should " @@ -1211,18 +1111,23 @@ intro_on_enter (PsppireImportAssistant *ia) if (w) gtk_container_remove (GTK_CONTAINER (table), w); - + GtkWidget *hbox_n_cases = psppire_scanf_new (_("Only the first %4d cases"), &ia->n_cases_spin); GtkAdjustment *adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (ia->n_cases_spin)); gtk_adjustment_set_lower (adj, 1.0); - if (ia->total_is_exact) - gtk_adjustment_set_value (adj, old_value); - if (ia->total_is_exact) - gtk_adjustment_set_upper (adj, ia->total_lines); - else - gtk_adjustment_set_upper (adj, DBL_MAX); + if (ia->text_file) + { + if (psppire_text_file_get_total_exact (ia->text_file)) + { + gulong total_lines = psppire_text_file_get_n_lines (ia->text_file); + gtk_adjustment_set_upper (adj, total_lines); + gtk_adjustment_set_value (adj, old_value); + } + else + gtk_adjustment_set_upper (adj, DBL_MAX); + } gtk_grid_attach (GTK_GRID (table), hbox_n_cases, 1, 1, 1, 1); @@ -1318,7 +1223,7 @@ destroy_columns (PsppireImportAssistant *ia) free (col->name); free (col->contents); } - + free (ia->columns); } @@ -1601,42 +1506,7 @@ get_separators (PsppireImportAssistant *ia) -/* Frees and clears the column data in IA's separators - substructure. */ -static void -clear_fields (PsppireImportAssistant *ia) -{ - if (ia->column_cnt > 0) - { - struct column *col; - size_t row; - - for (row = 0; row < ia->line_cnt; row++) - { - const struct string *line = &ia->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; - } -} - +#if SHEET_MERGE /* Breaks the file data in IA into columns based on the separators set in IA's separators substructure. */ @@ -1645,9 +1515,7 @@ split_fields (PsppireImportAssistant *ia) { size_t columns_allocated; bool space_sep; - size_t row; - - clear_fields (ia); + size_t row = 0; /* Is space in the set of separators? */ space_sep = ss_find_byte (ds_ss (&ia->separators), ' ') != SIZE_MAX; @@ -1657,13 +1525,20 @@ split_fields (PsppireImportAssistant *ia) contains variables names if ia->first_line.variable_names is TRUE. */ columns_allocated = 0; - for (row = 0; row < ia->line_cnt; row++) + + gint n_lines = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (ia->text_file), NULL); + GtkTreeIter iter; + gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ia->text_file), &iter); + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (ia->text_file), &iter)) { - struct string *line = &ia->lines[row]; - struct substring text = ds_ss (line); + row++; + gchar *xxx; + gtk_tree_model_get (GTK_TREE_MODEL (ia->text_file), &iter, 1, &xxx, -1); + struct substring text = ss_cstr (xxx); + g_free (xxx); size_t column_idx; - for (column_idx = 0; ; column_idx++) + for (column_idx = 0; ;column_idx++) { struct substring field = SS_EMPTY_INITIALIZER; struct column *column; @@ -1713,7 +1588,7 @@ split_fields (PsppireImportAssistant *ia) column = &ia->columns[ia->column_cnt++]; column->name = NULL; column->width = 0; - column->contents = xcalloc (ia->line_cnt, + column->contents = xcalloc (n_lines, sizeof *column->contents); } column = &ia->columns[column_idx]; @@ -1732,101 +1607,8 @@ split_fields (PsppireImportAssistant *ia) } } - -#if SHEET_MERGE -static PsppSheetViewColumn * -make_data_column (PsppireImportAssistant *ia, GtkWidget *tree_view, - bool input, gint dict_idx) -{ - struct variable *var = NULL; - struct column *column = NULL; - size_t char_cnt = 0; - gint content_width, header_width; - PsppSheetViewColumn *tree_column; - char *name = NULL; - - if (input) - { - column = &ia->columns[dict_idx]; - name = escape_underscores (column->name); - char_cnt = column->width; - } - else - { - var = dict_get_var (ia->dict, dict_idx); - name = escape_underscores (var_get_name (var)); - char_cnt = var_get_print_format (var)->w; - } - - content_width = get_monospace_width (tree_view, ia->fixed_renderer, - char_cnt); - header_width = get_string_width (tree_view, ia->prop_renderer, - name); - - tree_column = pspp_sheet_view_column_new (); - g_object_set_data (G_OBJECT (tree_column), "column-number", - GINT_TO_POINTER (dict_idx)); - pspp_sheet_view_column_set_title (tree_column, name); - pspp_sheet_view_column_pack_start (tree_column, ia->fixed_renderer, - FALSE); - pspp_sheet_view_column_set_cell_data_func ( - tree_column, ia->fixed_renderer, - input ? render_input_cell : render_output_cell, ia, NULL); - pspp_sheet_view_column_set_fixed_width (tree_column, MAX (content_width, - header_width)); - - free (name); - - return tree_column; -} - #endif -static GtkWidget * -create_data_tree_view (gboolean input, GtkContainer *parent, - PsppireImportAssistant *ia) -{ - gint i; -#if SHEET_MERGE - GtkWidget *tree_view = make_tree_view (ia); - - set_model_on_treeview (ia, tree_view, ia->skip_lines); - - - - pspp_sheet_selection_set_mode (pspp_sheet_view_get_selection (PSPP_SHEET_VIEW (tree_view)), - PSPP_SHEET_SELECTION_NONE); - - for (i = 0; i < ia->column_cnt; i++) - { - PsppSheetViewColumn *w = make_data_column (ia, tree_view, input, i); - - pspp_sheet_view_append_column (PSPP_SHEET_VIEW (tree_view), w); - } - - g_object_set (G_OBJECT (tree_view), "has-tooltip", TRUE, (void *) NULL); - g_signal_connect (tree_view, "query-tooltip", - G_CALLBACK (input ? on_query_input_tooltip - : on_query_output_tooltip), ia); - - GtkWidget *child = gtk_bin_get_child (GTK_BIN (parent)); - if (child) - { - g_object_ref (child); - gtk_container_remove (parent, child); - } - gtk_container_add (parent, tree_view); - if (child) - g_object_unref (child); - - gtk_widget_show (tree_view); - - return tree_view; -#endif -} - - - /* Chooses a name for each column on the separators page */ static void choose_column_names (PsppireImportAssistant *ia) @@ -1860,9 +1642,24 @@ static void on_separator_toggle (GtkToggleButton *toggle UNUSED, PsppireImportAssistant *ia) { - revise_fields_preview (ia); + int i; + GSList *delimiters = NULL; + for (i = 0; i < SEPARATOR_CNT; i++) + { + const struct separator *s = &separators[i]; + GtkWidget *button = get_widget_assert (ia->builder, s->name); + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) + { + delimiters = g_slist_prepend (delimiters, GINT_TO_POINTER (s->c)); + } + } + + g_object_set (ia->delimiters_model, "delimiters", delimiters, NULL); + + // revise_fields_preview (ia); } + /* Called when the user changes the entry field for custom separators. */ static void @@ -1889,7 +1686,7 @@ on_separators_custom_cb_toggle (GtkToggleButton *custom_cb, static void on_quote_combo_change (GtkComboBox *combo, PsppireImportAssistant *ia) { - revise_fields_preview (ia); + // revise_fields_preview (ia); } /* Called when the user toggles the checkbox that enables @@ -1914,7 +1711,6 @@ separators_page_create (PsppireImportAssistant *ia) g_object_set_data (G_OBJECT (w), "on-entering", prepare_separators_page); g_object_set_data (G_OBJECT (w), "on-reset", prepare_separators_page); - add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONTENT, _("Choose Separators")); @@ -1925,7 +1721,14 @@ separators_page_create (PsppireImportAssistant *ia) ia->quote_cb = get_widget_assert (builder, "quote-cb"); set_quote_list (GTK_COMBO_BOX (ia->quote_combo)); - ia->fields_tree_view = NULL; + + if (ia->fields_tree_view == NULL) + { + GtkWidget *scroller = get_widget_assert (ia->builder, "fields-scroller"); + ia->fields_tree_view = gtk_tree_view_new (); + gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (ia->fields_tree_view)); + gtk_widget_show_all (scroller); + } g_signal_connect (ia->quote_combo, "changed", G_CALLBACK (on_quote_combo_change), ia); @@ -1938,6 +1741,7 @@ separators_page_create (PsppireImportAssistant *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); + } @@ -2003,7 +1807,7 @@ formats_page_create (PsppireImportAssistant *ia) GtkWidget *w = get_widget_assert (builder, "Formats"); g_object_set_data (G_OBJECT (w), "on-entering", prepare_formats_page); g_object_set_data (G_OBJECT (w), "on-reset", reset_formats_page); - + add_page_to_assistant (ia, w, GTK_ASSISTANT_PAGE_CONFIRM, _("Adjust Variable Formats")); @@ -2015,7 +1819,7 @@ formats_page_create (PsppireImportAssistant *ia) -static void +static void separators_append_syntax (const PsppireImportAssistant *ia, struct string *s) { int i; @@ -2045,9 +1849,9 @@ formats_append_syntax (const PsppireImportAssistant *ia, struct string *s) int var_cnt; g_return_if_fail (ia->dict); - + ds_put_cstr (s, " /VARIABLES=\n"); - + var_cnt = dict_get_var_cnt (ia->dict); for (i = 0; i < var_cnt; i++) { @@ -2176,6 +1980,8 @@ sheet_spec_gen_syntax (PsppireImportAssistant *ia) struct string s = DS_EMPTY_INITIALIZER; + char *filename; + g_object_get (ia->text_file, "file-name", &filename, NULL); syntax_gen_pspp (&s, "GET DATA" "\n /TYPE=%ss" @@ -2183,11 +1989,10 @@ sheet_spec_gen_syntax (PsppireImportAssistant *ia) "\n /SHEET=index %d" "\n /READNAMES=%ss", (ia->spreadsheet->type == SPREADSHEET_GNUMERIC) ? "GNM" : "ODS", - ia->file_name, + filename, sheet_index, read_names ? "ON" : "OFF"); - if (range && 0 != strcmp ("", range)) { syntax_gen_pspp (&s, @@ -2202,7 +2007,7 @@ sheet_spec_gen_syntax (PsppireImportAssistant *ia) syntax_gen_pspp (&s, "."); - + return ds_cstr (&s); } @@ -2212,19 +2017,25 @@ psppire_import_assistant_generate_syntax (PsppireImportAssistant *ia) { struct string s = DS_EMPTY_INITIALIZER; -#if SHEET_MERGE if (!ia->spreadsheet) { - if (ia->file_name == NULL) + gchar *file_name = NULL; + gchar *encoding = NULL; + g_object_get (ia->text_file, + "filename", &file_name, + "encoding", &encoding, + NULL); + + if (file_name == NULL) return NULL; syntax_gen_pspp (&s, "GET DATA" "\n /TYPE=TXT" "\n /FILE=%sq\n", - ia->file_name); - if (ia->encoding && strcmp (ia->encoding, "Auto")) - syntax_gen_pspp (&s, " /ENCODING=%sq\n", ia->encoding); + file_name); + if (encoding && strcmp (encoding, "Auto")) + syntax_gen_pspp (&s, " /ENCODING=%sq\n", encoding); ds_put_cstr (&s, " /ARRANGEMENT=DELIMITED\n" @@ -2242,7 +2053,5 @@ psppire_import_assistant_generate_syntax (PsppireImportAssistant *ia) return sheet_spec_gen_syntax (ia); } -#endif - return ds_cstr (&s); } diff --git a/src/ui/gui/psppire-import-assistant.h b/src/ui/gui/psppire-import-assistant.h index 9d48d893bf..dc52c7aeb6 100644 --- a/src/ui/gui/psppire-import-assistant.h +++ b/src/ui/gui/psppire-import-assistant.h @@ -26,6 +26,7 @@ #include "libpspp/str.h" #include "psppire-dict.h" #include "data/spreadsheet-reader.h" +#include "psppire-text-file.h" G_BEGIN_DECLS @@ -58,12 +59,8 @@ typedef struct _PsppireImportAssistant PsppireImportAssistant; typedef struct _PsppireImportAssistantClass PsppireImportAssistantClass; -struct first_line_page; - typedef void page_func (PsppireImportAssistant *, GtkWidget *page); -enum { MAX_PREVIEW_LINES = 1000 }; /* Max number of lines to read. */ - struct _PsppireImportAssistant { GtkAssistant parent; @@ -112,7 +109,7 @@ struct _PsppireImportAssistant /* START first line page */ - GtkWidget *tree_view; + GtkWidget *first_line_tree_view; GtkWidget *variable_names_cb; /* END first line page */ @@ -125,23 +122,11 @@ struct _PsppireImportAssistant GtkCellRenderer *prop_renderer; GtkCellRenderer *fixed_renderer; - // START struct file file; - char *file_name; /* File name. */ - - /* Relevant only for text files */ - - gchar *encoding; /* Encoding. */ - unsigned long int total_lines; /* Number of lines in file. */ - gboolean total_is_exact; /* Is total_lines exact (or an estimate)? */ - - /* The first several lines of the file. */ - struct string lines[MAX_PREVIEW_LINES]; - size_t line_cnt; - - // END struct file file; + PsppireTextFile *text_file; + GtkTreeModel *delimiters_model; + struct sheet_spec_page *sheet_spec; - struct first_line_page *first_line; /* The columns produced. */ struct column *columns; /* Information about each column. */ diff --git a/src/ui/gui/psppire-text-file.c b/src/ui/gui/psppire-text-file.c new file mode 100644 index 0000000000..9030896638 --- /dev/null +++ b/src/ui/gui/psppire-text-file.c @@ -0,0 +1,519 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2017 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 +#define _(msgid) gettext (msgid) +#define P_(msgid) msgid + +#include "psppire-text-file.h" +#include +#include +#include +#include +#include "libpspp/line-reader.h" +#include "libpspp/message.h" +#include "libpspp/str.h" +#include "libpspp/i18n.h" + +#include + +/* Properties */ +enum + { + PROP_0, + PROP_FILE_NAME, + PROP_ENCODING + }; + +enum {MAX_LINE_LEN = 16384}; /* Max length of an acceptable line. */ + + +static void +read_lines (PsppireTextFile *tf) +{ + if (tf->file_name && 0 != g_strcmp0 ("unset", tf->encoding)) + { + struct line_reader *reader = line_reader_for_file (tf->encoding, tf->file_name, O_RDONLY); + + if (reader == NULL) + { + msg_error (errno, _("Could not open `%s'"), tf->file_name); + return; + } + + struct string input; + ds_init_empty (&input); + for (tf->line_cnt = 0; tf->line_cnt < MAX_PREVIEW_LINES; tf->line_cnt++) + { + ds_clear (&input); + if (!line_reader_read (reader, &input, MAX_LINE_LEN + 1) + || ds_length (&input) > MAX_LINE_LEN) + { + int i; + if (line_reader_eof (reader)) + break; + else if (line_reader_error (reader)) + msg (ME, _("Error reading `%s': %s"), + tf->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."), + tf->file_name, MAX_LINE_LEN); + line_reader_close (reader); + for (i = 0; i < tf->line_cnt; i++) + g_free (&tf->lines[i]); + tf->line_cnt = 0; + ds_destroy (&input); + return; + } + + tf->lines[tf->line_cnt] + = recode_substring_pool ("UTF-8", + line_reader_get_encoding (reader), + input.ss, NULL); + } + ds_destroy (&input); + + if (tf->line_cnt == 0) + { + int i; + msg (ME, _("`%s' is empty."), tf->file_name); + line_reader_close (reader); + for (i = 0; i < tf->line_cnt; i++) + g_free (&tf->lines[i]); + tf->line_cnt = 0; + goto done; + } + + if (tf->line_cnt < MAX_PREVIEW_LINES) + { + tf->total_lines = tf->line_cnt; + tf->total_is_exact = true; + } + else + { + /* Estimate the number of lines in the file. */ + struct stat s; + off_t position = line_reader_tell (reader); + if (fstat (line_reader_fileno (reader), &s) == 0 && position > 0) + { + tf->total_lines = (double) tf->line_cnt / position * s.st_size; + tf->total_is_exact = false; + } + else + { + tf->total_lines = 0; + tf->total_is_exact = true; + } + } + done: + line_reader_close (reader); + } + +} + +static void +psppire_text_file_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + PsppireTextFile *tf = PSPPIRE_TEXT_FILE (object); + + switch (prop_id) + { + case PROP_FILE_NAME: + tf->file_name = g_value_dup_string (value); + read_lines (tf); + break; + case PROP_ENCODING: + g_free (tf->encoding); + tf->encoding = g_value_dup_string (value); + read_lines (tf); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + }; + +} + +static void +psppire_text_file_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + PsppireTextFile *text_file = PSPPIRE_TEXT_FILE (object); + + switch (prop_id) + { + case PROP_FILE_NAME: + g_value_set_string (value, text_file->file_name); + break; + case PROP_ENCODING: + g_value_set_string (value, text_file->encoding); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + }; +} + + +static void psppire_text_file_init (PsppireTextFile *text_file); +static void psppire_text_file_class_init (PsppireTextFileClass *class); + +static void psppire_text_file_finalize (GObject *object); +static void psppire_text_file_dispose (GObject *object); + +static GObjectClass *parent_class = NULL; + + +static gboolean +__tree_get_iter (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreePath *path) +{ + PsppireTextFile *file = PSPPIRE_TEXT_FILE (tree_model); + + if (path == NULL) + return FALSE; + + gint *indices = gtk_tree_path_get_indices (path); + + gint n = *indices; + + if (n >= file->line_cnt) + return FALSE; + + iter->user_data = GINT_TO_POINTER (n); + iter->stamp = file->stamp; + + return TRUE; +} + + +static gboolean +__tree_iter_next (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + PsppireTextFile *file = PSPPIRE_TEXT_FILE (tree_model); + g_return_val_if_fail (file->stamp == iter->stamp, FALSE); + + gint n = GPOINTER_TO_INT (iter->user_data) + 1; + + if (n >= file->line_cnt) + return FALSE; + + iter->user_data = GINT_TO_POINTER (n); + + return TRUE; +} + + +static GType +__tree_get_column_type (GtkTreeModel *tree_model, + gint index) +{ + if (index == 0) + return G_TYPE_INT; + + return G_TYPE_STRING; +} + +static gboolean +__iter_has_child (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + return 0; +} + + +static gboolean +__iter_parent (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *child) +{ + return 0; +} + +static GtkTreePath * +__tree_get_path (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + PsppireTextFile *file = PSPPIRE_TEXT_FILE (tree_model); + g_return_val_if_fail (file->stamp == iter->stamp, FALSE); + + gint n = GPOINTER_TO_INT (iter->user_data); + + return gtk_tree_path_new_from_indices (n, -1); +} + + +static gboolean +__iter_children (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent) +{ + return 0; +} + + +static gint +__tree_model_iter_n_children (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + PsppireTextFile *file = PSPPIRE_TEXT_FILE (tree_model); + g_assert (iter == NULL); + return file->line_cnt; +} + +static GtkTreeModelFlags +__tree_model_get_flags (GtkTreeModel *model) +{ + g_return_val_if_fail (PSPPIRE_IS_TEXT_FILE (model), (GtkTreeModelFlags) 0); + + return GTK_TREE_MODEL_LIST_ONLY; +} + +static gint +__tree_model_get_n_columns (GtkTreeModel *tree_model) +{ + PsppireTextFile *tf = PSPPIRE_TEXT_FILE (tree_model); + return 2; +} + + +static gboolean +__iter_nth_child (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n) +{ + PsppireTextFile *file = PSPPIRE_TEXT_FILE (tree_model); + + g_assert (parent == NULL); + + g_return_val_if_fail (file, FALSE); + + if (n >= file->line_cnt) + { + iter->stamp = -1; + iter->user_data = NULL; + return FALSE; + } + + iter->user_data = GINT_TO_POINTER (n); + iter->stamp = file->stamp; + + return TRUE; +} + + +static void +__get_value (GtkTreeModel *tree_model, + GtkTreeIter *iter, + gint column, + GValue *value) +{ + PsppireTextFile *file = PSPPIRE_TEXT_FILE (tree_model); + + g_return_if_fail (iter->stamp == file->stamp); + + gint n = GPOINTER_TO_INT (iter->user_data); + + g_return_if_fail (n < file->line_cnt); + + if (column == 0) + { + g_value_init (value, G_TYPE_INT); + g_value_set_int (value, n + 1); + return; + } + + g_value_init (value, G_TYPE_STRING); + + if (column == 1) + { + char *s = ss_xstrdup (file->lines[n]); + g_value_set_string (value, s); + free (s); + return; + } + + g_assert_not_reached (); +} + + +static void +__tree_model_init (GtkTreeModelIface *iface) +{ + iface->get_flags = __tree_model_get_flags; + iface->get_n_columns = __tree_model_get_n_columns ; + iface->get_column_type = __tree_get_column_type; + iface->get_iter = __tree_get_iter; + iface->iter_next = __tree_iter_next; + iface->get_path = __tree_get_path; + iface->get_value = __get_value; + + iface->iter_children = __iter_children; + iface->iter_has_child = __iter_has_child; + iface->iter_n_children = __tree_model_iter_n_children; + iface->iter_nth_child = __iter_nth_child; + iface->iter_parent = __iter_parent; +} + + +GType +psppire_text_file_get_type (void) +{ + static GType text_file_type = 0; + + if (!text_file_type) + { + static const GTypeInfo text_file_info = + { + sizeof (PsppireTextFileClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) psppire_text_file_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (PsppireTextFile), + 0, + (GInstanceInitFunc) psppire_text_file_init, + }; + + static const GInterfaceInfo tree_model_info = { + (GInterfaceInitFunc) __tree_model_init, + NULL, + NULL + }; + + text_file_type = g_type_register_static (G_TYPE_OBJECT, + "PsppireTextFile", + &text_file_info, 0); + + g_type_add_interface_static (text_file_type, GTK_TYPE_TREE_MODEL, + &tree_model_info); + } + + return text_file_type; +} + + +static void +psppire_text_file_class_init (PsppireTextFileClass *class) +{ + GObjectClass *object_class; + + parent_class = g_type_class_peek_parent (class); + object_class = (GObjectClass*) class; + + GParamSpec *file_name_spec = + g_param_spec_string ("file-name", + "File Name", + P_("The name of the file from which this object was constructed"), + NULL, + G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE); + + GParamSpec *encoding_spec = + g_param_spec_string ("encoding", + "Character Encoding", + P_("The character encoding of the file from which this object was constructed"), + "unset", + G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE); + + object_class->set_property = psppire_text_file_set_property; + object_class->get_property = psppire_text_file_get_property; + + g_object_class_install_property (object_class, + PROP_FILE_NAME, + file_name_spec); + + g_object_class_install_property (object_class, + PROP_ENCODING, + encoding_spec); + + object_class->finalize = psppire_text_file_finalize; + object_class->dispose = psppire_text_file_dispose; +} + + +static void +psppire_text_file_init (PsppireTextFile *text_file) +{ + text_file->encoding = g_strdup ("unset"); + text_file->file_name = NULL; + + text_file->dispose_has_run = FALSE; + text_file->stamp = g_random_int (); +} + + +PsppireTextFile * +psppire_text_file_new (const gchar *file_name, const gchar *encoding) +{ + PsppireTextFile *retval = + g_object_new (PSPPIRE_TYPE_TEXT_FILE, + "file-name", file_name, + "encoding", encoding, + NULL); + + return retval; +} + +static void +psppire_text_file_finalize (GObject *object) +{ + PsppireTextFile *tf = PSPPIRE_TEXT_FILE (object); + + g_free (tf->encoding); + g_free (tf->file_name); + + /* must chain up */ + (* parent_class->finalize) (object); +} + + +static void +psppire_text_file_dispose (GObject *object) +{ + PsppireTextFile *ds = PSPPIRE_TEXT_FILE (object); + + if (ds->dispose_has_run) + return; + + /* must chain up */ + (* parent_class->dispose) (object); + + ds->dispose_has_run = TRUE; +} + +gboolean +psppire_text_file_get_total_exact (PsppireTextFile *tf) +{ + return tf->total_is_exact; +} + +gulong +psppire_text_file_get_n_lines (PsppireTextFile *tf) +{ + return tf->total_lines; +} diff --git a/src/ui/gui/psppire-text-file.h b/src/ui/gui/psppire-text-file.h new file mode 100644 index 0000000000..1d031c0926 --- /dev/null +++ b/src/ui/gui/psppire-text-file.h @@ -0,0 +1,88 @@ +/* PSPPIRE - a graphical user interface for PSPP. + Copyright (C) 2017 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 __PSPPIRE_TEXT_FILE_H__ +#define __PSPPIRE_TEXT_FILE_H__ + +#include "libpspp/str.h" + +#include + +G_BEGIN_DECLS + + + +#define PSPPIRE_TYPE_TEXT_FILE (psppire_text_file_get_type ()) + +#define PSPPIRE_TEXT_FILE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + PSPPIRE_TYPE_TEXT_FILE, PsppireTextFile)) + +#define PSPPIRE_TEXT_FILE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + PSPPIRE_TYPE_TEXT_FILE, \ + PsppireTextFileClass)) + + +#define PSPPIRE_IS_TEXT_FILE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PSPPIRE_TYPE_TEXT_FILE)) + +#define PSPPIRE_IS_TEXT_FILE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), PSPPIRE_TYPE_TEXT_FILE)) + +#define PSPPIRE_TEXT_FILE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + PSPPIRE_TYPE_TEXT_FILE, \ + PsppireTextFileClass)) + +enum { MAX_PREVIEW_LINES = 1000 }; /* Max number of lines to read. */ + +struct _PsppireTextFile +{ + GObject parent; + + gchar *file_name; + gchar *encoding; + + /* The first several lines of the file. */ + struct substring lines[MAX_PREVIEW_LINES]; + size_t line_cnt; + gulong total_lines; /* Number of lines in file. */ + gboolean total_is_exact; /* Is total_lines exact (or an estimate)? */ + + /*< private >*/ + gboolean dispose_has_run ; + gint stamp; +}; + +struct _PsppireTextFileClass +{ + GObjectClass parent_class; +}; + + +typedef struct _PsppireTextFile PsppireTextFile; +typedef struct _PsppireTextFileClass PsppireTextFileClass; + +GType psppire_text_file_get_type (void) G_GNUC_CONST; +PsppireTextFile *psppire_text_file_new (const gchar *file_name, const gchar *encoding); + +gboolean psppire_text_file_get_total_exact (PsppireTextFile *tf); +gulong psppire_text_file_get_n_lines (PsppireTextFile *tf); + +G_END_DECLS + +#endif /* __PSPPIRE_TEXT_FILE_H__ */ -- 2.30.2