Browse Source

Implement TODO statistics.

This uses the same cookies as Checkbox statistics, [%] and [/]
Carsten Dominik 17 years ago
parent
commit
2c0812caf1
5 changed files with 124 additions and 11 deletions
  1. 31 3
      ORGWEBPAGE/Changes.org
  2. 31 6
      doc/org.texi
  3. 2 0
      lisp/ChangeLog
  4. 1 1
      lisp/org-exp.el
  5. 59 1
      lisp/org.el

+ 31 - 3
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
 

+ 31 - 6
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

+ 2 - 0
lisp/ChangeLog

@@ -1,5 +1,7 @@
 2008-05-19  Carsten Dominik  <dominik@science.uva.nl>
 
+	* 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.

+ 1 - 1
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

+ 59 - 1
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)