;; emoney.el --- A home finance package. ;; Copyright (C) 2003 - 2017 Steve Youngs ;; Author: Steve Youngs ;; Maintainer: Steve Youngs ;; Created: <2003-06-04> ;; Keywords: money finance banking cash ;; This file is part of eMoney. ;; Redistribution and use in source and binary forms, with or without ;; modification, are permitted provided that the following conditions ;; are met: ;; ;; 1. Redistributions of source code must retain the above copyright ;; notice, this list of conditions and the following disclaimer. ;; ;; 2. Redistributions in binary form must reproduce the above copyright ;; notice, this list of conditions and the following disclaimer in the ;; documentation and/or other materials provided with the distribution. ;; ;; 3. Neither the name of the author nor the names of any contributors ;; may be used to endorse or promote products derived from this ;; software without specific prior written permission. ;; ;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR ;; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ;; DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE ;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ;; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR ;; BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, ;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE ;; OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN ;; IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ;;; Commentary: ;; ;; eMoney is based on balance.el, originally by Jason Baietto ;; and later maintained by Bob Newell ;; . ;; ;; eMoney tries to give the user a reasonably self-contained finance ;; solution. The target audience is the home user. It probably ;; will never be compatible with any commercial finance packages ;; available. ;; ;; Right now, eMoney is nothing more than a very simple cash book ;; program. Use it to balance your cheque book. See the TODO file ;; for a partial list of possible upcoming features. ;; ;; Installation (from source): ;; ;; tar zxf emoney-x.xx.tar.gz ;; cd emoney-x.xx ;; check the paths in Makefile ;; make ;; make install (you may need to be root for this) ;; ;; Installation (from XEmacs package tarball): ;; ;; cd /usr/local/share/sxemacs/site-packages ;; (/usr/local/lib/xemacs/site-packages for XEmacs) ;; tar zxf /path/to/emoney-x.xx-pkg.tar.gz ;; ;; (Re)start (S)XEmacs and do `M-x emoney RET' ;; ;; You can also invoke `emoney-mode' by simply visiting a file with a ;; `.emy' extension. ;; ;; Please note that I use SXEmacs exclusively, I have no idea whether ;; or not this will run or even byte-compile with GNU/Emacs (it _should_ ;; work fine with XEmacs). Also, I have no desire or intentions of ;; making this package portable between XEmacs and GNU/Emacs. Harsh ;; words? No, not really, I simply don't have the time or resources to ;; invest in the extra work involved. And also there is no way that ;; I'd be able to support GNU/Emacs users because I'm not familiar with ;; that flavour of Emacs. If you want to port it, be my guest, this is ;; an Open Source project. ;; ;; Another note: This package uses correct English spelling wherever ;; possible. For example, "cheque" instead of "check", "summarise" ;; instead of "summarize". ;;; ChangeLog: ;; ;; This is just a place holder so `emoney-commentary' will work ;; properly. See the ChangeLog files in ChangeLog.d and git log for ;; changes. ;;; Code: ;; Drag in what we need. (eval-and-compile (autoload 'calc "calc" nil t) (autoload 'clear-rectangle "rect" nil t) (autoload 'completing-read "minibuf") (autoload 'customize-group "cus-edit") (autoload 'lm-commentary "lisp-mnt") (autoload 'sort-numeric-fields "sort" nil t) (autoload 'untabify "tabify" nil t) (autoload 'with-electric-help "ehelp") (require 'wid-edit)) (eval-when-compile (autoload 'eval-when "cl-macs" nil nil 'macro) (autoload 'calc-quit "calc" nil t) (require 'advice) (defvar calc-was-split) (autoload 'browse-url "browse-url" nil t) (autoload 'regexp-opt "regexp-opt")) ;; Custom. (defgroup emoney nil "Customisations for `emoney-mode'." :prefix "emoney-" :group 'tools) (defcustom emoney-accounts-buffer-width 35 "How wide in columns for the accounts buffer." :type 'integer :group 'emoney) (defcustom emoney-accounts-buffer-height 9 "How high in lines for the accounts buffer." :type 'integer :group 'emoney) (defcustom emoney-header-buffer-height 4 "How high in lines for the header buffer." :type 'integer :group 'emoney) (defcustom emoney-accounts-directory (file-name-as-directory (expand-file-name ".emoney" (user-home-directory))) "*The directory where your eMoney account files are located." :type 'directory :group 'emoney) (defcustom emoney-history-directory (file-name-as-directory (expand-file-name "history" emoney-accounts-directory)) "*Directory containing previous years account files." :type 'directory :group 'emoney) (defcustom emoney-chart-of-accounts (if (file-directory-p emoney-accounts-directory) (directory-files emoney-accounts-directory nil "\\.emy$" nil t) nil) "*A list of eMoney accounts in `emoney-accounts-directory'." :type '(repeat (string :tag "Account Name")) :group 'emoney) (defcustom emoney-default-account (or (car emoney-chart-of-accounts) "default.emy") "*The default eMoney account to use. This is the account that has the focus when you start eMoney." :type 'string :group 'emoney) (defcustom emoney-credit-transaction-types '("autotellcr" "atmcr" "bankcredit" "bcr" "deposit" "dep" "directcr" "dcr" "gencr" "internetcr" "netcr" "phonecr" "phcr") "*List of valid credit transaction types. The default types are: autotellcr -- Automatic Teller Machine deposit atmcr -- short version of 'autotellcr' bankcredit -- Bank initiated credits like interest etc bcr -- short version of 'bankcredit' deposit -- Manual, non-direct deposits dep -- short version of 'deposit' directcr -- Direct credit transactions dcr -- short version of 'directcr' gencr -- General credit for things that don't fit like reversals etc internetcr -- Internet credit transactions netcr -- short version of 'internetcr' phonecr -- Telephone credit transactions phcr -- short version of 'phonecr'" :type '(repeat string) :group 'emoney) (defcustom emoney-debit-transaction-types '("autotelldb" "atmdb" "bankfee" "fee" "bpay" "cc" "directdb" "ddb" "eftpos" "eft" "gendb" "internetdb" "netdb" "paypal" "phonedb" "phdb" "venddb" "withdrawal" "wdl") "*List of valid debit transaction types. The default types are: autotelldb -- Automatic Teller Machine transaction atmdb -- short version of 'autoteller' bankfee -- Bank fees fee -- short version of 'bankfee' bpay -- BillPayments cc -- Credit card directdb -- Direct debit transactions ddb -- short version of 'directdb' eftpos -- Electronic Funds Transfer Point Of Sale eft -- short version of 'eftpos' gendb -- For general debits that don't fit other types like reversals. internetdb -- Internet transactions netdb -- short version of 'internet' paypal -- PayPal transactions phonedb -- Telephone transactions phcr -- short version of 'phone' venddb -- Vending machine transactions withdrawal -- Over the counter withdrawal. wdl -- short version of 'withdrawal'" :type '(repeat string) :group 'emoney) (defcustom emoney-cr-transfer-transaction-type "xfrcr" "*Credit transfer transaction types." :type 'string :group 'emoney) (defcustom emoney-db-transfer-transaction-type "xfrdb" "*Debit transfer transaction type." :type 'string :group 'emoney) (defcustom emoney-date-format "%Y-%m-%d" "*The format that `emoney-mode' uses for dates." :type '(choice (const :tag "yyyy-mm-dd" :value "%Y-%m-%d") (const :tag "yyyy/mm/dd" :value "%Y/%m/%d") (const :tag "mm/dd/yy" :value "%m/%d/%y") (const :tag "mm/dd/yyyy" :value "%m/%d/%Y") (const :tag "mm-dd-yy" :value "%m-%d-%y") (const :tag "mm-dd-yyyy" :value "%m-%d-%Y") (const :tag "dd/mm/yy" :value "%d/%m/%y") (const :tag "dd/mm/yyyy" :value "%d/%m/%Y") (const :tag "dd-mm-yy" :value "%d-%m-%y") (const :tag "dd-mm-yyyy" :value "%d-%m-%Y") (const :tag "yy/mm/dd" :value "%y/%m/%d") (const :tag "yy-mm-dd" :value "%y-%m-%d")) :group 'emoney) (defcustom emoney-uk-cheque-spelling t "*When non-nil, use UK spelling: \"chq\" instead of \"chk\"." :type 'boolean :group 'emoney) (defcustom emoney-save-after-recalculate t "*If non-nil, save the buffer after a recalculate. See `emoney-recalculate-buffer'." :type 'boolean :group 'emoney) (defcustom emoney-recalculate-on-quit nil "*If non-nil, recalculate each eMoney account buffer when quitting eMoney." :type 'boolean :group 'emoney) (defcustom emoney-use-new-frame nil "*If non-nil, eMoney will start in a new frame." :type 'boolean :group 'emoney) (defcustom emoney-bank-url "unset" "The URL of your bank's web page." :type 'string :group 'emoney) ;; Hooks (defgroup emoney-hooks nil "Various hooks for eMoney." :prefix "emoney-" :group 'emoney) (defcustom emoney-mode-hooks nil "*Hooks run after `emoney-mode' is entered." :type 'hook :group 'emoney-hooks) (defcustom emoney-switch-account-hook nil "*Hooks run after switching accounts." :type 'hook :group 'emoney-hooks) (defcustom emoney-setup-accounts-buffer-hook nil "*Hooks run after setting up the accounts buffer." :type 'hook :group 'emoney-hooks) (defcustom emoney-setup-header-buffer-hook nil "*Hooks run after setting up the header buffer." :type 'hook :group 'emoney-hooks) (defcustom emoney-setup-control-buffer-hook nil "*Hooks run after setting up the control buffer." :type 'hook :group 'emoney-hooks) (defcustom emoney-transaction-hook nil "*Hooks run after appending a new transaction. These hooks are run after any new transaction, including cheque and transfers. If you want to do additional things with cheque or transfer transactions, see `emoney-transaction-cheque-hook' & `emoney-transaction-transfer-hook'." :type 'hook :group 'emoney-hooks) (defcustom emoney-transaction-cheque-hook nil "*Hooks run after appending a cheque transaction." :type 'hook :group 'emoney-hooks) (defcustom emoney-transaction-transfer-hook nil "*Hooks run after appending a transfer transaction." :type 'hook :group 'emoney-hooks) (defcustom emoney-recalculate-before-hook nil "*Hooks run just prior to recalculating an eMoney buffer. See `emoney-recalculate-after-hook' for doing things after recalculating." :type 'hook :group 'emoney-hooks) (defcustom emoney-recalculate-after-hook nil "*Hooks run just after recalculating an eMoney buffer. See `emoney-recalculate-before-hook' for doing things before recalculating." :type 'hook :group 'emoney-hooks) (defcustom emoney-summarise-cheques-hook nil "*Hooks run after doing `emoney-summarise-cheques'." :type 'hook :group 'emoney-hooks) (defcustom emoney-new-account-hook nil "*Hooks run after creating a new account." :type 'hook :group 'emoney-hooks) (defcustom emoney-quit-before-hook nil "*Hooks run just prior to eMoney exiting." :type 'hook :group 'emoney-hooks) (defcustom emoney-quit-after-hook nil "*Hooks run as the last thing when eMoney exits." :type 'hook :group 'emoney-hooks) ;; Faces (defgroup emoney-faces nil "eMoney faces." :prefix "emoney-" :group 'emoney) (make-face 'emoney-account-name-face) (set-face-parent 'emoney-account-name-face 'font-lock-variable-name-face) (make-face 'emoney-debit-face) (set-face-parent 'emoney-debit-face 'font-lock-warning-face) (make-face 'emoney-credit-face) (set-face-parent 'emoney-credit-face 'font-lock-function-name-face) (make-face 'emoney-date-face) (set-face-parent 'emoney-date-face 'font-lock-keyword-face) (make-face 'emoney-clear-tran-face) (set-face-parent 'emoney-clear-tran-face 'font-lock-string-face) (make-face 'emoney-unclear-tran-face) (set-face-parent 'emoney-unclear-tran-face 'font-lock-comment-face) (make-face 'emoney-header-face) (set-face-parent 'emoney-header-face 'font-lock-comment-face) (defcustom emoney-account-name-face 'emoney-account-name-face "Face for highlighting eMoney account names." :type 'face :group 'emoney-faces) (defcustom emoney-debit-face 'emoney-debit-face "Face for highlighting debit amounts in eMoney." :type 'face :group 'emoney-faces) (defcustom emoney-credit-face 'emoney-credit-face "Face for highlighting credit amounts in eMoney." :type 'face :group 'emoney-faces) (defcustom emoney-date-face 'emoney-date-face "Face for highlighting dates in eMoney." :type 'face :group 'emoney-faces) (defcustom emoney-clear-tran-face 'emoney-clear-tran-face "Face for highlighting cleared transactions in eMoney." :type 'face :group 'emoney-faces) (defcustom emoney-unclear-tran-face 'emoney-unclear-tran-face "Face for highlighting uncleared transactions in eMoney." :type 'face :group 'emoney-faces) (defcustom emoney-header-face 'emoney-header-face "Face for highlighting the column header in eMoney." :type 'face :group 'emoney-faces) ;;; Internal variables (defconst emoney-codename "Finance" "The codename of the current version of eMoney.") (defconst emoney-is-beta t "Non-nil if the current version of eMoney is beta.") (eval-when (load eval) (unless (file-directory-p emoney-accounts-directory) (make-directory-path emoney-accounts-directory))) (require 'emoney-version) ;;;###autoload (defun emoney-version (&optional arg) "*Display the current version information for eMoney. Prefix Argument ARG, print version information at point in the current buffer." (interactive "P") (let ((fmt-string "eMoney: %s, \"%s\"")) (when emoney-is-beta (setq fmt-string (concat fmt-string " [Beta]"))) (if arg (insert (format fmt-string emoney-version emoney-codename)) (message fmt-string emoney-version emoney-codename)))) ;;;###autoload (defun emoney-commentary () "*Display the commentary section of emoney.el." (interactive) (with-electric-help '(lambda () (insert (with-temp-buffer (erase-buffer) (insert (lm-commentary (locate-library "emoney.el"))) (goto-char (point-min)) (while (re-search-forward "^;+ ?" nil t) (replace-match "" nil nil)) (buffer-string (current-buffer))))) "*eMoney Commentary*")) ;;;###autoload (defun emoney-copyright () "*Display the copyright notice for eMoney." (interactive) (with-electric-help '(lambda () (insert (with-temp-buffer (erase-buffer) (insert-file-contents (locate-library "emoney.el")) (goto-char (point-min)) (re-search-forward ";;; Commentary" nil t) (beginning-of-line) (narrow-to-region (point-min) (point)) (while (re-search-backward "^;+ ?" nil t) (replace-match "" nil nil)) (buffer-string (current-buffer))))) "*eMoney Copyright Notice*")) (defvar emoney-frame nil "The frame where eMoney is displayed, if in a new frame.") (defconst emoney-accounts-buffer "*eMoney A/C's*" "The buffer that holds the list of eMoney accounts.") (defconst emoney-control-buffer "*eMoney Control*" "The buffer containing the eMoney control buttons.") (defconst emoney-header-buffer "*eMoney Header*" "The buffer for the eMoney account register header line.") (defvar emoney-current-account-name (file-name-sans-extension emoney-default-account)) (defun emoney-switch-to-account (account) "Switch to account, ACCOUNT." (interactive (list (emoney-completing-read "Switch to A/C: " emoney-chart-of-accounts nil t))) (select-window (get-buffer-window (concat emoney-current-account-name ".emy"))) (switch-to-buffer account) (goto-char (point-max)) (setq emoney-current-account-name (file-name-sans-extension account)) (select-window (get-buffer-window emoney-header-buffer)) (emoney-setup-header-buffer) (switch-to-buffer emoney-header-buffer) (select-window (get-buffer-window (concat emoney-current-account-name ".emy"))) (run-hooks 'emoney-switch-account-hook)) (defun emoney-mouse-switch-to-account (event) "Switch to account under EVENT." (interactive "e") (save-excursion (set-buffer (window-buffer (event-window event))) (let ((switch-acc (extent-string (extent-at-event event)))) (select-window (get-buffer-window (concat emoney-current-account-name ".emy"))) (switch-to-buffer (concat switch-acc ".emy")) (goto-char (point-max)) (setq emoney-current-account-name switch-acc) (select-window (get-buffer-window emoney-header-buffer)) (emoney-setup-header-buffer) (switch-to-buffer emoney-header-buffer))) (select-window (get-buffer-window (concat emoney-current-account-name ".emy"))) (run-hooks 'emoney-switch-account-hook)) (defun emoney-goto-default-account () "Switch to `emoney-default-account'." (interactive) (emoney-switch-to-account emoney-default-account)) ;:*======================= ;:* Walk accounts (defun emoney-walk-accounts (direction) "Move to account in direction, DIRECTION." (let ((dl-acc-list (mapfam #'identity emoney-chart-of-accounts :result-type 'dllist))) ;; Line up the dllist's car with the current account (while (not (equal (dllist-car dl-acc-list) (concat emoney-current-account-name ".emy"))) (dllist-rrotate dl-acc-list)) (cond ((eq direction 'next) (progn (dllist-lrotate dl-acc-list) (emoney-switch-to-account (dllist-car dl-acc-list)))) ((eq direction 'previous) (progn (dllist-rrotate dl-acc-list) (emoney-switch-to-account (dllist-car dl-acc-list)))) (t (error 'invalid-argument))))) (defun emoney-walk-accounts-next () "Switch to the 'next' account in the chart of accounts." (interactive) (emoney-walk-accounts 'next)) (defun emoney-walk-accounts-previous () "Switch to the 'previous' account in the chart of accounts." (interactive) (emoney-walk-accounts 'previous)) (defconst emoney-accounts-buffer-map (let* ((map (make-sparse-keymap 'emoney-accounts-buffer-map))) (define-key map [button2] #'emoney-mouse-switch-to-account) map) "A keymap for the extents in eMoney Accounts buffer.") (defconst emoney-largest-balance "999999999.99" "This is only used for formatting purposes.") (defun emoney-setup-accounts-buffer () "Set up the eMoney \"Accounts\" buffer." (let ((buf emoney-accounts-buffer) (accounts emoney-chart-of-accounts) help-msg cbal) (save-excursion (when (buffer-live-p (get-buffer-create buf)) (kill-buffer buf)) (set-buffer (get-buffer-create buf)) (while accounts (setq help-msg (concat "Switch to A/C: " (file-name-sans-extension (car accounts)))) (insert (file-name-sans-extension (car accounts))) (set-extent-properties (make-extent (point-at-bol) (point)) `(face emoney-account-name-face mouse-face highlight help-echo ,help-msg balloon-help ,help-msg keymap ,emoney-accounts-buffer-map)) (with-current-buffer (car accounts) (goto-char (point-max)) (re-search-backward "\\s-[\\+-=]\\s-" nil t) (setq cbal (nth 2 (emoney-parse-transaction-data (buffer-substring (point) (point-at-eol)))))) (move-to-column 18 'force) (if cbal (insert-face (format (concat "%" (number-to-string (- (length emoney-largest-balance) (length (format "%.2f" cbal)))) "s$%.2f") "" cbal) (if (< cbal 0) 'emoney-debit-face 'emoney-credit-face)) (insert " Needs Recalc")) (insert "\n") (setq cbal nil) (setq accounts (cdr accounts))) (set-specifier horizontal-scrollbar-visible-p nil (current-buffer)) (set-specifier has-modeline-p nil (current-buffer)) (goto-char (point-min)) (run-hooks 'emoney-setup-accounts-buffer-hook)))) (defconst emoney-header "Date Type C Description Amount Balance ===============================================================================\n" "The header inserted at the top of a `emoney-mode' buffer.") (defun emoney-setup-header-buffer () "Set up the eMoney \"Account Register Header\" buffer." (let ((buf emoney-header-buffer)) (save-excursion (when (buffer-live-p (get-buffer-create buf)) (kill-buffer buf)) (set-buffer (get-buffer-create buf)) (center-line (insert-face (upcase emoney-current-account-name) 'emoney-header-face)) (insert "\n\n") (insert-face emoney-header 'emoney-header-face) (set-specifier horizontal-scrollbar-visible-p nil (current-buffer)) (set-specifier vertical-scrollbar-visible-p nil (current-buffer)) (set-specifier has-modeline-p nil (current-buffer)) (goto-char (point-min)) (run-hooks 'emoney-setup-header-buffer-hook)))) ;;;###autoload (defun emoney-customise () "*Convenience function to customise eMoney." (interactive) (customize-group 'emoney)) ;; Because lots of people in the world can't spell. ;;;###autoload (defalias 'emoney-customize 'emoney-customise) (defvar emoney-transaction-types '("init" "") "A list of valid transaction types. It is a combination of `emoney-credit-transaction-types', `emoney-debit-transaction-types', and a blank type.") (defconst emoney-cheque-type (if emoney-uk-cheque-spelling "chq[ \t]+\\([0-9]+\\)" "chk[ \t]+\\([0-9]+\\)") "Type field for cheque transactions. This is defined separately from the other transaction types because it is used by functions that perform special operations on cheque. transactions.") (defconst emoney-date-column 0 "Column where transaction date begins.") (defconst emoney-type-column 11 "Column where transaction type begins.") (defconst emoney-clear-column 22 "Column where status appears.") (defconst emoney-description-column 24 "Column where transaction description begins.") (defconst emoney-sign-column 52 "Column where transaction sign begins.") (defconst emoney-amount-column 54 "Column where transaction amount begins.") (defconst emoney-current-balance-column 65 "Column where current emoney begins.") (defconst emoney-tab-stop-list (list emoney-date-column emoney-type-column emoney-clear-column emoney-description-column emoney-sign-column emoney-amount-column emoney-current-balance-column) "List of tab stops that define the start of all transaction fields.") (defvar emoney-mode-map (let ((map (make-sparse-keymap 'emoney-mode-map))) (define-key map [(control c) (control b)] #'emoney-backward-field) (define-key map [(control c) (control c)] #'emoney-recalculate-buffer) (define-key map [(control c) (control d)] #'emoney-clear-current-field) (define-key map [(control c) (control f)] #'emoney-forward-field) (define-key map [(control c) (control n)] #'emoney-append-next-cheque) (define-key map [(control c) (control r)] #'emoney-summarise-cheques-region) (define-key map [(control c) (control s)] #'emoney-summarise-cheques-buffer) (define-key map [(control c) (control t)] #'emoney-append-transaction) (define-key map [(control c) (control x)] #'emoney-transfer-funds) (define-key map [tab] #'emoney-forward-field) (define-key map [iso-left-tab] #'emoney-backward-field) (define-key map [(control c) b] #'emoney-go-to-bank) (define-key map [(control c) c] #'emoney-calc) (define-key map [(control c) s] #'emoney-switch-to-account) (define-key map [(control c) d] #'emoney-goto-default-account) (define-key map [(control c) q] #'emoney-quit) (define-key map [(control c) (control q)] #'emoney-recalc-and-exit) (define-key map [(meta n)] #'emoney-walk-accounts-next) (define-key map [(meta p)] #'emoney-walk-accounts-previous) map) "Keymap for emoney buffer.") (defconst emoney-mode-menu '("eMoney" ["New A/C" emoney-new-account t] "---" [(concat "New " (if emoney-uk-cheque-spelling "Cheque " "Check ") "Transaction") emoney-append-next-cheque t] ["New Transaction" emoney-append-transaction t] ["Transfer Funds" emoney-transfer-funds t] "---" ["Next Field" emoney-forward-field t] ["Previous Field" emoney-backward-field t] ["Clear Current Field" emoney-clear-current-field t] "---" [(concat "Summary of " (if emoney-uk-cheque-spelling "cheques " "checks ") "(buffer)") emoney-summarise-cheques-buffer t] [(concat "Summary of " (if emoney-uk-cheque-spelling "cheques " "checks ") "(region)") emoney-summarise-cheques-region t] "---" ["Recalculate Buffer" emoney-recalculate-buffer t] "---" ["Go To The Bank!" emoney-go-to-bank t] ["Calculator" emoney-calc t] "---" ["Recalc All A/C's and Exit" emoney-recalc-and-exit t] ["Exit eMoney" emoney-quit t]) "Menu for `emoney-mode' buffers.") (easy-menu-define emoney-mode-easymenu nil "eMoney" emoney-mode-menu) (defvar emoney-credit-type-keywords (regexp-opt (append emoney-credit-transaction-types (list emoney-cr-transfer-transaction-type))) "eMoney font lock keywords for credit tran types") (defvar emoney-debit-type-keywords (regexp-opt (append emoney-debit-transaction-types (list emoney-db-transfer-transaction-type))) "eMoney font lock keywords for debit tran types") (defvar emoney-font-lock-keywords `(("x\\s-\\(.*\\)[\\+-]\\s-" (1 emoney-clear-tran-face)) ("o\\s-\\(.*\\)[\\+-]\\s-" (1 emoney-unclear-tran-face)) (,emoney-credit-type-keywords . emoney-credit-face) (,emoney-debit-type-keywords . emoney-debit-face) ("\\(^[0-9]+\\(-\\|\\/\\)[0-9]+\\(-\\|\\/\\)[0-9]+\\)" (1 emoney-date-face)) ("-\\s-+\\([0-9]+\\.[0-9][0-9]\\)" (1 emoney-debit-face)) ("-[0-9]+\\.[0-9]+" . emoney-debit-face) ("[^-]\\([0-9]+\\.[0-9]+$\\)" (1 emoney-credit-face)) ("\\+\\s-+\\([0-9]+\\.[0-9][0-9]\\)" (1 emoney-credit-face))) "Font lock keywords for `emoney-mode'.") ;;;###autoload (defun emoney-mode () "Major mode for editing a buffer containing financial transactions. The following bindings provide the main functionality of this mode: \\{emoney-mode-map} Transactions occur on a single line and have the following fields (in order): date The transaction date. See `emoney-date-format'. type This field must either be blank or match one of the expressions defined in `emoney-credit-transaction-types' and `emoney-debit-transaction-types'. clear Status of transaction, 'o' is open, 'x' is cleared. New transactions default to 'o'. description A possibly blank transaction description. sign This field must either be '+', '-' or '='. '+' means credit, '-' means debit, and '=' resets balance. eMoney will usually guess the correct sign to use. amount The transaction amount. balance The balance after this transaction. This field will be computed upon recalculation, you _don't_ need to fill it in. Just do: \\[emoney-recalculate-buffer]. Any line in the buffer that does not begin with a date will be considered a comment and ignored. Among other things, this allows the transaction description to span several lines. Changing any amount and recalculating again will update all visible balances. Transactions may be commented out by putting a semi-colon \(or any other non-numerical character\) at the beginning of the line. Entering `emoney-mode' runs the `emoney-mode-hooks' if any exist." (interactive) (kill-all-local-variables) (setq major-mode 'emoney-mode) (setq mode-name "eMoney") (use-local-map emoney-mode-map) (easy-menu-add emoney-mode-easymenu) (make-local-variable 'tab-stop-list) (setq tab-stop-list emoney-tab-stop-list) (make-local-variable 'indent-tabs-mode) (setq indent-tabs-mode nil) (setq indent-line-function 'emoney-forward-field) (overwrite-mode 1) (run-hooks 'emoney-mode-hooks)) (defun emoney-current-line () "Return the current buffer line at point." (save-excursion (beginning-of-line) (count-lines (point-min) (point)))) (defun emoney-completing-read (prompt table &optional predicate require-match initial-contents history default) "Like `completing-read', but also accepts strings. Arguments PROMPT, TABLE, PREDICATE, REQUIRE-MATCH, INITIAL-CONTENTS, HISTORY, DEFAULT are as per `completing-read'." (completing-read prompt (if (vectorp table) table (mapcar 'list table)) predicate require-match initial-contents history default)) (defun emoney-forward-field () "Move the cursor to the next field on the current line." (interactive) (move-to-tab-stop)) (defun emoney-go-to-bank () "Open your bank's URL with `browse-url'." (interactive) (if (or (equal emoney-bank-url "unset") (not emoney-bank-url)) (message-or-box "Please customise `emoney-bank-url'.") (browse-url emoney-bank-url))) (defun emoney-calc () "Wrapper around `calc' to get around \"window-edges bug\"." (interactive) (add-hook 'calc-end-hook #'(lambda () (setq calc-was-split nil))) (calc)) (defun emoney-last (list) "Return last element in LIST." (cond ((null list) '()) ((null (cdr list)) (car list)) (t (emoney-last (cdr list))))) (defun emoney-find-largest-less-than (list item) "Search a sorted LIST of numbers, return the largest number that is < ITEM." (let ((list-car (car list)) (list-cdr (cdr list)) (last nil)) (while (and list-car (< list-car item)) (setq last list-car) (setq list-car (car list-cdr)) (setq list-cdr (cdr list-cdr))) last)) (defun emoney-find-largest-less-than-equal (list item) "Return cdr of LIST starting @ the largest number that is <= to ITEM." (let ((list-car (car list)) (list-cdr (cdr list)) (last nil)) (while (and list-car (<= list-car item)) (setq last (cons list-car list-cdr)) (setq list-car (car list-cdr)) (setq list-cdr (cdr list-cdr))) last)) (defun emoney-find-field (column) "Return a list of the start and end of the field around COLUMN. End may be nil if column is after the last defined tab stop." (let ((field (emoney-find-largest-less-than-equal emoney-tab-stop-list column))) (if (equal 1 (length field)) (list (car field) nil) (list (nth 0 field) (nth 1 field))))) (defun emoney-backward-field () "Move the cursor to the previous entry field on the current line." (interactive) (let* ((col (current-column)) (prev (emoney-find-largest-less-than emoney-tab-stop-list col))) (if prev (move-to-column prev) (move-to-column (emoney-last emoney-tab-stop-list))))) (defun emoney-clear-current-field () "Fill the field around point with spaces, leave point at start of field." (interactive) (let* ((field (emoney-find-field (current-column))) (line-start (progn (beginning-of-line) (point))) (line-end (progn (end-of-line) (untabify line-start (point)) (point))) (field-start (+ line-start (nth 0 field))) (field-end (if (nth 1 field) (+ line-start (nth 1 field)) line-end))) (clear-rectangle field-start field-end) (goto-char field-start))) (defsubst emoney-build-types-list () "Dynamically build a list of transaction types. This is done dynamically so that the user can change or add to the list of transaction types without having to reload eMoney." ;; Initialise to ("init" ""). (setq emoney-transaction-types '("init" "")) ;; Load the debit and credit transaction types. (setq emoney-transaction-types (append emoney-transaction-types emoney-debit-transaction-types emoney-credit-transaction-types (list emoney-cr-transfer-transaction-type) (list emoney-db-transfer-transaction-type)))) ;; Stolen from Gnus' time-date.el (defun emoney-days-to-time (days) "Convert DAYS into a time value." (let* ((seconds (* 1.0 days 60 60 24)) (rest (expt 2 16)) (ms (condition-case nil (floor (/ seconds rest)) (range-error (expt 2 16))))) (list ms (condition-case nil (round (- seconds (* ms rest))) (range-error (expt 2 16)))))) ;; Stolen from Gnus' time-date.el (defun emoney-time-add (t1 t2) "Add two time values. One should represent a time difference." (let ((high (car t1)) (low (if (consp (cdr t1)) (nth 1 t1) (cdr t1))) (micro (if (numberp (car-safe (cdr-safe (cdr t1)))) (nth 2 t1) 0)) (high2 (car t2)) (low2 (if (consp (cdr t2)) (nth 1 t2) (cdr t2))) (micro2 (if (numberp (car-safe (cdr-safe (cdr t2)))) (nth 2 t2) 0))) ;; Add (setq micro (+ micro micro2)) (setq low (+ low low2)) (setq high (+ high high2)) ;; Normalize ;; `/' rounds towards zero while `mod' returns a positive number, ;; so we can't rely on (= a (+ (* 100 (/ a 100)) (mod a 100))). (setq low (+ low (/ micro 1000000) (if (< micro 0) -1 0))) (setq micro (mod micro 1000000)) (setq high (+ high (/ low 65536) (if (< low 0) -1 0))) (setq low (logand low 65535)) (list high low micro))) (defun emoney-append-transaction (&optional trans-type description amount) "Add a transaction to the end of current buffer using today's date." (interactive) (goto-char (point-max)) (if (not (equal 0 (current-column))) (newline)) (let* ((date-variance (read-number "Date (RET for current; -DAYS past; DAYS future): " 'integers-only "0")) (tran-date (format-time-string emoney-date-format (emoney-time-add (current-time) (emoney-days-to-time date-variance))))) (insert tran-date)) (move-to-tab-stop) (emoney-build-types-list) (let* ((type (or trans-type (emoney-completing-read "Transaction type: " emoney-transaction-types)))) (insert type) (move-to-tab-stop) (insert "o") (move-to-tab-stop) (let ((before-descript (point)) (start-column (current-column)) (fill-column (- emoney-sign-column emoney-description-column 1))) (save-excursion (insert (or description (read-string "Description: "))) (save-restriction (narrow-to-region before-descript (point)) (goto-char before-descript) (while (progn (move-to-column fill-column) (not (eobp))) (search-forward " " nil t) (insert "\n")) (fill-paragraph 1) (goto-char before-descript) (forward-line 1) (while (progn (beginning-of-line) (not (eobp))) (indent-to-column start-column) (forward-line 1))))) (move-to-tab-stop) (cond ((or (member type emoney-credit-transaction-types) (string= type emoney-cr-transfer-transaction-type)) (insert "+")) ((or (member type emoney-debit-transaction-types) (string= type emoney-db-transfer-transaction-type)) (insert "-")) ((string= type "init") (insert "=")) (t (if (y-or-n-p "Is this a credit transaction? ") (insert "+") (if (y-or-n-p "Is this a debit transaction? ") (insert "-") (if (y-or-n-p "Is this an initialising transaction? ") (insert "=") (warn "Couldn't determine +/-/=, leaving blank")))))) (move-to-tab-stop) (insert (if amount (number-to-string amount) (read-string "Amount: "))) (run-hooks 'emoney-transaction-hook))) (defun emoney-append-next-cheque () "Add a cheque transaction to the end of the current buffer using today's date. Inserts the cheque number following the last cheque number written into the transaction type column. Loses if you write cheques out of order." (interactive) (goto-char (point-max)) (if (not (equal 0 (current-column))) (newline)) (insert (format-time-string emoney-date-format)) (move-to-tab-stop) (let (cheque cheque-number noinit) (save-excursion (if (search-backward-regexp emoney-cheque-type 0 t) (progn (setq cheque (buffer-substring (match-beginning 1) (match-end 1))) (setq noinit nil)) (move-to-column emoney-type-column) (if (< (current-column) emoney-type-column) (indent-to-column emoney-type-column)) (if emoney-uk-cheque-spelling (insert "chq 000001") (insert "chk 000001")) (setq noinit t))) (unless noinit (setq cheque-number (1+ (string-to-number cheque))) (move-to-column emoney-type-column) (if (< (current-column) emoney-type-column) (indent-to-column emoney-type-column)) (if emoney-uk-cheque-spelling (insert (format "chq %06d" cheque-number)) (insert (format "chk %06d" cheque-number)))) (move-to-tab-stop) (insert "o") (move-to-tab-stop) (let ((before-descript (point)) (start-column (current-column)) (fill-column (- emoney-sign-column emoney-description-column 1))) (save-excursion (insert (read-string "Payable To: ")) (save-restriction (narrow-to-region before-descript (point)) (goto-char before-descript) (while (progn (move-to-column fill-column) (not (eobp))) (search-forward " " nil t) (insert "\n")) (fill-paragraph 1) (goto-char before-descript) (forward-line 1) (while (progn (beginning-of-line) (not (eobp))) (indent-to-column start-column) (forward-line 1))))) (move-to-tab-stop) (insert "-") (move-to-tab-stop) (insert (read-string "Amount: ")) (run-hooks 'emoney-transaction-hook) (run-hooks 'emoney-transaction-cheque-hook))) (defvar emoney-transfer-account-history nil) (defun emoney-transfer-funds (from to amount) "Transfer funds from one eMoney account to another. Argument FROM is the account to transfer from. Argument To is the account to transfer to. Argument AMOUNT is how much to transfer." (interactive (list (emoney-completing-read "Transfer from: " emoney-chart-of-accounts nil t (concat emoney-current-account-name ".emy") emoney-transfer-account-history) (emoney-completing-read "Transfer to: " emoney-chart-of-accounts nil t nil emoney-transfer-account-history) (read-number "Amount: "))) (let ((current-ac (concat emoney-current-account-name ".emy"))) (with-current-buffer from (emoney-append-transaction emoney-db-transfer-transaction-type (concat "T'fer to " (file-name-sans-extension to)) amount)) (with-current-buffer to (emoney-append-transaction emoney-cr-transfer-transaction-type (concat "T'fer from " (file-name-sans-extension from)) amount)) (loop for buf in '(from to) do (emoney-switch-to-account (symbol-value buf)) do (emoney-recalculate-buffer)) (run-hooks 'emoney-transaction-transfer-hook) (emoney-switch-to-account current-ac))) (defsubst emoney-build-type-regexp () "Return a regular expression that will match any valid transaction type. This is done dynamically so users can redefine the valid transactions in their `user-init-file' even after this file has been loaded." (emoney-build-types-list) (let ((types (append emoney-transaction-types (list emoney-cheque-type)))) (concat "^\\(" (mapconcat #'(lambda(x) x) types "\\|") "\\)[ \t]*$"))) (defun emoney-check-transaction-type (line-start) "Check to make sure a valid transaction type has been used. Argument LINE-START is the starting point. Please note that the word \"check\" here means \"verify\" and it has nothing to do with the American spelling of the word \"cheque\"." (let* ((type-regexp (emoney-build-type-regexp)) (type-start (+ line-start emoney-type-column)) (type-end (+ line-start emoney-clear-column)) (type-string (buffer-substring type-start type-end))) (if (string-match type-regexp type-string) nil (error "Line %d, invalid type: %s" (1+ (emoney-current-line)) type-string)))) (defun emoney-find-next-transaction () "Find the next line that is a complete transaction. Return a list of the line start, numeric data start and line end points." (let ((found nil) (line-regexp "^\\([0-90-9]\\)+.*$") line-start line-end data-start) (while (and (not found) (search-forward-regexp line-regexp (point-max) t)) (setq line-start (match-beginning 0)) (setq line-end (progn (end-of-line) (point))) (setq data-start (+ line-start emoney-sign-column)) (if (> line-end data-start) (setq found t))) (if found (list line-start data-start line-end) nil))) (defun emoney-parse-transaction-data (data) "Return a list of floating point numbers from DATA. DATA is a string representing the sign, amount and optionally balance of a transaction. Balance is nil if not present." (let ((data-regexp "\\([-+=]\\)[ \t]*\\([0-9.]+\\)?[ \t]*\\([-]?[0-9.]+\\)?") (balance nil) (reset nil) string sign amount) (string-match data-regexp data) (if (match-beginning 1) (setq sign (substring data (match-beginning 1) (match-end 1))) (error "Line %d, missing sign" (1+ (emoney-current-line)))) (if (equal "=" sign) (progn (setq sign "+") (setq reset t))) (if (match-beginning 2) (progn (setq string (substring data (match-beginning 2) (match-end 2))) (setq amount (string-to-number (concat sign string)))) (error "Line %d, missing amount" (1+ (emoney-current-line)))) (if (match-beginning 3) (progn (setq string (substring data (match-beginning 3) (match-end 3))) (setq balance (string-to-number string)))) (if reset (setq sign "=")) (list sign amount balance))) (defun emoney-same (amount1 amount2) "Compare two dollar amounts, AMOUNT1 AMOUNT2, for equivalence." (let ((string1 (format "%10.2f" amount1)) (string2 (format "%10.2f" amount2))) (equal string1 string2))) (defun emoney-form-transaction-data (sign amount balance) "Given SIGN, AMOUNT and a BALANCE, return a string. The string is suitable for placing in the numeric region of a transaction, based on the defined input columns." (let* ((amount (abs amount)) (width1 (- emoney-amount-column emoney-sign-column)) (width2 (- emoney-current-balance-column emoney-amount-column)) (len (length emoney-largest-balance)) (gap (- width2 len)) (value (concat "%" (number-to-string len) ".2f")) (format-string (concat "%-" (number-to-string width1) "s" value "%" (number-to-string gap) "s" value))) (format format-string sign amount "" balance))) (defun emoney-recalculate (start end) "Recalculate the balances for region START END. The final balance, uncleared total, and the number of balances that changed, and the transaction count are returned in a list." (run-hooks 'emoney-recalculate-before-hook) (let ((current-balance 0) (changes 0) (uncleared 0) (transactions 0) line-points) (save-excursion (save-restriction (narrow-to-region start end) (untabify (point-min) (point-max)) (goto-char (point-min)) (while (setq line-points (emoney-find-next-transaction)) (setq transactions (1+ transactions)) (let* ((line-start (nth 0 line-points)) (data-start (nth 1 line-points)) (data-end (nth 2 line-points)) (clear-flag (buffer-substring (+ line-start emoney-clear-column) (+ 1 line-start emoney-clear-column))) (data-string (buffer-substring data-start data-end)) (data-values (emoney-parse-transaction-data data-string)) (sign (nth 0 data-values)) (amount (nth 1 data-values)) (balance (nth 2 data-values)) (new-balance (if (equal sign "=") amount (+ current-balance amount))) (new-uncleared (if (equal clear-flag "x") uncleared (+ uncleared amount))) (new-string (emoney-form-transaction-data sign amount new-balance))) (emoney-check-transaction-type line-start) (setq current-balance new-balance) (setq uncleared new-uncleared) (if (or (null balance) (not (emoney-same balance new-balance))) (setq changes (1+ changes))) (if (not (equal data-string new-string)) (progn (delete-region data-start data-end) (goto-char data-start) (insert new-string))))) (widen))) (run-hooks 'emoney-recalculate-after-hook) (list current-balance uncleared changes transactions))) (defvar emoney-is-exiting nil "Non-nil when eMoney is in the process of quitting.") (defun emoney-update-acc-buf-bal (account balance) "Update ACCOUNT BALANCE in accounts buffer." (with-current-buffer emoney-accounts-buffer (goto-char (point-min)) (search-forward account) (kill-line) (move-to-column 18 'force) (insert-face (format (concat "%" (number-to-string (- (length emoney-largest-balance) (length (format "%.2f" balance)))) "s$%.2f") "" balance) (if (< balance 0) 'emoney-debit-face 'emoney-credit-face)))) (defun emoney-recalculate-buffer () "Recalculate the current buffer. See `emoney-recalculate'." (interactive) (let* ((result (emoney-recalculate (point-min) (point-max))) (balance (nth 0 result)) (uncleared (nth 1 result)) (changes (nth 2 result)) (total (nth 3 result))) (when emoney-save-after-recalculate (save-buffer (current-buffer))) (emoney-update-acc-buf-bal emoney-current-account-name balance) (if emoney-is-exiting (message (format "book bal %.2f unclrd %.2f bank bal %.2f (%d/%d recalcs)" balance uncleared (- balance uncleared) changes total)) (message-or-box (format "book bal %.2f unclrd %.2f bank bal %.2f (%d/%d recalcs)" balance uncleared (- balance uncleared) changes total))) (if (> changes 0) (end-of-line)))) (defun emoney-recalculate-region (start end) "Recalculate the current region, START END. See `emoney-recalculate'." (interactive "r") (let* ((result (emoney-recalculate start end)) (balance (nth 0 result)) (uncleared (nth 1 result)) (changes (nth 2 result)) (total (nth 3 result))) (message-or-box (format "Region balance %.2f uncleared %.2f (%d/%d recalcs)" balance uncleared changes total)) (if (> changes 0) (end-of-line)))) (defun emoney-summarise-cheques (start end) "Create a buffer that lists only the cheques in the specified region. The region is denoted by START END. The list is sorted on cheque number. Breaks in sequence are denoted by lines containing an asterisk between the cheques where the break occurs. The buffer is also recalculated, thus showing to total of the cheques summarised." (let ((emoney-buffer (current-buffer)) (summary-buffer (get-buffer-create "*cheque summary*")) (cheque-count 0) (sequence-breaks 0) line-points) (save-excursion (save-restriction (set-buffer summary-buffer) (delete-region (point-min) (point-max)) (set-buffer emoney-buffer) (narrow-to-region start end) (goto-char (point-min)) (while (setq line-points (emoney-find-next-transaction)) (let* ((line-start (nth 0 line-points)) (line-end (nth 2 line-points)) (type-start (+ line-start emoney-type-column)) (type-end (+ line-start emoney-description-column)) (type-string (buffer-substring type-start type-end))) (if (string-match emoney-cheque-type type-string) (progn (append-to-buffer summary-buffer line-start (1+ line-end)) (setq cheque-count (1+ cheque-count)))))) (widen) (set-buffer summary-buffer) (sort-numeric-fields 3 (point-min) (point-max)) (setq sequence-breaks (emoney-find-sequence-breaks)) (goto-char (point-max)) (insert (format "\n%d cheque%s summarised, %d sequence break%s\n" cheque-count (if (equal 1 cheque-count) "" "s") sequence-breaks (if (equal 1 sequence-breaks) "" "s"))) (set-buffer emoney-buffer))) (with-electric-help '(lambda () (insert (with-temp-buffer (erase-buffer) (insert-buffer summary-buffer) (buffer-string (current-buffer))))) "*cheque summary*")) (run-hooks 'emoney-summarise-cheques-hook)) (defun emoney-find-sequence-breaks () "Find cheque sequence breaks in the current cheque summary buffer. Mark breaks in sequence by inserting a line with an asterisk between the offending cheques. Return the count of sequence breaks found." (let ((last-cheque nil) (sequence-breaks 0)) (goto-char (point-min)) (while (search-forward-regexp (concat "\\([0-9/]+\\)[ \t]+" emoney-cheque-type) (point-max) t) (let* ((cheque-start (match-beginning 2)) (cheque-end (match-end 2)) (cheque-string (buffer-substring cheque-start cheque-end)) (cheque-number (string-to-int cheque-string))) (if (not last-cheque) (setq last-cheque cheque-number) (if (not (equal cheque-number (1+ last-cheque))) (progn (setq sequence-breaks (1+ sequence-breaks)) (beginning-of-line) (open-line 1) (insert "*") (next-line 2))) (setq last-cheque cheque-number)))) sequence-breaks)) (defun emoney-summarise-cheques-buffer () "Summarise the cheque transactions in the current buffer." (interactive) (emoney-summarise-cheques (point-min) (point-max)) (emoney-recalculate (point-min) (point-max))) (defun emoney-summarise-cheques-region (start end) "Summarise the cheque transactions in region START - END." (interactive "r") (emoney-summarise-cheques start end) (emoney-recalculate (point-min) (point-max))) (defun emoney-new-account (new-acc bal) "*Create a new A/C named NEW-ACC with initial balance BAL." (interactive (list (concat (read-string "New A/C Name: ") ".emy") (read-number "Initial Balance: " nil 0))) (find-file-noselect (expand-file-name new-acc emoney-accounts-directory)) (setq emoney-chart-of-accounts (push new-acc emoney-chart-of-accounts)) (select-window (get-buffer-window (concat emoney-current-account-name ".emy"))) (switch-to-buffer new-acc) (setq emoney-current-account-name (file-name-sans-extension new-acc)) (emoney-append-transaction "init" "Opening Balance" bal) (emoney-recalculate-buffer) (emoney-show-buffers) (switch-to-buffer new-acc) (goto-char (point-max)) (run-hooks 'emoney-new-account-hook)) (defun emoney-setup-control-buffer () "Set up the eMoney \"Control\" buffer." (let ((buf emoney-control-buffer)) (save-excursion (when (buffer-live-p (get-buffer-create buf)) (kill-buffer buf)) (set-buffer (get-buffer-create buf)) (widget-create 'push-button :notify (lambda (&rest ignore) (call-interactively 'emoney-new-account)) :help-echo "Create a new eMoney account." " New A/C ") (widget-insert " ") (widget-create 'push-button :notify (lambda (&rest ignore) (save-excursion (set-buffer (concat emoney-current-account-name ".emy")) (emoney-append-transaction))) :help-echo "Add a new transaction\n If you want to add a cheque transaction use \"Add Chq\" instead." "Add Trans") (widget-insert " ") (widget-create 'push-button :notify (lambda (&rest ignore) (save-excursion (set-buffer (concat emoney-current-account-name ".emy")) (emoney-append-next-cheque))) :help-echo (if emoney-uk-cheque-spelling "Add a new cheque transaction." "Add a new check transaction.") (if emoney-uk-cheque-spelling " Add Chq " " Add Chk ")) (widget-insert " ") (widget-create 'push-button :notify (lambda (&rest ignore) (save-excursion (set-buffer (concat emoney-current-account-name ".emy")) (emoney-recalculate-buffer))) :help-echo "Record the last transaction.\n Also recalculates the buffer." "End Trans") (widget-insert "\n\n") (widget-create 'push-button :notify (lambda (&rest ignore) (save-excursion (set-buffer (concat emoney-current-account-name ".emy")) (call-interactively 'emoney-transfer-funds))) :help-echo "Transfer funds between accounts." "Transfer ") (widget-insert " ") (widget-create 'push-button :notify (lambda (&rest ignore) (save-excursion (set-buffer (concat emoney-current-account-name ".emy")) (emoney-summarise-cheques-buffer))) :help-echo (if emoney-uk-cheque-spelling "Display a summary of cheques." "Display a summary of checks.") (if emoney-uk-cheque-spelling " Chq Sum " " Chk Sum ")) (widget-insert " ") (widget-create 'push-button :notify (lambda (&rest ignore) (emoney-calc)) :help-echo "Start the Emacs Calculator.\n So you can count up all of your millions!" " Calc ") (widget-insert " ") (widget-create 'push-button :notify (lambda (&rest ignore) (if emoney-is-beta (message-or-box "eMoney: %s, \"%s\" [Beta]" emoney-version emoney-codename) (message-or-box "eMoney: %s, \"%s\"" emoney-version emoney-codename))) :help-echo "Display eMoney version info." " Version ") (widget-insert "\n\n") (widget-create 'push-button :notify (lambda (&rest ignore) (emoney-go-to-bank)) :help-echo "Open your bank's web site in your browser." " Bank ") (widget-insert " ") (widget-create 'push-button :notify (lambda (&rest ignore) (emoney-quit)) :help-echo "Exit eMoney." " Exit ") (set-specifier horizontal-scrollbar-visible-p nil (current-buffer)) (set-specifier vertical-scrollbar-visible-p nil (current-buffer)) (set-specifier has-modeline-p nil (current-buffer)) (run-hooks 'emoney-setup-control-buffer-hook)))) (defun emoney-show-buffers () "Display all the eMoney buffers." (emoney-setup-accounts-buffer) (emoney-setup-control-buffer) (emoney-setup-header-buffer) (delete-other-windows nil) (switch-to-buffer emoney-accounts-buffer) (split-window nil emoney-accounts-buffer-height) (split-window nil emoney-accounts-buffer-width t) (other-window 1) (switch-to-buffer emoney-control-buffer) (other-window 1) (switch-to-buffer emoney-header-buffer) (split-window nil emoney-header-buffer-height) (other-window 1)) (defun emoney-quit () "*Exit from eMoney, optionally recalculating all accounts first. To have all of your accounts recalculated before eMoney exits set `emoney-recalculate-on-quit' to `t' If `emoney-save-after-recalculate' is also `t' the account buffers will be saved before eMoney exits." (interactive) (run-hooks 'emoney-quit-before-hook) (let ((accounts emoney-chart-of-accounts)) (setq emoney-is-exiting t) (dolist (buf accounts) (when emoney-recalculate-on-quit (set-buffer buf) (emoney-recalculate-buffer)) (kill-buffer buf)) (kill-buffer emoney-accounts-buffer) (kill-buffer emoney-control-buffer) (kill-buffer emoney-header-buffer) (run-hooks 'emoney-quit-after-hook) (when (and emoney-use-new-frame (frame-live-p emoney-frame)) (delete-frame emoney-frame)) (setq emoney-frame nil) (unless emoney-use-new-frame (jump-to-register ?\$)) (setq emoney-is-exiting nil))) (defun emoney-recalc-and-exit () "*Exit form eMoney, recalculating all accounts first." (interactive) (let ((old-recalc-val emoney-recalculate-on-quit)) (setq emoney-recalculate-on-quit t) (emoney-quit) (setq emoney-recalculate-on-quit old-recalc-val))) ;;;###autoload (defun emoney () "*Start a new eMoney session." (interactive) (unless emoney-use-new-frame (window-configuration-to-register ?\$)) (unless (frame-live-p emoney-frame) (setq emoney-frame (if emoney-use-new-frame ;; FIXME: make the frame props customisable (make-frame '((name . "eMoney") (height . 40) (width . 80))) (selected-frame)))) (select-frame emoney-frame) (let ((accounts emoney-chart-of-accounts) (dir emoney-accounts-directory)) (while accounts (find-file (expand-file-name (car accounts) dir)) (unless (eq major-mode 'emoney-mode) (emoney-mode)) (setq accounts (cdr accounts))) (setq emoney-current-account-name (file-name-sans-extension emoney-default-account)) (emoney-show-buffers) (switch-to-buffer emoney-default-account) (goto-char (point-max))) (focus-frame emoney-frame)) ;; Work around a problem with Emacs calc. If you start calc in a ;; frame with multiple buffers visible when calc exits it doesn't ;; return point to the place it was when you called calc. These ;; advices overcome that. (defadvice calc (before em-calc-win-save first activate) "Before starting calc, save the window config. This is so we can restore the window config when calc exits because calc doesn't DTRT in this regard by itself." (push-window-configuration)) (defadvice calc-quit (after em-calc-win-restore last activate) "Restore the \"pre calc\" window config on calc exit." (pop-window-configuration)) ;;;###autoload(add-to-list 'auto-mode-alist '("\\.emy$" . emoney-mode)) (provide 'emoney) ;;; emoney.el ends here