瀏覽代碼

Dependencies: Integrate John Wiegley's TODO dependency code.

See the documentation for details.
Carsten Dominik 16 年之前
父節點
當前提交
49e8ee37a8
共有 7 個文件被更改,包括 246 次插入39 次删除
  1. 37 1
      ORGWEBPAGE/Changes.org
  2. 4 0
      doc/ChangeLog
  3. 47 10
      doc/org.texi
  4. 9 0
      lisp/ChangeLog
  5. 47 0
      lisp/org-agenda.el
  6. 5 0
      lisp/org-faces.el
  7. 97 28
      lisp/org.el

+ 37 - 1
ORGWEBPAGE/Changes.org

@@ -10,8 +10,44 @@
 #+LINK_UP: index.html
 #+LINK_HOME: http://orgmode.org
 
-* Version 6.19
+** Version 6.20
 
+** Details
+*** Support for simple TODO dependencies
+
+John Wiegley's code for enforcing simple TODO dependencies has
+been integrated into Org-mode.  Thanks John!
+
+The structure of Org files (hierarchy and lists) makes it easy to
+define TODO dependencies.  A parent TODO task should not be
+marked DONE until all subtasks (defined as children tasks) are
+marked as DONE.  And sometimes there is a logical sequence to a
+number of (sub)tasks, so that one task cannot be acted upon
+before all siblings above it are done.  If you customize the
+variable =org-enforce-todo-dependencies=, Org will block entries
+from changing state while they have children that are not DONE.
+Furthermore, if an entry has a property =ORDERED=, each of its
+children will be blocked until all earlier siblings are marked
+DONE.  Here is an example:
+
+#+begin_src org
+,* TODO Blocked until (two) is done
+,** DONE one
+,** TODO two
+
+,* Parent
+,  :PROPERTIES:
+,    :ORDERED: t
+,  :END:
+,** TODO a
+,** TODO b, needs to wait for (a)
+,** TODO c, needs to wait for (a) and (b)
+#+end_src
+The variable =org-agenda-dim-blocked-tasks= controls how blocked
+entries should appear in the agenda, where they can be dimmed or
+even made invisible.
+
+** Version 6.19
   :PROPERTIES:
   :VISIBILITY: content
   :END:

+ 4 - 0
doc/ChangeLog

@@ -1,3 +1,7 @@
+2009-01-27  Carsten Dominik  <carsten.dominik@gmail.com>
+
+	* org.texi (TODO dependencies): New section.
+
 2009-01-26  Carsten Dominik  <carsten.dominik@gmail.com>
 
 	* org.texi (Plain lists, TODO basics, Priorities)

+ 47 - 10
doc/org.texi

@@ -181,6 +181,7 @@ Extended use of TODO keywords
 * Fast access to TODO states::  Single letter selection of a state
 * Per-file keywords::           Different files, different requirements
 * Faces for TODO keywords::     Highlighting states
+* TODO dependencies::           When one tasks needs to wait for others
 
 Progress logging
 
@@ -583,7 +584,7 @@ MY PROJECTS    -*- mode: org; -*-
 the file's name is.  See also the variable
 @code{org-insert-mode-line-in-empty-file}.
 
-Many commands in Org work on the region is the region is active.  To make use
+Many commands in Org work on the region if the region is active.  To make use
 of this, you need to have @code{transient-mark-mode} (@code{zmacs-regions} in
 XEmacs) turned on.  In Emacs 23 this is the default, in Emacs 22 you need to
 do this yourself with
@@ -3100,6 +3101,7 @@ TODO items in particular (@pxref{Tags}).
 * Fast access to TODO states::  Single letter selection of a state
 * Per-file keywords::           Different files, different requirements
 * Faces for TODO keywords::     Highlighting states
+* TODO dependencies::           When one tasks needs to wait for others
 @end menu
 
 @node Workflow states, TODO types, TODO extensions, TODO extensions
@@ -3282,7 +3284,7 @@ Org mode is activated after visiting a file.  @kbd{C-c C-c} with the
 cursor in a line starting with @samp{#+} is simply restarting Org mode
 for the current buffer.}.
 
-@node Faces for TODO keywords,  , Per-file keywords, TODO extensions
+@node Faces for TODO keywords, TODO dependencies, Per-file keywords, TODO extensions
 @subsection Faces for TODO keywords
 @cindex faces, for TODO keywords
 
@@ -3306,6 +3308,41 @@ While using a list with face properties as shown for CANCELED
 @emph{should} work, this does not aways seem to be the case.  If
 necessary, define a special face and use that.
 
+@node TODO dependencies,  , Faces for TODO keywords, TODO extensions
+@subsection TODO dependencies
+
+The structure of Org files (hierarchy and lists) makes it easy to define TODO
+dependencies.  Usually, a parent TODO task should not be marked DONE until
+all subtasks (defined as children tasks) are marked as DONE.  And sometimes
+there is a logical sequence to a number of (sub)tasks, so that one task
+cannot be acted upon before all siblings above it are done.  If you customize
+the variable @code{org-enforce-todo-dependencies}, Org will block entries
+from changing state while they have children that are not DONE.  Furthermore,
+if an entry has a property @code{ORDERED}, each of its children will be
+blocked until all earlier siblings are marked DONE.  Here is an example:
+
+@example
+* TODO Blocked until (two) is done
+** DONE one
+** TODO two
+
+* Parent
+  :PROPERTIES:
+    :ORDERED: t
+  :END:
+** TODO a
+** TODO b, needs to wait for (a)
+** TODO c, needs to wait for (a) and (b)
+@end example
+
+If you set the variable @code{org-agenda-dim-blocked-tasks}, TODO entries
+that cannot be closed because of such dependencies will be shown in a dimmed
+font or even made invisible in agenda views (@pxref{Agenda Views}).
+
+If you need more complex dependency structures, for example dependencies
+between entries in different trees or files, check out the contributed
+module @file{org-depend.el}.
+
 @page
 @node Progress logging, Priorities, TODO extensions, TODO Items
 @section Progress logging
@@ -10252,7 +10289,7 @@ incorporate project planning functionality directly into a notes file.
 A special thanks goes to @i{Bastien Guerry} who has not only written a large
 number of extensions to Org (most of them integrated into the core by now),
 but has also helped the development and maintenance of Org so much that he
-should be considered co-author of this package.
+should be considered the main co-contributor to this package.
 
 Since the first release, literally thousands of emails to me or on
 @code{emacs-orgmode@@gnu.org} have provided a constant stream of bug
@@ -10427,13 +10464,13 @@ keyword.
 system.
 @item
 @i{John Wiegley} wrote @file{emacs-wiki.el}, @file{planner.el}, and
-@file{muse.el}, which have similar goals as Org.  Initially the
-development of Org was fully independent because I was not aware of the
-existence of these packages.  But with time I have occasionally looked
-at John's code and learned a lot from it.  John has also contributed a
-number of great ideas and patches directly to Org, including the attachment
-system (@file{org-attach.el}) and integration with Apple Mail
-(@file{org-mac-message.el}).
+@file{muse.el}, which have some overlap with Org.  Initially the development
+of Org was fully independent because I was not aware of the existence of
+these packages.  But with time I have occasionally looked at John's code and
+learned a lot from it.  John has also contributed a number of great ideas and
+patches directly to Org, including the attachment system
+(@file{org-attach.el}), integration with Apple Mail
+(@file{org-mac-message.el}), and hierarchical dependencies of TODO items.
 @item
 @i{Carsten Wimmer} suggested some changes and helped fix a bug in
 linking to Gnus.

+ 9 - 0
lisp/ChangeLog

@@ -1,5 +1,14 @@
 2009-01-27  Carsten Dominik  <carsten.dominik@gmail.com>
 
+	* org-agenda.el (org-agenda-dim-blocked-tasks): New option.
+	(org-finalize-agenda): Call `org-agenda-dim-blocked-tasks'.
+	(org-agenda-dim-blocked-tasks): New function.
+
+	* org.el (org-enforce-todo-dependencies): New option.
+	(org-block-todo-from-children-or-siblings): New function.
+
+	* org-faces.el (org-agenda-dimmed-todo-face): New face.
+
 	* org.el (org-todo): Return correct state type even if the blocker
 	throws an error.
 	(org-modifier-cursor-error): Renamed from

+ 47 - 0
lisp/org-agenda.el

@@ -513,6 +513,21 @@ deadlines are always turned off when the item is DONE."
   :group 'org-agenda-daily/weekly
   :type 'boolean)
 
+(defcustom org-agenda-dim-blocked-tasks t
+  "Non-nil means, dim blocked tasks in the agenda display.
+This causes some overhead during agenda construction, but if you have turned
+on `org-enforce-todo-dependencies' or any other blocking mechanism, this
+will create useful feedback in the agenda.
+Instead ot t, this variable can also have the value `invisible'.  Then
+blocked tasks will be invisible and only become visible when they
+become unblocked."
+  :group 'org-agenda-daily/weekly
+  :group 'org-agenda-todo-list
+  :type '(choice
+	  (const :tag "Do not dim" nil)
+	  (const :tag "Dim to a grey face" t)
+	  (const :tag "Make invisibe" invisible)))
+
 (defcustom org-timeline-show-empty-dates 3
   "Non-nil means, `org-timeline' also shows dates without an entry.
 When nil, only the days which actually have entries are shown.
@@ -2132,6 +2147,8 @@ VALUE defaults to t."
 	  (org-agenda-columns))
       (when org-agenda-fontify-priorities
 	(org-fontify-priorities))
+      (when (and org-agenda-dim-blocked-tasks org-blocker-hook)
+	(org-agenda-dim-blocked-tasks))
       (run-hooks 'org-finalize-agenda-hook)
       (setq org-agenda-type (get-text-property (point) 'org-agenda-type))
       )))
@@ -2162,6 +2179,36 @@ VALUE defaults to t."
 	       ((equal p h) 'bold)))
 	(org-overlay-put ov 'org-type 'org-priority)))))
 
+(defun org-agenda-dim-blocked-tasks ()
+  "Dim currently blocked TODO's in the agenda display."
+  (mapc (lambda (o) (if (eq (org-overlay-get o 'org-type) 'org-blocked-todo)
+			(org-delete-overlay o)))
+	(org-overlays-in (point-min) (point-max)))
+  (save-excursion
+    (let ((inhibit-read-only t)
+	  (invis (eq org-agenda-dim-blocked-tasks 'invisible))
+	  b e p ov h l)
+      (goto-char (point-min))
+      (while (let ((pos (next-single-property-change (point) 'todo-state)))
+	       (and pos (goto-char (1+ pos))))
+	(let ((marker (get-text-property (point) 'org-hd-marker)))
+	  (when (and marker
+		     (not (with-current-buffer (marker-buffer marker)
+			    (save-excursion
+			      (goto-char marker)
+			      (run-hook-with-args-until-failure
+			       'org-blocker-hook
+			       (list :type 'todo-state-change
+				     :position marker
+				     :from 'todo
+				     :to 'done))))))
+	    (setq b (if invis (max (point-min) (1- (point))) (point))
+		  e (point-at-eol)
+		  ov (org-make-overlay b e))
+	    (if invis
+		(org-overlay-put ov 'invisible t)
+	      (org-overlay-put ov 'face 'org-agenda-dimmed-todo-face))
+	    (org-overlay-put ov 'org-type 'org-blocked-todo)))))))
 
 (defvar org-agenda-skip-function nil
   "Function to be called at each match during agenda construction.

+ 5 - 0
lisp/org-faces.el

@@ -456,6 +456,11 @@ belong to the weekend.")
   "Face for items scheduled for a certain day."
   :group 'org-faces)
 
+(defface org-agenda-dimmed-todo-face
+  '((((background light)) (:foreground "grey50"))
+    (((background dark)) (:foreground "grey50")))
+  "Face used to dimm blocked tasks in the agenda."
+  :group 'org-faces)
 
 (defface org-scheduled-previously
   (org-compatible-face nil

+ 97 - 28
lisp/org.el

@@ -1603,6 +1603,49 @@ Lisp variable `state'."
   :group 'org-todo
   :type 'hook)
 
+(defvar org-blocker-hook nil
+  "Hook for functions that are allowed to block a state change.
+
+Each function gets as its single argument a property list, see
+`org-trigger-hook' for more information about this list.
+
+If any of the functions in this hook returns nil, the state change
+is blocked.")
+
+(defvar org-trigger-hook nil
+  "Hook for functions that are triggered by a state change.
+
+Each function gets as its single argument a property list with at least
+the following elements:
+
+ (:type type-of-change :position pos-at-entry-start
+  :from old-state :to new-state)
+
+Depending on the type, more properties may be present.
+
+This mechanism is currently implemented for:
+
+TODO state changes
+------------------
+:type  todo-state-change
+:from  previous state (keyword as a string), or nil, or a symbol
+       'todo' or 'done', to indicate the general type of state.
+:to    new state, like in :from")
+
+(defcustom org-enforce-todo-dependencies nil
+  "Non-nil means, undone TODO entries will block switching the parent to DONE.
+Also, if a parent has an :ORDERED: property, switching an entry to DONE will
+be blocked if any prior sibling is not yet done.
+You need to set this variable through the customize interface, or to
+restart emacs after changing the value."
+  :set (lambda (var val)
+	 (set var val)
+	 (if val
+	     (add-hook 'org-blocker-hook 'org-block-todo-from-children-or-siblings)
+	   (remove-hook 'org-blocker-hook 'org-block-todo-from-children-or-siblings)))
+  :group 'org-todo
+  :type 'boolean)
+
 (defcustom org-todo-state-tags-triggers nil
   "Tag changes that should be triggered by TODO state changes.
 This is a list.  Each entry is
@@ -8272,34 +8315,6 @@ this is nil.")
 	      (push (nth 2 e) rtn)))
 	  rtn)))))
 
-(defvar org-blocker-hook nil
-  "Hook for functions that are allowed to block a state change.
-
-Each function gets as its single argument a property list, see
-`org-trigger-hook' for more information about this list.
-
-If any of the functions in this hook returns nil, the state change
-is blocked.")
-
-(defvar org-trigger-hook nil
-  "Hook for functions that are triggered by a state change.
-
-Each function gets as its single argument a property list with at least
-the following elements:
-
- (:type type-of-change :position pos-at-entry-start
-  :from old-state :to new-state)
-
-Depending on the type, more properties may be present.
-
-This mechanism is currently implemented for:
-
-TODO state changes
-------------------
-:type  todo-state-change
-:from  previous state (keyword as a string), or nil
-:to    new state (keyword as a string), or nil")
-
 (defvar org-agenda-headline-snapshot-before-repeat)
 (defun org-todo (&optional arg)
   "Change the TODO state of an item.
@@ -8492,6 +8507,60 @@ 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-block-todo-from-children-or-siblings (change-plist)
+  "Block turning an entry into a TODO, using the hierarchy.
+This checks whether the current task should be blocked from state
+changes.  Such blocking occurs when:
+
+  1. The task has children which are not all in a completed state.
+
+  2. A task has a parent with the property :ORDERED:, and there
+     are siblings prior to the current task with incomplete
+     status."
+  (catch 'dont-block
+    ;; If this is not a todo state change, or if this entry is already DONE,
+    ;; do not block
+    (when (or (not (eq (plist-get change-plist :type) 'todo-state-change))
+	      (member (plist-get change-plist :from)
+		      (cons 'done org-done-keywords)))
+      (throw 'dont-block t))
+    ;; If this task has children, and any are undone, it's blocked
+    (save-excursion
+      (org-back-to-heading t)
+      (let ((this-level (funcall outline-level)))
+	(outline-next-heading)
+	(let ((child-level (funcall outline-level)))
+	  (while (and (not (eobp))
+		      (> child-level this-level))
+	    ;; this todo has children, check whether they are all
+	    ;; completed
+	    (if (and (not (org-entry-is-done-p))
+		     (org-entry-is-todo-p))
+		(throw 'dont-block nil))
+	    (outline-next-heading)
+	    (setq child-level (funcall outline-level))))))
+    ;; Otherwise, if the task's parent has the :ORDERED: property, and
+    ;; any previous siblings are undone, it's blocked
+    (save-excursion
+      (org-back-to-heading t)
+      (when (save-excursion
+	      (ignore-errors
+		(outline-up-heading 1)
+		(org-entry-get (point) "ORDERED")))
+	(let* ((this-level (funcall outline-level))
+	       (current-level this-level))
+	  (while (and (not (bobp))
+		      (= current-level this-level))
+	    (outline-previous-heading)
+	    (setq current-level (funcall outline-level))
+	    (if (= current-level this-level)
+		;; this todo has children, check whether they are all
+		;; completed
+		(if (and (not (org-entry-is-done-p))
+			 (org-entry-is-todo-p))
+		    (throw 'dont-block nil)))))))
+    t))					; don't block
+
 (defun org-update-parent-todo-statistics ()
   "Update any statistics cookie in the parent of the current headline."
   (interactive)