import-main-matcher.c (gnucash-5.0.tar.bz2) | : | import-main-matcher.c (gnucash-5.1.tar.bz2) | ||
---|---|---|---|---|
skipping to change at line 516 | skipping to change at line 516 | |||
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (info->append_text), | gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (info->append_text), | |||
xaccAccountGetAppendText(account)); | xaccAccountGetAppendText(account)); | |||
gnc_gen_trans_list_create_matches (info); | gnc_gen_trans_list_create_matches (info); | |||
load_hash_tables (info); | load_hash_tables (info); | |||
resolve_conflicts (info); | resolve_conflicts (info); | |||
gtk_widget_show_all (GTK_WIDGET(info->main_widget)); | gtk_widget_show_all (GTK_WIDGET(info->main_widget)); | |||
gnc_gen_trans_list_show_accounts_column (info); | gnc_gen_trans_list_show_accounts_column (info); | |||
} | } | |||
static void acc_begin_edit (GList **accounts_modified, Account *acc) | ||||
{ | ||||
if (!acc || !accounts_modified || g_list_find (*accounts_modified, acc)) | ||||
return; | ||||
DEBUG ("xaccAccountBeginEdit for acc %s", xaccAccountGetName (acc)); | ||||
xaccAccountBeginEdit (acc); | ||||
*accounts_modified = g_list_prepend (*accounts_modified, acc); | ||||
} | ||||
void | void | |||
on_matcher_ok_clicked (GtkButton *button, GNCImportMainMatcher *info) | on_matcher_ok_clicked (GtkButton *button, GNCImportMainMatcher *info) | |||
{ | { | |||
g_assert (info); | g_assert (info); | |||
/* DEBUG ("Begin") */ | /* DEBUG ("Begin") */ | |||
GtkTreeModel *model = gtk_tree_view_get_model (info->view); | GtkTreeModel *model = gtk_tree_view_get_model (info->view); | |||
GtkTreeIter iter; | GtkTreeIter iter; | |||
if (!gtk_tree_model_get_iter_first (model, &iter)) | if (!gtk_tree_model_get_iter_first (model, &iter)) | |||
skipping to change at line 537 | skipping to change at line 546 | |||
// No transaction, we can just close the dialog. | // No transaction, we can just close the dialog. | |||
gnc_gen_trans_list_delete (info); | gnc_gen_trans_list_delete (info); | |||
return; | return; | |||
} | } | |||
/* Don't run any queries and/or split sorts while processing the matcher | /* Don't run any queries and/or split sorts while processing the matcher | |||
results. */ | results. */ | |||
gnc_suspend_gui_refresh (); | gnc_suspend_gui_refresh (); | |||
bool first_tran = true; | bool first_tran = true; | |||
bool append_text = gtk_toggle_button_get_active ((GtkToggleButton*) info->ap pend_text); | bool append_text = gtk_toggle_button_get_active ((GtkToggleButton*) info->ap pend_text); | |||
GList *accounts_modified = NULL; | ||||
do | do | |||
{ | { | |||
GNCImportTransInfo *trans_info; | GNCImportTransInfo *trans_info; | |||
gtk_tree_model_get (model, &iter, | gtk_tree_model_get (model, &iter, | |||
DOWNLOADED_COL_DATA, &trans_info, | DOWNLOADED_COL_DATA, &trans_info, | |||
-1); | -1); | |||
Split* first_split = gnc_import_TransInfo_get_fsplit (trans_info); | ||||
Transaction *trans = xaccSplitGetParent (first_split); | ||||
for (GList *n = xaccTransGetSplitList (trans); n; n = g_list_next (n)) | ||||
acc_begin_edit (&accounts_modified, xaccSplitGetAccount (n->data)); | ||||
// Allow the backend to know if the Append checkbox is ticked or unticke d | // Allow the backend to know if the Append checkbox is ticked or unticke d | |||
// by propagating info->append_text to every trans_info->append_text | // by propagating info->append_text to every trans_info->append_text | |||
gnc_import_TransInfo_set_append_text( trans_info, append_text); | gnc_import_TransInfo_set_append_text( trans_info, append_text); | |||
// When processing the first transaction, | // When processing the first transaction, | |||
// save the state of the Append checkbox to an account kvp so the same s tate can be | // save the state of the Append checkbox to an account kvp so the same s tate can be | |||
// defaulted next time this account is imported. | // defaulted next time this account is imported. | |||
// Get the import account from the first split. | // Get the import account from the first split. | |||
if (first_tran) | if (first_tran) | |||
{ | { | |||
Split* first_split = gnc_import_TransInfo_get_fsplit (trans_info); | ||||
xaccAccountSetAppendText (xaccSplitGetAccount(first_split), append_t ext); | xaccAccountSetAppendText (xaccSplitGetAccount(first_split), append_t ext); | |||
first_tran = false; | first_tran = false; | |||
} | } | |||
// Note: if there's only 1 split (unbalanced) one will be created with t | ||||
he unbalanced account, | Account *dest_acc = gnc_import_TransInfo_get_destacc (trans_info); | |||
// and for that account the defer balance will not be set. So things wil | acc_begin_edit (&accounts_modified, dest_acc); | |||
l be slow. | ||||
if (gnc_import_process_trans_item (NULL, trans_info)) | if (gnc_import_process_trans_item (NULL, trans_info)) | |||
{ | { | |||
if (info->transaction_processed_cb) | if (info->transaction_processed_cb) | |||
{ | { | |||
info->transaction_processed_cb (trans_info, true, | info->transaction_processed_cb (trans_info, true, | |||
info->user_data); | info->user_data); | |||
} | } | |||
} | } | |||
} | } | |||
while (gtk_tree_model_iter_next (model, &iter)); | while (gtk_tree_model_iter_next (model, &iter)); | |||
gnc_gen_trans_list_delete (info); | gnc_gen_trans_list_delete (info); | |||
/* Allow GUI refresh again. */ | /* Allow GUI refresh again. */ | |||
gnc_resume_gui_refresh (); | gnc_resume_gui_refresh (); | |||
/* DEBUG ("End") */ | /* DEBUG ("End") */ | |||
g_list_free_full (accounts_modified, (GDestroyNotify)xaccAccountCommitEdit); | ||||
} | } | |||
void | void | |||
on_matcher_cancel_clicked (GtkButton *button, gpointer user_data) | on_matcher_cancel_clicked (GtkButton *button, gpointer user_data) | |||
{ | { | |||
GNCImportMainMatcher *info = user_data; | GNCImportMainMatcher *info = user_data; | |||
gnc_gen_trans_list_delete (info); | gnc_gen_trans_list_delete (info); | |||
} | } | |||
bool | bool | |||
skipping to change at line 927 | skipping to change at line 944 | |||
bool ret = false; | bool ret = false; | |||
gtk_tree_model_get (model, iter, | gtk_tree_model_get (model, iter, | |||
COMPLETION_LIST_NORMALIZED_FOLDED, &existing_str, | COMPLETION_LIST_NORMALIZED_FOLDED, &existing_str, | |||
-1); | -1); | |||
if (existing_str && *existing_str && strstr (existing_str, entry_str)) | if (existing_str && *existing_str && strstr (existing_str, entry_str)) | |||
ret = true; | ret = true; | |||
g_free (existing_str); | g_free (existing_str); | |||
return ret; | return ret; | |||
} | } | |||
static void | typedef struct | |||
setup_entry (GtkWidget *entry, bool sensitive, GHashTable *hash, | ||||
const char *initial) | ||||
{ | { | |||
GtkWidget *entry; | ||||
GObject *override; | ||||
bool *can_edit; | ||||
GHashTable *hash; | ||||
const char *initial; | ||||
} EntryInfo; | ||||
static void override_clicked (GtkWidget *widget, EntryInfo *entryinfo) | ||||
{ | ||||
gtk_widget_set_visible (GTK_WIDGET (entryinfo->override), false); | ||||
gtk_widget_set_sensitive (entryinfo->entry, true); | ||||
gtk_entry_set_text (GTK_ENTRY (entryinfo->entry), ""); | ||||
gtk_widget_grab_focus (entryinfo->entry); | ||||
*entryinfo->can_edit = true; | ||||
} | ||||
static void | ||||
setup_entry (EntryInfo *entryinfo) | ||||
{ | ||||
bool sensitive = *entryinfo->can_edit; | ||||
GtkWidget *entry = entryinfo->entry; | ||||
GtkWidget *override = GTK_WIDGET (entryinfo->override); | ||||
GHashTable *hash = entryinfo->hash; | ||||
const char *initial = entryinfo->initial; | ||||
gtk_widget_set_sensitive (entry, sensitive); | gtk_widget_set_sensitive (entry, sensitive); | |||
gtk_widget_set_visible (override, !sensitive); | ||||
if (sensitive && initial && *initial) | if (sensitive && initial && *initial) | |||
gtk_entry_set_text (GTK_ENTRY (entry), initial); | gtk_entry_set_text (GTK_ENTRY (entry), initial); | |||
else if (!sensitive) | else if (!sensitive) | |||
{ | { | |||
gtk_entry_set_text (GTK_ENTRY (entry), _("Disabled")); | gtk_entry_set_text (GTK_ENTRY (entry), _("Click Edit to modify")); | |||
return; | g_signal_connect (override, "clicked", G_CALLBACK (override_clicked), | |||
entryinfo); | ||||
} | } | |||
GtkListStore *list = gtk_list_store_new (NUM_COMPLETION_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); | GtkListStore *list = gtk_list_store_new (NUM_COMPLETION_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); | |||
g_hash_table_foreach (hash, (GHFunc)populate_list, list); | g_hash_table_foreach (hash, (GHFunc)populate_list, list); | |||
if (initial && *initial && !g_hash_table_lookup (hash, (gpointer)initial)) | if (initial && *initial && !g_hash_table_lookup (hash, (gpointer)initial)) | |||
populate_list ((gpointer)initial, NULL, list); | populate_list ((gpointer)initial, NULL, list); | |||
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list), | gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list), | |||
COMPLETION_LIST_ORIGINAL, | COMPLETION_LIST_ORIGINAL, | |||
GTK_SORT_ASCENDING); | GTK_SORT_ASCENDING); | |||
skipping to change at line 976 | skipping to change at line 1019 | |||
// Set the name for this dialog so it can be easily manipulated with css | // Set the name for this dialog so it can be easily manipulated with css | |||
gtk_widget_set_name (GTK_WIDGET(dialog), "gnc-id-import-matcher-edits"); | gtk_widget_set_name (GTK_WIDGET(dialog), "gnc-id-import-matcher-edits"); | |||
gnc_widget_style_context_add_class (GTK_WIDGET(dialog), "gnc-class-imports") ; | gnc_widget_style_context_add_class (GTK_WIDGET(dialog), "gnc-class-imports") ; | |||
GtkWidget *desc_entry = GTK_WIDGET(gtk_builder_get_object (builder, "desc_en try")); | GtkWidget *desc_entry = GTK_WIDGET(gtk_builder_get_object (builder, "desc_en try")); | |||
GtkWidget *memo_entry = GTK_WIDGET(gtk_builder_get_object (builder, "memo_en try")); | GtkWidget *memo_entry = GTK_WIDGET(gtk_builder_get_object (builder, "memo_en try")); | |||
GtkWidget *notes_entry = GTK_WIDGET(gtk_builder_get_object (builder, "notes_ entry")); | GtkWidget *notes_entry = GTK_WIDGET(gtk_builder_get_object (builder, "notes_ entry")); | |||
Transaction *trans = gnc_import_TransInfo_get_trans (rowinfo->trans_info); | Transaction *trans = gnc_import_TransInfo_get_trans (rowinfo->trans_info); | |||
Split *split = gnc_import_TransInfo_get_fsplit (rowinfo->trans_info); | Split *split = gnc_import_TransInfo_get_fsplit (rowinfo->trans_info); | |||
setup_entry (desc_entry, info->can_edit_desc, info->desc_hash, xaccTransGetD | ||||
escription (trans)); | EntryInfo entries[] = { | |||
setup_entry (notes_entry, info->can_edit_notes, info->notes_hash, xaccTransG | { desc_entry, gtk_builder_get_object (builder, "desc_override"), &info-> | |||
etNotes (trans)); | can_edit_desc, info->desc_hash, xaccTransGetDescription (trans) }, | |||
setup_entry (memo_entry, info->can_edit_memo, info->memo_hash, xaccSplitGetM | { notes_entry, gtk_builder_get_object (builder, "notes_override"), &info | |||
emo (split)); | ->can_edit_notes, info->notes_hash, xaccTransGetNotes (trans) }, | |||
{ memo_entry, gtk_builder_get_object (builder, "memo_override"), &info-> | ||||
can_edit_memo, info->memo_hash, xaccSplitGetMemo (split) }, | ||||
{ NULL } }; | ||||
for (guint i = 0; entries[i].entry; i++) | ||||
setup_entry (&entries[i]); | ||||
/* ensure that an override button doesn't have focus. find the | ||||
first available entry and give it focus. */ | ||||
for (guint i = 0; entries[i].entry; i++) | ||||
if (*entries[i].can_edit) | ||||
{ | ||||
gtk_widget_grab_focus (entries[i].entry); | ||||
break; | ||||
} | ||||
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (info->main_wi dget)); | gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (info->main_wi dget)); | |||
// run the dialog | // run the dialog | |||
gtk_widget_show_all (dialog); | gtk_widget_show (dialog); | |||
bool retval = false; | bool retval = false; | |||
switch (gtk_dialog_run (GTK_DIALOG(dialog))) | switch (gtk_dialog_run (GTK_DIALOG(dialog))) | |||
{ | { | |||
case GTK_RESPONSE_OK: | case GTK_RESPONSE_OK: | |||
*new_desc = g_strdup (gtk_entry_get_text (GTK_ENTRY (desc_entry))); | *new_desc = g_strdup (gtk_entry_get_text (GTK_ENTRY (desc_entry))); | |||
*new_notes = g_strdup (gtk_entry_get_text (GTK_ENTRY (notes_entry))); | *new_notes = g_strdup (gtk_entry_get_text (GTK_ENTRY (notes_entry))); | |||
*new_memo = g_strdup (gtk_entry_get_text (GTK_ENTRY (memo_entry))); | *new_memo = g_strdup (gtk_entry_get_text (GTK_ENTRY (memo_entry))); | |||
retval = true; | retval = true; | |||
break; | break; | |||
skipping to change at line 2203 | skipping to change at line 2261 | |||
gnc_gen_trans_list_add_trans_internal (gui, trans, ref_id, NULL); | gnc_gen_trans_list_add_trans_internal (gui, trans, ref_id, NULL); | |||
} | } | |||
void gnc_gen_trans_list_add_trans_with_split_data (GNCImportMainMatcher *gui, | void gnc_gen_trans_list_add_trans_with_split_data (GNCImportMainMatcher *gui, | |||
Transaction *trans, | Transaction *trans, | |||
GNCImportLastSplitInfo *lspli t) | GNCImportLastSplitInfo *lspli t) | |||
{ | { | |||
gnc_gen_trans_list_add_trans_internal (gui, trans, 0, lsplit); | gnc_gen_trans_list_add_trans_internal (gui, trans, 0, lsplit); | |||
} | } | |||
/* Query the accounts used by the imported transactions to find a list of | /* Return a list of splits from already existing transactions for | |||
* candidate matching transactions. | * which the account matches an account used by the transactions to | |||
* import. The matching range is also date-limited (configurable | ||||
* via preferences) to not go too far in the past or future. | ||||
*/ | */ | |||
static GList* | static GList* | |||
query_imported_transaction_accounts (GNCImportMainMatcher *gui) | filter_existing_splits_on_account_and_date (GNCImportMainMatcher *gui) | |||
{ | { | |||
static const int secs_per_day = 86400; | static const int secs_per_day = 86400; | |||
gint match_date_limit = | gint match_date_limit = | |||
gnc_import_Settings_get_match_date_hardlimit (gui->user_settings); | gnc_import_Settings_get_match_date_hardlimit (gui->user_settings); | |||
time64 min_time=G_MAXINT64, max_time=0; | time64 min_time=G_MAXINT64, max_time=0; | |||
time64 match_timelimit = match_date_limit * secs_per_day; | time64 match_timelimit = match_date_limit * secs_per_day; | |||
GList *all_accounts = NULL; | GList *all_accounts = NULL; | |||
/* Go through all imported transactions, gather the list of accounts, and | /* Go through all imported transactions, gather the list of accounts, and | |||
* min/max date range. | * min/max date range. | |||
skipping to change at line 2253 | skipping to change at line 2313 | |||
GList *retval = g_list_copy (query_results); | GList *retval = g_list_copy (query_results); | |||
qof_query_destroy (query); | qof_query_destroy (query); | |||
return retval; | return retval; | |||
} | } | |||
/* Create a hash by account of all splits that could match one of the imported | /* Create a hash by account of all splits that could match one of the imported | |||
* transactions based on their account and date and organized per account. | * transactions based on their account and date and organized per account. | |||
*/ | */ | |||
static GHashTable* | static GHashTable* | |||
create_hash_of_potential_matches (GList *candidate_txns, | create_hash_of_potential_matches (GList *candidate_splits, | |||
GHashTable *account_hash) | GHashTable *account_hash) | |||
{ | { | |||
for (GList* candidate = candidate_txns; candidate != NULL; | for (GList* candidate = candidate_splits; candidate != NULL; | |||
candidate = g_list_next (candidate)) | candidate = g_list_next (candidate)) | |||
{ | { | |||
if (gnc_import_split_has_online_id (candidate->data)) | if (gnc_import_split_has_online_id (candidate->data)) | |||
continue; | continue; | |||
/* In this context an open transaction represents a freshly | /* In this context an open transaction represents a freshly | |||
* downloaded one. That can't possibly be a match yet */ | * downloaded one. That can't possibly be a match yet */ | |||
if (xaccTransIsOpen(xaccSplitGetParent(candidate->data))) | if (xaccTransIsOpen(xaccSplitGetParent(candidate->data))) | |||
continue; | continue; | |||
Account *split_account = xaccSplitGetAccount (candidate->data); | Account *split_account = xaccSplitGetAccount (candidate->data); | |||
/* g_hash_table_steal_extended would do the two calls in one shot but is | /* g_hash_table_steal_extended would do the two calls in one shot but is | |||
skipping to change at line 2349 | skipping to change at line 2409 | |||
} | } | |||
} | } | |||
void | void | |||
gnc_gen_trans_list_create_matches (GNCImportMainMatcher *gui) | gnc_gen_trans_list_create_matches (GNCImportMainMatcher *gui) | |||
{ | { | |||
GHashTable* account_hash = | GHashTable* account_hash = | |||
g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, | g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, | |||
(GDestroyNotify)g_slist_free); | (GDestroyNotify)g_slist_free); | |||
g_assert (gui); | g_assert (gui); | |||
GList *candidate_txns = query_imported_transaction_accounts (gui); | GList *candidate_splits = filter_existing_splits_on_account_and_date (gui); | |||
create_hash_of_potential_matches (candidate_txns, account_hash); | create_hash_of_potential_matches (candidate_splits, account_hash); | |||
perform_matching (gui, account_hash); | perform_matching (gui, account_hash); | |||
g_list_free (candidate_txns); | g_list_free (candidate_splits); | |||
g_hash_table_destroy (account_hash); | g_hash_table_destroy (account_hash); | |||
return; | return; | |||
} | } | |||
GtkWidget * | GtkWidget * | |||
gnc_gen_trans_list_widget (GNCImportMainMatcher *info) | gnc_gen_trans_list_widget (GNCImportMainMatcher *info) | |||
{ | { | |||
g_assert (info); | g_assert (info); | |||
return info->main_widget; | return info->main_widget; | |||
} | } | |||
End of changes. 19 change blocks. | ||||
25 lines changed or deleted | 83 lines changed or added |