[Gnucash-changes] New files for Chris Shoemaker's rewrite of
budgeting functionality.
David Hampton
hampton at cvs.gnucash.org
Thu Oct 27 20:40:44 EDT 2005
Log Message:
-----------
New files for Chris Shoemaker's rewrite of budgeting functionality.
Tags:
----
gnucash-gnome2-dev
Added Files:
-----------
gnucash/src/backend/file:
gnc-budget-xml-v2.c
gnc-recurrence-xml-v2.c
gnucash/src/gnome:
gnc-plugin-budget.c
gnc-plugin-budget.h
gnc-plugin-page-budget.c
gnc-plugin-page-budget.h
gncmod-budget.c
gnucash/src/gnome/ui:
gnc-plugin-budget-ui.xml
gnc-plugin-page-budget-ui.xml
gnucash/src/gnome-utils:
gnc-dialog.c
gnc-dialog.h
gnc-recurrence.c
gnc-recurrence.h
gnc-tree-model-budget.c
gnc-tree-model-budget.h
gnucash/src/gnome-utils/test:
test-gnc-dialog.c
test-gnc-recurrence.c
gnucash/src/report/standard-reports:
budget.scm
Revision Data
-------------
--- /dev/null
+++ src/backend/file/gnc-recurrence-xml-v2.c
@@ -0,0 +1,128 @@
+/*
+ * gnc-recurrence-xml-v2.c -- xml routines for Recurrence
+ *
+ * Copyright (C) 2005 Chris Shoemaker <c.shoemaker at cox.net>
+ *
+ * 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 at gnu.org
+ */
+
+
+#include "config.h"
+
+#include <glib.h>
+#include <string.h>
+
+#include "gnc-xml.h"
+#include "gnc-xml-helper.h"
+#include "gnc-engine-util.h"
+#include "gnc-trace.h"
+
+#include "sixtp.h"
+#include "sixtp-utils.h"
+#include "sixtp-parsers.h"
+#include "sixtp-utils.h"
+#include "sixtp-dom-parsers.h"
+#include "sixtp-dom-generators.h"
+#include "io-gncxml-v2.h"
+#include "Recurrence.h"
+
+static QofLogModule log_module = GNC_MOD_IO;
+
+const gchar *recurrence_version_string = "1.0.0";
+#define recurrence_root "gnc:recurrence"
+#define recurrence_mult "recurrence:mult"
+#define recurrence_period_type "recurrence:period_type"
+#define recurrence_start "recurrence:start"
+
+//TODO: I think three of these functions rightly belong in Recurrence.c.
+
+static gboolean
+recurrence_period_type_handler(xmlNodePtr node, gpointer d)
+{
+ PeriodType pt;
+ char *nodeTxt;
+
+ nodeTxt = dom_tree_to_text(node);
+ g_return_val_if_fail(nodeTxt, FALSE);
+ pt = recurrencePeriodTypeFromString(nodeTxt);
+ ((Recurrence *) d)->ptype = pt;
+ g_free(nodeTxt);
+ return (pt != -1);
+}
+
+static gboolean
+recurrence_start_date_handler(xmlNodePtr node, gpointer r)
+{
+ GDate *d;
+
+ d = dom_tree_to_gdate(node);
+ g_return_val_if_fail(d, FALSE);
+ g_return_val_if_fail(g_date_valid(d), FALSE);
+ ((Recurrence *) r)->start = *d;
+ g_date_free(d);
+ return TRUE;
+}
+
+static gboolean
+recurrence_mult_handler(xmlNodePtr node, gpointer r)
+{
+ return dom_tree_to_guint16(node, &((Recurrence *)r)->mult);
+}
+
+static struct dom_tree_handler recurrence_dom_handlers[] = {
+ { recurrence_mult, recurrence_mult_handler, 1, 0 },
+ { recurrence_period_type, recurrence_period_type_handler, 1, 0 },
+ { recurrence_start, recurrence_start_date_handler, 1, 0 },
+ { NULL, NULL, 0, 0 }
+};
+
+Recurrence *
+dom_tree_to_recurrence(xmlNodePtr node)
+{
+ gboolean successful;
+ Recurrence *r;
+
+ r = g_new(Recurrence, 1);
+ successful = dom_tree_generic_parse (node, recurrence_dom_handlers, r);
+ if (!successful) {
+ PERR ("failed to parse recurrence node");
+ xmlElemDump(stdout, NULL, node);
+ g_free(r);
+ r = NULL;
+ }
+ return r;
+}
+
+xmlNodePtr
+recurrence_to_dom_tree(const gchar *tag, const Recurrence *r)
+{
+ xmlNodePtr n;
+ PeriodType pt;
+ GDate d;
+
+ n = xmlNewNode(NULL, tag);
+ xmlSetProp(n, "version", recurrence_version_string );
+ xmlAddChild(n, guint_to_dom_tree(recurrence_mult,
+ recurrenceGetMultiplier(r)));
+ pt = recurrenceGetPeriodType(r);
+ xmlAddChild(n, text_to_dom_tree(recurrence_period_type,
+ recurrencePeriodTypeToString(pt)));
+ d = recurrenceGetDate(r);
+ xmlAddChild(n, gdate_to_dom_tree(recurrence_start, &d));
+ return n;
+}
--- /dev/null
+++ src/backend/file/gnc-budget-xml-v2.c
@@ -0,0 +1,226 @@
+/*
+ * gnc-budget-xml-v2.c -- budget xml i/o implementation
+ *
+ * Copyright (C) 2005 Chris Shoemaker <c.shoemaker at cox.net>
+ *
+ * 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 at gnu.org
+ */
+
+
+#include "config.h"
+
+#include <glib.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "gnc-xml-helper.h"
+#include "sixtp.h"
+#include "sixtp-utils.h"
+#include "sixtp-parsers.h"
+#include "sixtp-utils.h"
+#include "sixtp-dom-parsers.h"
+#include "sixtp-dom-generators.h"
+
+#include "gnc-xml.h"
+#include "io-gncxml-gen.h"
+#include "io-gncxml-v2.h"
+
+static QofLogModule log_module = GNC_MOD_IO;
+
+const gchar *budget_version_string = "2.0.0";
+
+/* ids */
+#define gnc_budget_string "gnc:budget"
+#define bgt_id_string "bgt:id"
+#define bgt_name_string "bgt:name"
+#define bgt_description_string "bgt:description"
+#define bgt_num_periods_string "bgt:num-periods"
+#define bgt_recurrence_string "bgt:recurrence"
+#define bgt_slots_string "bgt:slots"
+
+xmlNodePtr
+gnc_budget_dom_tree_create(GncBudget *bgt)
+{
+ xmlNodePtr ret;
+ KvpFrame *kf;
+
+ ENTER ("(budget=%p)", bgt);
+
+ ret = xmlNewNode(NULL, gnc_budget_string);
+ xmlSetProp(ret, "version", budget_version_string);
+
+ /* field: GUID */
+ xmlAddChild(ret, guid_to_dom_tree(bgt_id_string,
+ gnc_budget_get_guid(bgt)));
+ /* field: char* name */
+ xmlAddChild(ret, text_to_dom_tree(bgt_name_string,
+ gnc_budget_get_name(bgt)));
+ /* field: char* description */
+ xmlAddChild(ret, text_to_dom_tree(bgt_description_string,
+ gnc_budget_get_description(bgt)));
+ /* field: guint num_periods */
+ xmlAddChild(ret, guint_to_dom_tree(bgt_num_periods_string,
+ gnc_budget_get_num_periods(bgt)));
+ /* field: Recurrence* */
+ xmlAddChild(ret, recurrence_to_dom_tree(bgt_recurrence_string,
+ gnc_budget_get_recurrence(bgt)));
+ /* slots */
+ kf = qof_instance_get_slots(QOF_INSTANCE(bgt));
+ if (kf) {
+ xmlNodePtr kvpnode = kvp_frame_to_dom_tree(bgt_slots_string, kf);
+ if (kvpnode)
+ xmlAddChild(ret, kvpnode);
+ }
+
+ LEAVE (" ");
+ return ret;
+}
+
+/***********************************************************************/
+static inline gboolean
+set_string(xmlNodePtr node, GncBudget* bgt,
+ void (*func)(GncBudget *bgt, const gchar *txt))
+{
+ gchar* txt = dom_tree_to_text(node);
+ g_return_val_if_fail(txt, FALSE);
+
+ func(bgt, txt);
+ g_free(txt);
+ return TRUE;
+}
+
+static gboolean
+budget_id_handler (xmlNodePtr node, gpointer bgt)
+{
+ GUID *guid;
+
+ guid = dom_tree_to_guid(node);
+ g_return_val_if_fail(guid, FALSE);
+ qof_entity_set_guid(QOF_ENTITY(bgt), guid);
+ g_free(guid);
+ return TRUE;
+}
+
+static gboolean
+budget_name_handler (xmlNodePtr node, gpointer bgt)
+{
+ return set_string(node, GNC_BUDGET(bgt), gnc_budget_set_name);
+}
+
+static gboolean
+budget_description_handler (xmlNodePtr node, gpointer bgt)
+{
+ return set_string(node, GNC_BUDGET(bgt), gnc_budget_set_description);
+}
+
+static gboolean
+budget_num_periods_handler (xmlNodePtr node, gpointer bgt)
+{
+ guint num_periods;
+
+ if (dom_tree_to_guint(node, &num_periods)) {
+ gnc_budget_set_num_periods(GNC_BUDGET(bgt), num_periods);
+ return TRUE;
+ } else
+ return FALSE;
+}
+
+static gboolean
+budget_recurrence_handler (xmlNodePtr node, gpointer bgt)
+{
+ Recurrence *r;
+
+ if ((r = dom_tree_to_recurrence(node)) == NULL)
+ return FALSE;
+
+ gnc_budget_set_recurrence(GNC_BUDGET(bgt), r);
+ g_free(r);
+ return TRUE;
+}
+
+static gboolean
+budget_slots_handler (xmlNodePtr node, gpointer bgt)
+{
+ return dom_tree_to_kvp_frame_given(
+ node, qof_instance_get_slots(QOF_INSTANCE(bgt)));
+}
+
+static struct dom_tree_handler budget_handlers[] = {
+ { bgt_id_string, budget_id_handler, 1, 0 },
+ { bgt_name_string, budget_name_handler, 0, 0 },
+ { bgt_description_string, budget_description_handler, 0, 0 },
+ { bgt_num_periods_string, budget_num_periods_handler, 1, 0 },
+ { bgt_recurrence_string, budget_recurrence_handler, 1, 0 },
+ { bgt_slots_string, budget_slots_handler, 0, 0},
+ { NULL, 0, 0, 0 }
+};
+
+static gboolean
+gnc_budget_end_handler(gpointer data_for_children,
+ GSList* data_from_children, GSList* sibling_data,
+ gpointer parent_data, gpointer global_data,
+ gpointer *result, const gchar *tag)
+{
+ GncBudget *bgt;
+ xmlNodePtr tree = (xmlNodePtr)data_for_children;
+ gxpf_data *gdata = (gxpf_data*)global_data;
+ QofBook *book = gdata->bookdata;
+
+ if (parent_data) {
+ return TRUE;
+ }
+
+ /* OK. For some messed up reason this is getting called again with a
+ NULL tag. So we ignore those cases */
+ if(!tag) {
+ return TRUE;
+ }
+
+ g_return_val_if_fail(tree, FALSE);
+
+ bgt = dom_tree_to_budget(tree, book);
+ xmlFreeNode(tree);
+ if(bgt != NULL) {
+ /* ends up calling book_callback */
+ gdata->cb(tag, gdata->parsedata, bgt);
+ }
+
+ return bgt != NULL;
+}
+
+
+GncBudget*
+dom_tree_to_budget (xmlNodePtr node, QofBook *book)
+{
+ GncBudget *bgt;
+
+ bgt = gnc_budget_new(book);
+ if (!dom_tree_generic_parse (node, budget_handlers, bgt)) {
+ PERR ("failed to parse budget tree");
+ gnc_budget_free(bgt);
+ bgt = NULL;
+ }
+ return bgt;
+}
+
+sixtp*
+gnc_budget_sixtp_parser_create(void)
+{
+ return sixtp_dom_parser_new(gnc_budget_end_handler, NULL, NULL);
+}
+/* ====================== END OF FILE ===================*/
--- /dev/null
+++ src/gnome/gncmod-budget.c
@@ -0,0 +1,71 @@
+/*********************************************************************
+ * Copyright (C) 2005 Chris Shoemaker <c.shoemaker at cox.net> *
+ *
+ * gncmod-budget.c
+ * module definition/initialization for budget
+ *
+ * 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 at gnu.org
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <glib.h>
+#include <libguile.h>
+
+#include "gnc-module.h"
+#include "gnc-module-api.h"
+#include "gnc-plugin-budget.h"
+
+/* version of the gnc module system interface we require */
+int libgncmod_budget_gnc_module_system_interface = 0;
+
+/* module versioning uses libtool semantics. */
+int libgncmod_budget_gnc_module_current = 0;
+int libgncmod_budget_gnc_module_revision = 0;
+int libgncmod_budget_gnc_module_age = 0;
+
+/* forward references */
+char *libgncmod_budget_gnc_module_path(void);
+char *libgncmod_budegt_gnc_module_description(void);
+int libgncmod_budget_gnc_module_init(int refcount);
+int libgncmod_budget_gnc_module_end(int refcount);
+
+char * libgncmod_budget_gnc_module_path(void) {
+ return g_strdup("gnucash/gnome/");
+}
+
+char * libgncmod_budget_gnc_module_description(void) {
+ return g_strdup("Support for Budgets");
+}
+
+int libgncmod_budget_gnc_module_init(int refcount)
+{
+ /* load the engine (we depend on it) */
+ if(!gnc_module_load("gnucash/engine", 0)) {
+ return FALSE;
+ }
+
+ /* Add menu items with C callbacks */
+ gnc_plugin_budget_create_plugin();
+
+ return TRUE;
+}
+
+int libgncmod_budget_gnc_module_end(int refcount) {
+ return TRUE;
+}
--- /dev/null
+++ src/gnome/gnc-plugin-page-budget.h
@@ -0,0 +1,77 @@
+/* Copyright (C) 2005 Chris Shoemaker <c.shoemaker at cox.net>
+ *
+ * gnc-plugin-page-budget.h --
+ * (based on gnc-plugin-page-account-tree.h)
+ *
+ * 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 at gnu.org
+ */
+
+/** @addtogroup UI
+ @{ */
+/** @file gnc-plugin-page-budget.h
+ @brief
+*/
+
+#ifndef __GNC_PLUGIN_PAGE_BUDGET_H
+#define __GNC_PLUGIN_PAGE_BUDGET_H
+
+#include <gtk/gtkwindow.h>
+
+#include "gnc-plugin-page.h"
+#include "gnc-budget.h"
+
+G_BEGIN_DECLS
+
+/* type macros */
+#define GNC_TYPE_PLUGIN_PAGE_BUDGET (gnc_plugin_page_budget_get_type ())
+#define GNC_PLUGIN_PAGE_BUDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNC_TYPE_PLUGIN_PAGE_BUDGET, GncPluginPageBudget))
+#define GNC_PLUGIN_PAGE_BUDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNC_TYPE_PLUGIN_PAGE_BUDGET, GncPluginPageBudgetClass))
+#define GNC_IS_PLUGIN_PAGE_BUDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNC_TYPE_PLUGIN_PAGE_BUDGET))
+#define GNC_IS_PLUGIN_PAGE_BUDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNC_TYPE_PLUGIN_PAGE_BUDGET))
+#define GNC_PLUGIN_PAGE_BUDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNC_TYPE_PLUGIN_PAGE_BUDGET, GncPluginPageBudgetClass))
+
+#define GNC_PLUGIN_PAGE_BUDGET_NAME "gnc-plugin-page-budget"
+
+/* typedefs & structures */
+typedef struct GncPluginPageBudgetPrivate GncPluginPageBudgetPrivate;
+
+typedef struct {
+ GncPluginPage parent;
+ GncPluginPageBudgetPrivate *priv;
+} GncPluginPageBudget;
+
+typedef struct {
+ GncPluginPageClass parent;
+} GncPluginPageBudgetClass;
+
+/* function prototypes */
+GType gnc_plugin_page_budget_get_type (void);
+
+
+/** Create a new "budget" plugin page.
+ *
+ * @return The newly created plugin page.
+ */
+GncPluginPage *gnc_plugin_page_budget_new (GncBudget *budget);
+
+void gnc_budget_gui_delete_budget(GncBudget *budget);
+
+G_END_DECLS
+
+#endif /* __GNC_PLUGIN_PAGE_BUDGET_H */
+/** @} */
--- /dev/null
+++ src/gnome/gnc-plugin-budget.c
@@ -0,0 +1,277 @@
+/* Copyright (C) 2005 Chris Shoemaker <c.shoemaker at cox.net>
+ *
+ * gnc-plugin-budget.c --
+ * (based on gnc-plugin-account-tree.c)
+ *
+ * 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 at gnu.org
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gnc-plugin-budget.h"
+#include "gnc-plugin-page-budget.h"
+#include "gnc-tree-model-budget.h"
+
+#include "gnc-trace.h"
+#include "gnc-ui-util.h"
+#include "gnc-ui.h"
+#include "gnc-component-manager.h"
+
+#include "messages.h"
+
+#define PLUGIN_ACTIONS_NAME "gnc-plugin-budget-actions"
+#define PLUGIN_UI_FILENAME "gnc-plugin-budget-ui.xml"
+
+static QofLogModule log_module = GNC_MOD_GUI;
+
+static void gnc_plugin_budget_class_init (GncPluginBudgetClass *klass);
+static void gnc_plugin_budget_init (GncPluginBudget *plugin);
+static void gnc_plugin_budget_finalize (GObject *object);
+
+/* Command Callbacks */
+static void gnc_plugin_budget_cmd_new_budget (GtkAction *action,
+ GncMainWindowActionData *data);
+static void gnc_plugin_budget_cmd_open_budget (GtkAction *action,
+ GncMainWindowActionData *data);
+
+#if 0
+/* plugin window interface */
+static GncPluginPage *gnc_plugin_budget_create_page (GncPlugin *plugin,
+ const gchar *uri);
+#endif
+
+static GtkActionEntry gnc_plugin_actions [] = {
+ { "NewBudgetAction", NULL, N_("New Budget"), NULL,
+ N_("Create a new Budget"),
+ G_CALLBACK (gnc_plugin_budget_cmd_new_budget) },
+
+ { "OpenBudgetAction", NULL, N_("Open Budget"), NULL,
+ N_("Open an existing Budget"),
+ G_CALLBACK (gnc_plugin_budget_cmd_open_budget) },
+};
+static guint gnc_plugin_n_actions = G_N_ELEMENTS (gnc_plugin_actions);
+
+struct GncPluginBudgetPrivate {
+ gpointer dummy;
+};
+
+static GObjectClass *parent_class = NULL;
+
+GType
+gnc_plugin_budget_get_type (void)
+{
+ static GType gnc_plugin_budget_type = 0;
+
+ if (!gnc_plugin_budget_type) {
+ static const GTypeInfo our_info = {
+ sizeof (GncPluginBudgetClass),
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc) gnc_plugin_budget_class_init,
+ NULL, /* class_finalize */
+ NULL, /* class_data */
+ sizeof (GncPluginBudget),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) gnc_plugin_budget_init
+ };
+
+ gnc_plugin_budget_type = g_type_register_static(
+ GNC_TYPE_PLUGIN, "GncPluginBudget", &our_info, 0);
+ }
+
+ return gnc_plugin_budget_type;
+}
+
+GncPlugin * gnc_plugin_budget_new (void)
+{
+ GncPluginBudget *plugin;
+ ENTER(" ");
+ plugin = g_object_new (GNC_TYPE_PLUGIN_BUDGET, NULL);
+ LEAVE(" ");
+ return GNC_PLUGIN (plugin);
+}
+
+static void
+gnc_plugin_budget_class_init (GncPluginBudgetClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GncPluginClass *plugin_class = GNC_PLUGIN_CLASS (klass);
+
+ ENTER (" ");
+ parent_class = g_type_class_peek_parent (klass);
+ object_class->finalize = gnc_plugin_budget_finalize;
+
+ /* CAS: I'm still unsure how much needs to be overridden here. */
+
+ /* function overrides */
+ //plugin_class->create_page = gnc_plugin_budget_create_page;
+
+ plugin_class->plugin_name = GNC_PLUGIN_BUDGET_NAME;
+ plugin_class->actions_name = PLUGIN_ACTIONS_NAME;
+ plugin_class->actions = gnc_plugin_actions;
+ plugin_class->n_actions = gnc_plugin_n_actions;
+ plugin_class->ui_filename = PLUGIN_UI_FILENAME;
+
+ LEAVE (" ");
+}
+
+static void
+gnc_plugin_budget_init(GncPluginBudget *plugin)
+{
+ plugin->priv = g_new0(GncPluginBudgetPrivate, 1);
+}
+
+static void
+gnc_plugin_budget_finalize(GObject *object)
+{
+ GncPluginBudget *plugin = GNC_PLUGIN_BUDGET(object);
+
+ g_return_if_fail(GNC_IS_PLUGIN_BUDGET (object));
+ g_return_if_fail(plugin->priv != NULL);
+ ENTER(" ");
+ g_free (plugin->priv);
+ (parent_class->finalize)(object);
+ ENTER(" ");
+
+}
+
+/************************************************************
+ * Plugin Function Implementation *
+ ************************************************************/
+
+#if 0
+static GncPluginPage *
+gnc_plugin_budget_create_page (GncPlugin *plugin,
+ const gchar *uri)
+{
+ g_return_val_if_fail (GNC_IS_PLUGIN_BUDGET (plugin), NULL);
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ ENTER("");
+ LEAVE("");
+ /* FIXME add better URI handling */
+ if (strcmp ("default:", uri)) {
+ return NULL;
+ }
+
+ return NULL;
+}
+#endif
+
+
+/************************************************************
+ * Command Callbacks *
+ ************************************************************/
+
+/* Make a new budget; put it in a page; open the page. */
+static void
+gnc_plugin_budget_cmd_new_budget (GtkAction *action,
+ GncMainWindowActionData *data)
+{
+ GncBudget *budget;
+ GncPluginPage *page;
+
+ g_return_if_fail (data != NULL);
+
+ budget = gnc_budget_new(gnc_get_current_book());
+ page = gnc_plugin_page_budget_new(budget);
+ gnc_main_window_open_page (data->window, page);
+}
+
+static void just_get_one(QofEntity *ent, gpointer data)
+{
+ GncBudget **bgt = (GncBudget**)data;
+ if (bgt && !*bgt) *bgt = GNC_BUDGET(ent);
+}
+
+/* If only one budget exists, open it; otherwise user selects one to open */
+static void
+gnc_plugin_budget_cmd_open_budget (GtkAction *action,
+ GncMainWindowActionData *data)
+{
+ guint count;
+ QofBook *book;
+ GncBudget *bgt;
+ QofCollection *col;
+ g_return_if_fail (data != NULL);
+
+ book = gnc_get_current_book();
+ col = qof_book_get_collection(book, GNC_ID_BUDGET);
+ count = qof_collection_count(col);
+ if (count > 0) {
+ if (count == 1) {
+ qof_collection_foreach(col, just_get_one, &bgt);
+ } else {
+ bgt = gnc_budget_gui_select_budget(book);
+ }
+
+ if (bgt) gnc_main_window_open_page(
+ data->window, gnc_plugin_page_budget_new(bgt));
+ } else { /* if no budgets exist yet, just open a new budget */
+ gnc_plugin_budget_cmd_new_budget(action, data);
+ }
+}
+
+/************************************************************
+ * Other Functions *
+ ************************************************************/
+
+GncBudget *
+gnc_budget_gui_select_budget(QofBook *book)
+{
+ GncBudget *bgt;
+ GtkDialog *dlg;
+ GtkTreeView *tv;
+ GtkTreeIter iter;
+ GtkTreeSelection *sel;
+ GtkTreeModel *tm;
+ gint response;
+ gboolean ok;
+
+ dlg = GTK_DIALOG(gtk_dialog_new_with_buttons(
+ "Select a Budget", NULL, GTK_DIALOG_MODAL,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL));
+
+ tv = GTK_TREE_VIEW(gtk_tree_view_new());
+ sel = gtk_tree_view_get_selection(tv);
+ gtk_tree_selection_set_mode(sel, GTK_SELECTION_BROWSE);
+ tm = gnc_tree_model_budget_new(book);
+ gnc_tree_view_budget_set_model(tv, tm);
+ gtk_container_add(GTK_CONTAINER(dlg->vbox), GTK_WIDGET(tv));
+ gtk_widget_show_all(GTK_WIDGET(dlg));
+
+ bgt = NULL;
+ response = gtk_dialog_run(dlg);
+ switch (response) {
+ case GTK_RESPONSE_OK:
+ ok = gtk_tree_selection_get_selected(sel, &tm, &iter);
+ if (ok) {
+ bgt = gnc_tree_model_budget_get_budget(tm, &iter);
+ }
+ break;
+ default:
+ break;
+ }
+
+ gtk_widget_destroy(GTK_WIDGET(dlg));
+ return bgt;
+}
+
--- /dev/null
+++ src/gnome/gnc-plugin-page-budget.c
@@ -0,0 +1,824 @@
+/*
+ * gnc-plugin-page-budget.c --
+ *
+ * Copyright (C) 2005 Chris Shoemaker <c.shoemaker at cox.net>
+ * (based on gnc-plugin-page-account-tree.c)
+ *
+ * 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 at gnu.org
+ */
+
+
+/*
+ * TODO:
+ *
+ * *) I'd like to be able to update the budget estimates on a per cell
+ * basis, instead of a whole row (account) at one time. But, that
+ * would require some major coding.
+ *
+ * *) Right now, the account-type filter is not saved anywhere. Where
+ * should it be saved? Per budget? Gconf?
+ *
+ *
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include "gnc-plugin-page-register.h"
+#include "gnc-budget.h"
+
+#include "dialog-options.h"
+#include "dialog-utils.h"
+#include "gnc-gnome-utils.h"
+#include "gnc-html.h"
+#include "gnc-icons.h"
+#include "gnc-plugin-page-budget.h"
+#include "gnc-plugin-budget.h"
+
+#include "gnc-session.h"
+#include "gnc-tree-view-account.h"
+#include "gnc-ui.h"
+#include "gnc-ui-util.h"
+#include "option-util.h"
+#include "libguile.h"
+#include "gnc-main-window.h"
+#include "gnc-component-manager.h"
+
+#include "messages.h"
+#include "gnc-engine-util.h"
+#include "gnc-date.h"
+#include "gnc-trace.h"
+
+#include "gnc-dialog.h"
+#include "gnc-recurrence.h"
+#include "Recurrence.h"
+#include "gnc-tree-model-account-types.h"
+
+/* This static indicates the debugging module that this .o belongs to. */
+static QofLogModule log_module = GNC_MOD_BUDGET;
+
+#define PLUGIN_PAGE_BUDGET_CM_CLASS "plugin-page-budget"
+
+/************************************************************
+ * Prototypes *
+ ************************************************************/
+/* Plugin Actions */
+static void
+gnc_plugin_page_budget_class_init (GncPluginPageBudgetClass *klass);
+static void gnc_plugin_page_budget_init (GncPluginPageBudget *plugin_page);
+static void gnc_plugin_page_budget_finalize (GObject *object);
+
+static GtkWidget *
+gnc_plugin_page_budget_create_widget (GncPluginPage *plugin_page);
+static void gnc_plugin_page_budget_destroy_widget (GncPluginPage *plugin_page);
+
+static gboolean gnc_plugin_page_budget_button_press_cb(
+ GtkWidget *widget, GdkEventButton *event, GncPluginPage *page);
+static void gnc_plugin_page_budget_double_click_cb(
+ GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *col,
+ GncPluginPageBudget *page);
+
+static void gnc_plugin_page_budget_view_refresh (GncPluginPageBudget *page);
+
+/* Command Callbacks */
+static void gnc_plugin_page_budget_cmd_delete_budget(
+ GtkAction *action, GncPluginPageBudget *page);
+static void gnc_plugin_page_budget_cmd_view_options(
+ GtkAction *action, GncPluginPageBudget *page);
+static void gnc_plugin_page_budget_cmd_estimate_budget(
+ GtkAction *action, GncPluginPageBudget *page);
+
+
+
+static GtkActionEntry gnc_plugin_page_budget_actions [] = {
+ /* Toplevel */
+ { "FakeToplevel", "", NULL, NULL, NULL, NULL },
+
+ /* TODO: maybe there should be menu entries, too? */
+
+ /* Toolbar buttons */
+ { "DeleteBudgetAction", GNC_STOCK_DELETE_BUDGET, N_("_Delete Budget"),
+ NULL, N_("Delete the budget"),
+ G_CALLBACK (gnc_plugin_page_budget_cmd_delete_budget) },
+ { "OptionsBudgetAction", GTK_STOCK_PROPERTIES, N_("Budget Options"),
+ NULL, N_("Edit the budget view options"),
+ G_CALLBACK (gnc_plugin_page_budget_cmd_view_options) },
+ { "EstimateBudgetAction", GTK_STOCK_EXECUTE, N_("Estimate Budget"),
+ NULL,
+ N_("Estimate a budget value for the selected cells"),
+ G_CALLBACK (gnc_plugin_page_budget_cmd_estimate_budget) },
+};
+
+static guint gnc_plugin_page_budget_n_actions =
+ G_N_ELEMENTS (gnc_plugin_page_budget_actions);
+
+// TODO: What's all this do?
+/*
+static const gchar *actions_requiring_budget[] = {
+ "OpenBudgetAction",
+ "BudgetViewOptionsAction",
+ "DeleteBudgetAction",
+ NULL
+};
+*/
+
+/* DRH - Suggest this be added to libegg */
+/*
+static action_short_labels short_labels[] = {
+
+ { "OpenBudgetAction", N_("Open") },
+ //{ "EditBudgetAction", N_("Edit") },
+ //{ "EditBudgetOptionsAction", N_("Options") },
+ { "NewBudgetAction", N_("New") },
+ { "DeleteBudgetAction", N_("Delete") },
+ { NULL, NULL },
+};
+*/
+
+struct GncPluginPageBudgetPrivate
+{
+ GtkActionGroup *action_group;
+ guint merge_id;
+ GtkUIManager *ui_merge;
+
+ GtkWidget *widget; /* ends up being a vbox */
+ GtkTreeView *tree_view;
+
+ gint component_id;
+
+ GncBudget* budget;
+ GUID key;
+ GncDialog* d;
+
+ GList *period_col_list;
+ guint32 acct_types;
+};
+
+static GObjectClass *parent_class = NULL;
+
+GType
+gnc_plugin_page_budget_get_type (void)
+{
+ static GType gnc_plugin_page_budget_type = 0;
+
+ if (gnc_plugin_page_budget_type == 0) {
+ static const GTypeInfo our_info = {
+ sizeof (GncPluginPageBudgetClass),
+ NULL,
+ NULL,
+ (GClassInitFunc) gnc_plugin_page_budget_class_init,
+ NULL,
+ NULL,
+ sizeof (GncPluginPageBudget),
+ 0,
+ (GInstanceInitFunc) gnc_plugin_page_budget_init
+ };
+
+ gnc_plugin_page_budget_type =
+ g_type_register_static (GNC_TYPE_PLUGIN_PAGE,
+ "GncPluginPageBudget", &our_info, 0);
+ }
+
+ return gnc_plugin_page_budget_type;
+}
+
+GncPluginPage *
+gnc_plugin_page_budget_new (GncBudget *budget)
+{
+ GncPluginPageBudget *plugin_page;
+
+ g_return_val_if_fail(GNC_IS_BUDGET(budget), NULL);
+ ENTER(" ");
+ plugin_page = g_object_new(GNC_TYPE_PLUGIN_PAGE_BUDGET, NULL);
+
+ plugin_page->priv->budget = budget;
+ plugin_page->priv->key = *gnc_budget_get_guid(budget);
+ LEAVE("new budget page %p", plugin_page);
+ return GNC_PLUGIN_PAGE(plugin_page);
+}
+
+static void
+gnc_plugin_page_budget_class_init (GncPluginPageBudgetClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GncPluginPageClass *gnc_plugin_class = GNC_PLUGIN_PAGE_CLASS(klass);
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ object_class->finalize = gnc_plugin_page_budget_finalize;
+
+ gnc_plugin_class->tab_icon = GNC_STOCK_BUDGET;
+ gnc_plugin_class->plugin_name = GNC_PLUGIN_PAGE_BUDGET_NAME;
+ gnc_plugin_class->create_widget = gnc_plugin_page_budget_create_widget;
+ gnc_plugin_class->destroy_widget = gnc_plugin_page_budget_destroy_widget;
+}
+
+static void
+gnc_plugin_page_budget_init (GncPluginPageBudget *plugin_page)
+{
+ GtkActionGroup *action_group;
+ GncPluginPageBudgetPrivate *priv;
+ GncPluginPage *parent;
+ const gchar *url = NULL;
+ //int options_id;
+ //SCM find_options;
+ //SCM temp;
+ URLType type;
+
+ ENTER("page %p", plugin_page);
+ priv = plugin_page->priv = g_new0 (GncPluginPageBudgetPrivate, 1);
+
+ /* Init parent declared variables */
+ parent = GNC_PLUGIN_PAGE(plugin_page);
+ g_object_set(G_OBJECT(plugin_page),
+ "page-name", _("Budget"),
+ "page-uri", "default:",
+ "ui-description", "gnc-plugin-page-budget-ui.xml",
+ NULL);
+
+ /* change me when the system supports multiple books */
+ gnc_plugin_page_add_book(parent, gnc_get_current_book());
+
+ /* Create menu and toolbar information */
+ action_group =
+ gnc_plugin_page_create_action_group(parent,
+ "GncPluginPageBudgetActions");
+ gtk_action_group_add_actions (action_group,
+ gnc_plugin_page_budget_actions,
+ gnc_plugin_page_budget_n_actions,
+ plugin_page);
+ // FIXME? needed?
+ //gnc_gnome_utils_init_short_names (action_group, short_labels);
+
+ // FIXME: need to test this url case
+ if(!url) {
+ } else {
+ char * location = NULL;
+ char * label = NULL;
+
+ /* if an URL is specified, it should look like
+ * gnc-budget:id=17 . We want to get the number out,
+ * then look up the options in the global DB. */
+ type = gnc_html_parse_url(NULL, url, &location, &label);
+ g_free (location);
+ g_free (label);
+ }
+
+ LEAVE("page %p, priv %p, action group %p",
+ plugin_page, plugin_page->priv, action_group);
+}
+
+static void
+gnc_plugin_page_budget_finalize (GObject *object)
+{
+ GncPluginPageBudget *page;
+ GncPluginPageBudgetPrivate *priv;
+
+ ENTER("object %p", object);
+ page = GNC_PLUGIN_PAGE_BUDGET (object);
+ g_return_if_fail (GNC_IS_PLUGIN_PAGE_BUDGET (page));
+ priv = page->priv;
+ g_return_if_fail (priv != NULL);
+
+ g_list_free(priv->period_col_list);
+ g_free (priv);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+ LEAVE(" ");
+}
+
+
+/* Component Manager Callback Functions */
+static void
+gnc_plugin_page_budget_close_cb (gpointer user_data)
+{
+ GncPluginPage *page = GNC_PLUGIN_PAGE(user_data);
+ gnc_main_window_close_page (page);
+}
+
+static void
+gnc_plugin_page_budget_refresh_cb(GHashTable *changes, gpointer user_data)
+{
+ GncPluginPageBudget *page;
+ const EventInfo* ei;
+
+ page = GNC_PLUGIN_PAGE_BUDGET(user_data);
+ if (changes) {
+ ei = gnc_gui_get_entity_events(changes, &page->priv->key);
+ if (ei) {
+ if (ei->event_mask & GNC_EVENT_DESTROY) {
+ gnc_plugin_page_budget_close_cb(user_data);
+ return;
+ }
+ if (ei->event_mask & GNC_EVENT_MODIFY) {
+ DEBUG("refreshing budget view because budget was modified");
+ gnc_plugin_page_budget_view_refresh(page);
+ }
+ }
+ }
+}
+
+
+/*
+ * GncPluginPage Fucntions
+ */
+static GtkWidget *
+gnc_plugin_page_budget_create_widget (GncPluginPage *plugin_page)
+{
+ GncPluginPageBudget *page;
+ GtkTreeSelection *selection;
+ GtkTreeView *tree_view;
+ GtkWidget *scrolled_window;
+
+ ENTER("page %p", plugin_page);
+ page = GNC_PLUGIN_PAGE_BUDGET (plugin_page);
+ if (page->priv->widget != NULL) {
+ LEAVE("widget = %p", page->priv->widget);
+ return page->priv->widget;
+ }
+
+ page->priv->widget = gtk_vbox_new (FALSE, 0);
+ gtk_widget_show (page->priv->widget);
+
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_show (scrolled_window);
+ gtk_box_pack_start (GTK_BOX (page->priv->widget), scrolled_window,
+ TRUE, TRUE, 0);
+
+ tree_view = gnc_tree_view_account_new(FALSE);
+ gnc_tree_view_configure_columns(
+ GNC_TREE_VIEW(tree_view), "Name", NULL);
+ page->priv->tree_view = tree_view;
+ selection = gtk_tree_view_get_selection(tree_view);
+ gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
+
+ g_signal_connect (G_OBJECT (tree_view), "button-press-event",
+ G_CALLBACK (gnc_plugin_page_budget_button_press_cb),
+ plugin_page);
+ g_signal_connect (G_OBJECT (tree_view), "row-activated",
+ G_CALLBACK (gnc_plugin_page_budget_double_click_cb),
+ page);
+
+ gtk_tree_view_set_headers_visible(tree_view, TRUE);
+ gtk_widget_show (GTK_WIDGET (tree_view));
+ gtk_container_add (GTK_CONTAINER (scrolled_window),
+ GTK_WIDGET(tree_view));
+
+ page->priv->component_id =
+ gnc_register_gui_component(PLUGIN_PAGE_BUDGET_CM_CLASS,
+ gnc_plugin_page_budget_refresh_cb,
+ gnc_plugin_page_budget_close_cb,
+ page);
+
+ gnc_gui_component_set_session (page->priv->component_id,
+ gnc_get_current_session());
+
+ gnc_gui_component_watch_entity (page->priv->component_id,
+ gnc_budget_get_guid(page->priv->budget),
+ GNC_EVENT_DESTROY | GNC_EVENT_MODIFY);
+
+ gnc_plugin_page_budget_view_refresh(page);
+
+ LEAVE("widget = %p", page->priv->widget);
+ return page->priv->widget;
+}
+
+static void
+gnc_plugin_page_budget_destroy_widget (GncPluginPage *plugin_page)
+{
+ GncPluginPageBudget *page;
+
+ ENTER("page %p", plugin_page);
+ page = GNC_PLUGIN_PAGE_BUDGET (plugin_page);
+ if (page->priv->widget) {
+ g_object_unref(G_OBJECT(page->priv->widget));
+ page->priv->widget = NULL;
+ }
+
+ gnc_gui_component_clear_watches (page->priv->component_id);
+
+ if (page->priv->component_id != NO_COMPONENT) {
+ gnc_unregister_gui_component(page->priv->component_id);
+ page->priv->component_id = NO_COMPONENT;
+ }
+
+ LEAVE("widget destroyed");
+}
+
+/** This button press handler calls the common button press handler
+ * for all pages. The GtkTreeView eats all button presses and
+ * doesn't pass them up the widget tree, even when doesn't do
+ * anything with them. The only way to get access to the button
+ * presses in an account tree page is here on the tree view widget.
+ * Button presses on all other pages are caught by the signal
+ * registered in gnc-main-window.c. */
+static gboolean
+gnc_plugin_page_budget_button_press_cb (GtkWidget *widget,
+ GdkEventButton *event,
+ GncPluginPage *page)
+{
+ gboolean result;
+
+ g_return_val_if_fail(GNC_IS_PLUGIN_PAGE(page), FALSE);
+
+ ENTER("widget %p, event %p, page %p", widget, event, page);
+ result = gnc_main_window_button_press_cb(widget, event, page);
+ LEAVE(" ");
+ return result;
+}
+
+static void
+gnc_plugin_page_budget_double_click_cb (GtkTreeView *treeview,
+ GtkTreePath *path,
+ GtkTreeViewColumn *col,
+ GncPluginPageBudget *page)
+{
+ GtkWidget *window;
+ GncPluginPage *new_page;
+ Account *account;
+
+ g_return_if_fail(GNC_IS_PLUGIN_PAGE_BUDGET (page));
+ account = gnc_tree_view_account_get_account_from_path(
+ GNC_TREE_VIEW_ACCOUNT(treeview), path);
+ if (account == NULL)
+ return;
+
+ window = GNC_PLUGIN_PAGE(page)->window;
+ new_page = gnc_plugin_page_register_new(account, FALSE);
+ gnc_main_window_open_page(GNC_MAIN_WINDOW(window), new_page);
+}
+
+/* Command callbacks */
+
+static void
+gnc_plugin_page_budget_cmd_delete_budget (GtkAction *action,
+ GncPluginPageBudget *page)
+{
+ GncBudget *budget;
+
+ budget = page->priv->budget;
+ g_return_if_fail (GNC_IS_BUDGET(budget));
+ gnc_budget_gui_delete_budget(budget);
+
+}
+
+/******************************/
+/* Options Dialog */
+/******************************/
+
+static gboolean
+gnc_plugin_page_budget_options_apply_cb (GncDialog * d,
+ gpointer user_data)
+{
+ GncPluginPageBudgetPrivate *priv = user_data;
+ const gchar *name;
+ gchar *desc;
+ gint num_periods;
+ GncRecurrence *gr;
+ const Recurrence *r;
+ GtkTreeView *tv;
+ guint32 sel_mask;
+
+ if(!priv)
+ return TRUE;
+
+ ENTER(" ");
+ name = gnc_dialog_get_string(d, "BudgetName");
+ if (name) {
+ gnc_budget_set_name(priv->budget, name);
+ DEBUG("%s", name);
+ }
+
+
+ //FIXME: this is special broken case where we actually do need to
+ //free because widget is a GtkTextView
+ desc = (gchar *) gnc_dialog_get_string(d, "BudgetDescription");
+ gnc_budget_set_description(priv->budget, desc);
+ g_free(desc);
+
+ num_periods = gnc_dialog_get_int(d, "BudgetNumPeriods");
+ gnc_budget_set_num_periods(priv->budget, num_periods);
+
+ gr = GNC_RECURRENCE(gnc_dialog_get_widget(d, "BudgetRecurrenceEntry"));
+ r = gnc_recurrence_get(gr);
+ gnc_budget_set_recurrence(priv->budget, r);
+
+
+ tv = GTK_TREE_VIEW(gnc_dialog_get_widget(
+ d, "AccountTypesTreeView"));
+ sel_mask = gnc_tree_model_account_types_get_selection(tv);
+ priv->acct_types = sel_mask;
+ gnc_tree_view_account_set_filter(
+ GNC_TREE_VIEW_ACCOUNT(priv->tree_view),
+ gnc_tree_view_account_filter_by_type_selection,
+ GUINT_TO_POINTER(sel_mask), NULL);
+ LEAVE(" ");
+ return TRUE;
+}
+
+static gboolean
+gnc_plugin_page_budget_options_help_cb (GncDialog *d,
+ gpointer user_data)
+{
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_OK,
+ "Set the budget options using this dialog.");
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+ return TRUE;
+}
+
+static gboolean
+gnc_plugin_page_budget_options_close_cb (GncDialog *d,
+ gpointer user_data)
+{
+ GncPluginPageBudgetPrivate *priv = user_data;
+
+ g_return_val_if_fail(priv, TRUE);
+
+ gtk_widget_destroy(GTK_WIDGET(d));
+ priv->d = NULL;
+ return TRUE;
+}
+
+
+static void
+gnc_budget_gui_show_options(GncDialog *pw, GncBudget *budget,
+ GncPluginPageBudget *page)
+{
+ GtkTreeView *tv;
+ GtkTreeModel *tm;
+ GtkTreeSelection *sel;
+ GncRecurrence *gr;
+ GncPluginPageBudgetPrivate *priv;
+
+
+ g_return_if_fail (GNC_IS_PLUGIN_PAGE_BUDGET (page));
+ priv = page->priv;
+
+ gnc_dialog_set_string(pw, "BudgetName",
+ gnc_budget_get_name(budget));
+ gnc_dialog_set_string(pw, "BudgetDescription",
+ gnc_budget_get_description(budget));
+ gnc_dialog_set_int(pw, "BudgetNumPeriods",
+ gnc_budget_get_num_periods(budget));
+ gr = GNC_RECURRENCE(gnc_dialog_get_widget(
+ pw, "BudgetRecurrenceEntry"));
+ gnc_recurrence_set(gr, gnc_budget_get_recurrence(budget));
+
+ tv = GTK_TREE_VIEW(gnc_dialog_get_widget(
+ pw, "AccountTypesTreeView"));
+ tm = gnc_tree_model_account_types_master();
+ gtk_tree_view_set_model(tv, tm);
+ gtk_tree_view_insert_column_with_attributes(
+ tv, -1, "Account Types", gtk_cell_renderer_text_new(),
+ "text", GNC_TREE_MODEL_ACCOUNT_TYPES_COL_NAME, NULL);
+ sel = gtk_tree_view_get_selection(tv);
+ gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
+
+ //FIXME: this is just a default, need to save and set actual value.
+ if (priv->acct_types == 0)
+ priv->acct_types = 1 << INCOME | 1 << EXPENSE;
+ gnc_tree_model_account_types_set_selection(tv, priv->acct_types);
+}
+
+
+static void
+gnc_plugin_page_budget_cmd_view_options (GtkAction *action,
+ GncPluginPageBudget *page)
+{
+ GncPluginPageBudgetPrivate *priv;
+
+ g_return_if_fail (GNC_IS_PLUGIN_PAGE_BUDGET (page));
+ priv = page->priv;
+
+
+ if (!priv->d) {
+ priv->d = gnc_dialog_new(GNC_BUDGET_GUI_FILE, "BudgetOptions");
+ gtk_window_set_title(GTK_WINDOW(priv->d), "Budget Options");
+ gnc_dialog_set_cb(priv->d,
+ gnc_plugin_page_budget_options_apply_cb,
+ gnc_plugin_page_budget_options_close_cb,
+ gnc_plugin_page_budget_options_help_cb,
+ priv);
+ }
+
+ gnc_budget_gui_show_options(priv->d, priv->budget, page);
+ gtk_widget_show_all(GTK_WIDGET(priv->d));
+}
+
+
+void
+gnc_budget_gui_delete_budget(GncBudget *budget)
+{
+ const char *name;
+
+ g_return_if_fail(GNC_IS_BUDGET(budget));
+ name = gnc_budget_get_name (budget);
+ if (!name)
+ name = "Unnamed Budget";
+
+
+ if (gnc_verify_dialog (NULL, FALSE, "Delete %s?", name)) {
+ gnc_suspend_gui_refresh ();
+ gnc_budget_free(budget);
+ // Views should close themselves because the CM will notify them.
+ gnc_resume_gui_refresh ();
+ }
+
+}
+
+
+static void
+estimate_budget_helper(GtkTreeModel *model, GtkTreePath *path,
+ GtkTreeIter *iter, gpointer data)
+{
+ Account *acct;
+ guint num_periods, i;
+ gnc_numeric num;
+ GncPluginPageBudgetPrivate *priv;
+ GncPluginPageBudget *page = data;
+
+
+ g_return_if_fail(GNC_IS_PLUGIN_PAGE_BUDGET(page));
+ priv = page->priv;
+
+ acct = gnc_tree_view_account_get_account_from_path(
+ GNC_TREE_VIEW_ACCOUNT(priv->tree_view), path);
+
+ num_periods = g_list_length(priv->period_col_list);
+
+ for (i = 0; i < num_periods; i++) {
+ num = gnc_budget_get_account_period_actual_value(
+ priv->budget, acct, i);
+ if (!gnc_numeric_check(num)) {
+ if (gnc_reverse_balance (acct))
+ num = gnc_numeric_neg (num);
+
+ gnc_budget_set_account_period_value(
+ priv->budget, acct, i, num);
+ }
+ }
+
+}
+
+static void
+gnc_plugin_page_budget_cmd_estimate_budget(GtkAction *action,
+ GncPluginPageBudget *page)
+{
+ GncPluginPageBudgetPrivate *priv;
+ GtkTreeSelection *sel;
+
+ g_return_if_fail (GNC_IS_PLUGIN_PAGE_BUDGET (page));
+ priv = page->priv;
+
+ sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->tree_view));
+ gtk_tree_selection_selected_foreach(sel, estimate_budget_helper, page);
+
+
+
+}
+
+static gchar *
+budget_col_source(Account *account, GtkTreeViewColumn *col,
+ GtkCellRenderer *cell)
+{
+ GncBudget *budget;
+ guint period_num;
+ gnc_numeric numeric;
+ gchar amtbuff[100]; //FIXME: overkill, where's the #define?
+
+ budget = GNC_BUDGET(g_object_get_data(G_OBJECT(col), "budget"));
+ period_num = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(col),
+ "period_num"));
+ numeric = gnc_budget_get_account_period_value(budget, account, period_num);
+
+ if (gnc_numeric_zero_p(numeric))
+ amtbuff[0] = '\0';
+ else
+ xaccSPrintAmount (amtbuff, numeric,
+ gnc_account_print_info (account, FALSE));
+
+ return g_strdup(amtbuff);
+}
+
+static void
+budget_col_edited(Account *account, GtkTreeViewColumn *col,
+ const gchar *new_text)
+{
+ GncBudget *budget;
+ guint period_num;
+ gnc_numeric numeric;
+
+ if (!(xaccParseAmount (new_text, TRUE, &numeric, NULL)))
+ return;
+
+ period_num = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(col),
+ "period_num"));
+
+ budget = GNC_BUDGET(g_object_get_data(G_OBJECT(col), "budget"));
+
+ gnc_budget_set_account_period_value(budget, account, period_num, numeric);
+}
+
+static void
+gnc_plugin_page_budget_refresh_col_titles(GncPluginPageBudget *page)
+{
+ const Recurrence *r;
+ GDate date, nextdate;
+ GtkTreeViewColumn *col;
+ guint titlelen;
+ gint num_periods_visible;
+ gchar title[MAX_DATE_LENGTH];
+ GncPluginPageBudgetPrivate *priv;
+ GList *col_list;
+ gint i;
+
+ g_return_if_fail(GNC_IS_PLUGIN_PAGE_BUDGET(page));
+ priv = page->priv;
+
+ col_list = priv->period_col_list;
+ num_periods_visible = g_list_length(col_list);
+
+ /* Show the dates in column titles */
+ r = gnc_budget_get_recurrence(priv->budget);
+ date = r->start;
+ for (i = 0; i < num_periods_visible; i++) {
+ col = GTK_TREE_VIEW_COLUMN(g_list_nth_data(col_list, i));
+ titlelen = g_date_strftime(title, MAX_DATE_LENGTH, "%x", &date);
+ if (titlelen > 0)
+ gtk_tree_view_column_set_title(col, title);
+ recurrenceNextInstance(r, &date, &nextdate);
+ date = nextdate;
+ }
+
+}
+
+static void
+gnc_plugin_page_budget_view_refresh (GncPluginPageBudget *page)
+{
+ GncPluginPageBudgetPrivate *priv;
+ gint num_periods, num_periods_visible;
+ GtkTreeViewColumn *col;
+ GList *col_list;
+ gint i;
+
+ g_return_if_fail(GNC_IS_PLUGIN_PAGE_BUDGET(page));
+ priv = page->priv;
+
+ num_periods = gnc_budget_get_num_periods(priv->budget);
+ col_list = priv->period_col_list;
+ num_periods_visible = g_list_length(col_list);
+
+ /* Hide any unneeded extra columns */
+ for (i = num_periods_visible-1; i >= num_periods; i--) {
+ col = GTK_TREE_VIEW_COLUMN((g_list_last(col_list))->data);
+ //maybe better to just destroy it?
+ gtk_tree_view_column_set_visible(col, FALSE);
+ col_list = g_list_delete_link(col_list, g_list_last(col_list));
+ }
+
+ gnc_tree_view_configure_columns(
+ GNC_TREE_VIEW(priv->tree_view), NULL);
+
+ /* Create any needed columns */
+ num_periods_visible = g_list_length(col_list);
+ for (i = num_periods_visible; i < num_periods; i++) {
+ col = gnc_tree_view_account_add_custom_column(
+ GNC_TREE_VIEW_ACCOUNT(priv->tree_view), "",
+ budget_col_source,
+ budget_col_edited);
+ g_object_set_data(G_OBJECT(col), "budget", priv->budget);
+ g_object_set_data(G_OBJECT(col), "period_num", GUINT_TO_POINTER(i));
+ col_list = g_list_append(col_list, col);
+ }
+ num_periods_visible = num_periods;
+ priv->period_col_list = col_list;
+
+ gnc_plugin_page_budget_refresh_col_titles(page);
+}
--- /dev/null
+++ src/gnome/gnc-plugin-budget.h
@@ -0,0 +1,69 @@
+/*
+ * gnc-plugin-budget.h --
+ *
+ * Copyright (C) 2005 Chris Shoemaker <c.shoemaker at cox.net>
+ *
+ * Based on gnc-plugin-account.h, by:
+ * Jan Arne Petersen <jpetersen at uni-bonn.de>
+ *
+ * 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 at gnu.org
+ */
+
+#ifndef __GNC_PLUGIN_BUDGET_H
+#define __GNC_PLUGIN_BUDGET_H
+
+#include <gtk/gtkwindow.h>
+#include "gnc-plugin.h"
+#include "gnc-budget.h"
+
+G_BEGIN_DECLS
+
+/* type macros */
+#define GNC_TYPE_PLUGIN_BUDGET (gnc_plugin_budget_get_type ())
+#define GNC_PLUGIN_BUDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GNC_TYPE_PLUGIN_BUDGET, GncPluginBudget))
+#define GNC_PLUGIN_BUDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GNC_TYPE_PLUGIN_BUDGET, GncPluginBudgetClass))
+#define GNC_IS_PLUGIN_BUDGET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GNC_TYPE_PLUGIN_BUDGET))
+#define GNC_IS_PLUGIN_BUDGET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GNC_TYPE_PLUGIN_BUDGET))
+#define GNC_PLUGIN_BUDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GNC_TYPE_PLUGIN_BUDGET, GncPluginBudgetClass))
+
+#define GNC_PLUGIN_BUDGET_NAME "gnc-plugin-budget"
+#define GNC_BUDGET_GUI_FILE "budget.glade"
+
+/* typedefs & structures */
+typedef struct GncPluginBudgetPrivate GncPluginBudgetPrivate;
+
+typedef struct {
+ GncPlugin parent;
+ GncPluginBudgetPrivate *priv;
+} GncPluginBudget;
+
+typedef struct {
+ GncPluginClass parent;
+} GncPluginBudgetClass;
+
+/* function prototypes */
+GType gnc_plugin_budget_get_type(void);
+GncPlugin *gnc_plugin_budget_new(void);
+
+/* Launch the budget list dialog.*/
+GncBudget * gnc_budget_gui_select_budget(QofBook *book);
+
+
+G_END_DECLS
+
+#endif /* __GNC_PLUGIN_BUDGET_H */
--- /dev/null
+++ src/gnome/ui/gnc-plugin-page-budget-ui.xml
@@ -0,0 +1,27 @@
+<ui>
+ <menubar>
+ <menu name="Edit" action="EditAction">
+ <placeholder name="EditSelectedPlaceholder">
+ <menuitem name="Estimate" action="EstimateBudgetAction"/>
+ <menuitem name="Delete" action="DeleteBudgetAction"/>
+ </placeholder>
+ <menuitem name="Options" action="OptionsBudgetAction"/>
+ </menu>
+ </menubar>
+
+ <popup name="MainPopup" action="FakeToplevel">
+ <placeholder name="PopupPlaceholder1">
+ <menuitem name="Options" action="OptionsBudgetAction"/>
+ </placeholder>
+ </popup>
+
+ <toolbar name="DefaultToolbar">
+ <placeholder name="DefaultToolbarPlaceholder">
+ <separator name="ToolbarSep3"/>
+ <toolitem name="Options" action="OptionsBudgetAction"/>
+ <separator name="ToolbarSep4"/>
+ <toolitem name="Estimate" action="EstimateBudgetAction"/>
+ <toolitem name="Delete" action="DeleteBudgetAction"/>
+ </placeholder>
+ </toolbar>
+</ui>
--- /dev/null
+++ src/gnome/ui/gnc-plugin-budget-ui.xml
@@ -0,0 +1,17 @@
+<ui>
+ <menubar>
+ <menu name="File" action="FileAction">
+ <menu name="FileNewMenu" action="FileNewMenuAction">
+ <placeholder name="FileNewBottonPlaceholder">
+ <menuitem name="FileNewBudget" action="NewBudgetAction"/>
+ </placeholder>
+ </menu>
+ <menu name="FileOpenMenu" action="FileOpenMenuAction">
+ <placeholder name="FileOpenBottomPlaceholder">
+ <menuitem name="FileOpenBudget" action="OpenBudgetAction"/>
+ </placeholder>
+ </menu>
+ </menu>
+ </menubar>
+</ui>
+
--- /dev/null
+++ src/gnome-utils/gnc-tree-model-budget.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2005, Chris Shoemaker <c.shoemaker at cox.net>
+ *
+ * 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 at gnu.org
+ */
+
+/** @addtogroup gnome-util
+ * @{ */
+
+/* This file provides some utilities for working with the list of
+ * budgets in a book. TODO: This file is poorly named, since it
+ * covers both model and view.*/
+
+#ifndef __GNC_TREE_MODEL_BUDGET_H__
+#define __GNC_TREE_MODEL_BUDGET_H__
+
+#include <gtk/gtk.h>
+#include "gnc-budget.h"
+
+/* The budget list columns. */
+enum {
+ BUDGET_GUID_COLUMN,
+ BUDGET_NAME_COLUMN,
+ BUDGET_DESCRIPTION_COLUMN,
+ BUDGET_LIST_NUM_COLS
+};
+
+GtkTreeModel * gnc_tree_model_budget_new(QofBook *book);
+
+void gnc_tree_view_budget_set_model(GtkTreeView *tv, GtkTreeModel *tm);
+
+GncBudget *gnc_tree_model_budget_get_budget(GtkTreeModel *tm,
+ GtkTreeIter *iter);
+
+void gnc_tree_model_budget_get_iter_for_budget(GtkTreeModel *tm,
+ GtkTreeIter *iter,
+ GncBudget *bgt);
+
+#endif // __GNC_TREE_MODEL_BUDGET_H__
--- /dev/null
+++ src/gnome-utils/gnc-tree-model-budget.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2005, Chris Shoemaker <c.shoemaker at cox.net>
+ *
+ * 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 at gnu.org
+ */
+
+/** @addtogroup gnome-util
+ * @{ */
+
+#include "gnc-tree-model-budget.h"
+#include "gnc-budget.h"
+#include "gnc-ui-util.h"
+
+/* Add the new budget object to the tree model. */
+static void add_budget_to_model(QofEntity* data, gpointer user_data )
+{
+ GtkTreeIter iter;
+ GncBudget* budget = GNC_BUDGET(data);
+ GtkTreeModel* treeModel = user_data;
+
+ g_return_if_fail(GNC_IS_BUDGET(budget));
+ g_return_if_fail(budget && treeModel);
+
+ gtk_list_store_append (GTK_LIST_STORE(treeModel), &iter);
+ gtk_list_store_set (GTK_LIST_STORE(treeModel), &iter,
+ BUDGET_GUID_COLUMN, gnc_budget_get_guid(budget),
+ BUDGET_NAME_COLUMN, gnc_budget_get_name(budget),
+ BUDGET_DESCRIPTION_COLUMN,
+ gnc_budget_get_description(budget), -1);
+}
+
+/* CAS: Even though it works, something feels not-quite-right with
+ * this design. The idea here is to _not_ provide yet another
+ * implementation of GtkTreeModel, this time for budgets. Instead,
+ * right now, we're using the already implemented GtkListStore. This
+ * has a couple consequences: 1) We allocate a new store upon every
+ * call, so the memory is owned by caller. 2) The model won't reflect
+ * later updates to the book, so the model shouldn't be expected to
+ * track asynchronous changes.
+ *
+ * If, for some reason, I decide I can't live with or remove those
+ * consequences, I still think there must be some better way than
+ * re-implementing GtkTreeModel. One idea I'm toying with is to
+ * implement a GtkTreeModel for QofCollections, which would offer only
+ * the GUID as a field. Then, TreeViews could add their own columns
+ * with custom CellDataFuncs to display the object-specific fields.
+ * Or, something like that. :)
+ *
+ */
+GtkTreeModel *
+gnc_tree_model_budget_new(QofBook *book)
+{
+ GtkListStore* store;
+
+ store = gtk_list_store_new (BUDGET_LIST_NUM_COLS,
+ G_TYPE_POINTER,
+ G_TYPE_STRING,
+ G_TYPE_STRING);
+
+ qof_collection_foreach(qof_book_get_collection(book, GNC_ID_BUDGET),
+ add_budget_to_model, GTK_TREE_MODEL(store));
+
+ return GTK_TREE_MODEL(store);
+}
+
+void
+gnc_tree_view_budget_set_model(GtkTreeView *tv, GtkTreeModel *tm)
+{
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+
+ gtk_tree_view_set_model (tv, tm);
+
+ /* column for name */
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (
+ "Name", renderer, "text", BUDGET_NAME_COLUMN, NULL);
+ gtk_tree_view_append_column (tv, column);
+
+ /* column for description */
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (
+ "Description", renderer, "text", BUDGET_DESCRIPTION_COLUMN, NULL);
+ gtk_tree_view_append_column (tv, column);
+
+}
+
+GncBudget *
+gnc_tree_model_budget_get_budget(GtkTreeModel *tm, GtkTreeIter *iter)
+{
+ GncBudget *bgt;
+ GValue gv = { 0 };
+ GUID *guid;
+
+ gtk_tree_model_get_value(tm, iter, BUDGET_GUID_COLUMN, &gv);
+ guid = (GUID *) g_value_get_pointer(&gv);
+ g_value_unset(&gv);
+
+ bgt = gnc_budget_lookup(guid, gnc_get_current_book());
+ return bgt;
+}
+
+void
+gnc_tree_model_budget_get_iter_for_budget(GtkTreeModel *tm, GtkTreeIter *iter,
+ GncBudget *bgt)
+{
+ GValue gv = { 0 };
+ const GUID *guid1;
+ GUID *guid2;
+
+ g_return_if_fail(GNC_BUDGET(bgt));
+
+ guid1 = gnc_budget_get_guid(bgt);
+ for (gtk_tree_model_get_iter_first(tm, iter);
+ gtk_list_store_iter_is_valid(GTK_LIST_STORE(tm), iter);
+ gtk_tree_model_iter_next(tm, iter)) {
+
+ gtk_tree_model_get_value(tm, iter, BUDGET_GUID_COLUMN, &gv);
+ guid2 = (GUID *) g_value_get_pointer(&gv);
+ g_value_unset(&gv);
+
+ if (guid_equal(guid1, guid2))
+ return;
+ }
+}
--- /dev/null
+++ src/gnome-utils/gnc-dialog.c
@@ -0,0 +1,677 @@
+/* Copyright (C) 2005 Chris Shoemaker <c.shoemaker at cox.net>
+ *
+ * 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 at gnu.org
+ */
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include "gnc-trace.h"
+#include "gnome.h" // for gnome_date_edit
+#include "gnc-dialog.h"
+#include "gnc-gobject-utils.h"
+#include "dialog-utils.h" // for gnc_glade_xml_new
+
+static QofLogModule log_module = GNC_MOD_GUI;
+
+#define GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
+ GNC_TYPE_DIALOG, GncDialogPrivate))
+
+struct _GncDialog {
+ GtkDialog parent;
+};
+
+struct _GncDialogClass {
+ GtkDialogClass parent;
+ void (*changed) (GncDialog *d);
+};
+
+enum {
+ GNC_DIALOG_CHANGED,
+ LAST_SIGNAL
+};
+
+static gint gnc_dialog_signals [LAST_SIGNAL] = { GNC_DIALOG_CHANGED };
+static GtkDialogClass *parent_class = NULL;
+
+typedef struct {
+ GladeXML *xml;
+ GncDialogCallback apply_cb;
+ GncDialogCallback close_cb;
+ GncDialogCallback help_cb;
+ GtkWidget *cancel_btn;
+ GtkWidget *ok_btn;
+ GtkWidget *help_btn;
+
+ gpointer user_data;
+ gboolean changed;
+} GncDialogPrivate;
+
+static void
+gnc_dialog_finalize(GObject *d)
+{
+ g_return_if_fail(d);
+ /* We don't own any references, so do nothing */
+ gnc_gobject_tracking_forget(d);
+ G_OBJECT_CLASS(parent_class)->finalize(d);
+}
+
+static void
+gnc_dialog_class_init (GncDialogClass *klass)
+{
+ GObjectClass *gobject_class;
+
+ parent_class = g_type_class_peek_parent (klass);
+
+ gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (gobject_class, sizeof (GncDialogPrivate));
+
+ gnc_dialog_signals [GNC_DIALOG_CHANGED] =
+ g_signal_new ("changed",
+ G_OBJECT_CLASS_TYPE (gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (struct _GncDialogClass, changed),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ /* GObject signals */
+ gobject_class->finalize = gnc_dialog_finalize;
+}
+
+static void
+gnc_dialog_init (GncDialog *d, GncDialogClass *klass)
+{
+ gnc_gobject_tracking_remember(G_OBJECT(d), G_OBJECT_CLASS(klass));
+}
+
+GType gnc_dialog_get_type (void)
+{
+ static GType t = 0;
+
+ if (!t) {
+ static const GTypeInfo info = {
+ sizeof (struct _GncDialogClass),
+ NULL, /* base_init */
+ NULL, /* base_final */
+ (GClassInitFunc) gnc_dialog_class_init,
+ NULL, /* class final */
+ NULL, /* class data */
+ sizeof (struct _GncDialog),
+ 0, /* n_preallocs */
+ (GInstanceInitFunc) gnc_dialog_init,
+ NULL,
+ };
+ t = g_type_register_static (GTK_TYPE_DIALOG,
+ "GncDialog", &info, 0);
+ }
+ return t;
+}
+
+static void gnc_dialog_set_changed(GncDialog *_d, gboolean changed)
+{
+ GncDialogPrivate *priv;
+ struct _GncDialog *d = _d;
+
+ priv = GET_PRIVATE(d);
+ if (!priv->changed && changed)
+ gtk_dialog_set_response_sensitive(&d->parent, GTK_RESPONSE_OK,
+ changed);
+ priv->changed = changed;
+ if (changed)
+ g_signal_emit(G_OBJECT(d), gnc_dialog_signals[GNC_DIALOG_CHANGED], 0);
+}
+
+static void gnc_dialog_response_cb(GtkDialog *dlg,
+ gint response, GncDialog *d)
+{
+ gboolean success = TRUE;
+ GncDialogPrivate *priv = GET_PRIVATE(d);
+
+ switch (response) {
+ case GTK_RESPONSE_HELP:
+ if (priv->help_cb)
+ priv->help_cb(d, priv->user_data);
+ break;
+ case GTK_RESPONSE_OK:
+ //case GTK_RESPONSE_APPLY:
+ if (priv->apply_cb) {
+ success = priv->apply_cb(d, priv->user_data);
+ if (success)
+ gnc_dialog_set_changed(d, FALSE);
+ }
+
+ if (!success)
+ break;
+ // fall through
+ default:
+ if (priv->close_cb)
+ success = priv->close_cb(d, priv->user_data);
+ else
+ success = TRUE;
+
+ if (success)
+ gtk_widget_destroy(GTK_WIDGET(dlg));
+ }
+}
+
+static void changed_cb(GObject *obj, gpointer d)
+{
+ gnc_dialog_set_changed(GNC_DIALOG(d), TRUE);
+}
+
+static void
+gnc_dialog_watch_for_changes(GtkWidget *wid, gpointer d)
+{
+ if (GTK_IS_BUTTON(wid))
+ g_signal_connect(G_OBJECT(wid), "clicked", G_CALLBACK(changed_cb), d);
+
+ if (GTK_IS_EDITABLE(wid) || GTK_IS_COMBO_BOX(wid))
+ g_signal_connect(G_OBJECT(wid), "changed", G_CALLBACK(changed_cb), d);
+
+ if (GTK_IS_TREE_VIEW(wid)) {
+ GtkTreeSelection *sel =
+ gtk_tree_view_get_selection(GTK_TREE_VIEW(wid));
+ g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(changed_cb), d);
+ }
+
+ if (GTK_IS_TEXT_VIEW(wid)) {
+ GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(wid));
+ g_signal_connect(G_OBJECT(buf), "changed", G_CALLBACK(changed_cb), d);
+ }
+ //Possibly TODO: GtkCalendar?
+
+ /* Recurse over all "contained" widgets */
+ if (GTK_IS_CONTAINER(wid)) {
+ gtk_container_foreach(GTK_CONTAINER(wid),
+ gnc_dialog_watch_for_changes, d);
+ }
+}
+
+GncDialog *gnc_dialog_new(const char* filename,
+ const char* root)
+{
+ GncDialog *d;
+ GncDialogPrivate *priv;
+ GtkDialog *dlg;
+ GtkWidget *child;
+
+ d = g_object_new(GNC_TYPE_DIALOG, NULL);
+ dlg = GTK_DIALOG(d);
+ priv = GET_PRIVATE(d);
+
+ /* Load in the glade portion and plug it in. */
+ priv->xml = gnc_glade_xml_new(filename, root);
+ child = glade_xml_get_widget(priv->xml, root);
+ if (GTK_WIDGET_TOPLEVEL(child)) {
+ PERR("GncDialog root widget must not be a toplevel widget");
+ return NULL;
+ }
+
+ gtk_container_add(GTK_CONTAINER(dlg->vbox), child);
+
+ /* Prepare the dialog. */
+ priv->help_btn = gtk_dialog_add_button(dlg, GTK_STOCK_HELP,
+ GTK_RESPONSE_HELP);
+ priv->cancel_btn = gtk_dialog_add_button(dlg, GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+ priv->ok_btn = gtk_dialog_add_button(dlg, GTK_STOCK_OK,
+ GTK_RESPONSE_OK);
+
+ g_signal_connect(dlg, "response",
+ G_CALLBACK(gnc_dialog_response_cb), d);
+
+ glade_xml_signal_autoconnect_full(priv->xml,
+ gnc_glade_autoconnect_full_func, d);
+ gnc_dialog_watch_for_changes(child, (gpointer) d);
+ gtk_dialog_set_response_sensitive(dlg, GTK_RESPONSE_OK, FALSE);
+ return d;
+}
+
+void gnc_dialog_set_cb(GncDialog *d, GncDialogCallback apply_cb,
+ GncDialogCallback close_cb,
+ GncDialogCallback help_cb,
+ gpointer user_data)
+{
+ GncDialogPrivate *priv;
+
+ priv = GET_PRIVATE(d);
+ priv->apply_cb = apply_cb;
+ priv->close_cb = close_cb;
+ priv->help_cb = help_cb;
+ priv->user_data = user_data;
+
+ if (apply_cb == NULL)
+ gtk_widget_hide(priv->ok_btn);
+ if (help_cb == NULL)
+ gtk_widget_hide(priv->help_btn);
+}
+
+void gnc_dialog_block_until_close(GncDialog *d)
+{
+ gint result;
+ g_return_if_fail(d);
+
+ do {
+ result = gtk_dialog_run(GTK_DIALOG(d));
+ } while (result != GTK_RESPONSE_DELETE_EVENT);
+}
+
+/* There are certain containers that the type-specific functions don't
+ operate on. But, the API user might have used
+ gnc_dialog_get_widget() to get the container widget, and then added
+ their own widget to the container. For the purpose of the
+ type-specific functions, we'll consider references to those
+ containers as references to their child. (But only one
+ child.)
+ */
+static GtkWidget *gnc_dialog_get_widget_smart(GtkWidget *w)
+{
+ g_return_val_if_fail(w, NULL);
+
+ if (GTK_IS_BOX(w)) {
+ GList *children = gtk_container_get_children(GTK_CONTAINER(w));
+ if (g_list_length(children) == 1) {
+ return gnc_dialog_get_widget_smart(GTK_WIDGET(children->data));
+ }
+ }
+ return w;
+}
+
+
+/* Method 1 */
+GtkWidget *gnc_dialog_get_widget(GncDialog *d, const gchar* name)
+{
+ GncDialogPrivate *priv;
+
+ priv = GET_PRIVATE(d);
+ g_return_val_if_fail(name, NULL);
+ return glade_xml_get_widget(priv->xml, name);
+}
+
+void gnc_dialog_set_sensitive(GncDialog *d, const gchar* name, gboolean sen)
+{
+ gtk_widget_set_sensitive(gnc_dialog_get_widget(d, name), sen);
+}
+
+#define IS_A(wid, tname) (g_type_is_a(GTK_WIDGET_TYPE(wid), \
+ g_type_from_name(tname) ))
+
+#define TYPE_ERROR(wid, tname, failval) do { \
+ PERR("Expected %s, but found %s", (tname), \
+ g_type_name(GTK_WIDGET_TYPE(wid))); \
+ return (failval); \
+} while (0)
+
+#define SPECIFIC_INIT(d, name, wid, failval) \
+ GtkWidget *(wid); \
+ g_return_val_if_fail((d) && (name), (failval)); \
+ (wid) = gnc_dialog_get_widget((d), (name)); \
+ (wid) = gnc_dialog_get_widget_smart((wid)); \
+ g_return_val_if_fail((wid), (failval));
+
+/*
+ * Type-specific getter/setters.
+ *
+ */
+gboolean gnc_dialog_set_string(GncDialog *d, const gchar* name,
+ const gchar* val)
+{
+ SPECIFIC_INIT(d, name, wid, FALSE);
+
+ if (IS_A(wid, "GtkEntry"))
+ gtk_entry_set_text(GTK_ENTRY(wid), val);
+ else if (IS_A(wid, "GtkLabel"))
+ gtk_label_set_text(GTK_LABEL(wid), val);
+ else if (IS_A(wid, "GtkCombo")) //deprecated
+ gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(wid)->entry), val);
+ else if (IS_A(wid, "GtkTextView")) {
+ GtkTextBuffer *buf;
+ buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(wid));
+ gtk_text_buffer_set_text(buf, val, -1);
+ } else TYPE_ERROR(wid, "GtkEntry or GtkLabel or GtkTextView", FALSE);
+ //TODO: font support?
+
+ return TRUE;
+}
+
+const gchar * gnc_dialog_get_string(GncDialog *d, const gchar* name)
+{
+ SPECIFIC_INIT(d, name, wid, NULL);
+
+ if (IS_A(wid, "GtkEntry"))
+ return gtk_entry_get_text(GTK_ENTRY(wid));
+ else if (IS_A(wid, "GtkLabel"))
+ return gtk_label_get_text(GTK_LABEL(wid));
+ else if (IS_A(wid, "GtkCombo")) //deprecated
+ return gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(wid)->entry));
+ else if (IS_A(wid, "GtkTextView")) {
+ GtkTextBuffer *buf;
+ GtkTextIter start, end;
+ buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(wid));
+ gtk_text_buffer_get_bounds(buf, &start, &end);
+ return gtk_text_buffer_get_text(buf, &start, &end, TRUE);
+ //FIXME: LEAK: callers are expecting NOT to own the mem.
+ } else if (IS_A(wid, "GtkComboBoxEntry")) {
+ gint col;
+ GtkTreeModel *tm;
+ GtkTreeIter iter;
+ GType type;
+ col = gtk_combo_box_entry_get_text_column(GTK_COMBO_BOX_ENTRY(wid));
+ tm = gtk_combo_box_get_model(GTK_COMBO_BOX(wid));
+ type = gtk_tree_model_get_column_type(tm, col);
+ if (type != G_TYPE_STRING)
+ return NULL;
+ if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(wid), &iter)) {
+ GValue val;
+ gtk_tree_model_get_value(tm, &iter, col, &val);
+ return g_value_get_string(&val);
+ } else return NULL;
+ } else TYPE_ERROR(wid, "GtkEntry or GtkLabel or GtkTextView", NULL);
+}
+
+gboolean gnc_dialog_set_double(GncDialog *d, const gchar* name, gdouble val)
+{
+ SPECIFIC_INIT(d, name, wid, FALSE);
+
+ if (IS_A(wid, "GtkSpinButton"))
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(wid), val);
+ else TYPE_ERROR(wid, "GtkSpinButton", FALSE);
+ return TRUE;
+ //TODO: string conversion?
+}
+
+gdouble gnc_dialog_get_double(GncDialog *d, const gchar* name)
+{
+ SPECIFIC_INIT(d, name, wid, 0.0);
+
+ if (IS_A(wid, "GtkSpinButton"))
+ return gtk_spin_button_get_value(GTK_SPIN_BUTTON(wid));
+ else TYPE_ERROR(wid, "GtkSpinButton", 0.0);
+}
+gboolean gnc_dialog_set_int(GncDialog *d, const gchar* name, gint val)
+{
+ SPECIFIC_INIT(d, name, wid, FALSE);
+
+ if (IS_A(wid, "GtkSpinButton"))
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(wid), (gdouble)val);
+ else TYPE_ERROR(wid, "GtkSpinButton", FALSE);
+ return TRUE;
+}
+
+gint gnc_dialog_get_int(GncDialog *d, const gchar* name)
+{
+ SPECIFIC_INIT(d, name, wid, 0);
+
+ if (IS_A(wid, "GtkSpinButton"))
+ return gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(wid));
+ else TYPE_ERROR(wid, "GtkSpinButton", 0);
+}
+
+gboolean gnc_dialog_set_date(GncDialog *d, const gchar* name, time_t val)
+{
+ SPECIFIC_INIT(d, name, wid, FALSE);
+
+ if (IS_A(wid, "GnomeDateEdit"))
+ gnome_date_edit_set_time((GnomeDateEdit *)wid, val);
+ else TYPE_ERROR(wid, "GnomeDateEdit", FALSE);
+ return TRUE;
+}
+
+time_t gnc_dialog_get_date(GncDialog *d, const gchar* name)
+{
+ SPECIFIC_INIT(d, name, wid, ((time_t)(-1)));
+
+ if (IS_A(wid, "GnomeDateEdit"))
+ return gnome_date_edit_get_time((GnomeDateEdit *)wid);
+ else TYPE_ERROR(wid, "GnomeDateEdit", ((time_t)(-1)));
+}
+
+
+gboolean gnc_dialog_set_index(GncDialog *d, const gchar* name, gint val)
+{
+ SPECIFIC_INIT(d, name, wid, FALSE);
+
+ if (IS_A(wid, "GtkComboBox"))
+ gtk_combo_box_set_active(GTK_COMBO_BOX(wid), val);
+ else if (IS_A(wid, "GtkOptionMenu"))
+ gtk_option_menu_set_history(GTK_OPTION_MENU(wid),
+ (guint)(val < 0 ? -val : val));
+ else TYPE_ERROR(wid, "GtkComboBox", FALSE); // GtkOptionMenu is deprecated.
+ return TRUE;
+}
+
+gint gnc_dialog_get_index(GncDialog *d, const gchar* name)
+{
+ SPECIFIC_INIT(d, name, wid, -1);
+
+ if (IS_A(wid, "GtkComboBox"))
+ return gtk_combo_box_get_active(GTK_COMBO_BOX(wid));
+ else if (IS_A(wid, "GtkOptionMenu"))
+ return gtk_option_menu_get_history(GTK_OPTION_MENU(wid));
+ else TYPE_ERROR(wid, "GtkComboBox", -1); // GtkOptionMenu is deprecated.
+}
+
+gboolean gnc_dialog_set_boolean(GncDialog *d, const gchar* name,
+ gboolean val)
+{
+ SPECIFIC_INIT(d, name, wid, FALSE);
+
+ if (IS_A(wid, "GtkToggleButton"))
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(wid), val);
+ else TYPE_ERROR(wid, "GtkToggleButton", FALSE);
+ return TRUE;
+}
+
+gboolean gnc_dialog_get_boolean(GncDialog *d, const gchar* name)
+{
+ SPECIFIC_INIT(d, name, wid, FALSE);
+
+ if (IS_A(wid, "GtkToggleButton"))
+ return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(wid));
+ else TYPE_ERROR(wid, "GtkToggleButton", FALSE);
+}
+
+
+/* Method 3 */
+/* getters and setters */
+static gpointer gd_gtk_entry_get_text(gpointer w)
+{
+ return (gpointer)gtk_entry_get_text(GTK_ENTRY(w));
+}
+static gboolean gd_gtk_entry_set_text(gpointer wid, gpointer val)
+{
+ g_return_val_if_fail(GTK_IS_ENTRY(wid), FALSE);
+ gtk_entry_set_text(GTK_ENTRY(wid), (gchar *) val);
+ return TRUE;
+
+}
+static gpointer gd_gtk_spin_button_get_value(gpointer w)
+{
+ static gdouble d;
+ d = gtk_spin_button_get_value(GTK_SPIN_BUTTON(w));
+ return ((gpointer) &d);
+}
+static gboolean gd_gtk_spin_button_set_value(gpointer w, gpointer d)
+{
+ g_return_val_if_fail(GTK_IS_SPIN_BUTTON(w), FALSE);
+ gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), *(gdouble *)d);
+ return TRUE;
+}
+#if 0
+static const gpointer gd_gtk_toggle_button_get_active(GtkWidget *w)
+{
+ static gboolean b;
+ b = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w));
+ return ((gpointer) &b);
+}
+static void gd_gtk_toggle_button_set_active(GtkWidget *w, gpointer b)
+{
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), *(gboolean *)b);
+}
+static const gpointer gd_gtk_combo_box_get_active(GtkWidget *w)
+{
+ static gint i;
+ i = gtk_combo_box_get_active(GTK_COMBO_BOX(w));
+ return ((gpointer) &i);
+}
+static void gd_gtk_combo_box_set_active(GtkWidget *w, gpointer b)
+{
+ gtk_combo_box_set_active(GTK_COMBO_BOX(w), *(gint *)b);
+}
+static const gpointer gd_gtk_text_view_get_buffer(GtkWidget *w)
+{
+ return (gpointer)gtk_text_view_get_buffer(GTK_TEXT_VIEW(w));
+}
+static void gd_gtk_text_view_set_buffer(GtkWidget *w, gpointer b)
+{
+ gtk_text_view_set_buffer(GTK_TEXT_VIEW(w), GTK_TEXT_BUFFER(b));
+}
+static const gpointer gd_gnome_date_edit_get_time(GtkWidget *w)
+{
+ static time_t t;
+ t = gnome_date_edit_get_time(GNOME_DATE_EDIT(w));
+ return ((gpointer) &t);
+}
+static void gd_gnome_date_edit_set_time(GtkWidget *w, gpointer t)
+{
+ gnome_date_edit_set_time(GNOME_DATE_EDIT(w), *(time_t *)t);
+}
+
+/* Order is important. Children before parents. */
+static struct prop_type {
+ gchar *widget_type;
+ GD_Getter_Func getter;
+ GD_Setter_Func setter;
+} prop_types[] = {
+ {"GnomeDateEdit", gd_gnome_date_edit_get_time,
+ gd_gnome_date_edit_set_time },
+ {"GtkLabel", (GD_Getter_Func) gtk_label_get_label,
+ (GD_Setter_Func) gtk_label_set_label},
+ {"GtkToggleButton", gd_gtk_toggle_button_get_active,
+ gd_gtk_toggle_button_set_active},
+ {"GtkComboBox", gd_gtk_combo_box_get_active,
+ gd_gtk_combo_box_set_active},
+};
+
+#define NUM_PROP_TYPES \
+ (sizeof(prop_types) / sizeof(struct prop_type))
+
+static gint
+find_prop_type(GncDialog *d, GtkWidget *wid)
+{
+ gint i;
+ struct prop_type pt;
+
+ for(i = 0; i < NUM_PROP_TYPES; i++) {
+ pt = prop_types[i];
+ if (IS_A(wid, pt.widget_type))
+ return i;
+ }
+ return -1;
+}
+#endif
+
+typedef gpointer (*GD_Getter_Func)(GtkWidget *w);
+typedef void (*GD_Setter_Func)(GtkWidget *w, gpointer val);
+
+typedef struct {
+ GncDialogGetter getter;
+ GncDialogSetter setter;
+ GncDialogSetter filler;
+} custom_type;
+
+
+void gnc_dialog_register_testing_types(void)
+{
+ gnc_dialog_register_custom(g_type_from_name("GtkSpinButton"),
+ gd_gtk_spin_button_get_value,
+ gd_gtk_spin_button_set_value, NULL);
+ gnc_dialog_register_custom(g_type_from_name("GtkEntry"),
+ gd_gtk_entry_get_text,
+ gd_gtk_entry_set_text, NULL);
+
+}
+
+static GHashTable *custom_types;
+
+gboolean gnc_dialog_set_custom(GncDialog *d, const gchar* name,
+ const gpointer val)
+{
+ GType i;
+ custom_type *custom_spec = NULL;
+ SPECIFIC_INIT(d, name, wid, FALSE);
+
+ g_return_val_if_fail(custom_types, FALSE);
+ i = G_TYPE_FROM_INSTANCE(wid);
+ custom_spec = g_hash_table_lookup(
+ custom_types, &i);
+
+ g_return_val_if_fail(custom_spec, FALSE);
+
+ if (custom_spec->setter(wid, val)) {
+ gnc_dialog_set_changed(d, TRUE);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+gpointer gnc_dialog_get_custom(GncDialog *d, const gchar* name)
+{
+ GType i;
+ custom_type *custom_spec = NULL;
+ SPECIFIC_INIT(d, name, wid, NULL);
+
+ g_return_val_if_fail(custom_types, NULL);
+ i = G_TYPE_FROM_INSTANCE(wid);
+ custom_spec = g_hash_table_lookup(
+ custom_types, &i);
+ g_return_val_if_fail(custom_spec, NULL);
+
+ return custom_spec->getter(wid);
+}
+
+gboolean gnc_dialog_fill_custom(GncDialog *d, const char* name);
+
+
+void gnc_dialog_register_custom(GType widgetType, GncDialogGetter getter,
+ GncDialogSetter setter,
+ GncDialogSetter filler)
+{
+ custom_type *ct = g_new0(custom_type, 1);
+ GType *key = g_new0(GType, 1);
+
+ if (custom_types == NULL) {
+ custom_types = g_hash_table_new_full(
+ g_int_hash, g_int_equal, g_free, g_free);
+ }
+ ct->getter = getter;
+ ct->setter = setter;
+ ct->filler = filler;
+ *key = widgetType;
+ PINFO("registering with GType %d", (int)widgetType);
+ g_hash_table_insert(custom_types, key, ct);
+}
+
+void gnc_dialog_unregister_custom(GType widgetType)
+{
+ g_hash_table_remove(custom_types, &widgetType);
+}
--- /dev/null
+++ src/gnome-utils/gnc-dialog.h
@@ -0,0 +1,169 @@
+/* Copyright (C) 2005 Chris Shoemaker <c.shoemaker at cox.net>
+ *
+ * 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 at gnu.org
+ */
+
+
+#ifndef GNC_DIALOG_H
+#define GNC_DIALOG_H
+
+#include <time.h>
+
+GType gnc_dialog_get_type (void);
+
+/* type macros */
+#define GNC_TYPE_DIALOG (gnc_dialog_get_type ())
+#define GNC_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GNC_TYPE_DIALOG, GncDialog))
+#define GNC_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \
+ GNC_TYPE_DIALOG, GncDialogClass))
+#define GNC_IS_DIALOG(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GNC_TYPE_DIALOG))
+#define GNC_IS_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ GNC_TYPE_DIALOG))
+#define GNC_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GNC_TYPE_DIALOG, GncDialogClass))
+
+typedef struct _GncDialog GncDialog;
+typedef struct _GncDialogClass GncDialogClass;
+
+/**** PROTOTYPES *************************************************/
+
+/* filename is a glade filenames; and root is the name of the root
+ widget you want to show. */
+GncDialog *gnc_dialog_new(const char *filename,
+ const char *root);
+
+
+typedef gboolean (*GncDialogCallback) (GncDialog *d, gpointer user_data);
+
+/* The apply callback is optional, but useful dialogs will probably
+ * supply one. The apply callback should return FALSE if any values
+ * are invalid. In that case, the dialog will not close automatically
+ * after the user clicks OK, and the changed state will not be marked
+ * clean. If you supply NULL, for the apply callback, no "OK" button
+ * will be visible.
+ *
+ * The close callback is optional. If you provide a close callback,
+ * its return value should be TRUE if you want to proceed with the
+ * close. There's no destroy notifier for user_data, but you can
+ * treat the close_cb as one. So if you must pass this function its
+ * own copy of user_data, free it from within close_cb.
+ *
+ * The help callback return value is not checked.
+ *
+ * Any callback may be NULL, in which case it's not used. If help_cb
+ * is NULL, no help button is shown.
+ */
+void gnc_dialog_set_cb(GncDialog *d,
+ GncDialogCallback apply_cb,
+ GncDialogCallback close_cb,
+ GncDialogCallback help_cb,
+ gpointer user_data);
+
+/* By default, GncDialog works best in asynchronous environments,
+ * where your program execution flow isn't waiting for the dialog to
+ * close. But, if you're using the dialog to do something like fetch
+ * a value you want to return on the stack, then you have to block the
+ * current thread until the dialog is closed. Calling this function
+ * will do exactly that.
+ */
+void gnc_dialog_block_until_close(GncDialog *d);
+
+/* This is a catch-all interface to whatever kind of widgets may have
+ * been specified in the glade file. Once you have you widget you can
+ * use whatever interface that widget offers to set and get widget
+ * state. You _have_ to use if the widget type isn't supported by the
+ * type-specific or type-generic interfaces below.
+ */
+GtkWidget *gnc_dialog_get_widget(GncDialog *d, const gchar* name);
+
+void gnc_dialog_set_sensitive(GncDialog *d, const gchar* name, gboolean sen);
+
+/* Infers val type from widget type *
+*/
+
+/* Type-generic getter/setter: Be careful with these. They are NOT
+ * type safe. Also, if they prove to be more trouble than they're
+ * worth, they'll go away.
+ *
+ * These functions try to use the widget type to infer the type of
+ * data pointed at by val. They will return FALSE if they are unable
+ * to infer value type. The inferences made are:
+ *
+ * Widget Type ---> Value Type
+ * =========== ==========
+ * GnomeDateEdit GDate *
+ * GtkSpinButton gdouble *
+ * GtkToggleButton gboolean *
+ * GtkEntry gchar *
+ * GtkLabel gchar *
+ * GtkTextView GtkTextBuffer *
+ * GtkComboBox gint *
+ *
+ * WARNING: For the given widget type you must cast the corresponding
+ * value type to/from the passed gpointer. Having mis-matched widget
+ * and value types will likely cause a revolt among the electrons.
+ *
+ */
+gboolean gnc_dialog_set(GncDialog *d, const char* name, const gpointer val);
+gpointer gnc_dialog_get(GncDialog *d, const char* name);
+
+/* Type-specific getter/setters */
+gboolean gnc_dialog_set_string(GncDialog *d, const char* name,
+ const gchar* val);
+const gchar* gnc_dialog_get_string(GncDialog *d, const char* name);
+
+gboolean gnc_dialog_set_double(GncDialog *d, const char* name, gdouble val);
+gdouble gnc_dialog_get_double(GncDialog *d, const char* name);
+
+gboolean gnc_dialog_set_int(GncDialog *d, const char* name, gint val);
+gint gnc_dialog_get_int(GncDialog *d, const char* name);
+
+gboolean gnc_dialog_set_date(GncDialog *d, const char* name, time_t val);
+time_t gnc_dialog_get_date(GncDialog *d, const char* name);
+
+gboolean gnc_dialog_set_index(GncDialog *d, const char* name, gint val);
+gint gnc_dialog_get_index(GncDialog *d, const char* name);
+
+gboolean gnc_dialog_set_boolean(GncDialog *d, const char* name, gboolean val);
+gboolean gnc_dialog_get_boolean(GncDialog *d, const char* name);
+
+/* Possible TODO: there are more types that could be added here.
+
+Maybe currency/gnc_commodity *
+
+*/
+
+gpointer gnc_dialog_get_custom(GncDialog *d, const char* name);
+gboolean gnc_dialog_set_custom(GncDialog *d, const char* name, gpointer val);
+gboolean gnc_dialog_fill_custom(GncDialog *d, const char* name);
+
+/* should return true for success */
+typedef gboolean (*GncDialogSetter) (gpointer widget, gpointer val);
+typedef gpointer (*GncDialogGetter) (gpointer widget);
+//typedef gboolean (*GncDialogFiller) (gpointer widget, gpointer data);
+
+void gnc_dialog_register_custom(GType widgetType, GncDialogGetter getter,
+ GncDialogSetter setter,
+ GncDialogSetter filler);
+
+void gnc_dialog_unregister_custom(GType widgetType);
+void gnc_dialog_register_testing_types(void);
+
+#endif
--- /dev/null
+++ src/gnome-utils/gnc-recurrence.h
@@ -0,0 +1,66 @@
+/* gnc-recurrence.h:
+ *
+ * GncRecurrence is a minimal GUI for specifying a Recurrence.
+ *
+ * You see, small is _nice_. :)
+ *
+ */
+
+/* Copyright (C) 2005, Chris Shoemaker <c.shoemaker at cox.net> *
+ *
+ * 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 02111-1301, USA gnu at gnu.org
+ */
+
+#ifndef GNC_RECURRENCE_H
+#define GNC_RECURRENCE_H
+
+#include <glib.h>
+#include "Recurrence.h"
+
+#define GNC_TYPE_RECURRENCE (gnc_recurrence_get_type())
+#define GNC_RECURRENCE(obj) G_TYPE_CHECK_INSTANCE_CAST \
+ (obj, GNC_TYPE_RECURRENCE, GncRecurrence)
+#define GNC_RECURRENCE_CLASS(klass) G_TYPE_CHECK_CLASS_CAST \
+ (klass, GNC_TYPE_RECURRENCE, GncRecurrence)
+#define GNC_IS_RECURRENCE(obj) G_TYPE_CHECK_INSTANCE_TYPE \
+ (obj, GNC_TYPE_RECURRENCE)
+
+typedef struct _GncRecurrence GncRecurrence;
+typedef struct _GncRecurrenceComp GncRecurrenceComp;
+
+GType gnc_recurrence_get_type(void);
+GtkWidget * gnc_recurrence_new(void);
+
+void gnc_recurrence_set(GncRecurrence *gr, const Recurrence *r);
+
+/* The returned Recurrence is internally owned and is only valid as
+ long as the GncRecurrence is around. */
+const Recurrence * gnc_recurrence_get(GncRecurrence *gr);
+
+/* "composite" recurrences */
+void gnc_recurrence_comp_set_list(GncRecurrenceComp *grc, const GList *r);
+
+/* The GList is newly-allocated, but the Recurrences are internally
+ owned. */
+GList * gnc_recurrence_comp_get_list(GncRecurrenceComp *grc);
+
+/* This works, but is not used. Kind of experimental... */
+GtkWidget * gnc_recurrence_comp_new(void);
+GType gnc_recurrence_comp_get_type(void);
+
+#endif
--- /dev/null
+++ src/gnome-utils/gnc-recurrence.c
@@ -0,0 +1,562 @@
+/* gnc-recurrence.c:
+ *
+ */
+
+/* Copyright (C) 2005, Chris Shoemaker <c.shoemaker at cox.net>
+ *
+ * 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 at gnu.org
+ */
+
+#include "config.h"
+#include <glade/glade.h>
+#include <gnome.h>
+#include <glib.h>
+
+#include "dialog-utils.h"
+#include "gnc-recurrence.h"
+#include "Recurrence.h"
+#include "gnc-trace.h"
+#include "gnc-gdate-utils.h"
+
+static QofLogModule log_module = GNC_MOD_GUI;
+
+struct _GncRecurrence {
+ GtkVBox widget;
+
+ GnomeDateEdit *gde_start;
+ GtkComboBox *gcb_period;
+ GtkCheckButton *gcb_eom;
+ GtkSpinButton *gsb_mult;
+ GtkCheckButton *nth_weekday;
+ GladeXML *xml;
+
+ Recurrence recurrence;
+};
+
+typedef struct {
+ GtkVBoxClass parent_class;
+ void (*changed) (GncRecurrence *gr);
+} GncRecurrenceClass;
+
+typedef enum {
+ GNCRECURRENCE_CHANGED,
+ LAST_SIGNAL
+} GNCR_Signals;
+
+typedef enum {
+ GNCR_DAY,
+ GNCR_WEEK,
+ GNCR_MONTH,
+ GNCR_YEAR,
+} UIPeriodType;
+
+static GObjectClass *parent_class = NULL;
+
+static UIPeriodType get_pt_ui(GncRecurrence *gr)
+{
+ return (gtk_combo_box_get_active(gr->gcb_period));
+}
+
+static void set_pt_ui(GncRecurrence *gr, PeriodType pt)
+{
+ UIPeriodType idx;
+ switch (pt) {
+ case PERIOD_DAY:
+ idx = 0; break;
+ case PERIOD_WEEK:
+ idx = 1; break;
+ case PERIOD_MONTH:
+ case PERIOD_END_OF_MONTH:
+ case PERIOD_NTH_WEEKDAY:
+ case PERIOD_LAST_WEEKDAY:
+ idx = 2; break;
+ case PERIOD_YEAR:
+ idx = 3; break;
+ default: return;
+ }
+ gtk_combo_box_set_active(gr->gcb_period, idx);
+
+ gtk_toggle_button_set_active(
+ GTK_TOGGLE_BUTTON(gr->nth_weekday),
+ (pt == PERIOD_NTH_WEEKDAY || pt == PERIOD_LAST_WEEKDAY));
+
+ gtk_toggle_button_set_active(
+ GTK_TOGGLE_BUTTON(gr->gcb_eom),
+ (pt == PERIOD_END_OF_MONTH || pt == PERIOD_LAST_WEEKDAY));
+}
+
+static gboolean
+is_ambiguous_relative(const GDate *date)
+{
+ GDateDay d;
+ guint8 dim;
+
+ d = g_date_get_day(date);
+ dim = g_date_get_days_in_month(
+ g_date_get_month(date), g_date_get_year(date));
+ return ((d - 1) / 7 == 3) && (dim - d < 7);
+}
+
+static gboolean
+is_ambiguous_absolute(const GDate *date)
+{
+ return (g_date_is_last_of_month(date) &&
+ (g_date_get_day(date) < 31));
+}
+
+static void
+something_changed( GtkWidget *wid, gpointer d )
+{
+ UIPeriodType pt;
+ GDate start;
+ time_t t;
+ gboolean show_last, use_wd;
+ GncRecurrence *gr = GNC_RECURRENCE(d);
+
+
+ pt = get_pt_ui(gr);
+ t = gnome_date_edit_get_time(gr->gde_start);
+ g_date_set_time(&start, t);
+
+ if (pt == GNCR_MONTH)
+ g_object_set(G_OBJECT(gr->nth_weekday), "visible", TRUE, NULL);
+ else {
+ g_object_set(G_OBJECT(gr->nth_weekday), "visible", FALSE, NULL);
+ gtk_toggle_button_set_active(
+ GTK_TOGGLE_BUTTON(gr->nth_weekday), FALSE);
+ }
+ use_wd = gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(gr->nth_weekday));
+ //TODO: change label
+
+ /* The case under which we show the "end of month" flag is very
+ narrow, because we can almost always DTRT without it. */
+ if (pt == GNCR_MONTH) {
+ if (use_wd)
+ show_last = is_ambiguous_relative(&start);
+ else
+ show_last = is_ambiguous_absolute(&start);
+ } else {
+ show_last = FALSE;
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gr->gcb_eom), FALSE);
+ }
+ g_object_set(G_OBJECT(gr->gcb_eom), "visible", show_last, NULL);
+
+ g_signal_emit_by_name(d, "changed", NULL); // not sure if NULL is needed
+}
+
+static void
+gnc_recurrence_init( GncRecurrence *gr )
+{
+ GtkVBox *vb;
+
+ recurrenceSet(&gr->recurrence, 1, PERIOD_MONTH, NULL);
+
+ gr->xml = gnc_glade_xml_new("budget.glade", "RecurrenceEntryVBox");
+ vb = GTK_VBOX(glade_xml_get_widget(gr->xml, "RecurrenceEntryVBox"));
+ gr->gde_start = GNOME_DATE_EDIT(glade_xml_get_widget(gr->xml,
+ "GDE_StartDate"));
+ gtk_widget_set_no_show_all(GTK_WIDGET(gr->gde_start), TRUE);
+ gr->gcb_period = GTK_COMBO_BOX(glade_xml_get_widget(gr->xml,
+ "GCB_PeriodType"));
+ gr->gsb_mult = GTK_SPIN_BUTTON(glade_xml_get_widget(gr->xml, "GSB_Mult"));
+ gr->gcb_eom = GTK_CHECK_BUTTON(glade_xml_get_widget(gr->xml,
+ "GCB_EndOfMonth"));
+ gr->nth_weekday = GTK_CHECK_BUTTON(glade_xml_get_widget(gr->xml,
+ "GCB_NthWeekday"));
+ gtk_widget_set_no_show_all(GTK_WIDGET(gr->gcb_eom), TRUE);
+ gtk_widget_set_no_show_all(GTK_WIDGET(gr->nth_weekday), TRUE);
+
+
+ gtk_container_add( GTK_CONTAINER(&gr->widget), GTK_WIDGET(vb) );
+
+ gnc_recurrence_set(gr, &gr->recurrence);
+ something_changed( GTK_WIDGET(gr), gr);
+
+ /* respond to changes */
+ g_signal_connect( G_OBJECT(gr->gde_start), "date_changed",
+ G_CALLBACK(something_changed), gr );
+ g_signal_connect( G_OBJECT(gr->gcb_period), "changed",
+ G_CALLBACK(something_changed), gr );
+ g_signal_connect( G_OBJECT(gr->gsb_mult), "value-changed",
+ G_CALLBACK(something_changed), gr );
+ g_signal_connect( G_OBJECT(gr->gcb_eom), "toggled",
+ G_CALLBACK(something_changed), gr );
+ g_signal_connect( G_OBJECT(gr->nth_weekday), "toggled",
+ G_CALLBACK(something_changed), gr );
+
+ gtk_widget_show_all( GTK_WIDGET(&gr->widget) );
+}
+
+void
+gnc_recurrence_set(GncRecurrence *gr, const Recurrence *r)
+{
+ PeriodType pt;
+ guint mult;
+ GDate start;
+
+ g_return_if_fail(gr && r);
+ pt = recurrenceGetPeriodType(r);
+ mult = recurrenceGetMultiplier(r);
+ start = recurrenceGetDate(r);
+
+ gtk_spin_button_set_value(gr->gsb_mult, (gdouble) mult);
+
+ // is there some better way?
+ {
+ time_t t;
+ t = gnc_timet_get_day_start_gdate (&start);
+ gnome_date_edit_set_time(gr->gde_start, t);
+ }
+
+ set_pt_ui(gr, pt);
+}
+
+const Recurrence *
+gnc_recurrence_get(GncRecurrence *gr)
+{
+ time_t t;
+ guint mult;
+ UIPeriodType period;
+ PeriodType pt;
+ GDate start;
+ gboolean use_eom = FALSE, rel;
+
+ mult = (guint) gtk_spin_button_get_value_as_int(gr->gsb_mult);
+ t = gnome_date_edit_get_time(gr->gde_start);
+ g_date_set_time(&start, t);
+ period = get_pt_ui(gr);
+
+ switch (period) {
+ case GNCR_DAY:
+ pt = PERIOD_DAY; break;
+ case GNCR_WEEK:
+ pt = PERIOD_WEEK; break;
+ case GNCR_MONTH:
+ rel = gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(gr->nth_weekday));
+ if (rel) {
+ if (is_ambiguous_relative(&start)) {
+ use_eom = gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(gr->gcb_eom));
+ } else {
+ GDateDay d;
+ d = g_date_get_day(&start);
+
+ use_eom = ((d - 1) / 7 == 4);
+ }
+ if (use_eom)
+ pt = PERIOD_LAST_WEEKDAY;
+ else pt = PERIOD_NTH_WEEKDAY;
+ } else {
+ if (g_date_is_last_of_month(&start) &&
+ (g_date_get_day(&start) < 31)) {
+ // ambiguous, need to examine the checkbox
+ use_eom = gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(gr->gcb_eom));
+ } else {
+ // if it's the last dom, use eom anyway because it's the 31st.
+ use_eom = g_date_is_last_of_month(&start);
+ }
+ if (use_eom)
+ pt = PERIOD_END_OF_MONTH;
+ else pt = PERIOD_MONTH;
+ }
+ break;
+ case GNCR_YEAR:
+ pt = PERIOD_YEAR; break;
+ default:
+ pt = PERIOD_INVALID;
+ }
+
+
+ recurrenceSet(&gr->recurrence, mult, pt, &start);
+ return &gr->recurrence;
+
+}
+static void
+gnc_recurrence_finalize(GObject *o)
+{
+ GncRecurrence *gr = GNC_RECURRENCE(o);
+
+ if (gr)
+ G_OBJECT_CLASS (parent_class)->finalize (o);
+}
+
+static void
+gnc_recurrence_class_init( GncRecurrenceClass *klass )
+{
+ GObjectClass *object_class;
+ static gint signals[LAST_SIGNAL] = { 0 };
+
+ object_class = G_OBJECT_CLASS (klass);
+ signals[GNCRECURRENCE_CHANGED] =
+ g_signal_new ("changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GncRecurrenceClass, changed),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ parent_class = g_type_class_peek_parent (klass);
+ object_class->finalize = gnc_recurrence_finalize;
+}
+
+GType
+gnc_recurrence_get_type()
+{
+ static GType type = 0;
+ if (type == 0) {
+ static GTypeInfo typeinfo = {
+ sizeof(GncRecurrenceClass),
+ NULL, NULL,
+ (GClassInitFunc)gnc_recurrence_class_init,
+ NULL, NULL,
+ sizeof(GncRecurrence),
+ 0,
+ (GInstanceInitFunc)gnc_recurrence_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_VBOX, "GncRecurrence",
+ &typeinfo, 0);
+ }
+ return type;
+}
+
+GtkWidget *
+gnc_recurrence_new()
+{
+ GncRecurrence *gr;
+
+ ENTER(" ");
+ gr = g_object_new(gnc_recurrence_get_type(), NULL);
+ LEAVE(" ");
+ return GTK_WIDGET(gr);
+}
+
+/* TODO: Maybe this stuff should go into another file.
+ *
+ */
+
+struct _GncRecurrenceComp {
+ GtkScrolledWindow widget;
+
+ GtkVBox *vbox;
+ GtkHBox *hbox;
+ GtkHButtonBox *hbb;
+ gint num_rec;
+ GtkButton *buttRemove;
+ GtkButton *buttAdd;
+
+ GList *rlist;
+};
+
+typedef struct {
+ GtkScrolledWindowClass parent_class;
+ void (*changed) (GncRecurrenceComp *gr);
+} GncRecurrenceCompClass;
+
+typedef enum {
+ GNCRECURRENCECOMP_CHANGED,
+ GNCRC_LAST_SIGNAL
+} GNCRC_Signals;
+
+static void grc_changed(GtkWidget *w, gpointer data)
+{
+ g_signal_emit_by_name(data, "changed", NULL);
+}
+static void addRecurrence(GncRecurrenceComp *grc, GncRecurrence *gr)
+{
+
+ gtk_box_pack_start(GTK_BOX(grc->vbox), GTK_WIDGET(gr),
+ FALSE, FALSE, 3);
+ g_signal_connect( G_OBJECT(gr), "changed",
+ G_CALLBACK(grc_changed), grc );
+ grc->num_rec++;
+
+ g_object_set(G_OBJECT(grc->buttRemove), "sensitive",
+ (grc->num_rec > 1), NULL);
+ g_signal_emit_by_name(G_OBJECT(grc), "changed", NULL);
+
+
+}
+static void removeRecurrence(GncRecurrenceComp *grc)
+{
+ GList *children, *last;
+
+ grc->num_rec--;
+
+ children = gtk_container_get_children(GTK_CONTAINER(grc->vbox));
+ last = g_list_last(children);
+ gtk_widget_destroy(GTK_WIDGET(last->data));
+ g_list_free(children);
+ g_signal_emit_by_name(G_OBJECT(grc), "changed", NULL);
+
+
+ g_object_set(G_OBJECT(grc->buttRemove), "sensitive",
+ (grc->num_rec > 1), NULL);
+
+}
+
+static void addClicked(GtkButton *b, gpointer data)
+{
+ GncRecurrenceComp *grc = data;
+ GncRecurrence *gr;
+
+ gr = GNC_RECURRENCE(gnc_recurrence_new());
+ addRecurrence(grc, gr);
+}
+
+static void removeClicked(GtkButton *b, gpointer data)
+{
+ GncRecurrenceComp *grc = data;
+
+ if (grc->num_rec > 1)
+ removeRecurrence(grc);
+}
+
+void
+gnc_recurrence_comp_set_list(GncRecurrenceComp *grc, const GList *rlist)
+{
+ const GList *iter;
+
+ g_return_if_fail(grc);
+
+ while (grc->num_rec > 0)
+ removeRecurrence(grc);
+
+ for (iter = rlist; iter; iter = iter->next) {
+ GncRecurrence *gr = GNC_RECURRENCE(gnc_recurrence_new());
+
+ gnc_recurrence_set(gr, (Recurrence *)iter->data);
+ addRecurrence(grc, gr);
+ }
+}
+
+GList *
+gnc_recurrence_comp_get_list(GncRecurrenceComp *grc)
+{
+ GList *rlist = NULL, *children;
+ gint i;
+
+
+ children = gtk_container_get_children(GTK_CONTAINER(grc->vbox));
+ for (i = 0; i < g_list_length(children); i++) {
+ GncRecurrence *gr;
+ const Recurrence *r;
+ gr = GNC_RECURRENCE(g_list_nth_data(children, i));
+ r = gnc_recurrence_get(gr);
+ rlist = g_list_append(rlist, (gpointer)r);
+ }
+ g_list_free(children);
+ return rlist;
+}
+
+
+static void
+gnc_recurrence_comp_init(GncRecurrenceComp *grc)
+{
+ GtkWidget *vb;
+
+ grc->hbb = GTK_HBUTTON_BOX(gtk_hbutton_box_new());
+ grc->vbox = GTK_VBOX(gtk_vbox_new(FALSE, 1));
+ grc->rlist = NULL;
+
+ grc->buttAdd = GTK_BUTTON(gtk_button_new_from_stock(GTK_STOCK_ADD));
+ g_signal_connect(G_OBJECT(grc->buttAdd), "clicked",
+ G_CALLBACK(addClicked), grc);
+ grc->buttRemove = GTK_BUTTON(gtk_button_new_from_stock(GTK_STOCK_REMOVE));
+ g_signal_connect(G_OBJECT(grc->buttRemove), "clicked",
+ G_CALLBACK(removeClicked), grc);
+
+ gtk_box_pack_start(GTK_BOX(grc->hbb), GTK_WIDGET(grc->buttAdd),
+ FALSE, FALSE, 3);
+ gtk_box_pack_start(GTK_BOX(grc->hbb), GTK_WIDGET(grc->buttRemove),
+ FALSE, FALSE, 3);
+
+ vb = gtk_vbox_new(FALSE, 1);
+ gtk_box_pack_start(GTK_BOX(vb), GTK_WIDGET(grc->hbb),
+ FALSE, FALSE, 3);
+ gtk_box_pack_start(GTK_BOX(vb), GTK_WIDGET(grc->vbox),
+ FALSE, FALSE, 3);
+
+ gtk_scrolled_window_add_with_viewport(
+ GTK_SCROLLED_WINDOW(grc), GTK_WIDGET(vb));
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(grc),
+ GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+
+ grc->num_rec = 0;
+ gtk_widget_show_all(GTK_WIDGET(grc));
+ addClicked(NULL, grc);
+}
+
+static void
+gnc_recurrence_comp_class_init( GncRecurrenceCompClass *klass )
+{
+ GObjectClass *object_class;
+ static gint signals[GNCRC_LAST_SIGNAL] = { 0 };
+
+ object_class = G_OBJECT_CLASS (klass);
+ signals[GNCRECURRENCECOMP_CHANGED] =
+ g_signal_new ("changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GncRecurrenceCompClass, changed),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ //parent_class = g_type_class_peek_parent (klass);
+ //object_class->finalize = gnc_recurrence_finalize;
+}
+
+GType
+gnc_recurrence_comp_get_type()
+{
+ static GType type = 0;
+ if (type == 0) {
+ static GTypeInfo typeinfo = {
+ sizeof(GncRecurrenceCompClass),
+ NULL, NULL,
+ (GClassInitFunc)gnc_recurrence_comp_class_init,
+ NULL, NULL,
+ sizeof(GncRecurrenceComp),
+ 0,
+ (GInstanceInitFunc)gnc_recurrence_comp_init
+ };
+
+ type = g_type_register_static (GTK_TYPE_SCROLLED_WINDOW,
+ "GncRecurrenceComp", &typeinfo, 0);
+ }
+ return type;
+}
+
+GtkWidget *
+gnc_recurrence_comp_new()
+{
+ GncRecurrenceComp *grc;
+ grc = g_object_new(gnc_recurrence_comp_get_type(), NULL);
+ return GTK_WIDGET(grc);
+}
+
+/* ========================= END OF FILE =========================== */
--- /dev/null
+++ src/gnome-utils/test/test-gnc-recurrence.c
@@ -0,0 +1,106 @@
+/* Copyright (C) 2005, Chris Shoemaker <c.shoemaker at cox.net>
+ * This file is free software. See COPYING for details. */
+
+/* test-gnc-recurrence.c:
+ *
+ * When you close the window, a text description of the
+ * recurrence is printed.
+ *
+ */
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <stdio.h>
+#include "gnc-recurrence.h"
+#include "Recurrence.h"
+
+static GtkWidget * mainwin;
+static GncRecurrence *rw;
+static GncRecurrenceComp *grc;
+
+static void get_list(GtkWidget *w)
+{
+ gchar *s;
+ GList *rlist;
+ rlist = gnc_recurrence_comp_get_list(grc);
+ s = recurrenceListToString(rlist);
+ printf("%s\n", s);
+
+ g_free(s);
+ g_list_free(rlist);
+}
+
+static void changed(GtkWidget *widget)
+{
+ gchar *s;
+ const Recurrence *r;
+
+ r = gnc_recurrence_get(rw);
+ s = recurrenceToString(r);
+ printf("%s\n", s);
+ g_free(s);
+}
+
+static void die(GtkWidget *widget)
+{
+ gtk_main_quit();
+}
+
+static void show_gnc_recurrence()
+{
+ GDate d;
+ Recurrence *r;
+ GList *rl = NULL;
+
+ rw = GNC_RECURRENCE(gnc_recurrence_new());
+
+ r = g_new(Recurrence, 1);
+ g_list_append(rl, r);
+ g_date_set_dmy(&d, 17, 4, 2005);
+ recurrenceSet(r, 1, PERIOD_WEEK, &d);
+
+ gnc_recurrence_set(rw, r);
+ g_free(r);
+
+ gtk_container_add(GTK_CONTAINER(mainwin), GTK_WIDGET(rw));
+ g_signal_connect(rw, "changed", G_CALLBACK(changed), NULL);
+}
+
+static void show_gnc_recurrence_comp()
+{
+ GList *rlist = NULL;
+ Recurrence r[2];
+
+ grc = (GncRecurrenceComp *)gnc_recurrence_comp_new();
+
+ gtk_container_add(GTK_CONTAINER(mainwin), GTK_WIDGET(grc));
+
+ recurrenceSet(&r[0], 1, PERIOD_MONTH, NULL);
+ rlist = g_list_append(rlist, &r[0]);
+ recurrenceSet(&r[1], 1, PERIOD_YEAR, NULL);
+ rlist = g_list_append(rlist, &r[1]);
+
+ gnc_recurrence_comp_set_list(grc, rlist);
+ g_list_free(rlist);
+
+ g_signal_connect(grc, "changed", G_CALLBACK(get_list), NULL);
+ //rlist = gnc_recurrence_comp_get_list(grc);
+}
+
+
+int main (int argc, char ** argv)
+{
+ gtk_init(&argc, &argv);
+
+ mainwin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ g_signal_connect(mainwin, "delete-event", G_CALLBACK(die), NULL);
+
+ if (argc > 1)
+ show_gnc_recurrence();
+ else
+ show_gnc_recurrence_comp();
+
+ gtk_widget_show_all(mainwin);
+ gtk_main();
+ return 0;
+}
--- /dev/null
+++ src/gnome-utils/test/test-gnc-dialog.c
@@ -0,0 +1,145 @@
+/* Copyright (C) 2005, Chris Shoemaker <c.shoemaker at cox.net>
+ * This file is free software. See COPYING for details. */
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <stdio.h>
+#include "gnc-recurrence.h"
+#include "Recurrence.h"
+#include "qofbook.h"
+#include "gnc-dialog.h"
+#include "gnc-tree-view-account.h"
+
+//static GtkWidget * mainwin;
+static GncDialog *pw;
+//static QofBook *book;
+
+
+static gboolean apply_cb (GncDialog *pw, gpointer _n)
+{
+ const gchar *s;
+ gdouble d;
+ gpointer p;
+ gboolean b;
+ gint i;
+
+ s = gnc_dialog_get_custom(pw, "SampleEntry");
+ printf("Entry: %s\n", s);
+ s = gnc_dialog_get_string(pw, "SampleLabel");
+ printf("Label: %s\n", s);
+
+ p = gnc_dialog_get_custom(pw, "SampleSpinButton");
+ d = *(double *)p;
+ printf("SpinButton: %f\n", d);
+
+ b = gnc_dialog_get_boolean(pw, "SampleToggleButton");
+ printf("ToggleButton: %s\n", b?"true":"false");
+
+ i = gnc_dialog_get_index(pw, "SampleComboBox");
+ printf("ComboBox: %d\n", i);
+
+ /*
+ gnc_dialog_get(pw, "SampleEntry", &s);
+ gnc_dialog_get(pw, "SampleLabel", &s);
+ printf("Label: %s\n", s);
+ gnc_dialog_get(pw, "SampleSpinButton", &d);
+ printf("SpinButton: %f\n", *d);
+ gnc_dialog_get(pw, "SampleToggleButton", &b);
+ printf("ToggleButton: %s\n", *b?"true":"false");
+ gnc_dialog_get(pw, "SampleComboBox", &i);
+ printf("ComboBox: %d\n", *i);
+ */
+
+ //r = gnc_dialog_get(pw, "SampleCustomGncRecurrence");
+ //s = recurrenceToString(r);
+ //printf("Recurrence: %s\n", s);
+ //g_free(s);
+ return TRUE;
+
+}
+
+static gboolean close_cb (GncDialog *pw, gpointer _n)
+{
+ gtk_widget_destroy(GTK_WIDGET(pw));
+ gtk_main_quit();
+ return TRUE;
+}
+
+static void test_setters(GncDialog *pw)
+{
+ gdouble d = 3.0;
+ //GDate date;
+ //gboolean b = TRUE;
+ //gint i = 2;
+ //Recurrence *r;
+ //GList *rl;
+
+ gnc_dialog_set_custom(pw, "SampleEntry", "entrytest");
+ //gnc_dialog_set(pw, "SampleLabel", "labeltest");
+ gnc_dialog_set_custom(pw, "SampleSpinButton", &d);
+ //gnc_dialog_set(pw, "SampleToggleButton", &b);
+ //gnc_dialog_set(pw, "SampleComboBox", &i);
+
+ /*
+ r = recurrenceNew();
+ entry = recurrenceEntryNew();
+ recurrenceAddEntry(r, entry);
+ g_date_set_dmy(&date, 17, 4, 2005);
+ recurrenceEntrySet(entry, 2, PERIOD_WEEKLY, &date);
+ gnc_dialog_set(pw, "SampleCustomGncRecurrence", r);
+ g_free(r);
+ */
+}
+
+static void init_widgets(GncDialog *pw)
+{
+ //GtkComboBox *cbox;
+ GtkListStore *ls;
+ GtkTreeIter iter;
+ GtkCellRenderer *cell;
+ int i;
+
+ ls = gtk_list_store_new(1, G_TYPE_STRING);
+ for (i = 0; i<5; i++) {
+ gtk_list_store_append(ls, &iter);
+ gtk_list_store_set(ls, &iter, 0, "item", -1);
+ }
+
+ //cbox = GTK_COMBO_BOX(gnc_dialog_get_widget(pw, "SampleComboBox"));
+ //gtk_combo_box_set_model(cbox, GTK_TREE_MODEL(ls));
+
+ cell = gtk_cell_renderer_text_new();
+ //gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(cbox), cell, TRUE);
+ //gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(cbox), cell, "text", 0);
+
+ {
+ /*
+ GncTreeViewAccount *account_view;
+
+ account_view = GNC_TREE_VIEW_ACCOUNT(gnc_dialog_get_widget(pw, "SampleCustomTreeViewAccount"));
+
+ gnc_tree_view_account_configure_columns (account_view, "name", NULL);
+ */
+ }
+}
+
+int main (int argc, char ** argv)
+{
+ // g_log_set_always_fatal( G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING );
+
+ gtk_init(&argc, &argv);
+
+ g_type_init();
+ pw = gnc_dialog_new("budget.glade", "SampleOptions");
+ gnc_dialog_set_cb(pw, apply_cb, close_cb, NULL, NULL);
+
+ gnc_dialog_register_testing_types();
+ init_widgets(pw);
+
+ test_setters(pw);
+ gtk_widget_show_all(GTK_WIDGET(pw));
+
+ gtk_main();
+ return 0;
+
+}
--- /dev/null
+++ src/report/standard-reports/budget.scm
@@ -0,0 +1,376 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; budget.scm: budget report
+;;
+;; (C) 2005 by Chris Shoemaker <c.shoemaker at cox.net>
+;;
+;; based on cash-flow.scm by:
+;; Herbert Thoma <herbie at hthoma.de>
+;;
+;; 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 at gnu.org
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define-module (gnucash report budget))
+(use-modules (gnucash main)) ;; FIXME: delete after we finish modularizing.
+(use-modules (ice-9 slib))
+(use-modules (gnucash gnc-module))
+
+(require 'printf)
+(require 'sort)
+
+(gnc:module-load "gnucash/report/report-system" 0)
+(gnc:module-load "gnucash/gnome-utils" 0) ;for gnc:html-build-url
+
+(define reportname (N_ "Budget Report"))
+
+;; define all option's names so that they are properly defined
+;; in *one* place.
+;;(define optname-from-date (N_ "From"))
+;;(define optname-to-date (N_ "To"))
+
+(define optname-display-depth (N_ "Account Display Depth"))
+(define optname-show-subaccounts (N_ "Always show sub-accounts"))
+(define optname-accounts (N_ "Account"))
+
+(define optname-price-source (N_ "Price Source"))
+(define optname-show-rates (N_ "Show Exchange Rates"))
+(define optname-show-full-names (N_ "Show Full Account Names"))
+
+(define optname-budget (N_ "Budget"))
+
+;; options generator
+(define (budget-report-options-generator)
+ (let ((options (gnc:new-options)))
+
+ (gnc:register-option
+ options
+ (gnc:make-budget-option
+ gnc:pagename-general optname-budget
+ "a" (N_ "Budget")))
+
+ ;; date interval
+ ;;(gnc:options-add-date-interval!
+ ;; options gnc:pagename-general
+ ;; optname-from-date optname-to-date "a")
+
+ (gnc:options-add-price-source!
+ options gnc:pagename-general optname-price-source "c" 'weighted-average)
+
+ ;;(gnc:register-option
+ ;; options
+ ;; (gnc:make-simple-boolean-option
+ ;; gnc:pagename-general optname-show-rates
+ ;; "d" (N_ "Show the exchange rates used") #f))
+
+ (gnc:register-option
+ options
+ (gnc:make-simple-boolean-option
+ gnc:pagename-general optname-show-full-names
+ "e" (N_ "Show full account names (including parent accounts)") #t))
+
+ ;; accounts to work on
+ (gnc:options-add-account-selection!
+ options gnc:pagename-accounts
+ optname-display-depth optname-show-subaccounts
+ optname-accounts "a" 2
+ (lambda ()
+ (gnc:filter-accountlist-type
+ '(bank cash asset stock mutual-fund)
+ (gnc:group-get-subaccounts (gnc:get-current-group))))
+ #f)
+
+ ;; Set the general page as default option tab
+ (gnc:options-set-default-section options gnc:pagename-general)
+
+ options)
+ )
+
+(define (gnc:html-table-add-budget-values!
+ html-table acct-table budget params)
+
+ (define (gnc:html-table-add-budget-line!
+ html-table rownum colnum
+ budget acct exchange-fn)
+ (let* ((num-periods (gnc:budget-get-num-periods budget))
+ (period 0)
+ )
+ (while (< period num-periods)
+ (let* ((bgt-col (+ (* period 2) colnum 1))
+ (act-col (+ 1 bgt-col))
+
+ (comm (gnc:account-get-commodity acct))
+ (numeric-val (gnc:budget-get-account-period-value
+ budget acct period))
+
+ (bgt-val (gnc:make-gnc-monetary
+ comm numeric-val))
+ (numeric-val (gnc:budget-get-account-period-actual-value
+ budget acct period))
+ (act-val (gnc:make-gnc-monetary
+ comm numeric-val))
+ (reverse-balance? (gnc:account-reverse-balance? acct))
+ )
+
+ (cond (reverse-balance? (set! act-val
+ (gnc:monetary-neg act-val))))
+
+
+ (gnc:html-table-set-cell!
+ html-table
+ rownum bgt-col bgt-val)
+
+ (gnc:html-table-set-cell!
+ html-table
+ rownum act-col act-val)
+
+ (set! period (+ period 1))
+ )
+ )
+ )
+ )
+ (define (gnc:html-table-add-budget-headers!
+ html-table colnum budget)
+ (let* ((num-periods (gnc:budget-get-num-periods budget))
+ (period 0)
+ )
+
+ ;; prepend 2 empty rows
+ (gnc:html-table-prepend-row! html-table '())
+ (gnc:html-table-prepend-row! html-table '())
+
+ ;; make the column headers
+ (while (< period num-periods)
+ (let* ((bgt-col (+ (* period 2) colnum 1))
+ (act-col (+ 1 bgt-col))
+ (date (gnc:budget-get-period-start-date budget period))
+ )
+ (gnc:html-table-set-cell!
+ html-table 0 bgt-col (gnc:print-date date))
+
+ (gnc:html-table-set-cell!
+ html-table
+ 1 bgt-col "Bgt")
+
+ (gnc:html-table-set-cell!
+ html-table
+ 1 act-col "Act")
+
+ (set! period (+ period 1))
+ )
+ )
+ )
+ )
+
+ (let* ((num-rows (gnc:html-acct-table-num-rows acct-table))
+ (rownum 0)
+ (numcolumns (gnc:html-table-num-columns html-table))
+ ;;(html-table (or html-table (gnc:make-html-table)))
+ (get-val (lambda (alist key)
+ (let ((lst (assoc-ref alist key)))
+ (if lst (car lst) lst))))
+ ;; WARNING: we implicitly depend here on the details of
+ ;; gnc:html-table-add-account-balances. Specifically, we
+ ;; assume that it makes twice as many columns as it uses for
+ ;; account labels. For now, that seems to be a valid
+ ;; assumption.
+ (colnum (quotient numcolumns 2))
+
+ )
+
+ ''(display (list "colnum: " colnum "numcolumns: " numcolumns))
+ ;; call gnc:html-table-add-budget-line! for each account
+ (while (< rownum num-rows)
+ (let* ((env (append
+ (gnc:html-acct-table-get-row-env acct-table rownum)
+ params))
+ (acct (get-val env 'account))
+ (exchange-fn (get-val env 'exchange-fn))
+ )
+ (gnc:html-table-add-budget-line!
+ html-table rownum colnum
+ budget acct exchange-fn)
+ (set! rownum (+ rownum 1)) ;; increment rownum
+ )
+ ) ;; end of while
+
+ ;; column headers
+ (gnc:html-table-add-budget-headers! html-table colnum budget)
+
+ )
+ ) ;; end of define
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; budget-renderer
+;; set up the document and add the table
+;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (budget-renderer report-obj)
+ (define (get-option pagename optname)
+ (gnc:option-value
+ (gnc:lookup-option
+ (gnc:report-options report-obj) pagename optname)))
+
+ (gnc:report-starting reportname)
+
+ ;; get all option's values
+ (let* ((budget (get-option gnc:pagename-general optname-budget))
+ (display-depth (get-option gnc:pagename-accounts
+ optname-display-depth))
+ (show-subaccts? (get-option gnc:pagename-accounts
+ optname-show-subaccounts))
+ (accounts (get-option gnc:pagename-accounts
+ optname-accounts))
+ (row-num 0) ;; ???
+ (work-done 0)
+ (work-to-do 0)
+ ;;(report-currency (get-option gnc:pagename-general
+ ;; optname-report-currency))
+ (show-full-names? (get-option gnc:pagename-general
+ optname-show-full-names))
+ (separator (gnc:account-separator-char))
+
+ (doc (gnc:make-html-document))
+ ;;(table (gnc:make-html-table))
+ ;;(txt (gnc:make-html-text))
+ )
+
+ ;; is account in list of accounts?
+ (define (same-account? a1 a2)
+ (string=? (gnc:account-get-guid a1) (gnc:account-get-guid a2)))
+
+ (define (same-split? s1 s2)
+ (string=? (gnc:split-get-guid s1) (gnc:split-get-guid s2)))
+
+ (define account-in-list?
+ (lambda (account accounts)
+ (cond
+ ((null? accounts) #f)
+ ((same-account? (car accounts) account) #t)
+ (else (account-in-list? account (cdr accounts))))))
+
+ (define split-in-list?
+ (lambda (split splits)
+ (cond
+ ((null? splits) #f)
+ ((same-split? (car splits) split) #t)
+ (else (split-in-list? split (cdr splits))))))
+
+ (define account-in-alist
+ (lambda (account alist)
+ (cond
+ ((null? alist) #f)
+ ((same-account? (caar alist) account) (car alist))
+ (else (account-in-alist account (cdr alist))))))
+
+ ;; helper for sorting of account list
+ (define (account-full-name<? a b)
+ (string<? (gnc:account-get-full-name a) (gnc:account-get-full-name b)))
+
+ ;; helper for account depth
+ (define (account-get-depth account)
+ (define (account-get-depth-internal account-internal depth)
+ (let ((parent (gnc:account-get-parent-account account-internal)))
+ (if parent
+ (account-get-depth-internal parent (+ depth 1))
+ depth)))
+ (account-get-depth-internal account 1))
+
+ (define (accounts-get-children-depth accounts)
+ (apply max
+ (map (lambda (acct)
+ (let ((children
+ (gnc:account-get-immediate-subaccounts acct)))
+ (if (null? children)
+ 1
+ (+ 1 (accounts-get-children-depth children)))))
+ accounts)))
+ ;; end of defines
+
+ ;; add subaccounts if requested
+ (if show-subaccts?
+ (let ((sub-accounts (gnc:acccounts-get-all-subaccounts accounts)))
+ (for-each
+ (lambda (sub-account)
+ (if (not (account-in-list? sub-account accounts))
+ (set! accounts (append accounts sub-accounts))))
+ sub-accounts)))
+
+ (if (not (or (null? accounts) (null? budget) (not budget)))
+
+ (let* ((tree-depth (if (equal? display-depth 'all)
+ (accounts-get-children-depth accounts)
+ display-depth))
+ ;;(account-disp-list '())
+
+ ;; Things seem to crash if I don't set 'end-date to
+ ;; _something_ but the actual value isn't used.
+ (env (list (list 'end-date (gnc:get-today))
+ (list 'display-tree-depth tree-depth)
+ (list 'depth-limit-behavior 'flatten)
+ ))
+ (acct-table #f)
+ (html-table (gnc:make-html-table))
+ (params '())
+ (report-name (get-option gnc:pagename-general
+ gnc:optname-reportname))
+ )
+
+ (gnc:html-document-set-title!
+ doc (sprintf #f (_ "%s - %s")
+ report-name (gnc:budget-get-name budget)))
+
+ (set! accounts (sort accounts account-full-name<?))
+
+ (set! acct-table
+ (gnc:make-html-acct-table/env/accts env accounts))
+
+ ;; We do this in two steps: First the account names... the
+ ;; add-account-balances will actually compute and add a
+ ;; bunch of current account balances, too, but we'll
+ ;; overwrite them.
+ (set! html-table (gnc:html-table-add-account-balances
+ #f acct-table params))
+
+ ;; ... then the budget values
+ (gnc:html-table-add-budget-values!
+ html-table acct-table budget params)
+
+ ;; hmmm... I expected that add-budget-values would have to
+ ;; clear out any unused columns to the right, out to the
+ ;; table width, since the add-account-balance had put stuff
+ ;; there, but it doesn't seem to matter.
+
+ (gnc:html-document-add-object! doc html-table)
+ )
+
+ ;; error condition: either no accounts or no budgets specified
+ (gnc:html-document-add-object!
+ doc
+ (gnc:html-make-generic-options-warning
+ reportname (gnc:report-id report-obj))))
+
+ (gnc:report-finished)
+ doc))
+
+(gnc:define-report
+ 'version 1
+ 'name reportname
+ 'menu-path (list gnc:menuname-income-expense)
+ 'options-generator budget-report-options-generator
+ 'renderer budget-renderer)
+
More information about the gnucash-changes
mailing list