diff --git a/src/SplitLedger.c b/src/SplitLedger.c index f50a87cc98..3a939cff13 100644 --- a/src/SplitLedger.c +++ b/src/SplitLedger.c @@ -255,6 +255,67 @@ xaccSRSetReverseBalanceCallback(SRReverseBalanceCallback callback) } +/* copies basic split values from 'from' split to 'to' split. + * doesn't copy reconciled flag, or open the parent transactions + * for editing. Does *not* insert the 'to' split into an account!! */ +static void +gnc_copy_split(Split *from, Split *to) +{ + if ((from == NULL) || (to == NULL)) + return; + + xaccSplitSetMemo(to, xaccSplitGetMemo(from)); + xaccSplitSetAction(to, xaccSplitGetAction(from)); + xaccSplitSetDocref(to, xaccSplitGetDocref(from)); + xaccSplitSetSharePriceAndAmount(to, + xaccSplitGetSharePrice(from), + xaccSplitGetShareAmount(from)); +} + +/* copies the basic transaction values and the splits from the + * 'from' trans to the 'to' trans. Any existing splits in the + * 'to' trans are deleted. Does *not* open the 'to' trans for + * editing!!! Splits are copied using gnc_copy_split above. + * The new splits will be in exactly the same order as in + * the 'from' transaction. */ +static void +gnc_copy_trans(Transaction *from, Transaction *to) +{ + Split *from_split, *to_split; + Timespec timespec; + int num_splits; + int i; + + if ((from == NULL) || (to == NULL)) + return; + + /* remove the old splits */ + to_split = xaccTransGetSplit(to, 0); + while (to_split != NULL) + { + xaccSplitDestroy(to_split); + to_split = xaccTransGetSplit(to, 0); + } + + /* add in the new splits */ + num_splits = xaccTransCountSplits(from); + for (i = 0; i < num_splits; i++) + { + from_split = xaccTransGetSplit(from, i); + + to_split = xaccMallocSplit(); + gnc_copy_split(from_split, to_split); + + xaccTransAppendSplit(to, to_split); + } + + /* now do the transaction-specific values */ + xaccTransGetDateTS(from, ×pec); + xaccTransSetDateTS(to, ×pec); + xaccTransSetDescription(to, xaccTransGetDescription(from)); + xaccTransSetDocref(to, xaccTransGetDocref(from)); +} + /* ======================================================== */ /* this callback gets called when the user clicks on the gui * in such a way as to leave the current transaction, and to @@ -583,6 +644,144 @@ xaccSRGetSplitRowCol (SplitRegister *reg, Split *split, /* ======================================================== */ +Split * +xaccSRDuplicateCurrent (SplitRegister *reg) +{ + SRInfo *info = xaccSRGetInfo(reg); + CursorType cursor_type; + unsigned int changed; + Transaction *trans; + Split *return_split; + Split *split; + + split = xaccSRGetCurrentSplit(reg); + trans = xaccSplitGetParent(split); + + /* This shouldn't happen, but be paranoid. */ + if (trans == NULL) + return NULL; + + cursor_type = xaccSplitRegisterGetCursorType(reg); + + /* Can't do anything with this. */ + if (cursor_type == CURSOR_NONE) + return NULL; + + /* This shouldn't happen, but be paranoid. */ + if ((split == NULL) && (cursor_type == CURSOR_TRANS)) + return NULL; + + changed = xaccSplitRegisterGetChangeFlag(reg); + + /* See if we were asked to duplicate an unchanged blank split. + * There's no point in doing that! */ + if (!changed && ((split == NULL) || (split == info->blank_split))) + return NULL; + + /* If the cursor has been edited, we are going to have to commit + * it before we can duplicate. Make sure the user wants to do that. */ + if (changed) + { + GNCVerifyResult result; + + result = gnc_ok_cancel_dialog_parented(xaccSRGetParent(reg), + TRANS_CHANGED_MSG, + GNC_VERIFY_OK); + + if (result == GNC_VERIFY_CANCEL) + return NULL; + + xaccSRSaveRegEntry(reg, NULL); + + /* If the split is NULL, then we were on a blank split row + * in an expanded transaction. The new split (created by + * xaccSRSaveRegEntry above) will be the last split in the + * current transaction, as it was just added. */ + if (split == NULL) + split = xaccTransGetSplit(trans, xaccTransCountSplits(trans) - 1); + } + + /* Ok, we are now ready to make the copy. */ + + if (cursor_type == CURSOR_SPLIT) + { + Split *new_split; + Account *account; + + /* We are on a split in an expanded transaction. + * Just copy the split and add it to the transaction. */ + + new_split = xaccMallocSplit(); + + gnc_copy_split(split, new_split); + + account = xaccSplitGetAccount(split); + + xaccTransBeginEdit(trans, GNC_T); + xaccAccountBeginEdit(account, GNC_T); + + xaccTransAppendSplit(trans, new_split); + xaccAccountInsertSplit(account, new_split); + + xaccAccountCommitEdit(account); + xaccTransCommitEdit(trans); + + return_split = new_split; + } + else + { + Transaction *new_trans; + int num_splits; + int i; + + /* We are on a transaction row. Copy the whole transaction. */ + + new_trans = xaccMallocTransaction(); + + xaccTransBeginEdit(new_trans, GNC_T); + + gnc_copy_trans(trans, new_trans); + + num_splits = xaccTransCountSplits(trans); + return_split = NULL; + + /* Link the new splits into the accounts. */ + for (i = 0; i < num_splits; i++) + { + Account *account; + Split *old_split; + Split *new_split; + + old_split = xaccTransGetSplit(trans, i); + account = xaccSplitGetAccount(old_split); + + new_split = xaccTransGetSplit(new_trans, i); + + xaccAccountBeginEdit(account, GNC_T); + xaccAccountInsertSplit(account, new_split); + xaccAccountCommitEdit(account); + + /* Returned split is the transaction split */ + if (old_split == split) + return_split = new_split; + } + + xaccTransCommitEdit(new_trans); + + /* This shouldn't happen, but be paranoid. */ + if (return_split == NULL) + return_split = xaccTransGetSplit(new_trans, 0); + } + + /* Refresh the GUI. */ + gnc_transaction_ui_refresh(trans); + gnc_refresh_main_window(); + + return return_split; +} + +/* ======================================================== */ + void xaccSRDeleteCurrentSplit (SplitRegister *reg) { @@ -598,8 +797,8 @@ xaccSRDeleteCurrentSplit (SplitRegister *reg) return; /* If we just deleted the blank split, clean up. The user is - * allowed to delete the blank split as a method for discarding any - * edits they may have made to it. */ + * allowed to delete the blank split as a method for discarding + * any edits they may have made to it. */ if (split == info->blank_split) { account = xaccSplitGetAccount(split); @@ -609,8 +808,7 @@ xaccSRDeleteCurrentSplit (SplitRegister *reg) /* make a copy of all of the accounts that will be * affected by this deletion, so that we can update - * their register windows after the deletion. - */ + * their register windows after the deletion. */ trans = xaccSplitGetParent(split); num_splits = xaccTransCountSplits(trans); affected_accounts = (Account **) malloc((num_splits + 1) * diff --git a/src/engine/Transaction.c b/src/engine/Transaction.c index 89f8cbd44d..eb3f4c9a77 100644 --- a/src/engine/Transaction.c +++ b/src/engine/Transaction.c @@ -78,9 +78,8 @@ static short module = MOD_ENGINE; \********************************************************************/ void -xaccInitSplit( Split * split ) - { - +xaccInitSplit(Split * split) +{ /* fill in some sane defaults */ split->acc = NULL; split->parent = NULL; @@ -104,18 +103,18 @@ xaccInitSplit( Split * split ) split->cost_basis = 0.0; split->tickee = 0; - } +} /********************************************************************\ \********************************************************************/ Split * -xaccMallocSplit( void ) - { +xaccMallocSplit(void) +{ Split *split = (Split *)_malloc(sizeof(Split)); xaccInitSplit (split); return split; - } +} /********************************************************************\ \********************************************************************/ @@ -160,7 +159,7 @@ xaccCloneSplit (Split *s) void xaccFreeSplit( Split *split ) - { +{ if (!split) return; if (split->memo) free (split->memo); @@ -477,7 +476,7 @@ xaccSplitSetBaseValue (Split *s, double value, char * base_currency) } } - /* be more precise -- the value depends on the curency + /* be more precise -- the value depends on the currency * we want it expressed in. */ if (!safe_strcmp(s->acc->currency, base_currency)) { diff --git a/src/gnome/query-user.c b/src/gnome/query-user.c index dd57fd1666..351376dc87 100644 --- a/src/gnome/query-user.c +++ b/src/gnome/query-user.c @@ -204,10 +204,65 @@ gnc_foundation_query_dialog(const gchar *title, return query_dialog; } +/********************************************************************\ + * gnc_ok_cancel_dialog_parented * + * display a message, and asks the user to press "Ok" or "Cancel" * + * * + * NOTE: This function does not return until the dialog is closed * + * * + * Args: parent - the parent window * + * message - the message to display * + * default - the button that will be the default * + * Return: the result the user selected * +\********************************************************************/ +GNCVerifyResult +gnc_ok_cancel_dialog_parented(gncUIWidget parent, const char *message, + GNCVerifyResult default_result) +{ + GtkWidget *dialog = NULL; + gint default_button; + gint result; + + dialog = gnome_message_box_new(message, + GNOME_MESSAGE_BOX_QUESTION, + GNOME_STOCK_BUTTON_OK, + GNOME_STOCK_BUTTON_CANCEL, + NULL); + + switch (default_result) + { + case GNC_VERIFY_OK: + default_button = 0; + break; + case GNC_VERIFY_CANCEL: + default_button = 1; + break; + default: + PWARN("gnc_verify_cancel_dialog: bad default button\n"); + default_button = 0; + break; + } + + gnome_dialog_set_default(GNOME_DIALOG(dialog), default_button); + if (parent != NULL) + gnome_dialog_set_parent(GNOME_DIALOG(dialog), GTK_WINDOW(parent)); + + result = gnome_dialog_run_and_close(GNOME_DIALOG(dialog)); + + switch (result) + { + case 0: + return GNC_VERIFY_OK; + case 1: + default: + return GNC_VERIFY_CANCEL; + } +} + /********************************************************************\ * gnc_verify_cancel_dialog * * display a message, and asks the user to press "Yes", "No", or * - * "Cancel" + * "Cancel" * * * * NOTE: This function does not return until the dialog is closed * * * diff --git a/src/gnome/window-reconcile.c b/src/gnome/window-reconcile.c index d4b17403fa..beaa769b3a 100644 --- a/src/gnome/window-reconcile.c +++ b/src/gnome/window-reconcile.c @@ -118,7 +118,7 @@ void recnRefresh(Account *account) { RecnWindow *recnData; - + FIND_IN_LIST (RecnWindow, recnList, account, account, recnData); if (recnData == NULL) return; diff --git a/src/gnome/window-reconcile.h b/src/gnome/window-reconcile.h index 49237592d0..48470d32b9 100644 --- a/src/gnome/window-reconcile.h +++ b/src/gnome/window-reconcile.h @@ -1,6 +1,7 @@ /********************************************************************\ * window-reconcile.h -- the reconcile window * * Copyright (C) 1997 Robin D. Clark * + * Copyright (C) 1998-2000 Linas Vepstas * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * diff --git a/src/gnome/window-register.c b/src/gnome/window-register.c index 9813da2b5f..c36b468719 100644 --- a/src/gnome/window-register.c +++ b/src/gnome/window-register.c @@ -31,6 +31,7 @@ #include +#include "gnome-top-level.h" #include "MultiLedger.h" #include "LedgerUtils.h" #include "MainWindow.h" diff --git a/src/gnome/window-report.c b/src/gnome/window-report.c index 36b6253b22..b8226731c4 100644 --- a/src/gnome/window-report.c +++ b/src/gnome/window-report.c @@ -3,6 +3,7 @@ * Copyright (C) 1997 Robin D. Clark * * Copyright (C) 1998 Linas Vepstas * * Copyright (C) 1999 Jeremy Collins ( gtk-xmhtml port ) * + * Copyright (C) 2000 Dave Peticolas * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * @@ -35,6 +36,7 @@ #include "option-util.h" #include "guile-util.h" #include "dialog-options.h" +#include "ui-callbacks.h" #include "query-user.h" #include "messages.h" #include "util.h" @@ -222,14 +224,21 @@ static char * gnc_run_report(ReportData *report_data) { SCM result, nil; + gncUIWidget window; if (!gh_procedure_p(report_data->rendering_thunk)) return NULL; + window = gnc_html_window_get_window(reportwindow); + + gnc_set_busy_cursor(window); + nil = gh_eval_str("()"); result = gfec_apply(report_data->rendering_thunk, nil, gnc_report_error_dialog); + gnc_unset_busy_cursor(window); + if (!gh_string_p(result)) return NULL; diff --git a/src/scm/date-utilities.scm b/src/scm/date-utilities.scm index d751782819..537c806179 100644 --- a/src/scm/date-utilities.scm +++ b/src/scm/date-utilities.scm @@ -24,7 +24,7 @@ (define (gnc:leap-year? year) (if (= (remainder year 4) 0) (if (= (remainder year 100) 0) - (if (= (remainder year 400) 0) #t #f) + (if (= (remainder (+ year 1900) 400) 0) #t #f) #t) #f)) @@ -47,10 +47,31 @@ (/ (gnc:date-get-year-day lt) (* 1.0 (gnc:days-in-year (gnc:date-get-year lt))))))) +;; return the number of years (in floating point format) between two dates. +(define (gnc:date-year-delta caltime1 caltime2) + (let* ((lt1 (localtime caltime1)) + (lt2 (localtime caltime2)) + (day1 (gnc:date-get-year-day lt1)) + (day2 (gnc:date-get-year-day lt2)) + (year1 (gnc:date-get-year lt1)) + (year2 (gnc:date-get-year lt2)) + (dayadj1 (if (and (not (gnc:leap-year? year1)) + (>= day1 59)) + (+ day1 1) + day1)) + (dayadj2 (if (and (not (gnc:leap-year? year2)) + (>= day2 59)) + (+ day2 1) + day2))) + (+ (- (gnc:date-get-year lt2) (gnc:date-get-year lt1)) + (/ (- dayadj2 dayadj1) + 366.0)))) + ;; convert a date in seconds since 1970 into # of months since 1970 (define (gnc:date-to-month-fraction caltime) (let ((lt (localtime caltime))) (+ (* 12 (- (gnc:date-get-year lt) 1970.0)) + (gnc:date-get-month lt) (/ (- (gnc:date-get-month-day lt) 1.0) (gnc:days-in-month (gnc:date-get-month lt) (gnc:date-get-year lt)))))) @@ -60,10 +81,10 @@ (define (gnc:date-to-week-fraction caltime) (/ (- (/ (/ caltime 3600.0) 24) 3) 7)) -;; convert a date in seconds since 1970 into # of days since Jan 1, 1970 +;; convert a date in seconds since 1970 into # of days since Feb 28, 1970 ;; ignoring leap-seconds (define (gnc:date-to-day-fraction caltime) - (/ (/ caltime 3600.0) 24)) + (- (/ (/ caltime 3600.0) 24) 59)) ;; Modify a date (define (moddate op adate delta) diff --git a/src/scm/report/budget-report.scm b/src/scm/report/budget-report.scm index 09877f12ac..8ccdeffcc0 100644 --- a/src/scm/report/budget-report.scm +++ b/src/scm/report/budget-report.scm @@ -38,6 +38,29 @@ ((gnc:budget-year) (gnc:date-to-year-fraction caltime)) (else (gnc:debug "undefined period type in budget!") #f))) +(define (gnc:date-N-delta caltime1 caltime2 type) + (case type + ((gnc:budget-day) + (- (gnc:date-to-day-fraction caltime2) + (gnc:date-to-day-fraction caltime1))) + ((gnc:budget-week) + (- (gnc:date-to-week-fraction caltime2) + (gnc:date-to-week-fraction caltime1))) + ((gnc:budget-month) + (- (gnc:date-to-month-fraction caltime2) + (gnc:date-to-month-fraction caltime1))) + ((gnc:budget-year) (gnc:date-year-delta caltime1 caltime2)) + (else (gnc:debug "undefined period type in budget!") #f))) + +;; returns the "day number" of the specified period. +(define (gnc:date-to-N-remainder caltime type) + (case type + ((gnc:budget-day) 0) + ((gnc:budget-week) (gnc:date-get-week-day (localtime caltime))) + ((gnc:budget-month) (gnc:date-get-month-day (localtime caltime))) + ((gnc:budget-year) (gnc:date-get-year-day (localtime caltime))) + (else (gnc:debug "undefined period type in budget!") #f))) + ;; describe a time type (define (gnc:date-describe-type type) (case type @@ -60,20 +83,23 @@ (define budget-entry-structure (make-record-type "budget-entry-structure" - '(description amount accounts period period-type budget-type))) + '(description amount accounts period period-type budget-type + trigger-day))) -(define (make-budget-entry desc amt acct per ptype budget-type) +(define (make-budget-entry desc amt acct per ptype budget-type trig-day) ((record-constructor budget-entry-structure) - desc amt acct per ptype budget-type)) + desc amt acct per ptype budget-type trig-day)) (define gnc:budget-entries (list (make-budget-entry "lunch" 8 '("Food:Lunch") 1 - 'gnc:budget-day 'gnc:budget-recurring) + 'gnc:budget-day 'gnc:budget-recurring 0) (make-budget-entry "junk food" 0.50 '("Food:Junk") 1 - 'gnc:budget-day 'gnc:budget-recurring) + 'gnc:budget-day 'gnc:budget-recurring 0) (make-budget-entry "car repairs" 2500 '("Car:Repairs") 5 - 'gnc:budget-year 'gnc:budget-contingency))) + 'gnc:budget-year 'gnc:budget-contingency 0) + (make-budget-entry "rent" 312.50 '("Household:Rent") 1 + 'gnc:budget-month 'gnc:budget-trigger 15))) (define (budget-entry-get-description budget-entry) ((record-accessor budget-entry-structure 'description) budget-entry)) @@ -90,6 +116,9 @@ (define (budget-entry-get-period-type budget-entry) ((record-accessor budget-entry-structure 'period-type) budget-entry)) +(define (budget-entry-get-trigger-day budget-entry) + ((record-accessor budget-entry-structure 'trigger-day) budget-entry)) + (define (budget-description-html-proc) (lambda (budget-line) (html-generic-cell #f #f #f @@ -120,6 +149,10 @@ (gnc:date-describe-type (budget-entry-get-period-type (budget-line-get-entry budget-line)))))) +(define (budget-trigger-day-html-proc) + (lambda (budget-line) + (html-number-cell + #f #f "%i" (budget-entry-get-trigger-day (budget-line-get-entry budget-line))))) ;; budget report: a vector with indexes corresponding to the budget ;; 0 - actual: the amount spend / recieved @@ -135,11 +168,11 @@ (make-record-type "budget-report-structure" '(actual budgeted num-periods minimum-expected maximum-expected - time-remaining))) + time-remaining num-triggers))) (define (make-empty-budget-report) ((record-constructor budget-report-structure) - 0 0 0 0 0 0)) + 0 0 0 0 0 0 0)) (define (budget-report-get-actual brep) ((record-accessor budget-report-structure 'actual) brep)) @@ -159,6 +192,9 @@ (define (budget-report-get-time-remaining brep) ((record-accessor budget-report-structure 'time-remaining) brep)) +(define (budget-report-get-num-triggers brep) + ((record-accessor budget-report-structure 'num-triggers) brep)) + (define (budget-actual-html-proc) (lambda (budget-line) (html-currency-cell #f #f (budget-report-get-actual @@ -171,7 +207,7 @@ (define (budget-num-periods-html-proc) (lambda (budget-line) - (html-number-cell #f #f "%.1f" (budget-report-get-num-periods + (html-number-cell #f #f "%.6f" (budget-report-get-num-periods (budget-line-get-report budget-line))))) (define (budget-minimum-expected-html-proc) @@ -189,6 +225,11 @@ (html-number-cell #f #f "%.1f" (budget-report-get-time-remaining (budget-line-get-report budget-line))))) +(define (budget-num-triggers-html-proc) + (lambda (budget-line) + (html-number-cell #f #f "%.0f" (budget-report-get-num-triggers + (budget-line-get-report budget-line))))) + (define budget-line-structure (make-record-type "budget-line-structure" '(entry report))) @@ -212,13 +253,11 @@ ;; calculate the # of periods on a budget line. ;; dates are in # seconds after 1970 (define (budget-calculate-periods! budget-line begin-date end-date) - (let* ((entry (budget-line-get-entry budget-line)) - (N-type (budget-entry-get-period-type entry)) - (begin-N (gnc:date-to-N-fraction begin-date N-type)) - (end-N (gnc:date-to-N-fraction end-date N-type))) + (let ((entry (budget-line-get-entry budget-line))) ((record-modifier budget-report-structure 'num-periods) (budget-line-get-report budget-line) - (/ (- end-N begin-N) + (/ (gnc:date-N-delta begin-date end-date + (budget-entry-get-period-type entry)) (budget-entry-get-period entry))))) ;; calculate the budgeted value. @@ -234,7 +273,7 @@ (define (budget-calculate-expected! budget-line) (let ((brep (budget-line-get-report budget-line)) (entry (budget-line-get-entry budget-line))) - ; fixme: contingency type budget entries may have a lower minimum + ; fixme: contingency type budget entries may have a lower minimum ((record-modifier budget-report-structure 'minimum-expected) brep (* (budget-entry-get-amount entry) (floor (budget-report-get-num-periods brep)))) @@ -252,6 +291,20 @@ (* (- (ceiling periods) periods) (budget-entry-get-period entry))))) +;; calculate the number of times the trigger day occurs in the budget +;; period +(define (budget-calculate-num-triggers! budget-line begin-date end-date) + (let* ((entry (budget-line-get-entry budget-line)) + (brep (budget-line-get-report budget-line)) + (N-type (budget-entry-get-period-type entry)) + (trigger-day (budget-entry-get-trigger-day entry))) + ((record-modifier budget-report-structure 'num-triggers) brep + (+ -1 + (if (<= (gnc:date-to-N-remainder begin-date N-type) trigger-day) 1 0) + (if (>= (gnc:date-to-N-remainder end-date N-type) trigger-day) 1 0) + (- (floor (gnc:date-to-N-fraction end-date N-type)) + (floor (gnc:date-to-N-fraction begin-date N-type))))))) + ;; given an account name, return the budget line ;; return #f if there is no budget line for that account (define (budget-get-line account-name budget) @@ -369,9 +422,10 @@ (budget-calculate-periods! line begin-date-secs end-date-secs) (budget-calculate-budgeted! line) (budget-calculate-expected! line) - (budget-calculate-time-remaining! line))) + (budget-calculate-time-remaining! line) + (budget-calculate-num-triggers! line begin-date-secs end-date-secs))) budget-list) - + (gnc:debug "c") (let ((report-headers '()) @@ -386,12 +440,14 @@ "Period" "" "Actual" + "Trigger Day" "Budgeted" "Number of Periods" "Lower Limit" "Upper Limit" "Time Remaining" - "")) + "" + "Num Triggers")) (set! report-procs (list budget-description-html-proc budget-amount-html-proc @@ -399,12 +455,14 @@ budget-period-html-proc budget-period-type-html-proc budget-actual-html-proc + budget-trigger-day-html-proc budget-budgeted-html-proc budget-num-periods-html-proc budget-minimum-expected-html-proc budget-maximum-expected-html-proc budget-time-remaining-html-proc - budget-period-type-html-proc))) + budget-period-type-html-proc + budget-num-triggers-html-proc))) ((balancing) (set! report-headers (list "Description" diff --git a/src/ui-callbacks.h b/src/ui-callbacks.h index 9f144d8012..33aaf18d4f 100644 --- a/src/ui-callbacks.h +++ b/src/ui-callbacks.h @@ -32,7 +32,8 @@ typedef enum { GNC_VERIFY_NO, GNC_VERIFY_YES, - GNC_VERIFY_CANCEL + GNC_VERIFY_CANCEL, + GNC_VERIFY_OK } GNCVerifyResult; @@ -43,6 +44,11 @@ gnc_verify_cancel_dialog_parented(gncUIWidget parent, const char *message, GNCVerifyResult default_result); +GNCVerifyResult +gnc_ok_cancel_dialog_parented(gncUIWidget parent, + const char *message, + GNCVerifyResult default_result); + void gnc_warning_dialog_parented(gncUIWidget parent, const char *message); gncBoolean gnc_verify_dialog( const char *message,