From fac0cde7a24c1a971993669b3d7c175cbad3aa35 Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Sat, 9 Jul 2022 20:24:36 +0800 Subject: [PATCH 01/13] [import-main-matcher] further refinement to edit multiple rows but is only enabled iff the desc/notes/memo to be renamed are identical. --- gnucash/import-export/import-main-matcher.c | 117 +++++++++++++++----- 1 file changed, 88 insertions(+), 29 deletions(-) diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c index 54c9b9016c..d700293745 100644 --- a/gnucash/import-export/import-main-matcher.c +++ b/gnucash/import-export/import-main-matcher.c @@ -894,6 +894,30 @@ typedef enum NOTES, } edit_field; +typedef struct +{ + Split *split; + Transaction *trans; + GtkTreeIter iter; +} RowInfo; + +static void rowinfo_free (RowInfo* info) +{ + g_free (info); +} + +static RowInfo * row_get_info (gpointer row, GNCImportMainMatcher *info) +{ + GNCImportTransInfo *trans_info; + GtkTreeModel *model = gtk_tree_view_get_model (info->view); + RowInfo *retval = g_new (RowInfo, 1); + gtk_tree_model_get_iter (model, &retval->iter, row); + gtk_tree_model_get (model, &retval->iter, DOWNLOADED_COL_DATA, &trans_info, -1); + retval->trans = gnc_import_TransInfo_get_trans (trans_info); + retval->split = gnc_import_TransInfo_get_fsplit (trans_info); + return retval; +} + static void gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info, edit_field field) @@ -901,12 +925,9 @@ gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info, GtkTreeView *treeview; GtkTreeSelection *selection; GtkTreeModel *model; - GList *selected_rows; - GList *refs = NULL; + GList *selected_rows, *row_info_list; GtkTreeStore* store; - GNCImportTransInfo *trans_info; Transaction* trans; - GtkTreeIter iter; g_return_if_fail (info != NULL); ENTER("assign_transfer_account_to_selection_cb"); @@ -923,18 +944,8 @@ gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info, return; } - if (selected_rows->next) - { - LEAVE ("User selected multiple rows, not supported"); - return; - } - - g_return_if_fail (gtk_tree_model_get_iter (model, &iter, - selected_rows->data)); - - gtk_tree_model_get (model, &iter, DOWNLOADED_COL_DATA, - &trans_info, -1); - trans = gnc_import_TransInfo_get_trans (trans_info); + row_info_list = gnc_g_list_map (selected_rows, (GncGMapFunc) row_get_info, info); + trans = ((RowInfo*)row_info_list->data)->trans; switch (field) { @@ -945,24 +956,33 @@ gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info, _("Enter new Description"), xaccTransGetDescription (trans)); if (!new_field) break; - xaccTransSetDescription (trans, new_field); - gtk_tree_store_set (store, &iter, DOWNLOADED_COL_DESCRIPTION, - new_field, -1); + for (GList *n = row_info_list; n; n = g_list_next (n)) + { + RowInfo *info = n->data; + xaccTransSetDescription (info->trans, new_field); + gtk_tree_store_set (store, &info->iter, + DOWNLOADED_COL_DESCRIPTION, new_field, + -1); + } g_free (new_field); break; } case MEMO: { - Split *first_split = - gnc_import_TransInfo_get_fsplit (trans_info); + Split *first_split = xaccTransGetSplit (trans, 0); char *new_field = gnc_input_dialog_with_entry(info->main_widget, "", _("Enter new Memo"), xaccSplitGetMemo (first_split)); if (!new_field) break; - xaccSplitSetMemo (first_split, new_field); - gtk_tree_store_set (store, &iter, - DOWNLOADED_COL_MEMO, new_field, -1); + for (GList *n = row_info_list; n; n = g_list_next (n)) + { + RowInfo *info = n->data; + xaccSplitSetMemo (info->split, new_field); + gtk_tree_store_set (store, &info->iter, + DOWNLOADED_COL_MEMO, new_field, + -1); + } g_free (new_field); break; } @@ -973,11 +993,16 @@ gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info, _("Enter new Notes"), xaccTransGetNotes (trans)); if (!new_field) break; - xaccTransSetNotes (trans, new_field); + for (GList *n = row_info_list; n; n = g_list_next (n)) + { + RowInfo *info = n->data; + xaccTransSetNotes (info->trans, new_field); + } g_free (new_field); break; } } + g_list_free_full (row_info_list, (GDestroyNotify)rowinfo_free); g_list_free_full (selected_rows, (GDestroyNotify)gtk_tree_path_free); LEAVE(""); } @@ -1092,6 +1117,11 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, { GtkWidget *menu, *menuitem; GdkEventButton *event_button; + GtkTreeModel *model; + GtkTreeSelection *selection; + GList *selected_rows; + const char *desc, *memo, *notes; + gboolean edit_desc = TRUE, edit_notes = TRUE, edit_memo = TRUE; ENTER (""); menu = gtk_menu_new(); @@ -1104,7 +1134,31 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, DEBUG("Callback to assign destination account to selection connected"); gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); - if (show_edit_actions) + model = gtk_tree_view_get_model (treeview); + selection = gtk_tree_view_get_selection (treeview); + selected_rows = gtk_tree_selection_get_selected_rows (selection, &model); + for (GList *n = selected_rows; (edit_desc || edit_notes || edit_memo) && n; + n = g_list_next(n)) + { + RowInfo *rowinfo = row_get_info (n->data, info); + if (!n->prev) /* only the first row */ + { + desc = xaccTransGetDescription (rowinfo->trans); + notes = xaccTransGetNotes (rowinfo->trans); + memo = xaccSplitGetMemo (rowinfo->split); + rowinfo_free (rowinfo); + continue; + } + if (edit_desc && g_strcmp0 (desc, xaccTransGetDescription (rowinfo->trans))) + edit_desc = FALSE; + if (edit_notes && g_strcmp0 (notes, xaccTransGetNotes (rowinfo->trans))) + edit_notes = FALSE; + if (edit_memo && g_strcmp0 (memo, xaccSplitGetMemo (rowinfo->split))) + edit_memo = FALSE; + rowinfo_free (rowinfo); + } + + if (edit_desc) { menuitem = gtk_menu_item_new_with_label ( _("Edit description.")); @@ -1113,7 +1167,10 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, info); DEBUG("Callback to edit description"); gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); + } + if (edit_memo) + { menuitem = gtk_menu_item_new_with_label ( _("Edit memo.")); g_signal_connect (menuitem, "activate", @@ -1121,7 +1178,10 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, info); DEBUG("Callback to edit memo"); gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); + } + if (edit_notes) + { menuitem = gtk_menu_item_new_with_label ( _("Edit notes.")); g_signal_connect (menuitem, "activate", @@ -1135,6 +1195,7 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, /* Note: event can be NULL here when called from view_onPopupMenu; */ gtk_menu_popup_at_pointer (GTK_MENU(menu), (GdkEvent*)event); + g_list_free_full (selected_rows, (GDestroyNotify)gtk_tree_path_free); LEAVE (""); } @@ -1160,9 +1221,7 @@ gnc_gen_trans_onButtonPressed_cb (GtkTreeView *treeview, // or the selected transaction is an ADD. selection = gtk_tree_view_get_selection (treeview); count = gtk_tree_selection_count_selected_rows (selection); - if (count > 1) - gnc_gen_trans_view_popup_menu (treeview, event, info, FALSE); - else if (count > 0) + if (count > 0) { GList* selected; GtkTreeModel *model; From b10712951c4a6054ea5596323f9d84428a84fe4e Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Sat, 9 Jul 2022 20:30:01 +0800 Subject: [PATCH 02/13] [import-main-matcher] gnc_gen_trans_view_popup_menu: show_edit_actions obsolete --- gnucash/import-export/import-main-matcher.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c index d700293745..9b0395eed5 100644 --- a/gnucash/import-export/import-main-matcher.c +++ b/gnucash/import-export/import-main-matcher.c @@ -130,8 +130,7 @@ static void gnc_gen_trans_assign_transfer_account_to_selection_cb (GtkMenuItem * GNCImportMainMatcher *info); static void gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, GdkEvent *event, - GNCImportMainMatcher *info, - gboolean show_edit_actions); + GNCImportMainMatcher *info); static gboolean gnc_gen_trans_onButtonPressed_cb (GtkTreeView *treeview, GdkEvent *event, GNCImportMainMatcher *info); @@ -1112,8 +1111,7 @@ gnc_gen_trans_row_changed_cb (GtkTreeSelection *selection, static void gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, GdkEvent *event, - GNCImportMainMatcher *info, - gboolean show_edit_actions) + GNCImportMainMatcher *info) { GtkWidget *menu, *menuitem; GdkEventButton *event_button; @@ -1227,7 +1225,7 @@ gnc_gen_trans_onButtonPressed_cb (GtkTreeView *treeview, GtkTreeModel *model; selected = gtk_tree_selection_get_selected_rows (selection, &model); if (get_action_for_path (selected->data, model) == GNCImport_ADD) - gnc_gen_trans_view_popup_menu (treeview, event, info, TRUE); + gnc_gen_trans_view_popup_menu (treeview, event, info); g_list_free_full (selected, (GDestroyNotify)gtk_tree_path_free); } LEAVE("return TRUE"); @@ -1248,7 +1246,7 @@ gnc_gen_trans_onPopupMenu_cb (GtkTreeView *treeview, selection = gtk_tree_view_get_selection (treeview); if (gtk_tree_selection_count_selected_rows (selection) > 0) { - gnc_gen_trans_view_popup_menu (treeview, NULL, info, TRUE); + gnc_gen_trans_view_popup_menu (treeview, NULL, info); LEAVE ("TRUE"); return TRUE; } From 6f74d4e3ed63753f85f2c0aea78db6e468083514 Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Sat, 9 Jul 2022 21:36:46 +0800 Subject: [PATCH 03/13] [import-main-matcher] show Edit menuitem, disable if disallowed --- gnucash/import-export/import-main-matcher.c | 51 ++++++++------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c index 9b0395eed5..cf3ee41a45 100644 --- a/gnucash/import-export/import-main-matcher.c +++ b/gnucash/import-export/import-main-matcher.c @@ -1114,7 +1114,6 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, GNCImportMainMatcher *info) { GtkWidget *menu, *menuitem; - GdkEventButton *event_button; GtkTreeModel *model; GtkTreeSelection *selection; GList *selected_rows; @@ -1156,40 +1155,28 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, rowinfo_free (rowinfo); } - if (edit_desc) - { - menuitem = gtk_menu_item_new_with_label ( - _("Edit description.")); - g_signal_connect (menuitem, "activate", - G_CALLBACK (gnc_gen_trans_edit_description_cb), - info); - DEBUG("Callback to edit description"); - gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); - } + menuitem = gtk_menu_item_new_with_label (_("Edit description.")); + gtk_widget_set_sensitive (menuitem, edit_desc); + g_signal_connect (menuitem, "activate", + G_CALLBACK (gnc_gen_trans_edit_description_cb), + info); + gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); - if (edit_memo) - { - menuitem = gtk_menu_item_new_with_label ( - _("Edit memo.")); - g_signal_connect (menuitem, "activate", - G_CALLBACK (gnc_gen_trans_edit_memo_cb), - info); - DEBUG("Callback to edit memo"); - gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); - } + menuitem = gtk_menu_item_new_with_label (_("Edit memo.")); + gtk_widget_set_sensitive (menuitem, edit_memo); + g_signal_connect (menuitem, "activate", + G_CALLBACK (gnc_gen_trans_edit_memo_cb), + info); + gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); + + menuitem = gtk_menu_item_new_with_label (_("Edit notes.")); + gtk_widget_set_sensitive (menuitem, edit_notes); + g_signal_connect (menuitem, "activate", + G_CALLBACK (gnc_gen_trans_edit_notes_cb), + info); + gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); - if (edit_notes) - { - menuitem = gtk_menu_item_new_with_label ( - _("Edit notes.")); - g_signal_connect (menuitem, "activate", - G_CALLBACK (gnc_gen_trans_edit_notes_cb), - info); - DEBUG("Callback to edit notes"); - gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); - } gtk_widget_show_all (menu); - event_button = (GdkEventButton *) event; /* Note: event can be NULL here when called from view_onPopupMenu; */ gtk_menu_popup_at_pointer (GTK_MENU(menu), (GdkEvent*)event); From 3db8c56a909d033576ea5822f0213948721f08e5 Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Tue, 12 Jul 2022 23:00:11 +0800 Subject: [PATCH 04/13] [import-main-matcher] save orig desc/notes/memo to reset edits adds menu item "Reset edits." which resets imported transactions' desc/notes/memo back to the original imported strings. --- gnucash/import-export/import-main-matcher.c | 93 +++++++++++++++++++-- 1 file changed, 87 insertions(+), 6 deletions(-) diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c index cf3ee41a45..1ebea25040 100644 --- a/gnucash/import-export/import-main-matcher.c +++ b/gnucash/import-export/import-main-matcher.c @@ -90,7 +90,10 @@ enum downloaded_cols DOWNLOADED_COL_AMOUNT, DOWNLOADED_COL_AMOUNT_DOUBLE, // used only for sorting DOWNLOADED_COL_DESCRIPTION, + DOWNLOADED_COL_DESCRIPTION_ORIGINAL, DOWNLOADED_COL_MEMO, + DOWNLOADED_COL_MEMO_ORIGINAL, + DOWNLOADED_COL_NOTES_ORIGINAL, DOWNLOADED_COL_ACTION_ADD, DOWNLOADED_COL_ACTION_CLEAR, DOWNLOADED_COL_ACTION_UPDATE, @@ -898,10 +901,14 @@ typedef struct Split *split; Transaction *trans; GtkTreeIter iter; + char *orig_desc, *orig_notes, *orig_memo; } RowInfo; static void rowinfo_free (RowInfo* info) { + g_free (info->orig_desc); + g_free (info->orig_notes); + g_free (info->orig_memo); g_free (info); } @@ -911,7 +918,12 @@ static RowInfo * row_get_info (gpointer row, GNCImportMainMatcher *info) GtkTreeModel *model = gtk_tree_view_get_model (info->view); RowInfo *retval = g_new (RowInfo, 1); gtk_tree_model_get_iter (model, &retval->iter, row); - gtk_tree_model_get (model, &retval->iter, DOWNLOADED_COL_DATA, &trans_info, -1); + gtk_tree_model_get (model, &retval->iter, + DOWNLOADED_COL_DATA, &trans_info, + DOWNLOADED_COL_DESCRIPTION_ORIGINAL, &retval->orig_desc, + DOWNLOADED_COL_NOTES_ORIGINAL, &retval->orig_notes, + DOWNLOADED_COL_MEMO_ORIGINAL, &retval->orig_memo, + -1); retval->trans = gnc_import_TransInfo_get_trans (trans_info); retval->split = gnc_import_TransInfo_get_fsplit (trans_info); return retval; @@ -1024,6 +1036,46 @@ gnc_gen_trans_edit_notes_cb (GtkMenuItem *menuitem, GNCImportMainMatcher *info) gnc_gen_trans_edit_fields (menuitem, info, NOTES); } +static void +gnc_gen_trans_reset_edits_cb (GtkMenuItem *menuitem, GNCImportMainMatcher *info) +{ + GtkTreeView *treeview; + GtkTreeModel *model; + GtkTreeStore *store; + GtkTreeSelection *selection; + GList *selected_rows; + + g_return_if_fail (info != NULL); + ENTER("gnc_gen_trans_reset_edits_cb"); + + treeview = GTK_TREE_VIEW(info->view); + model = gtk_tree_view_get_model (treeview); + store = GTK_TREE_STORE (model); + selection = gtk_tree_view_get_selection (treeview); + selected_rows = gtk_tree_selection_get_selected_rows (selection, &model); + + if (!selected_rows) + { + LEAVE ("No selected rows"); + return; + } + + for (GList *n = selected_rows; n; n = g_list_next (n)) + { + RowInfo *rowinfo = row_get_info (n->data, info); + xaccTransSetDescription (rowinfo->trans, rowinfo->orig_desc); + xaccTransSetNotes (rowinfo->trans, rowinfo->orig_notes); + xaccSplitSetMemo (rowinfo->split, rowinfo->orig_memo); + gtk_tree_store_set (store, &rowinfo->iter, + DOWNLOADED_COL_DESCRIPTION, rowinfo->orig_desc, + DOWNLOADED_COL_MEMO, rowinfo->orig_memo, + -1); + rowinfo_free (rowinfo); + }; + g_list_free_full (selected_rows, (GDestroyNotify)gtk_tree_path_free); + LEAVE(""); +} + static void gnc_gen_trans_row_activated_cb (GtkTreeView *treeview, GtkTreePath *path, @@ -1119,6 +1171,7 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, GList *selected_rows; const char *desc, *memo, *notes; gboolean edit_desc = TRUE, edit_notes = TRUE, edit_memo = TRUE; + gboolean has_edits = FALSE; ENTER (""); menu = gtk_menu_new(); @@ -1138,6 +1191,13 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, n = g_list_next(n)) { RowInfo *rowinfo = row_get_info (n->data, info); + + if (!has_edits && + (g_strcmp0 (xaccSplitGetMemo (rowinfo->split), rowinfo->orig_memo) || + g_strcmp0 (xaccTransGetNotes (rowinfo->trans), rowinfo->orig_notes) || + g_strcmp0 (xaccTransGetDescription (rowinfo->trans), rowinfo->orig_desc))) + has_edits = TRUE; + if (!n->prev) /* only the first row */ { desc = xaccTransGetDescription (rowinfo->trans); @@ -1176,6 +1236,13 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, info); gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); + menuitem = gtk_menu_item_new_with_label (_("Reset edits.")); + gtk_widget_set_sensitive (menuitem, has_edits); + g_signal_connect (menuitem, "activate", + G_CALLBACK (gnc_gen_trans_reset_edits_cb), + info); + gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); + gtk_widget_show_all (menu); /* Note: event can be NULL here when called from view_onPopupMenu; */ gtk_menu_popup_at_pointer (GTK_MENU(menu), (GdkEvent*)event); @@ -1312,6 +1379,7 @@ gnc_gen_trans_init_view (GNCImportMainMatcher *info, view = info->view; store = gtk_tree_store_new (NUM_DOWNLOADED_COLS, G_TYPE_STRING, G_TYPE_INT64, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_DOUBLE, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_POINTER, G_TYPE_STRING, @@ -1726,13 +1794,22 @@ refresh_model_row (GNCImportMainMatcher *gui, gtk_tree_store_set (store, iter, DOWNLOADED_COL_AMOUNT, ro_text, -1); gtk_tree_store_set (store, iter, DOWNLOADED_COL_AMOUNT_DOUBLE, gnc_numeric_to_double (amount), -1); + /* Notes */ + ro_text = xaccTransGetNotes (gnc_import_TransInfo_get_trans (info)); + gtk_tree_store_set (store, iter, DOWNLOADED_COL_NOTES_ORIGINAL, ro_text, -1); + /*Description*/ ro_text = xaccTransGetDescription (gnc_import_TransInfo_get_trans (info) ); - gtk_tree_store_set (store, iter, DOWNLOADED_COL_DESCRIPTION, ro_text, -1); - + gtk_tree_store_set (store, iter, + DOWNLOADED_COL_DESCRIPTION, ro_text, + DOWNLOADED_COL_DESCRIPTION_ORIGINAL, ro_text, + -1); /*Memo*/ ro_text = xaccSplitGetMemo (split); - gtk_tree_store_set (store, iter, DOWNLOADED_COL_MEMO, ro_text, -1); + gtk_tree_store_set (store, iter, + DOWNLOADED_COL_MEMO, ro_text, + DOWNLOADED_COL_MEMO_ORIGINAL, ro_text, + -1); /*Actions*/ @@ -2203,10 +2280,14 @@ query_tooltip_tree_view_cb (GtkWidget *widget, gint x, gint y, switch (num_col) { case DOWNLOADED_COL_DESCRIPTION: - gtk_tree_model_get (model, &iter, DOWNLOADED_COL_DESCRIPTION, &tooltip_text, -1); + gtk_tree_model_get (model, &iter, + DOWNLOADED_COL_DESCRIPTION_ORIGINAL, &tooltip_text, + -1); break; case DOWNLOADED_COL_MEMO: - gtk_tree_model_get (model, &iter, DOWNLOADED_COL_MEMO, &tooltip_text, -1); + gtk_tree_model_get (model, &iter, + DOWNLOADED_COL_MEMO_ORIGINAL, &tooltip_text, + -1); break; default: break; From fb6091fb3e7b48488fd398d2d01e0e7e65575dfa Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Wed, 13 Jul 2022 23:55:46 +0800 Subject: [PATCH 05/13] [import-main-matcher] 1 dialog to edit all 3 fields --- gnucash/import-export/import-main-matcher.c | 208 ++++++++++---------- 1 file changed, 109 insertions(+), 99 deletions(-) diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c index 1ebea25040..6499a51ad1 100644 --- a/gnucash/import-export/import-main-matcher.c +++ b/gnucash/import-export/import-main-matcher.c @@ -80,6 +80,11 @@ struct _main_matcher_info GSList* temp_trans_list; // Temporary list of imported transactions GHashTable* acct_id_hash; // Hash table, per account, of list of transaction IDs. GSList* edited_accounts; // List of accounts currently edited. + + /* only when editing fields */ + gboolean edit_desc; + gboolean edit_notes; + gboolean edit_memo; }; enum downloaded_cols @@ -889,13 +894,6 @@ gnc_gen_trans_assign_transfer_account_to_selection_cb (GtkMenuItem *menuitem, LEAVE(""); } -typedef enum -{ - DESCRIPTION, - MEMO, - NOTES, -} edit_field; - typedef struct { Split *split; @@ -929,16 +927,79 @@ static RowInfo * row_get_info (gpointer row, GNCImportMainMatcher *info) return retval; } +static gboolean +input_new_fields (GtkWidget *parent, RowInfo *info, GtkTreeStore *store, + gboolean edit_desc, gboolean edit_notes, gboolean edit_memo, + char **new_desc, char **new_notes, char **new_memo) +{ + GtkWidget *desc_entry, *notes_entry, *memo_entry, *label, *grid; + GtkWidget *dialog = + gtk_dialog_new_with_buttons ("Edit imported transaction details", + GTK_WINDOW (parent), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + _("_OK"), GTK_RESPONSE_ACCEPT, + _("_Cancel"), GTK_RESPONSE_REJECT, + NULL); + GtkWidget *content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gboolean retval; + + grid = gtk_grid_new (); + + label = gtk_label_new ("Description"); + gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1); + + desc_entry = gtk_entry_new (); + gtk_widget_set_halign (desc_entry, GTK_ALIGN_START); + gtk_widget_set_sensitive (desc_entry, edit_desc); + gtk_entry_set_text (GTK_ENTRY (desc_entry), xaccTransGetDescription (info->trans)); + gtk_grid_attach (GTK_GRID (grid), desc_entry, 1, 0, 1, 1); + + label = gtk_label_new ("Notes"); + gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1); + + notes_entry = gtk_entry_new (); + gtk_widget_set_halign (notes_entry, GTK_ALIGN_START); + gtk_widget_set_sensitive (notes_entry, edit_notes); + gtk_entry_set_text (GTK_ENTRY (notes_entry), xaccTransGetNotes (info->trans)); + gtk_grid_attach (GTK_GRID (grid), notes_entry, 1, 1, 1, 1); + + label = gtk_label_new ("Memo"); + gtk_grid_attach (GTK_GRID (grid), label, 0, 2, 1, 1); + + memo_entry = gtk_entry_new (); + gtk_widget_set_halign (memo_entry, GTK_ALIGN_START); + gtk_widget_set_sensitive (memo_entry, edit_memo); + gtk_entry_set_text (GTK_ENTRY (memo_entry), xaccSplitGetMemo (info->split)); + gtk_grid_attach (GTK_GRID (grid), memo_entry, 1, 2, 1, 1); + + gtk_container_add_with_properties (GTK_CONTAINER (content_area), grid, + "position", 1, NULL); + + // run the dialog + gtk_widget_show_all (grid); + + retval = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT); + + if (retval) + { + *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_memo = g_strdup (gtk_entry_get_text (GTK_ENTRY (memo_entry))); + } + + gtk_widget_destroy (dialog); + return retval; +} + static void -gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info, - edit_field field) +gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info) { GtkTreeView *treeview; GtkTreeSelection *selection; GtkTreeModel *model; GList *selected_rows, *row_info_list; GtkTreeStore* store; - Transaction* trans; + char *new_desc, *new_notes, *new_memo; g_return_if_fail (info != NULL); ENTER("assign_transfer_account_to_selection_cb"); @@ -956,86 +1017,42 @@ gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info, } row_info_list = gnc_g_list_map (selected_rows, (GncGMapFunc) row_get_info, info); - trans = ((RowInfo*)row_info_list->data)->trans; - switch (field) + if (input_new_fields (info->main_widget, row_info_list->data, store, + info->edit_desc, info->edit_notes, info->edit_memo, + &new_desc, &new_notes, &new_memo)) { - case DESCRIPTION: + for (GList *n = row_info_list; n; n = g_list_next (n)) { - char* new_field = - gnc_input_dialog_with_entry(info->main_widget, "", - _("Enter new Description"), - xaccTransGetDescription (trans)); - if (!new_field) break; - for (GList *n = row_info_list; n; n = g_list_next (n)) + RowInfo *row = n->data; + if (info->edit_desc) { - RowInfo *info = n->data; - xaccTransSetDescription (info->trans, new_field); - gtk_tree_store_set (store, &info->iter, - DOWNLOADED_COL_DESCRIPTION, new_field, + gtk_tree_store_set (store, &row->iter, + DOWNLOADED_COL_DESCRIPTION, new_desc, -1); + xaccTransSetDescription (row->trans, new_desc); } - g_free (new_field); - break; - } - case MEMO: - { - Split *first_split = xaccTransGetSplit (trans, 0); - char *new_field = - gnc_input_dialog_with_entry(info->main_widget, "", - _("Enter new Memo"), - xaccSplitGetMemo (first_split)); - if (!new_field) break; - for (GList *n = row_info_list; n; n = g_list_next (n)) + + if (info->edit_notes) + xaccTransSetNotes (row->trans, new_notes); + + if (info->edit_memo) { - RowInfo *info = n->data; - xaccSplitSetMemo (info->split, new_field); - gtk_tree_store_set (store, &info->iter, - DOWNLOADED_COL_MEMO, new_field, + gtk_tree_store_set (store, &row->iter, + DOWNLOADED_COL_MEMO, new_memo, -1); + xaccSplitSetMemo (row->split, new_memo); } - g_free (new_field); - break; - } - case NOTES: - { - char* new_field = - gnc_input_dialog_with_entry(info->main_widget, "", - _("Enter new Notes"), - xaccTransGetNotes (trans)); - if (!new_field) break; - for (GList *n = row_info_list; n; n = g_list_next (n)) - { - RowInfo *info = n->data; - xaccTransSetNotes (info->trans, new_field); - } - g_free (new_field); - break; } + g_free (new_desc); + g_free (new_memo); + g_free (new_notes); } g_list_free_full (row_info_list, (GDestroyNotify)rowinfo_free); g_list_free_full (selected_rows, (GDestroyNotify)gtk_tree_path_free); LEAVE(""); } -static void -gnc_gen_trans_edit_description_cb (GtkMenuItem *menuitem, GNCImportMainMatcher *info) -{ - gnc_gen_trans_edit_fields (menuitem, info, DESCRIPTION); -} - -static void -gnc_gen_trans_edit_memo_cb (GtkMenuItem *menuitem, GNCImportMainMatcher *info) -{ - gnc_gen_trans_edit_fields (menuitem, info, MEMO); -} - -static void -gnc_gen_trans_edit_notes_cb (GtkMenuItem *menuitem, GNCImportMainMatcher *info) -{ - gnc_gen_trans_edit_fields (menuitem, info, NOTES); -} - static void gnc_gen_trans_reset_edits_cb (GtkMenuItem *menuitem, GNCImportMainMatcher *info) { @@ -1170,7 +1187,6 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, GtkTreeSelection *selection; GList *selected_rows; const char *desc, *memo, *notes; - gboolean edit_desc = TRUE, edit_notes = TRUE, edit_memo = TRUE; gboolean has_edits = FALSE; ENTER (""); @@ -1187,7 +1203,14 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, model = gtk_tree_view_get_model (treeview); selection = gtk_tree_view_get_selection (treeview); selected_rows = gtk_tree_selection_get_selected_rows (selection, &model); - for (GList *n = selected_rows; (edit_desc || edit_notes || edit_memo) && n; + + /* initialise */ + info->edit_desc = TRUE; + info->edit_notes = TRUE; + info->edit_memo = TRUE; + + for (GList *n = selected_rows; + (!has_edits || info->edit_desc || info->edit_notes || info->edit_memo) && n; n = g_list_next(n)) { RowInfo *rowinfo = row_get_info (n->data, info); @@ -1206,33 +1229,20 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, rowinfo_free (rowinfo); continue; } - if (edit_desc && g_strcmp0 (desc, xaccTransGetDescription (rowinfo->trans))) - edit_desc = FALSE; - if (edit_notes && g_strcmp0 (notes, xaccTransGetNotes (rowinfo->trans))) - edit_notes = FALSE; - if (edit_memo && g_strcmp0 (memo, xaccSplitGetMemo (rowinfo->split))) - edit_memo = FALSE; + if (info->edit_desc && g_strcmp0 (desc, xaccTransGetDescription (rowinfo->trans))) + info->edit_desc = FALSE; + if (info->edit_notes && g_strcmp0 (notes, xaccTransGetNotes (rowinfo->trans))) + info->edit_notes = FALSE; + if (info->edit_memo && g_strcmp0 (memo, xaccSplitGetMemo (rowinfo->split))) + info->edit_memo = FALSE; rowinfo_free (rowinfo); } - menuitem = gtk_menu_item_new_with_label (_("Edit description.")); - gtk_widget_set_sensitive (menuitem, edit_desc); + menuitem = gtk_menu_item_new_with_label (_("Edit description, notes, memo.")); + gtk_widget_set_sensitive (menuitem, + info->edit_desc || info->edit_notes || info->edit_memo); g_signal_connect (menuitem, "activate", - G_CALLBACK (gnc_gen_trans_edit_description_cb), - info); - gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); - - menuitem = gtk_menu_item_new_with_label (_("Edit memo.")); - gtk_widget_set_sensitive (menuitem, edit_memo); - g_signal_connect (menuitem, "activate", - G_CALLBACK (gnc_gen_trans_edit_memo_cb), - info); - gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); - - menuitem = gtk_menu_item_new_with_label (_("Edit notes.")); - gtk_widget_set_sensitive (menuitem, edit_notes); - g_signal_connect (menuitem, "activate", - G_CALLBACK (gnc_gen_trans_edit_notes_cb), + G_CALLBACK (gnc_gen_trans_edit_fields), info); gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); From d4ff2ede955f3f0741c0c07fd72953f8482fb698 Mon Sep 17 00:00:00 2001 From: Robert Fewell <14uBobIT@gmail.com> Date: Thu, 14 Jul 2022 21:25:20 +0800 Subject: [PATCH 06/13] [import-main-matcher] annotate changed fields to italic and change use to .glade --- gnucash/gtkbuilder/dialog-import.glade | 139 ++++++++++++++++++++ gnucash/import-export/import-main-matcher.c | 95 +++++++------ 2 files changed, 193 insertions(+), 41 deletions(-) diff --git a/gnucash/gtkbuilder/dialog-import.glade b/gnucash/gtkbuilder/dialog-import.glade index f3350bc708..f7e3d001ed 100644 --- a/gnucash/gtkbuilder/dialog-import.glade +++ b/gnucash/gtkbuilder/dialog-import.glade @@ -866,6 +866,145 @@ matcher_help_close + + False + Edit imported transaction details + 320 + dialog + + + False + vertical + 2 + + + False + end + + + _Cancel + True + True + True + True + + + True + True + 0 + + + + + _OK + True + True + True + True + + + True + True + 1 + + + + + False + False + 0 + + + + + + True + False + 3 + 6 + + + True + False + end + Description + + + 0 + 0 + + + + + True + False + end + Notes + + + 0 + 1 + + + + + True + False + end + Memo + + + 0 + 2 + + + + + True + True + True + + + 1 + 0 + + + + + True + True + True + + + 1 + 1 + + + + + True + True + True + + + 1 + 2 + + + + + False + True + 1 + + + + + + button1 + button2 + + True False diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c index 6499a51ad1..a9f6f4382c 100644 --- a/gnucash/import-export/import-main-matcher.c +++ b/gnucash/import-export/import-main-matcher.c @@ -96,8 +96,10 @@ enum downloaded_cols DOWNLOADED_COL_AMOUNT_DOUBLE, // used only for sorting DOWNLOADED_COL_DESCRIPTION, DOWNLOADED_COL_DESCRIPTION_ORIGINAL, + DOWNLOADED_COL_DESCRIPTION_STYLE, DOWNLOADED_COL_MEMO, DOWNLOADED_COL_MEMO_ORIGINAL, + DOWNLOADED_COL_MEMO_STYLE, DOWNLOADED_COL_NOTES_ORIGINAL, DOWNLOADED_COL_ACTION_ADD, DOWNLOADED_COL_ACTION_CLEAR, @@ -932,62 +934,48 @@ input_new_fields (GtkWidget *parent, RowInfo *info, GtkTreeStore *store, gboolean edit_desc, gboolean edit_notes, gboolean edit_memo, char **new_desc, char **new_notes, char **new_memo) { - GtkWidget *desc_entry, *notes_entry, *memo_entry, *label, *grid; - GtkWidget *dialog = - gtk_dialog_new_with_buttons ("Edit imported transaction details", - GTK_WINDOW (parent), - GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, - _("_OK"), GTK_RESPONSE_ACCEPT, - _("_Cancel"), GTK_RESPONSE_REJECT, - NULL); - GtkWidget *content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); - gboolean retval; + GtkWidget *desc_entry, *notes_entry, *memo_entry, *label; + GtkWidget *dialog; + GtkBuilder *builder; + gboolean retval = FALSE; - grid = gtk_grid_new (); + builder = gtk_builder_new (); + gnc_builder_add_from_file (builder, "dialog-import.glade", "transaction_edit_dialog"); - label = gtk_label_new ("Description"); - gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1); + dialog = GTK_WIDGET(gtk_builder_get_object (builder, "transaction_edit_dialog")); + + desc_entry = GTK_WIDGET(gtk_builder_get_object (builder, "desc_entry")); + memo_entry = GTK_WIDGET(gtk_builder_get_object (builder, "memo_entry")); + notes_entry = GTK_WIDGET(gtk_builder_get_object (builder, "notes_entry")); - desc_entry = gtk_entry_new (); - gtk_widget_set_halign (desc_entry, GTK_ALIGN_START); gtk_widget_set_sensitive (desc_entry, edit_desc); gtk_entry_set_text (GTK_ENTRY (desc_entry), xaccTransGetDescription (info->trans)); - gtk_grid_attach (GTK_GRID (grid), desc_entry, 1, 0, 1, 1); - label = gtk_label_new ("Notes"); - gtk_grid_attach (GTK_GRID (grid), label, 0, 1, 1, 1); - - notes_entry = gtk_entry_new (); - gtk_widget_set_halign (notes_entry, GTK_ALIGN_START); gtk_widget_set_sensitive (notes_entry, edit_notes); gtk_entry_set_text (GTK_ENTRY (notes_entry), xaccTransGetNotes (info->trans)); - gtk_grid_attach (GTK_GRID (grid), notes_entry, 1, 1, 1, 1); - label = gtk_label_new ("Memo"); - gtk_grid_attach (GTK_GRID (grid), label, 0, 2, 1, 1); - - memo_entry = gtk_entry_new (); - gtk_widget_set_halign (memo_entry, GTK_ALIGN_START); gtk_widget_set_sensitive (memo_entry, edit_memo); gtk_entry_set_text (GTK_ENTRY (memo_entry), xaccSplitGetMemo (info->split)); - gtk_grid_attach (GTK_GRID (grid), memo_entry, 1, 2, 1, 1); - gtk_container_add_with_properties (GTK_CONTAINER (content_area), grid, - "position", 1, NULL); + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent)); // run the dialog - gtk_widget_show_all (grid); + gtk_widget_show_all (dialog); - retval = (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT); - - if (retval) + switch (gtk_dialog_run (GTK_DIALOG(dialog))) { + case GTK_RESPONSE_OK: *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_memo = g_strdup (gtk_entry_get_text (GTK_ENTRY (memo_entry))); + retval = TRUE; + break; + default: + break; } gtk_widget_destroy (dialog); + g_object_unref (G_OBJECT(builder)); return retval; } @@ -1027,8 +1015,11 @@ gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info) RowInfo *row = n->data; if (info->edit_desc) { + guint64 style = g_strcmp0 (new_desc, row->orig_desc) ? + PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL; gtk_tree_store_set (store, &row->iter, DOWNLOADED_COL_DESCRIPTION, new_desc, + DOWNLOADED_COL_DESCRIPTION_STYLE, style, -1); xaccTransSetDescription (row->trans, new_desc); } @@ -1038,8 +1029,11 @@ gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info) if (info->edit_memo) { + guint64 style = g_strcmp0 (new_memo, row->orig_memo) ? + PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL; gtk_tree_store_set (store, &row->iter, DOWNLOADED_COL_MEMO, new_memo, + DOWNLOADED_COL_MEMO_STYLE, style, -1); xaccSplitSetMemo (row->split, new_memo); } @@ -1060,7 +1054,7 @@ gnc_gen_trans_reset_edits_cb (GtkMenuItem *menuitem, GNCImportMainMatcher *info) GtkTreeModel *model; GtkTreeStore *store; GtkTreeSelection *selection; - GList *selected_rows; + GList *selected_rows, *row_info_list; g_return_if_fail (info != NULL); ENTER("gnc_gen_trans_reset_edits_cb"); @@ -1086,6 +1080,8 @@ gnc_gen_trans_reset_edits_cb (GtkMenuItem *menuitem, GNCImportMainMatcher *info) gtk_tree_store_set (store, &rowinfo->iter, DOWNLOADED_COL_DESCRIPTION, rowinfo->orig_desc, DOWNLOADED_COL_MEMO, rowinfo->orig_memo, + DOWNLOADED_COL_DESCRIPTION_STYLE, PANGO_STYLE_NORMAL, + DOWNLOADED_COL_MEMO_STYLE, PANGO_STYLE_NORMAL, -1); rowinfo_free (rowinfo); }; @@ -1186,7 +1182,7 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, GtkTreeModel *model; GtkTreeSelection *selection; GList *selected_rows; - const char *desc, *memo, *notes; + const char *desc = NULL, *memo = NULL, *notes = NULL; gboolean has_edits = FALSE; ENTER (""); @@ -1346,6 +1342,12 @@ add_text_column (GtkTreeView *view, const gchar *title, int col_num, gboolean el else gtk_tree_view_column_set_sort_column_id (column, col_num); + if (col_num == DOWNLOADED_COL_DESCRIPTION) + gtk_tree_view_column_add_attribute (column, renderer, "style", DOWNLOADED_COL_DESCRIPTION_STYLE); + + if (col_num == DOWNLOADED_COL_MEMO) + gtk_tree_view_column_add_attribute (column, renderer, "style", DOWNLOADED_COL_MEMO_STYLE); + g_object_set (G_OBJECT(column), "reorderable", TRUE, "resizable", TRUE, @@ -1389,8 +1391,9 @@ gnc_gen_trans_init_view (GNCImportMainMatcher *info, view = info->view; store = gtk_tree_store_new (NUM_DOWNLOADED_COLS, G_TYPE_STRING, G_TYPE_INT64, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_DOUBLE, - G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, - G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT64, //description stuff + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_UINT64, //memo stuff + G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN); @@ -1720,9 +1723,19 @@ update_child_row (GNCImportMatchInfo *sel_match, GtkTreeModel *model, GtkTreeIte ro_text = xaccPrintAmount (xaccSplitGetAmount (sel_match->split), gnc_split_amount_print_info (sel_match->split, TRUE)); - gtk_tree_store_set (store, &child, DOWNLOADED_COL_AMOUNT, ro_text, -1); - gtk_tree_store_set (store, &child, DOWNLOADED_COL_MEMO, memo, -1); - gtk_tree_store_set (store, &child, DOWNLOADED_COL_DESCRIPTION, desc, -1); + gtk_tree_store_set (store, &child, + DOWNLOADED_COL_AMOUNT, ro_text, + -1); + + gtk_tree_store_set (store, &child, + DOWNLOADED_COL_MEMO, memo, + DOWNLOADED_COL_MEMO_STYLE, PANGO_STYLE_NORMAL, + -1); + + gtk_tree_store_set (store, &child, + DOWNLOADED_COL_DESCRIPTION, desc, + DOWNLOADED_COL_DESCRIPTION_STYLE, PANGO_STYLE_NORMAL, + -1); gtk_tree_store_set (store, &child, DOWNLOADED_COL_ENABLE, FALSE, -1); g_free (text); From 2bafe700dbd0c208bd65a07edbc95ee8933a7686 Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Fri, 15 Jul 2022 17:40:30 +0800 Subject: [PATCH 07/13] [import-main-matcher] use GtkEntry with GtkEntryCompletion completion enabled using existing transaction strings. --- gnucash/import-export/import-main-matcher.c | 72 +++++++++++++++++++-- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c index a9f6f4382c..4430116996 100644 --- a/gnucash/import-export/import-main-matcher.c +++ b/gnucash/import-export/import-main-matcher.c @@ -121,6 +121,8 @@ enum downloaded_cols /*static QofLogModule log_module = GNC_MOD_IMPORT;*/ static QofLogModule log_module = G_MOD_IMPORT_MATCHER; +static const gpointer one = GINT_TO_POINTER (1); + void on_matcher_ok_clicked (GtkButton *button, GNCImportMainMatcher *info); void on_matcher_cancel_clicked (GtkButton *button, gpointer user_data); gboolean on_matcher_delete_event (GtkWidget *widget, GdkEvent *event, gpointer data); @@ -929,6 +931,40 @@ static RowInfo * row_get_info (gpointer row, GNCImportMainMatcher *info) return retval; } +static void populate_list (gpointer key, gpointer value, GtkListStore *list) +{ + GtkTreeIter iter; + char *collated_key = g_utf8_collate_key (key, -1); + gtk_list_store_append (list, &iter); + gtk_list_store_set (list, &iter, 0, key, 1, collated_key, -1); + g_free (collated_key); +} + +static void +setup_entry (GtkWidget *entry, gboolean sensitive, GHashTable *hash, + const char *initial) +{ + GtkEntryCompletion* completion; + GtkListStore *list; + + gtk_entry_set_text (GTK_ENTRY (entry), sensitive ? initial : _("Disabled")); + gtk_widget_set_sensitive (entry, sensitive); + if (!sensitive) + return; + + list = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING); + g_hash_table_foreach (hash, (GHFunc)populate_list, list); + if (!g_hash_table_lookup (hash, (gpointer)initial)) + populate_list ((gpointer)initial, NULL, list); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list), 1, + GTK_SORT_ASCENDING); + + completion = gtk_entry_completion_new (); + gtk_entry_completion_set_model (completion, GTK_TREE_MODEL(list)); + gtk_entry_completion_set_text_column (completion, 0); + gtk_entry_set_completion (GTK_ENTRY (entry), completion); +} + static gboolean input_new_fields (GtkWidget *parent, RowInfo *info, GtkTreeStore *store, gboolean edit_desc, gboolean edit_notes, gboolean edit_memo, @@ -938,6 +974,7 @@ input_new_fields (GtkWidget *parent, RowInfo *info, GtkTreeStore *store, GtkWidget *dialog; GtkBuilder *builder; gboolean retval = FALSE; + GHashTable *desc_hash, *notes_hash, *memo_hash; builder = gtk_builder_new (); gnc_builder_add_from_file (builder, "dialog-import.glade", "transaction_edit_dialog"); @@ -948,14 +985,37 @@ input_new_fields (GtkWidget *parent, RowInfo *info, GtkTreeStore *store, memo_entry = GTK_WIDGET(gtk_builder_get_object (builder, "memo_entry")); notes_entry = GTK_WIDGET(gtk_builder_get_object (builder, "notes_entry")); - gtk_widget_set_sensitive (desc_entry, edit_desc); - gtk_entry_set_text (GTK_ENTRY (desc_entry), xaccTransGetDescription (info->trans)); + desc_hash = g_hash_table_new (g_str_hash, g_str_equal); + notes_hash = g_hash_table_new (g_str_hash, g_str_equal); + memo_hash = g_hash_table_new (g_str_hash, g_str_equal); - gtk_widget_set_sensitive (notes_entry, edit_notes); - gtk_entry_set_text (GTK_ENTRY (notes_entry), xaccTransGetNotes (info->trans)); + for (GList *n = xaccAccountGetSplitList (xaccSplitGetAccount (info->split)); + n; n = n->next) + { + const Split *s = n->data; + const Transaction *t = xaccSplitGetParent (s); + const gchar *key; - gtk_widget_set_sensitive (memo_entry, edit_memo); - gtk_entry_set_text (GTK_ENTRY (memo_entry), xaccSplitGetMemo (info->split)); + key = xaccTransGetDescription (t); + if (key && *key) + g_hash_table_insert (desc_hash, (gpointer)key, one); + + key = xaccTransGetNotes (t); + if (key && *key) + g_hash_table_insert (notes_hash, (gpointer)key, one); + + key = xaccSplitGetMemo (s); + if (key && *key) + g_hash_table_insert (memo_hash, (gpointer)key, one); + }; + + setup_entry (desc_entry, edit_desc, desc_hash, xaccTransGetDescription (info->trans)); + setup_entry (notes_entry, edit_notes, notes_hash, xaccTransGetNotes (info->trans)); + setup_entry (memo_entry, edit_memo, memo_hash, xaccSplitGetMemo (info->split)); + + g_hash_table_destroy (desc_hash); + g_hash_table_destroy (notes_hash); + g_hash_table_destroy (memo_hash); gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent)); From 6d1f3c5af60aa5bc27d088124600b94eb1b5370d Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Sat, 16 Jul 2022 00:00:20 +0800 Subject: [PATCH 08/13] [import-main-matcher] if multi-accounts, scan all accounts if an ofx has info on multiple accounts, and selection includes multiple accounts, scan all of them. also further separation between UI and data processing. --- gnucash/import-export/import-main-matcher.c | 101 ++++++++++++-------- 1 file changed, 63 insertions(+), 38 deletions(-) diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c index 4430116996..12ad9493e5 100644 --- a/gnucash/import-export/import-main-matcher.c +++ b/gnucash/import-export/import-main-matcher.c @@ -85,6 +85,10 @@ struct _main_matcher_info gboolean edit_desc; gboolean edit_notes; gboolean edit_memo; + + GHashTable *desc_hash; + GHashTable *notes_hash; + GHashTable *memo_hash; }; enum downloaded_cols @@ -236,6 +240,11 @@ gnc_gen_trans_list_delete (GNCImportMainMatcher *info) g_hash_table_foreach_remove (info->acct_id_hash, delete_hash, NULL); g_hash_table_destroy (info->acct_id_hash); info->acct_id_hash = NULL; + + g_hash_table_destroy (info->desc_hash); + g_hash_table_destroy (info->notes_hash); + g_hash_table_destroy (info->memo_hash); + g_free (info); } @@ -457,6 +466,47 @@ resolve_conflicts (GNCImportMainMatcher *info) } } + +static void +load_hash_tables (GNCImportMainMatcher *info) +{ + GtkTreeModel *model = gtk_tree_view_get_model (info->view); + GtkTreeIter import_iter; + GList *accounts_list = NULL; + gboolean valid = gtk_tree_model_get_iter_first (model, &import_iter); + while (valid) + { + GNCImportTransInfo *trans_info = get_trans_info (model, &import_iter); + Split *s = gnc_import_TransInfo_get_fsplit (trans_info); + Account *acc = xaccSplitGetAccount (s); + if (!g_list_find (accounts_list, acc)) + accounts_list = g_list_prepend (accounts_list, acc); + valid = gtk_tree_model_iter_next (model, &import_iter); + } + for (GList *m = accounts_list; m; m = m->next) + { + for (GList *n = xaccAccountGetSplitList (m->data); n; n = n->next) + { + const Split *s = n->data; + const Transaction *t = xaccSplitGetParent (s); + const gchar *key; + + key = xaccTransGetDescription (t); + if (key && *key) + g_hash_table_insert (info->desc_hash, (gpointer)key, one); + + key = xaccTransGetNotes (t); + if (key && *key) + g_hash_table_insert (info->notes_hash, (gpointer)key, one); + + key = xaccSplitGetMemo (s); + if (key && *key) + g_hash_table_insert (info->memo_hash, (gpointer)key, one); + } + } + g_list_free (accounts_list); +} + void gnc_gen_trans_list_show_all (GNCImportMainMatcher *info) { @@ -482,6 +532,7 @@ gnc_gen_trans_list_show_all (GNCImportMainMatcher *info) xaccAccountGetAppendText(account)); gnc_gen_trans_list_create_matches (info); + load_hash_tables (info); resolve_conflicts (info); gtk_widget_show_all (GTK_WIDGET(info->main_widget)); gnc_gen_trans_list_show_accounts_column (info); @@ -966,15 +1017,13 @@ setup_entry (GtkWidget *entry, gboolean sensitive, GHashTable *hash, } static gboolean -input_new_fields (GtkWidget *parent, RowInfo *info, GtkTreeStore *store, - gboolean edit_desc, gboolean edit_notes, gboolean edit_memo, +input_new_fields (GNCImportMainMatcher *info, RowInfo *rowinfo, char **new_desc, char **new_notes, char **new_memo) { GtkWidget *desc_entry, *notes_entry, *memo_entry, *label; GtkWidget *dialog; GtkBuilder *builder; gboolean retval = FALSE; - GHashTable *desc_hash, *notes_hash, *memo_hash; builder = gtk_builder_new (); gnc_builder_add_from_file (builder, "dialog-import.glade", "transaction_edit_dialog"); @@ -985,39 +1034,11 @@ input_new_fields (GtkWidget *parent, RowInfo *info, GtkTreeStore *store, memo_entry = GTK_WIDGET(gtk_builder_get_object (builder, "memo_entry")); notes_entry = GTK_WIDGET(gtk_builder_get_object (builder, "notes_entry")); - desc_hash = g_hash_table_new (g_str_hash, g_str_equal); - notes_hash = g_hash_table_new (g_str_hash, g_str_equal); - memo_hash = g_hash_table_new (g_str_hash, g_str_equal); + setup_entry (desc_entry, info->edit_desc, info->desc_hash, xaccTransGetDescription (rowinfo->trans)); + setup_entry (notes_entry, info->edit_notes, info->notes_hash, xaccTransGetNotes (rowinfo->trans)); + setup_entry (memo_entry, info->edit_memo, info->memo_hash, xaccSplitGetMemo (rowinfo->split)); - for (GList *n = xaccAccountGetSplitList (xaccSplitGetAccount (info->split)); - n; n = n->next) - { - const Split *s = n->data; - const Transaction *t = xaccSplitGetParent (s); - const gchar *key; - - key = xaccTransGetDescription (t); - if (key && *key) - g_hash_table_insert (desc_hash, (gpointer)key, one); - - key = xaccTransGetNotes (t); - if (key && *key) - g_hash_table_insert (notes_hash, (gpointer)key, one); - - key = xaccSplitGetMemo (s); - if (key && *key) - g_hash_table_insert (memo_hash, (gpointer)key, one); - }; - - setup_entry (desc_entry, edit_desc, desc_hash, xaccTransGetDescription (info->trans)); - setup_entry (notes_entry, edit_notes, notes_hash, xaccTransGetNotes (info->trans)); - setup_entry (memo_entry, edit_memo, memo_hash, xaccSplitGetMemo (info->split)); - - g_hash_table_destroy (desc_hash); - g_hash_table_destroy (notes_hash); - g_hash_table_destroy (memo_hash); - - gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent)); + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (info->main_widget)); // run the dialog gtk_widget_show_all (dialog); @@ -1047,7 +1068,7 @@ gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info) GtkTreeModel *model; GList *selected_rows, *row_info_list; GtkTreeStore* store; - char *new_desc, *new_notes, *new_memo; + char *new_desc = NULL, *new_notes = NULL, *new_memo = NULL; g_return_if_fail (info != NULL); ENTER("assign_transfer_account_to_selection_cb"); @@ -1066,8 +1087,7 @@ gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info) row_info_list = gnc_g_list_map (selected_rows, (GncGMapFunc) row_get_info, info); - if (input_new_fields (info->main_widget, row_info_list->data, store, - info->edit_desc, info->edit_notes, info->edit_memo, + if (input_new_fields (info, row_info_list->data, &new_desc, &new_notes, &new_memo)) { for (GList *n = row_info_list; n; n = g_list_next (n)) @@ -1657,6 +1677,11 @@ gnc_gen_trans_list_new (GtkWidget *parent, info); // This ensure this dialog is closed when the session is closed. gnc_gui_component_set_session (info->id, gnc_get_current_session()); + + info->desc_hash = g_hash_table_new (g_str_hash, g_str_equal); + info->notes_hash = g_hash_table_new (g_str_hash, g_str_equal); + info->memo_hash = g_hash_table_new (g_str_hash, g_str_equal); + return info; } From 0d3520f674b6e83a6096301bca15c1f2b2b5d967 Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Sun, 17 Jul 2022 00:56:39 +0800 Subject: [PATCH 09/13] [import-main-matcher] editing a field will add onto hash --- gnucash/import-export/import-main-matcher.c | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c index 12ad9493e5..edf63684f9 100644 --- a/gnucash/import-export/import-main-matcher.c +++ b/gnucash/import-export/import-main-matcher.c @@ -89,6 +89,8 @@ struct _main_matcher_info GHashTable *desc_hash; GHashTable *notes_hash; GHashTable *memo_hash; + + GList *new_strings; }; enum downloaded_cols @@ -245,6 +247,8 @@ gnc_gen_trans_list_delete (GNCImportMainMatcher *info) g_hash_table_destroy (info->notes_hash); g_hash_table_destroy (info->memo_hash); + g_list_free_full (info->new_strings, (GDestroyNotify)g_free); + g_free (info); } @@ -1102,10 +1106,24 @@ gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info) DOWNLOADED_COL_DESCRIPTION_STYLE, style, -1); xaccTransSetDescription (row->trans, new_desc); + if (*new_desc && !g_hash_table_lookup (info->desc_hash, new_desc)) + { + char *new_string = g_strdup (new_desc); + info->new_strings = g_list_prepend (info->new_strings, new_string); + g_hash_table_insert (info->desc_hash, new_string, one); + } } if (info->edit_notes) + { xaccTransSetNotes (row->trans, new_notes); + if (*new_notes && !g_hash_table_lookup (info->notes_hash, new_notes)) + { + char *new_string = g_strdup (new_notes); + info->new_strings = g_list_prepend (info->new_strings, new_string); + g_hash_table_insert (info->notes_hash, new_string, one); + } + } if (info->edit_memo) { @@ -1116,6 +1134,12 @@ gnc_gen_trans_edit_fields (GtkMenuItem *menuitem, GNCImportMainMatcher *info) DOWNLOADED_COL_MEMO_STYLE, style, -1); xaccSplitSetMemo (row->split, new_memo); + if (*new_memo && !g_hash_table_lookup (info->memo_hash, new_memo)) + { + char *new_string = g_strdup (new_memo); + info->new_strings = g_list_prepend (info->new_strings, new_string); + g_hash_table_insert (info->memo_hash, new_string, one); + } } } g_free (new_desc); @@ -1682,6 +1706,7 @@ gnc_gen_trans_list_new (GtkWidget *parent, info->notes_hash = g_hash_table_new (g_str_hash, g_str_equal); info->memo_hash = g_hash_table_new (g_str_hash, g_str_equal); + info->new_strings = NULL; return info; } From 1ee87fa4835f4db160b07a2c3b120a655284058d Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Sun, 17 Jul 2022 23:12:13 +0800 Subject: [PATCH 10/13] [import-main-matcher] add mnemonic, and amend menuitem text --- gnucash/import-export/import-main-matcher.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c index edf63684f9..d42da0a4c2 100644 --- a/gnucash/import-export/import-main-matcher.c +++ b/gnucash/import-export/import-main-matcher.c @@ -1291,8 +1291,8 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, ENTER (""); menu = gtk_menu_new(); - menuitem = gtk_menu_item_new_with_label ( - _("Assign a transfer account to the selection.")); + menuitem = gtk_menu_item_new_with_mnemonic + (_("_Assign a transfer account to the selection")); g_signal_connect (menuitem, "activate", G_CALLBACK( gnc_gen_trans_assign_transfer_account_to_selection_cb), @@ -1338,7 +1338,7 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, rowinfo_free (rowinfo); } - menuitem = gtk_menu_item_new_with_label (_("Edit description, notes, memo.")); + menuitem = gtk_menu_item_new_with_mnemonic (_("_Edit description, notes, memo")); gtk_widget_set_sensitive (menuitem, info->edit_desc || info->edit_notes || info->edit_memo); g_signal_connect (menuitem, "activate", @@ -1346,7 +1346,7 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, info); gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); - menuitem = gtk_menu_item_new_with_label (_("Reset edits.")); + menuitem = gtk_menu_item_new_with_mnemonic (_("_Reset all edits")); gtk_widget_set_sensitive (menuitem, has_edits); g_signal_connect (menuitem, "activate", G_CALLBACK (gnc_gen_trans_reset_edits_cb), From a215664870a00ea11d1770f2a732b36d2ba21e2d Mon Sep 17 00:00:00 2001 From: Robert Fewell <14uBobIT@gmail.com> Date: Mon, 18 Jul 2022 23:27:28 +0800 Subject: [PATCH 11/13] [import-main-matcher] full substring search, add mnemonics --- gnucash/gtkbuilder/dialog-import.glade | 12 ++++-- gnucash/import-export/import-main-matcher.c | 48 ++++++++++++++++++--- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/gnucash/gtkbuilder/dialog-import.glade b/gnucash/gtkbuilder/dialog-import.glade index f7e3d001ed..0fda9707aa 100644 --- a/gnucash/gtkbuilder/dialog-import.glade +++ b/gnucash/gtkbuilder/dialog-import.glade @@ -927,7 +927,9 @@ True False end - Description + _Description + True + desc_entry 0 @@ -939,7 +941,9 @@ True False end - Notes + _Notes + True + notes_entry 0 @@ -951,7 +955,9 @@ True False end - Memo + _Memo + True + memo_entry 0 diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c index d42da0a4c2..b4392cb23b 100644 --- a/gnucash/import-export/import-main-matcher.c +++ b/gnucash/import-export/import-main-matcher.c @@ -986,13 +986,45 @@ static RowInfo * row_get_info (gpointer row, GNCImportMainMatcher *info) return retval; } +enum +{ + COMPLETION_LIST_ORIGINAL, + COMPLETION_LIST_COLLATED, + COMPLETION_LIST_NORMALIZED_FOLDED, +}; + static void populate_list (gpointer key, gpointer value, GtkListStore *list) { GtkTreeIter iter; - char *collated_key = g_utf8_collate_key (key, -1); + const char *original = key; + char *collated = g_utf8_collate_key (original, -1); + char *normalized = collated ? g_utf8_normalize (original, -1, G_NORMALIZE_ALL) : NULL; + char *normalized_folded = normalized ? g_utf8_casefold (normalized, -1) : NULL; gtk_list_store_append (list, &iter); - gtk_list_store_set (list, &iter, 0, key, 1, collated_key, -1); - g_free (collated_key); + gtk_list_store_set (list, &iter, + COMPLETION_LIST_ORIGINAL, original, + COMPLETION_LIST_COLLATED, collated, + COMPLETION_LIST_NORMALIZED_FOLDED, normalized_folded, + -1); + g_free (normalized_folded); + g_free (normalized); + g_free (collated); +} + +static gboolean +match_func (GtkEntryCompletion *completion, const char *entry_str, + GtkTreeIter *iter, gpointer user_data) +{ + GtkTreeModel *model = user_data; + gchar *existing_str = NULL; + gboolean ret = FALSE; + gtk_tree_model_get (model, iter, + COMPLETION_LIST_NORMALIZED_FOLDED, &existing_str, + -1); + if (existing_str && *existing_str && strstr (existing_str, entry_str)) + ret = TRUE; + g_free (existing_str); + return ret; } static void @@ -1007,16 +1039,20 @@ setup_entry (GtkWidget *entry, gboolean sensitive, GHashTable *hash, if (!sensitive) return; - list = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING); + list = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); g_hash_table_foreach (hash, (GHFunc)populate_list, list); if (!g_hash_table_lookup (hash, (gpointer)initial)) populate_list ((gpointer)initial, NULL, list); - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list), 1, + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (list), + COMPLETION_LIST_COLLATED, GTK_SORT_ASCENDING); completion = gtk_entry_completion_new (); gtk_entry_completion_set_model (completion, GTK_TREE_MODEL(list)); - gtk_entry_completion_set_text_column (completion, 0); + gtk_entry_completion_set_text_column (completion, COMPLETION_LIST_ORIGINAL); + gtk_entry_completion_set_match_func (completion, + (GtkEntryCompletionMatchFunc)match_func, + GTK_TREE_MODEL(list), NULL); gtk_entry_set_completion (GTK_ENTRY (entry), completion); } From c4e02e1b4b9cc1a4d109df05cca1be5fdd17caca Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Wed, 20 Jul 2022 08:13:02 +0800 Subject: [PATCH 12/13] [import-main-matcher] tidy destructors --- gnucash/import-export/import-main-matcher.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c index b4392cb23b..8145ef3f24 100644 --- a/gnucash/import-export/import-main-matcher.c +++ b/gnucash/import-export/import-main-matcher.c @@ -164,14 +164,6 @@ static gboolean query_tooltip_tree_view_cb (GtkWidget *widget, gint x, gint y, gpointer user_data); /* end local prototypes */ -static -gboolean delete_hash (gpointer key, gpointer value, gpointer user_data) -{ - // Value is a hash table that needs to be destroyed. - g_hash_table_destroy (value); - return TRUE; -} - static void update_all_balances (GNCImportMainMatcher *info) { @@ -239,10 +231,7 @@ gnc_gen_trans_list_delete (GNCImportMainMatcher *info) update_all_balances (info); gnc_import_PendingMatches_delete (info->pending_matches); - g_hash_table_foreach_remove (info->acct_id_hash, delete_hash, NULL); g_hash_table_destroy (info->acct_id_hash); - info->acct_id_hash = NULL; - g_hash_table_destroy (info->desc_hash); g_hash_table_destroy (info->notes_hash); g_hash_table_destroy (info->memo_hash); @@ -1593,7 +1582,8 @@ gnc_gen_trans_init_view (GNCImportMainMatcher *info, g_signal_connect (view, "popup-menu", G_CALLBACK(gnc_gen_trans_onPopupMenu_cb), info); - info->acct_id_hash = g_hash_table_new (g_direct_hash, g_direct_equal); + info->acct_id_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, + (GDestroyNotify)g_hash_table_destroy); } static void From 078a579ed4d8144afc6eb9e1fd4bfead8f918dd1 Mon Sep 17 00:00:00 2001 From: Christopher Lam Date: Wed, 20 Jul 2022 08:13:02 +0800 Subject: [PATCH 13/13] [import-main-matcher] attach menu to treeview another leak plugged --- gnucash/import-export/import-main-matcher.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gnucash/import-export/import-main-matcher.c b/gnucash/import-export/import-main-matcher.c index 8145ef3f24..8ad8f3465c 100644 --- a/gnucash/import-export/import-main-matcher.c +++ b/gnucash/import-export/import-main-matcher.c @@ -1378,6 +1378,8 @@ gnc_gen_trans_view_popup_menu (GtkTreeView *treeview, info); gtk_menu_shell_append (GTK_MENU_SHELL(menu), menuitem); + gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (treeview), NULL); + gtk_widget_show_all (menu); /* Note: event can be NULL here when called from view_onPopupMenu; */ gtk_menu_popup_at_pointer (GTK_MENU(menu), (GdkEvent*)event);