From 2c0812caf14e674d938d80d05678576c0cefcc83 Mon Sep 17 00:00:00 2001 From: Carsten Dominik Date: Mon, 19 May 2008 14:11:47 +0200 Subject: [PATCH] Implement TODO statistics. This uses the same cookies as Checkbox statistics, [%] and [/] --- ORGWEBPAGE/Changes.org | 34 +++++++++++++++++++++--- doc/org.texi | 37 +++++++++++++++++++++----- lisp/ChangeLog | 2 ++ lisp/org-exp.el | 2 +- lisp/org.el | 60 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 124 insertions(+), 11 deletions(-) diff --git a/ORGWEBPAGE/Changes.org b/ORGWEBPAGE/Changes.org index 614cd53a4..9a388a1eb 100644 --- a/ORGWEBPAGE/Changes.org +++ b/ORGWEBPAGE/Changes.org @@ -9,9 +9,40 @@ #+LINK_HOME: http://orgmode.org * Version 6.04 + :PROPERTIES: + :VISIBILITY: content + :END: ** Details +*** Statistics for TODO entries + +The [/] and [%] cookies have already provided statistics for +checkboxes. Now they do the same also for TODO entries. So if a +headline contains either cookie, changing the TODO state of any +direct child will trigger an update of this cookie. Children +that are neither TODO nor DONE are ignored. + +There have already been requests to automatically switch the +parent headline to DONE when all children are done. I am not +makeing this a default feature, because one needs to make many +decisions about which keyword to use etc. Instead of a complex +customization variable, I am providing a hook that can be used. +This hook will be called each time a TODO statistics cookie is +updated, with the cursor in the corresponding line. Each +function in the hook will receive two arguments, the number of +done entries, and the number of not-done entries. Here is a +example implementation: + +#+begin_src emacs-lisp +(defun org-summary-todo (n-done n-not-done) + "Switch entry to DONE when all subentries are done, to TODO otherwise." + (let (org-log-done org-log-states) ; turn off logging + (org-todo (if (= n-not-done 0) "DONE" "TODO")))) + +(add-hook 'org-after-todo-statistics-hook 'org-summary-todo) +#+end_src + *** iCalendar now defines proper UIDs for entries This is necessary for synchronization services. The UIDs are @@ -36,9 +67,6 @@ but a synchronization program can still figure out from which entry all the different instances originate. * Version 6.03 - :PROPERTIES: - :VISIBILITY: content - :END: ** Overview diff --git a/doc/org.texi b/doc/org.texi index 50b7bc49a..b7a9924de 100644 --- a/doc/org.texi +++ b/doc/org.texi @@ -3171,12 +3171,37 @@ priority): @cindex tasks, breaking down It is often advisable to break down large tasks into smaller, manageable -subtasks. You can do this by creating an outline tree below a TODO -item, with detailed subtasks on the tree@footnote{To keep subtasks out -of the global TODO list, see the -@code{org-agenda-todo-list-sublevels}.}. Another possibility is the use -of checkboxes to identify (a hierarchy of) a large number of subtasks -(@pxref{Checkboxes}). +subtasks. You can do this by creating an outline tree below a TODO item, +with detailed subtasks on the tree@footnote{To keep subtasks out of the +global TODO list, see the @code{org-agenda-todo-list-sublevels}.}. To keep +the overview over the fraction of subtasks that are already completed, insert +either @samp{[/]} or @samp{[%]} anywhere in the headline. These cookies will +be updates each time the todo status of a child changes. For example: + +@example +* Organize Party [33%] +** TODO Call people [1/2] +*** TODO Peter +*** DONE Sarah +** TODO Buy food +** DONE Talk to neighbor +@end example + +If you would like a TODO entry to automatically change to DONE when all +chilrden are done, you can use the following setup: + +@example +(defun org-summary-todo (n-done n-not-done) + "Switch entry to DONE when all subentries are done, to TODO otherwise." + (let (org-log-done org-log-states) ; turn off logging + (org-todo (if (= n-not-done 0) "DONE" "TODO")))) + +(add-hook 'org-after-todo-statistics-hook 'org-summary-todo) +@end example + + +Another possibility is the use of checkboxes to identify (a hierarchy of) a +large number of subtasks (@pxref{Checkboxes}). @node Checkboxes, , Breaking down tasks, TODO Items diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 239097e02..52f97ff4d 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,5 +1,7 @@ 2008-05-19 Carsten Dominik + * org.el (org-update-parent-todo-statistics): New function. + * org-exp.el (org-icalendar-store-UID): New option. (org-icalendar-force-UID): Option removed. (org-print-icalendar-entries): IMplement UIDs. diff --git a/lisp/org-exp.el b/lisp/org-exp.el index ef5fbcadc..58a2bed33 100644 --- a/lisp/org-exp.el +++ b/lisp/org-exp.el @@ -3860,7 +3860,7 @@ END:VEVENT\n" UID: %s %s SUMMARY:%s%s%s -CATEGORIES:%s%s +CATEGORIES:%s SEQUENCE:1 PRIORITY:%d STATUS:%s diff --git a/lisp/org.el b/lisp/org.el index c6c91556e..6d83d10e8 100644 --- a/lisp/org.el +++ b/lisp/org.el @@ -174,6 +174,7 @@ to add the symbol `xyz', and the package must have a call to (const :tag " mouse: Additional mouse support" org-mouse) (const :tag "C annotate-file: Annotate a file with org syntax" org-annotate-file) + (const :tag "C annotation-helper: Call Remeber directly from Browser" org-annotation-helper) (const :tag "C bookmark: Org links to bookmarks" org-bookmark) (const :tag "C depend: TODO dependencies for Org-mode" org-depend) (const :tag "C elisp-symbol: Org links to emacs-lisp symbols" org-elisp-symbol) @@ -1388,6 +1389,13 @@ by a letter in parenthesis, like TODO(t)." (const :tag "By default" t) (const :tag "Only with C-u C-c C-t" prefix))) +(defcustom org-provide-todo-statistics t + "Non-nil means, update todo statistics after insert and toggle. +When this is set, todo statistics is updated in the parent of the current +entry each time a todo state is changed." + :group 'org-todo + :type 'boolean) + (defcustom org-after-todo-state-change-hook nil "Hook which is run after the state of a TODO item was changed. The new state (a string with a TODO keyword, or nil) is available in the @@ -2367,6 +2375,7 @@ If TABLE-TYPE is non-nil, also check for table.el-type tables." org-replace-region-by-html org-export-region-as-html org-export-as-html org-export-icalendar-this-file org-export-icalendar-all-agenda-files + org-table-clean-before-export org-export-icalendar-combine-agenda-files org-export-as-xoxo))) ;; Declare and autoload functions from org-exp.el @@ -4546,7 +4555,9 @@ state (TODO by default). Also with prefix arg, force first state." (not (match-beginning 2)) (member (match-string 2) org-done-keywords)) (insert (car org-todo-keywords-1) " ") - (insert (match-string 2) " ")))) + (insert (match-string 2) " ")) + (when org-provide-todo-statistics + (org-update-parent-todo-statistics)))) (defun org-insert-subheading (arg) "Insert a new subheading and demote it. @@ -8186,6 +8197,8 @@ For calling through lisp, arg is also interpreted in the following way: (org-add-log-setup 'state state 'findpos dolog))) ;; Fixup tag positioning (and org-auto-align-tags (not org-setting-tags) (org-set-tags nil t)) + (when org-provide-todo-statistics + (org-update-parent-todo-statistics)) (run-hooks 'org-after-todo-state-change-hook) (if (and arg (not (member state org-done-keywords))) (setq head (org-get-todo-sequence-head state))) @@ -8205,6 +8218,51 @@ For calling through lisp, arg is also interpreted in the following way: (save-excursion (run-hook-with-args 'org-trigger-hook change-plist))))))) +(defun org-update-parent-todo-statistics () + "Update any statistics cookie in the parent of the current headline." + (interactive) + (let ((box-re "\\(\\(\\[[0-9]*%\\]\\)\\|\\(\\[[0-9]*/[0-9]*\\]\\)\\)") + level (cnt-all 0) (cnt-done 0) is-percent kwd) + (catch 'exit + (save-excursion + (setq level (org-up-heading-safe)) + (unless (and level + (re-search-forward box-re (point-at-eol) t)) + (throw 'exit nil)) + (setq is-percent (match-end 2)) + (save-match-data + (unless (outline-next-heading) (throw 'exit nil)) + (while (looking-at org-todo-line-regexp) + (setq kwd (match-string 2)) + (and kwd (setq cnt-all (1+ cnt-all))) + (and (member kwd org-done-keywords) + (setq cnt-done (1+ cnt-done))) + (condition-case nil + (outline-forward-same-level 1) + (error (end-of-line 1))))) + (replace-match + (if is-percent + (format "[%d%%]" (/ (* 100 cnt-done) (max 1 cnt-all))) + (format "[%d/%d]" cnt-done cnt-all))) + (run-hook-with-args 'org-after-todo-statistics-hook + cnt-done (- cnt-all cnt-done)))))) + +(defvar org-after-todo-statistics-hook nil + "Hook that is called after a TODO statistics cookie has been updated. +Each function is called with two arguments: the number of not-done entries +and the number of done entries. + +For example, the following function, when added to this hook, will switch +an entry to DONE when all children are done, and back to TODO when new +entries are set to a TODO status. Note that this hook is only called +when there is a statistics cookie in the headline! + + (defun org-summary-todo (n-done n-not-done) + \"Switch entry to DONE when all subentries are done, to TODO otherwise.\" + (let (org-log-done org-log-states) ; turn off logging + (org-todo (if (= n-not-done 0) \"DONE\" \"TODO\")))) +") + (defun org-local-logging (value) "Get logging settings from a property VALUE." (let* (words w a)