From: John Darrington Date: Thu, 20 Apr 2017 14:29:56 +0000 (+0200) Subject: New objects PsppireTextFile and PsppireDelimitedText. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=50feac63e2fb9776f53e432f137311b4f5563ccf;p=pspp 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. --- 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__ */