瀏覽代碼

Use "#+attr_org: :radio" before lists to enable radio buttons

* doc/org-manual.org (Checkboxes): Document the use of
"#+attr_org".

* lisp/org.el (org-ctrl-c-ctrl-c): When the list at point is
preceded by "#+attr_org: :radio" use `org-toggle-radio-button'
instead of `org-toggle-checkbox'.

* lisp/org-list.el (org-at-radio-list-p): New defsubst.
(org-toggle-checkbox): Use it.

* etc/ORG-NEWS: Document the use of "#+attr_org".
Bastien 5 年之前
父節點
當前提交
4028cc731b
共有 4 個文件被更改,包括 111 次插入92 次删除
  1. 4 3
      doc/org-manual.org
  2. 3 0
      etc/ORG-NEWS
  3. 97 85
      lisp/org-list.el
  4. 7 4
      lisp/org.el

+ 4 - 3
doc/org-manual.org

@@ -4539,12 +4539,13 @@ The following commands work with checkboxes:
   Toggle checkbox status by using the checkbox of the item at point as
   a radio button: when turned on, all other checkboxes on the same
   level will be turned off.  With a universal prefix argument, toggle
-  the presence of the checkbox.  With double prefix argument, set it
+  the presence of the checkbox.  With a double prefix argument, set it
   to =[-]=.
 
   #+findex: org-list-checkbox-radio-mode
-  {{{kdb(C-c C-c)}}} can be told to consider checkboxes as radio buttons
-  by calling {{{kbd(M-x org-list-checkbox-radio-mode)}}}, as minor mode.
+  {{{kdb(C-c C-c)}}} can be told to consider checkboxes as radio buttons by
+  setting =#+ATTR_ORG: :radio= right before the list or by calling
+  {{{kbd(M-x org-list-checkbox-radio-mode)}}} to activate this minor mode.
 
 - {{{kbd(M-S-RET)}}} (~org-insert-todo-heading~) ::
 

+ 3 - 0
etc/ORG-NEWS

@@ -54,6 +54,9 @@ If you want to occasionally toggle a checkbox as a radio button
 without turning this minor mode on, you can use =<C-c C-x C-r>= to
 call ~org-toggle-radio-button~.
 
+You can also add =#+ATTR_ORG: :radio= right before the list to tell
+Org to use radio buttons for this list only.
+
 *** Looping agenda commands over headlines
 
 ~org-agenda-loop-over-headlines-in-active-region~ allows you to loop

+ 97 - 85
lisp/org-list.el

@@ -2337,6 +2337,16 @@ is an integer, 0 means `-', 1 means `+' etc.  If WHICH is
       (org-list-struct-apply-struct struct old-struct)
       (org-update-checkbox-count-maybe))))
 
+(defsubst org-at-radio-list-p ()
+  "Is point in a list with radio buttons?"
+  (let (attr)
+    (save-excursion
+      (org-at-item-p)
+      (goto-char (caar (org-list-struct)))
+      (org-backward-element)
+      (setq attr (car (org-element-property :attr_org (org-element-at-point))))
+      (when attr (string-match-p ":radio" attr)))))
+
 (defun org-toggle-checkbox (&optional toggle-presence)
   "Toggle the checkbox in the current line.
 
@@ -2351,92 +2361,94 @@ If point is on a headline, apply this to all checkbox items in
 the text below the heading, taking as reference the first item in
 subtree, ignoring planning line and any drawer following it."
   (interactive "P")
-  (save-excursion
-    (let* (singlep
-	   block-item
-	   lim-up
-	   lim-down
-	   (orderedp (org-entry-get nil "ORDERED"))
-	   (_bounds
-	    ;; In a region, start at first item in region.
+  (if (org-at-radio-list-p)
+      (org-toggle-radio-button toggle-presence)
+    (save-excursion
+      (let* (singlep
+	     block-item
+	     lim-up
+	     lim-down
+	     (orderedp (org-entry-get nil "ORDERED"))
+	     (_bounds
+	      ;; In a region, start at first item in region.
+	      (cond
+	       ((org-region-active-p)
+		(let ((limit (region-end)))
+		  (goto-char (region-beginning))
+		  (if (org-list-search-forward (org-item-beginning-re) limit t)
+		      (setq lim-up (point-at-bol))
+		    (error "No item in region"))
+		  (setq lim-down (copy-marker limit))))
+	       ((org-at-heading-p)
+		;; On a heading, start at first item after drawers and
+		;; time-stamps (scheduled, etc.).
+		(let ((limit (save-excursion (outline-next-heading) (point))))
+		  (org-end-of-meta-data t)
+		  (if (org-list-search-forward (org-item-beginning-re) limit t)
+		      (setq lim-up (point-at-bol))
+		    (error "No item in subtree"))
+		  (setq lim-down (copy-marker limit))))
+	       ;; Just one item: set SINGLEP flag.
+	       ((org-at-item-p)
+		(setq singlep t)
+		(setq lim-up (point-at-bol)
+		      lim-down (copy-marker (point-at-eol))))
+	       (t (error "Not at an item or heading, and no active region"))))
+	     ;; Determine the checkbox going to be applied to all items
+	     ;; within bounds.
+	     (ref-checkbox
+	      (progn
+		(goto-char lim-up)
+		(let ((cbox (and (org-at-item-checkbox-p) (match-string 1))))
+		  (cond
+		   ((equal toggle-presence '(16)) "[-]")
+		   ((equal toggle-presence '(4))
+		    (unless cbox "[ ]"))
+		   ((equal "[X]" cbox) "[ ]")
+		   (t "[X]"))))))
+	;; When an item is found within bounds, grab the full list at
+	;; point structure, then: (1) set check-box of all its items
+	;; within bounds to REF-CHECKBOX, (2) fix check-boxes of the
+	;; whole list, (3) move point after the list.
+	(goto-char lim-up)
+	(while (and (< (point) lim-down)
+		    (org-list-search-forward (org-item-beginning-re)
+					     lim-down 'move))
+	  (let* ((struct (org-list-struct))
+		 (struct-copy (copy-tree struct))
+		 (parents (org-list-parents-alist struct))
+		 (prevs (org-list-prevs-alist struct))
+		 (bottom (copy-marker (org-list-get-bottom-point struct)))
+		 (items-to-toggle (cl-remove-if
+				   (lambda (e) (or (< e lim-up) (> e lim-down)))
+				   (mapcar #'car struct))))
+	    (mapc (lambda (e) (org-list-set-checkbox
+			       e struct
+			       ;; If there is no box at item, leave as-is
+			       ;; unless function was called with C-u prefix.
+			       (let ((cur-box (org-list-get-checkbox e struct)))
+				 (if (or cur-box (equal toggle-presence '(4)))
+				     ref-checkbox
+				   cur-box))))
+		  items-to-toggle)
+	    (setq block-item (org-list-struct-fix-box
+			      struct parents prevs orderedp))
+	    ;; Report some problems due to ORDERED status of subtree.
+	    ;; If only one box was being checked, throw an error, else,
+	    ;; only signal problems.
 	    (cond
-	     ((org-region-active-p)
-	      (let ((limit (region-end)))
-		(goto-char (region-beginning))
-		(if (org-list-search-forward (org-item-beginning-re) limit t)
-		    (setq lim-up (point-at-bol))
-		  (error "No item in region"))
-		(setq lim-down (copy-marker limit))))
-	     ((org-at-heading-p)
-	      ;; On a heading, start at first item after drawers and
-	      ;; time-stamps (scheduled, etc.).
-	      (let ((limit (save-excursion (outline-next-heading) (point))))
-		(org-end-of-meta-data t)
-		(if (org-list-search-forward (org-item-beginning-re) limit t)
-		    (setq lim-up (point-at-bol))
-		  (error "No item in subtree"))
-		(setq lim-down (copy-marker limit))))
-	     ;; Just one item: set SINGLEP flag.
-	     ((org-at-item-p)
-	      (setq singlep t)
-	      (setq lim-up (point-at-bol)
-		    lim-down (copy-marker (point-at-eol))))
-	     (t (error "Not at an item or heading, and no active region"))))
-	   ;; Determine the checkbox going to be applied to all items
-	   ;; within bounds.
-	   (ref-checkbox
-	    (progn
-	      (goto-char lim-up)
-	      (let ((cbox (and (org-at-item-checkbox-p) (match-string 1))))
-		(cond
-		 ((equal toggle-presence '(16)) "[-]")
-		 ((equal toggle-presence '(4))
-		  (unless cbox "[ ]"))
-		 ((equal "[X]" cbox) "[ ]")
-		 (t "[X]"))))))
-      ;; When an item is found within bounds, grab the full list at
-      ;; point structure, then: (1) set check-box of all its items
-      ;; within bounds to REF-CHECKBOX, (2) fix check-boxes of the
-      ;; whole list, (3) move point after the list.
-      (goto-char lim-up)
-      (while (and (< (point) lim-down)
-		  (org-list-search-forward (org-item-beginning-re)
-					   lim-down 'move))
-	(let* ((struct (org-list-struct))
-	       (struct-copy (copy-tree struct))
-	       (parents (org-list-parents-alist struct))
-	       (prevs (org-list-prevs-alist struct))
-	       (bottom (copy-marker (org-list-get-bottom-point struct)))
-	       (items-to-toggle (cl-remove-if
-				 (lambda (e) (or (< e lim-up) (> e lim-down)))
-				 (mapcar #'car struct))))
-	  (mapc (lambda (e) (org-list-set-checkbox
-			     e struct
-			     ;; If there is no box at item, leave as-is
-			     ;; unless function was called with C-u prefix.
-			     (let ((cur-box (org-list-get-checkbox e struct)))
-			       (if (or cur-box (equal toggle-presence '(4)))
-				   ref-checkbox
-				 cur-box))))
-		items-to-toggle)
-	  (setq block-item (org-list-struct-fix-box
-			    struct parents prevs orderedp))
-	  ;; Report some problems due to ORDERED status of subtree.
-	  ;; If only one box was being checked, throw an error, else,
-	  ;; only signal problems.
-	  (cond
-	   ((and singlep block-item (> lim-up block-item))
-	    (error
-	     "Checkbox blocked because of unchecked box at line %d"
-	     (org-current-line block-item)))
-	   (block-item
-	    (message
-	     "Checkboxes were removed due to unchecked box at line %d"
-	     (org-current-line block-item))))
-	  (goto-char bottom)
-	  (move-marker bottom nil)
-	  (org-list-struct-apply-struct struct struct-copy)))
-      (move-marker lim-down nil)))
+	     ((and singlep block-item (> lim-up block-item))
+	      (error
+	       "Checkbox blocked because of unchecked box at line %d"
+	       (org-current-line block-item)))
+	     (block-item
+	      (message
+	       "Checkboxes were removed due to unchecked box at line %d"
+	       (org-current-line block-item))))
+	    (goto-char bottom)
+	    (move-marker bottom nil)
+	    (org-list-struct-apply-struct struct struct-copy)))
+	(move-marker lim-down nil))))
   (org-update-checkbox-count-maybe))
 
 (defun org-reset-checkbox-state-subtree ()

+ 7 - 4
lisp/org.el

@@ -17171,6 +17171,7 @@ This command does many different things, depending on context:
 			  src-block statistics-cookie table table-cell table-row
 			  timestamp)
 	     t))
+	   (radio-list-p (org-at-radio-list-p))
 	   (type (org-element-type context)))
       ;; For convenience: at the first line of a paragraph on the same
       ;; line as an item, apply function on that item instead.
@@ -17217,8 +17218,9 @@ This command does many different things, depending on context:
 	 ;; unconditionally, whereas `C-u' will toggle its presence.
 	 ;; Without a universal argument, if the item has a checkbox,
 	 ;; toggle it.  Otherwise repair the list.
-	 (if (and (boundp org-list-checkbox-radio-mode)
-		  org-list-checkbox-radio-mode)
+	 (if (or radio-list-p
+		 (and (boundp org-list-checkbox-radio-mode)
+		      org-list-checkbox-radio-mode))
 	     (org-toggle-radio-button arg)
 	   (let* ((box (org-element-property :checkbox context))
 		  (struct (org-element-property :structure context))
@@ -17259,8 +17261,9 @@ This command does many different things, depending on context:
 	 ;; will toggle their presence according to the state of the
 	 ;; first item in the list.  Without an argument, repair the
 	 ;; list.
-	 (if (and (boundp org-list-checkbox-radio-mode)
-		  org-list-checkbox-radio-mode)
+	 (if (or radio-list-p
+		 (and (boundp org-list-checkbox-radio-mode)
+		      org-list-checkbox-radio-mode))
 	     (org-toggle-radio-button arg)
 	   (let* ((begin (org-element-property :contents-begin context))
 		  (struct (org-element-property :structure context))