Merge Jean Laroche's branch 'add_multi_ofx_import_master' PR 697 to master

This commit is contained in:
Robert Fewell 2020-05-28 10:32:32 +01:00
commit 2dcc0b7a19
8 changed files with 315 additions and 136 deletions

View File

@ -60,29 +60,15 @@ static QofLogModule log_module = GNC_MOD_GUI;
static GNCShutdownCB shutdown_cb = NULL;
static gint save_in_progress = 0;
/********************************************************************\
* gnc_file_dialog *
* Pops up a file selection dialog (either a "Save As" or an *
* "Open"), and returns the name of the file the user selected. *
* (This function does not return until the user selects a file *
* or presses "Cancel" or the window manager destroy button) *
* *
* Args: title - the title of the window *
* filters - list of GtkFileFilters to use, will be *
freed automatically *
* default_dir - start the chooser in this directory *
* type - what type of dialog (open, save, etc.) *
* Return: containing the name of the file the user selected *
\********************************************************************/
char *
gnc_file_dialog (GtkWindow *parent,
const char * title,
GList * filters,
const char * starting_dir,
GNCFileDialogType type
)
// gnc_file_dialog_int is used both by gnc_file_dialog and gnc_file_dialog_multi
static GSList *
gnc_file_dialog_int (GtkWindow *parent,
const char * title,
GList * filters,
const char * starting_dir,
GNCFileDialogType type,
gboolean multi
)
{
GtkWidget *file_box;
const char *internal_name;
@ -91,6 +77,7 @@ gnc_file_dialog (GtkWindow *parent,
const gchar *ok_icon = NULL;
GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
gint response;
GSList* file_name_list = NULL;
ENTER(" ");
@ -130,6 +117,9 @@ gnc_file_dialog (GtkWindow *parent,
action,
_("_Cancel"), GTK_RESPONSE_CANCEL,
NULL);
if (multi)
gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (file_box), TRUE);
if (ok_icon)
gnc_gtk_dialog_add_button(file_box, okbutton, ok_icon, GTK_RESPONSE_ACCEPT);
else
@ -174,23 +164,85 @@ gnc_file_dialog (GtkWindow *parent,
if (response == GTK_RESPONSE_ACCEPT)
{
/* look for constructs like postgres://foo */
internal_name = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER (file_box));
if (internal_name != NULL)
if (multi)
{
if (strstr (internal_name, "file://") == internal_name)
file_name_list = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (file_box));
}
else
{
/* look for constructs like postgres://foo */
internal_name = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER (file_box));
if (internal_name != NULL)
{
/* nope, a local file name */
internal_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (file_box));
if (strstr (internal_name, "file://") == internal_name)
{
/* nope, a local file name */
internal_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (file_box));
}
file_name = g_strdup(internal_name);
}
file_name = g_strdup(internal_name);
file_name_list = g_slist_append (file_name_list, file_name);
}
}
gtk_widget_destroy(GTK_WIDGET(file_box));
LEAVE("%s", file_name ? file_name : "(null)");
return file_name_list;
}
/********************************************************************\
* gnc_file_dialog *
* Pops up a file selection dialog (either a "Save As" or an *
* "Open"), and returns the name of the file the user selected. *
* (This function does not return until the user selects a file *
* or presses "Cancel" or the window manager destroy button) *
* *
* Args: title - the title of the window *
* filters - list of GtkFileFilters to use, will be *
* freed automatically *
* default_dir - start the chooser in this directory *
* type - what type of dialog (open, save, etc.) *
* Return: containing the name of the file the user selected *
\********************************************************************/
char *
gnc_file_dialog (GtkWindow *parent,
const char * title,
GList * filters,
const char * starting_dir,
GNCFileDialogType type
)
{
gchar* file_name = NULL;
GSList* ret = gnc_file_dialog_int (parent, title, filters, starting_dir, type, FALSE);
if (ret)
file_name = g_strdup (ret->data);
g_slist_free_full (ret, g_free);
return file_name;
}
/********************************************************************\
* gnc_file_dialog_multi *
* Pops up a file selection dialog (either a "Save As" or an *
* "Open"), and returns the name of the files the user selected. *
* Similar to gnc_file_dialog with allowing multi-file selection *
* *
* Args: title - the title of the window *
* filters - list of GtkFileFilters to use, will be *
* freed automatically *
* default_dir - start the chooser in this directory *
* type - what type of dialog (open, save, etc.) *
* Return: GList containing the names of the selected files *
\********************************************************************/
GSList *
gnc_file_dialog_multi (GtkWindow *parent,
const char * title,
GList * filters,
const char * starting_dir,
GNCFileDialogType type
)
{
return gnc_file_dialog_int (parent, title, filters, starting_dir, type, TRUE);
}
gboolean
show_session_error (GtkWindow *parent,

View File

@ -149,6 +149,12 @@ char * gnc_file_dialog (GtkWindow *parent,
const char * starting_dir,
GNCFileDialogType type);
GSList * gnc_file_dialog_multi (GtkWindow *parent,
const char * title,
GList * filters,
const char * starting_dir,
GNCFileDialogType type);
gboolean gnc_file_open_file (GtkWindow *parent,
const char *filename,
gboolean open_readonly);

View File

@ -1800,7 +1800,6 @@ recnWindowWithBalance (GtkWidget *parent, Account *account, gnc_numeric new_endi
gnc_register_gui_component (WINDOW_RECONCILE_CM_CLASS,
refresh_handler, close_handler,
recnData);
// This window should close if we close the session.
gnc_gui_component_set_session (recnData->component_id, gnc_get_current_session());
recn_set_watches (recnData);
@ -2118,6 +2117,15 @@ gnc_ui_reconcile_window_raise(RecnWindow * recnData)
gtk_window_present(GTK_WINDOW(recnData->window));
}
GtkWidget*
gnc_ui_reconcile_window_get_widget(RecnWindow * recnData)
{
if (recnData == NULL || recnData->window == NULL)
return NULL;
return recnData->window;
}
/********************************************************************\
* recn_destroy_cb *

View File

@ -63,5 +63,5 @@ RecnWindow *recnWindowWithBalance (GtkWidget *parent, Account *account,
time64 statement_date);
void gnc_ui_reconcile_window_raise(RecnWindow * recnData);
GtkWidget* gnc_ui_reconcile_window_get_widget(RecnWindow * recnData);
#endif /* WINDOW_RECONCILE_H */

View File

@ -945,6 +945,21 @@
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="reconcile_after_close_button">
<property name="label" translatable="yes">Reconcile after match</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>

View File

@ -69,6 +69,7 @@ struct _main_matcher_info
GtkTreeViewColumn *account_column;
GtkWidget *show_account_column;
GtkWidget *show_matched_info;
GtkWidget *reconcile_after_close;
gboolean add_toggled; // flag to indicate that add has been toggled to stop selection
gint id;
};
@ -196,6 +197,8 @@ gboolean gnc_gen_trans_list_empty(GNCImportMainMatcher *info)
void gnc_gen_trans_list_show_all(GNCImportMainMatcher *info)
{
gtk_widget_show_all (GTK_WIDGET (info->main_widget));
// By default, do not show this check box.
gnc_gen_trans_list_show_reconcile_after_close (info, FALSE, FALSE);
}
void
@ -1005,6 +1008,9 @@ GNCImportMainMatcher *gnc_gen_trans_list_new (GtkWidget *parent,
g_signal_connect (G_OBJECT(info->show_matched_info), "toggled",
G_CALLBACK(show_matched_info_toggled_cb), info);
// Create the checkbox, but do not show it by default.
info->reconcile_after_close = GTK_WIDGET(gtk_builder_get_object (builder, "reconcile_after_close_button"));
show_update = gnc_import_Settings_get_action_update_enabled (info->user_settings);
gnc_gen_trans_init_view (info, all_from_same_account, show_update);
heading_label = GTK_WIDGET(gtk_builder_get_object (builder, "heading_label"));
@ -1089,6 +1095,8 @@ GNCImportMainMatcher * gnc_gen_trans_assist_new (GtkWidget *parent,
g_signal_connect (G_OBJECT(info->show_matched_info), "toggled",
G_CALLBACK(show_matched_info_toggled_cb), info);
info->reconcile_after_close = GTK_WIDGET(gtk_builder_get_object (builder, "reconcile_when_close_button"));
show_update = gnc_import_Settings_get_action_update_enabled (info->user_settings);
gnc_gen_trans_init_view (info, all_from_same_account, show_update);
heading_label = GTK_WIDGET(gtk_builder_get_object (builder, "heading_label"));
@ -1468,6 +1476,18 @@ void gnc_gen_trans_list_add_trans (GNCImportMainMatcher *gui, Transaction *trans
return;
}/* end gnc_import_add_trans() */
void gnc_gen_trans_list_show_reconcile_after_close(GNCImportMainMatcher *info, gboolean reconcile_after_close, gboolean active)
{
gtk_widget_set_visible (info->reconcile_after_close,reconcile_after_close);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (info->reconcile_after_close), active);
}
GtkWidget*
gnc_gen_trans_list_get_reconcile_widget(GNCImportMainMatcher *info)
{
return info->reconcile_after_close;
}
void gnc_gen_trans_list_add_trans_with_ref_id (GNCImportMainMatcher *gui, Transaction *trans, guint32 ref_id)
{
GNCImportTransInfo * transaction_info = NULL;

View File

@ -196,5 +196,8 @@ gboolean gnc_gen_trans_list_empty(GNCImportMainMatcher *info);
*/
void gnc_gen_trans_list_show_all(GNCImportMainMatcher *info);
void gnc_gen_trans_list_show_reconcile_after_close(GNCImportMainMatcher *info, gboolean reconcile_after_close, gboolean active);
GtkWidget* gnc_gen_trans_list_get_reconcile_widget(GNCImportMainMatcher *info);
#endif
/**@}*/

View File

@ -67,16 +67,20 @@ static Account *ofx_parent_account = NULL;
// Structure we use to gather information about statement balance/account etc.
typedef struct _ofx_info
{
gint num_trans_processed;
GSList* statement;
GtkWindow* parent;
GNCImportMainMatcher *gnc_ofx_importer_gui;
Account *last_import_account;
Account *last_investment_account;
Account *last_income_account;
GList *created_commodites ;
gint num_trans_processed; // Number of transactions processed
struct OfxStatementData* statement; // Statement, if any
gboolean run_reconcile; // If TRUE the reconcile window is opened after matching.
GList *created_commodites;
GSList* file_list; // List of OFX files to import
} ofx_info ;
GList *ofx_created_commodites = NULL;
/*
int ofx_proc_status_cb(struct OfxStatusData data)
{
@ -118,13 +122,11 @@ set_associated_income_account(Account* investment_account,
xaccAccountCommitEdit(investment_account);
}
int ofx_proc_statement_cb(struct OfxStatementData data,
void *statement_user_data);
int ofx_proc_security_cb(const struct OfxSecurityData data,
void *security_user_data);
int ofx_proc_statement_cb (struct OfxStatementData data, void * statement_user_data);
int ofx_proc_security_cb (const struct OfxSecurityData data, void * security_user_data);
int ofx_proc_transaction_cb (struct OfxTransactionData data, void *user_data);
int ofx_proc_account_cb(struct OfxAccountData data, void * account_user_data);
static double ofx_get_investment_amount(const struct OfxTransactionData* data);
int ofx_proc_account_cb (struct OfxAccountData data, void * account_user_data);
static double ofx_get_investment_amount (const struct OfxTransactionData* data);
static const gchar *gnc_ofx_ttype_to_string(TransactionType t)
{
@ -917,11 +919,10 @@ int ofx_proc_transaction_cb(struct OfxTransactionData data, void *user_data)
int ofx_proc_statement_cb (struct OfxStatementData data, void * statement_user_data)
{
ofx_info* info = (ofx_info*) statement_user_data;
struct OfxStatementData* statement = g_new (struct OfxStatementData, 1);
*statement = data;
info->statement = g_slist_append (info->statement, statement);
info->statement = g_new (struct OfxStatementData, 1);
*info->statement = data;
return 0;
}//end ofx_proc_statement()
}
int ofx_proc_account_cb(struct OfxAccountData data, void * account_user_data)
@ -1003,22 +1004,23 @@ int ofx_proc_account_cb(struct OfxAccountData data, void * account_user_data)
gnc_utf8_strip_invalid(data.account_name);
gnc_utf8_strip_invalid(data.account_id);
account_description = g_strdup_printf( /* This string is a default account
account_description = g_strdup_printf (/* This string is a default account
name. It MUST NOT contain the
character ':' anywhere in it or
in any translation. */
"%s \"%s\"",
account_type_name,
data.account_name);
/* use the info->parent as import-matcher is not displayed yet */
account = gnc_import_select_account (GTK_WIDGET(info->parent),
"%s \"%s\"",
account_type_name,
data.account_name);
account = gnc_import_select_account (gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
data.account_id, 1,
account_description, default_commodity,
default_type, info->last_import_account, NULL);
default_type, NULL, NULL);
if (account)
{
info->last_import_account = account;
}
g_free(account_description);
}
else
@ -1051,6 +1053,142 @@ double ofx_get_investment_amount(const struct OfxTransactionData* data)
}
}
// Forward declaration, required because several static functions depend on one-another.
static void
gnc_file_ofx_import_process_file (ofx_info* info);
// gnc_ofx_process_next_file processes the next file in the info->file_list.
static void
gnc_ofx_process_next_file (GtkDialog *dialog, gpointer user_data)
{
ofx_info* info = (ofx_info*) user_data;
// Free the statement (if it was allocated)
g_free (info->statement);
info->statement = NULL;
// Done with the previous OFX file, process the next one if any.
info->file_list = g_slist_delete_link(info->file_list, info->file_list);
if (info->file_list)
gnc_file_ofx_import_process_file (info);
else
{
// Final cleanup.
g_free (info);
if (ofx_created_commodites)
{
/* FIXME: Present some result window about the newly created
* commodities */
g_warning ("Created %d new commodities during import", g_list_length(ofx_created_commodites));
g_list_free (ofx_created_commodites);
ofx_created_commodites = NULL;
}
else
{
//g_warning("No new commodities created");
}
}
}
// This callback is called when the user is done matching transactions.
static void
gnc_ofx_match_done (GtkDialog *dialog, gint response_id, gpointer user_data)
{
ofx_info* info = (ofx_info*) user_data;
if (response_id == GTK_RESPONSE_OK && info->run_reconcile && info->statement)
{
// Open a reconcile window.
Account* account = gnc_import_select_account (gnc_gen_trans_list_widget(info->gnc_ofx_importer_gui),
info->statement->account_id,
0, NULL, NULL, ACCT_TYPE_NONE, NULL, NULL);
if (account && info->statement->ledger_balance_valid)
{
gnc_numeric value = double_to_gnc_numeric (info->statement->ledger_balance,
xaccAccountGetCommoditySCU (account),
GNC_HOW_RND_ROUND_HALF_UP);
RecnWindow* rec_window = recnWindowWithBalance (GTK_WIDGET (info->parent), account, value, info->statement->ledger_balance_date);
// Connect to destroy, at which point we'll process the next OFX file..
g_signal_connect (G_OBJECT (gnc_ui_reconcile_window_get_widget (rec_window)), "destroy", G_CALLBACK (gnc_ofx_process_next_file), info);
}
}
else
{
gtk_widget_hide (GTK_WIDGET(dialog));
gnc_ofx_process_next_file (dialog, info);
}
}
// This callback is triggered when the user checks or unchecks the reconcile after match
// check box in the matching dialog.
static void
reconcile_when_close_toggled_cb (GtkToggleButton *togglebutton, ofx_info* info)
{
info->run_reconcile = gtk_toggle_button_get_active (togglebutton);
}
// Aux function to process the OFX file in info->file_list
static void
gnc_file_ofx_import_process_file (ofx_info* info)
{
LibofxContextPtr libofx_context = libofx_get_new_context();
char* filename = NULL;
char * selected_filename = NULL;
GtkWindow *parent = info->parent;
if (info->file_list == NULL)
return;
filename = info->file_list->data;
#ifdef G_OS_WIN32
selected_filename = g_win32_locale_filename_from_utf8 (filename);
g_free (filename);
#else
selected_filename = filename;
#endif
DEBUG("Filename found: %s", selected_filename);
// Reset the reconciliation information.
info->num_trans_processed = 0;
info->statement = NULL;
/* Initialize libofx and set the callbacks*/
ofx_set_statement_cb (libofx_context, ofx_proc_statement_cb, info);
ofx_set_account_cb (libofx_context, ofx_proc_account_cb, info);
ofx_set_transaction_cb (libofx_context, ofx_proc_transaction_cb, info);
ofx_set_security_cb (libofx_context, ofx_proc_security_cb, info);
/*ofx_set_status_cb(libofx_context, ofx_proc_status_cb, 0);*/
// Create the match dialog, and run the ofx file through the importer.
info->gnc_ofx_importer_gui = gnc_gen_trans_list_new (GTK_WIDGET(parent), NULL, TRUE, 42, FALSE);
libofx_proc_file (libofx_context, selected_filename, AUTODETECT);
// See whether the view has anything in it and warn the user if not.
if(gnc_gen_trans_list_empty (info->gnc_ofx_importer_gui))
{
gnc_gen_trans_list_delete (info->gnc_ofx_importer_gui);
if(info->num_trans_processed)
gnc_info_dialog (parent, _("OFX file(s) imported, %d transactions processed, no transactions to match"), info->num_trans_processed);
// Process the next OFX file if any.
gnc_ofx_process_next_file (NULL, info);
}
else
{
// Show the match dialog and connect to the "response" signal so we can trigger a reconcile if the user clicks OK when done matching transactions.
g_signal_connect (G_OBJECT (gnc_gen_trans_list_widget (info->gnc_ofx_importer_gui)), "response", G_CALLBACK (gnc_ofx_match_done), info);
gnc_gen_trans_list_show_all (info->gnc_ofx_importer_gui);
// Show or hide the check box for reconciling after match, depending on whether a statement was received.
gnc_gen_trans_list_show_reconcile_after_close (info->gnc_ofx_importer_gui, info->statement != NULL, info->run_reconcile);
// Finally connect to the reconcile after match check box so we can be notified if the user wants/does not want to reconcile.
g_signal_connect (G_OBJECT (gnc_gen_trans_list_get_reconcile_widget (info->gnc_ofx_importer_gui)), "toggled",
G_CALLBACK (reconcile_when_close_toggled_cb), info);
}
g_free(selected_filename);
}
// The main import function. Starts the chain of file imports (if there are several)
void gnc_file_ofx_import (GtkWindow *parent)
{
extern int ofx_PARSER_msg;
@ -1059,14 +1197,14 @@ void gnc_file_ofx_import (GtkWindow *parent)
extern int ofx_ERROR_msg;
extern int ofx_INFO_msg;
extern int ofx_STATUS_msg;
char *selected_filename;
GSList* selected_filenames = NULL;
char *default_dir;
LibofxContextPtr libofx_context = libofx_get_new_context();
GList *filters = NULL;
GSList* iter = NULL;
ofx_info* info = NULL;
GtkFileFilter* filter = gtk_file_filter_new ();
GSList *iter = NULL;
// Create the structure we're using to gather reconciliation information.
ofx_info info = {0, NULL, parent, NULL, NULL, NULL, NULL, NULL};
ofx_PARSER_msg = false;
ofx_DEBUG_msg = false;
@ -1082,97 +1220,34 @@ void gnc_file_ofx_import (GtkWindow *parent)
gtk_file_filter_add_pattern (filter, "*.[oqOQ][fF][xX]");
filters = g_list_prepend( filters, filter );
selected_filename = gnc_file_dialog(parent,
_("Select an OFX/QFX file to process"),
filters,
default_dir,
GNC_FILE_DIALOG_IMPORT);
selected_filenames = gnc_file_dialog_multi (parent,
_("Select one or multiple OFX/QFX file(s) to process"),
filters,
default_dir,
GNC_FILE_DIALOG_IMPORT);
g_free(default_dir);
if (selected_filename != NULL)
if (selected_filenames)
{
#ifdef G_OS_WIN32
gchar *conv_name;
#endif
/* Remember the directory as the default. */
default_dir = g_path_get_dirname(selected_filename);
default_dir = g_path_get_dirname(selected_filenames->data);
gnc_set_default_directory(GNC_PREFS_GROUP, default_dir);
g_free(default_dir);
/*strncpy(file,selected_filename, 255);*/
DEBUG("Filename found: %s", selected_filename);
/* Create the Generic transaction importer GUI. */
info.gnc_ofx_importer_gui = gnc_gen_trans_list_new (GTK_WIDGET(parent), NULL, FALSE, 42, FALSE);
/* Look up the needed preferences */
auto_create_commodity =
gnc_prefs_get_bool (GNC_PREFS_GROUP_IMPORT, GNC_PREF_AUTO_COMMODITY);
/* Initialize libofx and set the callbacks*/
ofx_set_statement_cb (libofx_context, ofx_proc_statement_cb, &info);
ofx_set_account_cb (libofx_context, ofx_proc_account_cb, &info);
ofx_set_transaction_cb (libofx_context, ofx_proc_transaction_cb, &info);
ofx_set_security_cb (libofx_context, ofx_proc_security_cb, &info);
/*ofx_set_status_cb(libofx_context, ofx_proc_status_cb, 0);*/
#ifdef G_OS_WIN32
conv_name = g_win32_locale_filename_from_utf8(selected_filename);
g_free(selected_filename);
selected_filename = conv_name;
#endif
DEBUG("Opening selected file");
libofx_proc_file(libofx_context, selected_filename, AUTODETECT);
// See whether the view has anything in it and warn the user if not.
if(gnc_gen_trans_list_empty(info.gnc_ofx_importer_gui))
{
gnc_gen_trans_list_delete (info.gnc_ofx_importer_gui);
if(info.num_trans_processed)
gnc_info_dialog (parent, _("OFX file imported, %d transactions processed, no transactions to match"), info.num_trans_processed);
}
else
{
gnc_gen_trans_list_show_all(info.gnc_ofx_importer_gui);
}
// Open a reconcile window for each balance statement found.
for (iter=info.statement; iter; iter=iter->next)
{
struct OfxStatementData* statement = (struct OfxStatementData*) iter->data;
Account* account = gnc_import_select_account (gnc_gen_trans_list_widget(info.gnc_ofx_importer_gui),
statement->account_id,
0, NULL, NULL, ACCT_TYPE_NONE,
NULL, NULL);
if (account)
{
if (statement->ledger_balance_valid)
{
gnc_numeric value =
double_to_gnc_numeric (statement->ledger_balance,
xaccAccountGetCommoditySCU (account),
GNC_HOW_RND_ROUND_HALF_UP);
recnWindowWithBalance (GTK_WIDGET (parent),
account,
value,
statement->ledger_balance_date);
}
}
}
g_free (selected_filename);
g_slist_free_full (info.statement,g_free);
}
if (info.created_commodites)
{
/* FIXME: Present some result window about the newly created
* commodities */
g_warning("Created %d new commodities during import", g_list_length(info.created_commodites));
g_list_free(info.created_commodites);
}
else
{
//g_warning("No new commodities created");
DEBUG("Opening selected file(s)");
// Create the structure that holds the list of files to process and the statement info.
info = g_new(ofx_info,1);
info->num_trans_processed = 0;
info->statement = NULL;
info->parent = parent;
info->run_reconcile = FALSE;
info->file_list = selected_filenames;
// Call the aux import function.
gnc_file_ofx_import_process_file (info);
}
}