1 ;; emoney.el --- A home finance package.
3 ;; Copyright (C) 2003 - 2017 Steve Youngs
5 ;; Author: Steve Youngs <steve@sxemacs.org>
6 ;; Maintainer: Steve Youngs <steve@sxemacs.org>
7 ;; Created: <2003-06-04>
8 ;; Keywords: money finance banking cash
10 ;; This file is part of eMoney.
12 ;; Redistribution and use in source and binary forms, with or without
13 ;; modification, are permitted provided that the following conditions
16 ;; 1. Redistributions of source code must retain the above copyright
17 ;; notice, this list of conditions and the following disclaimer.
19 ;; 2. Redistributions in binary form must reproduce the above copyright
20 ;; notice, this list of conditions and the following disclaimer in the
21 ;; documentation and/or other materials provided with the distribution.
23 ;; 3. Neither the name of the author nor the names of any contributors
24 ;; may be used to endorse or promote products derived from this
25 ;; software without specific prior written permission.
27 ;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
28 ;; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
29 ;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30 ;; DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 ;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 ;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 ;; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
34 ;; BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
35 ;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
36 ;; OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
37 ;; IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 ;; eMoney is based on balance.el, originally by Jason Baietto
42 ;; <jason@ssd.csd.harris.com> and later maintained by Bob Newell
43 ;; <bnewell@alum.mit.edu>.
45 ;; eMoney tries to give the user a reasonably self-contained finance
46 ;; solution. The target audience is the home user. It probably
47 ;; will never be compatible with any commercial finance packages
50 ;; Right now, eMoney is nothing more than a very simple cash book
51 ;; program. Use it to balance your cheque book. See the TODO file
52 ;; for a partial list of possible upcoming features.
54 ;; Installation (from source):
56 ;; tar zxf emoney-x.xx.tar.gz
58 ;; check the paths in Makefile
60 ;; make install (you may need to be root for this)
62 ;; Installation (from XEmacs package tarball):
64 ;; cd /usr/local/share/sxemacs/site-packages
65 ;; (/usr/local/lib/xemacs/site-packages for XEmacs)
66 ;; tar zxf /path/to/emoney-x.xx-pkg.tar.gz
68 ;; (Re)start (S)XEmacs and do `M-x emoney RET'
70 ;; You can also invoke `emoney-mode' by simply visiting a file with a
73 ;; Please note that I use SXEmacs exclusively, I have no idea whether
74 ;; or not this will run or even byte-compile with GNU/Emacs (it _should_
75 ;; work fine with XEmacs). Also, I have no desire or intentions of
76 ;; making this package portable between XEmacs and GNU/Emacs. Harsh
77 ;; words? No, not really, I simply don't have the time or resources to
78 ;; invest in the extra work involved. And also there is no way that
79 ;; I'd be able to support GNU/Emacs users because I'm not familiar with
80 ;; that flavour of Emacs. If you want to port it, be my guest, this is
81 ;; an Open Source project.
83 ;; Another note: This package uses correct English spelling wherever
84 ;; possible. For example, "cheque" instead of "check", "summarise"
85 ;; instead of "summarize".
89 ;; This is just a place holder so `emoney-commentary' will work
90 ;; properly. See the ChangeLog files in ChangeLog.d and git log for
94 ;; A little compatibility insurance
95 (globally-declare-fboundp
96 '(mapfam dllist-car dllist-lrotate dllist-rrotate))
98 (defvar emoney-emacs-is-sexy-enough (and (featurep 'dllist)
100 "Non-nil when emacs has some needed extra features.
102 Currently, that is support for dllists and #'mapfam. Don't worry, if
103 you're not sexy enough you can still use eMoney, you just won't be
104 able to \"walk\" through accounts, but you can still switch to any
107 See: `emoney-walk-accounts'.")
109 ;; Drag in what we need.
111 (autoload 'calc "calc" nil t)
112 (autoload 'clear-rectangle "rect" nil t)
113 (autoload 'completing-read "minibuf")
114 (autoload 'customize-group "cus-edit")
115 (autoload 'lm-commentary "lisp-mnt")
116 (autoload 'sort-numeric-fields "sort" nil t)
117 (autoload 'untabify "tabify" nil t)
118 (autoload 'with-electric-help "ehelp")
122 (autoload 'eval-when "cl-macs" nil nil 'macro)
123 (autoload 'calc-quit "calc" nil t)
125 (defvar calc-was-split)
126 (autoload 'browse-url "browse-url" nil t)
127 (autoload 'regexp-opt "regexp-opt"))
131 "Customisations for `emoney-mode'."
135 (defcustom emoney-accounts-buffer-width 35
136 "How wide in columns for the accounts buffer."
140 (defcustom emoney-accounts-buffer-height 9
141 "How high in lines for the accounts buffer."
145 (defcustom emoney-header-buffer-height 4
146 "How high in lines for the header buffer."
150 (defcustom emoney-accounts-directory (file-name-as-directory
151 (expand-file-name ".emoney"
152 (user-home-directory)))
153 "*The directory where your eMoney account files are located."
157 (defcustom emoney-history-directory (file-name-as-directory
158 (expand-file-name "history"
159 emoney-accounts-directory))
160 "*Directory containing previous years account files."
164 (defcustom emoney-chart-of-accounts
165 (if (file-directory-p emoney-accounts-directory)
166 (directory-files emoney-accounts-directory nil "\\.emy$" nil t)
168 "*A list of eMoney accounts in `emoney-accounts-directory'."
170 (string :tag "Account Name"))
173 (defcustom emoney-default-account (or (car emoney-chart-of-accounts)
175 "*The default eMoney account to use.
177 This is the account that has the focus when you start eMoney."
181 (defcustom emoney-credit-transaction-types
182 '("autotellcr" "atmcr" "bankcredit" "bcr" "deposit" "dep" "directcr"
183 "dcr" "gencr" "internetcr" "netcr" "phonecr" "phcr")
184 "*List of valid credit transaction types.
186 The default types are:
188 autotellcr -- Automatic Teller Machine deposit
189 atmcr -- short version of 'autotellcr'
191 bankcredit -- Bank initiated credits like interest etc
192 bcr -- short version of 'bankcredit'
194 deposit -- Manual, non-direct deposits
195 dep -- short version of 'deposit'
197 directcr -- Direct credit transactions
198 dcr -- short version of 'directcr'
200 gencr -- General credit for things that don't fit like
203 internetcr -- Internet credit transactions
204 netcr -- short version of 'internetcr'
206 phonecr -- Telephone credit transactions
207 phcr -- short version of 'phonecr'"
208 :type '(repeat string)
211 (defcustom emoney-debit-transaction-types
212 '("autotelldb" "atmdb" "bankfee" "fee" "bpay" "cc" "directdb" "ddb"
213 "eftpos" "eft" "gendb" "internetdb" "netdb" "paypal" "phonedb"
214 "phdb" "venddb" "withdrawal" "wdl")
215 "*List of valid debit transaction types.
217 The default types are:
219 autotelldb -- Automatic Teller Machine transaction
220 atmdb -- short version of 'autoteller'
223 fee -- short version of 'bankfee'
229 directdb -- Direct debit transactions
230 ddb -- short version of 'directdb'
232 eftpos -- Electronic Funds Transfer Point Of Sale
233 eft -- short version of 'eftpos'
235 gendb -- For general debits that don't fit other types
238 internetdb -- Internet transactions
239 netdb -- short version of 'internet'
241 paypal -- PayPal transactions
243 phonedb -- Telephone transactions
244 phcr -- short version of 'phone'
246 venddb -- Vending machine transactions
248 withdrawal -- Over the counter withdrawal.
249 wdl -- short version of 'withdrawal'"
250 :type '(repeat string)
253 (defcustom emoney-cr-transfer-transaction-type "xfrcr"
254 "*Credit transfer transaction types."
258 (defcustom emoney-db-transfer-transaction-type "xfrdb"
259 "*Debit transfer transaction type."
263 (defcustom emoney-date-format "%Y-%m-%d"
264 "*The format that `emoney-mode' uses for dates."
266 (const :tag "yyyy-mm-dd"
268 (const :tag "yyyy/mm/dd"
270 (const :tag "mm/dd/yy"
272 (const :tag "mm/dd/yyyy"
274 (const :tag "mm-dd-yy"
276 (const :tag "mm-dd-yyyy"
278 (const :tag "dd/mm/yy"
280 (const :tag "dd/mm/yyyy"
282 (const :tag "dd-mm-yy"
284 (const :tag "dd-mm-yyyy"
286 (const :tag "yy/mm/dd"
288 (const :tag "yy-mm-dd"
292 (defcustom emoney-uk-cheque-spelling t
293 "*When non-nil, use UK spelling: \"chq\" instead of \"chk\"."
297 (defcustom emoney-save-after-recalculate t
298 "*If non-nil, save the buffer after a recalculate.
300 See `emoney-recalculate-buffer'."
304 (defcustom emoney-recalculate-on-quit nil
305 "*If non-nil, recalculate each eMoney account buffer when quitting eMoney."
309 (defcustom emoney-use-new-frame nil
310 "*If non-nil, eMoney will start in a new frame."
314 (defcustom emoney-bank-url "unset"
315 "The URL of your bank's web page."
320 (defgroup emoney-hooks nil
321 "Various hooks for eMoney."
325 (defcustom emoney-mode-hooks nil
326 "*Hooks run after `emoney-mode' is entered."
328 :group 'emoney-hooks)
330 (defcustom emoney-switch-account-hook nil
331 "*Hooks run after switching accounts."
333 :group 'emoney-hooks)
335 (defcustom emoney-setup-accounts-buffer-hook nil
336 "*Hooks run after setting up the accounts buffer."
338 :group 'emoney-hooks)
340 (defcustom emoney-setup-header-buffer-hook nil
341 "*Hooks run after setting up the header buffer."
343 :group 'emoney-hooks)
345 (defcustom emoney-setup-control-buffer-hook nil
346 "*Hooks run after setting up the control buffer."
348 :group 'emoney-hooks)
350 (defcustom emoney-transaction-hook nil
351 "*Hooks run after appending a new transaction.
353 These hooks are run after any new transaction, including cheque
354 and transfers. If you want to do additional things with cheque or
355 transfer transactions, see `emoney-transaction-cheque-hook' &
356 `emoney-transaction-transfer-hook'."
358 :group 'emoney-hooks)
360 (defcustom emoney-transaction-cheque-hook nil
361 "*Hooks run after appending a cheque transaction."
363 :group 'emoney-hooks)
365 (defcustom emoney-transaction-transfer-hook nil
366 "*Hooks run after appending a transfer transaction."
368 :group 'emoney-hooks)
370 (defcustom emoney-recalculate-before-hook nil
371 "*Hooks run just prior to recalculating an eMoney buffer.
373 See `emoney-recalculate-after-hook' for doing things after recalculating."
375 :group 'emoney-hooks)
377 (defcustom emoney-recalculate-after-hook nil
378 "*Hooks run just after recalculating an eMoney buffer.
380 See `emoney-recalculate-before-hook' for doing things before recalculating."
382 :group 'emoney-hooks)
384 (defcustom emoney-summarise-cheques-hook nil
385 "*Hooks run after doing `emoney-summarise-cheques'."
387 :group 'emoney-hooks)
389 (defcustom emoney-new-account-hook nil
390 "*Hooks run after creating a new account."
392 :group 'emoney-hooks)
394 (defcustom emoney-quit-before-hook nil
395 "*Hooks run just prior to eMoney exiting."
397 :group 'emoney-hooks)
399 (defcustom emoney-quit-after-hook nil
400 "*Hooks run as the last thing when eMoney exits."
402 :group 'emoney-hooks)
405 (defgroup emoney-faces nil
410 (make-face 'emoney-account-name-face)
411 (set-face-parent 'emoney-account-name-face 'font-lock-variable-name-face)
413 (make-face 'emoney-debit-face)
414 (set-face-parent 'emoney-debit-face 'font-lock-warning-face)
416 (make-face 'emoney-credit-face)
417 (set-face-parent 'emoney-credit-face 'font-lock-function-name-face)
419 (make-face 'emoney-date-face)
420 (set-face-parent 'emoney-date-face 'font-lock-keyword-face)
422 (make-face 'emoney-clear-tran-face)
423 (set-face-parent 'emoney-clear-tran-face 'font-lock-string-face)
425 (make-face 'emoney-unclear-tran-face)
426 (set-face-parent 'emoney-unclear-tran-face 'font-lock-comment-face)
428 (make-face 'emoney-header-face)
429 (set-face-parent 'emoney-header-face 'font-lock-comment-face)
431 (defcustom emoney-account-name-face 'emoney-account-name-face
432 "Face for highlighting eMoney account names."
434 :group 'emoney-faces)
436 (defcustom emoney-debit-face 'emoney-debit-face
437 "Face for highlighting debit amounts in eMoney."
439 :group 'emoney-faces)
441 (defcustom emoney-credit-face 'emoney-credit-face
442 "Face for highlighting credit amounts in eMoney."
444 :group 'emoney-faces)
446 (defcustom emoney-date-face 'emoney-date-face
447 "Face for highlighting dates in eMoney."
449 :group 'emoney-faces)
451 (defcustom emoney-clear-tran-face 'emoney-clear-tran-face
452 "Face for highlighting cleared transactions in eMoney."
454 :group 'emoney-faces)
456 (defcustom emoney-unclear-tran-face 'emoney-unclear-tran-face
457 "Face for highlighting uncleared transactions in eMoney."
459 :group 'emoney-faces)
461 (defcustom emoney-header-face 'emoney-header-face
462 "Face for highlighting the column header in eMoney."
464 :group 'emoney-faces)
466 ;;; Internal variables
467 (defconst emoney-codename "Finance"
468 "The codename of the current version of eMoney.")
470 (defconst emoney-is-beta t
471 "Non-nil if the current version of eMoney is beta.")
473 (eval-when (load eval)
474 (unless (file-directory-p emoney-accounts-directory)
475 (make-directory-path emoney-accounts-directory)))
477 (require 'emoney-version)
480 (defun emoney-version (&optional arg)
481 "*Display the current version information for eMoney.
483 Prefix Argument ARG, print version information at point
484 in the current buffer."
486 (let ((fmt-string "eMoney: %s, \"%s\""))
488 (setq fmt-string (concat fmt-string " [Beta]")))
490 (insert (format fmt-string emoney-version emoney-codename))
491 (message fmt-string emoney-version emoney-codename))))
495 (defun emoney-commentary ()
496 "*Display the commentary section of emoney.el."
503 (insert (lm-commentary (locate-library "emoney.el")))
504 (goto-char (point-min))
505 (while (re-search-forward "^;+ ?" nil t)
506 (replace-match "" nil nil))
507 (buffer-string (current-buffer)))))
508 "*eMoney Commentary*"))
511 (defun emoney-copyright ()
512 "*Display the copyright notice for eMoney."
519 (insert-file-contents (locate-library "emoney.el"))
520 (goto-char (point-min))
521 (re-search-forward ";;; Commentary" nil t)
523 (narrow-to-region (point-min) (point))
524 (while (re-search-backward "^;+ ?" nil t)
525 (replace-match "" nil nil))
526 (buffer-string (current-buffer)))))
527 "*eMoney Copyright Notice*"))
529 (defvar emoney-frame nil
530 "The frame where eMoney is displayed, if in a new frame.")
532 (defconst emoney-accounts-buffer "*eMoney A/C's*"
533 "The buffer that holds the list of eMoney accounts.")
535 (defconst emoney-control-buffer "*eMoney Control*"
536 "The buffer containing the eMoney control buttons.")
538 (defconst emoney-header-buffer "*eMoney Header*"
539 "The buffer for the eMoney account register header line.")
541 (defvar emoney-current-account-name
542 (file-name-sans-extension emoney-default-account))
544 (defun emoney-switch-to-account (account)
545 "Switch to account, ACCOUNT."
547 (list (emoney-completing-read "Switch to A/C: "
548 emoney-chart-of-accounts nil t)))
549 (select-window (get-buffer-window
550 (concat emoney-current-account-name ".emy")))
551 (switch-to-buffer account)
552 (goto-char (point-max))
553 (setq emoney-current-account-name
554 (file-name-sans-extension account))
555 (select-window (get-buffer-window emoney-header-buffer))
556 (emoney-setup-header-buffer)
557 (switch-to-buffer emoney-header-buffer)
558 (select-window (get-buffer-window
559 (concat emoney-current-account-name ".emy")))
560 (run-hooks 'emoney-switch-account-hook))
562 (defun emoney-mouse-switch-to-account (event)
563 "Switch to account under EVENT."
566 (set-buffer (window-buffer (event-window event)))
567 (let ((switch-acc (extent-string (extent-at-event event))))
568 (select-window (get-buffer-window
569 (concat emoney-current-account-name ".emy")))
570 (switch-to-buffer (concat switch-acc ".emy"))
571 (goto-char (point-max))
572 (setq emoney-current-account-name switch-acc)
573 (select-window (get-buffer-window emoney-header-buffer))
574 (emoney-setup-header-buffer)
575 (switch-to-buffer emoney-header-buffer)))
576 (select-window (get-buffer-window
577 (concat emoney-current-account-name ".emy")))
578 (run-hooks 'emoney-switch-account-hook))
580 (defun emoney-goto-default-account ()
581 "Switch to `emoney-default-account'."
583 (emoney-switch-to-account emoney-default-account))
585 ;:*=======================
587 (defun emoney-walk-accounts (direction)
588 "Move to account in direction, DIRECTION."
589 (let ((dl-acc-list (mapfam #'identity
590 emoney-chart-of-accounts
591 :result-type 'dllist)))
592 ;; Line up the dllist's car with the current account
593 (while (not (equal (dllist-car dl-acc-list)
594 (concat emoney-current-account-name ".emy")))
595 (dllist-rrotate dl-acc-list))
597 ((eq direction 'next)
599 (dllist-lrotate dl-acc-list)
600 (emoney-switch-to-account (dllist-car dl-acc-list))))
601 ((eq direction 'previous)
603 (dllist-rrotate dl-acc-list)
604 (emoney-switch-to-account (dllist-car dl-acc-list))))
605 (t (error 'invalid-argument)))))
607 (defun emoney-walk-accounts-next ()
608 "Switch to the 'next' account in the chart of accounts."
610 (and emoney-emacs-is-sexy-enough
611 (emoney-walk-accounts 'next)))
614 (defun emoney-walk-accounts-previous ()
615 "Switch to the 'previous' account in the chart of accounts."
617 (and emoney-emacs-is-sexy-enough
618 (emoney-walk-accounts 'previous)))
620 (defconst emoney-accounts-buffer-map
621 (let* ((map (make-sparse-keymap 'emoney-accounts-buffer-map)))
622 (define-key map [button2] #'emoney-mouse-switch-to-account)
624 "A keymap for the extents in eMoney Accounts buffer.")
626 (defconst emoney-largest-balance "999999999.99"
627 "This is only used for formatting purposes.")
629 (defun emoney-setup-accounts-buffer ()
630 "Set up the eMoney \"Accounts\" buffer."
631 (let ((buf emoney-accounts-buffer)
632 (accounts emoney-chart-of-accounts)
635 (when (buffer-live-p (get-buffer-create buf))
637 (set-buffer (get-buffer-create buf))
639 (setq help-msg (concat "Switch to A/C: "
640 (file-name-sans-extension (car accounts))))
641 (insert (file-name-sans-extension (car accounts)))
642 (set-extent-properties
643 (make-extent (point-at-bol) (point))
644 `(face emoney-account-name-face
647 balloon-help ,help-msg
648 keymap ,emoney-accounts-buffer-map))
649 (with-current-buffer (car accounts)
650 (goto-char (point-max))
651 (re-search-backward "\\s-[\\+-=]\\s-" nil t)
652 (setq cbal (nth 2 (emoney-parse-transaction-data
653 (buffer-substring (point) (point-at-eol))))))
654 (move-to-column 18 'force)
659 (number-to-string (- (length emoney-largest-balance)
660 (length (format "%.2f" cbal))))
664 'emoney-credit-face))
665 (insert " Needs Recalc"))
668 (setq accounts (cdr accounts)))
669 (set-specifier horizontal-scrollbar-visible-p nil (current-buffer))
670 (set-specifier has-modeline-p nil (current-buffer))
671 (goto-char (point-min))
672 (run-hooks 'emoney-setup-accounts-buffer-hook))))
674 (defconst emoney-header
675 "Date Type C Description Amount Balance
676 ===============================================================================\n"
677 "The header inserted at the top of a `emoney-mode' buffer.")
679 (defun emoney-setup-header-buffer ()
680 "Set up the eMoney \"Account Register Header\" buffer."
681 (let ((buf emoney-header-buffer))
683 (when (buffer-live-p (get-buffer-create buf))
685 (set-buffer (get-buffer-create buf))
687 (insert-face (upcase emoney-current-account-name)
688 'emoney-header-face))
690 (insert-face emoney-header 'emoney-header-face)
691 (set-specifier horizontal-scrollbar-visible-p nil (current-buffer))
692 (set-specifier vertical-scrollbar-visible-p nil (current-buffer))
693 (set-specifier has-modeline-p nil (current-buffer))
694 (goto-char (point-min))
695 (run-hooks 'emoney-setup-header-buffer-hook))))
698 (defun emoney-customise ()
699 "*Convenience function to customise eMoney."
701 (customize-group 'emoney))
703 ;; Because lots of people in the world can't spell.
705 (defalias 'emoney-customize 'emoney-customise)
707 (defvar emoney-transaction-types '("init" "")
708 "A list of valid transaction types.
710 It is a combination of `emoney-credit-transaction-types',
711 `emoney-debit-transaction-types', and a blank type.")
713 (defconst emoney-cheque-type
714 (if emoney-uk-cheque-spelling
715 "chq[ \t]+\\([0-9]+\\)"
716 "chk[ \t]+\\([0-9]+\\)")
717 "Type field for cheque transactions.
719 This is defined separately from the other transaction types because
720 it is used by functions that perform special operations on cheque.
723 (defconst emoney-date-column 0
724 "Column where transaction date begins.")
726 (defconst emoney-type-column 11
727 "Column where transaction type begins.")
729 (defconst emoney-clear-column 22
730 "Column where status appears.")
732 (defconst emoney-description-column 24
733 "Column where transaction description begins.")
735 (defconst emoney-sign-column 52
736 "Column where transaction sign begins.")
738 (defconst emoney-amount-column 54
739 "Column where transaction amount begins.")
741 (defconst emoney-current-balance-column 65
742 "Column where current emoney begins.")
744 (defconst emoney-tab-stop-list
749 emoney-description-column
752 emoney-current-balance-column)
753 "List of tab stops that define the start of all transaction fields.")
755 (defvar emoney-mode-map
756 (let ((map (make-sparse-keymap 'emoney-mode-map)))
757 (define-key map [(control c) (control b)] #'emoney-backward-field)
758 (define-key map [(control c) (control c)] #'emoney-recalculate-buffer)
759 (define-key map [(control c) (control d)] #'emoney-clear-current-field)
760 (define-key map [(control c) (control f)] #'emoney-forward-field)
761 (define-key map [(control c) (control n)] #'emoney-append-next-cheque)
762 (define-key map [(control c) (control r)] #'emoney-summarise-cheques-region)
763 (define-key map [(control c) (control s)] #'emoney-summarise-cheques-buffer)
764 (define-key map [(control c) (control t)] #'emoney-append-transaction)
765 (define-key map [(control c) (control x)] #'emoney-transfer-funds)
766 (define-key map [tab] #'emoney-forward-field)
767 (define-key map [iso-left-tab] #'emoney-backward-field)
768 (define-key map [(control c) b] #'emoney-go-to-bank)
769 (define-key map [(control c) c] #'emoney-calc)
770 (define-key map [(control c) s] #'emoney-switch-to-account)
771 (define-key map [(control c) d] #'emoney-goto-default-account)
772 (define-key map [(control c) q] #'emoney-quit)
773 (define-key map [(control c) (control q)] #'emoney-recalc-and-exit)
774 (when emoney-emacs-is-sexy-enough
775 (define-key map [(meta n)] #'emoney-walk-accounts-next)
776 (define-key map [(meta p)] #'emoney-walk-accounts-previous))
778 "Keymap for emoney buffer.")
780 (defconst emoney-mode-menu
782 ["New A/C" emoney-new-account t]
785 (if emoney-uk-cheque-spelling
788 "Transaction") emoney-append-next-cheque t]
789 ["New Transaction" emoney-append-transaction t]
790 ["Transfer Funds" emoney-transfer-funds t]
792 ["Next Field" emoney-forward-field t]
793 ["Previous Field" emoney-backward-field t]
794 ["Clear Current Field" emoney-clear-current-field t]
796 [(concat "Summary of "
797 (if emoney-uk-cheque-spelling
800 "(buffer)") emoney-summarise-cheques-buffer t]
801 [(concat "Summary of "
802 (if emoney-uk-cheque-spelling
805 "(region)") emoney-summarise-cheques-region t]
807 ["Recalculate Buffer" emoney-recalculate-buffer t]
809 ["Go To The Bank!" emoney-go-to-bank t]
810 ["Calculator" emoney-calc t]
812 ["Recalc All A/C's and Exit" emoney-recalc-and-exit t]
813 ["Exit eMoney" emoney-quit t])
814 "Menu for `emoney-mode' buffers.")
817 emoney-mode-easymenu nil "eMoney" emoney-mode-menu)
819 (defvar emoney-credit-type-keywords
820 (regexp-opt (append emoney-credit-transaction-types
821 (list emoney-cr-transfer-transaction-type)))
822 "eMoney font lock keywords for credit tran types")
824 (defvar emoney-debit-type-keywords
825 (regexp-opt (append emoney-debit-transaction-types
826 (list emoney-db-transfer-transaction-type)))
827 "eMoney font lock keywords for debit tran types")
829 (defvar emoney-font-lock-keywords
830 `(("x\\s-\\(.*\\)[\\+-]\\s-" (1 emoney-clear-tran-face))
831 ("o\\s-\\(.*\\)[\\+-]\\s-" (1 emoney-unclear-tran-face))
832 (,emoney-credit-type-keywords . emoney-credit-face)
833 (,emoney-debit-type-keywords . emoney-debit-face)
834 ("\\(^[0-9]+\\(-\\|\\/\\)[0-9]+\\(-\\|\\/\\)[0-9]+\\)"
835 (1 emoney-date-face))
836 ("-\\s-+\\([0-9]+\\.[0-9][0-9]\\)" (1 emoney-debit-face))
837 ("-[0-9]+\\.[0-9]+" . emoney-debit-face)
838 ("[^-]\\([0-9]+\\.[0-9]+$\\)" (1 emoney-credit-face))
839 ("\\+\\s-+\\([0-9]+\\.[0-9][0-9]\\)" (1 emoney-credit-face)))
840 "Font lock keywords for `emoney-mode'.")
844 (defun emoney-mode ()
845 "Major mode for editing a buffer containing financial transactions.
846 The following bindings provide the main functionality of this mode:
850 Transactions occur on a single line and have the following fields (in
853 date The transaction date. See `emoney-date-format'.
854 type This field must either be blank or match one of the
855 expressions defined in `emoney-credit-transaction-types'
856 and `emoney-debit-transaction-types'.
857 clear Status of transaction, 'o' is open, 'x' is cleared. New
858 transactions default to 'o'.
859 description A possibly blank transaction description.
860 sign This field must either be '+', '-' or '='. '+' means
861 credit, '-' means debit, and '=' resets balance. eMoney
862 will usually guess the correct sign to use.
863 amount The transaction amount.
864 balance The balance after this transaction. This field will be
865 computed upon recalculation, you _don't_ need to fill it
866 in. Just do: \\[emoney-recalculate-buffer].
868 Any line in the buffer that does not begin with a date will be
869 considered a comment and ignored. Among other things, this allows
870 the transaction description to span several lines.
872 Changing any amount and recalculating again will update all visible
873 balances. Transactions may be commented out by putting a semi-colon
874 \(or any other non-numerical character\) at the beginning of the line.
876 Entering `emoney-mode' runs the `emoney-mode-hooks' if any exist."
878 (kill-all-local-variables)
879 (setq major-mode 'emoney-mode)
880 (setq mode-name "eMoney")
881 (use-local-map emoney-mode-map)
882 (easy-menu-add emoney-mode-easymenu)
883 (make-local-variable 'tab-stop-list)
884 (setq tab-stop-list emoney-tab-stop-list)
885 (make-local-variable 'indent-tabs-mode)
886 (setq indent-tabs-mode nil)
887 (setq indent-line-function 'emoney-forward-field)
889 (run-hooks 'emoney-mode-hooks))
891 (defun emoney-current-line ()
892 "Return the current buffer line at point."
895 (count-lines (point-min) (point))))
897 (defun emoney-completing-read (prompt table &optional predicate require-match
898 initial-contents history default)
899 "Like `completing-read', but also accepts strings.
901 Arguments PROMPT, TABLE, PREDICATE, REQUIRE-MATCH, INITIAL-CONTENTS,
902 HISTORY, DEFAULT are as per `completing-read'."
907 (mapcar 'list table))
908 predicate require-match initial-contents history default))
910 (defun emoney-forward-field ()
911 "Move the cursor to the next field on the current line."
915 (defun emoney-go-to-bank ()
916 "Open your bank's URL with `browse-url'."
918 (if (or (equal emoney-bank-url "unset")
919 (not emoney-bank-url))
920 (message-or-box "Please customise `emoney-bank-url'.")
921 (browse-url emoney-bank-url)))
923 (defun emoney-calc ()
924 "Wrapper around `calc' to get around \"window-edges bug\"."
926 (add-hook 'calc-end-hook #'(lambda ()
927 (setq calc-was-split nil)))
930 (defun emoney-last (list)
931 "Return last element in LIST."
937 (t (emoney-last (cdr list)))))
939 (defun emoney-find-largest-less-than (list item)
940 "Search a sorted LIST of numbers, return the largest number that is < ITEM."
941 (let ((list-car (car list))
942 (list-cdr (cdr list))
944 (while (and list-car (< list-car item))
946 (setq list-car (car list-cdr))
947 (setq list-cdr (cdr list-cdr)))
950 (defun emoney-find-largest-less-than-equal (list item)
951 "Return cdr of LIST starting @ the largest number that is <= to ITEM."
952 (let ((list-car (car list))
953 (list-cdr (cdr list))
955 (while (and list-car (<= list-car item))
956 (setq last (cons list-car list-cdr))
957 (setq list-car (car list-cdr))
958 (setq list-cdr (cdr list-cdr)))
961 (defun emoney-find-field (column)
962 "Return a list of the start and end of the field around COLUMN.
964 End may be nil if column is after the last defined tab stop."
965 (let ((field (emoney-find-largest-less-than-equal
966 emoney-tab-stop-list column)))
967 (if (equal 1 (length field))
968 (list (car field) nil)
969 (list (nth 0 field) (nth 1 field)))))
971 (defun emoney-backward-field ()
972 "Move the cursor to the previous entry field on the current line."
974 (let* ((col (current-column))
975 (prev (emoney-find-largest-less-than emoney-tab-stop-list col)))
977 (move-to-column prev)
978 (move-to-column (emoney-last emoney-tab-stop-list)))))
980 (defun emoney-clear-current-field ()
981 "Fill the field around point with spaces, leave point at start of field."
983 (let* ((field (emoney-find-field (current-column)))
984 (line-start (progn (beginning-of-line) (point)))
987 (untabify line-start (point))
989 (field-start (+ line-start (nth 0 field)))
990 (field-end (if (nth 1 field)
991 (+ line-start (nth 1 field))
993 (clear-rectangle field-start field-end)
994 (goto-char field-start)))
996 (defsubst emoney-build-types-list ()
997 "Dynamically build a list of transaction types.
999 This is done dynamically so that the user can change or add to the
1000 list of transaction types without having to reload eMoney."
1001 ;; Initialise to ("init" "").
1002 (setq emoney-transaction-types '("init" ""))
1003 ;; Load the debit and credit transaction types.
1004 (setq emoney-transaction-types
1005 (append emoney-transaction-types
1006 emoney-debit-transaction-types
1007 emoney-credit-transaction-types
1008 (list emoney-cr-transfer-transaction-type)
1009 (list emoney-db-transfer-transaction-type))))
1011 ;; Stolen from Gnus' time-date.el
1012 (defun emoney-days-to-time (days)
1013 "Convert DAYS into a time value."
1014 (let* ((seconds (* 1.0 days 60 60 24))
1016 (ms (condition-case nil (floor (/ seconds rest))
1017 (range-error (expt 2 16)))))
1018 (list ms (condition-case nil (round (- seconds (* ms rest)))
1019 (range-error (expt 2 16))))))
1021 ;; Stolen from Gnus' time-date.el
1022 (defun emoney-time-add (t1 t2)
1023 "Add two time values. One should represent a time difference."
1024 (let ((high (car t1))
1025 (low (if (consp (cdr t1)) (nth 1 t1) (cdr t1)))
1026 (micro (if (numberp (car-safe (cdr-safe (cdr t1))))
1030 (low2 (if (consp (cdr t2)) (nth 1 t2) (cdr t2)))
1031 (micro2 (if (numberp (car-safe (cdr-safe (cdr t2))))
1035 (setq micro (+ micro micro2))
1036 (setq low (+ low low2))
1037 (setq high (+ high high2))
1040 ;; `/' rounds towards zero while `mod' returns a positive number,
1041 ;; so we can't rely on (= a (+ (* 100 (/ a 100)) (mod a 100))).
1042 (setq low (+ low (/ micro 1000000) (if (< micro 0) -1 0)))
1043 (setq micro (mod micro 1000000))
1044 (setq high (+ high (/ low 65536) (if (< low 0) -1 0)))
1045 (setq low (logand low 65535))
1047 (list high low micro)))
1049 (defun emoney-append-transaction (&optional trans-type description amount)
1050 "Add a transaction to the end of current buffer using today's date."
1052 (goto-char (point-max))
1053 (if (not (equal 0 (current-column)))
1055 (let* ((date-variance
1056 (read-number "Date (RET for current; -DAYS past; DAYS future): "
1057 'integers-only "0"))
1059 (format-time-string emoney-date-format
1062 (emoney-days-to-time date-variance)))))
1065 (emoney-build-types-list)
1066 (let* ((type (or trans-type
1067 (emoney-completing-read "Transaction type: "
1068 emoney-transaction-types))))
1073 (let ((before-descript (point))
1074 (start-column (current-column))
1075 (fill-column (- emoney-sign-column
1076 emoney-description-column
1079 (insert (or description
1080 (read-string "Description: ")))
1082 (narrow-to-region before-descript (point))
1083 (goto-char before-descript)
1084 (while (progn (move-to-column fill-column) (not (eobp)))
1085 (search-forward " " nil t)
1088 (goto-char before-descript)
1090 (while (progn (beginning-of-line) (not (eobp)))
1091 (indent-to-column start-column)
1092 (forward-line 1)))))
1094 (cond ((or (member type emoney-credit-transaction-types)
1095 (string= type emoney-cr-transfer-transaction-type))
1097 ((or (member type emoney-debit-transaction-types)
1098 (string= type emoney-db-transfer-transaction-type))
1100 ((string= type "init")
1102 (t (if (y-or-n-p "Is this a credit transaction? ")
1104 (if (y-or-n-p "Is this a debit transaction? ")
1106 (if (y-or-n-p "Is this an initialising transaction? ")
1108 (warn "Couldn't determine +/-/=, leaving blank"))))))
1111 (number-to-string amount)
1112 (read-string "Amount: ")))
1113 (run-hooks 'emoney-transaction-hook)))
1115 (defun emoney-append-next-cheque ()
1116 "Add a cheque transaction to the end of the current buffer using today's date.
1118 Inserts the cheque number following the last cheque number written into the
1119 transaction type column. Loses if you write cheques out of order."
1121 (goto-char (point-max))
1122 (if (not (equal 0 (current-column)))
1124 (insert (format-time-string emoney-date-format))
1126 (let (cheque cheque-number noinit)
1128 (if (search-backward-regexp emoney-cheque-type 0 t)
1130 (setq cheque (buffer-substring (match-beginning 1) (match-end 1)))
1132 (move-to-column emoney-type-column)
1133 (if (< (current-column) emoney-type-column)
1134 (indent-to-column emoney-type-column))
1135 (if emoney-uk-cheque-spelling
1136 (insert "chq 000001")
1137 (insert "chk 000001"))
1140 (setq cheque-number (1+ (string-to-number cheque)))
1141 (move-to-column emoney-type-column)
1142 (if (< (current-column) emoney-type-column)
1143 (indent-to-column emoney-type-column))
1144 (if emoney-uk-cheque-spelling
1145 (insert (format "chq %06d" cheque-number))
1146 (insert (format "chk %06d" cheque-number))))
1150 (let ((before-descript (point))
1151 (start-column (current-column))
1152 (fill-column (- emoney-sign-column
1153 emoney-description-column
1156 (insert (read-string "Payable To: "))
1158 (narrow-to-region before-descript (point))
1159 (goto-char before-descript)
1160 (while (progn (move-to-column fill-column) (not (eobp)))
1161 (search-forward " " nil t)
1164 (goto-char before-descript)
1166 (while (progn (beginning-of-line) (not (eobp)))
1167 (indent-to-column start-column)
1168 (forward-line 1)))))
1172 (insert (read-string "Amount: "))
1173 (run-hooks 'emoney-transaction-hook)
1174 (run-hooks 'emoney-transaction-cheque-hook)))
1176 (defvar emoney-transfer-account-history nil)
1178 (defun emoney-transfer-funds (from to amount)
1179 "Transfer funds from one eMoney account to another.
1181 Argument FROM is the account to transfer from.
1182 Argument To is the account to transfer to.
1183 Argument AMOUNT is how much to transfer."
1185 (list (emoney-completing-read "Transfer from: "
1186 emoney-chart-of-accounts nil t
1187 (concat emoney-current-account-name
1189 emoney-transfer-account-history)
1190 (emoney-completing-read "Transfer to: "
1191 emoney-chart-of-accounts nil t nil
1192 emoney-transfer-account-history)
1193 (read-number "Amount: ")))
1194 (let ((current-ac (concat emoney-current-account-name ".emy")))
1195 (with-current-buffer from
1196 (emoney-append-transaction
1197 emoney-db-transfer-transaction-type
1198 (concat "T'fer to " (file-name-sans-extension to))
1200 (with-current-buffer to
1201 (emoney-append-transaction
1202 emoney-cr-transfer-transaction-type
1203 (concat "T'fer from " (file-name-sans-extension from))
1205 (loop for buf in '(from to)
1206 do (emoney-switch-to-account (symbol-value buf))
1207 do (emoney-recalculate-buffer))
1208 (run-hooks 'emoney-transaction-transfer-hook)
1209 (emoney-switch-to-account current-ac)))
1211 (defsubst emoney-build-type-regexp ()
1212 "Return a regular expression that will match any valid transaction type.
1214 This is done dynamically so users can redefine the valid transactions in
1215 their `user-init-file' even after this file has been loaded."
1216 (emoney-build-types-list)
1217 (let ((types (append emoney-transaction-types
1218 (list emoney-cheque-type))))
1222 #'(lambda(x) x) types "\\|")
1225 (defun emoney-check-transaction-type (line-start)
1226 "Check to make sure a valid transaction type has been used.
1228 Argument LINE-START is the starting point.
1230 Please note that the word \"check\" here means \"verify\" and it has
1231 nothing to do with the American spelling of the word \"cheque\"."
1232 (let* ((type-regexp (emoney-build-type-regexp))
1233 (type-start (+ line-start emoney-type-column))
1234 (type-end (+ line-start emoney-clear-column))
1235 (type-string (buffer-substring type-start type-end)))
1236 (if (string-match type-regexp type-string)
1238 (error "Line %d, invalid type: %s"
1239 (1+ (emoney-current-line)) type-string))))
1241 (defun emoney-find-next-transaction ()
1242 "Find the next line that is a complete transaction.
1244 Return a list of the line start, numeric data start and line end
1248 "^\\([0-90-9]\\)+.*$")
1254 (search-forward-regexp line-regexp (point-max) t))
1255 (setq line-start (match-beginning 0))
1256 (setq line-end (progn (end-of-line) (point)))
1257 (setq data-start (+ line-start emoney-sign-column))
1258 (if (> line-end data-start)
1261 (list line-start data-start line-end)
1264 (defun emoney-parse-transaction-data (data)
1265 "Return a list of floating point numbers from DATA.
1267 DATA is a string representing the sign, amount and optionally balance of a
1268 transaction. Balance is nil if not present."
1269 (let ((data-regexp "\\([-+=]\\)[ \t]*\\([0-9.]+\\)?[ \t]*\\([-]?[0-9.]+\\)?")
1273 (string-match data-regexp data)
1274 (if (match-beginning 1)
1275 (setq sign (substring data (match-beginning 1) (match-end 1)))
1276 (error "Line %d, missing sign" (1+ (emoney-current-line))))
1277 (if (equal "=" sign)
1281 (if (match-beginning 2)
1283 (setq string (substring data (match-beginning 2) (match-end 2)))
1284 (setq amount (string-to-number (concat sign string))))
1285 (error "Line %d, missing amount" (1+ (emoney-current-line))))
1286 (if (match-beginning 3)
1288 (setq string (substring data (match-beginning 3) (match-end 3)))
1289 (setq balance (string-to-number string))))
1290 (if reset (setq sign "="))
1291 (list sign amount balance)))
1293 (defun emoney-same (amount1 amount2)
1294 "Compare two dollar amounts, AMOUNT1 AMOUNT2, for equivalence."
1295 (let ((string1 (format "%10.2f" amount1))
1296 (string2 (format "%10.2f" amount2)))
1297 (equal string1 string2)))
1299 (defun emoney-form-transaction-data (sign amount balance)
1300 "Given SIGN, AMOUNT and a BALANCE, return a string.
1302 The string is suitable for placing in the numeric region of a
1303 transaction, based on the defined input columns."
1304 (let* ((amount (abs amount))
1305 (width1 (- emoney-amount-column emoney-sign-column))
1306 (width2 (- emoney-current-balance-column emoney-amount-column))
1307 (len (length emoney-largest-balance))
1308 (gap (- width2 len))
1309 (value (concat "%" (number-to-string len) ".2f"))
1310 (format-string (concat "%-" (number-to-string width1)
1312 "%" (number-to-string gap) "s" value)))
1313 (format format-string sign amount "" balance)))
1315 (defun emoney-recalculate (start end)
1316 "Recalculate the balances for region START END.
1318 The final balance, uncleared total, and the number of balances that
1319 changed, and the transaction count are returned in a list."
1320 (run-hooks 'emoney-recalculate-before-hook)
1321 (let ((current-balance 0)
1328 (narrow-to-region start end)
1329 (untabify (point-min) (point-max))
1330 (goto-char (point-min))
1331 (while (setq line-points (emoney-find-next-transaction))
1332 (setq transactions (1+ transactions))
1333 (let* ((line-start (nth 0 line-points))
1334 (data-start (nth 1 line-points))
1335 (data-end (nth 2 line-points))
1336 (clear-flag (buffer-substring
1337 (+ line-start emoney-clear-column)
1338 (+ 1 line-start emoney-clear-column)))
1339 (data-string (buffer-substring data-start data-end))
1340 (data-values (emoney-parse-transaction-data data-string))
1341 (sign (nth 0 data-values))
1342 (amount (nth 1 data-values))
1343 (balance (nth 2 data-values))
1344 (new-balance (if (equal sign "=")
1346 (+ current-balance amount)))
1347 (new-uncleared (if (equal clear-flag "x")
1349 (+ uncleared amount)))
1351 (emoney-form-transaction-data sign amount new-balance)))
1352 (emoney-check-transaction-type line-start)
1353 (setq current-balance new-balance)
1354 (setq uncleared new-uncleared)
1355 (if (or (null balance) (not (emoney-same balance new-balance)))
1356 (setq changes (1+ changes)))
1357 (if (not (equal data-string new-string))
1359 (delete-region data-start data-end)
1360 (goto-char data-start)
1361 (insert new-string)))))
1363 (run-hooks 'emoney-recalculate-after-hook)
1364 (list current-balance uncleared changes transactions)))
1366 (defvar emoney-is-exiting nil
1367 "Non-nil when eMoney is in the process of quitting.")
1369 (defun emoney-update-acc-buf-bal (account balance)
1370 "Update ACCOUNT BALANCE in accounts buffer."
1371 (with-current-buffer emoney-accounts-buffer
1372 (goto-char (point-min))
1373 (search-forward account)
1375 (move-to-column 18 'force)
1378 (number-to-string (- (length emoney-largest-balance)
1379 (length (format "%.2f" balance))))
1380 "s$%.2f") "" balance)
1383 'emoney-credit-face))))
1385 (defun emoney-recalculate-buffer ()
1386 "Recalculate the current buffer.
1388 See `emoney-recalculate'."
1390 (let* ((result (emoney-recalculate (point-min) (point-max)))
1391 (balance (nth 0 result))
1392 (uncleared (nth 1 result))
1393 (changes (nth 2 result))
1394 (total (nth 3 result)))
1395 (when emoney-save-after-recalculate
1396 (save-buffer (current-buffer)))
1397 (emoney-update-acc-buf-bal emoney-current-account-name balance)
1398 (if emoney-is-exiting
1400 (format "book bal %.2f unclrd %.2f bank bal %.2f (%d/%d recalcs)"
1401 balance uncleared (- balance uncleared) changes total))
1403 (format "book bal %.2f unclrd %.2f bank bal %.2f (%d/%d recalcs)"
1404 balance uncleared (- balance uncleared) changes total)))
1408 (defun emoney-recalculate-region (start end)
1409 "Recalculate the current region, START END.
1411 See `emoney-recalculate'."
1413 (let* ((result (emoney-recalculate start end))
1414 (balance (nth 0 result))
1415 (uncleared (nth 1 result))
1416 (changes (nth 2 result))
1417 (total (nth 3 result)))
1418 (message-or-box (format "Region balance %.2f uncleared %.2f (%d/%d recalcs)"
1419 balance uncleared changes total))
1423 (defun emoney-summarise-cheques (start end)
1424 "Create a buffer that lists only the cheques in the specified region.
1426 The region is denoted by START END.
1428 The list is sorted on cheque number. Breaks in sequence are denoted by lines
1429 containing an asterisk between the cheques where the break occurs. The buffer
1430 is also recalculated, thus showing to total of the cheques summarised."
1431 (let ((emoney-buffer (current-buffer))
1432 (summary-buffer (get-buffer-create "*cheque summary*"))
1438 (set-buffer summary-buffer)
1439 (delete-region (point-min) (point-max))
1440 (set-buffer emoney-buffer)
1441 (narrow-to-region start end)
1442 (goto-char (point-min))
1443 (while (setq line-points (emoney-find-next-transaction))
1444 (let* ((line-start (nth 0 line-points))
1445 (line-end (nth 2 line-points))
1446 (type-start (+ line-start emoney-type-column))
1447 (type-end (+ line-start emoney-description-column))
1448 (type-string (buffer-substring type-start type-end)))
1449 (if (string-match emoney-cheque-type type-string)
1452 summary-buffer line-start (1+ line-end))
1453 (setq cheque-count (1+ cheque-count))))))
1455 (set-buffer summary-buffer)
1456 (sort-numeric-fields 3 (point-min) (point-max))
1457 (setq sequence-breaks (emoney-find-sequence-breaks))
1458 (goto-char (point-max))
1459 (insert (format "\n%d cheque%s summarised, %d sequence break%s\n"
1461 (if (equal 1 cheque-count) "" "s")
1463 (if (equal 1 sequence-breaks) "" "s")))
1464 (set-buffer emoney-buffer)))
1470 (insert-buffer summary-buffer)
1471 (buffer-string (current-buffer)))))
1472 "*cheque summary*"))
1473 (run-hooks 'emoney-summarise-cheques-hook))
1475 (defun emoney-find-sequence-breaks ()
1476 "Find cheque sequence breaks in the current cheque summary buffer.
1478 Mark breaks in sequence by inserting a line with an asterisk between
1479 the offending cheques. Return the count of sequence breaks found."
1480 (let ((last-cheque nil)
1481 (sequence-breaks 0))
1482 (goto-char (point-min))
1483 (while (search-forward-regexp
1484 (concat "\\([0-9/]+\\)[ \t]+" emoney-cheque-type)
1486 (let* ((cheque-start (match-beginning 2))
1487 (cheque-end (match-end 2))
1488 (cheque-string (buffer-substring cheque-start cheque-end))
1489 (cheque-number (string-to-int cheque-string)))
1490 (if (not last-cheque)
1491 (setq last-cheque cheque-number)
1492 (if (not (equal cheque-number (1+ last-cheque)))
1494 (setq sequence-breaks (1+ sequence-breaks))
1499 (setq last-cheque cheque-number))))
1502 (defun emoney-summarise-cheques-buffer ()
1503 "Summarise the cheque transactions in the current buffer."
1505 (emoney-summarise-cheques (point-min) (point-max))
1506 (emoney-recalculate (point-min) (point-max)))
1508 (defun emoney-summarise-cheques-region (start end)
1509 "Summarise the cheque transactions in region START - END."
1511 (emoney-summarise-cheques start end)
1512 (emoney-recalculate (point-min) (point-max)))
1514 (defun emoney-new-account (new-acc bal)
1515 "*Create a new A/C named NEW-ACC with initial balance BAL."
1517 (list (concat (read-string "New A/C Name: ") ".emy")
1518 (read-number "Initial Balance: " nil 0)))
1520 (expand-file-name new-acc emoney-accounts-directory))
1521 (setq emoney-chart-of-accounts
1522 (push new-acc emoney-chart-of-accounts))
1523 (select-window (get-buffer-window
1524 (concat emoney-current-account-name ".emy")))
1525 (switch-to-buffer new-acc)
1526 (setq emoney-current-account-name
1527 (file-name-sans-extension new-acc))
1528 (emoney-append-transaction "init" "Opening Balance" bal)
1529 (emoney-recalculate-buffer)
1530 (emoney-show-buffers)
1531 (switch-to-buffer new-acc)
1532 (goto-char (point-max))
1533 (run-hooks 'emoney-new-account-hook))
1535 (defun emoney-setup-control-buffer ()
1536 "Set up the eMoney \"Control\" buffer."
1537 (let ((buf emoney-control-buffer))
1539 (when (buffer-live-p (get-buffer-create buf))
1541 (set-buffer (get-buffer-create buf))
1542 (widget-create 'push-button
1543 :notify (lambda (&rest ignore)
1544 (call-interactively 'emoney-new-account))
1545 :help-echo "Create a new eMoney account."
1548 (widget-create 'push-button
1549 :notify (lambda (&rest ignore)
1552 (concat emoney-current-account-name ".emy"))
1553 (emoney-append-transaction)))
1554 :help-echo "Add a new transaction\n
1555 If you want to add a cheque transaction
1556 use \"Add Chq\" instead."
1559 (widget-create 'push-button
1560 :notify (lambda (&rest ignore)
1563 (concat emoney-current-account-name ".emy"))
1564 (emoney-append-next-cheque)))
1565 :help-echo (if emoney-uk-cheque-spelling
1566 "Add a new cheque transaction."
1567 "Add a new check transaction.")
1568 (if emoney-uk-cheque-spelling
1572 (widget-create 'push-button
1573 :notify (lambda (&rest ignore)
1576 (concat emoney-current-account-name ".emy"))
1577 (emoney-recalculate-buffer)))
1578 :help-echo "Record the last transaction.\n
1579 Also recalculates the buffer."
1581 (widget-insert "\n\n")
1582 (widget-create 'push-button
1583 :notify (lambda (&rest ignore)
1586 (concat emoney-current-account-name ".emy"))
1587 (call-interactively 'emoney-transfer-funds)))
1588 :help-echo "Transfer funds between accounts."
1591 (widget-create 'push-button
1592 :notify (lambda (&rest ignore)
1595 (concat emoney-current-account-name ".emy"))
1596 (emoney-summarise-cheques-buffer)))
1597 :help-echo (if emoney-uk-cheque-spelling
1598 "Display a summary of cheques."
1599 "Display a summary of checks.")
1600 (if emoney-uk-cheque-spelling
1604 (widget-create 'push-button
1605 :notify (lambda (&rest ignore)
1607 :help-echo "Start the Emacs Calculator.\n
1608 So you can count up all
1612 (widget-create 'push-button
1613 :notify (lambda (&rest ignore)
1616 "eMoney: %s, \"%s\" [Beta]"
1617 emoney-version emoney-codename)
1619 "eMoney: %s, \"%s\""
1620 emoney-version emoney-codename)))
1621 :help-echo "Display eMoney version info."
1623 (widget-insert "\n\n")
1624 (widget-create 'push-button
1625 :notify (lambda (&rest ignore)
1626 (emoney-go-to-bank))
1627 :help-echo "Open your bank's web site in your browser."
1630 (widget-create 'push-button
1631 :notify (lambda (&rest ignore)
1633 :help-echo "Exit eMoney."
1635 (set-specifier horizontal-scrollbar-visible-p nil (current-buffer))
1636 (set-specifier vertical-scrollbar-visible-p nil (current-buffer))
1637 (set-specifier has-modeline-p nil (current-buffer))
1638 (run-hooks 'emoney-setup-control-buffer-hook))))
1640 (defun emoney-show-buffers ()
1641 "Display all the eMoney buffers."
1642 (emoney-setup-accounts-buffer)
1643 (emoney-setup-control-buffer)
1644 (emoney-setup-header-buffer)
1645 (delete-other-windows nil)
1646 (switch-to-buffer emoney-accounts-buffer)
1647 (split-window nil emoney-accounts-buffer-height)
1648 (split-window nil emoney-accounts-buffer-width t)
1650 (switch-to-buffer emoney-control-buffer)
1652 (switch-to-buffer emoney-header-buffer)
1653 (split-window nil emoney-header-buffer-height)
1656 (defun emoney-quit ()
1657 "*Exit from eMoney, optionally recalculating all accounts first.
1659 To have all of your accounts recalculated before eMoney exits set
1660 `emoney-recalculate-on-quit' to `t'
1662 If `emoney-save-after-recalculate' is also `t' the account buffers
1663 will be saved before eMoney exits."
1665 (run-hooks 'emoney-quit-before-hook)
1666 (let ((accounts emoney-chart-of-accounts))
1667 (setq emoney-is-exiting t)
1668 (dolist (buf accounts)
1669 (when emoney-recalculate-on-quit
1671 (emoney-recalculate-buffer))
1673 (kill-buffer emoney-accounts-buffer)
1674 (kill-buffer emoney-control-buffer)
1675 (kill-buffer emoney-header-buffer)
1676 (run-hooks 'emoney-quit-after-hook)
1677 (when (and emoney-use-new-frame
1678 (frame-live-p emoney-frame))
1679 (delete-frame emoney-frame))
1680 (setq emoney-frame nil)
1681 (unless emoney-use-new-frame
1682 (jump-to-register ?\$))
1683 (setq emoney-is-exiting nil)))
1685 (defun emoney-recalc-and-exit ()
1686 "*Exit form eMoney, recalculating all accounts first."
1688 (let ((old-recalc-val emoney-recalculate-on-quit))
1689 (setq emoney-recalculate-on-quit t)
1691 (setq emoney-recalculate-on-quit old-recalc-val)))
1695 "*Start a new eMoney session."
1697 (unless emoney-use-new-frame
1698 (window-configuration-to-register ?\$))
1699 (unless (frame-live-p emoney-frame)
1701 (if emoney-use-new-frame
1702 ;; FIXME: make the frame props customisable
1703 (make-frame '((name . "eMoney")
1707 (select-frame emoney-frame)
1708 (let ((accounts emoney-chart-of-accounts)
1709 (dir emoney-accounts-directory))
1711 (find-file (expand-file-name (car accounts) dir))
1712 (unless (eq major-mode 'emoney-mode)
1714 (setq accounts (cdr accounts)))
1715 (setq emoney-current-account-name
1716 (file-name-sans-extension emoney-default-account))
1717 (emoney-show-buffers)
1718 (switch-to-buffer emoney-default-account)
1719 (goto-char (point-max)))
1720 (focus-frame emoney-frame))
1722 ;; Work around a problem with Emacs calc. If you start calc in a
1723 ;; frame with multiple buffers visible when calc exits it doesn't
1724 ;; return point to the place it was when you called calc. These
1725 ;; advices overcome that.
1726 (defadvice calc (before em-calc-win-save first activate)
1727 "Before starting calc, save the window config.
1728 This is so we can restore the window config when calc exits because
1729 calc doesn't DTRT in this regard by itself."
1730 (push-window-configuration))
1732 (defadvice calc-quit (after em-calc-win-restore last activate)
1733 "Restore the \"pre calc\" window config on calc exit."
1734 (pop-window-configuration))
1737 ;;;###autoload(add-to-list 'auto-mode-alist '("\\.emy$" . emoney-mode))
1741 ;;; emoney.el ends here