Merge branch 'import-map-editor'

This commit is contained in:
John Ralls 2016-01-11 12:40:10 -08:00
commit a3e59e557a
12 changed files with 1457 additions and 1 deletions

View File

@ -208,6 +208,7 @@ src/gnome/dialog-commodities.c
src/gnome/dialog-fincalc.c
src/gnome/dialog-find-transactions2.c
src/gnome/dialog-find-transactions.c
src/gnome/dialog-imap-editor.c
src/gnome/dialog-lot-viewer.c
src/gnome/dialog-new-user.c
src/gnome/dialog-price-edit-db.c
@ -248,6 +249,7 @@ src/gnome/gtkbuilder/assistant-loan.glade
src/gnome/gtkbuilder/assistant-stock-split.glade
src/gnome/gtkbuilder/dialog-commodities.glade
src/gnome/gtkbuilder/dialog-fincalc.glade
src/gnome/gtkbuilder/dialog-imap-editor.glade
src/gnome/gtkbuilder/dialog-lot-viewer.glade
src/gnome/gtkbuilder/dialog-new-user.glade
src/gnome/gtkbuilder/dialog-price.glade

View File

@ -5485,6 +5485,216 @@ gnc_account_imap_add_account_bayes (GncImportMatchMap *imap,
LEAVE(" ");
}
/*******************************************************************************/
static void
build_bayes_layer_two (const char *key, const GValue *value, gpointer user_data)
{
QofBook *book;
Account *root;
gchar *kvp_path;
gchar *count;
struct imap_info *imapInfo_node;
struct imap_info *imapInfo = (struct imap_info*)user_data;
// Get the book
book = qof_instance_get_book (imapInfo->source_account);
root = gnc_book_get_root_account (book);
PINFO("build_bayes_layer_two: account '%s', token_count: '%" G_GINT64_FORMAT "'",
(char*)key, g_value_get_int64(value));
count = g_strdup_printf ("%" G_GINT64_FORMAT, g_value_get_int64 (value));
kvp_path = g_strconcat (imapInfo->category_head, "/", key, NULL);
PINFO("build_bayes_layer_two: kvp_path is '%s'", kvp_path);
imapInfo_node = g_malloc(sizeof(*imapInfo_node));
imapInfo_node->source_account = imapInfo->source_account;
imapInfo_node->map_account = gnc_account_lookup_by_full_name (root, key);
imapInfo_node->full_category = g_strdup (kvp_path);
imapInfo_node->match_string = g_strdup (imapInfo->match_string);
imapInfo_node->category_head = g_strdup (imapInfo->category_head);
imapInfo_node->count = g_strdup (count);
imapInfo->list = g_list_append (imapInfo->list, imapInfo_node);
g_free (kvp_path);
g_free (count);
}
static void
build_bayes (const char *key, const GValue *value, gpointer user_data)
{
gchar *kvp_path;
struct imap_info *imapInfo = (struct imap_info*)user_data;
struct imap_info imapInfol2;
PINFO("build_bayes: match string '%s'", (char*)key);
if (G_VALUE_HOLDS (value, G_TYPE_STRING) && g_value_get_string (value) == NULL)
{
kvp_path = g_strdup_printf (IMAP_FRAME_BAYES "/%s", key);
if (qof_instance_has_slot (QOF_INSTANCE(imapInfo->source_account), kvp_path))
{
PINFO("build_bayes: kvp_path is '%s', key '%s'", kvp_path, key);
imapInfol2.source_account = imapInfo->source_account;
imapInfol2.match_string = g_strdup (key);
imapInfol2.category_head = g_strdup (kvp_path);
imapInfol2.list = imapInfo->list;
qof_instance_foreach_slot (QOF_INSTANCE(imapInfo->source_account), kvp_path,
build_bayes_layer_two, &imapInfol2);
imapInfo->list = imapInfol2.list;
g_free (imapInfol2.match_string);
g_free (imapInfol2.category_head);
}
g_free (kvp_path);
}
}
static void
build_non_bayes (const char *key, const GValue *value, gpointer user_data)
{
if (G_VALUE_HOLDS_BOXED (value))
{
QofBook *book;
GncGUID *guid = NULL;
gchar *kvp_path;
gchar *guid_string = NULL;
struct imap_info *imapInfo_node;
struct imap_info *imapInfo = (struct imap_info*)user_data;
// Get the book
book = qof_instance_get_book (imapInfo->source_account);
guid = (GncGUID*)g_value_get_boxed (value);
guid_string = guid_to_string (guid);
PINFO("build_non_bayes: account '%s', match account guid: '%s'",
(char*)key, guid_string);
kvp_path = g_strconcat (imapInfo->category_head, "/", key, NULL);
PINFO("build_non_bayes: kvp_path is '%s'", kvp_path);
imapInfo_node = g_malloc(sizeof(*imapInfo_node));
imapInfo_node->source_account = imapInfo->source_account;
imapInfo_node->map_account = xaccAccountLookup (guid, book);
imapInfo_node->full_category = g_strdup (kvp_path);
imapInfo_node->match_string = g_strdup (key);
imapInfo_node->category_head = g_strdup (imapInfo->category_head);
imapInfo_node->count = g_strdup (" ");
imapInfo->list = g_list_append (imapInfo->list, imapInfo_node);
g_free (kvp_path);
g_free (guid_string);
}
}
GList *
gnc_account_imap_get_info_bayes (Account *acc)
{
GList *list = NULL;
struct imap_info imapInfo;
imapInfo.source_account = acc;
imapInfo.list = list;
if (qof_instance_has_slot (QOF_INSTANCE(acc), IMAP_FRAME_BAYES))
qof_instance_foreach_slot (QOF_INSTANCE(acc), IMAP_FRAME_BAYES,
build_bayes, &imapInfo);
return imapInfo.list;
}
GList *
gnc_account_imap_get_info (Account *acc, const char *category)
{
GList *list = NULL;
gchar *category_head = NULL;
struct imap_info imapInfo;
imapInfo.source_account = acc;
imapInfo.list = list;
category_head = g_strdup_printf (IMAP_FRAME "/%s", category);
imapInfo.category_head = category_head;
if (qof_instance_has_slot (QOF_INSTANCE(acc), category_head))
qof_instance_foreach_slot (QOF_INSTANCE(acc), category_head,
build_non_bayes, &imapInfo);
g_free (category_head);
return imapInfo.list;
}
/*******************************************************************************/
gchar *
gnc_account_get_map_entry (Account *acc, const char *full_category)
{
GValue v = G_VALUE_INIT;
gchar *text = NULL;
gchar *kvp_path = g_strdup (full_category);
if (qof_instance_has_slot (QOF_INSTANCE(acc), kvp_path))
{
qof_instance_get_kvp (QOF_INSTANCE(acc), kvp_path, &v);
if (G_VALUE_HOLDS_STRING (&v))
{
gchar const *string;
string = g_value_get_string (&v);
text = g_strdup (string);
}
}
g_free (kvp_path);
return text;
}
void
gnc_account_delete_map_entry (Account *acc, char *full_category, gboolean empty)
{
gchar *kvp_path = g_strdup (full_category);
if ((acc != NULL) && qof_instance_has_slot (QOF_INSTANCE(acc), kvp_path))
{
xaccAccountBeginEdit (acc);
if (empty)
qof_instance_slot_delete_if_empty (QOF_INSTANCE(acc), kvp_path);
else
qof_instance_slot_delete (QOF_INSTANCE(acc), kvp_path);
PINFO("Account is '%s', path is '%s'", xaccAccountGetName (acc), kvp_path);
qof_instance_set_dirty (QOF_INSTANCE(acc));
xaccAccountCommitEdit (acc);
}
g_free (kvp_path);
g_free (full_category);
}
/* ================================================================ */
/* QofObject function implementation and registration */

View File

@ -1406,6 +1406,37 @@ Account* gnc_account_imap_find_account_bayes (GncImportMatchMap *imap, GList* to
void gnc_account_imap_add_account_bayes (GncImportMatchMap *imap, GList* tokens,
Account *acc);
struct imap_info
{
Account *source_account;
Account *map_account;
GList *list;
char *category_head;
char *full_category;
char *match_string;
char *count;
};
/** Returns a GList of structure imap_info of all Bayesian mappings for
* required Account
*/
GList *gnc_account_imap_get_info_bayes (Account *acc);
/** Returns a GList of structure imap_info of all Non Bayesian mappings for
* required Account
*/
GList *gnc_account_imap_get_info (Account *acc, const char *category);
/** Returns the text string pointed to by full_category for the Account, free
* the returned text
*/
gchar *gnc_account_get_map_entry (Account *acc, const char *full_category);
/** Delete the entry for Account pointed to by full_category, if empty is TRUE then use
* delete if empty, full_category is freed
*/
void gnc_account_delete_map_entry (Account *acc, char *full_category, gboolean empty);
/** @} */

View File

@ -32,6 +32,7 @@ libgnc_gnome_la_SOURCES = \
dialog-fincalc.c \
dialog-find-transactions.c \
dialog-find-transactions2.c \
dialog-imap-editor.c \
dialog-lot-viewer.c \
dialog-new-user.c \
dialog-price-editor.c \
@ -84,6 +85,7 @@ noinst_HEADERS = \
dialog-fincalc.h \
dialog-find-transactions.h \
dialog-find-transactions2.h \
dialog-imap-editor.h \
dialog-lot-viewer.h \
dialog-new-user.h \
dialog-print-check.h \

View File

@ -0,0 +1,777 @@
/********************************************************************\
* dialog-imap-editor.c -- Import Map Editor dialog *
* Copyright (C) 2015 Robert Fewell *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
\********************************************************************/
#include "config.h"
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include "dialog-imap-editor.h"
#include "dialog-utils.h"
#include "gnc-component-manager.h"
#include "gnc-session.h"
#include "gnc-ui-util.h"
#include "Account.h"
#define DIALOG_IMAP_CM_CLASS "dialog-imap-edit"
#define GNC_PREFS_GROUP "dialogs.imap-editor"
#define IMAP_FRAME_BAYES "import-map-bayes"
#define IMAP_FRAME "import-map"
#define IMAP_FRAME_DESC "desc"
#define IMAP_FRAME_MEMO "memo"
#define IMAP_FRAME_CSV "csv-account-map"
/** Enumeration for the tree-store */
enum GncImapColumn {SOURCE_FULL_ACC, SOURCE_ACCOUNT, BASED_ON, MATCH_STRING,
MAP_FULL_ACC, MAP_ACCOUNT, FULL_CATEGORY, COUNT, FILTER};
typedef enum
{
BAYES,
NBAYES,
ONLINE
}GncListType;
typedef struct
{
GtkWidget *dialog;
QofSession *session;
GtkWidget *view;
GncListType type;
GtkWidget *radio_bayes;
GtkWidget *radio_nbayes;
GtkWidget *radio_online;
GtkWidget *filter_button;
GtkWidget *filter_text_entry;
GtkWidget *filter_label;
GtkWidget *expand_button;
GtkWidget *collapse_button;
}ImapDialog;
/* This static indicates the debugging module that this .o belongs to. */
static QofLogModule log_module = GNC_MOD_GUI;
void gnc_imap_dialog_window_destroy_cb (GtkWidget *object, gpointer user_data);
void gnc_imap_dialog_close_cb (GtkDialog *dialog, gpointer user_data);
void gnc_imap_dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data);
static void get_account_info (ImapDialog *imap_dialog);
void
gnc_imap_dialog_window_destroy_cb (GtkWidget *object, gpointer user_data)
{
ImapDialog *imap_dialog = user_data;
ENTER(" ");
gnc_unregister_gui_component_by_data (DIALOG_IMAP_CM_CLASS, imap_dialog);
if (imap_dialog->dialog)
{
gtk_widget_destroy (imap_dialog->dialog);
imap_dialog->dialog = NULL;
}
g_free (imap_dialog);
LEAVE(" ");
}
void
gnc_imap_dialog_close_cb (GtkDialog *dialog, gpointer user_data)
{
ImapDialog *imap_dialog = user_data;
ENTER(" ");
gnc_close_gui_component_by_data (DIALOG_IMAP_CM_CLASS, imap_dialog);
LEAVE(" ");
}
static gboolean
are_you_sure (ImapDialog *imap_dialog)
{
GtkWidget *dialog;
gint response;
const char *title = _("Are you sure you want to delete the entries ?");
dialog = gtk_message_dialog_new (GTK_WINDOW (imap_dialog->dialog),
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_CANCEL,
"%s", title);
gtk_dialog_add_button (GTK_DIALOG(dialog), _("_Delete"), GTK_RESPONSE_ACCEPT);
gtk_widget_grab_focus (gtk_dialog_get_widget_for_response (GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT));
response = gtk_dialog_run (GTK_DIALOG(dialog));
gtk_widget_destroy (dialog);
if (response == GTK_RESPONSE_ACCEPT)
return TRUE;
else
return FALSE;
}
static void
delete_info_bayes (Account *source_account, gchar *match_string, gint depth)
{
gchar *full_category;
full_category = g_strdup_printf (IMAP_FRAME_BAYES "/%s", match_string);
gnc_account_delete_map_entry (source_account, full_category, TRUE);
full_category = g_strdup_printf (IMAP_FRAME_BAYES);
if (depth == 1) // top level, delete all below
gnc_account_delete_map_entry (source_account, full_category, FALSE);
else
gnc_account_delete_map_entry (source_account, full_category, TRUE);
}
static void
delete_info_nbayes (Account *source_account, gint depth)
{
gchar *full_category;
full_category = g_strdup_printf (IMAP_FRAME "/%s", IMAP_FRAME_DESC);
gnc_account_delete_map_entry (source_account, full_category, TRUE);
full_category = g_strdup_printf (IMAP_FRAME "/%s", IMAP_FRAME_MEMO);
gnc_account_delete_map_entry (source_account, full_category, TRUE);
full_category = g_strdup_printf (IMAP_FRAME "/%s", IMAP_FRAME_CSV);
gnc_account_delete_map_entry (source_account, full_category, TRUE);
full_category = g_strdup_printf (IMAP_FRAME);
if (depth == 1) // top level, delete all below
gnc_account_delete_map_entry (source_account, full_category, FALSE);
else
gnc_account_delete_map_entry (source_account, full_category, TRUE);
}
static void
delete_selected_row (GtkTreeModel *model, GtkTreeIter *iter, ImapDialog *imap_dialog)
{
Account *source_account = NULL;
gchar *full_source_account;
gchar *full_category;
gchar *match_string;
gtk_tree_model_get (model, iter, SOURCE_ACCOUNT, &source_account, SOURCE_FULL_ACC, &full_source_account,
FULL_CATEGORY, &full_category, MATCH_STRING, &match_string, -1);
PINFO("Account is '%s', Full Category is '%s', Match String is '%s'", full_source_account, full_category, match_string);
if (source_account != NULL)
{
GtkTreePath *tree_path;
gint depth;
// Get the level we are at in the tree-model
tree_path = gtk_tree_model_get_path (model, iter);
depth = gtk_tree_path_get_depth (tree_path);
gtk_tree_path_free (tree_path);
gnc_account_delete_map_entry (source_account, full_category, FALSE);
if (imap_dialog->type == BAYES)
delete_info_bayes (source_account, match_string, depth);
if (imap_dialog->type == NBAYES)
delete_info_nbayes (source_account, depth);
}
else
g_free (full_category);
g_free (match_string);
g_free (full_source_account);
}
static void
gnc_imap_dialog_delete (ImapDialog *imap_dialog)
{
GList *list, *row;
GtkTreeModel *model;
GtkTreeIter iter;
GtkTreeSelection *selection;
model = gtk_tree_view_get_model (GTK_TREE_VIEW(imap_dialog->view));
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(imap_dialog->view));
list = gtk_tree_selection_get_selected_rows (selection, &model);
// Make sure we have some rows selected
if (g_list_length (list) == 0)
return;
// Are we sure we want to delete the entries
if (are_you_sure (imap_dialog) == FALSE)
return;
// reverse list
list = g_list_reverse (list);
// Walk the list
for (row = g_list_first (list); row; row = g_list_next (row))
{
if (gtk_tree_model_get_iter (model, &iter, row->data))
delete_selected_row (model, &iter, imap_dialog);
}
g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
g_list_free (list);
get_account_info (imap_dialog);
}
void
gnc_imap_dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data)
{
ImapDialog *imap_dialog = user_data;
switch (response_id)
{
case GTK_RESPONSE_APPLY:
gnc_imap_dialog_delete (imap_dialog);
return;
case GTK_RESPONSE_CLOSE:
default:
gnc_close_gui_component_by_data (DIALOG_IMAP_CM_CLASS, imap_dialog);
return;
}
}
static gboolean
filter_test_and_move_next (GtkTreeModel *model, GtkTreeIter *iter, ImapDialog *imap_dialog)
{
GtkTreePath *tree_path;
gint depth;
gboolean valid;
const gchar *filter_text;
const gchar *match_string;
const gchar *map_full_acc;
// Read the row
gtk_tree_model_get (model, iter, MATCH_STRING, &match_string, MAP_FULL_ACC, &map_full_acc, -1);
filter_text = gtk_entry_get_text (GTK_ENTRY(imap_dialog->filter_text_entry));
// Get the level we are at in the tree-model
tree_path = gtk_tree_model_get_path (model, iter);
depth = gtk_tree_path_get_depth (tree_path);
// Reset filter to TRUE
gtk_tree_store_set (GTK_TREE_STORE(model), iter, FILTER, TRUE, -1);
// Check for a filter_text entry
if (g_strcmp0 (filter_text, "") != 0)
{
if (match_string != NULL) // Check for match_string is not NULL, valid line
{
if ((g_strrstr (match_string, filter_text) == NULL) &&
(g_strrstr (map_full_acc, filter_text) == NULL ))
gtk_tree_store_set (GTK_TREE_STORE(model), iter, FILTER, FALSE, -1);
else
gtk_tree_view_expand_to_path (GTK_TREE_VIEW(imap_dialog->view), tree_path);
}
}
// Select next entry based on path
if (depth == 1)
gtk_tree_path_down (tree_path);
else
{
gtk_tree_path_next (tree_path);
if (!gtk_tree_model_get_iter (model, iter, tree_path))
{
gtk_tree_path_prev (tree_path);
gtk_tree_path_up (tree_path);
gtk_tree_path_next (tree_path);
}
}
valid = gtk_tree_model_get_iter (model, iter, tree_path);
gtk_tree_path_free (tree_path);
return valid;
}
static void
filter_button_cb (GtkButton *button, ImapDialog *imap_dialog)
{
GtkTreeModel *model, *filter;
GtkTreeIter iter;
gboolean valid;
filter = gtk_tree_view_get_model (GTK_TREE_VIEW(imap_dialog->view));
model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter));
// Collapse all nodes
gtk_tree_view_collapse_all (GTK_TREE_VIEW(imap_dialog->view));
valid = gtk_tree_model_get_iter_first (model, &iter);
while (valid)
{
valid = filter_test_and_move_next (model, &iter, imap_dialog);
}
}
static void
expand_button_cb (GtkButton *button, ImapDialog *imap_dialog)
{
// Clear the filter
gtk_entry_set_text (GTK_ENTRY(imap_dialog->filter_text_entry), "");
filter_button_cb (button, imap_dialog);
gtk_tree_view_expand_all (GTK_TREE_VIEW(imap_dialog->view));
}
static void
collapse_button_cb (GtkButton *button, ImapDialog *imap_dialog)
{
// Clear the filter
gtk_entry_set_text (GTK_ENTRY(imap_dialog->filter_text_entry), "");
filter_button_cb (button, imap_dialog);
gtk_tree_view_collapse_all (GTK_TREE_VIEW(imap_dialog->view));
}
static void
list_type_selected_cb (GtkToggleButton* button, ImapDialog *imap_dialog)
{
GncListType type;
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(imap_dialog->radio_bayes)))
type = BAYES;
else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(imap_dialog->radio_nbayes)))
type = NBAYES;
else
type = ONLINE;
// Lets do this only on change of list type
if (type != imap_dialog->type)
{
imap_dialog->type = type;
get_account_info (imap_dialog);
}
}
static void
show_count_column (ImapDialog *imap_dialog, gboolean show)
{
GtkTreeViewColumn *tree_column;
// Show Count Column
tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW(imap_dialog->view), 4);
gtk_tree_view_column_set_visible (tree_column, show);
// Hide Based on Column
tree_column = gtk_tree_view_get_column (GTK_TREE_VIEW(imap_dialog->view), 1);
gtk_tree_view_column_set_visible (tree_column, !show);
gtk_tree_view_columns_autosize (GTK_TREE_VIEW(imap_dialog->view));
}
static void
add_to_store (GtkTreeModel *model, GtkTreeIter *iter, const gchar *text, gpointer user_data)
{
gchar *fullname = NULL;
gchar *map_fullname = NULL;
struct imap_info *imapInfo = (struct imap_info*)user_data;
fullname = gnc_account_get_full_name (imapInfo->source_account);
// Do we have a valid map account
if (imapInfo->map_account == NULL)
map_fullname = g_strdup (_("Map Account NOT found"));
else
map_fullname = gnc_account_get_full_name (imapInfo->map_account);
PINFO("Add to Store: Source Acc '%s', Match '%s', Map Acc '%s'", fullname, imapInfo->match_string, map_fullname);
gtk_tree_store_set (GTK_TREE_STORE(model), iter,
SOURCE_FULL_ACC, fullname, SOURCE_ACCOUNT, imapInfo->source_account,
BASED_ON, text,
MATCH_STRING, imapInfo->match_string,
MAP_FULL_ACC, map_fullname, MAP_ACCOUNT, imapInfo->map_account,
FULL_CATEGORY, imapInfo->full_category, COUNT, imapInfo->count,
FILTER, TRUE, -1);
g_free (fullname);
g_free (map_fullname);
}
static void
get_imap_info (Account *acc, const gchar *category, GtkTreeModel *model, const gchar *text)
{
GtkTreeIter toplevel, child;
GList *imap_list, *node;
gchar *acc_name = NULL;
acc_name = gnc_account_get_full_name (acc);
PINFO("Source Acc '%s', Based on '%s', Path Head '%s'", acc_name, text, category);
if (category == NULL) // For Bayesian, category is NULL
imap_list = gnc_account_imap_get_info_bayes (acc);
else
imap_list = gnc_account_imap_get_info (acc, category);
if (g_list_length (imap_list) > 0)
{
PINFO("List length is %d", g_list_length (imap_list));
// Add top level entry of Source full Account and Based on.
gtk_tree_store_append (GTK_TREE_STORE(model), &toplevel, NULL);
gtk_tree_store_set (GTK_TREE_STORE(model), &toplevel,
SOURCE_ACCOUNT, acc, SOURCE_FULL_ACC, acc_name, BASED_ON, text, FILTER, TRUE, -1);
for (node = imap_list; node; node = g_list_next (node))
{
struct imap_info *imapInfo = node->data;
// First add a child entry and pass iter to add_to_store
gtk_tree_store_append (GTK_TREE_STORE(model), &child, &toplevel);
add_to_store (model, &child, text, imapInfo);
// Free the members and structure
g_free (imapInfo->category_head);
g_free (imapInfo->full_category);
g_free (imapInfo->match_string);
g_free (imapInfo->count);
g_free (imapInfo);
}
}
g_free (acc_name);
g_list_free (imap_list); // Free the List
}
static void
show_first_row (ImapDialog *imap_dialog)
{
GtkTreeModel *model, *filter;
GtkTreeIter iter;
filter = gtk_tree_view_get_model (GTK_TREE_VIEW(imap_dialog->view));
model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter));
// See if there are any entries
if (gtk_tree_model_get_iter_first (model, &iter))
{
GtkTreePath *path;
path = gtk_tree_path_new_first (); // Set Path to first entry
gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(imap_dialog->view), path, NULL, TRUE, 0.0, 0.0);
gtk_tree_path_free (path);
}
}
static void
get_account_info_bayes (GList *accts, GtkTreeModel *model)
{
GList *ptr;
/* Go through list of accounts */
for (ptr = accts; ptr; ptr = g_list_next (ptr))
{
Account *acc = ptr->data;
get_imap_info (acc, NULL, model, _("Bayesian"));
}
}
static void
get_account_info_nbayes (GList *accts, GtkTreeModel *model)
{
GList *ptr;
/* Go through list of accounts */
for (ptr = accts; ptr; ptr = g_list_next (ptr))
{
Account *acc = ptr->data;
// Description
get_imap_info (acc, IMAP_FRAME_DESC, model, _("Description Field"));
// Memo
get_imap_info (acc, IMAP_FRAME_MEMO, model, _("Memo Field"));
// CSV Account Map
get_imap_info (acc, IMAP_FRAME_CSV, model, _("CSV Account Map"));
}
}
static void
get_account_info_online (GList *accts, GtkTreeModel *model)
{
GList *ptr;
GtkTreeIter toplevel;
struct imap_info imapInfo;
/* Go through list of accounts */
for (ptr = accts; ptr; ptr = g_list_next (ptr))
{
gchar *text = NULL;
Account *acc = ptr->data;
// Save source account
imapInfo.source_account = acc;
imapInfo.full_category = "online_id";
text = gnc_account_get_map_entry (acc, imapInfo.full_category);
if (text != NULL)
{
if (g_strcmp0 (text, "") == 0)
imapInfo.map_account = NULL;
else
imapInfo.map_account = imapInfo.source_account;
imapInfo.match_string = text;
imapInfo.count = " ";
// Add top level entry and pass iter to add_to_store
gtk_tree_store_append (GTK_TREE_STORE(model), &toplevel, NULL);
add_to_store (model, &toplevel, _("Online Id"), &imapInfo);
}
g_free (text);
}
}
static void
show_filter_option (ImapDialog *imap_dialog, gboolean show)
{
if (show)
{
gtk_widget_show (imap_dialog->filter_text_entry);
gtk_widget_show (imap_dialog->filter_button);
gtk_widget_show (imap_dialog->filter_label);
gtk_widget_show (imap_dialog->expand_button);
gtk_widget_show (imap_dialog->collapse_button);
}
else
{
gtk_widget_hide (imap_dialog->filter_text_entry);
gtk_widget_hide (imap_dialog->filter_button);
gtk_widget_hide (imap_dialog->filter_label);
gtk_widget_hide (imap_dialog->expand_button);
gtk_widget_hide (imap_dialog->collapse_button);
}
}
static void
get_account_info (ImapDialog *imap_dialog)
{
Account *root;
GList *accts;
GtkTreeModel *model, *filter;
GtkTreeIter iter;
/* Get list of Accounts */
root = gnc_book_get_root_account (gnc_get_current_book());
accts = gnc_account_get_descendants_sorted (root);
filter = gtk_tree_view_get_model (GTK_TREE_VIEW(imap_dialog->view));
model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(filter));
// Clear the tree store
gtk_tree_store_clear (GTK_TREE_STORE(model));
// Clear the filter
gtk_entry_set_text (GTK_ENTRY(imap_dialog->filter_text_entry), "");
// Hide Count Column
show_count_column (imap_dialog, FALSE);
// Show Filter Option
show_filter_option (imap_dialog, TRUE);
if (imap_dialog->type == BAYES)
{
get_account_info_bayes (accts, model);
// Show Count Column
show_count_column (imap_dialog, TRUE);
}
else if (imap_dialog->type == NBAYES)
get_account_info_nbayes (accts, model);
else if (imap_dialog->type == ONLINE)
{
// Hide Filter Option
show_filter_option (imap_dialog, FALSE);
get_account_info_online (accts, model);
}
// if there are any entries, show first row
show_first_row (imap_dialog);
g_list_free (accts);
}
static void
gnc_imap_dialog_create (GtkWidget *parent, ImapDialog *imap_dialog)
{
GtkWidget *dialog;
GtkBuilder *builder;
GtkTreeModel *filter;
GtkTreeSelection *selection;
ENTER(" ");
builder = gtk_builder_new();
gnc_builder_add_from_file (builder, "dialog-imap-editor.glade", "tree-store");
gnc_builder_add_from_file (builder, "dialog-imap-editor.glade", "treemodelfilter");
gnc_builder_add_from_file (builder, "dialog-imap-editor.glade", "Import Map Dialog");
dialog = GTK_WIDGET(gtk_builder_get_object (builder, "Import Map Dialog"));
imap_dialog->dialog = dialog;
imap_dialog->session = gnc_get_current_session();
imap_dialog->type = BAYES;
/* parent */
if (parent != NULL)
gtk_window_set_transient_for (GTK_WINDOW(dialog), GTK_WINDOW(parent));
/* Connect the radio buttons...*/
imap_dialog->radio_bayes = GTK_WIDGET(gtk_builder_get_object (builder, "radio-bayes"));
imap_dialog->radio_nbayes = GTK_WIDGET(gtk_builder_get_object (builder, "radio-nbayes"));
imap_dialog->radio_online = GTK_WIDGET(gtk_builder_get_object (builder, "radio-online"));
g_signal_connect (imap_dialog->radio_bayes, "toggled",
G_CALLBACK(list_type_selected_cb), (gpointer)imap_dialog);
g_signal_connect (imap_dialog->radio_nbayes, "toggled",
G_CALLBACK(list_type_selected_cb), (gpointer)imap_dialog);
imap_dialog->filter_text_entry = GTK_WIDGET(gtk_builder_get_object (builder, "filter-text-entry"));
imap_dialog->filter_label = GTK_WIDGET(gtk_builder_get_object (builder, "filter-label"));
imap_dialog->filter_button = GTK_WIDGET(gtk_builder_get_object (builder, "filter-button"));
g_signal_connect (imap_dialog->filter_button, "clicked",
G_CALLBACK(filter_button_cb), (gpointer)imap_dialog);
imap_dialog->expand_button = GTK_WIDGET(gtk_builder_get_object (builder, "expand-button"));
g_signal_connect (imap_dialog->expand_button, "clicked",
G_CALLBACK(expand_button_cb), (gpointer)imap_dialog);
imap_dialog->collapse_button = GTK_WIDGET(gtk_builder_get_object (builder, "collapse-button"));
g_signal_connect (imap_dialog->collapse_button, "clicked",
G_CALLBACK(collapse_button_cb), (gpointer)imap_dialog);
imap_dialog->view = GTK_WIDGET(gtk_builder_get_object (builder, "treeview"));
// Set filter column
filter = gtk_tree_view_get_model (GTK_TREE_VIEW(imap_dialog->view));
gtk_tree_model_filter_set_visible_column (GTK_TREE_MODEL_FILTER(filter), FILTER);
/* Enable alternative line colors */
gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(imap_dialog->view), TRUE);
/* default to 'close' button */
gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_CLOSE);
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(imap_dialog->view));
gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, imap_dialog);
g_object_unref (G_OBJECT(builder));
gnc_restore_window_size (GNC_PREFS_GROUP, GTK_WINDOW(imap_dialog->dialog));
get_account_info (imap_dialog);
LEAVE(" ");
}
static void
close_handler (gpointer user_data)
{
ImapDialog *imap_dialog = user_data;
ENTER(" ");
gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(imap_dialog->dialog));
gtk_widget_destroy (GTK_WIDGET(imap_dialog->dialog));
LEAVE(" ");
}
static void
refresh_handler (GHashTable *changes, gpointer user_data)
{
ENTER(" ");
LEAVE(" ");
}
static gboolean
show_handler (const char *klass, gint component_id,
gpointer user_data, gpointer iter_data)
{
ImapDialog *imap_dialog = user_data;
ENTER(" ");
if (!imap_dialog)
{
LEAVE("No data strucure");
return(FALSE);
}
gtk_window_present (GTK_WINDOW(imap_dialog->dialog));
LEAVE(" ");
return(TRUE);
}
/********************************************************************\
* gnc_imap_dialog *
* opens a window showing Bayesian and Non-Bayesian information *
* *
* Args: parent - the parent of the window to be created *
* Return: nothing *
\********************************************************************/
void
gnc_imap_dialog (GtkWidget *parent)
{
ImapDialog *imap_dialog;
gint component_id;
ENTER(" ");
if (gnc_forall_gui_components (DIALOG_IMAP_CM_CLASS, show_handler, NULL))
{
LEAVE("Existing dialog raised");
return;
}
imap_dialog = g_new0 (ImapDialog, 1);
gnc_imap_dialog_create (parent, imap_dialog);
component_id = gnc_register_gui_component (DIALOG_IMAP_CM_CLASS,
refresh_handler, close_handler,
imap_dialog);
gnc_gui_component_set_session (component_id, imap_dialog->session);
gtk_widget_show (imap_dialog->dialog);
LEAVE(" ");
}

View File

@ -0,0 +1,28 @@
/********************************************************************\
* dialog-imap-editor.h -- Import Map Editor dialog *
* Copyright (C) 2015 Robert Fewell *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
\********************************************************************/
#ifndef DIALOG_IMAP_EDITOR_H
#define DIALOG_IMAP_EDITOR_H
void gnc_imap_dialog (GtkWidget *parent);
#endif

View File

@ -44,6 +44,7 @@
#include "dialog-fincalc.h"
#include "dialog-find-transactions.h"
#include "dialog-find-transactions2.h"
#include "dialog-imap-editor.h"
#include "dialog-sx-since-last-run.h"
#include "dialog-totd.h"
#include "assistant-acct-period.h"
@ -88,6 +89,7 @@ static void gnc_main_window_cmd_tools_financial_calculator (GtkAction *action, G
static void gnc_main_window_cmd_tools_close_book (GtkAction *action, GncMainWindowActionData *data);
static void gnc_main_window_cmd_tools_find_transactions (GtkAction *action, GncMainWindowActionData *data);
static void gnc_main_window_cmd_tools_price_editor (GtkAction *action, GncMainWindowActionData *data);
static void gnc_main_window_cmd_tools_imap_editor (GtkAction *action, GncMainWindowActionData *data);
static void gnc_main_window_cmd_tools_commodity_editor (GtkAction *action, GncMainWindowActionData *data);
static void gnc_main_window_cmd_help_totd (GtkAction *action, GncMainWindowActionData *data);
@ -201,6 +203,11 @@ static GtkActionEntry gnc_plugin_actions [] =
N_("Close the Book at the end of the Period"),
G_CALLBACK (gnc_main_window_cmd_tools_close_book)
},
{
"ToolsImapEditorAction", NULL, N_("_Import Map Editor"), NULL,
N_("View and Delete Bayesian and Non Bayesian information"),
G_CALLBACK (gnc_main_window_cmd_tools_imap_editor)
},
/* Help menu */
@ -602,6 +609,14 @@ gnc_main_window_cmd_actions_close_books (GtkAction *action, GncMainWindowActionD
}
#endif /* CLOSE_BOOKS_ACTUALLY_WORKS */
static void
gnc_main_window_cmd_tools_imap_editor (GtkAction *action, GncMainWindowActionData *data)
{
gnc_set_busy_cursor(NULL, TRUE);
gnc_imap_dialog (NULL);
gnc_unset_busy_cursor(NULL);
}
static void
gnc_main_window_cmd_tools_price_editor (GtkAction *action, GncMainWindowActionData *data)
{

View File

@ -1,6 +1,7 @@
<schemalist gettext-domain="@GETTEXT_PACKAGE@">
<schema id="org.gnucash.dialogs" path="/org/gnucash/dialogs/">
<child name="account" schema="org.gnucash.dialogs.account"/>
<child name="imap-editor" schema="org.gnucash.dialogs.imap-editor"/>
<child name="preferences" schema="org.gnucash.dialogs.preferences"/>
<child name="price-editor" schema="org.gnucash.dialogs.price-editor"/>
<child name="pricedb-editor" schema="org.gnucash.dialogs.pricedb-editor"/>
@ -30,6 +31,16 @@
</key>
</schema>
<schema id="org.gnucash.dialogs.imap-editor" path="/org/gnucash/dialogs/imap-editor/">
<key type="(iiii)" name="last-geometry">
<default>(-1,-1,-1,-1)</default>
<summary>Last window position and size</summary>
<description>This setting describes the size and position of the window when it was last closed.
The numbers are the X and Y coordinates of the top left corner of the window
followed by the width and height of the window.</description>
</key>
</schema>
<schema id="org.gnucash.dialogs.preferences" path="/org/gnucash/dialogs/preferences/">
<key name="last-geometry" type="(iiii)">
<default>(-1,-1,-1,-1)</default>

View File

@ -5,6 +5,7 @@ gtkbuilder_DATA = \
assistant-loan.glade \
assistant-stock-split.glade \
dialog-commodities.glade \
dialog-imap-editor.glade \
dialog-fincalc.glade \
dialog-lot-viewer.glade \
dialog-new-user.glade \

View File

@ -0,0 +1,370 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkTreeStore" id="tree-store">
<columns>
<!-- column-name source_account_name -->
<column type="gchararray"/>
<!-- column-name source_account -->
<column type="gpointer"/>
<!-- column-name based_on -->
<column type="gchararray"/>
<!-- column-name match_string -->
<column type="gchararray"/>
<!-- column-name map_account_name -->
<column type="gchararray"/>
<!-- column-name map_account -->
<column type="gpointer"/>
<!-- column-name full_category -->
<column type="gchararray"/>
<!-- column-name count -->
<column type="gchararray"/>
<!-- column-name filter -->
<column type="gboolean"/>
</columns>
</object>
<object class="GtkTreeModelFilter" id="treemodelfilter">
<property name="child_model">tree-store</property>
</object>
<object class="GtkDialog" id="Import Map Dialog">
<property name="can_focus">False</property>
<property name="border_width">6</property>
<property name="title" translatable="yes">Import Map Editor</property>
<property name="modal">True</property>
<property name="default_width">600</property>
<property name="default_height">400</property>
<property name="type_hint">dialog</property>
<signal name="destroy" handler="gnc_imap_dialog_window_destroy_cb" swapped="no"/>
<signal name="response" handler="gnc_imap_dialog_response_cb" swapped="no"/>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox2">
<property name="can_focus">False</property>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area1">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="delete_button">
<property name="label">gtk-delete</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="close_button">
<property name="label">gtk-close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">What type of information to display?</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="border_width">5</property>
<child>
<object class="GtkRadioButton" id="radio-bayes">
<property name="label" translatable="yes">Bayesian</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio-nbayes">
<property name="label" translatable="yes">Non-Bayesian</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">radio-bayes</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="radio-online">
<property name="label" translatable="yes">Online ID</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">radio-bayes</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">treemodelfilter</property>
<child>
<object class="GtkTreeViewColumn" id="source_account_name">
<property name="resizable">True</property>
<property name="title" translatable="yes">Source Account Name</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext3"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="based_on">
<property name="resizable">True</property>
<property name="title" translatable="yes">Based On</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext4">
<property name="xpad">10</property>
</object>
<attributes>
<attribute name="text">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="match">
<property name="resizable">True</property>
<property name="title" translatable="yes">Match String</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1">
<property name="xpad">10</property>
</object>
<attributes>
<attribute name="text">3</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="map_account_name">
<property name="resizable">True</property>
<property name="title" translatable="yes">Mapped to Account Name</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2">
<property name="xpad">10</property>
</object>
<attributes>
<attribute name="text">4</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="count">
<property name="resizable">True</property>
<property name="title" translatable="yes">Count of Match String Usage</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext5">
<property name="xpad">10</property>
<property name="alignment">center</property>
</object>
<attributes>
<attribute name="text">7</attribute>
</attributes>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="filter-label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ypad">5</property>
<property name="label" translatable="yes">Case sensative filtering is available on 'Match String' and 'Mapped to Account Name'.</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="filter-text-entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="filter-button">
<property name="label" translatable="yes">_Filter</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="expand-button">
<property name="label" translatable="yes">_Expand All</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="collapse-button">
<property name="label" translatable="yes">_Collapse All</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ypad">5</property>
<property name="label" translatable="yes">Multiple rows can be selected and then deleted by pressing the delete button...</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">6</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-10">delete_button</action-widget>
<action-widget response="-6">close_button</action-widget>
</action-widgets>
</object>
</interface>

View File

@ -56,6 +56,7 @@
<menuitem name="ToolsCommodityEditor" action="ToolsCommodityEditorAction"/>
<menuitem name="ToolsFinancialCalculator" action="ToolsFinancialCalculatorAction"/>
<menuitem name="ToolsBookClose" action="ToolsBookCloseAction"/>
<menuitem name="ToolsImapEditor" action="ToolsImapEditorAction"/>
</placeholder>
</menu>

View File

@ -1280,8 +1280,16 @@ struct wrap_param
static void
wrap_gvalue_function (const char* key, KvpValue *val, gpointer data)
{
GValue *gv;
auto param = static_cast<wrap_param*>(data);
GValue *gv = gvalue_from_kvp_value(val);
if (val->get_type() != KvpValue::Type::FRAME)
gv = gvalue_from_kvp_value(val);
else
{
gv = g_slice_new0 (GValue);
g_value_init (gv, G_TYPE_STRING);
g_value_set_string (gv, nullptr);
}
param->proc(key, gv, param->user_data);
g_slice_free (GValue, gv);
}