[patch 0/8] Budget patches

classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

[patch 0/8] Budget patches

Chris Shoemaker
Here's my implementation of budgets.  I've also thrown in the etags patch again just for kicks.

-chris

**************************etags.diff*************************
Subject: [patch 0/8] [etags.diff] fixup tags dependencies

 * Makefile.TAGS |   11 +++++------
 * Makefile.am   |    4 ++--
   - exclude CVS and hidden directories and files from consideration
by [ec]tags
   - allow 'make' to consider age of tags file when determining
whether or not to rerun [ec]tags.
   - fix perpetual remake trigger by excluding toplevel directory
dependency from etags.files rule which touches said directory.

 Makefile.TAGS |   24 ++++++++----------------
 Makefile.am   |    4 ++--
 2 files changed, 10 insertions(+), 18 deletions(-)

**************************recurrence.diff*************************
Subject: [patch 0/8] [recurrence.diff] Add the Recurrence data type to the engine

 * src/engine/Makefile.am            |    2
 * src/engine/Recurrence.c           |  299 +++++++++++++++++++++++++++++
 * src/engine/Recurrence.h           |  121 ++++++++++++
 * src/engine/test/Makefile.am       |    2
 * src/engine/test/test-recurrence.c |  383 ++++++++++++++++++++++++++++++++++++++
   - Add the Recurrence data type to the engine

 src/engine/Makefile.am            |    2
 src/engine/Recurrence.c           |  298 +++++++++++++++++++++++++++++
 src/engine/Recurrence.h           |  121 +++++++++++
 src/engine/test/Makefile.am       |    2
 src/engine/test/test-recurrence.c |  384 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 807 insertions(+)

**************************budget-engine.diff*************************
Subject: [patch 0/8] [budget-engine.diff] Core Budget implementation

 * src/engine/Makefile.am:
   src/engine/gnc-budget.[ch]:
    - budget functionality in the engine

 * src/engine/gnc-engine.[ch]
    - add the budget logging module

 * src/engine/gw-engine-spec.scm
    - g-wrap bindings for budget functions needed by guile


 src/engine/Makefile.am        |   16 -
 src/engine/gnc-budget.c       |  642 +++++++++++++++++-------------------------
 src/engine/gnc-budget.h       |  211 +++++--------
 src/engine/gnc-engine.c       |    2
 src/engine/gnc-engine.h       |   19 +
 src/engine/gw-engine-spec.scm |   83 +++++
 6 files changed, 444 insertions(+), 529 deletions(-)

**************************budget-gui.diff*************************
Subject: [patch 0/8] [budget-gui.diff] GUI support for Budgets

 - Adds GUI support for Budgets

 src/gnome-utils/Makefile.am                |   14
 src/gnome-utils/gnc-dialog.c               |  697 ++++++++++++++++++++++
 src/gnome-utils/gnc-dialog.h               |  171 +++++
 src/gnome-utils/gnc-html.c                 |    1
 src/gnome-utils/gnc-html.h                 |    1
 src/gnome-utils/gnc-icons.h                |    8
 src/gnome-utils/gnc-recurrence.c           |  545 +++++++++++++++++
 src/gnome-utils/gnc-recurrence.h           |   50 +
 src/gnome-utils/gnc-tree-model-budget.c    |  129 ++++
 src/gnome-utils/gnc-tree-model-budget.h    |   55 +
 src/gnome-utils/test/Makefile.am           |   21
 src/gnome-utils/test/test-gnc-dialog.c     |  145 ++++
 src/gnome-utils/test/test-gnc-recurrence.c |  106 +++
 src/gnome/Makefile.am                      |   20
 src/gnome/glade/budget.glade               |  914 +++++++++++++++++++++++++++++
 src/gnome/gnc-plugin-budget.c              |  273 ++++++++
 src/gnome/gnc-plugin-budget.h              |   69 ++
 src/gnome/gnc-plugin-page-budget.c         |  890 ++++++++++++++++++++++++++++
 src/gnome/gnc-plugin-page-budget.h         |   77 ++
 src/gnome/gncmod-budget.c                  |   55 +
 src/gnome/top-level.c                      |    6
 src/gnome/ui/Makefile.am                   |    2
 src/gnome/ui/gnc-plugin-budget-ui.xml      |   13
 src/gnome/ui/gnc-plugin-page-budget-ui.xml |    8
 24 files changed, 4250 insertions(+), 20 deletions(-)

**************************budget-option.diff*************************
Subject: [patch 0/8] [budget-option.diff] Add an option widget for selecting a budget

 * src/app-utils/app-utils.scm:
 * src/app-utils/options.scm:
 * src/gnome-utils/dialog-options.c:
   - Add an option widget to select a particular budget.
   - line wrap fixes
   - add error message for failed option lookup

 src/app-utils/app-utils.scm      |    2
 src/app-utils/options.scm        |   51 +++++++++
 src/gnome-utils/dialog-options.c |  201 ++++++++++++++++++++++++++++++++++++---
 3 files changed, 238 insertions(+), 16 deletions(-)

**************************budget-persist.diff*************************
Subject: [patch 0/8] [budget-persist.diff] Budget persistence for the file backend

 * src/backend/file/Makefile.am
 * src/backend/file/gnc-budget-xml-v2.c
 * src/backend/file/gnc-recurrence-xml-v2.c
 * src/backend/file/gnc-xml.h
 * src/backend/file/io-gncxml-v2.c
 * src/backend/file/io-gncxml-v2.h
 * src/backend/file/sixtp-dom-generators.c
 * src/backend/file/sixtp-dom-generators.h
 * src/backend/file/sixtp-dom-parsers.c
 * src/backend/file/sixtp-dom-parsers.h
 * src/backend/file/sixtp-utils.c
 * src/backend/file/test/Makefile.am
  - add Budget persistence for the file backend

 * src/backend/file/sixtp-dom-generators.c
 * src/backend/file/sixtp-dom-generators.h
  - add 'const' qualifier to GDate pointer in gdate_to_dom_tree()
  - reindent function

 src/backend/file/Makefile.am             |    2
 src/backend/file/gnc-budget-xml-v2.c     |  227 +++++++++++++++++++++++++++++++
 src/backend/file/gnc-recurrence-xml-v2.c |  129 +++++++++++++++++
 src/backend/file/gnc-xml.h               |    4
 src/backend/file/io-gncxml-v2.c          |   74 +++++++++-
 src/backend/file/io-gncxml-v2.h          |    3
 src/backend/file/sixtp-dom-generators.c  |   50 +++---
 src/backend/file/sixtp-dom-generators.h  |    4
 src/backend/file/sixtp-dom-parsers.c     |   27 +++
 src/backend/file/sixtp-dom-parsers.h     |    6
 src/backend/file/sixtp-utils.c           |    3
 src/backend/file/test/Makefile.am        |    8 -
 12 files changed, 500 insertions(+), 37 deletions(-)

**************************budget-report.diff*************************
Subject: [patch 0/8] [budget-report.diff] add a Budget Report

 * src/report/report-system/html-utilities.scm
 * src/report/report-system/report-system.scm
 * src/report/standard-reports/Makefile.am
 * src/report/standard-reports/budget.scm
 * src/report/standard-reports/standard-reports.scm
   - add a Budget Report


 src/report/report-system/html-utilities.scm      |   28 +
 src/report/report-system/report-system.scm       |    3
 src/report/standard-reports/Makefile.am          |    1
 src/report/standard-reports/budget.scm           |  376 +++++++++++++++++++++++
 src/report/standard-reports/standard-reports.scm |    1
 5 files changed, 408 insertions(+), 1 deletion(-)

**************************budget-remove-legacy.diff*************************
Subject: [patch 0/8] [budget-remove-legacy.diff] Remove some legacy budget workbench stuff

 * src/gnome/gnc-plugin-basic-commands.c
 * src/gnome/ui/gnc-plugin-basic-commands-ui.xml
  - remove some budget workbench stuff

 src/gnome/gnc-plugin-basic-commands.c         |   11 -----------
 src/gnome/ui/gnc-plugin-basic-commands-ui.xml |    1 -
 2 files changed, 12 deletions(-)

--
_______________________________________________
gnucash-patches mailing list
[hidden email]
https://lists.gnucash.org/mailman/listinfo/gnucash-patches
Reply | Threaded
Open this post in threaded view
|

[patch 1/8] [etags.diff] fixup tags dependencies

Chris Shoemaker
 * Makefile.TAGS |   11 +++++------
 * Makefile.am   |    4 ++--
   - exclude CVS and hidden directories and files from consideration
by [ec]tags
   - allow 'make' to consider age of tags file when determining
whether or not to rerun [ec]tags.
   - fix perpetual remake trigger by excluding toplevel directory
dependency from etags.files rule which touches said directory.

 Makefile.TAGS |   24 ++++++++----------------
 Makefile.am   |    4 ++--
 2 files changed, 10 insertions(+), 18 deletions(-)

Index: gnucash/Makefile.TAGS
===================================================================
--- gnucash.orig/Makefile.TAGS
+++ gnucash/Makefile.TAGS
@@ -7,25 +7,17 @@
 # i.e. TAGS: etags.files $(shell cat etags.files) doesn't work right
 # because etags.files contents are expanded before it's re-generated.
 
-# The $(shell find . -type d) dependency seems to be pointless -- it
-# always fires.  It appears that someone's always touching something.
-etags.files: $(shell find . -type d)
- find . -path './debian' -prune -o -path './.pc' -prune -o \
- -path '*.#*' -prune -o -name '*.[ch]' -print -o \
- -name '*.scm' -print | sort > etags.files.tmp
- @if cmp --quiet etags.files etags.files.tmp; \
-        then \
-          echo "TAGS file list hasn't changed."; \
-          rm -f etags.files.tmp; \
-        else \
-          echo "TAGS file list has changed."; \
-          mv etags.files.tmp etags.files; \
-        fi
+etags.files: $(shell find . -mindepth 1 -type d ! -name CVS ! -path "*/.*")
+ find . -path '*/.*' -prune -o \( -name '*.[ch]' -o -name '*.scm' \) \
+ -print | sort > etags.files.tmp
+ cmp -s etags.files etags.files.tmp || cp etags.files.tmp etags.files
+ rm -f etags.files.tmp
+ touch etags.files
 
 # we don't need an etags.files dep here b/c you always call this after
 # re-generating etags.files if needed from the top-level Makefile.am.
-etags: $(shell cat etags.files)
+TAGS: $(shell cat etags.files)
  etags `cat etags.files`
 
-ctags: $(shell cat etags.files)
+tags: $(shell cat etags.files)
  ctags `cat etags.files`
Index: gnucash/Makefile.am
===================================================================
--- gnucash.orig/Makefile.am
+++ gnucash/Makefile.am
@@ -97,7 +97,7 @@ etags:
         # make sure etags.files is up to date.
  ${MAKE} -f Makefile.TAGS etags.files
         # now use the contents of etags.files to re-make TAGS if needed.
- ${MAKE} -f Makefile.TAGS etags
+ ${MAKE} -f Makefile.TAGS TAGS
 
 else
 
@@ -112,7 +112,7 @@ ctags:
         # make sure etags.files is up to date.
  ${MAKE} -f Makefile.TAGS etags.files
         # now use the contents of etags.files to re-make TAGS if needed.
- ${MAKE} -f Makefile.TAGS ctags
+ ${MAKE} -f Makefile.TAGS tags
 
 else
 

--
_______________________________________________
gnucash-patches mailing list
[hidden email]
https://lists.gnucash.org/mailman/listinfo/gnucash-patches
Reply | Threaded
Open this post in threaded view
|

[patch 2/8] [recurrence.diff] Add the Recurrence data type to the engine

Chris Shoemaker
In reply to this post by Chris Shoemaker
 * src/engine/Makefile.am            |    2
 * src/engine/Recurrence.c           |  299 +++++++++++++++++++++++++++++
 * src/engine/Recurrence.h           |  121 ++++++++++++
 * src/engine/test/Makefile.am       |    2
 * src/engine/test/test-recurrence.c |  383 ++++++++++++++++++++++++++++++++++++++
   - Add the Recurrence data type to the engine

 src/engine/Makefile.am            |    2
 src/engine/Recurrence.c           |  298 +++++++++++++++++++++++++++++
 src/engine/Recurrence.h           |  121 +++++++++++
 src/engine/test/Makefile.am       |    2
 src/engine/test/test-recurrence.c |  384 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 807 insertions(+)

Index: gnucash/src/engine/Makefile.am
===================================================================
--- gnucash.orig/src/engine/Makefile.am
+++ gnucash/src/engine/Makefile.am
@@ -13,6 +13,7 @@ AM_CFLAGS = \
 libgncmod_engine_la_SOURCES = \
   Account.c \
   FreqSpec.c \
+  Recurrence.c \
   Group.c \
   Period.c \
   Query.c \
@@ -69,6 +70,7 @@ gncincludedir = ${GNC_INCLUDE_DIR}
 gncinclude_HEADERS = \
   Account.h \
   FreqSpec.h \
+  Recurrence.h \
   GNCId.h \
   Group.h \
   Period.h \
Index: gnucash/src/engine/Recurrence.c
===================================================================
--- /dev/null
+++ gnucash/src/engine/Recurrence.c
@@ -0,0 +1,298 @@
+/* Copyright (C) 2005, Chris Shoemaker <[hidden email]>  *
+ * This file is free software.  See COPYING for details.
+ */
+
+#include "config.h"
+#include <time.h>
+#include <glib.h>
+#include <string.h>
+#include "Recurrence.h"
+#include "gnc-date.h"
+#include "qof.h"
+#include "gnc-engine.h"
+
+static QofLogModule log_module = GNC_MOD_ENGINE;
+
+static GDate invalid_gdate;
+
+/* Do not intl. These are used for xml storage. */
+static gchar *period_type_strings[NUM_PERIOD_TYPES] = {
+    "once", "day", "week", "month", "end of month",
+    "nth weekday", "last weekday", "year",
+};
+
+#define VALID_PERIOD_TYPE(pt)  ((0 <= (pt)) && ((pt) < NUM_PERIOD_TYPES))
+
+PeriodType
+recurrenceGetPeriodType(const Recurrence *r)
+{
+    return r ? r->ptype : PERIOD_INVALID;
+}
+
+guint
+recurrenceGetMultiplier(const Recurrence *r)
+{
+    return r ? r->mult : 0;
+}
+
+GDate
+recurrenceGetDate(const Recurrence *r)
+{
+    return r ? r->start : invalid_gdate;
+}
+
+void
+recurrenceSet(Recurrence *r, guint16 mult, PeriodType pt, const GDate *_start)
+{
+    r->ptype = VALID_PERIOD_TYPE(pt) ? pt : PERIOD_MONTH;
+    r->mult = (pt == PERIOD_ONCE) ? 0 : (mult > 0 ? mult : 1);
+
+    if (_start && g_date_valid(_start))
+        r->start = *_start;
+    else
+        g_date_set_time(&r->start, time(NULL));
+
+    /* Some of the unusual period types also specify phase.  For those
+       types, we ensure that the start date agrees with that phase. */
+    switch (r->ptype) {
+    case PERIOD_END_OF_MONTH:
+        g_date_set_day(&r->start, g_date_get_days_in_month
+                       (g_date_get_month(&r->start),
+                        g_date_get_year(&r->start)));
+        break;
+    case PERIOD_LAST_WEEKDAY: {
+        GDateDay dim;
+        dim = g_date_get_days_in_month(g_date_get_month(&r->start),
+                                       g_date_get_year(&r->start));
+        while (dim - g_date_get_day(&r->start) >=7)
+            g_date_add_days(&r->start, 7);
+    } break;
+    case PERIOD_NTH_WEEKDAY:
+        if ((g_date_get_day(&r->start)-1) / 7 == 4) /* Fifth week */
+            r->ptype = PERIOD_LAST_WEEKDAY;
+        break;
+    default: break;
+    }
+}
+
+/* nth_weekday_compare() is a helper function for the
+   PERIOD_{NTH,LAST}_WEEKDAY case.  It returns the offset, in days,
+   from 'next' to the nth weekday specified by the 'start' date (and
+   the period type), in the same month as 'next'.  A negative offset
+   means earlier than 'next'; a zero offset means 'next' *is* the nth
+   weekday in that month; a positive offset means later than
+   'next'. */
+static gint
+nth_weekday_compare(const GDate *start, const GDate *next, PeriodType pt)
+{
+    GDateDay sd, nd;
+    gint matchday, dim;
+
+    nd = g_date_get_day(next);
+    sd = g_date_get_day(start);
+
+    /* matchday has a week part, capped at 3 weeks, and a day part,
+       capped at 7 days, so max(matchday) == 3*7 + 7 == 28. */
+    matchday = 7 * ((sd-1)/7 == 4 ? 3 : (sd-1)/7) +
+        (nd - g_date_get_weekday(next) + g_date_get_weekday(start) + 7) % 7;
+    /* That " + 7" is to avoid negative modulo in case nd < 6. */
+
+    dim = g_date_get_days_in_month(
+        g_date_get_month(next), g_date_get_year(next));
+    if ((dim - matchday) >= 7 && pt == PERIOD_LAST_WEEKDAY)
+        matchday += 7;     /* Go to the fifth week, if needed */
+
+    return matchday - nd;  /* Offset from 'next' to matchday */
+}
+
+
+/* This is the only real algorithm related to recurrences.  It goes:
+   Step 1) Go forward one period from the reference date.
+   Step 2) Back up to align to the phase of the start date.
+*/
+void
+recurrenceNextInstance(const Recurrence *r, const GDate *ref, GDate *next)
+{
+    PeriodType pt;
+    const GDate *start;
+    guint mult;
+
+    g_return_if_fail(r && ref);
+    g_return_if_fail(g_date_valid(&r->start) && g_date_valid(ref));
+
+    /* If the ref date comes before the start date then the next
+       occurrence is always the start date, and we're done. */
+    start = &r->start;
+    if (g_date_compare(ref, start) < 0) {
+        g_date_set_julian(next, g_date_get_julian(start));
+        return;
+    }
+    g_date_set_julian(next, g_date_get_julian(ref)); /* start at refDate */
+
+    /* Step 1: move FORWARD one period, passing exactly one occurrence. */
+    mult = r->mult;
+    pt = r->ptype;
+    switch (pt) {
+    case PERIOD_YEAR:
+        mult *= 12;             /* fall-through */
+    case PERIOD_MONTH:
+    case PERIOD_NTH_WEEKDAY:
+    case PERIOD_LAST_WEEKDAY:
+    case PERIOD_END_OF_MONTH:
+        /* Takes care of short months. */
+        if ( g_date_is_last_of_month(next) ||
+             ((pt == PERIOD_MONTH || pt == PERIOD_YEAR) &&
+              g_date_get_day(next) >= g_date_get_day(start)) ||
+             ((pt == PERIOD_NTH_WEEKDAY || pt == PERIOD_LAST_WEEKDAY) &&
+              nth_weekday_compare(start, next, pt) <= 0) )
+            g_date_add_months(next, mult);
+        else
+            /* one fewer month fwd because of the occurrence in this month */
+            g_date_add_months(next, mult - 1);
+        break;
+    case PERIOD_WEEK:
+        mult *= 7;              /* fall-through */
+    case PERIOD_DAY:
+        g_date_add_days(next, mult);
+        break;
+    case PERIOD_ONCE:
+        g_date_clear(next, 1);  /* We already caught the case where ref is */
+        return;                 /* earlier than start, so this is invalid. */
+    default:
+        PERR("Invalid period type");
+    }
+
+    /* Step 2: Back up to align to the base phase. To ensure forward
+       progress, we never subtract as much as we added (x % mult < mult). */
+    switch (pt) {
+    case PERIOD_YEAR:
+    case PERIOD_MONTH:
+    case PERIOD_NTH_WEEKDAY:
+    case PERIOD_LAST_WEEKDAY:
+    case PERIOD_END_OF_MONTH: {
+        guint dim, n_months;
+
+        n_months = 12 * (g_date_get_year(next) - g_date_get_year(start)) +
+            (g_date_get_month(next) - g_date_get_month(start));
+        g_date_subtract_months(next, n_months % mult);
+
+        /* Ok, now we're in the right month, so we just have to align
+           the day in one of the three possible ways. */
+        dim = g_date_get_days_in_month(g_date_get_month(next),
+                                       g_date_get_year(next));
+        if (pt == PERIOD_NTH_WEEKDAY || pt == PERIOD_LAST_WEEKDAY)
+            g_date_add_days(next, nth_weekday_compare(start, next, pt));
+        else if (pt == PERIOD_END_OF_MONTH || g_date_get_day(start) >= dim)
+            g_date_set_day(next, dim);  /* last day in the month */
+        else
+            g_date_set_day(next, g_date_get_day(start)); /*same day as start*/
+
+    } break;
+    case PERIOD_WEEK:
+    case PERIOD_DAY:
+        g_date_subtract_days(next, g_date_days_between(start, next) % mult);
+        break;
+    default:
+        PERR("Invalid period type");
+    }
+}
+
+/* Zero-based index */
+void
+recurrenceNthInstance(const Recurrence *r, guint n, GDate *date)
+{
+    GDate ref;
+    guint i;
+
+    for (*date = ref = r->start, i = 0; i < n; i++) {
+        recurrenceNextInstance(r, &ref, date);
+        ref = *date;
+    }
+}
+
+void
+recurrenceListNextInstance(const GList *rlist, const GDate *ref, GDate *next)
+{
+    const GList *iter;
+    GDate nextSingle;  /* The next date for an individual recurrence */
+
+    g_return_if_fail(rlist && ref && next && g_date_valid(ref));
+
+    g_date_clear(next, 1);
+    for (iter = rlist; iter; iter = iter->next) {
+        const Recurrence *r = iter->data;
+
+        recurrenceNextInstance(r, ref, &nextSingle);
+        if (!g_date_valid(&nextSingle)) continue;
+
+        if (g_date_valid(next))
+            g_date_order(next, &nextSingle); /* swaps dates if needed */
+        else
+            *next = nextSingle; /* first date is always earliest so far */
+    }
+}
+
+/* Caller owns the returned memory */
+gchar *
+recurrenceToString(const Recurrence *r)
+{
+    gchar *tmpDate;
+    gchar *tmpPeriod, *ret;
+
+    g_return_val_if_fail(g_date_valid(&r->start), NULL);
+    tmpDate = g_new0(gchar, MAX_DATE_LENGTH+1);
+    g_date_strftime(tmpDate, MAX_DATE_LENGTH, "%x", &r->start);
+
+    if (r->ptype == PERIOD_ONCE) {
+        ret = g_strdup_printf("once on %s", tmpDate);
+        goto done;
+    }
+
+    tmpPeriod = period_type_strings[r->ptype];
+    if (r->mult > 1)
+        ret = g_strdup_printf("Every %d %ss beginning %s",
+                              r->mult, tmpPeriod, tmpDate);
+    else
+        ret = g_strdup_printf("Every %s beginning %s",
+                              tmpPeriod, tmpDate);
+done:
+    g_free(tmpDate);
+    return ret;
+}
+
+/* caller owns the returned memory */
+gchar *
+recurrenceListToString(const GList *r)
+{
+    const GList *iter;
+    GString *str;
+    gchar *s;
+    g_return_val_if_fail(r, NULL);
+
+    str = g_string_new("");
+    for(iter = r; iter; iter = iter->next){
+        s = recurrenceToString((Recurrence *)iter->data);
+        g_string_append(str, s);
+        g_string_append(str, " + ");
+        g_free(s);
+    }
+    g_string_truncate(str, str->len - 3); /* kill the last " + " */
+    return g_string_free(str, FALSE);
+}
+
+gchar *
+recurrencePeriodTypeToString(PeriodType pt)
+{
+    return VALID_PERIOD_TYPE(pt) ? g_strdup(period_type_strings[pt]) : NULL;
+}
+
+PeriodType
+recurrencePeriodTypeFromString(const gchar *str)
+{
+    int i;
+
+    for (i = 0; i < NUM_PERIOD_TYPES; i++)
+        if (safe_strcmp(period_type_strings[i], str) == 0)
+            return i;
+    return -1;
+}
Index: gnucash/src/engine/Recurrence.h
===================================================================
--- /dev/null
+++ gnucash/src/engine/Recurrence.h
@@ -0,0 +1,121 @@
+/* Recurrence.h:
+ *
+ *   A Recurrence represents the periodic occurrence of dates, with a
+ *   beginning point.  For example, "Every Friday, beginning April 15,
+ *   2005" or "The 1st of every 3rd month, beginning April 1, 2001."
+ *
+ *   Technically, a Recurrence can also represent certain useful
+ *   "almost periodic" date sequences.  For example, "The last day of
+ *   every month, beginning Feb. 28, 2005."
+ *
+ *   The main operation you can perform on a Recurrence is to find the
+ *   earliest date in the sequence of occurrences that is after some
+ *   specified date (often the "previous" occurrence).
+ *
+ *   In addition, you can use a GList of Recurrences to represent a
+ *   sequence containing all the dates in each Recurrence in the list,
+ *   and perform the same "next instance" computation for this
+ *   sequence.
+ *
+ *   Note: Recurrence is similar to FreqSpec, but it represents a
+ *   broader concept than FreqSpec (because it also represents the
+ *   beginning of the recurrence).
+ *
+ * Copyright (C) 2005, Chris Shoemaker <[hidden email]>
+ * This file is free software.  See COPYING for details.
+ */
+
+#ifndef RECURRENCE_H
+#define RECURRENCE_H
+
+#include <glib.h>
+
+typedef enum {
+    PERIOD_ONCE,         /* Not a true period at all, but convenient here. */
+    PERIOD_DAY,
+    PERIOD_WEEK,
+    PERIOD_MONTH,
+    PERIOD_END_OF_MONTH, /* This is actually a period plus a phase. */
+    PERIOD_NTH_WEEKDAY,  /* Also a phase, e.g. Second Tueday.       */
+    PERIOD_LAST_WEEKDAY, /* Also a phase. */
+    PERIOD_YEAR,
+    NUM_PERIOD_TYPES,
+    PERIOD_INVALID = -1,
+} PeriodType;
+
+/* Recurrences represent both the phase and period of a recurring event. */
+
+typedef struct {
+    GDate start;       /* First date in the recurrence; specifies phase. */
+    PeriodType ptype;  /* see PeriodType enum */
+    guint16 mult;      /* a period multiplier */
+} Recurrence;
+
+
+/* recurrenceSet() will enforce internal consistency by overriding
+   inconsistent inputs so that 'r' will _always_ end up being valid
+   recurrence.
+
+     - if the period type is invalid, PERIOD_MONTH is used.
+
+     - if the period type is PERIOD_ONCE, then mult is ignored,
+       otherwise, if mult is zero, then mult of 1 is used.
+
+     - if the date is invalid, the current date is used.
+
+     - if the period type specifies phase, the date is made to agree
+       with that phase:
+
+         - for PERIOD_END_OF_MONTH, the last day of date's month is used.
+
+         - for PERIOD_NTH_WEEKDAY, a fifth weekday converts to a
+           PERIOD_LAST_WEEKDAY
+
+         - for PERIOD_LAST_WEEKDAY, the last day in date's month with
+           date's day-of-week is used.
+
+*/
+void recurrenceSet(Recurrence *r, guint16 mult, PeriodType pt,
+                   const GDate *date);
+
+/* get the fields */
+PeriodType recurrenceGetPeriodType(const Recurrence *r);
+guint recurrenceGetMultiplier(const Recurrence *r);
+GDate recurrenceGetDate(const Recurrence *r);
+
+/* Get the occurence immediately after refDate.
+ *
+ * This function has strict and precise post-conditions:
+ *
+ * Given a valid recurrence and a valid 'refDate', 'nextDate' will be
+ * *IN*valid IFF the period_type is PERIOD_ONCE, and 'refDate' is
+ * later-than or equal to the single occurrence (start_date).
+ *
+ * A valid 'nextDate' will _always_ be:
+ *    - strictly later than the 'refDate', AND
+ *    - later than or equal to the start date of the recurrence, AND
+ *    - exactly an integral number of periods away from the start date
+ *
+ * Furthermore, there will be no date _earlier_ than 'nextDate' for
+ * which the three things above are true.
+ *
+ */
+void recurrenceNextInstance(const Recurrence *r, const GDate *refDate,
+                            GDate *nextDate);
+
+/* Zero-based.  n == 1 gets the instance after the start date. */
+void recurrenceNthInstance(const Recurrence *r, guint n, GDate *date);
+
+/* Get the earliest of the next occurances -- a "composite" recurrence */
+void recurrenceListNextInstance(const GList *r, const GDate *refDate,
+                                GDate *nextDate);
+
+/* These two functions are only for xml storage, not user presentation. */
+gchar *recurrencePeriodTypeToString(PeriodType pt);
+PeriodType recurrencePeriodTypeFromString(const gchar *str);
+
+/* For debugging.  Caller owns the returned string.  Not intl. */
+gchar *recurrenceToString(const Recurrence *r);
+gchar *recurrenceListToString(const GList *rlist);
+
+#endif  /* RECURRENCE_H */
Index: gnucash/src/engine/test/Makefile.am
===================================================================
--- gnucash.orig/src/engine/test/Makefile.am
+++ gnucash/src/engine/test/Makefile.am
@@ -41,6 +41,7 @@ TESTS = \
   test-transaction-reversal \
   test-transaction-voiding \
   test-freq-spec \
+  test-recurrence \
   test-scm-query \
   test-book-merge
   
@@ -63,6 +64,7 @@ noinst_PROGRAMS = \
   test-commodities \
   test-date \
   test-freq-spec \
+  test-recurrence \
   test-guid \
   test-group-vs-book \
   test-load-engine \
Index: gnucash/src/engine/test/test-recurrence.c
===================================================================
--- /dev/null
+++ gnucash/src/engine/test/test-recurrence.c
@@ -0,0 +1,384 @@
+/* Copyright (C) 2005, Chris Shoemaker <[hidden email]>
+ * This file is free software.  See COPYING for details. */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib.h>
+
+#include "test-stuff.h"
+#include "FreqSpec.h"
+#include "Recurrence.h"
+#include "gnc-engine.h"
+#include "qofbook.h"
+
+static QofBook *book;
+static FreqSpec *fs;
+//#define FREQSPECTEST
+
+static void check_valid(GDate *next, GDate *ref, GDate *start,
+                        guint16 mult, PeriodType pt)
+{
+    gboolean valid;
+    gint startToNext;
+
+    valid = g_date_valid(next);
+    if (pt == PERIOD_ONCE && g_date_compare(start, ref) <= 0)
+        do_test(!valid, "incorrectly valid");
+    else
+        do_test(valid, "incorrectly invalid");
+
+    if (!valid) return;
+
+    // FreqSpec.h does claim to offer this.
+    do_test(g_date_compare(ref, next) < 0,
+            "next date not strictly later than ref date");
+    startToNext = g_date_get_julian(next) - g_date_get_julian(start);
+
+    // FreqSpec *doesn't* offer beginning dates.
+#ifndef FREQSPECTEST
+    do_test(startToNext >= 0, "next date is before start date");
+#endif
+
+    // Phase test
+    switch (pt) {
+    case PERIOD_YEAR:
+        do_test((g_date_get_year(next) - g_date_get_year(start)) % mult == 0,
+                "year period phase wrong"); // redundant
+        mult *= 12;
+        // fall-through
+    case PERIOD_END_OF_MONTH:
+#ifdef FREQSPECTEST
+        return;   // FreqSpec doesn't have this case
+#endif
+        if (pt == PERIOD_END_OF_MONTH)
+            do_test(g_date_is_last_of_month(next), "end of month phase wrong");
+        // fall-through
+    case PERIOD_LAST_WEEKDAY:
+    case PERIOD_NTH_WEEKDAY:
+    case PERIOD_MONTH: {
+        gint monthdiff;
+        GDateDay day_start, day_next;
+
+        monthdiff = (g_date_get_month(next) - g_date_get_month(start)) +
+            12 * (g_date_get_year(next) - g_date_get_year(start));
+        do_test(monthdiff % mult == 0, "month or year phase wrong");
+
+        if (pt == PERIOD_NTH_WEEKDAY || pt == PERIOD_LAST_WEEKDAY) {
+            guint sweek, nweek;
+
+            do_test(g_date_get_weekday(next) == g_date_get_weekday(start),
+                    "weekday phase wrong");
+            sweek = (g_date_get_day(start)-1) / 7;
+            nweek = (g_date_get_day(next)-1) / 7;
+
+            /* 3 cases: either the weeks agree, OR 'next' didn't have
+               5 of the weekday that 'start' did, so it's only the
+               4th, OR 'start' didn't have 5 of the weekday that
+               'next' does and we want the LAST weekday, so it's the
+               5th of that weekday */
+            do_test(sweek == nweek ||
+                    (sweek == 4 && nweek == 3 && (g_date_get_day(next) + 7) >
+                    g_date_get_days_in_month(
+                        g_date_get_month(next), g_date_get_year(next))) ||
+                    (sweek == 3 && nweek == 4 && (pt == PERIOD_LAST_WEEKDAY)),
+                    "week of month phase wrong");
+
+        } else {
+            day_start = g_date_get_day(start);
+            day_next = g_date_get_day(next);
+            if (day_start < 28)
+                do_test(day_start == day_next, "dom don't match");
+            else if (pt != PERIOD_END_OF_MONTH) {
+                // the end of month case was already checked above.  near
+                // the end of the month, the days should still agree,
+                // unless they can't because of a short month.
+                do_test(day_start == day_next || g_date_is_last_of_month(next),
+                        "dom don't match and next is not eom");
+            }
+        }
+    }
+        break;
+    case PERIOD_WEEK:
+        mult *= 7;
+        // fall-through
+    case PERIOD_DAY:
+        do_test((startToNext % mult) == 0, "week or day period phase wrong");
+        break;
+    case PERIOD_ONCE:
+        do_test(startToNext == 0, "period once not on start date");
+        break;
+    default:
+        do_test(FALSE, "invalid PeriodType");
+    }
+
+}
+
+#ifdef FREQSPECTEST
+static void convert_pt_to_fs(FreqSpec *fs, guint mult,
+                             PeriodType pt, GDate *start)
+{
+    switch (pt) {
+    case PERIOD_ONCE:
+        xaccFreqSpecSetOnceDate(fs, start);
+        break;
+    case PERIOD_DAY:
+        xaccFreqSpecSetDaily(fs, start, mult);
+        break;
+    case PERIOD_WEEK:
+        xaccFreqSpecSetWeekly(fs, start, mult);
+        break;
+    case PERIOD_MONTH:
+        xaccFreqSpecSetMonthly(fs, start, mult);
+        break;
+    case PERIOD_END_OF_MONTH:
+        // not handled
+    case PERIOD_NTH_WEEKDAY:
+        break;
+    case PERIOD_YEAR:
+        xaccFreqSpecSetMonthly(fs, start, 12*mult);
+        break;
+    default:
+        ;
+    }
+}
+#endif
+
+#define NUM_DATES_TO_TEST 300
+#define NUM_DATES_TO_TEST_REF 300
+#define NUM_MULT_TO_TEST 10
+#define JULIAN_START 2003*365     // years have to be < 10000
+
+/* Mult of zero is usually not valid, but it gets regularized to 1, so
+   the effect is just that we end up testing mult of 1 twice, plus the
+   regularization. */
+static void test_all()
+{
+    Recurrence r;
+    GDate d_start, d_start_reg;
+    GDate d_ref, d_next;
+    guint16 mult, mult_reg;
+    PeriodType pt, pt_reg;
+    gint32 j1, j2;
+    gint i_ref;
+
+    for (pt = PERIOD_ONCE; pt < NUM_PERIOD_TYPES; pt++) {
+        for (j1 = JULIAN_START; j1 < JULIAN_START + NUM_DATES_TO_TEST; j1++) {
+            g_date_set_julian(&d_start, j1);
+            for (i_ref = 0; i_ref < NUM_DATES_TO_TEST_REF; i_ref++) {
+                j2 = (guint32) get_random_int_in_range(1, 1 << 19);
+                g_date_set_julian(&d_ref, j2);
+
+                for (mult = 0; mult < NUM_MULT_TO_TEST; mult++) {
+                    recurrenceSet(&r, mult, pt, &d_start);
+                    pt_reg = recurrenceGetPeriodType(&r);
+                    d_start_reg = recurrenceGetDate(&r);
+                    mult_reg = recurrenceGetMultiplier(&r);
+
+                    recurrenceNextInstance(&r, &d_ref, &d_next);
+#ifdef FREQSPECTEST
+                    convert_pt_to_fs(fs, mult_reg, pt_reg, &d_start_reg);
+                    xaccFreqSpecGetNextInstance(fs, &d_ref, &d_next);
+#endif
+                    check_valid(&d_next, &d_ref, &d_start_reg,
+                                mult_reg, pt_reg);
+
+                }
+            }
+        }
+    }
+}
+
+static gboolean test_equal(GDate *d1, GDate *d2)
+{
+    if (!do_test(g_date_compare(d1, d2) == 0, "dates don't match")) {
+        gchar s1[21];
+        gchar s2[21];
+        g_date_strftime(s1, 20, "%x", d1);
+        g_date_strftime(s2, 20, "%x", d2);
+
+        printf("%s != %s\n", s1, s2);
+        return FALSE;
+    }
+    return TRUE;
+}
+
+
+static void test_specific(PeriodType pt, guint16 mult,
+                          GDateMonth sm, GDateDay sd, GDateYear sy,
+                          GDateMonth rm, GDateDay rd, GDateYear ry,
+                          GDateMonth nm, GDateDay nd, GDateYear ny)
+{
+    GDate start;
+    GDate ref, next, true_next;
+    Recurrence r;
+
+    g_date_set_dmy(&start, sd, sm, sy);
+    g_date_set_dmy(&ref, rd, rm, ry);
+    g_date_set_dmy(&true_next, nd, nm, ny);
+
+
+    recurrenceSet(&r, mult, pt, &start);
+    recurrenceNextInstance(&r, &ref, &next);
+#ifdef FREQSPECTEST
+    convert_pt_to_fs(fs, mult, pt, &start);
+    xaccFreqSpecGetNextInstance(fs, &ref, &next);
+#endif
+
+    check_valid(&next, &ref, &start, mult, pt);
+    if (!test_equal(&next, &true_next)) {
+        gchar s1[21], s2[21], s3[21];
+        g_date_strftime(s1, 20, "%x", &start);
+        g_date_strftime(s2, 20, "%x", &ref);
+        g_date_strftime(s3, 20, "%x", &true_next);
+        printf("pt = %d; mult = %d; start = %s; ref = %s; true_next = %s\n",
+               pt, mult, s1, s2, s3);
+    }
+}
+
+#if 0
+static void test_nth(GDateMonth sm, GDateDay sd, GDateYear sy,
+                     GDateMonth nm, GDateDay nd, GDateYear ny,
+                     gint diff, PeriodType pt)
+{
+    GDate start, next;
+    gint d;
+
+    g_date_set_dmy(&start, sd, sm, sy);
+    g_date_set_dmy(&next, nd, nm, ny);
+
+    d = nth_weekday_compare(&start, &next, pt);
+    do_test(d == diff, "nth");
+}
+
+static void test_nth_compare()
+{
+    test_nth(4,1,2005,   4,2,2005, -1, PERIOD_NTH_WEEKDAY);
+    test_nth(4,1,2005,   4,4,2005, -3, PERIOD_NTH_WEEKDAY);
+    test_nth(4,1,2005,   4,7,2005, -6, PERIOD_NTH_WEEKDAY);
+    test_nth(4,1,2005,   4,8,2005, -7, PERIOD_NTH_WEEKDAY);
+    test_nth(4,1,2005,   4,14,2005, -13, PERIOD_NTH_WEEKDAY);
+    test_nth(4,1,2005,   4,30,2005, -29, PERIOD_NTH_WEEKDAY);
+    test_nth(4,1,2005,   5,1,2005, 5, PERIOD_NTH_WEEKDAY);
+    test_nth(4,1,2005,   5,5,2005, 1, PERIOD_NTH_WEEKDAY);
+    test_nth(4,1,2005,   5,6,2005, 0, PERIOD_NTH_WEEKDAY);
+    test_nth(4,1,2005,   5,7,2005, -1, PERIOD_NTH_WEEKDAY);
+    test_nth(4,1,2005,   5,8,2005, -2, PERIOD_NTH_WEEKDAY);
+    test_nth(4,1,2005,   5,21,2005, -15, PERIOD_NTH_WEEKDAY);
+
+
+    test_nth(4,6,2005,   4,1,2005, 5, PERIOD_NTH_WEEKDAY);
+    test_nth(4,6,2005,   4,4,2005, 2, PERIOD_NTH_WEEKDAY);
+    test_nth(4,6,2005,   4,6,2005, 0, PERIOD_NTH_WEEKDAY);
+    test_nth(4,6,2005,   4,9,2005, -3, PERIOD_NTH_WEEKDAY);
+    test_nth(4,6,2005,   4,11,2005, -5, PERIOD_NTH_WEEKDAY);
+    test_nth(4,6,2005,   4,13,2005, -7, PERIOD_NTH_WEEKDAY);
+    test_nth(4,6,2005,   4,14,2005, -8, PERIOD_NTH_WEEKDAY);
+    test_nth(4,6,2005,   4,29,2005, -23, PERIOD_NTH_WEEKDAY);
+
+    test_nth(4,12,2005,   4,1,2005, 11, PERIOD_NTH_WEEKDAY);
+    test_nth(4,12,2005,   4,4,2005, 8, PERIOD_NTH_WEEKDAY);
+    test_nth(4,12,2005,   4,11,2005, 1, PERIOD_NTH_WEEKDAY);
+    test_nth(4,12,2005,   4,12,2005, 0, PERIOD_NTH_WEEKDAY);
+    test_nth(4,12,2005,   4,13,2005, -1, PERIOD_NTH_WEEKDAY);
+    test_nth(4,12,2005,   4,17,2005, -5, PERIOD_NTH_WEEKDAY);
+    test_nth(4,12,2005,   4,19,2005, -7, PERIOD_NTH_WEEKDAY);
+    test_nth(4,12,2005,   4,28,2005, -16, PERIOD_NTH_WEEKDAY);
+
+    test_nth(4,29,2005,   4,30,2005, -1, PERIOD_LAST_WEEKDAY);
+    test_nth(4,29,2005,   5,1,2005, 26, PERIOD_LAST_WEEKDAY);
+    test_nth(4,29,2005,   7,9,2005, 20, PERIOD_LAST_WEEKDAY);
+    test_nth(4,29,2005,   7,31,2005, -2, PERIOD_LAST_WEEKDAY);
+
+    test_nth(4,28,2005,   4,30,2005, -2, PERIOD_LAST_WEEKDAY);
+    test_nth(4,28,2005,   5,1,2005, 25, PERIOD_LAST_WEEKDAY);
+    test_nth(4,28,2005,   7,9,2005, 19, PERIOD_LAST_WEEKDAY);
+    test_nth(4,28,2005,   7,31,2005, -3, PERIOD_LAST_WEEKDAY);
+    test_nth(4,28,2005,   9,21,2005, 8, PERIOD_LAST_WEEKDAY);
+
+}
+#endif
+static void test_some()
+{
+    test_specific(PERIOD_NTH_WEEKDAY, 1, 4,1,2005,    4,2,2005,  5,6,2005);
+    test_specific(PERIOD_NTH_WEEKDAY, 1, 7,14,2005,   11,15,2005,  12,8,2005);
+    test_specific(PERIOD_NTH_WEEKDAY, 1, 7,14,2005,   11,5,2005,  11,10,2005);
+    test_specific(PERIOD_NTH_WEEKDAY, 1, 4,1,2005,    4,2,2005,  5,6,2005);
+    test_specific(PERIOD_NTH_WEEKDAY, 1, 4,1,2005,    4,2,2005,  5,6,2005);
+
+    test_specific(PERIOD_LAST_WEEKDAY, 1, 4,29,2005,    4,30,2005,  5,27,2005);
+    test_specific(PERIOD_LAST_WEEKDAY, 1, 4,29,2005,    5,1,2005,  5,27,2005);
+    test_specific(PERIOD_LAST_WEEKDAY, 1, 4,29,2005,    7,9,2005,  7,29,2005);
+    test_specific(PERIOD_LAST_WEEKDAY, 1, 4,29,2005,    6,30,2005,  7,29,2005);
+    test_specific(PERIOD_LAST_WEEKDAY, 1, 4,29,2005,    7,31,2005,  8,26,2005);
+
+    test_specific(PERIOD_NTH_WEEKDAY, 2, 4,27,2005,    4,27,2005,  6,22,2005);
+    //exit(1);
+    //return;
+    test_specific(PERIOD_YEAR,          3,   9,8,838,    6,30,1094,  9,8,1096);
+    test_specific(PERIOD_YEAR,          2,   9,8,838,    6,30,1094,  9,8,1094);
+    test_specific(PERIOD_YEAR,          1,   1,10,1000,  1,5,1002,  1,10,1002);
+    //return;
+    test_specific(PERIOD_MONTH, 1,     1,12,1,    2,6,1,    2,12,1);
+
+    test_specific(PERIOD_MONTH, 1,     1,12,1,    2,12,1,   3,12,1);
+    test_specific(PERIOD_MONTH, 1,     1,12,1,    2,20,1,   3,12,1);
+    test_specific(PERIOD_MONTH, 1,     1,30,1,    2,28,1,   3,30,1);
+    test_specific(PERIOD_MONTH, 1,     1,30,1,    2,27,1,   2,28,1);
+    test_specific(PERIOD_MONTH, 1,     2,28,1,    3,30,1,   4,28,1);
+
+#ifdef FREQSPECTEST
+    test_specific(PERIOD_MONTH, 3,    12, 12, 1,  2, 1, 1,    3, 12, 1);
+#else
+    test_specific(PERIOD_END_OF_MONTH, 1,   2,28,1,    3,30,1,   3,31,1);
+    test_specific(PERIOD_END_OF_MONTH, 5,   4,30,1,    4,21,1,  4,30,1);
+    test_specific(PERIOD_END_OF_MONTH, 5,   2,28,1,    5,21,1,  7,31,1);
+#endif
+    test_specific(PERIOD_YEAR,          7,   6,8,199,    9,10,1338,  6,8,1340);
+    test_specific(PERIOD_YEAR,          2,   9,8,838,    6,30,1094,  9,8,1094);
+
+    test_specific(PERIOD_YEAR,1,    5,2,13, 1,11,101,   5,2,101);
+    test_specific(PERIOD_DAY, 7,    4,1,2000,    4,8,2000,  4,15,2000);
+}
+
+static void test_use()
+{
+    Recurrence *r;
+
+    r = g_new(Recurrence, 1);
+    do_test(r != NULL, "allocation");
+    g_free(r);
+}
+
+static void test_main()
+{
+
+    book = qof_book_new ();
+    fs = xaccFreqSpecMalloc(book);
+
+    test_use();
+
+    test_some();
+
+    test_all();
+
+    xaccFreqSpecFree(fs);
+    qof_book_destroy (book);
+}
+
+
+int
+main (int argc, char **argv)
+{
+    g_log_set_always_fatal( G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING );
+
+#if 0
+    set_success_print(TRUE);
+#endif
+
+    test_main();
+
+    print_test_results();
+    return get_rv();
+}

--
_______________________________________________
gnucash-patches mailing list
[hidden email]
https://lists.gnucash.org/mailman/listinfo/gnucash-patches
Reply | Threaded
Open this post in threaded view
|

[patch 3/8] [budget-engine.diff] Core Budget implementation

Chris Shoemaker
In reply to this post by Chris Shoemaker
 * src/engine/Makefile.am:
   src/engine/gnc-budget.[ch]:
    - budget functionality in the engine

 * src/engine/gnc-engine.[ch]
    - add the budget logging module

 * src/engine/gw-engine-spec.scm
    - g-wrap bindings for budget functions needed by guile


 src/engine/Makefile.am        |   16 -
 src/engine/gnc-budget.c       |  642 +++++++++++++++++-------------------------
 src/engine/gnc-budget.h       |  211 +++++--------
 src/engine/gnc-engine.c       |    2
 src/engine/gnc-engine.h       |   19 +
 src/engine/gw-engine-spec.scm |   83 +++++
 6 files changed, 444 insertions(+), 529 deletions(-)

Index: gnucash/src/engine/Makefile.am
===================================================================
--- gnucash.orig/src/engine/Makefile.am
+++ gnucash/src/engine/Makefile.am
@@ -8,6 +8,7 @@ AM_CFLAGS = \
  -I${top_srcdir}/src \
  -I${top_srcdir}/src/gnc-module \
  -I${top_srcdir}/src/business/business-core/ \
+ -I${top_srcdir}/src/core-utils \
  ${GNUCASH_ENGINE_CFLAGS}
 
 libgncmod_engine_la_SOURCES = \
@@ -28,10 +29,6 @@ libgncmod_engine_la_SOURCES = \
   cap-gains.c \
   cashobjects.c \
   gnc-associate-account.c \
-  gnc-budget-book.c \
-  gnc-budget-cat.c \
-  gnc-budget-period-value.c \
-  gnc-budget-period.c \
   gnc-budget.c \
   gnc-commodity.c \
   gnc-date.c \
@@ -62,7 +59,7 @@ libgncmod_engine_la_SOURCES = \
   qofobject.c \
   qofquery.c \
   qofquerycore.c \
-  qofsession.c
+  qofsession.c
 
 EXTRA_libgncmod_engine_la_SOURCES = iso-4217-currencies.c
 
@@ -92,10 +89,6 @@ gncinclude_HEADERS = \
   glib-helpers.h \
   gnc-associate-account.h \
   gnc-book.h \
-  gnc-budget-book.h \
-  gnc-budget-cat.h \
-  gnc-budget-period-value.h \
-  gnc-budget-period.h \
   gnc-budget.h \
   gnc-commodity.h \
   gnc-date.h \
@@ -142,11 +135,6 @@ noinst_HEADERS = \
   SX-book.h \
   SX-ttinfo.h \
   TransactionP.h \
-  gnc-budget-book-p.h \
-  gnc-budget-cat-p.h \
-  gnc-budget-period-value-p.h \
-  gnc-budget-period-p.h \
-  gnc-budget-p.h \
   gnc-event-p.h \
   gnc-hooks-scm.h \
   gnc-lot.h \
Index: gnucash/src/engine/gnc-budget.c
===================================================================
--- gnucash.orig/src/engine/gnc-budget.c
+++ gnucash/src/engine/gnc-budget.c
@@ -1,6 +1,7 @@
 /********************************************************************\
- * gnc-budget.c -- Implementation of the top level Budgeting API's.     *
+ * gnc-budget.c -- Implementation of the top level Budgeting API's. *
  * Copyright (C) 04 sep 2003    Darin Willits <[hidden email]>    *
+ * Copyright (C) 2005  Chris Shoemaker <[hidden email]>        *
  *                                                                  *
  * This program is free software; you can redistribute it and/or    *
  * modify it under the terms of the GNU General Public License as   *
@@ -21,492 +22,375 @@
  *                                                                  *
 \********************************************************************/
 
-/** @file gnc-budget.c
- *  @brief Implementation of the top level budgeting API's.
- *  @author Created by Darin Willits 04 sep 2003
- *  @author Copyright (c) 2003 Darin Willits <[hidden email]>
- *
- *
- */
-
-// Includes
-#include "config.h"
-#include <stdio.h>
-
-#include "gnc-budget-p.h"
-#include "qofbook.h"
-#include "qofbook-p.h"
-#include "qofid-p.h"
-#include "gnc-engine.h"
-#include "gnc-engine-util.h"
-#include "gnc-event-p.h"
-#include "Group.h"
+#include <glib.h>
+#include <glib/gprintf.h>
+
+#include "qof.h"
+
 #include "Account.h"
-#include "gnc-budget-period.h"
+#include "Group.h"
 
+#include "gnc-budget.h"
+#include "gnc-event-p.h"
+#include "gnc-commodity.h"
+#include "gnc-trace.h"
+#include "gnc-gdate-utils.h"
+
+static QofLogModule log_module = GNC_MOD_ENGINE;
 
-static void budget_fill_categories(GncBudget* budget);
+struct gnc_budget_private{
+    QofInstance inst;
+
+    gchar* name;
+    gchar* description;
+    Recurrence recurrence;
+    guint  num_periods;
+};
 
-GncBudget* gnc_budget_new(QofBook *book)
+GncBudget*
+gnc_budget_new(QofBook *book)
 {
     GncBudget* budget;
-    GDate date;
-    GString* freqStr;
 
     g_return_val_if_fail(book, NULL);
 
+    ENTER(" ");
     budget = g_new0(GncBudget, 1);
-    
-    
     qof_instance_init (&budget->inst, GNC_ID_BUDGET, book);
-    //budget->entity_table = qof_book_get_entity_table (book);
-    //qof_entity_guid_new (budget->entity_table, &budget->guid);
-    //qof_entity_store( budget->entity_table, budget,
-    //                &budget->guid, GNC_ID_BUDGET );
-    
-    budget->book = book;
-
-    /* Create the default period frequency.*/
-    budget->period_freq = xaccFreqSpecMalloc(book);
-    
-    g_date_clear(&date, 1);
-    g_date_set_time(&date, time(NULL));
-    xaccFreqSpecSetMonthly(budget->period_freq, &date, 1);
-    xaccFreqSpecSetUIType(budget->period_freq, UIFREQ_MONTHLY);
-
-    freqStr = g_string_sized_new(16);
-    xaccFreqSpecGetFreqStr(budget->period_freq, freqStr);
-    //printf("Category Freq: %s\n", freqStr->str);
-
-    
-    
-    /* fill the categories based on the current account hierarchy.
-     * FIXME: This should probably not be here but rather be a separate
-     * operation with a public interface.  For now it is convienent.*/
-    budget_fill_categories(budget);
 
+    recurrenceSet(&budget->recurrence, 1, PERIOD_MONTH, NULL);
+
+    gnc_budget_set_name(budget, "Unnamed Budget");
+    gnc_budget_set_description(budget, "");
+    gnc_budget_set_num_periods(budget, 12);
 
     gnc_engine_gen_event( &budget->inst.entity, GNC_EVENT_CREATE );
 
+    LEAVE(" ");
     return budget;
 }
 
-void gnc_budget_delete(GncBudget* budget)
+static void
+remove_bgt_line_items(QofEntity *act, gpointer bgt)
+{
+    KvpFrame *frame;
+    const GUID *guid;
+    gchar guidbuf[GUID_ENCODING_LENGTH+1];
+
+    frame = qof_instance_get_slots(QOF_INSTANCE(bgt));
+    guid = qof_entity_get_guid(QOF_ENTITY(act));
+    guid_to_string_buff(guid, guidbuf);
+    kvp_frame_delete(kvp_frame_get_frame(frame, guidbuf));
+}
+
+static void
+gnc_budget_remove_all_line_items(GncBudget *budget)
 {
-    if(budget == NULL){
+    QofBook *book;
+    QofCollection *col;
+
+    g_return_if_fail(GNC_IS_BUDGET(budget));
+
+    book = qof_instance_get_book(QOF_INSTANCE(budget));
+    col = qof_book_get_collection(book, GNC_ID_ACCOUNT);
+    qof_collection_foreach(col, remove_bgt_line_items, (gpointer) budget);
+}
+
+void
+gnc_budget_free(GncBudget* budget)
+{
+    if (budget == NULL)
         return;
-    }
-    
+
+    g_return_if_fail(GNC_IS_BUDGET(budget));
+    gnc_budget_remove_all_line_items(budget);
+
     /* We first send the message that this object is about to be
      * destroyed so that any GUI elements can remove it before it is
-     * actually gone.
-     */
+     * actually gone. */
     gnc_engine_gen_event( &budget->inst.entity, GNC_EVENT_DESTROY);
-    
-    //qof_entity_remove(budget->entity_table, &budget->guid);
 
-    if(budget->name){
+    if (budget->name)
         g_free(budget->name);
-    }
-    
-    if(budget->description){
+
+    if (budget->description)
         g_free(budget->description);
-    }
 
     qof_instance_release (&budget->inst);
     g_free(budget);
 }
 
-void gnc_budget_set_name(GncBudget* budget, const gchar* name)
+void
+gnc_budget_set_name(GncBudget* budget, const gchar* name)
 {
-    g_return_if_fail( name != NULL );
-    if ( budget->name != NULL ) {
-        g_free( budget->name );
-        budget->name = NULL;
-    }
-    budget->name = g_strdup( name );
+    g_return_if_fail(GNC_IS_BUDGET(budget));
+    g_return_if_fail(name);
+
+    if (budget->name)
+        g_free(budget->name);
+    budget->name = g_strdup(name);
+    gnc_engine_gen_event( &budget->inst.entity, GNC_EVENT_MODIFY);
 }
 
-gchar* gnc_budget_get_name(GncBudget* budget)
+const gchar*
+gnc_budget_get_name(GncBudget* budget)
 {
-    if(budget == NULL){
-        return NULL;
-    }
+    g_return_val_if_fail(GNC_IS_BUDGET(budget), NULL);
     return budget->name;
 }
 
-void gnc_budget_set_description(GncBudget* budget, const gchar* description)
+void
+gnc_budget_set_description(GncBudget* budget, const gchar* description)
 {
-    g_return_if_fail( description != NULL );
-    if ( budget->description != NULL ) {
+    g_return_if_fail(GNC_IS_BUDGET(budget));
+    g_return_if_fail(description);
+
+    if (budget->description)
         g_free( budget->description);
-        budget->description = NULL;
-    }
     budget->description = g_strdup(description);
+    gnc_engine_gen_event( &budget->inst.entity, GNC_EVENT_MODIFY);
 }
 
-gchar* gnc_budget_get_description(GncBudget* budget)
+const gchar*
+gnc_budget_get_description(GncBudget* budget)
 {
-    if(budget == NULL){
-        return NULL;
-    }
+    g_return_val_if_fail(GNC_IS_BUDGET(budget), NULL);
     return budget->description;
 }
 
-
-
-
-void gnc_budget_set_period_frequency(GncBudget* budget, FreqSpec* period)
+void
+gnc_budget_set_recurrence(GncBudget *budget, const Recurrence *r)
 {
-    if(budget == NULL){
-        return;
-    }
-    
-    if((budget->period_freq == period) || (period == NULL)){
-        return;
-    }
-
-    if(budget->period_freq != NULL){
-        /* Delete the existing object before setting the new one. */
-        xaccFreqSpecFree(budget->period_freq);
-    }
-
-    budget->period_freq = period;
+    g_return_if_fail(budget && r);
+    budget->recurrence = *r;
+    gnc_engine_gen_event(&budget->inst.entity, GNC_EVENT_MODIFY);
 }
 
-FreqSpec* gnc_budget_get_period_frequency(GncBudget* budget)
+const Recurrence *
+gnc_budget_get_recurrence(GncBudget *budget)
 {
-    if(budget == NULL){
-        return NULL;
-    }
-
-    return budget->period_freq;
+    g_return_val_if_fail(budget, NULL);
+    return (&budget->recurrence);
 }
 
-
-
-
-void gnc_budget_set_start_date(GncBudget* budget, GDate* date)
+const GUID*
+gnc_budget_get_guid(GncBudget* budget)
 {
-    if(budget == NULL){
-        return;
-    }
-    budget->start_date = *date;
+    g_return_val_if_fail(budget, NULL);
+    g_return_val_if_fail(GNC_IS_BUDGET(budget), NULL);
+    return qof_entity_get_guid(QOF_ENTITY(budget));
 }
 
-GDate* gnc_budget_get_start_date(GncBudget* budget)
+void
+gnc_budget_set_num_periods(GncBudget* budget, guint num_periods)
 {
-    if(budget == NULL){
-        return NULL;
-    }
-    return &budget->start_date;
+    g_return_if_fail(GNC_IS_BUDGET(budget));
+    budget->num_periods = num_periods;
+    gnc_engine_gen_event( &budget->inst.entity, GNC_EVENT_MODIFY);
 }
 
-
-
-
-
-
-void gnc_budget_set_length_months(GncBudget* budget, gint months)
+guint
+gnc_budget_get_num_periods(GncBudget* budget)
 {
-    if(budget == NULL){
-        return;
-    }
-
-    budget->length = months;
+    g_return_val_if_fail(GNC_IS_BUDGET(budget), 0);
+    return budget->num_periods;
 }
 
-void gnc_budget_set_length_years(GncBudget* budget, gint years)
-{
-    if(budget == NULL){
-        return;
-    }
-
-    budget->length = years * 12;
-}
+#define BUF_SIZE (10 + GUID_ENCODING_LENGTH + \
+   GNC_BUDGET_MAX_NUM_PERIODS_DIGITS)
 
-gint gnc_budget_get_length_months(GncBudget* budget)
+/* period_num is zero-based */
+/* What happens when account is deleted, after we have an entry for it? */
+void
+gnc_budget_set_account_period_value(GncBudget *budget, Account *account,
+                                    guint period_num, gnc_numeric val)
 {
-    if(budget == NULL){
-        return 0;
-    }
+    const GUID *guid;
+    KvpFrame *frame;
+    gchar path[BUF_SIZE];
+    gchar *bufend;
 
-    return budget->length;
-}
+    frame = qof_instance_get_slots(QOF_INSTANCE(budget));
+    guid = xaccAccountGetGUID(account);
+    bufend = guid_to_string_buff(guid, path);
+    g_sprintf(bufend, "/%d", period_num);
 
-gint gnc_budget_get_length_years(GncBudget* budget)
-{
-    if(budget == NULL){
-        return 0;
-    }
+    kvp_frame_set_numeric(frame, path, val);
+    gnc_engine_gen_event( &budget->inst.entity, GNC_EVENT_MODIFY);
 
-    return budget->length / 12;
 }
 
+/* We don't need these here, but maybe they're useful somewhere else?
+   Maybe this should move to Account.h */
+#if 0
+static gpointer
+is_same_commodity(Account *a, gpointer data)
+{
+    gnc_commodity *acct_comm;
+    gnc_commodity *comm;
 
+    g_return_val_if_fail(data, NULL);
+    // What? No type-checking macro?
+    comm = (gnc_commodity *) data;
+    acct_comm = xaccAccountGetCommodity(a);
 
-
-
-
-void gnc_budget_add_inflow_category(GncBudget* budget, GncBudgetCategory* category)
-{
-    if(budget == NULL){
-        return;
-    }
-    gnc_budget_category_add_child(budget->inflow_category, category);
+    return gnc_commodity_equal(comm, acct_comm) ? NULL : data;
 }
 
-void gnc_budget_remove_inflow_category(GncBudget* budget, GncBudgetCategory* category)
+static gboolean
+xaccAccountChildrenHaveSameCommodity(Account *account)
 {
-    if(budget == NULL){
-        return;
-    }
-    gnc_budget_category_remove_child(budget->inflow_category, category);
-}
+    AccountGroup *grp;
+    gpointer different;
+    gnc_commodity *comm;
 
-void gnc_budget_set_inflow_category(GncBudget* budget, GncBudgetCategory* inflow)
-{
-    if(budget == NULL){
-        return;
-    }
-    budget->inflow_category = inflow;
+    comm = xaccAccountGetCommodity(account);
+    grp = xaccAccountGetChildren(account);
+    different = xaccGroupForEachAccount(
+        grp, is_same_commodity, comm, TRUE);
+    return (different == NULL);
 }
+#endif
 
-GncBudgetCategory* gnc_budget_get_inflow_category(GncBudget* budget)
-{
-    if(budget == NULL){
-        return NULL;
-    }
-    return budget->inflow_category;
-}
 
-void gnc_budget_set_outflow_category(GncBudget* budget, GncBudgetCategory* outflow)
+/* In order to distinguish between a value of zero and an unset value,
+   this function can return a gnc_numeric with GNC_ERROR_ARG set.
+   Currently, it only does so for placeholder accounts with
+   mixed-commodity subaccounts. */
+gnc_numeric
+gnc_budget_get_account_period_value(GncBudget *budget, Account *account,
+                                    guint period_num)
 {
-    if(budget == NULL){
-        return;
-    }
-    budget->outflow_category = outflow;
-}
+    gnc_numeric numeric;
+    gchar path[BUF_SIZE];
+    gchar *bufend;
+    const GUID *guid;
+    KvpFrame *frame;
 
-GncBudgetCategory* gnc_budget_get_outflow_category(GncBudget* budget)
-{
-    if(budget == NULL){
-        return NULL;
-    }
-    return budget->outflow_category;
-}
+    numeric = gnc_numeric_zero();
+    g_return_val_if_fail(GNC_IS_BUDGET(budget), numeric);
+    g_return_val_if_fail(account, numeric);
 
-void gnc_budget_add_outflow_category(GncBudget* budget, GncBudgetCategory* category)
-{
-    if(budget == NULL){
-        return;
-    }
-    gnc_budget_category_add_child(budget->outflow_category, category);
+    /* FIXME? check for unset?  Right now, returns zero on unset. */
+    frame = qof_instance_get_slots(QOF_INSTANCE(budget));
+    guid = xaccAccountGetGUID(account);
+    bufend = guid_to_string_buff(guid, path);
+    g_sprintf(bufend, "/%d", period_num);
+    numeric = kvp_frame_get_numeric(frame, path);
+    return numeric;
 }
 
-void gnc_budget_remove_outflow_category(GncBudget* budget, GncBudgetCategory* category)
+/* If 'end' is true, then get a time just before the beginning of the
+   next period */
+static time_t
+gnc_budget_get_period_time(GncBudget *budget, guint period_num, gboolean end)
 {
-    if(budget == NULL){
-        return;
+    GDate date;
+    recurrenceNthInstance(&(budget->recurrence),
+                          period_num + (end ? 1 : 0), &date);
+    if (end) {
+        g_date_subtract_days(&date, 1);
+        return gnc_timet_get_day_end_gdate(&date);
+    } else {
+        return gnc_timet_get_day_start_gdate(&date);
     }
-    gnc_budget_category_remove_child(budget->inflow_category, category);
-}
 
-QofBook* gnc_budget_get_book(GncBudget* budget)
-{
-    if(budget == NULL){
-        return NULL;
-    }
-    return budget->book;
 }
 
-static void free_budget_period(gpointer data, gpointer user_data)
+Timespec
+gnc_budget_get_period_start_date(GncBudget *budget, guint period_num)
 {
-    GncBudgetPeriod* period = data;
-    g_free(period);
+    Timespec ts;
+    timespecFromTime_t(
+        &ts,  gnc_budget_get_period_time(budget, period_num, FALSE));
+    return ts;
 }
 
-void gnc_budget_generate_periods(GncBudget* budget)
+gnc_numeric
+gnc_budget_get_account_period_actual_value(
+    GncBudget *budget, Account *account, guint period_num)
 {
-    GDate currentPeriod, outDate;
-    GDate endDate;
-    GDate periodStartDate;
-    GncBudgetPeriod* budgetPeriod;
+    gnc_numeric numeric, num1, num2;
+    time_t t1, t2;
 
-    if(budget == NULL){
-        return;
-    }
-
-    /* TODO: Add in some error checking such that we don't regenerate
-     * the periods if they are the same as before.  We may still need
-     * to recalculate the values however.
-     */
-    if(budget->period_list != NULL){
-        g_list_foreach(budget->period_list, free_budget_period, NULL);
-        g_list_free(budget->period_list);
-        budget->period_list = NULL;
-    }
-    
-    /* Add the starting period.  Every budget should have at least
-     * one period. */
-    g_date_set_dmy(&periodStartDate, g_date_get_day(&budget->start_date),
-                                     g_date_get_month(&budget->start_date),
-                                     g_date_get_year(&budget->start_date));
-    budgetPeriod = gnc_budget_period_new();
-    gnc_budget_period_set_start_date(budgetPeriod, &periodStartDate);
-    budget->period_list = g_list_append(budget->period_list, budgetPeriod);
-
-    
-    /* Setup the end Date */
-    g_date_set_dmy(&endDate, g_date_get_day(&budget->start_date),
-                             g_date_get_month(&budget->start_date),
-                             g_date_get_year(&budget->start_date));
-    g_date_add_months(&endDate, budget->length);
-    
-
-    g_date_set_dmy(&currentPeriod, g_date_get_day(&budget->start_date),
-                                  g_date_get_month(&budget->start_date),
-                                  g_date_get_year(&budget->start_date));
-    do{
-        xaccFreqSpecGetNextInstance(budget->period_freq, &currentPeriod, &outDate);
-        if(g_date_valid(&outDate) == FALSE){
-            break;
-        }
-        
-        if(g_date_compare(&outDate, &endDate) >= 0){
-            break;
-        }
-        else{
-            /* Set the end date of the previous period. */
-            gnc_budget_period_set_end_date(budgetPeriod, &outDate);
-
-            /* Create the new period. */
-            budgetPeriod = gnc_budget_period_new();
-            gnc_budget_period_set_start_date(budgetPeriod, &outDate);
-            
-            budget->period_list = g_list_append(budget->period_list, budgetPeriod);
-            g_date_set_dmy(&currentPeriod, g_date_get_day(&outDate),
-                            g_date_get_month(&outDate),
-                            g_date_get_year(&outDate));
-        }
-    }while(1);
-
-    gnc_budget_period_set_end_date(budgetPeriod, &endDate);
-    
-    /* Now we should generate the list of values for each category.*/
-    gnc_budget_category_generate_values(budget->inflow_category);
-    gnc_budget_category_generate_values(budget->outflow_category);
-}
+    // FIXME: maybe zero is not best error return val.
+    g_return_val_if_fail(GNC_IS_BUDGET(budget) && account, gnc_numeric_zero());
+    t1 = gnc_budget_get_period_time(budget, period_num, FALSE);
+    t2 = gnc_budget_get_period_time(budget, period_num, TRUE);
 
+    num1 = xaccAccountGetBalanceAsOfDateInCurrency(
+        account, t1, NULL, TRUE);
+    num2 = xaccAccountGetBalanceAsOfDateInCurrency(
+        account, t2, NULL, TRUE);
 
-gint gnc_budget_get_num_periods(GncBudget* budget)
-{
-    if(budget == NULL){
-        return 0;
-    }
-    return g_list_length(budget->period_list);
+    numeric = gnc_numeric_sub(num2, num1, GNC_DENOM_AUTO,
+                              GNC_HOW_DENOM_FIXED);
+    return numeric;
 }
 
-GList* gnc_budget_get_period_list(GncBudget* budget)
+QofBook*
+gnc_budget_get_book(GncBudget* budget)
 {
-    if(budget == NULL){
-        return NULL;
-    }
-    return budget->period_list;
+    g_return_val_if_fail(GNC_IS_BUDGET(budget), NULL);
+    return qof_instance_get_book(&budget->inst);
 }
 
-
-
-/* ==================================================================== */
-/* Private member functions.
- *
- * ==================================================================== */
-
-static void add_category_from_account(gpointer data, gpointer userdata)
+GList*
+gnc_book_get_budgets(QofBook* book)
 {
-    GncBudget* budget;
-    Account* account;
-    AccountList* children;
-    GncBudgetCategory* category = NULL;
-    gchar* name;
-    AccountGroup* group;
-    int numChildren;
-    GNCAccountType accountType;
-
-    account = data;
-    budget = userdata;
-    
-    if(account == NULL){
-        return;
-    }
-
-    //printf("Adding Category from account\n");
-    //printf("Account: %s\n", xaccAccountGetName(account));
-    group  = xaccAccountGetChildren(account);
-    children = xaccGroupGetAccountList(group);
-    numChildren = xaccGroupGetNumAccounts(group);
-    //printf("NumChildren: %d\n", numChildren);
-
-    if(numChildren == 0){
-        name = xaccAccountGetFullName(account, ':');
-        accountType = xaccAccountGetType(account);
-        
-        category = gnc_budget_category_new(budget->book, budget);
-        gnc_budget_category_set_name(category, name);
-        gnc_budget_category_add_account(category, account);
-                
-        if(accountType == INCOME){
-            //printf("Inflow Category: %s\n", gnc_budget_category_get_name(category));
-            gnc_budget_category_set_type(category, BUDGET_CATEGORY_INFLOW);
-            gnc_budget_category_add_child(budget->inflow_category, category);
-        }
-        else{
-            //printf("Outflow Category: %s\n", gnc_budget_category_get_name(category));
-            gnc_budget_category_set_type(category, BUDGET_CATEGORY_OUTFLOW);
-            gnc_budget_category_add_child(budget->outflow_category, category);
-        }
-        
-        g_free(name);
-    }
+    QofCollection *col;
 
+    g_return_val_if_fail(book, NULL);
+    col = qof_book_get_collection (book, GNC_ID_BUDGET);
+    return qof_collection_get_list(col);
 }
 
-static void budget_fill_categories(GncBudget* budget)
+GncBudget*
+gnc_budget_lookup (const GUID *guid, QofBook *book)
 {
-    AccountList* children;
-    if(budget == NULL){
-        return;
-    }
-    
-    //printf("Creating top level budget categories.\n");
-
-    budget->inflow_category = gnc_budget_category_new(budget->book, budget);
-    gnc_budget_category_set_name(budget->inflow_category, "Inflow");
+    QofCollection *col;
 
-    
-    budget->outflow_category = gnc_budget_category_new(budget->book, budget);
-    gnc_budget_category_set_name(budget->outflow_category, "Outflow");
+    g_return_val_if_fail(guid, NULL);
+    g_return_val_if_fail(book, NULL);
+    col = qof_book_get_collection (book, GNC_ID_BUDGET);
+    return GNC_BUDGET(qof_collection_lookup_entity (col, guid));
+}
 
-    
-    //printf("Filing budget Categories.\n");
-    //children = xaccGroupGetAccountList(gnc_book_get_group(budget->book));
-    children = xaccGroupGetSubAccounts(gnc_book_get_group(budget->book));
-    g_list_foreach(children, add_category_from_account, budget);
+GncBudget*
+gnc_budget_get_default (QofBook *book)
+{
+    GList *list;
+    GncBudget *bgt = NULL;
 
-    //printf("Number of inflow categories: %d\n",
-            //gnc_budget_category_get_num_children(budget->inflow_category));
-    //printf("Number of outflow categories: %d\n",
-            //gnc_budget_category_get_num_children(budget->outflow_category));
-}
+    g_return_val_if_fail(book, NULL);
+    list = gnc_book_get_budgets(book);
+    if (g_list_length(list) > 0) {
+        bgt = GNC_BUDGET(list->data);  // Just get the first one.
+        g_list_free(list);
+    }
+    return bgt;
+}
+
+/* Define the QofObject. */
+/* TODO: Eventually, I'm think I'm going to have to check if this struct is
+   complete.  Also, do we need one of those QofParam thingys? */
+static QofObject budget_object_def =
+{
+    interface_version: QOF_OBJECT_VERSION,
+    e_type:            GNC_ID_BUDGET,
+    type_label:        "BUDGET",
+    create:            (gpointer (*)(QofBook *)) gnc_budget_new,
+    book_begin:        NULL,
+    book_end:          NULL,
+    is_dirty:          NULL,
+    mark_clean:        NULL,
+    foreach:           qof_collection_foreach,
+    printable:         NULL,
+    version_cmp:       (int (*)(gpointer, gpointer)) qof_instance_version_cmp,
+};
 
-GncBudget* gnc_budget_lookup (const GUID *guid, QofBook *book)
+/* Register ourselves with the engine. */
+gboolean gnc_budget_register (void)
 {
-    QofCollection *col;
-    
-    if (!guid || !book){
-        return NULL;
-    }
-        
-    col = qof_book_get_collection (book, GNC_ID_BUDGET);
-    return (GncBudget*)qof_collection_lookup_entity (col, guid);
+    return qof_object_register (&budget_object_def);
 }
Index: gnucash/src/engine/gnc-budget.h
===================================================================
--- gnucash.orig/src/engine/gnc-budget.h
+++ gnucash/src/engine/gnc-budget.h
@@ -1,6 +1,7 @@
 /********************************************************************\
- * gnc-budget.h -- Budget public handling routines.                     *
+ * gnc-budget.h -- Budget public handling routines.                 *
  * Copyright (C) 04 sep 2003    Darin Willits <[hidden email]>    *
+ * Copyright (C) 2005  Chris Shoemaker <[hidden email]>        *
  *                                                                  *
  * This program is free software; you can redistribute it and/or    *
  * modify it under the terms of the GNU General Public License as   *
@@ -21,39 +22,58 @@
  *                                                                  *
 \********************************************************************/
 
-
-/** @addtogroup Engine
- *     @{ */
-/** @addtogroup Budget Budget objects
- * A budget object contains a list of budget_categories each of
- * which define an individual budgetable item.  When a budget is first
- * created these catagories will be initialized based on the existing
- * account hierarchy.
- *     @{ */
-/** @file gnc-budget.h
- *  @brief Public interface for budget objects.
- *  @author Created by Darin Willits 04 sep 2003
- *  @author Copyright (c) 04 Darin Willits <[hidden email]>
- *
- * This file contains the public methods to interact with a budgeting
- * object.
- * This is pre-alpha code right now.  User beware.
+/*  Design decisions:
+ *
+ *  - The budget values that the user enters (and that are stored) for
+ *  each account are inclusive of any sub-accounts.
+ *
+ *     - Reason: This allows the user to budget an amount for a
+ *     "parent" account, while tracking (e.g.) expenses with more
+ *     detail in sub-accounts.
+ *
+ *     - Implication: when reporting budgeted values for parent
+ *     accounts, just show the stored value, not a sum.
+ *
+ *  - Budget values are always in the account's commodity.
+ *
+ *
+ *
+ *  Accounts with sub-accounts can have a value budgeted.  For those
+ *  accounts,
+ *
+ *    Option 1: when setting or getting budgeted values, the value is
+ *    *always* exclusive of sub-account values.  Pro: consistent
+ *    values in all contexts.  Con: no summary behavior.
+ *
+ *    Option 2: make setting only for account proper, but always
+ *    report summaries. Con: value can change as soon as it is
+ *    entered; forces entry from bottom-up.
+ *
+ *    * Option 3: value is always *inclusive* of sub-accounts, although
+ *    potentially in a different commodity.  Pro: allows top-down
+ *    entry; no auto value update. Con: ?  [ This option was selected. ]
+ *
  */
 
-
 #ifndef __GNC_BUDGET_H__
 #define __GNC_BUDGET_H__
 
 #include <glib.h>
 
 /** The budget data.*/
-typedef struct gncp_budget GncBudget;
+typedef struct gnc_budget_private GncBudget;
 
 #include "guid.h"
 #include "qofbook.h"
-#include "gnc-budget-cat.h"
-#include "FreqSpec.h"
+#include "Account.h"
+#include "Recurrence.h"
+
+#define GNC_IS_BUDGET(obj)  (QOF_CHECK_TYPE((obj), GNC_ID_BUDGET))
+#define GNC_BUDGET(obj)     (QOF_CHECK_CAST((obj), GNC_ID_BUDGET, GncBudget))
 
+#define GNC_BUDGET_MAX_NUM_PERIODS_DIGITS 3 // max num periods == 999
+
+gboolean gnc_budget_register(void);
 
 /**
  * Creates and initializes a Budget.
@@ -61,130 +81,53 @@ typedef struct gncp_budget GncBudget;
 GncBudget *gnc_budget_new(QofBook *book);
 
 /** Deletes the given budget object.*/
-void gnc_budget_delete(GncBudget* budget);
-
+void gnc_budget_free(GncBudget* budget);
 
+const GUID* gnc_budget_get_guid(GncBudget* budget);
+#define gnc_budget_return_guid(X) \
+  (X ? *(qof_entity_get_guid(QOF_ENTITY(X))) : *(guid_null()))
 
-/**  Set the Name of the Budget.
- */
+/** Set/Get the name of the Budget */
 void gnc_budget_set_name(GncBudget* budget, const gchar* name);
+const gchar* gnc_budget_get_name(GncBudget* budget);
 
-/**  Retieve the Name of the Budget.
- */
-gchar* gnc_budget_get_name(GncBudget* budget);
-
-
-
-/**  Set the Description of the Budget.
- */
+/** Set/Get the description of the Budget */
 void gnc_budget_set_description(GncBudget* budget, const gchar* description);
+const gchar* gnc_budget_get_description(GncBudget* budget);
 
-/**  Retieve the Description of the Budget.
- */
-gchar* gnc_budget_get_description(GncBudget* budget);
-
-
-
-/** Set the period frequency.
- */
-void gnc_budget_set_period_frequency(GncBudget* budget, FreqSpec* period);
-
-/** Get the period frequency.
- */
-FreqSpec* gnc_budget_get_period_frequency(GncBudget* budget);
-
-
-
-/** Set the start date*/
-void gnc_budget_set_start_date(GncBudget* budget, GDate* date);
-
-/** Return the start date */
-GDate* gnc_budget_get_start_date(GncBudget* budget);
-
-
-/** Set the Length for this budget in terms of a number of months.
- */
-void gnc_budget_set_length_months(GncBudget* budget, gint months);
+/** Set/Get the number of periods in the Budget */
+void gnc_budget_set_num_periods(GncBudget* budget, guint num_periods);
+guint gnc_budget_get_num_periods(GncBudget* budget);
+
+void gnc_budget_set_recurrence(GncBudget *budget, const Recurrence *r);
+const Recurrence * gnc_budget_get_recurrence(GncBudget *budget);
+
+/** Set/Get the starting date of the Budget */
+void gnc_budget_set_start_date(GncBudget* budget, Timespec date);
+Timespec gnc_budget_get_start_date(GncBudget* budget);
+Timespec gnc_budget_get_period_start_date(GncBudget* budget, guint period_num);
+
+/* Period indices are zero-based. */
+void gnc_budget_set_account_period_value(
+    GncBudget* budget, Account* account, guint period_num, gnc_numeric val);
+
+gnc_numeric gnc_budget_get_account_period_value(
+    GncBudget *budget, Account *account, guint period_num);
+gnc_numeric gnc_budget_get_account_period_actual_value(
+    GncBudget *budget, Account *account, guint period_num);
 
-/** Set the Length for this budget in terms of a number of months.
- */
-void gnc_budget_set_length_years(GncBudget* budget, gint years);
-
-/** Retrieve the Length for this budget in number of months.
- */
-gint gnc_budget_get_length_months(GncBudget* budget);
-
-/** Retrieve the Length for this budget in number of years.
- */
-gint gnc_budget_get_length_years(GncBudget* budget);
-
-
-
-/* Add the inflow category for this budget.  
- * This is a wrapper function to make it easier to add a category
- * as a child of the inflow category.  It's here cause I'm a lazy ass.
- */
-void gnc_budget_add_inflow_category(GncBudget* budget, GncBudgetCategory* inflow);
-
-/* Remove the inflow category for this budget.  
- */
-void gnc_budget_remove_inflow_category(GncBudget* budget, GncBudgetCategory* category);
-
-/* Set the inflow category for this budget.  
- */
-void gnc_budget_set_inflow_category(GncBudget* budget, GncBudgetCategory* inflow);
-
-/** Retrieve the inflow category for this budget.
- */
-GncBudgetCategory* gnc_budget_get_inflow_category(GncBudget* budget);
-
-
-
-
-/* Add the outflow category for this budget.  
- */
-void gnc_budget_add_outflow_category(GncBudget* budget, GncBudgetCategory* outflow);
-
-/* Remove the outflow category for this budget.  
- */
-void gnc_budget_remove_outflow_category(GncBudget* budget, GncBudgetCategory* inflow);
-
-/* Set the outflow category for this budget.  
- */
-void gnc_budget_set_outflow_category(GncBudget* budget, GncBudgetCategory* category);
-
-/** Retrieve the outflow category for this budget.
- */
-GncBudgetCategory* gnc_budget_get_outflow_category(GncBudget* budget);
-
-
-
-/** Retrieve the book that this budget is associated with.*/
+/** Get the book that this budget is associated with. */
 QofBook* gnc_budget_get_book(GncBudget* budget);
 
+/* Caller owns the list of all budgets in the book. */
+GList* gnc_book_get_budgets(QofBook* book);
 
-/** Generate the list of periods.
- * This function will use the start date and budget length to generate a
- * list of budget periods.  The periods are represented by a list of start
- * dates for each period.
- * As well this function regenerates the list of values for each
- * category.
- * */
-void gnc_budget_generate_periods(GncBudget* budget);
-
-/** Get the number of periods.
- */
-gint gnc_budget_get_num_periods(GncBudget* budget);
-
-/* Return the list of periods.
- */
-GList* gnc_budget_get_period_list(GncBudget* budget);
-
+/* Returns some budget in the book, or NULL. */
+GncBudget* gnc_budget_get_default(QofBook *book);
 
-/** Retrieve the budget object associated with the given GUID from
- * the given book.
- */
+/* Get the budget associated with the given GUID from the given book. */
 GncBudget* gnc_budget_lookup (const GUID *guid, QofBook *book);
+#define  gnc_budget_lookup_direct(g,b) gnc_budget_lookup(&(g),(b))
 
 #endif // __BUDGET_H__
 
Index: gnucash/src/engine/gnc-engine.c
===================================================================
--- gnucash.orig/src/engine/gnc-engine.c
+++ gnucash/src/engine/gnc-engine.c
@@ -31,7 +31,7 @@
 #include "AccountP.h"
 #include "GroupP.h"
 #include "SX-book-p.h"
-#include "gnc-budget-book-p.h"
+#include "gnc-budget.h"
 #include "TransactionP.h"
 #include "gnc-commodity.h"
 #include "gnc-lot-p.h"
Index: gnucash/src/engine/gnc-engine.h
===================================================================
--- gnucash.orig/src/engine/gnc-engine.h
+++ gnucash/src/engine/gnc-engine.h
@@ -62,6 +62,7 @@
 #define GNC_MOD_IMPORT    "gnucash-import-export"
 #define GNC_MOD_DRUID     "gnucash-druids"
 #define GNC_MOD_TEST      "gnucash-tests"
+#define GNC_MOD_BUDGET    "gnucash-budget"
 //@}
 
 /** @brief IDENTIFIERS
@@ -99,13 +100,29 @@
 #define GNC_ID_SPLIT          "Split"
 #define GNC_ID_SCHEDXACTION   "SchedXaction"
 #define GNC_ID_BUDGET         "Budget"
-#define GNC_ID_BUDGET_CATEGORY "BudgetCategory"
 #define GNC_ID_SXTG           "SXTGroup"
 #define GNC_ID_SXTT           "SXTTrans"
 #define GNC_ID_TRANS          "Trans"
                                                                                 
 /* TYPES **********************************************************/
 
+/* CAS: ISTM, it would make more sense to put the typedefs in their
+   corresponding header files, (e.g. Account.h), and to #include all
+   the engine API header files right here.  After all, when I jump to
+   the definition "Account", I want to end up in Account.h, not this
+   file, like I do now.
+
+   Also, as it is now, if I want to use the engine api, I need to
+   include this header, plus all the other engine headers for the
+   types whose functions I call, so this header is providing almost no
+   benefit of aggregation.  But, if it included all the headers I
+   could just include this file.  Or would that cause a massive
+   recompile everytime one engine header changed?
+   Even if including all the headers here doesn't make sense, I think
+   distributing the stuff in the "Types" section does.
+*/
+
+
 /** @brief Account in Gnucash.
  * This is the typename for an account. The actual structure is
  * defined in the private header AccountP.h, but no one outside the
Index: gnucash/src/engine/gw-engine-spec.scm
===================================================================
--- gnucash.orig/src/engine/gw-engine-spec.scm
+++ gnucash/src/engine/gw-engine-spec.scm
@@ -26,6 +26,7 @@
     "#include <guid.h>\n"
     "#include <Group.h>\n"
     "#include <Query.h>\n"
+    "#include <gnc-budget.h>\n"
     "#include <gnc-commodity.h>\n"
     "#include <gnc-date.h>\n"
     "#include <gnc-engine.h>\n"
@@ -260,6 +261,7 @@
 ;
 (gw:wrap-value ws 'gnc:id-account '<gnc:id-type> "GNC_ID_ACCOUNT")
 (gw:wrap-value ws 'gnc:id-book '<gnc:id-type> "GNC_ID_BOOK")
+(gw:wrap-value ws 'gnc:id-budget '<gnc:id-type> "GNC_ID_BUDGET")
 (gw:wrap-value ws 'gnc:id-lot '<gnc:id-type> "GNC_ID_LOT")
 (gw:wrap-value ws 'gnc:id-price '<gnc:id-type> "GNC_ID_PRICE")
 (gw:wrap-value ws 'gnc:id-split '<gnc:id-type> "GNC_ID_SPLIT")
@@ -2491,6 +2493,87 @@ the timepair representing midday on that
  '(((gw:glist-of (<gw:mchars> callee-owned) callee-owned) choices))
  "Takes a list of installed Finance::Quote souces and records it internally.")
 
+
+;; Budget functions
+
+(gw:wrap-as-wct ws '<gnc:Budget*> "GncBudget *" "const GncBudget *")
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-get-guid
+ '<gnc:guid-scm>
+ "gnc_budget_return_guid"
+ '((<gnc:Budget*> budget))
+ "Gets the guid of the budget")
+
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-lookup
+ '<gnc:Budget*>
+ "gnc_budget_lookup_direct"
+ '((<gnc:guid-scm> guid)
+   (<gnc:Book*> book))
+ "Lookup a budget from its GUID.")
+
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-get-default
+ '<gnc:Budget*>
+ "gnc_budget_get_default"
+ '((<gnc:Book*> book))
+ "Get the default budget for the book.")
+
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-get-name
+ '(<gw:mchars> callee-owned const)
+ "gnc_budget_get_name"
+ '((<gnc:Budget*> budget))
+ "Get the brief name for the budget.")
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-get-num-periods
+ '<gw:unsigned-int>
+ "gnc_budget_get_num_periods"
+ '((<gnc:Budget*> budget))
+ "Get the number of periods in a budget.")
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-get-account-period-value
+ '<gnc:numeric>
+ "gnc_budget_get_account_period_value"
+ '((<gnc:Budget*> budget)
+   (<gnc:Account*> acct)
+   (<gw:unsigned-int> period_num)
+   )
+ "Get the budgeted value for the given account and budget period.")
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-get-account-period-actual-value
+ '<gnc:numeric>
+ "gnc_budget_get_account_period_actual_value"
+ '((<gnc:Budget*> budget)
+   (<gnc:Account*> acct)
+   (<gw:unsigned-int> period_num)
+   )
+ "Get the actual account value for the given account and budget period.")
+
+(gw:wrap-function
+ ws
+ 'gnc:budget-get-period-start-date
+ '<gnc:time-pair>
+ "gnc_budget_get_period_start_date"
+ '((<gnc:Budget*> budget)
+   (<gw:unsigned-int> period_num)
+   )
+ "Get the date that the given period begins.")
+
 ;;
 ;; gnc-hooks-scm.h
 ;;   (and gnc-hooks.h)

--
_______________________________________________
gnucash-patches mailing list
[hidden email]
https://lists.gnucash.org/mailman/listinfo/gnucash-patches
Reply | Threaded
Open this post in threaded view
|

[patch 4/8] [budget-gui.diff] GUI support for Budgets

Chris Shoemaker
In reply to this post by Chris Shoemaker
 - Adds GUI support for Budgets

 src/gnome-utils/Makefile.am                |   14
 src/gnome-utils/gnc-dialog.c               |  697 ++++++++++++++++++++++
 src/gnome-utils/gnc-dialog.h               |  171 +++++
 src/gnome-utils/gnc-html.c                 |    1
 src/gnome-utils/gnc-html.h                 |    1
 src/gnome-utils/gnc-icons.h                |    8
 src/gnome-utils/gnc-recurrence.c           |  545 +++++++++++++++++
 src/gnome-utils/gnc-recurrence.h           |   50 +
 src/gnome-utils/gnc-tree-model-budget.c    |  129 ++++
 src/gnome-utils/gnc-tree-model-budget.h    |   55 +
 src/gnome-utils/test/Makefile.am           |   21
 src/gnome-utils/test/test-gnc-dialog.c     |  145 ++++
 src/gnome-utils/test/test-gnc-recurrence.c |  106 +++
 src/gnome/Makefile.am                      |   20
 src/gnome/glade/budget.glade               |  914 +++++++++++++++++++++++++++++
 src/gnome/gnc-plugin-budget.c              |  273 ++++++++
 src/gnome/gnc-plugin-budget.h              |   69 ++
 src/gnome/gnc-plugin-page-budget.c         |  890 ++++++++++++++++++++++++++++
 src/gnome/gnc-plugin-page-budget.h         |   77 ++
 src/gnome/gncmod-budget.c                  |   55 +
 src/gnome/top-level.c                      |    6
 src/gnome/ui/Makefile.am                   |    2
 src/gnome/ui/gnc-plugin-budget-ui.xml      |   13
 src/gnome/ui/gnc-plugin-page-budget-ui.xml |    8
 24 files changed, 4250 insertions(+), 20 deletions(-)

Index: gnucash/src/gnome-utils/Makefile.am
===================================================================
--- gnucash.orig/src/gnome-utils/Makefile.am
+++ gnucash/src/gnome-utils/Makefile.am
@@ -71,8 +71,6 @@ libgncmod_gnome_utils_la_SOURCES = \
   druid-gconf-setup.c \
   gnc-account-sel.c \
   gnc-amount-edit.c \
-  gnc-budget-list-tree-model.c \
-  gnc-budget-tree-model.c \
   gnc-commodity-edit.c \
   gnc-currency-edit.c \
   gnc-date-delta.c \
@@ -86,6 +84,7 @@ libgncmod_gnome_utils_la_SOURCES = \
   gnc-embedded-window.c \
   gnc-file.c \
   gnc-frequency.c \
+  gnc-recurrence.c \
   gnc-general-select.c \
   gnc-gnome-utils.c \
   gnc-gui-query.c \
@@ -119,7 +118,9 @@ libgncmod_gnome_utils_la_SOURCES = \
   gncmod-gnome-utils.c \
   misc-gnome-utils.c \
   print-session.c \
-  search-param.c
+  search-param.c \
+  gnc-dialog.c \
+  gnc-tree-model-budget.c
 
 gncincludedir = ${GNC_INCLUDE_DIR}
 gncinclude_HEADERS = \
@@ -137,8 +138,6 @@ gncinclude_HEADERS = \
   druid-gconf-setup.h \
   gnc-account-sel.h \
   gnc-amount-edit.h \
-  gnc-budget-list-tree-model.h \
-  gnc-budget-tree-model.h \
   gnc-commodity-edit.h \
   gnc-currency-edit.h \
   gnc-date-delta.h \
@@ -149,6 +148,7 @@ gncinclude_HEADERS = \
   gnc-embedded-window.h \
   gnc-file.h \
   gnc-frequency.h \
+  gnc-recurrence.h \
   gnc-general-select.h \
   gnc-gnome-utils.h \
   gnc-gui-query.h \
@@ -180,7 +180,9 @@ gncinclude_HEADERS = \
   gnc-tree-view.h \
   gnc-window.h \
   misc-gnome-utils.h \
-  print-session.h
+  print-session.h \
+  gnc-dialog.h \
+  gnc-tree-model-budget.h
 
 noinst_HEADERS = \
   argv-list-converters.h \
Index: gnucash/src/gnome-utils/gnc-dialog.c
===================================================================
--- /dev/null
+++ gnucash/src/gnome-utils/gnc-dialog.c
@@ -0,0 +1,697 @@
+/********************************************************************\
+ * Copyright (C) 2005 Chris Shoemaker ([hidden email])          *
+ *                                                                   *
+ * 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        *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652        *
+ * Boston, MA  02111-1307,  USA       [hidden email]                    *
+ *                                                                   *
+\********************************************************************/
+
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+
+#include "gnc-trace.h"
+#include "gnome.h"         // for gnome_date_edit
+#include "gnc-dialog.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;
+static GList *active_pw = 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 */
+    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;
+
+#if DEBUG_REFERENCE_COUNTING
+    gtk_quit_add (0, (GtkFunction)gnc_dialog_report_references,
+          NULL);
+#endif
+}
+
+#if DEBUG_REFERENCE_COUNTING
+static void
+dump_model (GncDialog *d, gpointer dummy)
+{
+    g_warning("GncDialog %p still exists.", d);
+}
+static gint
+gnc_dialog_report_references (void)
+{
+  g_list_foreach(active_pw, (GFunc)dump_model, NULL);
+  return 0;
+}
+#endif
+
+static void
+gnc_dialog_init (GncDialog *d)
+{
+    active_pw = g_list_append (active_pw, d);
+}
+
+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);
+}
Index: gnucash/src/gnome-utils/gnc-dialog.h
===================================================================
--- /dev/null
+++ gnucash/src/gnome-utils/gnc-dialog.h
@@ -0,0 +1,171 @@
+/********************************************************************\
+ * Copyright (C) 2005 Chris Shoemaker <[hidden email]>          *
+ *                                                                   *
+ * 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        *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652        *
+ * Boston, MA  02111-1307,  USA       [hidden email]                    *
+ *                                                                   *
+\********************************************************************/
+
+
+#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
Index: gnucash/src/gnome-utils/gnc-html.c
===================================================================
--- gnucash.orig/src/gnome-utils/gnc-html.c
+++ gnucash/src/gnome-utils/gnc-html.c
@@ -376,6 +376,7 @@ gnc_html_initialize (void)
     { URL_TYPE_HELP, "gnc-help" },
     { URL_TYPE_XMLDATA, "gnc-xml" },
     { URL_TYPE_PRICE, "gnc-price" },
+    { URL_TYPE_BUDGET, "gnc-budget" },
     { URL_TYPE_OTHER, "" },
     { NULL, NULL }};
 
Index: gnucash/src/gnome-utils/gnc-html.h
===================================================================
--- gnucash.orig/src/gnome-utils/gnc-html.h
+++ gnucash/src/gnome-utils/gnc-html.h
@@ -44,6 +44,7 @@ typedef char * URLType;
 #define URL_TYPE_XMLDATA "xmldata"    /* links to gnucash XML data files */
 #define URL_TYPE_PRICE "price"      /* for price editor popups */
 #define URL_TYPE_OTHER "other"
+#define URL_TYPE_BUDGET "budget"
 
 #include "gnc-html-history.h"
 
Index: gnucash/src/gnome-utils/gnc-icons.h
===================================================================
--- gnucash.orig/src/gnome-utils/gnc-icons.h
+++ gnucash/src/gnome-utils/gnc-icons.h
@@ -15,6 +15,14 @@ G_BEGIN_DECLS
 #define GNC_STOCK_NEW_ACCOUNT "gnc-new-account"
 #define GNC_STOCK_OPEN_ACCOUNT "gnc-open-account"
 
+//FIXME: use own budget icons?
+#define GNC_STOCK_BUDGET "gnc-budget"
+#define GNC_STOCK_NEW_BUDGET "gnc-account"
+#define GNC_STOCK_OPEN_BUDGET "gnc-open-account"
+//#define GNC_STOCK_CLOSE_BUDGET "gnc-close-account"
+//#define GNC_STOCK_EDIT_BUDGET "gnc-edit-account"
+#define GNC_STOCK_DELETE_BUDGET "gnc-delete-account"
+
 void gnc_load_stock_icons (void);
 
 G_END_DECLS
Index: gnucash/src/gnome-utils/gnc-recurrence.c
===================================================================
--- /dev/null
+++ gnucash/src/gnome-utils/gnc-recurrence.c
@@ -0,0 +1,545 @@
+/* gnc-recurrence.c:
+ *
+ */
+
+/* Copyright (C) 2005, Chris Shoemaker <[hidden email]>
+ * This file is free software.  See COPYING for details. */
+
+#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 =========================== */
Index: gnucash/src/gnome-utils/gnc-recurrence.h
===================================================================
--- /dev/null
+++ gnucash/src/gnome-utils/gnc-recurrence.h
@@ -0,0 +1,50 @@
+/* gnc-recurrence.h:
+ *
+ *  GncRecurrence is a minimal GUI for specifying a Recurrence.
+ *
+ *  You see, small is _nice_.  :)
+ *
+ */
+
+/* Copyright (C) 2005, Chris Shoemaker <[hidden email]>  *
+ * This file is free software.  See COPYING for details.
+ */
+
+#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
Index: gnucash/src/gnome-utils/gnc-tree-model-budget.c
===================================================================
--- /dev/null
+++ gnucash/src/gnome-utils/gnc-tree-model-budget.c
@@ -0,0 +1,129 @@
+/********************************************************************\
+ * Copyright (C) 2005, Chris Shoemaker <[hidden email]>        *
+ * This file is free software.  See COPYING for details.            */
+
+/** @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( gpointer data, gpointer user_data )
+{
+    GncBudget* budget;
+    GtkTreeIter iter;
+    GtkTreeModel* treeModel;
+
+    budget = data;
+    g_return_if_fail(GNC_IS_BUDGET(budget));
+    treeModel = user_data;
+
+    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)
+{
+    GList *budgetList;
+    GtkListStore* store;
+
+    store = gtk_list_store_new (BUDGET_LIST_NUM_COLS,
+                                G_TYPE_POINTER,
+                                G_TYPE_STRING,
+                                G_TYPE_STRING);
+    budgetList = gnc_book_get_budgets(book);
+
+    g_list_foreach(budgetList, add_budget_to_model, GTK_TREE_MODEL(store));
+    g_list_free(budgetList);
+
+    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;
+    }
+}
Index: gnucash/src/gnome-utils/gnc-tree-model-budget.h
===================================================================
--- /dev/null
+++ gnucash/src/gnome-utils/gnc-tree-model-budget.h
@@ -0,0 +1,55 @@
+/********************************************************************\
+ * Copyright (C) 2005 Chris Shoemaker <[hidden email]>         *
+ *                                                                  *
+ * 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       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       [hidden email]                   *
+ *                                                                  *
+\********************************************************************/
+
+/** @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__
Index: gnucash/src/gnome-utils/test/Makefile.am
===================================================================
--- gnucash.orig/src/gnome-utils/test/Makefile.am
+++ gnucash/src/gnome-utils/test/Makefile.am
@@ -1,5 +1,9 @@
 TESTS =  \
-  test-link-module test-load-module
+  test-link-module test-load-module test-gnc-recurrence test-gnc-dialog
+
+##lib_LTLIBRARIES = libgncgnome.la
+
+
 
 GNC_TEST_DEPS := @GNC_TEST_SRFI_LOAD_CMD@ \
   --gnc-module-dir ${top_builddir}/src/gnc-module \
@@ -23,12 +27,23 @@ TESTS_ENVIRONMENT := \
   $(shell ${top_srcdir}/src/gnc-test-env --no-exports ${GNC_TEST_DEPS})
 
 noinst_PROGRAMS = \
-  test-link-module
+  test-link-module test-gnc-recurrence test-gnc-dialog
 
 INCLUDES= \
   -I${top_srcdir}/src \
+  -I${top_srcdir}/src/engine \
+  -I${top_srcdir}/src/gnome-utils \
   -I${top_srcdir}/src/gnc-module \
-  ${GLIB_CFLAGS} ${GUILE_INCS}
+  -I${top_srcdir}/src/app-utils \
+  ${GLIB_CFLAGS} ${GUILE_INCS} ${GNOME_CFLAGS} ${GLADE_CFLAGS}
+
+test_gnc_recurrence_SOURCES=test-gnc-recurrence.c
+test_gnc_recurrence_LDADD = ${GNOME_LIBS} \
+  ${top_builddir}/src/gnome-utils/libgncmod-gnome-utils.la
+
+test_gnc_dialog_LDADD = ${GNOME_LIBS} \
+  ${top_builddir}/src/app-utils/libgncmod-app-utils.la \
+  ${top_builddir}/src/gnome-utils/libgncmod-gnome-utils.la
 
 test_link_module_SOURCES=test-link-module.c
 test_link_module_LDADD= \
Index: gnucash/src/gnome-utils/test/test-gnc-dialog.c
===================================================================
--- /dev/null
+++ gnucash/src/gnome-utils/test/test-gnc-dialog.c
@@ -0,0 +1,145 @@
+/* Copyright (C) 2005, Chris Shoemaker <[hidden email]>
+ * This file is free software.  See COPYING for det
Reply | Threaded
Open this post in threaded view
|

[patch 5/8] [budget-option.diff] Add an option widget for selecting a budget

Chris Shoemaker
In reply to this post by Chris Shoemaker
 * src/app-utils/app-utils.scm:
 * src/app-utils/options.scm:
 * src/gnome-utils/dialog-options.c:
   - Add an option widget to select a particular budget.
   - line wrap fixes
   - add error message for failed option lookup

 src/app-utils/app-utils.scm      |    2
 src/app-utils/options.scm        |   51 +++++++++
 src/gnome-utils/dialog-options.c |  201 ++++++++++++++++++++++++++++++++++++---
 3 files changed, 238 insertions(+), 16 deletions(-)

Index: gnucash/src/app-utils/app-utils.scm
===================================================================
--- gnucash.orig/src/app-utils/app-utils.scm
+++ gnucash/src/app-utils/app-utils.scm
@@ -15,6 +15,7 @@
 (export-syntax N_)
 (export gnc:make-string-database)
 
+;; options.scm
 (export gnc:make-option)
 (export gnc:option-section)
 (export gnc:option-name)
@@ -52,6 +53,7 @@
 (export gnc:make-complex-boolean-option)
 (export gnc:make-pixmap-option)
 (export gnc:make-date-option)
+(export gnc:make-budget-option)
 (export gnc:get-rd-option-data-subtype)
 (export gnc:get-rd-option-data-show-time)
 (export gnc:get-rd-option-data-rd-list)
Index: gnucash/src/app-utils/options.scm
===================================================================
--- gnucash.orig/src/app-utils/options.scm
+++ gnucash/src/app-utils/options.scm
@@ -121,7 +121,7 @@
 (define (gnc:option-value-validator option)  
   (vector-ref option 11))
 (define (gnc:option-data option)
-    (vector-ref option 12))
+  (vector-ref option 12))
 (define (gnc:option-data-fns option)
   (vector-ref option 13))
 
@@ -301,6 +301,55 @@
       (lambda (x) (list #t x))
       #f #f #f #f)))
 
+;; budget option
+;; TODO: need to double-check this proc
+(define (gnc:make-budget-option
+ section
+ name
+ sort-tag
+         documentation-string)
+
+  (define (budget->guid budget)
+    (if (string? budget)
+        budget
+        (gnc:budget-get-guid budget)))
+
+  (define (guid->budget budget)
+    (if (string? budget)
+        (gnc:budget-lookup budget (gnc:get-current-book))
+        budget))
+
+  (let* ((default-value (gnc:budget-get-default (gnc:get-current-book)))
+         (value (budget->guid default-value))
+         (option-set #f)
+         (value->string (lambda ()
+                          (string-append
+                           "'" (gnc:value->string (if option-set option #f)))))
+
+         )
+    (gnc:make-option
+     section name sort-tag 'budget documentation-string
+     ;; the getter should always return a budget pointer
+     (lambda () (guid->budget  ;; getter
+                 (if option-set
+                     value
+                     default-value))
+             )
+
+     (lambda (x) (set! value (budget->guid x))
+             (set! option-set #t)) ;; setter
+     (lambda ()
+       (guid->budget
+        (gnc:budget-get-default (gnc:get-current-book)))) ;; default-getter
+     (gnc:restore-form-generator value->string) ;; ??
+     (lambda (f p) (gnc:kvp-frame-set-slot-path f value p))
+     (lambda (f p)
+       (let ((v (gnc:kvp-frame-get-slot-path f p)))
+         (if (and v (string? v))
+             (set! value v))))
+     (lambda (x) (list #t x)) ;; value-validator
+     #f #f #f #f)))
+
 ;; commodity options use a specialized widget for entering commodities
 ;; in the GUI implementation.
 (define (gnc:make-commodity-option
Index: gnucash/src/gnome-utils/dialog-options.c
===================================================================
--- gnucash.orig/src/gnome-utils/dialog-options.c
+++ gnucash/src/gnome-utils/dialog-options.c
@@ -25,6 +25,9 @@
 #include <gnome.h>
 #include <g-wrap-wct.h>
 
+#include "gnc-tree-model-budget.h" //FIXME?
+#include "gnc-budget.h"
+
 #include "dialog-options.h"
 #include "dialog-utils.h"
 #include "engine-helpers.h"
@@ -48,6 +51,9 @@
 #include "gnc-date-format.h"
 #include "misc-gnome-utils.h"
 
+/* TODO: clean up "register-stocks" junk
+ */
+
 
 /* This static indicates the debugging module that this .o belongs to.  */
 static QofLogModule log_module = GNC_MOD_GUI;
@@ -58,6 +64,7 @@ static QofLogModule log_module = GNC_MOD
  */
 #define MAX_TAB_COUNT 4
 
+/* A Hash-table of GNCOptionDef_t keyed with option names. */
 static GHashTable *optionTable = NULL;
 
 struct gnc_option_win
@@ -93,9 +100,11 @@ typedef enum {
 static GNCOptionWinCallback global_help_cb = NULL;
 gpointer global_help_cb_data = NULL;
 
-void gnc_options_dialog_response_cb(GtkDialog *dialog, gint response, GNCOptionWin *window);
+void gnc_options_dialog_response_cb(GtkDialog *dialog, gint response,
+    GNCOptionWin *window);
 static void gnc_options_dialog_reset_cb(GtkWidget * w, gpointer data);
-void gnc_options_dialog_list_select_cb(GtkWidget * list, GtkWidget * item, gpointer data);
+void gnc_options_dialog_list_select_cb(GtkWidget * list, GtkWidget * item,
+       gpointer data);
 
 
 static void
@@ -229,17 +238,16 @@ gnc_option_set_ui_value_internal (GNCOpt
   if (option_def && option_def->set_value)
   {
     bad_value = option_def->set_value (option, use_default, widget, value);
+    if (bad_value)
+    {
+      PERR("bad value\n");
+    }
   }
   else
   {
     PERR("Unknown type. Ignoring.\n");
   }
 
-  if (bad_value)
-  {
-    PERR("bad value\n");
-  }
-
   free(type);
 }
 
@@ -434,6 +442,23 @@ gnc_option_create_date_widget (GNCOption
   }
 }
 
+static GtkWidget *
+gnc_option_create_budget_widget(GNCOption *option)
+{
+    GtkTreeModel *tm;
+    GtkComboBox *cb;
+    GtkCellRenderer *cr;
+
+    tm = gnc_tree_model_budget_new(gnc_get_current_book());
+    cb = GTK_COMBO_BOX(gtk_combo_box_new_with_model(tm));
+    g_object_unref(tm);
+    cr = gtk_cell_renderer_text_new();
+    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(cb), cr, TRUE);
+
+    gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(cb), cr, "text",
+                                   BUDGET_NAME_COLUMN, NULL);
+    return GTK_WIDGET(cb);
+}
 
 static GtkWidget *
 gnc_option_create_multichoice_widget(GNCOption *option)
@@ -524,6 +549,7 @@ gnc_option_create_radiobutton_widget(cha
   return frame;
 }
 
+
 static void
 gnc_option_account_cb(GtkTreeSelection *selection, gpointer data)
 {
@@ -1117,7 +1143,14 @@ gnc_options_register_stocks (void)
 #endif
 }
 
-
+/* gnc_options_dialog_new:
+ *
+ *   - Opens the preferences glade file
+ *   - Connects signals specified in the glade file
+ *   - Sets the window's title
+ *   - Initializes a new GtkNotebook, and adds it to the window
+ *
+ */
 GNCOptionWin *
 gnc_options_dialog_new(gchar *title)
 {
@@ -1146,6 +1179,8 @@ gnc_options_dialog_new(gchar *title)
   return retval;
 }
 
+/* Creates a new GNCOptionWin structure, but assumes you have your own
+   dialog widget you want to plugin */
 GNCOptionWin *
 gnc_options_dialog_new_w_dialog(gchar *title, GtkWidget *dialog)
 {
@@ -1188,6 +1223,7 @@ gnc_options_dialog_set_global_help_cb(GN
   global_help_cb_data = cb_data;
 }
 
+/* This is for global program preferences. */
 void
 gnc_options_dialog_destroy(GNCOptionWin * win)
 {
@@ -1212,8 +1248,40 @@ gnc_options_dialog_destroy(GNCOptionWin
 /*****************************************************************/
 /* Option Registration                                           */
 
-/* SET WIDGET */
-
+/*************************
+ *       SET WIDGET      *
+ *************************
+ *
+ * gnc_option_set_ui_widget_<type>():
+ *
+ * You should create the widget representation for the option type,
+ * and set the top-level container widget for your control in
+ * *enclosing.  If you want to pack the widget into the page yourself,
+ * then you may -- just set *packed to TRUE.  Otherwise, the widget
+ * you return in *enclosing will be packed for you.  (*packed is
+ * initialized to FALSE, so if you're not setting it to TRUE, you
+ * don't have to touch it at all.)
+ *
+ * If you need to initialize the state of your control or to connect
+ * any signals to you widgets, then you should do so in this function.
+ * If you want to create a label for the widget you should use 'name'
+ * for the label text.
+ *
+ * Somewhere in this function, you should also call
+ * gnc_option_set_widget(option, value); where 'value' is the
+ * GtkWidget you will actually store the value in.
+ *
+ * Also call gnc_option_set_ui_value(option, FALSE);
+ *
+ * You probably want to end with something like:
+ *   gtk_widget_show_all(*enclosing);
+ *
+ * If you can can detect state changes for your widget's value, you should also
+ * gnc_option_changed_widget_cb() upon changes.
+ *
+ * The widget you return from this function should be the widget in
+ * which you're storing the option value.
+ */
 static GtkWidget *
 gnc_option_set_ui_widget_boolean (GNCOption *option, GtkBox *page_box,
   GtkTooltips *tooltips,
@@ -1484,7 +1552,7 @@ gnc_option_set_ui_widget_account_sel (GN
   GtkWidget *value;
   GtkWidget *label;
   GList *acct_type_list;
-  char * colon_name;
+  gchar *colon_name;
 
   colon_name = g_strconcat(name, ":", NULL);
   label = gtk_label_new(colon_name);
@@ -1500,6 +1568,8 @@ gnc_option_set_ui_widget_account_sel (GN
    G_CALLBACK(gnc_option_changed_widget_cb), option);
 
   gnc_option_set_widget (option, value);
+  /* DOCUMENT ME: Why is the only option type that sets use_default to
+     TRUE? */
   gnc_option_set_ui_value(option, TRUE);
 
   *enclosing = gtk_hbox_new(FALSE, 5);
@@ -1777,7 +1847,43 @@ gnc_option_set_ui_widget_dateformat (GNC
   return *enclosing;
 }
 
-/* SET VALUE */
+static GtkWidget *
+gnc_option_set_ui_widget_budget (GNCOption *option, GtkBox *page_box,
+                                 GtkTooltips *tooltips,
+                                 char *name, char *documentation,
+                                 /* Return values */
+                                 GtkWidget **enclosing, gboolean *packed)
+{
+  GtkWidget *value;
+
+  *enclosing = gtk_hbox_new(FALSE, 5);
+
+  value = gnc_option_create_budget_widget(option);
+
+  gnc_option_set_widget (option, value);
+  gnc_option_set_ui_value(option, FALSE);
+
+  /* Maybe connect destroy handler for tree model here? */
+  g_signal_connect(G_OBJECT(value), "changed",
+   G_CALLBACK(gnc_option_changed_widget_cb), option);
+
+  gtk_box_pack_start(GTK_BOX(*enclosing), value, FALSE, FALSE, 0);
+  gtk_widget_show_all(*enclosing);
+  return value;
+}
+
+/*************************
+ *       SET VALUE       *
+ *************************
+ *
+ * gnc_option_set_ui_value_<type>():
+ *
+ *   In this function you should set the state of the gui widget to
+ * correspond to the value provided in 'value'.  You should return
+ * TRUE if there was an error, FALSE otherwise.
+ *
+ *
+ */
 
 static gboolean
 gnc_option_set_ui_value_boolean (GNCOption *option, gboolean use_default,
@@ -1986,7 +2092,7 @@ gnc_option_set_ui_value_account_sel (GNC
 
   if (value != SCM_BOOL_F) {
     if (!gw_wcp_p(value))
-      scm_misc_error("gnc_optoin_set_ui_value_account_sel",
+      scm_misc_error("gnc_option_set_ui_value_account_sel",
      "Option Value not a gw:wcp.", value);
       
     acc = gw_wcp_get_ptr(value);
@@ -2113,6 +2219,31 @@ gnc_option_set_ui_value_pixmap (GNCOptio
   return TRUE;
 }
 
+static gboolean gnc_option_set_ui_value_budget(
+    GNCOption *option, gboolean use_default, GtkWidget *widget, SCM value)
+{
+    GncBudget *bgt;
+    GtkComboBox *cb;
+    GtkTreeModel *tm;
+    GtkTreeIter iter;
+
+    if (value != SCM_BOOL_F) {
+        if (!gw_wcp_p(value))
+            scm_misc_error("gnc_option_set_ui_value_budget",
+                           "Option Value not a gw:wcp.", value);
+
+        bgt = gw_wcp_get_ptr(value);
+        cb = GTK_COMBO_BOX(widget);
+        tm = gtk_combo_box_get_model(cb);
+        gnc_tree_model_budget_get_iter_for_budget(tm, &iter, bgt);
+        gtk_combo_box_set_active_iter(cb, &iter);
+    }
+
+
+    //FIXME: Unimplemented.
+    return FALSE;
+}
+
 static gboolean
 gnc_option_set_ui_value_radiobutton (GNCOption *option, gboolean use_default,
      GtkWidget *widget, SCM value)
@@ -2173,7 +2304,19 @@ gnc_option_set_ui_value_dateformat (GNCO
   return FALSE;
 }
 
-/* GET VALUE */
+/*************************
+ *       GET VALUE       *
+ *************************
+ *
+ * gnc_option_get_ui_value_<type>():
+ *
+ * 'widget' will be the widget returned from the
+ * gnc_option_set_ui_widget_<type>() function.
+ *
+ * You should return a SCM value corresponding to the current state of the
+ * gui widget.
+ *
+ */
 
 static SCM
 gnc_option_get_ui_value_boolean (GNCOption *option, GtkWidget *widget)
@@ -2325,6 +2468,26 @@ gnc_option_get_ui_value_account_sel (GNC
 }
 
 static SCM
+gnc_option_get_ui_value_budget(GNCOption *option, GtkWidget *widget)
+{
+    GncBudget *bgt;
+    GtkComboBox *cb;
+    GtkTreeModel *tm;
+    GtkTreeIter iter;
+    gboolean success;
+
+    cb = GTK_COMBO_BOX(widget);
+    success = gtk_combo_box_get_active_iter(cb, &iter);
+    tm = gtk_combo_box_get_model(cb);
+    bgt = gnc_tree_model_budget_get_budget(tm, &iter);
+
+    if (!bgt)
+        return SCM_BOOL_F;
+
+    return gw_wcp_assimilate_ptr(bgt, scm_c_eval_string("<gnc:Budget*>"));
+}
+
+static SCM
 gnc_option_get_ui_value_list (GNCOption *option, GtkWidget *widget)
 {
   SCM result;
@@ -2468,6 +2631,8 @@ static void gnc_options_initialize_optio
       gnc_option_set_ui_value_radiobutton, gnc_option_get_ui_value_radiobutton },
     { "dateformat", gnc_option_set_ui_widget_dateformat,
       gnc_option_set_ui_value_dateformat, gnc_option_get_ui_value_dateformat },
+    { "budget", gnc_option_set_ui_widget_budget,
+      gnc_option_set_ui_value_budget, gnc_option_get_ui_value_budget },
     { NULL, NULL, NULL, NULL }
   };
   int i;
@@ -2482,15 +2647,21 @@ void gnc_options_ui_register_option (GNC
   g_return_if_fail (optionTable);
   g_return_if_fail (option);
 
+  /* FIXME: should protect against repeat insertion. */
   g_hash_table_insert (optionTable, (gpointer)(option->option_name), option);
 }
 
 GNCOptionDef_t * gnc_options_ui_get_option (const char *option_name)
 {
+  GNCOptionDef_t *retval;
   g_return_val_if_fail (optionTable, NULL);
   g_return_val_if_fail (option_name, NULL);
 
-  return g_hash_table_lookup (optionTable, option_name);
+  retval = g_hash_table_lookup (optionTable, option_name);
+  if (!retval) {
+      PERR("Option lookup for type '%s' failed!", option_name);
+  }
+  return retval;
 }
 
 void gnc_options_ui_initialize (void)

--
_______________________________________________
gnucash-patches mailing list
[hidden email]
https://lists.gnucash.org/mailman/listinfo/gnucash-patches
Reply | Threaded
Open this post in threaded view
|

[patch 6/8] [budget-persist.diff] Budget persistence for the file backend

Chris Shoemaker
In reply to this post by Chris Shoemaker
 * src/backend/file/Makefile.am
 * src/backend/file/gnc-budget-xml-v2.c
 * src/backend/file/gnc-recurrence-xml-v2.c
 * src/backend/file/gnc-xml.h
 * src/backend/file/io-gncxml-v2.c
 * src/backend/file/io-gncxml-v2.h
 * src/backend/file/sixtp-dom-generators.c
 * src/backend/file/sixtp-dom-generators.h
 * src/backend/file/sixtp-dom-parsers.c
 * src/backend/file/sixtp-dom-parsers.h
 * src/backend/file/sixtp-utils.c
 * src/backend/file/test/Makefile.am
  - add Budget persistence for the file backend

 * src/backend/file/sixtp-dom-generators.c
 * src/backend/file/sixtp-dom-generators.h
  - add 'const' qualifier to GDate pointer in gdate_to_dom_tree()
  - reindent function

 src/backend/file/Makefile.am             |    2
 src/backend/file/gnc-budget-xml-v2.c     |  227 +++++++++++++++++++++++++++++++
 src/backend/file/gnc-recurrence-xml-v2.c |  129 +++++++++++++++++
 src/backend/file/gnc-xml.h               |    4
 src/backend/file/io-gncxml-v2.c          |   74 +++++++++-
 src/backend/file/io-gncxml-v2.h          |    3
 src/backend/file/sixtp-dom-generators.c  |   50 +++---
 src/backend/file/sixtp-dom-generators.h  |    4
 src/backend/file/sixtp-dom-parsers.c     |   27 +++
 src/backend/file/sixtp-dom-parsers.h     |    6
 src/backend/file/sixtp-utils.c           |    3
 src/backend/file/test/Makefile.am        |    8 -
 12 files changed, 500 insertions(+), 37 deletions(-)

Index: gnucash/src/backend/file/Makefile.am
===================================================================
--- gnucash.orig/src/backend/file/Makefile.am
+++ gnucash/src/backend/file/Makefile.am
@@ -18,10 +18,12 @@ libgnc_backend_file_la_SOURCES = \
   gnc-account-xml-v2.c \
   gnc-backend-file.c \
   gnc-book-xml-v2.c \
+  gnc-budget-xml-v2.c \
   gnc-commodity-xml-v2.c \
   gnc-freqspec-xml-v2.c \
   gnc-lot-xml-v2.c \
   gnc-pricedb-xml-v2.c \
+  gnc-recurrence-xml-v2.c \
   gnc-schedxaction-xml-v2.c \
   gnc-transaction-xml-v2.c \
   io-example-account.c \
Index: gnucash/src/backend/file/gnc-budget-xml-v2.c
===================================================================
--- /dev/null
+++ gnucash/src/backend/file/gnc-budget-xml-v2.c
@@ -0,0 +1,227 @@
+/********************************************************************\
+ * gnc-budget-xml-v2.c -- budget xml i/o implementation             *
+ *                                                                  *
+ * Copyright (C) 2005 Chris Shoemaker <[hidden email]>         *
+ *                                                                  *
+ * 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       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       [hidden email]                   *
+ *                                                                  *
+\********************************************************************/
+
+
+#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 ===================*/
Index: gnucash/src/backend/file/gnc-recurrence-xml-v2.c
===================================================================
--- /dev/null
+++ gnucash/src/backend/file/gnc-recurrence-xml-v2.c
@@ -0,0 +1,129 @@
+/********************************************************************
+ * gnc-recurrence-xml-v2.c -- xml routines for Recurrence           *
+ *                                                                  *
+ * Copyright (C) 2005 Chris Shoemaker <[hidden email]>         *
+ *                                                                  *
+ * 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       *
+ * 59 Temple Place - Suite 330        Fax:    +1-617-542-2652       *
+ * Boston, MA  02111-1307,  USA       [hidden email]                   *
+ *                                                                  *
+ *******************************************************************/
+
+
+#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;
+}
Index: gnucash/src/backend/file/gnc-xml.h
===================================================================
--- gnucash.orig/src/backend/file/gnc-xml.h
+++ gnucash/src/backend/file/gnc-xml.h
@@ -32,6 +32,7 @@
 #include "qofbook.h"
 #include "gnc-engine.h"
 #include "gnc-pricedb.h"
+#include "gnc-budget.h"
 #include "gnc-xml-helper.h"
 #include "sixtp.h"
 
@@ -58,6 +59,9 @@ sixtp* gnc_pricedb_sixtp_parser_create(v
 xmlNodePtr gnc_schedXaction_dom_tree_create( SchedXaction *sx );
 sixtp* gnc_schedXaction_sixtp_parser_create(void);
 
+xmlNodePtr gnc_budget_dom_tree_create( GncBudget *bgt );
+sixtp* gnc_budget_sixtp_parser_create(void);
+
 xmlNodePtr gnc_transaction_dom_tree_create(Transaction *txn);
 sixtp* gnc_transaction_sixtp_parser_create(void);
 
Index: gnucash/src/backend/file/io-gncxml-v2.c
===================================================================
--- gnucash.orig/src/backend/file/io-gncxml-v2.c
+++ gnucash/src/backend/file/io-gncxml-v2.c
@@ -273,7 +273,7 @@ add_template_transaction_local( sixtp_gd
             xaccGetAccountFromName( acctGroup,
                                     xaccAccountGetName( (Account*)n->data ) );
             if ( tmpAcct != NULL ) {
-/* XXX hack alert FIXME .... Should the be 'Remove', or 'Destroy'?
+/* XXX hack alert FIXME .... Should this be 'Remove', or 'Destroy'?
  * If we just remove, then this seems to be a memory leak to me, since
  * it is never reparented.  Shouldn't it be a Destroy ???
  */
@@ -302,6 +302,16 @@ add_pricedb_local(sixtp_gdv2 *data, GNCP
     return TRUE;
 }
 
+#if 0
+static gboolean
+add_budget_local(sixtp_gdv2 *data, GncBudget *bgt)
+{
+/* CAS:I don't think anything is needed here, because budgets are
+ * automatically added to their book's collections when they are created. */
+  return TRUE;
+}
+#endif
+
 static void
 do_counter_cb (const char *type, gpointer data_p, gpointer be_data_p)
 {
@@ -379,6 +389,10 @@ gnc_counter_end_handler(gpointer data_fo
     {
         sixdata->counter.schedXactions_total = val;
     }
+    else if(safe_strcmp(type, "budget") == 0)
+    {
+        sixdata->counter.budgets_total = val;
+    }
     else
     {
       struct file_backend be_data;
@@ -421,6 +435,8 @@ print_counter_data(load_counter *data)
            data->commodities_total, data->commodities_loaded);
     PINFO("Scheduled Tansactions: Total: %d, Loaded: %d",
            data->schedXactions_total, data->schedXactions_loaded);
+    PINFO("Budgets: Total: %d, Loaded: %d",
+  data->budgets_total, data->budgets_loaded);
 }
 
 static void
@@ -436,13 +452,15 @@ file_rw_feedback (sixtp_gdv2 *gd, const
     counter = &gd->counter;
     loaded = counter->transactions_loaded + counter->accounts_loaded +
       counter->books_loaded + counter->commodities_loaded +
-      counter->schedXactions_loaded;
+      counter->schedXactions_loaded + counter->budgets_loaded;
     total = counter->transactions_total + counter->accounts_total +
       counter->books_total + counter->commodities_total +
-      counter->schedXactions_total;
+      counter->schedXactions_total + counter->budgets_total;
 
     percentage = (loaded * 100)/total;
     if (percentage > 100) {
+      /* FIXME: Perhaps the below should be replaced by:
+ print_counter_data(counter); */
       printf("Transactions: Total: %d, Loaded: %d\n",
              counter->transactions_total, counter->transactions_loaded);
       printf("Accounts: Total: %d, Loaded: %d\n",
@@ -453,6 +471,8 @@ file_rw_feedback (sixtp_gdv2 *gd, const
              counter->commodities_total, counter->commodities_loaded);
       printf("Scheduled Tansactions: Total: %d, Loaded: %d\n",
              counter->schedXactions_total, counter->schedXactions_loaded);
+      printf("Budgets: Total: %d, Loaded: %d\n",
+     counter->budgets_total, counter->budgets_loaded);
     }
     percentage = MIN(percentage, 100);
     gd->gui_display_fn(NULL, percentage);
@@ -468,6 +488,7 @@ static const char *COUNT_DATA_TAG = "gnc
 static const char *TRANSACTION_TAG = "gnc:transaction";
 static const char *SCHEDXACTION_TAG = "gnc:schedxaction";
 static const char *TEMPLATE_TRANSACTION_TAG = "gnc:template-transactions";
+static const char *BUDGET_TAG = "gnc:budget";
 
 static void
 add_item_cb (const char *type, gpointer data_p, gpointer be_data_p)
@@ -514,10 +535,15 @@ book_callback(const char *tag, gpointer
     {
         add_schedXaction_local(gd, (SchedXaction*)data);
     }
-    else if(safe_strcmp(tag, TEMPLATE_TRANSACTION_TAG ) == 0 )
+    else if(safe_strcmp(tag, TEMPLATE_TRANSACTION_TAG) == 0)
     {
         add_template_transaction_local( gd, (gnc_template_xaction_data*)data );
     }
+    else if(safe_strcmp(tag, BUDGET_TAG) == 0)
+    {
+        // What's this for?  Needed?
+        //add_budget_local(gd, (GncBudget *)data);
+    }
     else
     {
       struct file_backend be_data;
@@ -612,6 +638,8 @@ gnc_sixtp_gdv2_new (
     gd->counter.prices_total = 0;
     gd->counter.schedXactions_loaded = 0;
     gd->counter.schedXactions_total = 0;
+    gd->counter.budgets_loaded = 0;
+    gd->counter.budgets_total = 0;
     gd->exporting = exporting;
     gd->countCallback = countcallback;
     gd->gui_display_fn = gui_display_fn;
@@ -670,6 +698,7 @@ qof_session_load_from_xml_file_v2(FileBa
            PRICEDB_TAG, gnc_pricedb_sixtp_parser_create(),
            COMMODITY_TAG, gnc_commodity_sixtp_parser_create(),
            ACCOUNT_TAG, gnc_account_sixtp_parser_create(),
+           BUDGET_TAG, gnc_budget_sixtp_parser_create(),
            TRANSACTION_TAG, gnc_transaction_sixtp_parser_create(),
            SCHEDXACTION_TAG, gnc_schedXaction_sixtp_parser_create(),
            TEMPLATE_TRANSACTION_TAG, gnc_template_transaction_sixtp_parser_create(),
@@ -796,6 +825,7 @@ static void write_pricedb (FILE *out, Qo
 static void write_transactions (FILE *out, QofBook *book, sixtp_gdv2 *gd);
 static void write_template_transaction_data (FILE *out, QofBook *book, sixtp_gdv2 *gd);
 static void write_schedXactions(FILE *out, QofBook *book, sixtp_gdv2 *gd);
+static void write_budgets(FILE *out, QofBook *book, sixtp_gdv2 *gd);
 
 static void
 write_counts_cb (const char *type, gpointer data_p, gpointer be_data_p)
@@ -864,6 +894,9 @@ write_book(FILE *out, QofBook *book, six
  }
     write_book_parts (out, book);
 
+    /* gd->counter.{foo}_total fields should have all these totals
+       already collected.  I don't know why we're re-calling all these
+       functions.  */
     write_counts(out,
                  "commodity",
                  gnc_commodity_table_get_size(
@@ -874,7 +907,9 @@ write_book(FILE *out, QofBook *book, six
                  gnc_book_count_transactions(book),
                  "schedxaction",
                  g_list_length( gnc_book_get_schedxactions(book) ),
-                 NULL);
+ "budget",
+ g_list_length(gnc_book_get_budgets(book)),
+ NULL);
 
  qof_object_foreach_backend (GNC_FILE_BACKEND, write_counts_cb, &be_data);
 
@@ -884,7 +919,7 @@ write_book(FILE *out, QofBook *book, six
     write_transactions(out, book, gd);
     write_template_transaction_data(out, book, gd);
     write_schedXactions(out, book, gd);
-
+    write_budgets(out, book, gd);
     qof_object_foreach_backend (GNC_FILE_BACKEND, write_data_cb, &be_data);
 
     if(fprintf( out, "</%s>\n", BOOK_TAG ) < 0) {
@@ -1034,6 +1069,29 @@ write_schedXactions( FILE *out, QofBook
     } while ( (schedXactions = schedXactions->next) );
 }
 
+static void
+write_budgets( FILE *out, QofBook *book, sixtp_gdv2 *gd)
+{
+    GList *budgetList;
+    GncBudget *tmp;
+    xmlNodePtr node;
+
+    /* get list of budgets from QofBook */
+    budgetList = gnc_book_get_budgets(book);
+
+    g_return_if_fail(budgetList);
+
+    do {
+        tmp = budgetList->data;
+        node = gnc_budget_dom_tree_create( tmp );
+        xmlElemDump( out, NULL, node );
+        fprintf( out, "\n" );
+        xmlFreeNode( node );
+        gd->counter.budgets_loaded++;
+        run_callback(gd, "budgets");
+    } while ((budgetList = budgetList->next));
+}
+
 void
 gnc_xml2_write_namespace_decl (FILE *out, const char *namespace)
 {
@@ -1102,7 +1160,7 @@ gnc_book_write_to_xml_filehandle_v2(QofB
     gd->counter.transactions_total = gnc_book_count_transactions(book);
     gd->counter.schedXactions_total =
       g_list_length( gnc_book_get_schedxactions(book));
-
+    gd->counter.budgets_total = g_list_length(gnc_book_get_budgets(book));
     write_book(out, book, gd);
 
     fprintf(out, "</" GNC_V2_STRING ">\n\n");
@@ -1207,7 +1265,7 @@ gnc_book_write_to_xml_file_v2(
     FILE *out;
 
     out = try_gz_open(filename, "w", compress);
-     if (out == NULL)
+    if (out == NULL)
     {
         return FALSE;
     }
Index: gnucash/src/backend/file/io-gncxml-v2.h
===================================================================
--- gnucash.orig/src/backend/file/io-gncxml-v2.h
+++ gnucash/src/backend/file/io-gncxml-v2.h
@@ -58,6 +58,9 @@ typedef struct
 
     int schedXactions_total;
     int schedXactions_loaded;
+
+    int budgets_total;
+    int budgets_loaded;
 } load_counter;
 
 typedef struct sixtp_gdv2 sixtp_gdv2;
Index: gnucash/src/backend/file/sixtp-dom-generators.c
===================================================================
--- gnucash.orig/src/backend/file/sixtp-dom-generators.c
+++ gnucash/src/backend/file/sixtp-dom-generators.c
@@ -38,14 +38,14 @@ static QofLogModule log_module = GNC_MOD
 xmlNodePtr
 text_to_dom_tree(const char *tag, const char *str)
 {
-  xmlNodePtr result;
+    xmlNodePtr result;
 
-  g_return_val_if_fail(tag, NULL);
-  g_return_val_if_fail(str, NULL);
-  result = xmlNewNode(NULL, BAD_CAST tag);
-  g_return_val_if_fail(result, NULL);
-  xmlNodeAddContent(result, BAD_CAST str);
-  return result;
+    g_return_val_if_fail(tag, NULL);
+    g_return_val_if_fail(str, NULL);
+    result = xmlNewNode(NULL, BAD_CAST tag);
+    g_return_val_if_fail(result, NULL);
+    xmlNodeAddContent(result, BAD_CAST str);
+    return result;
 }
 
 xmlNodePtr
@@ -55,11 +55,26 @@ int_to_dom_tree(const char *tag, gint64
     xmlNodePtr result;
 
     text = g_strdup_printf("%" G_GINT64_FORMAT, val);
+    g_return_val_if_fail(text, NULL);
     result = text_to_dom_tree(tag, text);
     g_free(text);
     return result;
 }
+
+xmlNodePtr
+guint_to_dom_tree(const char *tag, guint an_int)
+{
+    gchar *text;
+    xmlNodePtr result;
     
+    text = g_strdup_printf("%u", an_int );
+    g_return_val_if_fail(text, NULL);
+    result = text_to_dom_tree(tag, text);
+    g_free(text);
+    return result;
+}
+
+
 xmlNodePtr
 guid_to_dom_tree(const char *tag, const GUID* gid)
 {
@@ -72,7 +87,7 @@ guid_to_dom_tree(const char *tag, const
 
     if (!guid_to_string_buff(gid, guid_str))
     {
-        PERR("guid_to_string failed\n");
+        PERR("guid_to_string_buff failed\n");
         return NULL;
     }
 
@@ -158,7 +173,7 @@ timespec_to_dom_tree(const char *tag, co
 }
 
 xmlNodePtr
-gdate_to_dom_tree(const char *tag, GDate *date)
+gdate_to_dom_tree(const char *tag, const GDate *date)
 {
  xmlNodePtr ret;
  gchar *date_str = NULL;
@@ -275,6 +290,7 @@ add_kvp_value_node(xmlNodePtr node, gcha
         xmlSetProp(val_node, BAD_CAST "type", BAD_CAST "string");
         break;
     case KVP_TYPE_GUID:
+        /* THREAD-UNSAFE */
         add_text_to_node(val_node,"guid",
                          g_strdup(guid_to_string(kvp_value_get_guid(val))));
         break;
@@ -371,19 +387,3 @@ kvp_frame_to_dom_tree(const char *tag, c
     return ret;
 }
 
-xmlNodePtr guint_to_dom_tree(const char *tag, guint an_int)
-{
-    xmlNodePtr ret;
-    gchar *numstr;
-
-    numstr = g_strdup_printf( "%u", an_int );
-    g_return_val_if_fail(numstr, NULL);
-
-    ret = xmlNewNode(NULL, BAD_CAST tag);
-
-    xmlNodeAddContent(ret, BAD_CAST numstr);
-
-    g_free(numstr);
-
-    return ret;
-}
Index: gnucash/src/backend/file/sixtp-dom-generators.h
===================================================================
--- gnucash.orig/src/backend/file/sixtp-dom-generators.h
+++ gnucash/src/backend/file/sixtp-dom-generators.h
@@ -34,6 +34,7 @@
 #include "gnc-commodity.h"
 #include "gnc-date.h"
 #include "gnc-numeric.h"
+#include "Recurrence.h"
 #include "kvp_frame.h"
 #include "qofid.h"
 
@@ -44,10 +45,11 @@ xmlNodePtr commodity_ref_to_dom_tree(con
 xmlNodePtr timespec_to_dom_tree(const char *tag, const Timespec *spec);
 gchar * timespec_nsec_to_string(const Timespec *ts);
 gchar * timespec_sec_to_string(const Timespec *ts);
-xmlNodePtr gdate_to_dom_tree(const char *tag, GDate *spec);
+xmlNodePtr gdate_to_dom_tree(const char *tag, const GDate *spec);
 xmlNodePtr gnc_numeric_to_dom_tree(const char *tag, const gnc_numeric *num);
 xmlNodePtr kvp_frame_to_dom_tree(const char *tag, const kvp_frame *frame);
 xmlNodePtr guint_to_dom_tree(const char *tag, guint an_int);
+xmlNodePtr recurrence_to_dom_tree(const gchar *tag, const Recurrence *r);
 
 gchar* double_to_string(double value);
 
Index: gnucash/src/backend/file/sixtp-dom-parsers.c
===================================================================
--- gnucash.orig/src/backend/file/sixtp-dom-parsers.c
+++ gnucash/src/backend/file/sixtp-dom-parsers.c
@@ -111,6 +111,33 @@ dom_tree_to_integer(xmlNodePtr node, gin
     return ret;
 }
 
+gboolean
+dom_tree_to_guint16(xmlNodePtr node, guint16 *i)
+{
+    gboolean ret;
+    guint j = 0;
+
+    ret = dom_tree_to_guint(node, &j);
+    *i = (guint16) j;
+    return ret;
+}
+
+gboolean
+dom_tree_to_guint(xmlNodePtr node, guint *i)
+{
+    gchar *text, *endptr;
+    gboolean ret;
+
+    text = dom_tree_to_text(node);
+    /* In spite of the strange string_to_gint64 function, I'm just
+       going to use strtoul here until someone shows me the error of
+       my ways. -CAS */
+    *i = (guint) strtoul(text, &endptr, 0);
+    ret = (endptr != text);
+    g_free(text);
+    return ret;
+}
+
 kvp_value*
 dom_tree_to_double_kvp_value(xmlNodePtr node)
 {
Index: gnucash/src/backend/file/sixtp-dom-parsers.h
===================================================================
--- gnucash.orig/src/backend/file/sixtp-dom-parsers.h
+++ gnucash/src/backend/file/sixtp-dom-parsers.h
@@ -36,7 +36,7 @@
 #include "kvp_frame.h"
 #include "qofbook.h"
 #include "qofid.h"
-
+#include "gnc-budget.h"
 
 GUID* dom_tree_to_guid(xmlNodePtr node);
 
@@ -44,6 +44,7 @@ gnc_commodity* dom_tree_to_commodity_ref
 gnc_commodity *dom_tree_to_commodity_ref_no_engine(xmlNodePtr node, QofBook *);
 
 FreqSpec* dom_tree_to_freqSpec( xmlNodePtr node, QofBook *book);
+Recurrence* dom_tree_to_recurrence(xmlNodePtr node);
 
 Timespec dom_tree_to_timespec(xmlNodePtr node);
 #define is_valid_timespec(ts) (ts.tv_sec || ts.tv_nsec)
@@ -67,12 +68,15 @@ kvp_value* dom_tree_to_list_kvp_value(xm
 kvp_value* dom_tree_to_frame_kvp_value(xmlNodePtr node);
 
 gboolean dom_tree_to_integer(xmlNodePtr node, gint64 *daint);
+gboolean dom_tree_to_guint16(xmlNodePtr node, guint16 *i);
+gboolean dom_tree_to_guint(xmlNodePtr node, guint *i);
 
 /* higher level structures */
 Account* dom_tree_to_account(xmlNodePtr node, QofBook *book);
 QofBook* dom_tree_to_book   (xmlNodePtr node, QofBook *book);
 GNCLot*  dom_tree_to_lot    (xmlNodePtr node, QofBook *book);
 Transaction* dom_tree_to_transaction(xmlNodePtr node, QofBook *book);
+GncBudget* dom_tree_to_budget(xmlNodePtr node, QofBook *book);
 
 struct dom_tree_handler
 {
Index: gnucash/src/backend/file/sixtp-utils.c
===================================================================
--- gnucash.orig/src/backend/file/sixtp-utils.c
+++ gnucash/src/backend/file/sixtp-utils.c
@@ -211,7 +211,8 @@ string_to_double(const char *str, double
 /*********/
 /* gint64
  */
-
+/* Maybe there should be a comment here explaining why this function
+   doesn't call g_ascii_strtoull, because it's not so obvious. -CAS */
 gboolean
 string_to_gint64(const gchar *str, gint64 *v)
 {
Index: gnucash/src/backend/file/test/Makefile.am
===================================================================
--- gnucash.orig/src/backend/file/test/Makefile.am
+++ gnucash/src/backend/file/test/Makefile.am
@@ -39,11 +39,13 @@ test_load_example_account_SOURCES = \
   ${top_srcdir}/src/backend/file/sixtp-stack.c \
   ${top_srcdir}/src/backend/file/sixtp-to-dom-parser.c \
   ${top_srcdir}/src/backend/file/io-example-account.c \
-  ${top_srcdir}/src/backend/file/gnc-account-xml-v2.c \
   ${top_srcdir}/src/backend/file/io-gncxml-gen.c \
   ${top_srcdir}/src/backend/file/io-gncxml-v2.c \
   ${top_srcdir}/src/backend/file/io-utils.c \
+  ${top_srcdir}/src/backend/file/gnc-account-xml-v2.c \
+  ${top_srcdir}/src/backend/file/gnc-budget-xml-v2.c \
   ${top_srcdir}/src/backend/file/gnc-lot-xml-v2.c \
+  ${top_srcdir}/src/backend/file/gnc-recurrence-xml-v2.c \
   ${top_srcdir}/src/backend/file/gnc-schedxaction-xml-v2.c \
   ${top_srcdir}/src/backend/file/gnc-freqspec-xml-v2.c \
   ${top_srcdir}/src/backend/file/gnc-transaction-xml-v2.c \
@@ -124,9 +126,11 @@ test_xml_transaction_SOURCES = \
   ${top_srcdir}/src/backend/file/sixtp-to-dom-parser.c \
   ${top_srcdir}/src/backend/file/io-gncxml-gen.c \
   ${top_srcdir}/src/backend/file/gnc-account-xml-v2.c \
+  ${top_srcdir}/src/backend/file/gnc-budget-xml-v2.c \
   ${top_srcdir}/src/backend/file/gnc-lot-xml-v2.c \
   ${top_srcdir}/src/backend/file/gnc-schedxaction-xml-v2.c \
   ${top_srcdir}/src/backend/file/gnc-freqspec-xml-v2.c \
+  ${top_srcdir}/src/backend/file/gnc-recurrence-xml-v2.c \
   ${top_srcdir}/src/backend/file/gnc-transaction-xml-v2.c \
   ${top_srcdir}/src/backend/file/gnc-commodity-xml-v2.c \
   ${top_srcdir}/src/backend/file/gnc-book-xml-v2.c \
@@ -143,7 +147,9 @@ test_xml2_is_file_SOURCES = \
   ${top_srcdir}/src/backend/file/sixtp-stack.c \
   ${top_srcdir}/src/backend/file/sixtp-to-dom-parser.c \
   ${top_srcdir}/src/backend/file/gnc-account-xml-v2.c \
+  ${top_srcdir}/src/backend/file/gnc-budget-xml-v2.c \
   ${top_srcdir}/src/backend/file/gnc-lot-xml-v2.c \
+  ${top_srcdir}/src/backend/file/gnc-recurrence-xml-v2.c \
   ${top_srcdir}/src/backend/file/gnc-schedxaction-xml-v2.c \
   ${top_srcdir}/src/backend/file/gnc-freqspec-xml-v2.c \
   ${top_srcdir}/src/backend/file/gnc-transaction-xml-v2.c \

--
_______________________________________________
gnucash-patches mailing list
[hidden email]
https://lists.gnucash.org/mailman/listinfo/gnucash-patches
Reply | Threaded
Open this post in threaded view
|

[patch 7/8] [budget-report.diff] add a Budget Report

Chris Shoemaker
In reply to this post by Chris Shoemaker
 * src/report/report-system/html-utilities.scm
 * src/report/report-system/report-system.scm
 * src/report/standard-reports/Makefile.am
 * src/report/standard-reports/budget.scm
 * src/report/standard-reports/standard-reports.scm
   - add a Budget Report


 src/report/report-system/html-utilities.scm      |   28 +
 src/report/report-system/report-system.scm       |    3
 src/report/standard-reports/Makefile.am          |    1
 src/report/standard-reports/budget.scm           |  376 +++++++++++++++++++++++
 src/report/standard-reports/standard-reports.scm |    1
 5 files changed, 408 insertions(+), 1 deletion(-)

Index: gnucash/src/report/report-system/html-utilities.scm
===================================================================
--- gnucash.orig/src/report/report-system/html-utilities.scm
+++ gnucash/src/report/report-system/html-utilities.scm
@@ -784,6 +784,34 @@
     
     table))
 
+
+;; TODO: How 'bout factoring the "Edit report options" stuff out of
+;; these 3 functions?
+
+(define (gnc:html-make-generic-options-warning
+ report-title-string report-id)
+  (let ((p (gnc:make-html-text)))
+    (gnc:html-text-append!
+     p
+     (gnc:html-markup-h2 (string-append
+  report-title-string
+  ":"))
+     (gnc:html-markup-h2 (_ ""))
+     (gnc:html-markup-p
+      (_ "This report requires you to specify certain report options.")))
+    (if report-id
+ (gnc:html-text-append!
+ p
+ (gnc:html-markup-p
+  (gnc:html-markup-anchor
+   (gnc:html-build-url gnc:url-type-options
+       (string-append "report-id="
+      (sprintf #f "%a" report-id))
+       #f)
+   (_ "Edit report options")))))
+    p))
+
+
 (define (gnc:html-make-no-account-warning
  report-title-string report-id)
   (let ((p (gnc:make-html-text)))
Index: gnucash/src/report/report-system/report-system.scm
===================================================================
--- gnucash.orig/src/report/report-system/report-system.scm
+++ gnucash/src/report/report-system/report-system.scm
@@ -89,6 +89,7 @@
 (export gnc:first-html-build-acct-table)
 (export gnc:html-make-exchangerates)
 (export gnc:html-make-no-account-warning)
+(export gnc:html-make-generic-options-warning)
 (export gnc:html-make-empty-data-warning)
 
 ;; report.scm
@@ -435,7 +436,7 @@
 (export gnc:account-code-less-p)
 (export gnc:account-name-less-p)
 (export gnc:account-path-less-p)
-(export gnc:identity)
+;;(export gnc:identity)
 (export gnc:html-table-add-labeled-amount-line!)
 (export gnc:html-table-add-account-balances)
 (export gnc:second-html-build-acct-table)
Index: gnucash/src/report/standard-reports/Makefile.am
===================================================================
--- gnucash.orig/src/report/standard-reports/Makefile.am
+++ gnucash/src/report/standard-reports/Makefile.am
@@ -26,6 +26,7 @@ gncscmmod_DATA = \
    average-balance.scm \
    balance-sheet.scm \
    cash-flow.scm \
+   budget.scm \
    category-barchart.scm \
    daily-reports.scm \
    equity-statement.scm \
Index: gnucash/src/report/standard-reports/budget.scm
===================================================================
--- /dev/null
+++ gnucash/src/report/standard-reports/budget.scm
@@ -0,0 +1,376 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; budget.scm: budget report
+;;
+;; (C) 2005 by Chris Shoemaker <[hidden email]>
+;;
+;; based on cash-flow.scm by:
+;; Herbert Thoma <[hidden email]>
+;;
+;; 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
+;; 59 Temple Place - Suite 330        Fax:    +1-617-542-2652
+;; Boston, MA  02111-1307,  USA       [hidden email]
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(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)
+
Index: gnucash/src/report/standard-reports/standard-reports.scm
===================================================================
--- gnucash.orig/src/report/standard-reports/standard-reports.scm
+++ gnucash/src/report/standard-reports/standard-reports.scm
@@ -75,6 +75,7 @@
 (use-modules (gnucash report general-journal))
 (use-modules (gnucash report general-ledger))
 (use-modules (gnucash report cash-flow))
+(use-modules (gnucash report budget))
 (use-modules (gnucash report category-barchart))
 (use-modules (gnucash report daily-reports))
 (use-modules (gnucash report net-barchart))

--
_______________________________________________
gnucash-patches mailing list
[hidden email]
https://lists.gnucash.org/mailman/listinfo/gnucash-patches
Reply | Threaded
Open this post in threaded view
|

[patch 8/8] [budget-remove-legacy.diff] Remove some legacy budget workbench stuff

Chris Shoemaker
In reply to this post by Chris Shoemaker
 * src/gnome/gnc-plugin-basic-commands.c
 * src/gnome/ui/gnc-plugin-basic-commands-ui.xml
  - remove some budget workbench stuff

 src/gnome/gnc-plugin-basic-commands.c         |   11 -----------
 src/gnome/ui/gnc-plugin-basic-commands-ui.xml |    1 -
 2 files changed, 12 deletions(-)

Index: gnucash/src/gnome/gnc-plugin-basic-commands.c
===================================================================
--- gnucash.orig/src/gnome/gnc-plugin-basic-commands.c
+++ gnucash/src/gnome/gnc-plugin-basic-commands.c
@@ -26,7 +26,6 @@
 
 #include "gnc-plugin-basic-commands.h"
 
-#include "dialog-budget-list.h"
 #include "dialog-chart-export.h"
 #include "dialog-fincalc.h"
 #include "dialog-find-transactions.h"
@@ -61,7 +60,6 @@ static void gnc_main_window_cmd_file_cha
 static void gnc_main_window_cmd_edit_tax_options (GtkAction *action, GncMainWindowActionData *data);
 static void gnc_main_window_cmd_actions_mortgage_loan (GtkAction *action, GncMainWindowActionData *data);
 static void gnc_main_window_cmd_actions_scheduled_transaction_editor (GtkAction *action, GncMainWindowActionData *data);
-static void gnc_main_window_cmd_actions_budget_workbench(GtkAction *action, GncMainWindowActionData *data);
 static void gnc_main_window_cmd_actions_since_last_run (GtkAction *action, GncMainWindowActionData *data);
 static void gnc_main_window_cmd_actions_close_books (GtkAction *action, GncMainWindowActionData *data);
 static void gnc_main_window_cmd_tools_financial_calculator (GtkAction *action, GncMainWindowActionData *data);
@@ -125,9 +123,6 @@ static GtkActionEntry gnc_plugin_actions
   { "ActionsMortgageLoanAction", NULL, N_("_Mortgage & Loan Repayment..."), NULL,
     N_("Setup scheduled transactions for repayment of a loan"),
     G_CALLBACK (gnc_main_window_cmd_actions_mortgage_loan) },
-  { "ActionsBudgetWorkbenchAction", NULL, N_("Budget _Workbench (Experimental)"), NULL,
-    N_("Create, Manage, and Monitor Budgets." ),
-    G_CALLBACK(gnc_main_window_cmd_actions_budget_workbench) },
   { "ActionsCloseBooksAction", NULL, N_("Close _Books"), NULL,
     N_("Archive old data using accounting periods"),
     G_CALLBACK (gnc_main_window_cmd_actions_close_books) },
@@ -397,12 +392,6 @@ gnc_main_window_cmd_actions_mortgage_loa
   gnc_ui_sx_loan_druid_create ();
 }
 
-static void
-gnc_main_window_cmd_actions_budget_workbench(GtkAction *action, GncMainWindowActionData *data)
-{
-  gnc_budget_list_dialog_create();
-}
-
 static void
 gnc_main_window_cmd_actions_close_books (GtkAction *action, GncMainWindowActionData *data)
 {
Index: gnucash/src/gnome/ui/gnc-plugin-basic-commands-ui.xml
===================================================================
--- gnucash.orig/src/gnome/ui/gnc-plugin-basic-commands-ui.xml
+++ gnucash/src/gnome/ui/gnc-plugin-basic-commands-ui.xml
@@ -48,7 +48,6 @@
           <menuitem name="ActionsSinceLastRun" action="ActionsSinceLastRunAction"/>
           <menuitem name="ActionsMortgageLoan" action="ActionsMortgageLoanAction"/>
         </menu>
-        <menuitem name="ActionsBudgetWorkbenchAction" action="ActionsBudgetWorkbenchAction"/>
         <menuitem name="ActionsCloseBooks" action="ActionsCloseBooksAction"/>
       </placeholder>
     </menu>

--
_______________________________________________
gnucash-patches mailing list
[hidden email]
https://lists.gnucash.org/mailman/listinfo/gnucash-patches
Reply | Threaded
Open this post in threaded view
|

Re: [patch 0/8] Budget patches

Chris Shoemaker
In reply to this post by Chris Shoemaker
On Sat, Oct 15, 2005 at 12:18:27AM -0400, [hidden email] wrote:

Hrmm.. looks like quilt was a bit too eager to rewrite my log file.
That should of course be 1-8 of 8.

> Subject: [patch 0/8] [etags.diff] fixup tags dependencies
> Subject: [patch 0/8] [recurrence.diff] Add the Recurrence data type to the engine
> Subject: [patch 0/8] [budget-engine.diff] Core Budget implementation
> Subject: [patch 0/8] [budget-gui.diff] GUI support for Budgets
> Subject: [patch 0/8] [budget-option.diff] Add an option widget for selecting a budget
> Subject: [patch 0/8] [budget-persist.diff] Budget persistence for the file backend
> Subject: [patch 0/8] [budget-report.diff] add a Budget Report
> Subject: [patch 0/8] [budget-remove-legacy.diff] Remove some legacy budget workbench stuff
_______________________________________________
gnucash-patches mailing list
[hidden email]
https://lists.gnucash.org/mailman/listinfo/gnucash-patches