Paul Sexton committed a3348f3

Save day, rather than time, of most recent update.
Option to wait for a number of seconds when displaying messages, to prevent
them being hidden by other orgmode messages.
Silence messages when 'org-gamify-block-todo' is called
by 'org-entry-blocked-p'.
Forcibly run 'org-gamify-daily-update' before showing inventory.

Comments (0)

Files changed (2)

 #+STARTUP: showall
+Gamify your org-mode.
+- It makes you think about rewards - things you enjoy - and it makes you
+  quantify how much enjoyment you get from them, compared with how much effort
+  you expend on chores.
+- It makes you take "habits" more seriously - if a habit is a potential source
+  of regular income as long as it is maintained, then you are more likely to
+  maintain it.
+There is an infinity of ideas from MMORPGs, free-to-play mobile games and so
 * Ideas
 ;;;; - Use icons in org-gamify-show-inventory.
 ;;;; - Improve messages and interactive commands.
+;;;; - Message in echo area is immediately overwritten by other messages about
+;;;;   changes in the log for the task. There is no obvious way to get around
+;;;;   this.
 ;;;; Ideas for Later
 ;;;; ---------------
   :type '(alist :key-type symbol :value-type plist))
-(defcustom org-gamify-last-update-time nil
-  "The time when the last daily update scan was performed. This should not be
-edited by the user."
+(defcustom org-gamify-last-update-day nil
+  "The day since the beginning of the epoch when the last daily update scan was performed.
+This should not be edited by the user."
   :group 'org-gamify
-  :type 'list)
+  :type 'number)
+(defcustom org-gamify-message-delay 2.5
+  "Number of seconds to wait while displaying org-gamify messages in the
+echo area."
+  :group 'org-gamify
+  :type 'number)
 (defvar org-gamify-delta-cache (make-hash-table :test 'equal)
 which the task is scheduled; DEADLINE is the number of days past the deadline
 for the task; and CHAIN is the number of days that the chain has been
- The functions will be run in turn until one of them returns non-nil.")
+The functions will be run in turn until one of them returns non-nil. Each
+function must either return nil, or a list of currency deltas which will be
+used as the deltas for the current item.")
 ;;; * Currency definitions
 (defun org-gamify-save-inventory ()
   (message "Saving inventory...")
-  (customize-save-variable 'org-gamify-inventory org-gamify-inventory)
-  (customize-save-variable 'org-gamify-last-update-time
-                           org-gamify-last-update-time))
+  (ignore-errors
+    (customize-save-variable 'org-gamify-inventory org-gamify-inventory)
+    (customize-save-variable 'org-gamify-last-update-day
+                             org-gamify-last-update-day)))
 (add-hook 'kill-emacs-hook 'org-gamify-save-inventory)
 (defun org-gamify-currency-changed-message (currency old-balance new-balance)
   "This function is called after a currency's balance is altered. It displays
 a message in the minibuffer notifying the user that the balance has
+changed. Returns the message as a string."
   (let* ((currency-plist (org-gamify-get-currency-plist currency))
          (cname (plist-get currency-plist :name))
          (balance-function (or (plist-get currency-plist :balance-function)
                 (format "%+d %s!  Balance now %s" (- new-balance old-balance)
                         (or cname currency)
                         (funcall balance-function new-balance))))))
-    (message msg)))
+    (message msg)
+    (sit-for org-gamify-message-delay)
+    msg))
                 (format "Too much %s!\nBalance: %s" cname
                         (funcall balance-function old-balance))))))
-    (prog1
-        (message (propertize msg 'face 'warning))
-      (ding)
-      (sit-for 2))))
+    (message (propertize msg 'face 'warning))
+    (sit-for org-gamify-message-delay)
+    msg))
 (add-hook 'org-blocker-hook 'org-gamify-block-todo)
+;;; `org-blocker-hook' are run when a task's state is being changed, and is
+;;; also run by the function `org-entry-blocked-p' which tests whether a
+;;; hypothetical state change would be blocked. There doesn't seem to be any
+;;; way to tell whether a state change is hypothetical or actual. Hence we
+;;; advise `org-entry-blocked-p' to silence org-gamify messages.
+(defadvice org-entry-blocked-p (around gamify-silence-currency-blocked-message
+                                       () activate)
+  (flet ((org-gamify-currency-blocked-message (&rest args) nil))
+    ad-do-it))
 ;;; * Daily update
 list `org-gamify-daily-update-functions', stopping as soon as one
 of the functions returns non-nil."
   (let* ((now (time-to-days (current-time)))
-         (last-updated (if org-gamify-last-update-time
-                           (time-to-days org-gamify-last-update-time)
-                         (1- now)))
+         (last-updated (or org-gamify-last-update-day
+                           (1- now)))
          (update-cycles-due (- now last-updated)))
+    (message "Org-gamify: running daily update of all active tasks...")
     (when (plusp update-cycles-due)
        (lambda ()
        "/!"                            ; this search matches all non-done tasks
        'agenda 'archive)
-      (setq org-gamify-last-update-time (current-time)))))
+      (setq org-gamify-last-update-day now))))
 (run-at-time "00:00" nil 'org-gamify-daily-update)
 (defun org-gamify-show-inventory ()
   "Display a summary of all currency balances, in a temporary buffer."
+  ;; Forcibly run org-gamify-daily-update
+  (org-gamify-daily-update)
   (let ((buf (get-buffer-create "*Inventory*")))
     (with-current-buffer buf
       (read-only-mode -1)
+      (if (featurep 'rainbow-mode)
+          (rainbow-turn-off))
       (kill-region (point-min) (point-max))
       (dolist (category
                (cons nil (remove nil (remove-duplicates