Add `org-toggle-radio-button' and related minor mode

* doc/org-manual.org (Checkboxes): Document the new minor mode
and command.

* lisp/org-keys.el (org-mode-map): Bind C-c C-x C-r to
`org-toggle-radio-button'.

* lisp/org-list.el (org-list-checkbox-radio-mode): New minor
mode to let C-c C-c call `org-toggle-radio-button' instead of
`org-toggle-checkbox'.
(org-toggle-radio-button): New command.

* lisp/org.el (org-ctrl-c-ctrl-c): Use `org-toggle-radio-button'.

* etc/ORG-NEWS: Document the new minor mode and command.

Thanks to Phil Sainty for sharing this idea and links to similar
implementations.
This commit is contained in:
Bastien 2020-02-11 23:55:53 +01:00
parent 8f59e01aa8
commit 561feb128d
5 changed files with 135 additions and 67 deletions

View File

@ -4531,6 +4531,21 @@ The following commands work with checkboxes:
- If there is no active region, just toggle the checkbox at point.
- {{{kbd(C-c C-x C-r)}}} (~org-toggle-radio-button~) ::
#+kindex: C-c C-x C-r
#+findex: org-toggle-radio-button
#+cindex: radio button, checkbox as
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
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.
- {{{kbd(M-S-RET)}}} (~org-insert-todo-heading~) ::
#+kindex: M-S-RET

View File

@ -44,6 +44,16 @@ You can activate this minor mode by default by setting the option
~org-table-header-line-p~ to =t=. You can also change the face for
the header line by customizing the ~org-table-header~ face.
*** New minor mode ~org-list-checkbox-radio-mode~
When this minor mode is on, checkboxes behave as radio buttons: if a
checkbox is turned on, other checkboxes at the same level are turned
off.
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~.
*** Looping agenda commands over headlines
~org-agenda-loop-over-headlines-in-active-region~ allows you to loop

View File

@ -196,6 +196,7 @@
(declare-function org-todo "org" (&optional arg1))
(declare-function org-toggle-archive-tag "org" (&optional find-done))
(declare-function org-toggle-checkbox "org" (&optional toggle-presence))
(declare-function org-toggle-radio-button "org" (&optional arg))
(declare-function org-toggle-comment "org" ())
(declare-function org-toggle-fixed-width "org" ())
(declare-function org-toggle-inline-images "org" (&optional include-linked))
@ -658,6 +659,7 @@ COMMANDS is a list of alternating OLDDEF NEWDEF command names."
(org-defkey org-mode-map (kbd "C-c C-x C-M-v") #'org-redisplay-inline-images)
(org-defkey org-mode-map (kbd "C-c C-x \\") #'org-toggle-pretty-entities)
(org-defkey org-mode-map (kbd "C-c C-x C-b") #'org-toggle-checkbox)
(org-defkey org-mode-map (kbd "C-c C-x C-r") #'org-toggle-radio-button)
(org-defkey org-mode-map (kbd "C-c C-x p") #'org-set-property)
(org-defkey org-mode-map (kbd "C-c C-x P") #'org-set-property-and-value)
(org-defkey org-mode-map (kbd "C-c C-x e") #'org-set-effort)

View File

@ -2302,6 +2302,41 @@ is an integer, 0 means `-', 1 means `+' etc. If WHICH is
(org-list-struct-fix-ind struct parents)
(org-list-struct-apply-struct struct old-struct)))))
;;;###autoload
(define-minor-mode org-list-checkbox-radio-mode
"When turned on, use list checkboxes as radio buttons."
nil " CheckBoxRadio" nil
(unless (eq major-mode 'org-mode)
(user-error "Cannot turn this mode outside org-mode buffers")))
(defun org-toggle-radio-button (&optional arg)
"Toggle off all checkboxes and toggle on the one at point."
(interactive "P")
(if (not (org-at-item-p))
(user-error "Cannot toggle checkbox outside of a list")
(let* ((cpos (org-in-item-p))
(struct (org-list-struct))
(orderedp (org-entry-get nil "ORDERED"))
(parents (org-list-parents-alist struct))
(old-struct (copy-tree struct))
(cbox (org-list-get-checkbox cpos struct))
(prevs (org-list-prevs-alist struct))
(start (org-list-get-list-begin (point-at-bol) struct prevs))
(new (unless (and cbox (equal arg '(4)) (equal start cpos))
"[ ]")))
(dolist (pos (org-list-get-all-items
start struct (org-list-prevs-alist struct)))
(org-list-set-checkbox pos struct new))
(when new
(org-list-set-checkbox
cpos struct
(cond ((equal arg '(4)) (unless cbox "[ ]"))
((equal arg '(16)) (unless cbox "[-]"))
(t (if (equal cbox "[X]") "[ ]" "[X]")))))
(org-list-struct-fix-box struct parents prevs orderedp)
(org-list-struct-apply-struct struct old-struct)
(org-update-checkbox-count-maybe))))
(defun org-toggle-checkbox (&optional toggle-presence)
"Toggle the checkbox in the current line.

View File

@ -17217,39 +17217,79 @@ 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.
(let* ((box (org-element-property :checkbox context))
(struct (org-element-property :structure context))
(old-struct (copy-tree struct))
(parents (org-list-parents-alist struct))
(prevs (org-list-prevs-alist struct))
(orderedp (org-not-nil (org-entry-get nil "ORDERED"))))
(org-list-set-checkbox
(org-element-property :begin context) struct
(cond ((equal arg '(16)) "[-]")
((and (not box) (equal arg '(4))) "[ ]")
((or (not box) (equal arg '(4))) nil)
((eq box 'on) "[ ]")
(t "[X]")))
;; Mimic `org-list-write-struct' but with grabbing a return
;; value from `org-list-struct-fix-box'.
(org-list-struct-fix-ind struct parents 2)
(org-list-struct-fix-item-end struct)
(org-list-struct-fix-bul struct prevs)
(org-list-struct-fix-ind struct parents)
(let ((block-item
(org-list-struct-fix-box struct parents prevs orderedp)))
(if (and box (equal struct old-struct))
(if (equal arg '(16))
(message "Checkboxes already reset")
(user-error "Cannot toggle this checkbox: %s"
(if (eq box 'on)
"all subitems checked"
"unchecked subitems")))
(org-list-struct-apply-struct struct old-struct)
(org-update-checkbox-count-maybe))
(when block-item
(message "Checkboxes were removed due to empty box at line %d"
(org-current-line block-item))))))
(if (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))
(old-struct (copy-tree struct))
(parents (org-list-parents-alist struct))
(prevs (org-list-prevs-alist struct))
(orderedp (org-not-nil (org-entry-get nil "ORDERED"))))
(org-list-set-checkbox
(org-element-property :begin context) struct
(cond ((equal arg '(16)) "[-]")
((and (not box) (equal arg '(4))) "[ ]")
((or (not box) (equal arg '(4))) nil)
((eq box 'on) "[ ]")
(t "[X]")))
;; Mimic `org-list-write-struct' but with grabbing a return
;; value from `org-list-struct-fix-box'.
(org-list-struct-fix-ind struct parents 2)
(org-list-struct-fix-item-end struct)
(org-list-struct-fix-bul struct prevs)
(org-list-struct-fix-ind struct parents)
(let ((block-item
(org-list-struct-fix-box struct parents prevs orderedp)))
(if (and box (equal struct old-struct))
(if (equal arg '(16))
(message "Checkboxes already reset")
(user-error "Cannot toggle this checkbox: %s"
(if (eq box 'on)
"all subitems checked"
"unchecked subitems")))
(org-list-struct-apply-struct struct old-struct)
(org-update-checkbox-count-maybe))
(when block-item
(message "Checkboxes were removed due to empty box at line %d"
(org-current-line block-item)))))))
(`plain-list
;; At a plain list, with a double C-u argument, set
;; checkboxes of each item to "[-]", whereas a single one
;; 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)
(org-toggle-radio-button arg)
(let* ((begin (org-element-property :contents-begin context))
(struct (org-element-property :structure context))
(old-struct (copy-tree struct))
(first-box (save-excursion
(goto-char begin)
(looking-at org-list-full-item-re)
(match-string-no-properties 3)))
(new-box (cond ((equal arg '(16)) "[-]")
((equal arg '(4)) (unless first-box "[ ]"))
((equal first-box "[X]") "[ ]")
(t "[X]"))))
(cond
(arg
(dolist (pos
(org-list-get-all-items
begin struct (org-list-prevs-alist struct)))
(org-list-set-checkbox pos struct new-box)))
((and first-box (eq (point) begin))
;; For convenience, when point is at bol on the first
;; item of the list and no argument is provided, simply
;; toggle checkbox of that item, if any.
(org-list-set-checkbox begin struct new-box)))
(when (equal
(org-list-write-struct
struct (org-list-parents-alist struct) old-struct)
old-struct)
(message "Cannot update this checkbox"))
(org-update-checkbox-count-maybe))))
(`keyword
(let ((org-inhibit-startup-visibility-stuff t)
(org-startup-align-all-tables nil))
@ -17258,40 +17298,6 @@ This command does many different things, depending on context:
(setq org-table-coordinate-overlays nil))
(org-save-outline-visibility 'use-markers (org-mode-restart)))
(message "Local setup has been refreshed"))
(`plain-list
;; At a plain list, with a double C-u argument, set
;; checkboxes of each item to "[-]", whereas a single one
;; will toggle their presence according to the state of the
;; first item in the list. Without an argument, repair the
;; list.
(let* ((begin (org-element-property :contents-begin context))
(struct (org-element-property :structure context))
(old-struct (copy-tree struct))
(first-box (save-excursion
(goto-char begin)
(looking-at org-list-full-item-re)
(match-string-no-properties 3)))
(new-box (cond ((equal arg '(16)) "[-]")
((equal arg '(4)) (unless first-box "[ ]"))
((equal first-box "[X]") "[ ]")
(t "[X]"))))
(cond
(arg
(dolist (pos
(org-list-get-all-items
begin struct (org-list-prevs-alist struct)))
(org-list-set-checkbox pos struct new-box)))
((and first-box (eq (point) begin))
;; For convenience, when point is at bol on the first
;; item of the list and no argument is provided, simply
;; toggle checkbox of that item, if any.
(org-list-set-checkbox begin struct new-box)))
(when (equal
(org-list-write-struct
struct (org-list-parents-alist struct) old-struct)
old-struct)
(message "Cannot update this checkbox"))
(org-update-checkbox-count-maybe)))
((or `property-drawer `node-property)
(call-interactively #'org-property-action))
(`radio-target