Browse Source

Dependencies: Integrate John Wiegley's TODO dependency code.

See the documentation for details.
Carsten Dominik 16 years ago
parent
commit
49e8ee37a8
7 changed files with 246 additions and 39 deletions
  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_UP: index.html
 #+LINK_HOME: http://orgmode.org
 #+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:
   :PROPERTIES:
   :VISIBILITY: content
   :VISIBILITY: content
   :END:
   :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>
 2009-01-26  Carsten Dominik  <carsten.dominik@gmail.com>
 
 
 	* org.texi (Plain lists, TODO basics, Priorities)
 	* 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
 * Fast access to TODO states::  Single letter selection of a state
 * Per-file keywords::           Different files, different requirements
 * Per-file keywords::           Different files, different requirements
 * Faces for TODO keywords::     Highlighting states
 * Faces for TODO keywords::     Highlighting states
+* TODO dependencies::           When one tasks needs to wait for others
 
 
 Progress logging
 Progress logging
 
 
@@ -583,7 +584,7 @@ MY PROJECTS    -*- mode: org; -*-
 the file's name is.  See also the variable
 the file's name is.  See also the variable
 @code{org-insert-mode-line-in-empty-file}.
 @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
 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
 XEmacs) turned on.  In Emacs 23 this is the default, in Emacs 22 you need to
 do this yourself with
 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
 * Fast access to TODO states::  Single letter selection of a state
 * Per-file keywords::           Different files, different requirements
 * Per-file keywords::           Different files, different requirements
 * Faces for TODO keywords::     Highlighting states
 * Faces for TODO keywords::     Highlighting states
+* TODO dependencies::           When one tasks needs to wait for others
 @end menu
 @end menu
 
 
 @node Workflow states, TODO types, TODO extensions, TODO extensions
 @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
 cursor in a line starting with @samp{#+} is simply restarting Org mode
 for the current buffer.}.
 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
 @subsection Faces for TODO keywords
 @cindex 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
 @emph{should} work, this does not aways seem to be the case.  If
 necessary, define a special face and use that.
 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
 @page
 @node Progress logging, Priorities, TODO extensions, TODO Items
 @node Progress logging, Priorities, TODO extensions, TODO Items
 @section Progress logging
 @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
 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),
 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
 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
 Since the first release, literally thousands of emails to me or on
 @code{emacs-orgmode@@gnu.org} have provided a constant stream of bug
 @code{emacs-orgmode@@gnu.org} have provided a constant stream of bug
@@ -10427,13 +10464,13 @@ keyword.
 system.
 system.
 @item
 @item
 @i{John Wiegley} wrote @file{emacs-wiki.el}, @file{planner.el}, and
 @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
 @item
 @i{Carsten Wimmer} suggested some changes and helped fix a bug in
 @i{Carsten Wimmer} suggested some changes and helped fix a bug in
 linking to Gnus.
 linking to Gnus.

+ 9 - 0
lisp/ChangeLog

@@ -1,5 +1,14 @@
 2009-01-27  Carsten Dominik  <carsten.dominik@gmail.com>
 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
 	* org.el (org-todo): Return correct state type even if the blocker
 	throws an error.
 	throws an error.
 	(org-modifier-cursor-error): Renamed from
 	(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
   :group 'org-agenda-daily/weekly
   :type 'boolean)
   :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
 (defcustom org-timeline-show-empty-dates 3
   "Non-nil means, `org-timeline' also shows dates without an entry.
   "Non-nil means, `org-timeline' also shows dates without an entry.
 When nil, only the days which actually have entries are shown.
 When nil, only the days which actually have entries are shown.
@@ -2132,6 +2147,8 @@ VALUE defaults to t."
 	  (org-agenda-columns))
 	  (org-agenda-columns))
       (when org-agenda-fontify-priorities
       (when org-agenda-fontify-priorities
 	(org-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)
       (run-hooks 'org-finalize-agenda-hook)
       (setq org-agenda-type (get-text-property (point) 'org-agenda-type))
       (setq org-agenda-type (get-text-property (point) 'org-agenda-type))
       )))
       )))
@@ -2162,6 +2179,36 @@ VALUE defaults to t."
 	       ((equal p h) 'bold)))
 	       ((equal p h) 'bold)))
 	(org-overlay-put ov 'org-type 'org-priority)))))
 	(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
 (defvar org-agenda-skip-function nil
   "Function to be called at each match during agenda construction.
   "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."
   "Face for items scheduled for a certain day."
   :group 'org-faces)
   :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
 (defface org-scheduled-previously
   (org-compatible-face nil
   (org-compatible-face nil

+ 97 - 28
lisp/org.el

@@ -1603,6 +1603,49 @@ Lisp variable `state'."
   :group 'org-todo
   :group 'org-todo
   :type 'hook)
   :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
 (defcustom org-todo-state-tags-triggers nil
   "Tag changes that should be triggered by TODO state changes.
   "Tag changes that should be triggered by TODO state changes.
 This is a list.  Each entry is
 This is a list.  Each entry is
@@ -8272,34 +8315,6 @@ this is nil.")
 	      (push (nth 2 e) rtn)))
 	      (push (nth 2 e) rtn)))
 	  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)
 (defvar org-agenda-headline-snapshot-before-repeat)
 (defun org-todo (&optional arg)
 (defun org-todo (&optional arg)
   "Change the TODO state of an item.
   "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
 	  (save-excursion
 	    (run-hook-with-args 'org-trigger-hook change-plist)))))))
 	    (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 ()
 (defun org-update-parent-todo-statistics ()
   "Update any statistics cookie in the parent of the current headline."
   "Update any statistics cookie in the parent of the current headline."
   (interactive)
   (interactive)