Hi,

Currently, the checkbox summary types in column view ("X", "X/", and
"X%") only summarize the direct children of a heading. I would like to
be able to count checkboxes recursively across the subtree -- the same
behavior is already present for checkbox statistics cookies
(`org-checkbox-children-only-statistics') and TODO statistics cookies
(`org-todo-children-only-statistics').

This patch introduces a new user option:
`org-columns-checkbox-children-only-statistics' (defaulting to t).

When set to nil, "X", "X/", and "X%" count all checkboxes in the whole
subtree. The naming follows the convention established recently in
commit 437fd1151 ("org: Rename statistics scope options"), where
`org-checkbox-hierarchical-statistics' and `org-hierarchical-todo-statistics'
were renamed to describe their non-nil behavior explicitly.

Regarding the manual, I considered adding this option there as well, but
the column view section would benefit from some restructuring first to
be more readable. To avoid scope creep and keep this patch small and
focused, I left the Org manual untouched for now. I plan to improve
and update that section in my upcoming patches concerning column view.

Best,
-- 
Slawomir Grochowski
>From bf513a3a780b14de0a28b6f994796b5e16f32692 Mon Sep 17 00:00:00 2001
From: Slawomir Grochowski <[email protected]>
Date: Sat, 5 Sep 2026 08:12:18 +0200
Subject: [PATCH] org-columns: Add option to count checkboxes recursively

* lisp/org-colview.el (org-columns-checkbox-children-only-statistics):
New option.  When nil, the "X", "X/" and "X%" summary types count the
checkboxes of the whole subtree, instead of the children only.
(org-columns--checkbox-operators): New constant.
(org-colview-construct-allowed-dates): Use
`org-columns--checkbox-operators'.
(org-columns--subtree-checkbox-summary-p): New function.
(org-columns--compute-spec): When an entry is summarized while the new
option is nil, pass the values found below it up the tree, instead of
its own summary.

* testing/lisp/test-org-colview.el
(test-org-colview/columns-checkbox-children-only-statistics): New test.

* etc/ORG-NEWS (New option ~org-columns-checkbox-children-only-statistics~):
Document the new option.

This mirrors what `org-checkbox-children-only-statistics' does for
statistics cookies in plain lists.
---
 etc/ORG-NEWS                     |  8 +++++
 lisp/org-colview.el              | 28 ++++++++++++++--
 testing/lisp/test-org-colview.el | 57 ++++++++++++++++++++++++++++++++
 3 files changed, 90 insertions(+), 3 deletions(-)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index db6637135..0a85108af 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -188,6 +188,14 @@ and some environments listed in the manual are not supported.
 # Changes dealing with changing default values of customizations,
 # adding new customizations, or changing the interpretation of the
 # existing customizations.
+*** New option ~org-columns-checkbox-children-only-statistics~
+
+The checkbox summary types =X=, =X/=, and =X%= in column view only
+cover children of the heading.  Setting the new
+~org-columns-checkbox-children-only-statistics~ to nil makes them count
+the checkboxes of the whole subtree instead, mirroring what
+~org-checkbox-children-only-statistics~ does for statistics cookies.
+
 *** New hook ~org-hidden-text-functions~
 
 Abnormal hook called when certain text is hidden during fontification
diff --git a/lisp/org-colview.el b/lisp/org-colview.el
index 9556c2957..26a795418 100644
--- a/lisp/org-colview.el
+++ b/lisp/org-colview.el
@@ -76,6 +76,14 @@ node `(org)Column attributes')."
                   (const :tag "Intermediate state [-]" "[-]")
                   (string :tag "Arbitrary string"))))
 
+(defcustom org-columns-checkbox-children-only-statistics t
+  "Non-nil means checkbox summary types only cover children of the heading.
+The affected summary types are \"X\", \"X/\", and \"X%\".  When nil,
+they count the checkboxes of the whole subtree instead."
+  :group 'org-properties
+  :package-version '(Org . "10.0")
+  :type 'boolean)
+
 (defcustom org-columns-modify-value-for-display-function nil
   "Function that modifies values for display in column view.
 For example, it can be used to cut out a certain part from a timestamp.
@@ -155,6 +163,9 @@ For more information, see `org-columns-dblock-write-default'."
   "Map operators to summary functions.
 See `org-columns-summary-types' for details.")
 
+(defconst org-columns--checkbox-operators '("X" "X/" "X%")
+  "Summary operators computing a checkbox summary.")
+
 
 ;;;; Column View
 
@@ -983,7 +994,7 @@ an integer, select that value."
 		 (or (org-property-get-allowed-values pom key)
 		     (and (member (org-columns--spec-operator
 				   (org-columns--spec-at-point))
-				  '("X" "X/" "X%"))
+				  org-columns--checkbox-operators)
 			  org-columns-checkbox-allowed-values)
 		     (org-colview-construct-allowed-dates value))))
 	    (if previous (reverse all) all))))
@@ -1549,6 +1560,13 @@ they have their own way to be computed."
     (and (not (member property org-special-properties))
 	 (org-columns--spec-operator spec))))
 
+(defun org-columns--subtree-checkbox-summary-p (operator)
+  "Non-nil when OPERATOR counts the checkboxes of a whole subtree.
+Only the operators in `org-columns--checkbox-operators' can do so, and
+only when `org-columns-checkbox-children-only-statistics' is nil."
+  (and (not org-columns-checkbox-children-only-statistics)
+       (member operator org-columns--checkbox-operators)))
+
 (defun org-columns--compute-spec (spec &optional update-property-p)
   "Update tree according to SPEC.
 SPEC is a column format specification.  When optional argument
@@ -1559,6 +1577,7 @@ existing ones in properties drawers."
 	  (format-string (org-columns--spec-format-string spec))
 	  (collect-function (org-columns--collect-function operator))
 	  (summarize-function (org-columns--summarize-function operator))
+	  (subtree-checkboxes-p (org-columns--subtree-checkbox-summary-p operator))
 	  (stack nil))
       (org-with-wide-buffer
        ;; Find the region to compute.
@@ -1585,8 +1604,11 @@ existing ones in properties drawers."
 			   (when update-property-p (org-columns--update-summary-property property current-value summary))
 			   summary)
 			  (t current-value))))
-	     (when (org-string-nw-p value)
-	       (push (cons level value) stack)))))))))
+	     (cond
+	      ((and summary subtree-checkboxes-p)
+	       (dolist (v (reverse child-values))
+		 (push (cons level v) stack)))
+	      ((org-string-nw-p value) (push (cons level value) stack))))))))))
 
 ;;;###autoload
 (defun org-columns-compute (property)
diff --git a/testing/lisp/test-org-colview.el b/testing/lisp/test-org-colview.el
index bfd9cf727..9b97b16e7 100644
--- a/testing/lisp/test-org-colview.el
+++ b/testing/lisp/test-org-colview.el
@@ -1081,6 +1081,63 @@ https://list.orgmode.org/[email protected]/T/#u.";
       (list (get-char-property (point) 'org-columns-value-modified)
 	    (get-char-property (1+ (point)) 'org-columns-value-modified))))))
 
+(ert-deftest test-org-colview/columns-checkbox-children-only-statistics ()
+  "Test `org-columns-checkbox-children-only-statistics' specifications."
+  ;; The tree has four leaf checkboxes (three checked, one unchecked).
+  ;; S1 has [1/2], S2 has [2/2].
+  (let ((tree "* H
+** S1
+*** S11
+:PROPERTIES:
+:A: [X]
+:END:
+*** S12
+:PROPERTIES:
+:A: [ ]
+:END:
+** S2
+*** S21
+:PROPERTIES:
+:A: [X]
+:END:
+*** S22
+:PROPERTIES:
+:A: [X]
+:END:"))
+    (cl-flet ((summary (format children-only)
+                (org-test-with-temp-text tree
+                  (let ((org-columns-default-format format)
+                        (org-columns-checkbox-children-only-statistics
+                         children-only))
+                    (org-columns))
+                  (get-char-property (point) 'org-columns-value-modified))))
+      ;; By default (t), only direct children are counted:
+      ;; H has two children: S1 ([1/2], incomplete) and S2 ([2/2], complete).
+      (should (equal "[1/2]" (summary "%A{X/}" t)))
+      ;; When nil, count all checkboxes in the entire subtree (3 of 4 checked).
+      (should (equal "[3/4]" (summary "%A{X/}" nil)))
+      ;; Percentage summary type {X%}: 50% (1 of 2 children) vs 75% (3 of 4 checkboxes).
+      (should (equal "[50%]" (summary "%A{X%}" t)))
+      (should (equal "[75%]" (summary "%A{X%}" nil)))))
+  ;; When a subheading has its own checkbox property and also has children,
+  ;; the children's values override the parent's value so it is not counted twice.
+  (should
+   (equal "[0/1]"
+          (org-test-with-temp-text
+              "* H
+** S1
+:PROPERTIES:
+:A: [X]
+:END:
+*** S11
+:PROPERTIES:
+:A: [ ]
+:END:"
+            (let ((org-columns-default-format "%A{X/}")
+                  (org-columns-checkbox-children-only-statistics nil))
+              (org-columns))
+            (get-char-property (point) 'org-columns-value-modified)))))
+
 (ert-deftest test-org-colview/columns-custom-summary-hierarchical ()
   "Test custom summary types on hierarchical headings."
   (should
-- 
2.47.3

Reply via email to