2

CONTEXT

I have org-todo-keywords set to "TODO(t) | DONE(d) CANCELED(c)".

Also, I have configured org-todo-state-triggers to

(cons 'quote (list (cons 'todo (list (cons org-archive-tag nil)))
             (cons "CANCELED" (list (cons org-archive-tag t)))))

This sets the :ARCHIVE: tag on the entire subtree, once the todo-state is set to CANCELED. It also automatically removes the tag if the todo-state is changed back to TODO.

PROBLEM

Finally, I have org-enforce-todo-dependencies set to t.

This would prevent setting the todo-state to DONE, when any subtask is not yet DONE.

Unfortunately, it has the undesirable side-effect, that until all subtasks are in done state (i.e. DONE or CANCELED), I would not be able to set the parent task to the CANCELED state, as it is blocked by the enforcement rule.

Is there a way to make the CANCELED todo-state ignore the subtask dependencies, or even better, to set the entire subtree to CANCELED state atomically?

2 Answers 2

2

I have not found a builtin way to accomplish this "easily", so I wrote the following two functions which can manually be invoked to change the status of all todo states in a subtree to CANCELLED and back. You can use undo right after cancelling to reset it but this won't work some days later, therefore the uncancel-function.

IMPORTANT: This includes ALL todo keywords. If you created some other keywords like HOLD or WAITING, they will also be changed to CANCELLED and on "uncancelling" they will be set to TODO. Done keywords will remain as they are. (check the example)

One Logbook entry will automatically be created for the main headline if desired.

You can change the CANCELLED to CANCELED in the first line (I didn't know both spellings are okay).

;; Since there are different spellings for that word we rather have a variable for it
(setq org-cancel-keyword "CANCELLED")

;; Taken from http://doc.norang.ca/org-mode.html#Projects
(defun bh/is-project-p ()
  "Any task with a todo keyword subtask"
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task has-subtask))))

(defun my/org-cancel-subtree ()
  (interactive)
  (save-mark-and-excursion
      (let ((projects (list)))
        (org-map-entries
         (lambda ()
           (if (org-entry-is-todo-p)
                (if (bh/is-project-p)
                    (push (point) projects)
                  (org-todo org-cancel-keyword))))
         nil
         'tree)
        (dolist (project projects)
          (goto-char project)
          (org-todo org-cancel-keyword))))
    ;; We need to mark the main project as cancelled, too
    (if (org-entry-is-todo-p) (org-todo org-cancel-keyword)))

(defun my/org-uncancel-subtree ()
  (interactive)
  (save-mark-and-excursion
    ;; Ignore the first line, so that the note will be inserted on the first line
    (let ((first-header (save-excursion (org-back-to-heading)(point))))
      (org-map-entries
       (lambda () (if (not (= (point) first-header)) (org-todo "TODO")))
         (concat "/" org-cancel-keyword)
         'tree)))
    (org-todo "TODO"))

Here an example. Before cancellation:

* TODO My Tree
** TODO Some todo
** TODO A Project with TODOs in it
*** TODO Some todo
*** DONE A DONE todo
*** TODO another project within a project
**** TODO some todo
**** HOLD This todo is on HOLD
:LOGBOOK:
- State "HOLD"       from              [2020-01-05 Sun 14:08] \\
  reason
:END:
**** TODO some more todo
**** Just an information - not a todo
** TODO more TODOs
** more Informations
** TODO Another TODO

After Cancellation:

* CANCELLED My Tree                                               :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
:LOGBOOK:
- State "CANCELLED"  from "TODO"       [2020-01-05 Sun 15:57] \\
  This is my cancellation Note.
:END:
** CANCELLED Some todo                                           :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
** CANCELLED A Project with TODOs in it                          :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
*** CANCELLED Some todo                                         :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
*** DONE A DONE todo
*** CANCELLED another project within a project                  :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
**** CANCELLED some todo                                       :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
**** CANCELLED This todo is on HOLD                            :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
:LOGBOOK:
- State "HOLD"       from              [2020-01-05 Sun 14:08] \\
  reason
:END:
**** CANCELLED some more todo                                  :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
**** Just an information - not a todo
** CANCELLED more TODOs                                          :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]
** more Informations
** CANCELLED Another TODO                                        :CANCELLED:
CLOSED: [2020-01-05 Sun 15:57]

After "uncancellation":

* TODO My Tree
:LOGBOOK:
- State "TODO"       from "CANCELLED"  [2020-01-05 Sun 17:30]
- State "CANCELLED"  from "TODO"       [2020-01-05 Sun 15:57] \\
  This is my cancellation Note.
:END:
** TODO Some todo
** TODO A Project with TODOs in it
*** TODO Some todo
*** DONE A DONE todo
*** TODO another project within a project
**** TODO some todo
**** TODO This todo is on HOLD
:LOGBOOK:
- State "HOLD"       from              [2020-01-05 Sun 14:08] \\
  reason
:END:
**** TODO some more todo
**** Just an information - not a todo
** TODO more TODOs
** more Informations
** TODO Another TODO

Funfact: It took me about 3 hours and some useless debugging to find out that / prefix matches TODOs

3
  • Please remove the vulgar language from your example.
    – Dan
    Jan 5, 2020 at 17:40
  • Thanks for the suggestion, I think my issue is less with the changing of the todo-state of the whole subtree, and more with overriding the dependency restrictions when changing to the CANCELED done state. This is mainly because I already have a org-todo-state-tags-triggers rule for the CANCELED state, that it adds the :ARCHIVED: tag, and this way it archives the entire subtree outright. Also, it is easily reversible. Jan 6, 2020 at 16:36
  • Meanwhile I found the solution to my problem. For something what you are trying to do though, something like this might work better: (defun org-cancel-subtree () (interactive) (org-map-entries (lambda () (org-todo "CANCELED") (setq org-map-continue-from (outline-previous-heading))) "+TODO" 'tree)) Unfortunately this doesn't solve my problem, because the todo dependencies would still be enforced. Jan 6, 2020 at 18:11
2

It is actually explained in the docs that it is possible to simply override the enforcement of todo dependencies by using the C-c C-t command with triple prefix argument: C-u C-u C-u.

Also, there is no need to set all the subtasks individually to CANCELED state, as the archive-tag archives the entire subtree, which removes all the subtasks from the agenda.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.