cus-theme.el 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. ;;; cus-theme.el -- custom theme creation user interface
  2. ;;
  3. ;; Copyright (C) 2001-2012 Free Software Foundation, Inc.
  4. ;;
  5. ;; Author: Alex Schroeder <alex@gnu.org>
  6. ;; Maintainer: FSF
  7. ;; Keywords: help, faces
  8. ;; Package: emacs
  9. ;; This file is part of GNU Emacs.
  10. ;; GNU Emacs is free software: you can redistribute it and/or modify
  11. ;; it under the terms of the GNU General Public License as published by
  12. ;; the Free Software Foundation, either version 3 of the License, or
  13. ;; (at your option) any later version.
  14. ;; GNU Emacs is distributed in the hope that it will be useful,
  15. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;; GNU General Public License for more details.
  18. ;; You should have received a copy of the GNU General Public License
  19. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  20. ;;; Code:
  21. (require 'widget)
  22. (require 'cus-edit)
  23. (eval-when-compile
  24. (require 'wid-edit))
  25. (defvar custom-new-theme-mode-map
  26. (let ((map (make-keymap)))
  27. (set-keymap-parent map widget-keymap)
  28. (suppress-keymap map)
  29. (define-key map "\C-x\C-s" 'custom-theme-write)
  30. (define-key map "n" 'widget-forward)
  31. (define-key map "p" 'widget-backward)
  32. map)
  33. "Keymap for `custom-new-theme-mode'.")
  34. (define-derived-mode custom-new-theme-mode nil "Custom-Theme"
  35. "Major mode for editing Custom themes.
  36. Do not call this mode function yourself. It is meant for internal use."
  37. (use-local-map custom-new-theme-mode-map)
  38. (custom--initialize-widget-variables)
  39. (set (make-local-variable 'revert-buffer-function) 'custom-theme-revert))
  40. (put 'custom-new-theme-mode 'mode-class 'special)
  41. (defvar custom-theme-name nil)
  42. ;; Each element has the form (VAR CHECKBOX-WIDGET VAR-WIDGET)
  43. (defvar custom-theme-variables nil)
  44. ;; Each element has the form (FACE CHECKBOX-WIDGET FACE-WIDGET)
  45. (defvar custom-theme-faces nil)
  46. (defvar custom-theme-description nil)
  47. (defvar custom-theme--migrate-settings nil)
  48. (defvar custom-theme-insert-variable-marker nil)
  49. (defvar custom-theme-insert-face-marker nil)
  50. (defvar custom-theme--listed-faces '(default cursor fixed-pitch
  51. variable-pitch escape-glyph minibuffer-prompt highlight region
  52. shadow secondary-selection trailing-whitespace
  53. font-lock-builtin-face font-lock-comment-delimiter-face
  54. font-lock-comment-face font-lock-constant-face
  55. font-lock-doc-face font-lock-function-name-face
  56. font-lock-keyword-face font-lock-negation-char-face
  57. font-lock-preprocessor-face font-lock-regexp-grouping-backslash
  58. font-lock-regexp-grouping-construct font-lock-string-face
  59. font-lock-type-face font-lock-variable-name-face
  60. font-lock-warning-face button link link-visited fringe
  61. header-line tooltip mode-line mode-line-buffer-id
  62. mode-line-emphasis mode-line-highlight mode-line-inactive
  63. isearch isearch-fail lazy-highlight match next-error
  64. query-replace)
  65. "Faces listed by default in the *Custom Theme* buffer.")
  66. (defvar custom-theme--save-name)
  67. ;;;###autoload
  68. (defun customize-create-theme (&optional theme buffer)
  69. "Create or edit a custom theme.
  70. THEME, if non-nil, should be an existing theme to edit. If THEME
  71. is `user', the resulting *Custom Theme* buffer also contains a
  72. checkbox for removing the theme settings specified in the buffer
  73. from the Custom save file.
  74. BUFFER, if non-nil, should be a buffer to use; the default is
  75. named *Custom Theme*."
  76. (interactive)
  77. (switch-to-buffer (get-buffer-create (or buffer "*Custom Theme*")))
  78. (let ((inhibit-read-only t))
  79. (erase-buffer)
  80. (dolist (ov (overlays-in (point-min) (point-max)))
  81. (delete-overlay ov)))
  82. (custom-new-theme-mode)
  83. (make-local-variable 'custom-theme-name)
  84. (set (make-local-variable 'custom-theme--save-name) theme)
  85. (set (make-local-variable 'custom-theme-faces) nil)
  86. (set (make-local-variable 'custom-theme-variables) nil)
  87. (set (make-local-variable 'custom-theme-description) "")
  88. (set (make-local-variable 'custom-theme--migrate-settings) nil)
  89. (make-local-variable 'custom-theme-insert-face-marker)
  90. (make-local-variable 'custom-theme-insert-variable-marker)
  91. (make-local-variable 'custom-theme--listed-faces)
  92. (when (called-interactively-p 'interactive)
  93. (unless (y-or-n-p "Include basic face customizations in this theme? ")
  94. (setq custom-theme--listed-faces nil)))
  95. (if (eq theme 'user)
  96. (widget-insert "This buffer contains all the Custom settings you have made.
  97. You can convert them into a new custom theme, and optionally
  98. remove them from your saved Custom file.\n\n"))
  99. (widget-create 'push-button
  100. :tag " Visit Theme "
  101. :help-echo "Insert the settings of a pre-defined theme."
  102. :action (lambda (_widget &optional _event)
  103. (call-interactively 'custom-theme-visit-theme)))
  104. (widget-insert " ")
  105. (widget-create 'push-button
  106. :tag " Merge Theme "
  107. :help-echo "Merge in the settings of a pre-defined theme."
  108. :action (lambda (_widget &optional _event)
  109. (call-interactively 'custom-theme-merge-theme)))
  110. (widget-insert " ")
  111. (widget-create 'push-button
  112. :tag " Revert "
  113. :help-echo "Revert this buffer to its original state."
  114. :action (lambda (&rest ignored) (revert-buffer)))
  115. (widget-insert "\n\nTheme name : ")
  116. (setq custom-theme-name
  117. (widget-create 'editable-field
  118. :value (if (and theme (not (eq theme 'user)))
  119. (symbol-name theme)
  120. "")))
  121. (widget-insert "Description: ")
  122. (setq custom-theme-description
  123. (widget-create 'text
  124. :value (format-time-string "Created %Y-%m-%d.")))
  125. (widget-create 'push-button
  126. :notify (function custom-theme-write)
  127. " Save Theme ")
  128. (when (eq theme 'user)
  129. (setq custom-theme--migrate-settings t)
  130. (widget-insert " ")
  131. (widget-create 'checkbox
  132. :value custom-theme--migrate-settings
  133. :action (lambda (widget &optional event)
  134. (when (widget-value widget)
  135. (widget-toggle-action widget event)
  136. (setq custom-theme--migrate-settings
  137. (widget-value widget)))))
  138. (widget-insert (propertize " Remove saved theme settings from Custom save file."
  139. 'face '(variable-pitch (:height 0.9)))))
  140. (let (vars values faces face-specs)
  141. ;; Load the theme settings.
  142. (when theme
  143. (unless (eq theme 'user)
  144. (load-theme theme nil t))
  145. (dolist (setting (get theme 'theme-settings))
  146. (if (eq (car setting) 'theme-value)
  147. (progn (push (nth 1 setting) vars)
  148. (push (nth 3 setting) values))
  149. (push (nth 1 setting) faces)
  150. (push (nth 3 setting) face-specs))))
  151. ;; If THEME is non-nil, insert all of that theme's faces.
  152. ;; Otherwise, insert those in `custom-theme--listed-faces'.
  153. (widget-insert "\n\n Theme faces:\n ")
  154. (if theme
  155. (while faces
  156. (custom-theme-add-face-1 (pop faces) (pop face-specs)))
  157. (dolist (face custom-theme--listed-faces)
  158. (custom-theme-add-face-1 face nil)))
  159. (setq custom-theme-insert-face-marker (point-marker))
  160. (widget-insert " ")
  161. (widget-create 'push-button
  162. :tag "Insert Additional Face"
  163. :help-echo "Add another face to this theme."
  164. :follow-link 'mouse-face
  165. :button-face 'custom-link
  166. :mouse-face 'highlight
  167. :pressed-face 'highlight
  168. :action (lambda (_widget &optional _event)
  169. (call-interactively 'custom-theme-add-face)))
  170. ;; If THEME is non-nil, insert all of that theme's variables.
  171. (widget-insert "\n\n Theme variables:\n ")
  172. (if theme
  173. (while vars
  174. (if (eq (car vars) 'custom-enabled-themes)
  175. (progn (pop vars) (pop values))
  176. (custom-theme-add-var-1 (pop vars) (eval (pop values))))))
  177. (setq custom-theme-insert-variable-marker (point-marker))
  178. (widget-insert " ")
  179. (widget-create 'push-button
  180. :tag "Insert Variable"
  181. :help-echo "Add another variable to this theme."
  182. :follow-link 'mouse-face
  183. :button-face 'custom-link
  184. :mouse-face 'highlight
  185. :pressed-face 'highlight
  186. :action (lambda (_widget &optional _event)
  187. (call-interactively 'custom-theme-add-variable)))
  188. (widget-insert ?\n)
  189. (widget-setup)
  190. (goto-char (point-min))
  191. (message "")))
  192. (defun custom-theme-revert (_ignore-auto noconfirm)
  193. "Revert the current *Custom Theme* buffer.
  194. This is the `revert-buffer-function' for `custom-new-theme-mode'."
  195. (when (or noconfirm (y-or-n-p "Discard current changes? "))
  196. (customize-create-theme custom-theme--save-name (current-buffer))))
  197. ;;; Theme variables
  198. (defun custom-theme-add-variable (var value)
  199. "Add a widget for VAR (a symbol) to the *New Custom Theme* buffer.
  200. VALUE should be a value to which to set the widget; when called
  201. interactively, this defaults to the current value of VAR."
  202. (interactive
  203. (let ((v (read-variable "Variable name: ")))
  204. (list v (symbol-value v))))
  205. (let ((entry (assq var custom-theme-variables)))
  206. (cond ((null entry)
  207. ;; If VAR is not yet in the buffer, add it.
  208. (save-excursion
  209. (goto-char custom-theme-insert-variable-marker)
  210. (custom-theme-add-var-1 var value)
  211. (move-marker custom-theme-insert-variable-marker (point))
  212. (widget-setup)))
  213. ;; Otherwise, alter that var widget.
  214. (t
  215. (widget-value-set (nth 1 entry) t)
  216. (let ((widget (nth 2 entry)))
  217. (widget-put widget :shown-value (list value))
  218. (custom-redraw widget))))))
  219. (defun custom-theme-add-var-1 (symbol val)
  220. (widget-insert " ")
  221. (push (list symbol
  222. (prog1 (widget-create 'checkbox
  223. :value t
  224. :help-echo "Enable/disable this variable.")
  225. (widget-insert " "))
  226. (widget-create 'custom-variable
  227. :tag (custom-unlispify-tag-name symbol)
  228. :value symbol
  229. :shown-value (list val)
  230. :notify 'ignore
  231. :custom-level 0
  232. :custom-state 'hidden
  233. :custom-style 'simple))
  234. custom-theme-variables)
  235. (widget-insert " "))
  236. ;;; Theme faces
  237. (defun custom-theme-add-face (face &optional spec)
  238. "Add a widget for FACE (a symbol) to the *New Custom Theme* buffer.
  239. SPEC, if non-nil, should be a face spec to which to set the widget."
  240. (interactive (list (read-face-name "Face name" nil nil) nil))
  241. (unless (or (facep face) spec)
  242. (error "`%s' has no face definition" face))
  243. (let ((entry (assq face custom-theme-faces)))
  244. (cond ((null entry)
  245. ;; If FACE is not yet in the buffer, add it.
  246. (save-excursion
  247. (goto-char custom-theme-insert-face-marker)
  248. (custom-theme-add-face-1 face spec)
  249. (move-marker custom-theme-insert-face-marker (point))
  250. (widget-setup)))
  251. ;; Otherwise, if SPEC is supplied, alter that face widget.
  252. (spec
  253. (widget-value-set (nth 1 entry) t)
  254. (let ((widget (nth 2 entry)))
  255. (widget-put widget :shown-value spec)
  256. (custom-redraw widget)))
  257. ((called-interactively-p 'interactive)
  258. (error "`%s' is already present" face)))))
  259. (defun custom-theme-add-face-1 (symbol spec)
  260. (widget-insert " ")
  261. (push (list symbol
  262. (prog1
  263. (widget-create 'checkbox
  264. :value t
  265. :help-echo "Enable/disable this face.")
  266. (widget-insert " "))
  267. (widget-create 'custom-face
  268. :tag (custom-unlispify-tag-name symbol)
  269. :documentation-shown t
  270. :value symbol
  271. :custom-state 'hidden
  272. :custom-style 'simple
  273. :shown-value spec
  274. :sample-indent 34))
  275. custom-theme-faces)
  276. (widget-insert " "))
  277. ;;; Reading and writing
  278. ;;;###autoload
  279. (defun custom-theme-visit-theme (theme)
  280. "Set up a Custom buffer to edit custom theme THEME."
  281. (interactive
  282. (list
  283. (intern (completing-read "Find custom theme: "
  284. (mapcar 'symbol-name
  285. (custom-available-themes))))))
  286. (unless (custom-theme-name-valid-p theme)
  287. (error "No valid theme named `%s'" theme))
  288. (cond ((not (eq major-mode 'custom-new-theme-mode))
  289. (customize-create-theme theme))
  290. ((y-or-n-p "Discard current changes? ")
  291. (setq custom-theme--save-name theme)
  292. (custom-theme-revert nil t))))
  293. (defun custom-theme-merge-theme (theme)
  294. "Merge the custom theme THEME's settings into the current buffer."
  295. (interactive
  296. (list
  297. (intern (completing-read "Merge custom theme: "
  298. (mapcar 'symbol-name
  299. (custom-available-themes))))))
  300. (unless (eq theme 'user)
  301. (unless (custom-theme-name-valid-p theme)
  302. (error "Invalid theme name `%s'" theme))
  303. (load-theme theme nil t))
  304. (let ((settings (reverse (get theme 'theme-settings))))
  305. (dolist (setting settings)
  306. (let ((option (eq (car setting) 'theme-value))
  307. (name (nth 1 setting))
  308. (value (nth 3 setting)))
  309. (unless (and option
  310. (memq name '(custom-enabled-themes
  311. custom-safe-themes)))
  312. (funcall (if option
  313. 'custom-theme-add-variable
  314. 'custom-theme-add-face)
  315. name value)))))
  316. theme)
  317. ;; From cus-edit.el
  318. (defvar custom-reset-standard-faces-list)
  319. (defvar custom-reset-standard-variables-list)
  320. (defun custom-theme-write (&rest _ignore)
  321. "Write the current custom theme to its theme file."
  322. (interactive)
  323. (let* ((name (widget-value custom-theme-name))
  324. (doc (widget-value custom-theme-description))
  325. (vars custom-theme-variables)
  326. (faces custom-theme-faces)
  327. filename)
  328. (when (string-equal name "")
  329. (setq name (read-from-minibuffer "Theme name: " (user-login-name)))
  330. (widget-value-set custom-theme-name name))
  331. (unless (custom-theme-name-valid-p (intern name))
  332. (error "Custom themes cannot be named `%s'" name))
  333. (setq filename (expand-file-name (concat name "-theme.el")
  334. custom-theme-directory))
  335. (and (file-exists-p filename)
  336. (not (y-or-n-p (format "File %s exists. Overwrite? " filename)))
  337. (error "Aborted"))
  338. (with-temp-buffer
  339. (emacs-lisp-mode)
  340. (unless (file-directory-p custom-theme-directory)
  341. (make-directory (file-name-as-directory custom-theme-directory) t))
  342. (setq buffer-file-name filename)
  343. (erase-buffer)
  344. (insert "(deftheme " name)
  345. (if doc (insert "\n \"" doc "\""))
  346. (insert ")\n")
  347. (custom-theme-write-variables name (reverse vars))
  348. (custom-theme-write-faces name (reverse faces))
  349. (insert "\n(provide-theme '" name ")\n")
  350. (save-buffer))
  351. (message "Theme written to %s" filename)
  352. (when custom-theme--migrate-settings
  353. ;; Remove these settings from the Custom file.
  354. (let ((custom-reset-standard-variables-list '(t))
  355. (custom-reset-standard-faces-list '(t)))
  356. (dolist (var vars)
  357. (when (and (not (eq (car var) 'custom-enabled-themes))
  358. (widget-get (nth 1 var) :value))
  359. (widget-apply (nth 2 var) :custom-mark-to-reset-standard)))
  360. (dolist (face faces)
  361. (when (widget-get (nth 1 face) :value)
  362. (widget-apply (nth 2 face) :custom-mark-to-reset-standard)))
  363. (custom-save-all))
  364. (let ((custom-theme-load-path (list 'custom-theme-directory)))
  365. (load-theme (intern name))))))
  366. (defun custom-theme-write-variables (theme vars)
  367. "Write a `custom-theme-set-variables' command for THEME.
  368. It includes all variables in list VARS."
  369. (when vars
  370. (let ((standard-output (current-buffer)))
  371. (princ "\n(custom-theme-set-variables\n")
  372. (princ " '")
  373. (princ theme)
  374. (princ "\n")
  375. (dolist (spec vars)
  376. (when (widget-get (nth 1 spec) :value)
  377. (let* ((symbol (nth 0 spec))
  378. (widget (nth 2 spec))
  379. (child (car-safe (widget-get widget :children)))
  380. (value (if child
  381. (widget-value child)
  382. ;; Child is null if the widget is closed (hidden).
  383. (car (widget-get widget :shown-value)))))
  384. (when (boundp symbol)
  385. (unless (bolp)
  386. (princ "\n"))
  387. (princ " '(")
  388. (prin1 symbol)
  389. (princ " ")
  390. (prin1 (custom-quote value))
  391. (princ ")")))))
  392. (if (bolp)
  393. (princ " "))
  394. (princ ")")
  395. (unless (looking-at "\n")
  396. (princ "\n")))))
  397. (defun custom-theme-write-faces (theme faces)
  398. "Write a `custom-theme-set-faces' command for THEME.
  399. It includes all faces in list FACES."
  400. (when faces
  401. (let ((standard-output (current-buffer)))
  402. (princ "\n(custom-theme-set-faces\n")
  403. (princ " '")
  404. (princ theme)
  405. (princ "\n")
  406. (dolist (spec faces)
  407. ;; Insert the face iff the checkbox widget is checked.
  408. (when (widget-get (nth 1 spec) :value)
  409. (let* ((symbol (nth 0 spec))
  410. (widget (nth 2 spec))
  411. (value
  412. (cond
  413. ((car-safe (widget-get widget :children))
  414. (custom-face-widget-to-spec widget))
  415. ;; Child is null if the widget is closed (hidden).
  416. ((widget-get widget :shown-value))
  417. (t (custom-face-get-current-spec symbol)))))
  418. (when (and (facep symbol) value)
  419. (princ (if (bolp) " '(" "\n '("))
  420. (prin1 symbol)
  421. (princ " ")
  422. (prin1 value)
  423. (princ ")")))))
  424. (if (bolp) (princ " "))
  425. (princ ")")
  426. (unless (looking-at "\n")
  427. (princ "\n")))))
  428. ;;; Describing Custom themes.
  429. ;;;###autoload
  430. (defun describe-theme (theme)
  431. "Display a description of the Custom theme THEME (a symbol)."
  432. (interactive
  433. (list
  434. (intern (completing-read "Describe custom theme: "
  435. (mapcar 'symbol-name
  436. (custom-available-themes))))))
  437. (unless (custom-theme-name-valid-p theme)
  438. (error "Invalid theme name `%s'" theme))
  439. (help-setup-xref (list 'describe-theme theme)
  440. (called-interactively-p 'interactive))
  441. (with-help-window (help-buffer)
  442. (with-current-buffer standard-output
  443. (describe-theme-1 theme))))
  444. (defun describe-theme-1 (theme)
  445. (prin1 theme)
  446. (princ " is a custom theme")
  447. (let ((fn (locate-file (concat (symbol-name theme) "-theme.el")
  448. (custom-theme--load-path)
  449. '("" "c")))
  450. doc)
  451. (when fn
  452. (princ " in `")
  453. (help-insert-xref-button (file-name-nondirectory fn)
  454. 'help-theme-def fn)
  455. (princ "'"))
  456. (princ ".\n")
  457. (if (custom-theme-p theme)
  458. (progn
  459. (if (custom-theme-enabled-p theme)
  460. (princ "It is loaded and enabled.")
  461. (princ "It is loaded but disabled."))
  462. (setq doc (get theme 'theme-documentation)))
  463. (princ "It is not loaded.")
  464. ;; Attempt to grab the theme documentation
  465. (when fn
  466. (with-temp-buffer
  467. (insert-file-contents fn)
  468. (let ((sexp (let ((read-circle nil))
  469. (condition-case nil
  470. (read (current-buffer))
  471. (end-of-file nil)))))
  472. (and sexp (listp sexp)
  473. (eq (car sexp) 'deftheme)
  474. (setq doc (nth 2 sexp)))))))
  475. (princ "\n\nDocumentation:\n")
  476. (princ (if (stringp doc)
  477. doc
  478. "No documentation available.")))
  479. (princ "\n\nYou can ")
  480. (help-insert-xref-button "customize" 'help-theme-edit theme)
  481. (princ " this theme."))
  482. ;;; Theme chooser
  483. (defvar custom--listed-themes)
  484. (defcustom custom-theme-allow-multiple-selections nil
  485. "Whether to allow multi-selections in the *Custom Themes* buffer."
  486. :version "24.1"
  487. :type 'boolean
  488. :group 'custom-buffer)
  489. (defvar custom-theme-choose-mode-map
  490. (let ((map (make-keymap)))
  491. (set-keymap-parent map (make-composed-keymap widget-keymap
  492. special-mode-map))
  493. (suppress-keymap map)
  494. (define-key map "\C-x\C-s" 'custom-theme-save)
  495. (define-key map "n" 'widget-forward)
  496. (define-key map "p" 'widget-backward)
  497. (define-key map "?" 'custom-describe-theme)
  498. map)
  499. "Keymap for `custom-theme-choose-mode'.")
  500. (define-derived-mode custom-theme-choose-mode special-mode "Themes"
  501. "Major mode for selecting Custom themes.
  502. Do not call this mode function yourself. It is meant for internal use."
  503. (use-local-map custom-theme-choose-mode-map)
  504. (custom--initialize-widget-variables)
  505. (set (make-local-variable 'revert-buffer-function)
  506. (lambda (_ignore-auto noconfirm)
  507. (when (or noconfirm (y-or-n-p "Discard current choices? "))
  508. (customize-themes (current-buffer))))))
  509. (put 'custom-theme-choose-mode 'mode-class 'special)
  510. ;;;###autoload
  511. (defun customize-themes (&optional buffer)
  512. "Display a selectable list of Custom themes.
  513. When called from Lisp, BUFFER should be the buffer to use; if
  514. omitted, a buffer named *Custom Themes* is used."
  515. (interactive)
  516. (switch-to-buffer (get-buffer-create (or buffer "*Custom Themes*")))
  517. (let ((inhibit-read-only t))
  518. (erase-buffer))
  519. (custom-theme-choose-mode)
  520. (set (make-local-variable 'custom--listed-themes) nil)
  521. (make-local-variable 'custom-theme-allow-multiple-selections)
  522. (and (null custom-theme-allow-multiple-selections)
  523. (> (length custom-enabled-themes) 1)
  524. (setq custom-theme-allow-multiple-selections t))
  525. (widget-insert
  526. (substitute-command-keys
  527. "Type RET or click to enable/disable listed custom themes.
  528. Type \\[custom-describe-theme] to describe the theme at point.
  529. Theme files are named *-theme.el in `"))
  530. (widget-create 'link :value "custom-theme-load-path"
  531. :button-face 'custom-link
  532. :mouse-face 'highlight
  533. :pressed-face 'highlight
  534. :help-echo "Describe `custom-theme-load-path'."
  535. :keymap custom-mode-link-map
  536. :follow-link 'mouse-face
  537. :action (lambda (_widget &rest _ignore)
  538. (describe-variable 'custom-theme-load-path)))
  539. (widget-insert "'.\n\n")
  540. ;; If the user has made customizations, display a warning and
  541. ;; provide buttons to disable or convert them.
  542. (let ((user-settings (get 'user 'theme-settings)))
  543. (unless (or (null user-settings)
  544. (and (null (cdr user-settings))
  545. (eq (caar user-settings) 'theme-value)
  546. (eq (cadr (car user-settings)) 'custom-enabled-themes)))
  547. (widget-insert
  548. (propertize
  549. " Note: Your custom settings take precedence over theme settings.
  550. To migrate your settings into a theme, click "
  551. 'face 'font-lock-warning-face))
  552. (widget-create 'link :value "here"
  553. :button-face 'custom-link
  554. :mouse-face 'highlight
  555. :pressed-face 'highlight
  556. :help-echo "Migrate."
  557. :keymap custom-mode-link-map
  558. :follow-link 'mouse-face
  559. :action (lambda (_widget &rest _ignore)
  560. (customize-create-theme 'user)))
  561. (widget-insert ".\n\n")))
  562. (widget-create 'push-button
  563. :tag " Save Theme Settings "
  564. :help-echo "Save the selected themes for future sessions."
  565. :action 'custom-theme-save)
  566. (widget-insert ?\n)
  567. (widget-create 'checkbox
  568. :value custom-theme-allow-multiple-selections
  569. :action 'custom-theme-selections-toggle)
  570. (widget-insert (propertize " Select more than one theme at a time"
  571. 'face '(variable-pitch (:height 0.9))))
  572. (widget-insert "\n\nAvailable Custom Themes:\n")
  573. (let ((help-echo "mouse-2: Enable this theme for this session")
  574. widget)
  575. (dolist (theme (custom-available-themes))
  576. (setq widget (widget-create 'checkbox
  577. :value (custom-theme-enabled-p theme)
  578. :theme-name theme
  579. :help-echo help-echo
  580. :action 'custom-theme-checkbox-toggle))
  581. (push (cons theme widget) custom--listed-themes)
  582. (widget-create-child-and-convert widget 'push-button
  583. :button-face-get 'ignore
  584. :mouse-face-get 'ignore
  585. :value (format " %s" theme)
  586. :action 'widget-parent-action
  587. :help-echo help-echo)
  588. (widget-insert " -- "
  589. (propertize (custom-theme-summary theme)
  590. 'face 'shadow)
  591. ?\n)))
  592. (goto-char (point-min))
  593. (widget-setup))
  594. (defun custom-theme-summary (theme)
  595. "Return the summary line of THEME."
  596. (let (doc)
  597. (if (custom-theme-p theme)
  598. (setq doc (get theme 'theme-documentation))
  599. (let ((fn (locate-file (concat (symbol-name theme) "-theme.el")
  600. (custom-theme--load-path)
  601. '("" "c"))))
  602. (when fn
  603. (with-temp-buffer
  604. (insert-file-contents fn)
  605. (let ((sexp (let ((read-circle nil))
  606. (condition-case nil
  607. (read (current-buffer))
  608. (end-of-file nil)))))
  609. (and sexp (listp sexp)
  610. (eq (car sexp) 'deftheme)
  611. (setq doc (nth 2 sexp))))))))
  612. (cond ((null doc)
  613. "(no documentation available)")
  614. ((string-match ".*" doc)
  615. (match-string 0 doc))
  616. (t doc))))
  617. (defun custom-theme-checkbox-toggle (widget &optional event)
  618. (let ((this-theme (widget-get widget :theme-name)))
  619. (if (widget-value widget)
  620. ;; Disable the theme.
  621. (progn
  622. (disable-theme this-theme)
  623. (widget-toggle-action widget event))
  624. ;; Enable the theme.
  625. (unless custom-theme-allow-multiple-selections
  626. ;; If only one theme is allowed, disable all other themes and
  627. ;; uncheck their boxes.
  628. (dolist (theme custom-enabled-themes)
  629. (and (not (eq theme this-theme))
  630. (assq theme custom--listed-themes)
  631. (disable-theme theme)))
  632. (dolist (theme custom--listed-themes)
  633. (unless (eq (car theme) this-theme)
  634. (widget-value-set (cdr theme) nil)
  635. (widget-apply (cdr theme) :notify (cdr theme) event))))
  636. (when (load-theme this-theme)
  637. (widget-toggle-action widget event)))
  638. ;; Mark `custom-enabled-themes' as "set for current session".
  639. (put 'custom-enabled-themes 'customized-value
  640. (list (custom-quote custom-enabled-themes)))))
  641. (defun custom-describe-theme ()
  642. "Describe the Custom theme on the current line."
  643. (interactive)
  644. (let ((widget (widget-at (line-beginning-position))))
  645. (and widget
  646. (describe-theme (widget-get widget :theme-name)))))
  647. (defun custom-theme-save (&rest _ignore)
  648. (interactive)
  649. (customize-save-variable 'custom-enabled-themes custom-enabled-themes)
  650. (message "Custom themes saved for future sessions."))
  651. (defun custom-theme-selections-toggle (widget &optional event)
  652. (when (widget-value widget)
  653. ;; Deactivate multiple-selections.
  654. (if (< 1 (length (delq nil (mapcar (lambda (x) (widget-value (cdr x)))
  655. custom--listed-themes))))
  656. (error "More than one theme is currently selected")))
  657. (widget-toggle-action widget event)
  658. (setq custom-theme-allow-multiple-selections (widget-value widget)))
  659. (provide 'cus-theme)
  660. ;;; cus-theme.el ends here