/*******************************************************************\
 * assistant-csv-trans-import.c -- An assistant for importing       *
 *                                     Transactions from a file.    *
 *                                                                  *
 * Copyright (C) 2012 Robert Fewell                                 *
 * Copyright (c) 2007 Benny Sperisen <lasindi@gmail.com>            *
 *                                                                  *
 * 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 2 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, contact:                        *
 *                                                                  *
 * Free Software Foundation           Voice:  +1-617-542-5942       *
 * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
 * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
\********************************************************************/
/** @file assistant-csv-trans-import.c
    @brief CSV Import Assistant
    @author Copyright (c) 2012 Robert Fewell
*/
#include "config.h"

#include <gtk/gtk.h>
#include <glib/gi18n.h>

#include "gnc-ui.h"
#include "gnc-uri-utils.h"
#include "gnc-ui-util.h"
#include "dialog-utils.h"

#include "gnc-component-manager.h"

#include "assistant-csv-trans-import.h"

#include "import-account-matcher.h"
#include "import-main-matcher.h"

#include "gnc-csv-model.h"
#include "gnc-csv-gnumeric-popup.h"
#include <goffice/gtk/go-charmap-sel.h>

#define MIN_COL_WIDTH 70
#define GNC_PREFS_GROUP "dialogs.import.csv"
#define ASSISTANT_CSV_IMPORT_TRANS_CM_CLASS "assistant-csv-trans-import"

/* This static indicates the debugging module that this .o belongs to.  */
static QofLogModule log_module = GNC_MOD_ASSISTANT;

/** Enumeration for separator checkbutton types. These are the
 * different types of checkbuttons that the user can click to
 * configure separators in a delimited file. */
enum SEP_BUTTON_TYPES {SEP_SPACE, SEP_TAB, SEP_COMMA, SEP_COLON, SEP_SEMICOLON, SEP_HYPHEN,
                       SEP_NUM_OF_TYPES
                      };

typedef struct
{

    GtkWidget       *window;

    GtkWidget       *file_chooser;                  /**< The widget for the file chooser */
    gchar           *starting_dir;                  /**< The starting directory for import file */
    gchar           *file_name;                     /**< The import file name */

    GtkWidget       *check_label;                   /**< The widget for the check label */
    GtkWidget       *check_butt;                    /**< The widget for the check label button */
    GtkWidget       *start_row_spin;                /**< The widget for the start row spinner */
    GtkWidget       *end_row_spin;                  /**< The widget for the end row spinner */
    GtkWidget       *skip_rows;                     /**< The widget for Skip alternate rows from start row */
    int              start_row;                     /**< The liststore start row, smallest is 0 */
    int              end_row;                       /**< The liststore end row, max number of rows -1 */

    GncCsvParseData *parse_data;                    /**< The actual data we are previewing */
    GOCharmapSel    *encselector;                   /**< The widget for selecting the encoding */
    GtkCheckButton  *sep_buttons[SEP_NUM_OF_TYPES]; /**< Checkbuttons for common separators */
    GtkCheckButton  *custom_cbutton;                /**< The checkbutton for a custom separator */
    GtkEntry        *custom_entry;                  /**< The entry for custom separators */
    GtkComboBoxText *date_format_combo;             /**< The Combo Text widget for selecting the date format */
    GtkComboBoxText *currency_format_combo;         /**< The Combo Text widget for selecting the currency format */
    GtkTreeView     *treeview;                      /**< The treeview containing the data */
    GtkTreeView     *ctreeview;                     /**< The treeview containing the column types */
    GtkLabel        *instructions_label;            /**< The instructions label */
    GtkImage        *instructions_image;            /**< The instructions image */
    gboolean         encoding_selected_called;      /**< Before encoding_selected is first called, this is FALSE.
                                                       * (See description of encoding_selected.) */
    gboolean         not_empty;                     /**< FALSE initially, true after the first type gnc_csv_preview_update_assist is called. */
    gboolean         previewing_errors;             /**< TRUE if the dialog is displaying
                                                       * error lines, instead of all the file data. */
    int              code_encoding_calls;           /**< Normally this is 0. If the computer
                                                       * changes encselector, this is set to
                                                       * 2. encoding_selected is called twice,
                                                       * each time decrementing this by 1. */
    gboolean         approved;                      /**< This is FALSE until the user clicks "OK". */
    GtkWidget      **treeview_buttons;              /**< This array contains the header buttons in treeview */
    int              num_of_rows;                   /**< The number of rows in the store */
    int              longest_line;                  /**< The length of the longest row */
    int              fixed_context_col;             /**< The number of the column whose the user has clicked */
    int              fixed_context_dx;              /**< The horizontal coordinate of the pixel in the header of the column
                                                       * the user has clicked */

    GtkWidget           *account_page;              /**< The widget for the account page, to be packed with the account matcher */
    GtkWidget           *account_label;             /**< The account page label at bottom of page */
    AccountPickerDialog *account_picker;            /**< The AccountPickerDialog structure */
    gboolean             account_page_step;         /**< Allows for auto stepping the account page if we match online id */

    GNCImportMainMatcher *gnc_csv_importer_gui;     /**< The GNCImportMainMatcher structure */
    GtkWidget            *match_page;               /**< The widget for the matcher page, to be packed with the transaction matcher */
    GtkWidget            *match_label;              /**< The match label at the bottom of the page */
    GtkWidget            *help_button;              /**< The widget for the help button on the matcher page */
    GtkWidget            *cancel_button;            /**< The widget for the new cancel button when going back is blocked */
    gboolean              match_parse_run;          /**< This is set after the first run */

    GtkWidget            *summary_label;            /**< The summary text */

    gboolean              new_book;                 /**< Are we importing into a new book?; if yes, call book options */

} CsvImportTrans;


/*************************************************************************/

void csv_import_trans_assistant_prepare (GtkAssistant  *assistant, GtkWidget *page, gpointer user_data);
void csv_import_trans_assistant_finish (GtkAssistant *gtkassistant, gpointer user_data);
void csv_import_trans_assistant_cancel (GtkAssistant *gtkassistant, gpointer user_data);
void csv_import_trans_assistant_close (GtkAssistant *gtkassistant, gpointer user_data);

void csv_import_trans_assistant_start_page_prepare (GtkAssistant *gtkassistant, gpointer user_data);
void csv_import_trans_assistant_file_page_prepare (GtkAssistant *assistant, gpointer user_data);
void csv_import_trans_assistant_preview_page_prepare (GtkAssistant *gtkassistant, gpointer user_data);
void csv_import_trans_assistant_account_page_prepare (GtkAssistant *assistant, gpointer user_data);
void csv_import_trans_assistant_doc_page_prepare (GtkAssistant *assistant, gpointer user_data);
void csv_import_trans_assistant_match_page_prepare (GtkAssistant *assistant, gpointer user_data);
void csv_import_trans_assistant_finish_page_prepare (GtkAssistant *assistant, gpointer user_data);
void csv_import_trans_assistant_summary_page_prepare (GtkAssistant *assistant, gpointer user_data);

void csv_import_trans_srow_cb (GtkWidget *spin, gpointer user_data);
void csv_import_trans_erow_cb (GtkWidget *spin, gpointer user_data);
void csv_import_trans_skiprows_cb (GtkWidget *checkbox, gpointer user_data);
void csv_import_trans_auto_cb (GtkWidget *cb, gpointer user_data);
void csv_import_trans_file_chooser_confirm_cb (GtkWidget *button, CsvImportTrans *info);

static void gnc_csv_preview_update_assist (CsvImportTrans* info);
gboolean preview_settings_valid (CsvImportTrans *info);

/*************************************************************************/

/**************************************************
 * csv_import_trans_file_chooser_confirm_cb
 *
 * call back for ok button in file chooser widget
 **************************************************/
void
csv_import_trans_file_chooser_confirm_cb (GtkWidget *button, CsvImportTrans *info)
{
    GtkAssistant *assistant = GTK_ASSISTANT(info->window);
    gint num = gtk_assistant_get_current_page (assistant);
    GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);
    GError* error;
    gchar *file_name;
    GncCsvParseData* parse_data;

    gtk_assistant_set_page_complete (assistant, page, FALSE);

    file_name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(info->file_chooser));

    if (file_name)
    {
        gchar *filepath = gnc_uri_get_path (file_name);
        gchar *filedir = g_path_get_dirname (filepath);
        info->starting_dir = g_strdup (filedir);
        g_free (filedir);
        g_free (filepath);

        info->file_name = g_strdup (file_name);
        error = NULL;
        /* Load the file into parse_data. */
        parse_data = gnc_csv_new_parse_data();
        if (gnc_csv_load_file (parse_data, file_name, &error))
        {
            /* If we couldn't load the file ... */
            gnc_error_dialog (NULL, "%s", error->message);
            if (error->code == GNC_CSV_IMP_ERROR_OPEN)
            {
                gnc_csv_parse_data_free (parse_data);
                return;
            }
            /* If we couldn't guess the encoding, we are content with just
             * displaying an error message and move on with a blank
             * display. */
        }
        else
        {
            /* Parse the data. */
            if (gnc_csv_parse (parse_data, TRUE, &error))
            {
                /* If we couldn't parse the data ... */
                gnc_error_dialog (NULL, "%s", error->message);
                gnc_csv_parse_data_free (parse_data);
            }
            else
            {
                info->parse_data = parse_data;
                info->previewing_errors = FALSE; /* We're looking at all the data. */
                info->approved = FALSE; /* This is FALSE until the user clicks "OK". */
                gtk_assistant_set_page_complete (assistant, page, TRUE);
            }
        }
    }
    g_free (file_name);

    DEBUG("file_name selected is %s", info->file_name);
    DEBUG("starting directory is %s", info->starting_dir);

    /* Step to next page if page is complete */
    if(gtk_assistant_get_page_complete (assistant, page))
        gtk_assistant_set_current_page (assistant, num + 1);
}


/**************************************************
 * row_selection_update
 *
 * refresh the start and end row highlighting
 **************************************************/
static
void row_selection_update (CsvImportTrans* info)
{
    GtkListStore *store;
    GtkTreeIter iter;
    gboolean valid;
    int i = 0;

    store = GTK_LIST_STORE(gtk_tree_view_get_model (info->treeview));

    /* Start of file */
    for ( i = 0; i <= info->start_row; i++)
    {
        /* Modify background color of rows less than start row */
        if (info->start_row == i)
        {
            valid = gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(store), &iter, NULL, i);
            if (valid)
                gtk_list_store_set (store, &iter, 0, NULL, -1);
        }
        else
        {
            valid = gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(store), &iter, NULL, i);
            if (valid)
                gtk_list_store_set (store, &iter, 0, "pink", -1);
            valid = gtk_tree_model_iter_next (GTK_TREE_MODEL(store), &iter);
            if (valid)
                gtk_list_store_set (store, &iter, 0, NULL, -1);
        }

    }

    /* End of File */
    for ( i = info->num_of_rows - 1; i >= info->end_row; i--)
    {
        /* Modify background color of rows more than end row */
        if (i == info->end_row)
        {
            valid = gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(store), &iter, NULL, i);
            if (valid)
                gtk_list_store_set (store, &iter, 0, NULL, -1);
        }
        else
        {
            valid = gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(store), &iter, NULL, i);
            if (valid)
                gtk_list_store_set (store, &iter, 0, "pink", -1);
            valid = gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(store), &iter, NULL, i - 1);
            if (valid)
                gtk_list_store_set (store, &iter, 0, NULL, -1);
        }
    }

    /* Remove background color from the start row to end row */
    for ( i = info->start_row + 1; i <= info->end_row; i++)
    {
        valid = gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(store), &iter, NULL, i);
        if (valid)
            gtk_list_store_set (store, &iter, 0, NULL, -1);
    }

    /* Skip rows */
    if (info->parse_data->skip_rows == TRUE)
    {
        for ( i = info->start_row + 1; i <= info->end_row; i = i + 2)
        {
            /* Modify background color of alternate rows from the start row */
            valid = gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(store), &iter, NULL, i);
            if (valid)
                gtk_list_store_set (store, &iter, 0, "pink", -1);
        }
    }
}


/*******************************************************
 * csv_import_trans_srow_cb
 *
 * call back for import start row
 *******************************************************/
void csv_import_trans_srow_cb (GtkWidget *spin, gpointer user_data)
{
    CsvImportTrans *info = user_data;
    GtkAdjustment *adj;

    /* Get number of rows for header */
    info->start_row = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON(spin)) - 1;

    info->parse_data->start_row = info->start_row;

    adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON(info->end_row_spin));
    gtk_adjustment_set_lower (adj, info->start_row + 1);

    /* Refresh the row highlighting */
    row_selection_update (info);
}


/*******************************************************
 * csv_import_trans_erow_cb
 *
 * call back for import end row
 *******************************************************/
void csv_import_trans_erow_cb (GtkWidget *spin, gpointer user_data)
{
    CsvImportTrans *info = user_data;
    GtkAdjustment *adj;

    /* Get number of rows for header */
    info->end_row = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON(spin)) - 1;

    info->parse_data->end_row = info->end_row + 1;

    adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON(info->start_row_spin));
    gtk_adjustment_set_upper (adj, info->end_row + 1);

    /* Refresh the row highlighting */
    row_selection_update (info);
}


/*******************************************************
 * csv_import_trans_auto_cb
 *
 * call back for auto create account / Skip Errors
 *******************************************************/
void csv_import_trans_auto_cb (GtkWidget *cb, gpointer user_data)
{
    CsvImportTrans *info = user_data;

    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(cb)))
    {
        if (info->previewing_errors == TRUE)
            info->approved = TRUE;
        else
            info->account_picker->auto_create = TRUE;
    }
    else
    {
        if (info->previewing_errors == TRUE)
            info->approved = FALSE;
        else
            info->account_picker->auto_create = FALSE;
    }
}


/*******************************************************
 * csv_import_trans_skiprows_cb
 *
 * call back for import skip rows checkbox
 *******************************************************/
void csv_import_trans_skiprows_cb (GtkWidget *checkbox, gpointer user_data)
{
    CsvImportTrans *info = user_data;

    /* Set the skip_rows variable */
    info->parse_data->skip_rows = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(checkbox));

    /* Refresh the row highlighting */
    row_selection_update (info);
}


/** Returns the cell renderer from a column in the preview's treeview.
 * @param info The display of the data being imported
 * @param col The number of the column whose cell renderer is being retrieved
 * @return The cell renderer of column number col
 */
static GtkCellRenderer* gnc_csv_preview_get_cell_renderer (CsvImportTrans* info, int col)
{
    GList* renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT(gtk_tree_view_get_column (info->treeview, col)));

    GtkCellRenderer* cell = GTK_CELL_RENDERER(renderers->data);
    g_list_free (renderers);
    return cell;
}


/** Event handler for separator changes. This function is called
 * whenever one of the widgets for configuring the separators (the
 * separator checkbuttons or the custom separator entry) is
 * changed.
 * @param widget The widget that was changed
 * @param info The data that is being configured
 */
static void sep_button_clicked (GtkWidget* widget, CsvImportTrans* info)
{
    int i;
    char* stock_separator_characters[] = {" ", "\t", ",", ":", ";", "-"};
    GSList* checked_separators = NULL;
    GError* error;

    /* Add the corresponding characters to checked_separators for each
     * button that is checked. */
    for (i = 0; i < SEP_NUM_OF_TYPES; i++)
    {
        if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(info->sep_buttons[i])))
            checked_separators = g_slist_append (checked_separators, stock_separator_characters[i]);
    }

    /* Add the custom separator if the user checked its button. */
    if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(info->custom_cbutton)))
    {
        char* custom_sep = (char*)gtk_entry_get_text (info->custom_entry);
        if (custom_sep[0] != '\0') /* Don't add a blank separator (bad things will happen!). */
            checked_separators = g_slist_append (checked_separators, custom_sep);
    }

    /* Set the parse options using the checked_separators list. */
    stf_parse_options_csv_set_separators (info->parse_data->options, NULL, checked_separators);
    g_slist_free (checked_separators);

    /* Parse the data using the new options. We don't want to reguess
     * the column types because we want to leave the user's
     * configurations in tact. */
    if (gnc_csv_parse (info->parse_data, FALSE, &error))
    {
        /* Warn the user there was a problem and try to undo what caused
         * the error. (This will cause a reparsing and ideally a usable
         * configuration.) */
        gnc_error_dialog (NULL, "Error in parsing");
        /* If the user changed the custom separator, erase that custom separator. */
        if (widget == GTK_WIDGET(info->custom_entry))
        {
            gtk_entry_set_text (GTK_ENTRY(widget), "");
        }
        /* If the user checked a checkbutton, toggle that checkbutton back. */
        else
        {
            gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(widget),
                                         !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(widget)));
        }
        return;
    }

    /* If we parsed successfully, redisplay the data. */
    gnc_csv_preview_update_assist (info);

    /* Refresh the row highlighting */
    row_selection_update (info);
}


/** Event handler for clicking one of the format type radio
 * buttons. This occurs if the format (Fixed-Width or CSV) is changed.
 * @param csv_button The "Separated" radio button
 * @param info The display of the data being imported
 */
static void separated_or_fixed_selected (GtkToggleButton* csv_button, CsvImportTrans* info)
{
    GError* error = NULL;
    /* Set the parsing type correctly. */
    if (gtk_toggle_button_get_active (csv_button)) /* If we're in CSV mode ... */
    {
        stf_parse_options_set_type (info->parse_data->options, PARSE_TYPE_CSV);
    }
    else /* If we're in fixed-width mode ... */
    {
        stf_parse_options_set_type (info->parse_data->options, PARSE_TYPE_FIXED);
    }

    /* Reparse the data. */
    if (gnc_csv_parse (info->parse_data, FALSE, &error))
    {
        /* Show an error dialog explaining the problem. */
        gnc_error_dialog (NULL, "%s", error->message);
        return;
    }

    /* Show the new data. */
    gnc_csv_preview_update_assist (info);

    /* Refresh the row highlighting */
    row_selection_update (info);
}


/** Event handler for a new encoding. This is called when the user
 * selects a new encoding; the data is reparsed and shown to the
 * user.
 * @param selector The widget the user uses to select a new encoding
 * @param encoding The encoding that the user selected
 * @param info The display of the data being imported
 */
static void encoding_selected (GOCharmapSel* selector, const char* encoding,
                              CsvImportTrans* info)
{
    /* This gets called twice everytime a new encoding is selected. The
     * second call actually passes the correct data; thus, we only do
     * something the second time this is called. */

    /* Prevent code-caused calls of this function from having an impact. */
    if (info->code_encoding_calls > 0)
    {
        info->code_encoding_calls--;
        return;
    }

    /* If this is the second time the function is called ... */
    if (info->encoding_selected_called)
    {
        const char* previous_encoding = info->parse_data->encoding;
        GError* error = NULL;
        /* Try converting the new encoding and reparsing. */
        if (gnc_csv_convert_encoding (info->parse_data, encoding, &error) ||
                gnc_csv_parse (info->parse_data, FALSE, &error))
        {
            /* If it fails, change back to the old encoding. */
            gnc_error_dialog (NULL, "%s", _("Invalid encoding selected"));
            info->encoding_selected_called = FALSE;
            go_charmap_sel_set_encoding (selector, previous_encoding);
            return;
        }

        gnc_csv_preview_update_assist (info);
        info->encoding_selected_called = FALSE;
    }
    else /* If this is the first call of the function ... */
    {
        info->encoding_selected_called = TRUE; /* ... set the flag and wait for the next call. */
    }
}


/** Event handler for selecting a new date format.
 * @param format_selector The combo box for selecting date formats
 * @param info The display of the data being imported
 */
static void date_format_selected (GtkComboBoxText* format_selector, CsvImportTrans* info)
{
    info->parse_data->date_format = gtk_combo_box_get_active (GTK_COMBO_BOX(format_selector));
}


/** Event handler for selecting a new currency format.
 * @param currency_selector The combo box for selecting currency formats
 * @param info The display of the data being imported
 */
static void currency_format_selected (GtkComboBoxText* currency_selector, CsvImportTrans* info)
{
    info->parse_data->currency_format = gtk_combo_box_get_active (GTK_COMBO_BOX(currency_selector));
}


/*======================================================================*/
/*================== Beginning of Gnumeric Code ========================*/

/* The following is code copied from Gnumeric 1.7.8 licensed under the
 * GNU General Public License version 2 and/or version 3. It is from the file
 * gnumeric/src/dialogs/dialog-stf-fixed-page.c, and it has been
 * modified slightly to work within GnuCash. */

/*
 * Copyright 2001 Almer S. Tigelaar <almer@gnome.org>
 * Copyright 2003 Morten Welinder <terra@gnome.org>
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

enum
{
    CONTEXT_STF_IMPORT_MERGE_LEFT = 1,
    CONTEXT_STF_IMPORT_MERGE_RIGHT = 2,
    CONTEXT_STF_IMPORT_SPLIT = 3,
    CONTEXT_STF_IMPORT_WIDEN = 4,
    CONTEXT_STF_IMPORT_NARROW = 5
};

static GnumericPopupMenuElement const popup_elements[] =
{
    {
        N_("Merge with column on _left"), GTK_STOCK_REMOVE,
        0, 1 << CONTEXT_STF_IMPORT_MERGE_LEFT, CONTEXT_STF_IMPORT_MERGE_LEFT
    },
    {
        N_("Merge with column on _right"), GTK_STOCK_REMOVE,
        0, 1 << CONTEXT_STF_IMPORT_MERGE_RIGHT, CONTEXT_STF_IMPORT_MERGE_RIGHT
    },
    { "", NULL, 0, 0, 0 },
    {
        N_("_Split this column"), NULL,
        0, 1 << CONTEXT_STF_IMPORT_SPLIT, CONTEXT_STF_IMPORT_SPLIT
    },
    { "", NULL, 0, 0, 0 },
    {
        N_("_Widen this column"), GTK_STOCK_GO_FORWARD,
        0, 1 << CONTEXT_STF_IMPORT_WIDEN, CONTEXT_STF_IMPORT_WIDEN
    },
    {
        N_("_Narrow this column"), GTK_STOCK_GO_BACK,
        0, 1 << CONTEXT_STF_IMPORT_NARROW, CONTEXT_STF_IMPORT_NARROW
    },
    { NULL, NULL, 0, 0, 0 },
};

static gboolean
make_new_column (CsvImportTrans* info, int col, int dx, gboolean test_only)
{
    PangoLayout *layout;
    PangoFontDescription *font_desc;
    int charindex, width;
    GtkCellRenderer *cell =	gnc_csv_preview_get_cell_renderer (info, col);
    int colstart, colend;
    GError* error = NULL;

    colstart = (col == 0)
               ? 0
               : stf_parse_options_fixed_splitpositions_nth (info->parse_data->options, col - 1);
    colend = stf_parse_options_fixed_splitpositions_nth (info->parse_data->options, col);

    g_object_get (G_OBJECT(cell), "font_desc", &font_desc, NULL);
    layout = gtk_widget_create_pango_layout (GTK_WIDGET(info->treeview), "x");
    pango_layout_set_font_description (layout, font_desc);
    pango_layout_get_pixel_size (layout, &width, NULL);
    if (width < 1) width = 1;
    charindex = colstart + (dx + width / 2) / width;
    g_object_unref (layout);
    pango_font_description_free (font_desc);

    if (charindex <= colstart || (colend != -1 && charindex >= colend))
        return FALSE;

    if (!test_only)
    {
        stf_parse_options_fixed_splitpositions_add (info->parse_data->options, charindex);
        if (gnc_csv_parse (info->parse_data, FALSE, &error))
        {
            gnc_error_dialog (NULL, "%s", error->message);
            return FALSE;
        }
        gnc_csv_preview_update_assist (info);
    }

    return TRUE;
}

static gboolean
widen_column (CsvImportTrans* info, int col, gboolean test_only)
{
    int colcount = stf_parse_options_fixed_splitpositions_count (info->parse_data->options);
    int nextstart, nextnextstart;
    GError* error = NULL;

    if (col >= colcount - 1)
        return FALSE;

    nextstart = stf_parse_options_fixed_splitpositions_nth (info->parse_data->options, col);

    nextnextstart = (col == colcount - 2)
                    ? info->longest_line
                    : stf_parse_options_fixed_splitpositions_nth (info->parse_data->options, col + 1);

    if (nextstart + 1 >= nextnextstart)
        return FALSE;

    if (!test_only)
    {
        stf_parse_options_fixed_splitpositions_remove (info->parse_data->options, nextstart);
        stf_parse_options_fixed_splitpositions_add (info->parse_data->options, nextstart + 1);
        if (gnc_csv_parse (info->parse_data, FALSE, &error))
        {
            gnc_error_dialog (NULL, "%s", error->message);
            return FALSE;
        }
        gnc_csv_preview_update_assist (info);
    }
    return TRUE;
}

static gboolean
narrow_column (CsvImportTrans* info, int col, gboolean test_only)
{
    int colcount = stf_parse_options_fixed_splitpositions_count (info->parse_data->options);
    int thisstart, nextstart;
    GError* error = NULL;

    if (col >= colcount - 1)
        return FALSE;

    thisstart = (col == 0)
                ? 0
                : stf_parse_options_fixed_splitpositions_nth (info->parse_data->options, col - 1);
    nextstart = stf_parse_options_fixed_splitpositions_nth (info->parse_data->options, col);

    if (nextstart - 1 <= thisstart)
        return FALSE;

    if (!test_only)
    {
        stf_parse_options_fixed_splitpositions_remove (info->parse_data->options, nextstart);
        stf_parse_options_fixed_splitpositions_add (info->parse_data->options, nextstart - 1);
        if (gnc_csv_parse (info->parse_data, FALSE, &error))
        {
            gnc_error_dialog (NULL, "%s", error->message);
            return FALSE;
        }
        gnc_csv_preview_update_assist (info);
    }
    return TRUE;
}

static gboolean
delete_column (CsvImportTrans* info, int col, gboolean test_only)
{
    GError* error = NULL;
    int colcount = stf_parse_options_fixed_splitpositions_count (info->parse_data->options);
    if (col < 0 || col >= colcount - 1)
        return FALSE;

    if (!test_only)
    {
        int nextstart = stf_parse_options_fixed_splitpositions_nth (info->parse_data->options, col);
        stf_parse_options_fixed_splitpositions_remove (info->parse_data->options, nextstart);
        if (gnc_csv_parse (info->parse_data, FALSE, &error))
        {
            gnc_error_dialog (NULL, "%s", error->message);
            return FALSE;
        }
        gnc_csv_preview_update_assist (info);
    }
    return TRUE;
}

static void
select_column (CsvImportTrans* info, int col)
{
    int colcount = stf_parse_options_fixed_splitpositions_count (info->parse_data->options);
    GtkTreeViewColumn *column;

    if (col < 0 || col >= colcount)
        return;

    column = gtk_tree_view_get_column (info->treeview, col);
    gtk_widget_grab_focus (column->button);
}

static gboolean
fixed_context_menu_handler (GnumericPopupMenuElement const *element,
                            gpointer user_data)
{
    CsvImportTrans* info = user_data;
    int col = info->fixed_context_col;

    switch (element->index)
    {
    case CONTEXT_STF_IMPORT_MERGE_LEFT:
        delete_column (info, col - 1, FALSE);
        break;
    case CONTEXT_STF_IMPORT_MERGE_RIGHT:
        delete_column (info, col, FALSE);
        break;
    case CONTEXT_STF_IMPORT_SPLIT:
        make_new_column (info, col, info->fixed_context_dx, FALSE);
        break;
    case CONTEXT_STF_IMPORT_WIDEN:
        widen_column (info, col, FALSE);
        break;
    case CONTEXT_STF_IMPORT_NARROW:
        narrow_column (info, col, FALSE);
        break;
    default:
        ; /* Nothing */
    }
    return TRUE;
}

static void
fixed_context_menu (CsvImportTrans* info, GdkEventButton *event,
                    int col, int dx)
{
    int sensitivity_filter = 0;

    info->fixed_context_col = col;
    info->fixed_context_dx = dx;

    if (!delete_column (info, col - 1, TRUE))
        sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_MERGE_LEFT);
    if (!delete_column (info, col, TRUE))
        sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_MERGE_RIGHT);
    if (!make_new_column (info, col, dx, TRUE))
        sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_SPLIT);
    if (!widen_column (info, col, TRUE))
        sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_WIDEN);
    if (!narrow_column (info, col, TRUE))
        sensitivity_filter |= (1 << CONTEXT_STF_IMPORT_NARROW);

    select_column (info, col);
    gnumeric_create_popup_menu (popup_elements, &fixed_context_menu_handler,
                                info, 0,
                                sensitivity_filter, event);
}

/*===================== End of Gnumeric Code ===========================*/
/*======================================================================*/


/** Event handler for the data treeview being resized. When the data
 * treeview is resized, the column types treeview's columns are also resized to
 * match.
 * @param widget The data treeview
 * @param allocation The size of the data treeview
 * @param info The display of the data being imported
 */
static void treeview_resized (GtkWidget* widget, GtkAllocation* allocation, CsvImportTrans* info)
{
    /* ncols is the number of columns in the data. */
    int i, ncols = info->parse_data->column_types->len;

    /* Go through each column except for the last. (We don't want to set
     * the width of the last column because the user won't be able to
     * shrink the dialog back if it's expanded.) */

    for (i = 0; i < ncols - 1; i++)
    {
        gint col_width; /* The width of the column in info->treeview. */
        GtkTreeViewColumn* pcol;
        GtkTreeViewColumn* ccol; /* The corresponding column in info->ctreeview. */

        /* Get the width. */
        col_width = gtk_tree_view_column_get_width (gtk_tree_view_get_column (info->treeview, i));
        /* Set the minimum width for a column so that drop down selector can be seen. */
        if (col_width < MIN_COL_WIDTH)
        {
            col_width = MIN_COL_WIDTH;
        }
        pcol = gtk_tree_view_get_column (info->treeview, i);
        gtk_tree_view_column_set_min_width (pcol, col_width);
        /* Set ccol's width the same. */
        ccol = gtk_tree_view_get_column (info->ctreeview, i);
        gtk_tree_view_column_set_min_width (ccol, col_width);
        gtk_tree_view_column_set_max_width (ccol, col_width);
    }
}


/** Event handler for the user selecting a new column type. When the
 * user selects a new column type, that column's text must be changed
 * to that selection, and any other columns containing that selection
 * must be changed to "None" because we don't allow duplicates.
 * @param renderer The renderer of the column the user changed
 * @param path There is only 1 row in info->ctreeview, so this is always 0.
 * @param new_text The text the user selected
 * @param info The display of the data being imported
 */
static void column_type_changed (GtkCellRenderer* renderer, gchar* path,
                                GtkTreeIter* new_text_iter, CsvImportTrans* info)
{
    /* ncols is the number of columns in the data. */
    int i, ncols = info->parse_data->column_types->len;
    /* store has the actual strings that appear in info->ctreeview. */
    GtkTreeModel* store = gtk_tree_view_get_model (info->ctreeview);
    GtkTreeModel* model;
    gint textColumn;
    GtkTreeIter iter;
    gchar* new_text;

    /* Get the new text */
    g_object_get (renderer, "model", &model, "text-column", &textColumn, NULL);
    gtk_tree_model_get (model, new_text_iter, textColumn, &new_text, -1);

    /* Get an iterator for the first (and only) row. */
    gtk_tree_model_get_iter_first (store, &iter);

    /* Go through each column. */
    for (i = 0; i < ncols; i++)
    {
        /* We need all this stuff so that we can find out whether or not
         * this was the column that was changed. */
        GtkCellRenderer* col_renderer; /* The renderer for this column. */
        /* The column in the treeview we are looking at */
        GtkTreeViewColumn* col = gtk_tree_view_get_column (info->ctreeview, i);
        /* The list of renderers for col */
        GList* rend_list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT(col));
        /* rend_list has only one entry, which we put in col_renderer. */
        col_renderer = rend_list->data;
        g_list_free (rend_list);

        /* If this is not the column that was changed ... */
        if (col_renderer != renderer)
        {
            /* The string that appears in the column */
            gchar* contents = NULL;
            /* Get the type string. (store is arranged so that every two
             * columns is a pair of the model used for the combobox and the
             * string that appears, so that store looks like:
             * model 0, string 0, model 1, string 1, ..., model ncols, string ncols. */
            gtk_tree_model_get(store, &iter, 2 * i + 1, &contents, -1);
            /* If this column has the same string that the user selected ... */
            if (!g_strcmp0 (contents, new_text))
            {
                /* ... set this column to the "None" type. (We can't allow duplicate types.) */
                gtk_list_store_set (GTK_LIST_STORE(store), &iter, 2 * i + 1,
                                   _(gnc_csv_column_type_strs[GNC_CSV_NONE]), -1);
            }
            g_free (contents);
        }
        else /* If this is the column that was changed ... */
        {
            /* Set the text for this column to what the user selected. (See
             * comment above "Get the type string. ..." for why we set
             * column 2*i+1 in store.) */
            gtk_list_store_set (GTK_LIST_STORE(store), &iter, 2 * i + 1, new_text, -1);
        }
    }
}


/** Event handler for clicking on column headers. This function is
 * called whenever the user clicks on column headers in
 * preview->treeview to modify columns when in fixed-width mode.
 * @param button The button at the top of a column of the treeview
 * @param event The event that happened (where the user clicked)
 * @param info The data being configured
 */
static void header_button_press_handler (GtkWidget* button, GdkEventButton* event,
                                        CsvImportTrans* info)
{
    /* col is the number of the column that was clicked, and offset is
       to correct for the indentation of button. */
    int i, offset;
    GtkAllocation alloc;
    int col = 0, ncols = info->parse_data->column_types->len;

    gtk_widget_get_allocation (gtk_bin_get_child (GTK_BIN(button)), &alloc);
    offset = alloc.x - alloc.x;
    /* Find the column that was clicked. */
    for (i = 0; i < ncols; i++)
    {
        if (info->treeview_buttons[i] == button)
        {
            col = i;
            break;
        }
    }

    /* Don't let the user affect the last column if it has error messages. */
    if (info->parse_data->orig_max_row < ncols && ncols - col == 1)
    {
        return;
    }

    /* Double clicks can split columns. */
    if (event->type == GDK_2BUTTON_PRESS && event->button == 1)
    {
        make_new_column (info, col, (int)event->x - offset, FALSE);
    }
    /* Right clicking brings up a context menu. */
    else if (event->type == GDK_BUTTON_PRESS && event->button == 3)
    {
        fixed_context_menu (info, event, col, (int)event->x - offset);
    }
}


/* Test for the required minimum number of coloumns selected and
 * a valid date format.
 * Returns TRUE if we do or FALSE if we don't.
 *
 * @param info The data being previewed
 */
gboolean preview_settings_valid (CsvImportTrans* info)
{
    /* Shorten the column_types identifier. */
    GArray* column_types = info->parse_data->column_types;
    int i, ncols = column_types->len; /* ncols is the number of columns in the data. */
    int weight = 0;
    gboolean valid = TRUE;
    /* store contains the actual strings appearing in the column types treeview. */
    GtkTreeModel* store = gtk_tree_view_get_model (info->ctreeview);
    /* datastore contains the actual strings appearing in the preview treeview. */
    GtkTreeModel* datastore = gtk_tree_view_get_model (info->treeview);
    GtkTreeIter iter, iter2;
    /* Get an iterator for the first (and only) row. */
    gtk_tree_model_get_iter_first (store, &iter);

    /* Get an iterator for the first required row in the data store. */
    gtk_tree_model_iter_nth_child (GTK_TREE_MODEL(datastore), &iter2, NULL, info->start_row);

    /* Go through each of the columns. */
    for (i = 0; i < ncols; i++)
    {
        int type; /* The column type contained in this column. */
        gchar* contents = NULL; /* The column type string in this column. */
        gchar* prevstr = NULL; /* The string in this column from datastore. */
        gchar* accstr = NULL; /* The string in this column from datastore. */
        /* Get the type string first. (store is arranged so that every two
         * columns is a pair of the model used for the combobox and the
         * string that appears, so that store looks like:
         * model 0, string 0, model 1, string 1, ..., model ncols, string ncols. */
        gtk_tree_model_get (store, &iter, 2 * i + 1, &contents, -1);

        /* Go through each column type until ... */
        for (type = 0; type < GNC_CSV_NUM_COL_TYPES; type++)
        {
            /* ... we find one that matches with what's in the column. */
            if (!g_strcmp0 (contents, _(gnc_csv_column_type_strs[type])))
            {
                /* Set the column_types array appropriately and quit. */
                column_types->data[i] = type;

                switch (type)
                {
                case GNC_CSV_DATE:
                    weight = weight + 1000;
                    gtk_tree_model_get (datastore, &iter2, i + 1, &prevstr, -1);

                    if (parse_date (prevstr, info->parse_data->date_format) == -1)
                        valid = FALSE;
                    break;

                case GNC_CSV_DESCRIPTION:
                case GNC_CSV_NOTES:
                    weight = weight + 100;
                    break;

                case GNC_CSV_BALANCE:
                case GNC_CSV_DEPOSIT:
                case GNC_CSV_WITHDRAWAL:
                    weight = weight + 10;
                    break;

                case GNC_CSV_NUM:
                    weight = weight + 1;
                    break;
                case GNC_CSV_ACCOUNT:
                    weight = weight + 1;
                    gtk_tree_model_get (datastore, &iter2, i + 1, &accstr, -1);
                    info->account_picker->account_online_id_value = strdup (accstr);
                    break;
                }
                break;
            }
        }
        /* Free the type string created by gtk_tree_model_get() */
        g_free (contents);
        g_free (prevstr);
        g_free (accstr);
    }
    if (weight < 1109 || valid == FALSE)
        return FALSE;
    else
        return TRUE;
}


/* Loads the preview's data into its data treeview. not_empty is TRUE
 * when the data treeview already contains data, FALSE otherwise
 * (e.g. the first time this function is called on a preview).
 * @param info The data being previewed
 */
static void gnc_csv_preview_update_assist (CsvImportTrans* info)
{
    /* store has the data from the file being imported. cstores is an
     * array of stores that hold the combo box entries for each
     * column. ctstore contains both pointers to models in cstore and
     * the actual text that appears in info->ctreeview. */
    GtkListStore *store, **cstores, *ctstore;
    GtkTreeIter iter;
    GtkTreeSelection *selection;
    /* ncols is the number of columns in the file data. */
    int i, j, ncols = info->parse_data->column_types->len,
              max_str_len = info->parse_data->file_str.end - info->parse_data->file_str.begin;

    /* store contains only strings. */
    GType* types = g_new (GType, 2 * ncols);
    for (i = 0; i <  ncols + 1; i++)
        types[i] = G_TYPE_STRING;
    store = gtk_list_store_newv (ncols + 1, types);

    /* ctstore is arranged as follows:
     * model 0, text 0, model 1, text 1, ..., model ncols, text ncols. */
    for (i = 0; i < 2 * ncols; i += 2)
    {
        types[i] = GTK_TYPE_TREE_MODEL;
        types[i+1] = G_TYPE_STRING;
    }
    ctstore = gtk_list_store_newv (2 * ncols, types);

    g_free (types);

    /* Each element in cstores is a single column model. */
    cstores = g_new (GtkListStore*, ncols);
    for (i = 0; i < ncols; i++)
    {
        cstores[i] = gtk_list_store_new (1, G_TYPE_STRING);
        /* Add all of the possible entries to the combo box. */
        for (j = 0; j < GNC_CSV_NUM_COL_TYPES; j++)
        {
            gtk_list_store_append (cstores[i], &iter);
            gtk_list_store_set (cstores[i], &iter, 0, _(gnc_csv_column_type_strs[j]), -1);
        }
    }

    if (info->not_empty)
    {
        GList *tv_columns, *tv_columns_begin, *ctv_columns, *ctv_columns_begin;
        tv_columns = tv_columns_begin = gtk_tree_view_get_columns (info->treeview);
        ctv_columns = ctv_columns_begin = gtk_tree_view_get_columns (info->ctreeview);
        /* Clear out existing columns in info->treeview. */
        while (tv_columns != NULL)
        {
            gtk_tree_view_remove_column (info->treeview, GTK_TREE_VIEW_COLUMN(tv_columns->data));
            tv_columns = g_list_next (tv_columns);
        }
        /* Do the same in info->ctreeview. */
        while (ctv_columns != NULL)
        {
            gtk_tree_view_remove_column (info->ctreeview, GTK_TREE_VIEW_COLUMN(ctv_columns->data));
            ctv_columns = g_list_next (ctv_columns);
        }
        g_list_free (tv_columns_begin);
        g_list_free (ctv_columns_begin);
        g_free (info->treeview_buttons);
    }

    /* Fill the data treeview with data from the file. */
    /* Also, update the longest line value within the following loop (whichever is executed). */
    info->longest_line = 0;
    if (info->previewing_errors) /* If we are showing only errors ... */
    {
        /* ... only pick rows that are in info->error_lines. */
        GList* error_lines = info->parse_data->error_lines;
        while (error_lines != NULL)
        {
            int this_line_length = 0;
            i = GPOINTER_TO_INT(error_lines->data);
            gtk_list_store_append (store, &iter);

            /* Row Color column */
            gtk_list_store_set (store, &iter, 0, NULL, -1);

            for (j = 0; j < ((GPtrArray*)(info->parse_data->orig_lines->pdata[i]))->len; j++)
            {
                /* Add this cell's length to the row's length and set the value of the list store. */
                gchar* cell_string = (gchar*)((GPtrArray*)(info->parse_data->orig_lines->pdata[i]))->pdata[j];
                this_line_length += g_utf8_strlen(cell_string, max_str_len);
                gtk_list_store_set (store, &iter, j + 1, cell_string, -1);
            }

            if (this_line_length > info->longest_line)
                info->longest_line = this_line_length;

            error_lines = g_list_next (error_lines);
        }
    }
    else /* Otherwise, put in all of the data. */
    {
        for (i = 0; i < info->parse_data->orig_lines->len; i++)
        {
            int this_line_length = 0;
            gtk_list_store_append (store, &iter);

            /* Row Color column */
            gtk_list_store_set (store, &iter, 0, NULL, -1);

            for (j = 0; j < ((GPtrArray*)(info->parse_data->orig_lines->pdata[i]))->len; j++)
            {
                /* Add this cell's length to the row's length and set the value of the list store. */
                gchar* cell_string = (gchar*)((GPtrArray*)(info->parse_data->orig_lines->pdata[i]))->pdata[j];
                this_line_length += g_utf8_strlen(cell_string, max_str_len);
                gtk_list_store_set (store, &iter, j + 1, cell_string, -1);
            }

            if (this_line_length > info->longest_line)
                info->longest_line = this_line_length;

            /* Set the number of rows in the store */
            info->num_of_rows = i + 1;
        }
    }

    /* Set all the column types to what's in the parse data. */
    gtk_list_store_append (ctstore, &iter);
    gtk_list_store_set (ctstore, &iter, 0, NULL, -1); /* Dummy Column to match row color */
    for (i = 0; i < ncols; i++)
    {
        gtk_list_store_set (ctstore, &iter, 2 * i, cstores[i], 2 * i + 1,
                           _(gnc_csv_column_type_strs[(int)(info->parse_data->column_types->data[i])]),
                           -1);
    }

    info->treeview_buttons = g_new (GtkWidget*, ncols);
    /* Insert columns into the data and column type treeviews. */
    for (i = 0; i < ncols ; i++)
    {
        GtkTreeViewColumn* col; /* The column we add to info->treeview. */
        /* Create renderers for the data treeview (renderer) and the
         * column type treeview (crenderer). */
        GtkCellRenderer* renderer = gtk_cell_renderer_text_new(),
                         *crenderer = gtk_cell_renderer_combo_new();
        /* We want a monospace font for the data in case of fixed-width data. */
        g_object_set (G_OBJECT(renderer), "family", "monospace", NULL);
        /* We are using cstores for the combo box entries, and we don't
         * want the user to be able to manually enter their own column
         * types. */
        g_object_set (G_OBJECT(crenderer), "model", cstores[i], "text-column", 0,
                     "editable", TRUE, "has-entry", FALSE, NULL);
        g_signal_connect (G_OBJECT(crenderer), "changed",
                         G_CALLBACK(column_type_changed), (gpointer)info);

        /* Add a single column for the treeview. */
        col = gtk_tree_view_column_new_with_attributes ("", renderer, "text", i + 1, NULL);

        /* Add the Color column 0 to the renderer */
        gtk_tree_view_column_add_attribute (col, renderer, "background", 0);

        gtk_tree_view_insert_column (info->treeview, col, -1);
        /* Enable resizing of the columns. */
        gtk_tree_view_column_set_resizable (col, TRUE);
        /* Use the alternating model and text entries from ctstore in
         * info->ctreeview. */
        gtk_tree_view_insert_column_with_attributes (info->ctreeview,
                -1, "", crenderer, "model", 2 * i,
                "text", 2 * i + 1, NULL);

        /* We need to allow clicking on the column headers for fixed-width
         * column splitting and merging. */
        g_object_set (G_OBJECT(col), "clickable", TRUE, NULL);
        g_signal_connect (G_OBJECT(col->button), "button_press_event",
                         G_CALLBACK(header_button_press_handler), (gpointer)info);
        info->treeview_buttons[i] = col->button;
    }

    /* Set the treeviews to use the models. */
    gtk_tree_view_set_model (info->treeview, GTK_TREE_MODEL(store));
    gtk_tree_view_set_model (info->ctreeview, GTK_TREE_MODEL(ctstore));

    /* Select the header row */
    gtk_tree_model_get_iter_first (GTK_TREE_MODEL(ctstore), &iter);
    selection = gtk_tree_view_get_selection (info->ctreeview);
    gtk_tree_selection_select_iter (selection, &iter);

    /* Free the memory for the stores. */
    g_object_unref (GTK_TREE_MODEL(store));
    g_object_unref (GTK_TREE_MODEL(ctstore));
    for (i = 0; i < ncols; i++)
        g_object_unref (GTK_TREE_MODEL(cstores[i]));

    /* Make the things actually appear. */
    gtk_widget_show_all (GTK_WIDGET(info->treeview));
    gtk_widget_show_all (GTK_WIDGET(info->ctreeview));

    /* Set the encoding selector to the right encoding. */
    info->code_encoding_calls = 2;
    go_charmap_sel_set_encoding (info->encselector, info->parse_data->encoding);

    /* Set the date format to what's in the combo box (since we don't
     * necessarily know if this will always be the same). */
    info->parse_data->date_format = gtk_combo_box_get_active (GTK_COMBO_BOX(info->date_format_combo));

    /* It's now been filled with some stuff. */
    info->not_empty = TRUE;
}


/*******************************************************
 * load_settings
 *
 * load the default settings for the assistant
 *******************************************************/
static
void load_settings (CsvImportTrans *info)
{
    info->start_row = 0;
    info->account_page_step = TRUE;
    info->match_parse_run = FALSE;
    info->file_name = NULL;
    info->starting_dir = NULL;

    /* The default directory for the user to select files. */
    info->starting_dir = gnc_get_default_directory (GNC_PREFS_GROUP);
}

/*======================================================================*/
/*======================================================================*/

/*******************************************************
 * Assistant page prepare functions
 *******************************************************/
void
csv_import_trans_assistant_start_page_prepare (GtkAssistant *assistant,
        gpointer user_data)
{
    gint num = gtk_assistant_get_current_page (assistant);
    GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);

    /* Enable the Assistant Buttons */
    gtk_assistant_set_page_complete (assistant, page, TRUE);
}


void
csv_import_trans_assistant_file_page_prepare (GtkAssistant *assistant,
        gpointer user_data)
{
    CsvImportTrans *info = user_data;
    gint num = gtk_assistant_get_current_page (assistant);
    GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);

    info->account_picker->auto_create = TRUE; /* Step over account page if we find matching online id */
    info->previewing_errors = FALSE;          /* We're looking at all the data. */
    info->approved = FALSE;                   /* This is FALSE until the user clicks "OK". */

    /* Set the default directory */
    if (info->starting_dir)
        gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER(info->file_chooser), info->starting_dir);

    /* Disable the Forward Assistant Button */
    gtk_assistant_set_page_complete (assistant, page, FALSE);
}


void
csv_import_trans_assistant_preview_page_prepare (GtkAssistant *assistant,
        gpointer user_data)
{
    CsvImportTrans *info = user_data;
    GtkAdjustment *adj;

    g_signal_connect (G_OBJECT(info->treeview), "size-allocate",
                     G_CALLBACK(treeview_resized), (gpointer)info);

    if (info->previewing_errors == TRUE)
    {
        gchar* name;
        GtkIconSize size;

        /* Block going back */
        gtk_assistant_commit (GTK_ASSISTANT(info->window));

        gtk_image_get_stock (info->instructions_image, &name, &size);
        gtk_image_set_from_stock (info->instructions_image, GTK_STOCK_DIALOG_ERROR, size);
        gtk_label_set_text (info->instructions_label,
                           _("The rows displayed below had errors which are in the last column. You can attempt to correct them by changing the configuration."));
        gtk_widget_show (GTK_WIDGET(info->instructions_image));
        gtk_widget_show (GTK_WIDGET(info->instructions_label));

        /* Set spin buttons not sensative */
        gtk_widget_set_sensitive (info->start_row_spin, FALSE);
        gtk_widget_set_sensitive (info->end_row_spin, FALSE);
        gtk_widget_set_sensitive (info->skip_rows, FALSE);
        info->parse_data->skip_rows = FALSE;

        /* Set check button label */
        gtk_label_set_text (GTK_LABEL(info->check_label), _("Skip Errors"));
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(info->check_butt), FALSE);
    }

    /* Load the data into the treeview. */
    gnc_csv_preview_update_assist (info);

    /* Set the upper limit of spin button to number of rows */
    adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON(info->end_row_spin));
    if (gtk_adjustment_get_upper (adj) != info->num_of_rows)
    {
        gtk_adjustment_set_upper (adj, info->num_of_rows);
        gtk_spin_button_set_value (GTK_SPIN_BUTTON(info->end_row_spin), info->num_of_rows);
    }

    /* Set start row */
    adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON(info->start_row_spin));
    gtk_spin_button_set_value (GTK_SPIN_BUTTON(info->start_row_spin), 1);

    /* Update the row selection highlight */
    row_selection_update (info);
}


void
csv_import_trans_assistant_account_page_prepare (GtkAssistant *assistant,
        gpointer user_data)
{
    CsvImportTrans *info = user_data;
    gint num = gtk_assistant_get_current_page (assistant);
    GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);
    gchar *text, *mtext;
    Account * account = NULL;

    if (!preview_settings_valid (info) && (info->approved == FALSE))
    {
        text = g_strdup_printf (gettext ("There are problems with the import settings!\nThe date format could be wrong "
                                        "or there are not enough columns set..."));
        mtext = g_strdup_printf ("<span size=\"medium\" color=\"red\"><b>%s</b></span>", text);
        gtk_label_set_markup (GTK_LABEL(info->account_label), mtext);
        g_free (mtext);
        g_free (text);

        gtk_widget_set_sensitive (info->account_page, FALSE);
    }
    else
    {
        text = g_strdup_printf (gettext ("To Change the account, double click on the required account, click Forward to proceed."));
        mtext = g_strdup_printf ("<span size=\"medium\" color=\"red\"><b>%s</b></span>", text);
        gtk_label_set_markup (GTK_LABEL(info->account_label), mtext);
        g_free (mtext);
        g_free (text);

        gtk_widget_set_sensitive (info->account_page, TRUE);

        /* Let the user select an account to put the transactions in. */
        account = gnc_import_account_assist_update (info->account_picker);

        /* If we have a valid account and auto_create is TRUE, move on to matcher */
        if (!(account == NULL) && (info->account_picker->auto_create == TRUE))
            gtk_assistant_set_current_page (assistant, num + 1);
    }

    /* Enable the Forward Assistant Button */
    if (account == NULL)
        gtk_assistant_set_page_complete (assistant, page, FALSE);
    else
        gtk_assistant_set_page_complete (assistant, page, TRUE);
}


void
csv_import_trans_assistant_doc_page_prepare (GtkAssistant *assistant,
        gpointer user_data)
{
    CsvImportTrans *info = user_data;

    /* Block going back */
    gtk_assistant_commit (GTK_ASSISTANT(info->window));

    if ( info->match_parse_run == FALSE)
    {
        /* Add the Cancel button for the matcher */
        info->cancel_button = gtk_button_new_with_mnemonic (_("_Cancel"));
        gtk_assistant_add_action_widget (assistant, info->cancel_button);
        g_signal_connect (info->cancel_button, "clicked",
                         G_CALLBACK(csv_import_trans_assistant_cancel), info);
        gtk_widget_show (GTK_WIDGET(info->cancel_button));
    }
}


void
csv_import_trans_assistant_match_page_prepare (GtkAssistant *assistant,
        gpointer user_data)
{
    CsvImportTrans *info = user_data;
    gint num = gtk_assistant_get_current_page (assistant);
    GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);
    gchar *text, *mtext;

    /* Block going back */
    gtk_assistant_commit (GTK_ASSISTANT(info->window));

    /* Before creating transactions, if this is a new book, let user specify
     * book options, since they affect how transactions are created */
    if (info->new_book)
        info->new_book = gnc_new_book_option_display (info->window);

    /* Create transactions from the parsed data, first time with FALSE
       Subsequent times with TRUE */
    if ( info->match_parse_run == FALSE)
    {
        gnc_csv_parse_to_trans (info->parse_data, info->account_picker->retAccount, FALSE);
    }
    else
        gnc_csv_parse_to_trans (info->parse_data, info->account_picker->retAccount, TRUE);
    info->match_parse_run = TRUE;

    /* if there are errors, we jump back to preview to correct */
    if (!(info->parse_data->error_lines == NULL) && (info->approved == FALSE) )
    {
        info->previewing_errors = TRUE; /* We're looking at errors. */
        gtk_assistant_set_current_page (assistant, 2);
    }

    if ((info->parse_data->error_lines == NULL) || (info->approved == TRUE) )
    {
        GList* transactions; /* A list of the transactions we create */

        text =  _("Double click on rows to change, then click on Apply to Import");
        mtext = g_strdup_printf ("<span size=\"medium\" color=\"red\"><b>%s</b></span>", text);
        gtk_label_set_markup (GTK_LABEL(info->match_label), mtext);
        g_free (mtext);

        if (info->gnc_csv_importer_gui == NULL)
        {
            /* Create the genereic transaction importer GUI. */
            info->gnc_csv_importer_gui = gnc_gen_trans_assist_new (info->match_page, NULL, FALSE, 42);

            /* Add the help button for the matcher */
            info->help_button = gtk_button_new_with_mnemonic (_("_Help"));
            gtk_assistant_add_action_widget (assistant, info->help_button);
            g_signal_connect (info->help_button, "clicked",
                             G_CALLBACK(on_matcher_help_clicked), info->gnc_csv_importer_gui);
            gtk_widget_show (GTK_WIDGET(info->help_button));

            /* Get the list of the transactions that were created. */
            transactions = info->parse_data->transactions;
            /* Copy all of the transactions to the importer GUI. */
            while (transactions != NULL)
            {
                GncCsvTransLine* trans_line = transactions->data;
                gnc_gen_trans_list_add_trans (info->gnc_csv_importer_gui, trans_line->trans);
                transactions = g_list_next (transactions);
            }
            g_list_free (transactions);
        }
    }
    /* Enable the Forward Assistant Button */
    gtk_assistant_set_page_complete (assistant, page, TRUE);
}


void
csv_import_trans_assistant_summary_page_prepare (GtkAssistant *assistant,
        gpointer user_data)
{
    CsvImportTrans *info = user_data;
    gchar *text, *mtext;

    /* Save the Window size and directory */
    gnc_set_default_directory (GNC_PREFS_GROUP, info->starting_dir);

    /* Remove the added button */
    gtk_assistant_remove_action_widget (assistant, info->help_button);
    gtk_assistant_remove_action_widget (assistant, info->cancel_button);

    text = g_strdup_printf (gettext ("The transactions were imported from the file '%s'."), info->file_name);
    mtext = g_strdup_printf ("<span size=\"medium\"><b>%s</b></span>", text);
    gtk_label_set_markup (GTK_LABEL(info->summary_label), mtext);
    g_free (text);
    g_free (mtext);
}


void
csv_import_trans_assistant_prepare (GtkAssistant *assistant, GtkWidget *page,
                                    gpointer user_data)
{
    gint currentpage = gtk_assistant_get_current_page (assistant);

    switch (currentpage)
    {
    case 0:
        /* Current page is Import Start page */
        csv_import_trans_assistant_start_page_prepare (assistant, user_data);
        break;
    case 1:
        /* Current page is File select page */
        csv_import_trans_assistant_file_page_prepare (assistant, user_data);
        break;
    case 2:
        /* Current page is Preview page */
        csv_import_trans_assistant_preview_page_prepare (assistant, user_data);
        break;
    case 3:
        /* Current page is Account page */
        csv_import_trans_assistant_account_page_prepare (assistant, user_data);
        break;
    case 4:
        /* Current page is Transaction Doc page */
        csv_import_trans_assistant_doc_page_prepare (assistant, user_data);
        break;
    case 5:
        /* Current page is Match page */
        csv_import_trans_assistant_match_page_prepare (assistant, user_data);
        break;
    case 6:
        /* Current page is Summary page */
        csv_import_trans_assistant_summary_page_prepare (assistant, user_data);
        break;
    }
}


/*******************************************************
 * Assistant call back functions
 *******************************************************/
static void
csv_import_trans_assistant_destroy_cb (GtkWidget *object, gpointer user_data)
{
    CsvImportTrans *info = user_data;
    gnc_unregister_gui_component_by_data (ASSISTANT_CSV_IMPORT_TRANS_CM_CLASS, info);
    g_free (info);
}

void
csv_import_trans_assistant_cancel (GtkAssistant *assistant, gpointer user_data)
{
    CsvImportTrans *info = user_data;
    gnc_close_gui_component_by_data (ASSISTANT_CSV_IMPORT_TRANS_CM_CLASS, info);
}

void
csv_import_trans_assistant_close (GtkAssistant *assistant, gpointer user_data)
{
    CsvImportTrans *info = user_data;
    gnc_close_gui_component_by_data (ASSISTANT_CSV_IMPORT_TRANS_CM_CLASS, info);
}

void
csv_import_trans_assistant_finish (GtkAssistant *assistant, gpointer user_data)
{
    CsvImportTrans *info = user_data;

    /* Start the import */
    if (info->parse_data->transactions != NULL)
        gnc_gen_trans_assist_start (info->gnc_csv_importer_gui);
    else
        gnc_gen_trans_list_delete (info->gnc_csv_importer_gui);
}

static void
csv_import_trans_close_handler (gpointer user_data)
{
    CsvImportTrans *info = user_data;

    g_free(info->file_name);
    g_free(info->starting_dir);

    /* Free the memory we allocated. */
    if (!(info->parse_data == NULL))
        gnc_csv_parse_data_free (info->parse_data);

    if (!(info->account_picker == NULL))
        info->account_picker = NULL;

    if (!(info->gnc_csv_importer_gui == NULL))
        info->gnc_csv_importer_gui = NULL;

    gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(info->window));
    gtk_widget_destroy (info->window);
}

/*******************************************************
 * Create the Assistant
 *******************************************************/
static GtkWidget *
csv_import_trans_assistant_create (CsvImportTrans *info)
{
    GtkBuilder *builder;
    GtkWidget *window;
    GtkWidget *box;
    GtkWidget *button, *csv_button, *h_box;

    builder = gtk_builder_new();
    gnc_builder_add_from_file  (builder , "assistant-csv-trans-import.glade", "start_row_adj");
    gnc_builder_add_from_file  (builder , "assistant-csv-trans-import.glade", "end_row_adj");
    gnc_builder_add_from_file  (builder , "assistant-csv-trans-import.glade", "CSV Transaction Assistant");
    window = GTK_WIDGET(gtk_builder_get_object (builder, "CSV Transaction Assistant"));
    info->window = window;

    /* Load default settings */
    load_settings (info);

    /* Enable buttons on all page. */
    gtk_assistant_set_page_complete (GTK_ASSISTANT(window),
                                     GTK_WIDGET(gtk_builder_get_object (builder, "start_page")),
                                     TRUE);
    gtk_assistant_set_page_complete (GTK_ASSISTANT(window),
                                     GTK_WIDGET(gtk_builder_get_object (builder, "file_page")),
                                     FALSE);
    gtk_assistant_set_page_complete (GTK_ASSISTANT(window),
                                     GTK_WIDGET(gtk_builder_get_object (builder, "preview_page")),
                                     TRUE);
    gtk_assistant_set_page_complete (GTK_ASSISTANT(window),
                                     GTK_WIDGET(gtk_builder_get_object (builder, "account_page")),
                                     FALSE);
    gtk_assistant_set_page_complete (GTK_ASSISTANT(window),
                                     GTK_WIDGET(gtk_builder_get_object (builder, "doc_page")),
                                     TRUE);
    gtk_assistant_set_page_complete (GTK_ASSISTANT(window),
                                     GTK_WIDGET(gtk_builder_get_object (builder, "match_page")),
                                     FALSE);
    gtk_assistant_set_page_complete (GTK_ASSISTANT(window),
                                     GTK_WIDGET(gtk_builder_get_object (builder, "summary_page")),
                                     TRUE);

    /* Start Page */

    /* File chooser Page */
    info->file_chooser = gtk_file_chooser_widget_new (GTK_FILE_CHOOSER_ACTION_OPEN);
    g_signal_connect (G_OBJECT(info->file_chooser), "file-activated",
                      G_CALLBACK(csv_import_trans_file_chooser_confirm_cb), info);
    button = gtk_button_new_from_stock (GTK_STOCK_OK);
    gtk_widget_set_size_request (button, 100, -1);
    gtk_widget_show (button);
    h_box = gtk_hbox_new (TRUE, 0);
    gtk_box_pack_start (GTK_BOX(h_box), button, FALSE, FALSE, 0);
    gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER(info->file_chooser), h_box);
    g_signal_connect (G_OBJECT(button), "clicked",
                      G_CALLBACK(csv_import_trans_file_chooser_confirm_cb), info);

    box = GTK_WIDGET(gtk_builder_get_object(builder, "file_page"));
    gtk_box_pack_start (GTK_BOX(box), info->file_chooser, TRUE, TRUE, 6);
    gtk_widget_show (info->file_chooser);

    /* Preview Settings Page */
    {
        char* sep_button_names[] = {"space_cbutton",
                                    "tab_cbutton",
                                    "comma_cbutton",
                                    "colon_cbutton",
                                    "semicolon_cbutton",
                                    "hyphen_cbutton"
                                   };
        GtkContainer *date_format_container, *currency_format_container;
        int i;

        /* The table containing info->encselector and the separator configuration widgets */
        GtkTable* enctable;

        info->start_row_spin = GTK_WIDGET(gtk_builder_get_object (builder, "start_row"));
        info->end_row_spin = GTK_WIDGET(gtk_builder_get_object (builder, "end_row"));
        info->skip_rows = GTK_WIDGET(gtk_builder_get_object (builder, "skip_rows"));
        info->check_label = GTK_WIDGET(gtk_builder_get_object (builder, "check_label"));
        info->check_butt = GTK_WIDGET(gtk_builder_get_object (builder, "check_butt"));

        info->encselector = GO_CHARMAP_SEL(go_charmap_sel_new(GO_CHARMAP_SEL_TO_UTF8));
        /* Connect the selector to the encoding_selected event handler. */
        g_signal_connect (G_OBJECT(info->encselector), "charmap_changed",
                         G_CALLBACK(encoding_selected), (gpointer)info);

        /* Load the separator buttons from the glade builder file into the
         * info->sep_buttons array. */
        for (i = 0; i < SEP_NUM_OF_TYPES; i++)
        {
            info->sep_buttons[i]
                = (GtkCheckButton*)GTK_WIDGET(gtk_builder_get_object (builder, sep_button_names[i]));
            /* Connect them to the sep_button_clicked event handler. */
            g_signal_connect (G_OBJECT(info->sep_buttons[i]), "toggled",
                             G_CALLBACK(sep_button_clicked), (gpointer)info);
        }

        /* Load and connect the custom separator checkbutton in the same way
         * as the other separator buttons. */
        info->custom_cbutton
            = (GtkCheckButton*)GTK_WIDGET(gtk_builder_get_object (builder, "custom_cbutton"));
        g_signal_connect (G_OBJECT(info->custom_cbutton), "clicked",
                         G_CALLBACK(sep_button_clicked), (gpointer)info);

        /* Load the entry for the custom separator entry. Connect it to the
         * sep_button_clicked event handler as well. */
        info->custom_entry = (GtkEntry*)GTK_WIDGET(gtk_builder_get_object (builder, "custom_entry"));
        g_signal_connect (G_OBJECT(info->custom_entry), "changed",
                         G_CALLBACK(sep_button_clicked), (gpointer)info);

        /* Get the table from the Glade builder file. */
        enctable = GTK_TABLE(gtk_builder_get_object (builder, "enctable"));
        /* Put the selector in at the top. */
        gtk_table_attach_defaults (enctable, GTK_WIDGET(info->encselector), 1, 2, 0, 1);
        /* Show the table in all its glory. */
        gtk_widget_show_all (GTK_WIDGET(enctable));

        /* The instructions label and image */
        info->instructions_label = GTK_LABEL(gtk_builder_get_object (builder, "instructions_label"));
        info->instructions_image = GTK_IMAGE(gtk_builder_get_object (builder, "instructions_image"));

        /* Add in the date format combo box and hook it up to an event handler. */
        info->date_format_combo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
        for (i = 0; i < num_date_formats; i++)
        {
            gtk_combo_box_text_append_text (info->date_format_combo, _(date_format_user[i]));
        }
        gtk_combo_box_set_active (GTK_COMBO_BOX(info->date_format_combo), 0);
        g_signal_connect (G_OBJECT(info->date_format_combo), "changed",
                         G_CALLBACK(date_format_selected), (gpointer)info);

        /* Add it to the assistant. */
        date_format_container = GTK_CONTAINER(gtk_builder_get_object (builder, "date_format_container"));
        gtk_container_add (date_format_container, GTK_WIDGET(info->date_format_combo));
        gtk_widget_show_all (GTK_WIDGET(date_format_container));

        /* Add in the currency format combo box and hook it up to an event handler. */
        info->currency_format_combo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
        for (i = 0; i < num_currency_formats; i++)
        {
            gtk_combo_box_text_append_text (info->currency_format_combo, _(currency_format_user[i]));
        }
        /* Default will the locale */
        gtk_combo_box_set_active (GTK_COMBO_BOX(info->currency_format_combo), 0);
        g_signal_connect (G_OBJECT(info->currency_format_combo), "changed",
                         G_CALLBACK(currency_format_selected), (gpointer)info);

        /* Add it to the assistant. */
        currency_format_container = GTK_CONTAINER(gtk_builder_get_object (builder, "currency_format_container"));
        gtk_container_add (currency_format_container, GTK_WIDGET(info->currency_format_combo));
        gtk_widget_show_all (GTK_WIDGET(currency_format_container));

        /* Connect the CSV/Fixed-Width radio button event handler. */
        csv_button = GTK_WIDGET(gtk_builder_get_object (builder, "csv_button"));
        g_signal_connect (csv_button, "toggled",
                         G_CALLBACK(separated_or_fixed_selected), (gpointer)info);

        /* Load the data treeview and connect it to its resizing event handler. */
        info->treeview = (GtkTreeView*)GTK_WIDGET(gtk_builder_get_object (builder, "treeview"));

        /* Load the column type treeview. */
        info->ctreeview = (GtkTreeView*)GTK_WIDGET(gtk_builder_get_object (builder, "ctreeview"));

        /* This is TRUE only after encoding_selected is called, so we must
         * set it initially to FALSE. */
        info->encoding_selected_called = FALSE;

        /* It is empty at first. */
        info->not_empty = FALSE;
    }

    /* Account page */
    /* Initialise the Account Picker and add to the Assistant */
    info->account_page  = GTK_WIDGET(gtk_builder_get_object (builder, "account_page"));
    info->account_picker = gnc_import_account_assist_setup (info->account_page);
    info->account_label = GTK_WIDGET(gtk_builder_get_object (builder, "account_label"));

    /* Matcher page */
    info->match_page  = GTK_WIDGET(gtk_builder_get_object (builder, "match_page"));
    info->match_label = GTK_WIDGET(gtk_builder_get_object (builder, "match_label"));

    /* Summary Page */
    info->summary_label = GTK_WIDGET(gtk_builder_get_object(builder, "summary_label"));

    g_signal_connect (G_OBJECT(window), "destroy",
                      G_CALLBACK (csv_import_trans_assistant_destroy_cb), info);

    gnc_restore_window_size (GNC_PREFS_GROUP, GTK_WINDOW(info->window));

    gtk_builder_connect_signals (builder, info);
    g_object_unref (G_OBJECT(builder));
    return window;
}


/********************************************************************\
 * gnc_file_csv_trans_import                                        *
 * opens up a assistant to import accounts.                         *
 *                                                                  *
 * Args:   import_type                                              *
 * Return: nothing                                                  *
\********************************************************************/
void
gnc_file_csv_trans_import(void)
{
    CsvImportTrans *info;

    info = g_new0 (CsvImportTrans, 1);

    /* In order to trigger a book options display on the creation of a new book,
     * we need to detect when we are dealing with a new book. */
    info->new_book = gnc_is_new_book();

    csv_import_trans_assistant_create (info);

    gnc_register_gui_component (ASSISTANT_CSV_IMPORT_TRANS_CM_CLASS,
                                NULL, csv_import_trans_close_handler,
                                info);

    gtk_widget_show_all (info->window);

    gnc_window_adjust_for_screen (GTK_WINDOW(info->window));
}
