1566 lines
70 KiB
EmacsLisp
1566 lines
70 KiB
EmacsLisp
;;; toolbar-x.el --- fancy toolbar handling in Emacs -*- lexical-binding: t; -*-
|
||
|
||
;; Copyright (C) 2004-2022 Free Software Foundation, Inc.
|
||
|
||
;; This program is free software; you can redistribute it and/or
|
||
;; modify it under the terms of the GNU General Public License as
|
||
;; published by the Free Software Foundation; either version 3 of
|
||
;; the License, or (at your option) any later version.
|
||
|
||
;; This program is distributed in the hope that it will be
|
||
;; useful, but WITHOUT ANY WARRANTY; without even the implied
|
||
;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||
;; PURPOSE. See the GNU General Public License for more details.
|
||
|
||
;; You should have received a copy of the GNU General Public
|
||
;; License along with this program; if not, write to the Free
|
||
;; Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
|
||
;; MA 02110-1301 USA
|
||
|
||
;;; Author: Miguel Vinicius Santini Frasson
|
||
|
||
;;; Commentary:
|
||
;; This program implements a common interface to display toolbar
|
||
;; buttons. A toolbar should be basically
|
||
;; defined by a image and a command to run when the button is pressed,
|
||
;; and additional properties could be added. This is the idea of this
|
||
;; program. See the documentation of function
|
||
;; `toolbarx-install-toolbar' for a description of how to specify
|
||
;; toolbars.
|
||
|
||
;;; Features:
|
||
|
||
;; * Button properties are given in the toolbar definition (BUTTON
|
||
;; paramenter in `toolbarx-install-toolbar') and/or in an alist with
|
||
;; associates the symbol with properties (MEANING-ALIST paramenter in
|
||
;; `toolbarx-install-toolbar').
|
||
|
||
;; * Supported properties:
|
||
;; - `:insert', `:image', `:command', `:help', `:enable',
|
||
;; `:append-command', `:prepend-command',
|
||
;; `:visible' and `:button';
|
||
;; For the precise value-type for each property, see documentation of
|
||
;; the function `toolbarx-install-toolbar'.
|
||
;; (ps: properties that are particular to an editor are just ignored
|
||
;; the other editor flavour.)
|
||
|
||
;; * Properties can have value specified by function (with no
|
||
;; argument) or variables that evaluate to an object of the correct
|
||
;; type for a particular property. The evaluation is done when the
|
||
;; roolbar is refresh (a call of `toolbarx-refresh'.)
|
||
;; (ps: this is valid only for properties that *not* have \`form\' as
|
||
;; value type.)
|
||
|
||
;; * On `refresh time' (a call `toolbarx-refresh', necessary when the
|
||
;; toolbar should change), the `:insert' property (if present) is
|
||
;; evaluated to decide if button will be displayed.
|
||
|
||
;; Properties can be distributed to several buttons, using \`groups\'.
|
||
;; Example: (foo (bar baz :enable (mytest)) :help "please")
|
||
;; means that `foo', `bar' and `baz' have `:help "please"' and `bar' and
|
||
;; `baz' have the property `:enable (mytest)'.
|
||
|
||
;; * (Part of) the toolbar definition can be stored in a variable,
|
||
;; evaluated in `installation time'. See `:eval-group' on the
|
||
;; documentation of the function `toolbarx-install-toolbar'.
|
||
|
||
;; * It is possible to define sets of buttons that appear according to
|
||
;; an option selected in a dropdown menu. See `:dropdown-group' on
|
||
;; the documentation of the function `toolbarx-install-toolbar'.
|
||
|
||
;;; Rough description of the implementation
|
||
;; There are 2 \`engines\' implemented:
|
||
|
||
;; == the 1st one (parsing) parses the toolbar definition
|
||
;; independently of editor flavour and store the parsed buttons with
|
||
;; their properties, in the same order that they appear in the
|
||
;; definitions, in a variable `toolbarx-internal-button-switches';
|
||
|
||
;; == the 2nd one (refresh for Emacs) inserts buttons in the Emacs
|
||
;; toolbar in the same order that they appear in the definitions;
|
||
;; if a (real) button does not have at least (valid) image
|
||
;; and command properties, they are silently ignored;
|
||
|
||
;;; History:
|
||
|
||
;; This program was motivated by the intention of implementation of a
|
||
;; good toolbar for AUCTeX, that would work in both Emacs and XEmacs.
|
||
;; Since toolbars were very different in behaviour and implementation
|
||
;; (for instance, in Emacs one can display as many toolbar buttons as
|
||
;; wanted, because it becomes mult-line, and in XEmacs, there is one
|
||
;; line, but toolbars and all sides of a frame.)
|
||
|
||
|
||
;;; Code:
|
||
|
||
(eval-when-compile
|
||
(require 'cl-lib))
|
||
|
||
;; Note that this just gives a useful default. Icons are expected to
|
||
;; be in subdirectory "images" or "toolbar" relative to the load-path.
|
||
;; Packages loading toolbarx are advised to explicitly add their own
|
||
;; searchpath with add-to-list here even when they fulfill that
|
||
;; criterion: another package might have loaded toolbar-x previously
|
||
;; when load-path was not yet correctly set. The default setting
|
||
;; really caters only for toolbar-x' stock icons.
|
||
|
||
(defvar toolbarx-image-path
|
||
(nconc
|
||
(delq nil (mapcar #'(lambda(x)
|
||
(and x
|
||
(member
|
||
(file-name-nondirectory
|
||
(directory-file-name x))
|
||
'("toolbar" "images"))
|
||
;;(file-directory-p x)
|
||
x))
|
||
load-path))
|
||
(list data-directory))
|
||
"List of directories where toolbarx finds its images.")
|
||
|
||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
;;; First engine: Parsing buttons
|
||
|
||
;; it obtains button information, process it and stores result in
|
||
;; `toolbarx-internal-button-switches', which is a list with 1st
|
||
;; element the symbol `:switches', the 2nd element as a list of
|
||
;; processed buttons, and the 3rd element is used for Emacs to store
|
||
;; the keys used in ``constant'' buttons.
|
||
|
||
;; The 2nd element of `toolbarx-internal-button-switches' is a list
|
||
;; where each element is either:
|
||
;; * a button-list, that is, a list with elements to define a button.
|
||
;; * a list where 1st elem is `:insert' and 2nd is a form, and the
|
||
;; following elements are in the same format of the 2nd element of
|
||
;; `toolbarx-internal-button-switches'.
|
||
|
||
(defun toolbarx-make-string-from-symbol (symbol)
|
||
"Return a string from the name of a SYMBOL.
|
||
Upcase initials and replace dashes by spaces."
|
||
(let* ((str (upcase-initials (symbol-name symbol)))
|
||
(str2))
|
||
(dolist (i (append str nil))
|
||
(if (eq i 45) ; if dash, push space
|
||
(push 32 str2)
|
||
(push i str2))) ; else push identical
|
||
(concat (nreverse str2))))
|
||
|
||
(defun toolbarx-make-symbol-from-string (string)
|
||
"Return a (intern) symbol from STRING.
|
||
Downcase string and replace spaces by dashes."
|
||
(let* ((str1 (append (downcase string) nil))
|
||
(str2))
|
||
(dolist (i str1)
|
||
(if (eq i 32) ; if dash, push space
|
||
(push 45 str2)
|
||
(push i str2)))
|
||
(intern (concat (nreverse str2)))))
|
||
|
||
(defun toolbarx-good-option-list-p (option-list valid-options)
|
||
"Non-nil means the OPTION-LIST is of form (OPT FORM ... OPT FORM).
|
||
Each OPT is member of VALID-OPTIONS and OPT are pairwise
|
||
different. OPTION-LIST equal to nil is a good option list."
|
||
(let ((elt-in-valid t)
|
||
(temp-opt-list option-list)
|
||
(list-diff)
|
||
(n (/ (length option-list) 2)))
|
||
(dotimes (i n)
|
||
(when (> i 0)
|
||
(setq temp-opt-list (cddr temp-opt-list)))
|
||
(cl-pushnew (car temp-opt-list) list-diff :test #'equal)
|
||
(setq elt-in-valid (and elt-in-valid
|
||
(memq (car temp-opt-list)
|
||
valid-options))))
|
||
(and elt-in-valid ; options are on VALID-OPTOPNS
|
||
;; OPTION-LIST has all option different from each other
|
||
(eq (length list-diff) n)
|
||
;; OPTION-LIST has even number of elements
|
||
(eq (% (length option-list) 2) 0))))
|
||
|
||
(defun toolbarx-separate-options (group-list valid-options &optional check)
|
||
"Return a cons cell with non-options and options of GROUP-LIST.
|
||
The options-part is the largest tail of the list GROUP-LIST that
|
||
has an element of VALID-OPTIONS (the comparation is made with
|
||
`memq'.) The non-options-part is the beginning of GROUP-LIST
|
||
less its tail. Return a cons cell which `car' is the
|
||
non-options-part and the `cdr' is the options-part.
|
||
|
||
If CHECK is non-nil, the tail is the largest that yield non-nil
|
||
when applied to `toolbarx-good-option-list-p'."
|
||
(let ((maximal)
|
||
(temp))
|
||
(dolist (i valid-options)
|
||
(setq temp (memq i group-list))
|
||
(when (and (> (length temp) (length maximal))
|
||
(if check
|
||
(toolbarx-good-option-list-p temp valid-options)
|
||
t))
|
||
(setq maximal (memq i group-list))))
|
||
(cons (butlast group-list (length maximal)) maximal)))
|
||
|
||
|
||
(defun toolbarx-merge-props (inner-props outer-props override add)
|
||
"Merge property lists INNER-PROPS and OUTER-PROPS.
|
||
INNER-PROPS and OUTER-PROPS are two lists in the format
|
||
(PROP VAL PROP VAL ... PROP VAL).
|
||
Returns a list with properties and values merged.
|
||
|
||
OVERRIDE and ADD are supposed to be lists of symbols. The value
|
||
of a property in OVERRIDE is the one on OUTER-PROPS or
|
||
INNER-PROPS, but if the property is in both, the value in
|
||
INNER-PROPS is used. The value of a property in ADD will be a
|
||
list with first element the symbol `:add-value-list' and the rest
|
||
are the properties, inner properties first."
|
||
(let* ((merged)
|
||
(inner-prop)
|
||
(outer-prop))
|
||
(dolist (prop override)
|
||
(if (memq prop inner-props)
|
||
(setq merged (append merged
|
||
(list prop (cadr (memq prop inner-props)))))
|
||
(when (memq prop outer-props)
|
||
(setq merged (append merged
|
||
(list prop (cadr (memq prop outer-props))))))))
|
||
(dolist (prop add merged)
|
||
(setq inner-prop (memq prop inner-props))
|
||
(when inner-prop
|
||
(if (and (listp (cadr inner-prop))
|
||
(eq (car (cadr inner-prop)) :add-value-list))
|
||
(setq inner-prop (cdr (cadr inner-prop)))
|
||
(setq inner-prop (list (cadr inner-prop)))))
|
||
(setq outer-prop (memq prop outer-props))
|
||
(when outer-prop
|
||
(if (and (listp (cadr outer-prop))
|
||
(eq (car (cadr outer-prop)) :add-value-list))
|
||
(setq outer-prop (cdr (cadr outer-prop)))
|
||
(setq outer-prop (list (cadr outer-prop)))))
|
||
(when (append inner-prop outer-prop)
|
||
(setq merged (append merged
|
||
(list prop (cons :add-value-list
|
||
(append inner-prop
|
||
outer-prop)))))))))
|
||
|
||
(defun toolbarx-make-command (comm prep app)
|
||
"Return a command made from COMM, PREP and APP.
|
||
COMM is a command or a form. PREP and APP are forms. If PREP or
|
||
APP are non-nil, they are added to the resulting command at the
|
||
beginning and end, respectively. If both are nil and COMM is a
|
||
command, COMM is returned."
|
||
(let ((comm-is-command (commandp comm)))
|
||
(if (and (not prep)
|
||
(not app)
|
||
comm-is-command)
|
||
comm
|
||
(lambda () (interactive)
|
||
(let (result)
|
||
(when prep (setq result (eval prep t)))
|
||
(when comm (setq result
|
||
(if comm-is-command
|
||
(call-interactively comm)
|
||
(eval comm t))))
|
||
(when app (setq result (eval app t)))
|
||
result)))))
|
||
|
||
(defun toolbarx-emacs-mount-popup-menu
|
||
(strings var type &optional title save)
|
||
"Return an interactive `lambda'-expression that shows a popup menu.
|
||
This function is the action of `toolbarx-mount-popup-menu' if
|
||
inside Emacs. See documentation of that function for more."
|
||
;; making the menu keymap by adding each menu-item definition
|
||
;; see (info "(elisp)Menu keymaps")
|
||
(let* ((keymap (make-sparse-keymap title))
|
||
(count 1)
|
||
(used-symbols '(nil))
|
||
(key)
|
||
(real-type
|
||
(pcase type
|
||
((or `toggle `radio) type)
|
||
;; Warn if type is not `radio' or `toggle'.
|
||
(_ (display-warning 'toolbarx
|
||
(format "TYPE should be symbols `radio' or `toggle', but %s found; using `radio'"
|
||
type))
|
||
;; Use `radio' if incorrect.
|
||
'radio)))
|
||
(real-save
|
||
(pcase save
|
||
((or `nil `offer `always) save)
|
||
;; Warn if save is not `nil', `offer' or ;; `always'.
|
||
(_ (display-warning 'toolbarx
|
||
(format "SAVE should be symbols `nil', `offer' or `always', but %s found; using `nil'"
|
||
save))
|
||
;; Use nil when incorrect.
|
||
nil))))
|
||
(dolist (i strings)
|
||
;; finding a new symbol
|
||
(let* ((aux-count 0)
|
||
(i-symb (toolbarx-make-symbol-from-string i)))
|
||
(setq key i-symb)
|
||
(while (memq key used-symbols)
|
||
(setq aux-count (1+ aux-count))
|
||
(setq key (intern (format "%s-%d" i-symb aux-count))))
|
||
(setq used-symbols (cons key used-symbols)))
|
||
(define-key-after keymap (vector key)
|
||
`(menu-item ,i
|
||
,(let ((count count))
|
||
(lambda () (interactive)
|
||
(set var
|
||
(if (eq real-type 'radio)
|
||
count
|
||
(if (memq count (symbol-value var))
|
||
(delete count (symbol-value var))
|
||
(sort (cons count (symbol-value var)) #'<))))
|
||
(toolbarx-refresh)
|
||
(when (eq real-save 'always)
|
||
(customize-save-variable var (symbol-value var)))
|
||
(symbol-value var)))
|
||
:button ,(if (eq real-type 'radio)
|
||
`(:radio eq ,var ,count)
|
||
`(:toggle memq ,count ,var))))
|
||
(setq count (1+ count)))
|
||
(when (eq real-save 'offer)
|
||
(define-key-after keymap [sep] '(menu-item "--shadow-etched-in-dash"))
|
||
(let* ((aux-count 0)
|
||
(i-symb 'custom-save))
|
||
(setq key i-symb)
|
||
(while (memq key used-symbols)
|
||
(setq aux-count (1+ aux-count))
|
||
(setq key (intern (format "%s-%d" i-symb aux-count))))
|
||
(setq used-symbols (cons key used-symbols)))
|
||
(define-key-after keymap (vector key)
|
||
`(menu-item "Save state of this menu"
|
||
(lambda nil (interactive)
|
||
(customize-save-variable (quote ,var) ,var)))))
|
||
;; returns a `lambda'-expression
|
||
(lambda () (interactive) (popup-menu keymap))))
|
||
|
||
(defun toolbarx-mount-popup-menu (strings var type &optional title save)
|
||
"Return a command that show a popup menu.
|
||
The return is a `lambda'-expression with a interactive declaration.
|
||
|
||
STRINGS is a list of strings which will be the itens of the menu.
|
||
|
||
VAR is a symbol that is set when an item is clicked. TYPE should
|
||
be one of the symbols `radio' or `toggle': `radio' means that the
|
||
nth item is selected if VAR is `n' and this item sets VAR to `n';
|
||
`toggle' means that VAR should be a list of integers and the nth
|
||
item is selected if `n' belongs to VAR. The item inserts or
|
||
deletes `n' from VAR.
|
||
|
||
TITLE is a string (the title of the popup menu) or nil for no
|
||
title.
|
||
|
||
SAVE is one of the symbols nil, `offer' or `always'. If value
|
||
is nil, do not try to save anything. If it is `offer', a menu
|
||
item is added offering the user the possibiity to save state of
|
||
that dropdown menu for future sesseions (using `custom'). If it
|
||
is `always', state is saved every time that a item is clicked."
|
||
(toolbarx-emacs-mount-popup-menu strings var type title save))
|
||
|
||
(defun toolbarx-option-value (opt)
|
||
"If OPT is a vector, return first element, otherwise, return OPT.
|
||
If OPT is vector and length is smaller than the necessary, then
|
||
nil is returned."
|
||
;; FIXME: This is backward compatibility for when we supported XEmacs
|
||
;; and entries could take the shape [FOO BAR] where FOO was the
|
||
;; value to use for Emacs and BAR the value to use for XEmacs.
|
||
;; This is unused since Mar 2021.
|
||
(if (vectorp opt)
|
||
(when (> (length opt) 0)
|
||
(aref opt 0))
|
||
opt))
|
||
|
||
(defun toolbarx-eval-function-or-symbol (object type-test-func)
|
||
"Return a cons cell (GOOD-OBJ . VAL).
|
||
GOOD-OBJ non-nil means that VAL is a valid value, according to
|
||
the car of the result of TYPE-TEST-FUNCTION, that should return a
|
||
cons cell in the same format as the return of this function.
|
||
|
||
If OBJECT applied to TYPE-TEST-FUNC return (GOOD-OBJ . VAL), and
|
||
GOOD-OBJ is non-nil, return that. Else, check if OBJECT is a
|
||
function. If so, evaluate and test again with TYPE-TEST-FUNC. If
|
||
not a function or if GOOD-OBJ is again nil, test if OBJECT is a
|
||
bound symbol, evaluate that and return the result of
|
||
TYPE-TEST-FUNC."
|
||
(let* ((ret (funcall type-test-func object)))
|
||
(unless (car ret)
|
||
(if (functionp object)
|
||
(progn
|
||
(setq ret (funcall type-test-func (funcall object)))
|
||
(unless (car ret)
|
||
(when (and (symbolp object) (boundp object))
|
||
(setq ret (funcall type-test-func (symbol-value object))))))
|
||
;; ok, obj is not function; try symbol
|
||
(when (and (symbolp object) (boundp object))
|
||
(setq ret (funcall type-test-func (symbol-value object))))))
|
||
ret))
|
||
|
||
(defun toolbarx-test-image-type (obj)
|
||
"Return a cons cell (GOOD-OBJ . VAL).
|
||
GOOD-OBJ is non-nil if OBJ yields a valid image object VAL (see
|
||
documentation of function `toolbarx-process-symbol')."
|
||
(let ((toolbarx-test-image-type-simple
|
||
(lambda (img)
|
||
(let* ((val (toolbarx-option-value img))
|
||
(all-obj-ok t)
|
||
(good-obj
|
||
(or (stringp val) ; string
|
||
(eq (car-safe val) 'image) ; or image descriptor
|
||
(and (symbolp val) ; or a symbol bound to a
|
||
(boundp val) ; image descriptor
|
||
; (defined with `defimage')
|
||
(consp (symbol-value val))
|
||
(eq (car (symbol-value val)) 'image))
|
||
(and (listp val) ; or list with 4 strings or
|
||
; image descriptors
|
||
(= (length val) 4)
|
||
(dolist (i val all-obj-ok)
|
||
(setq all-obj-ok
|
||
(and all-obj-ok
|
||
(or (stringp i)
|
||
(eq (car-safe i) 'image)))))))))
|
||
(cons good-obj val)))))
|
||
(toolbarx-eval-function-or-symbol obj toolbarx-test-image-type-simple)))
|
||
|
||
(defun toolbarx-test-button-type (obj)
|
||
"Return a cons cell (GOOD-OBJ . VAL).
|
||
GOOD-OBJ is non-nil if OBJ yields a valid button object VAL (see
|
||
documentation of function `toolbarx-process-symbol')."
|
||
(let ((toolbarx-test-button-type-simple
|
||
(lambda (but)
|
||
(let* ((val (toolbarx-option-value but))
|
||
(good-obj
|
||
(and (consp val)
|
||
(memq (car val) '(:toggle :radio)))))
|
||
(cons good-obj val)))))
|
||
(toolbarx-eval-function-or-symbol obj toolbarx-test-button-type-simple)))
|
||
|
||
(defun toolbarx-test-any-type (obj)
|
||
"Return a cons cell (t . VAL).
|
||
If OBJ is vector, return VAL according to editor. Else, return
|
||
OBJ, because it is a form anyway."
|
||
(cons t (toolbarx-option-value obj)))
|
||
|
||
(defun toolbarx-test-string-or-nil (obj)
|
||
"Return a cons cell (GOOD-OBJ . VAL).
|
||
GOOD-OBJ is non-nil if OBJ yields a valid help object VAL (see
|
||
documentation of function `toolbarx-process-symbol')."
|
||
(let ((toolbarx-test-string-or-nil-simple
|
||
(lambda (obj)
|
||
(let* ((val (toolbarx-option-value obj))
|
||
(good-obj (or (stringp val)
|
||
(not val))))
|
||
(cons good-obj val)))))
|
||
(toolbarx-eval-function-or-symbol obj toolbarx-test-string-or-nil-simple)))
|
||
|
||
(defun toolbarx-test-toolbar-type (obj)
|
||
"Return a cons cell (GOOD-OBJ . VAL).
|
||
GOOD-OBJ is non-nil if OBJ yields a valid toolbar property object
|
||
VAL (see documentation of function `toolbarx-process-symbol')."
|
||
(let ((toolbarx-test-toolbar-type-simple
|
||
(lambda (obj)
|
||
(let* ((val (toolbarx-option-value obj))
|
||
;; (all-but-def-opts '(top bottom left right))
|
||
;; (all-opts '(default top bottom left right))
|
||
;; (good-obj t)
|
||
)
|
||
(cons t val)))))
|
||
(toolbarx-eval-function-or-symbol obj toolbarx-test-toolbar-type-simple)))
|
||
|
||
(defun toolbarx-test-dropdown-type (obj)
|
||
"Return a cons cell (GOOD-OBJ . VAL).
|
||
GOOD-OBJ is non-nil if OBJ yields a valid `:type' property object
|
||
VAL of a dropdown group (see documentation of function
|
||
`toolbarx-process-dropdown-group'."
|
||
(let ((toolbarx-test-dropdown-type-simple
|
||
(lambda (obj)
|
||
(let* ((val (toolbarx-option-value obj))
|
||
(good-obj (memq val '(radio toggle))))
|
||
(cons good-obj val)))))
|
||
(toolbarx-eval-function-or-symbol obj toolbarx-test-dropdown-type-simple)))
|
||
|
||
(defun toolbarx-test-symbol (obj)
|
||
"Return a cons cell (GOOD-OBJ . VAL).
|
||
GOOD-OBJ is non-nil if OBJ yields a valid `:variable' property
|
||
object VAL of a dropdown group (see documentation of function
|
||
`toolbarx-process-dropdown-group'."
|
||
(let ((toolbarx-test-symbol-simple
|
||
(lambda (obj)
|
||
(let* ((val (toolbarx-option-value obj))
|
||
(good-obj (symbolp val)))
|
||
(cons good-obj val)))))
|
||
(toolbarx-eval-function-or-symbol obj toolbarx-test-symbol-simple)))
|
||
|
||
(defun toolbarx-test-dropdown-default (obj)
|
||
"Return a cons cell (GOOD-OBJ . VAL).
|
||
GOOD-OBJ is non-nil if OBJ yields a valid `:default' property
|
||
object VAL of a dropdown group (see documentation of function
|
||
`toolbarx-process-dropdown-group'."
|
||
(let ((toolbarx-test-dropdown-default-simple
|
||
(lambda (obj)
|
||
(let* ((val (toolbarx-option-value obj))
|
||
(good-obj (or (integerp val)
|
||
(and (listp val)
|
||
(let ((ok t))
|
||
(dolist (i val ok)
|
||
(setq ok (and ok (integerp i)))))))))
|
||
(cons good-obj val)))))
|
||
(toolbarx-eval-function-or-symbol obj
|
||
toolbarx-test-dropdown-default-simple)))
|
||
|
||
(defun toolbarx-test-dropdown-save (obj)
|
||
"Return a cons cell (GOOD-OBJ . VAL).
|
||
GOOD-OBJ is non-nil if OBJ yields a valid `:save' property
|
||
object VAL of a dropdown group (see documentation of function
|
||
`toolbarx-process-dropdown-group'."
|
||
(let ((toolbarx-test-dropdown-save-simple
|
||
(lambda (obj)
|
||
(let* ((val (toolbarx-option-value obj))
|
||
(good-obj (memq val '(nil offer always))))
|
||
(cons good-obj val)))))
|
||
(toolbarx-eval-function-or-symbol obj toolbarx-test-dropdown-save-simple)))
|
||
|
||
(defconst toolbarx-button-props
|
||
(let* ((props-types-alist
|
||
'((:image toolbarx-test-image-type)
|
||
(:command toolbarx-test-any-type)
|
||
(:enable toolbarx-test-any-type)
|
||
(:visible toolbarx-test-any-type)
|
||
(:help toolbarx-test-string-or-nil)
|
||
(:insert toolbarx-test-any-type . and)
|
||
;; (:toolbar toolbarx-test-toolbar-type)
|
||
(:button toolbarx-test-button-type)
|
||
(:append-command toolbarx-test-any-type . progn)
|
||
(:prepend-command toolbarx-test-any-type . progn)))
|
||
(possible-props (nreverse (let* ((props ()))
|
||
(dolist (p props-types-alist props)
|
||
(setq props (cons (car p) props))))))
|
||
(props-override (nreverse (let* ((props ()))
|
||
(dolist (p props-types-alist props)
|
||
(unless (cddr p)
|
||
(setq props (cons (car p) props)))))))
|
||
(props-add (nreverse (let* ((props ()))
|
||
(dolist (p props-types-alist props)
|
||
(when (cddr p)
|
||
(setq props (cons (car p) props))))))))
|
||
(list props-types-alist possible-props props-override props-add))
|
||
"List yielding all encarnations of properties of a button.
|
||
First element: alist, where each element is of form
|
||
(PROP . (TYPE-TEST-FUNCTION . ADD-OR-NIL))
|
||
Second is a list with all properties.
|
||
Third, a list with properties that override when merging.
|
||
Fourth, a list of lists, each in the format (PROP ADD).")
|
||
|
||
(defconst toolbarx-dropdown-props
|
||
;; for naming dropdown properties see `Convention' in the doc string
|
||
(let* ((props-types-alist
|
||
'((:type toolbarx-test-dropdown-type)
|
||
(:variable toolbarx-test-symbol)
|
||
(:default toolbarx-test-dropdown-default)
|
||
(:save toolbarx-test-dropdown-save)
|
||
(:title toolbarx-test-string-or-nil)
|
||
(:dropdown-image toolbarx-test-image-type)
|
||
(:dropdown-enable toolbarx-test-any-type)
|
||
(:dropdown-visible toolbarx-test-any-type)
|
||
(:dropdown-insert toolbarx-test-any-type . and)
|
||
(:dropdown-help toolbarx-test-string-or-nil)
|
||
;; (:dropdown-toolbar toolbarx-test-toolbar-type)
|
||
(:dropdown-append-command toolbarx-test-any-type . progn)
|
||
(:dropdown-prepend-command toolbarx-test-any-type . progn)))
|
||
(possible-props (nreverse (let* ((props ()))
|
||
(dolist (p props-types-alist props)
|
||
(setq props (cons (car p) props))))))
|
||
(props-override (nreverse (let* ((props ()))
|
||
(dolist (p props-types-alist props)
|
||
(unless (cddr p)
|
||
(setq props (cons (car p) props)))))))
|
||
(props-add (nreverse (let* ((props ()))
|
||
(dolist (p props-types-alist props)
|
||
(when (cddr p)
|
||
(setq props (cons (car p) props))))))))
|
||
(list props-types-alist possible-props props-override props-add))
|
||
"List yielding all encarnations of properties of a dropdown group.
|
||
First element: alist, where each element is of form
|
||
(PROP . (TYPE-TEST-FUNCTION . ADD-OR-NIL))
|
||
Second is a list with all properties.
|
||
Third, a list with properties that override when merging.
|
||
Fourth, a list of lists, each in the format (PROP ADD).
|
||
|
||
Convention: properties for the dropdown button should be formed
|
||
with the strings \":dropdown-\" with the button property name
|
||
without `:'. This is used on the implementation.")
|
||
|
||
(defun toolbarx-process-group-without-insert (group-without-props
|
||
merged-props-without-insert
|
||
meaning-alist switches)
|
||
"Return an updated version of SWITCHES.
|
||
GROUP-WITHOUT-PROPS and MERGED-PROPS-WITHOUT-INSERT are
|
||
preprocessed variables in `toolbarx-process-group'."
|
||
(let ((current-switches switches))
|
||
(dolist (i group-without-props current-switches)
|
||
(setq i (toolbarx-option-value i))
|
||
(if (symbolp i)
|
||
(setq current-switches
|
||
(toolbarx-process-symbol i meaning-alist
|
||
merged-props-without-insert
|
||
current-switches))
|
||
(when (listp i)
|
||
(setq current-switches
|
||
(toolbarx-process-group i meaning-alist
|
||
merged-props-without-insert
|
||
current-switches)))))))
|
||
|
||
(defun toolbarx-process-group (group meaning-alist props switches)
|
||
"Return an updated version of SWITCHES.
|
||
Append to already processed buttons (stored in SWITCHES) a
|
||
processed version of GROUP. Groups are useful to distribute
|
||
properties. External properties are given in PROPS, and merged
|
||
with the internal properties that are in the end of GROUP. If
|
||
properties (after merge) contain a `:insert' property, return a
|
||
list where the first and second elements are `:insert' and its
|
||
value, and after that a list in the same format as SWITCHES."
|
||
(cond
|
||
;; if DROPDOWN group
|
||
((eq (car group) :dropdown-group)
|
||
(toolbarx-process-dropdown-group group meaning-alist props switches))
|
||
;; if EVAL group
|
||
((eq (car group) :eval-group)
|
||
(let ((current-switches switches))
|
||
(dolist (elt (cdr group) current-switches)
|
||
(let ((eval-elt (eval elt t)))
|
||
(setq current-switches
|
||
(toolbarx-process-group (if (listp eval-elt)
|
||
eval-elt
|
||
(list eval-elt))
|
||
meaning-alist props
|
||
current-switches))))))
|
||
;; if normal group
|
||
(t
|
||
(let* ((splited-props
|
||
(toolbarx-separate-options
|
||
group (append (nth 1 toolbarx-button-props)
|
||
(nth 1 toolbarx-dropdown-props))))
|
||
(intern-props (cdr splited-props))
|
||
(group-without-props (car splited-props))
|
||
(merged-props
|
||
(toolbarx-merge-props intern-props props
|
||
(append (nth 2 toolbarx-button-props)
|
||
(nth 2 toolbarx-dropdown-props))
|
||
(append (nth 3 toolbarx-button-props)
|
||
(nth 3 toolbarx-dropdown-props)))))
|
||
;; check whether merged props have an `:insert'
|
||
(if (memq :insert merged-props)
|
||
;; if yes, prepend switches with a (:insert cond elements)
|
||
(let* ((memq-ins (memq :insert merged-props))
|
||
(ins-val (if (and (listp (cadr memq-ins))
|
||
(eq :add-value-list
|
||
(car (cadr memq-ins))))
|
||
;; if property is add-value property
|
||
(let* ((p (assq
|
||
:insert
|
||
(nth 0 toolbarx-button-props)))
|
||
(add-list (list (cddr p)))
|
||
(prop-good-val))
|
||
(dolist (val (cdr (cadr memq-ins)))
|
||
(setq prop-good-val (funcall (cadr p) val))
|
||
(when (car prop-good-val)
|
||
(setq add-list (cons (cdr prop-good-val)
|
||
add-list))))
|
||
;; return: (nreverse add-list)
|
||
(setq add-list (nreverse add-list))
|
||
(if (eq 2 (length add-list))
|
||
(cadr add-list) ; just 1 value, no
|
||
add-list)) ; add-function
|
||
;; if property is not add-value
|
||
(cadr memq-ins)))
|
||
(merged-props-without-insert
|
||
(append (butlast merged-props (length memq-ins))
|
||
(cddr memq-ins)))
|
||
(group-switches
|
||
(toolbarx-process-group-without-insert
|
||
group-without-props merged-props-without-insert
|
||
meaning-alist nil)))
|
||
;; return
|
||
(nreverse (cons (append (list :insert ins-val)
|
||
group-switches)
|
||
(nreverse switches))))
|
||
;; if not, just append what is processed to switches
|
||
(toolbarx-process-group-without-insert group-without-props
|
||
merged-props meaning-alist
|
||
switches))))))
|
||
|
||
(defun toolbarx-process-symbol (symbol meaning-alist props switches)
|
||
"Process a button given by SYMBOL in MEANING-ALIST.
|
||
The processed button is appended in SWITCHES, which is returned.
|
||
Look for a association of SYMBOL in MEANING-ALIST for collecting
|
||
properties. Such association is a list that represents either a
|
||
normal button (a description of the button) or an alias
|
||
group (the symbol is an alias for a group of buttons). PROPS is
|
||
a externel list of properties that are merged and then applied to
|
||
the button. Scope is given by GLOBAL-FLAG."
|
||
;; there are 3 situations: symbol is :new-line, there is an alias group
|
||
;; or a normal button
|
||
(let ((button-assq (cdr (assq symbol meaning-alist))))
|
||
(cond
|
||
((eq (car button-assq) :alias)
|
||
;; button association is ALIAS GROUP is passed to
|
||
;; `toolbarx-process-group' as is but without the car.
|
||
;; return: (toolbarx-process-group... returns updates switch
|
||
(toolbarx-process-group (cdr button-assq) meaning-alist props switches))
|
||
(t
|
||
;; NORMAL BUTTON (association is a list of properties)
|
||
;;
|
||
;; properties need to be processed, that is, merge internal
|
||
;; and external (given by PROPS) properties
|
||
(let* (;; button properties defined in `toolbarx-button-props'
|
||
(props-override (nth 2 toolbarx-button-props))
|
||
(props-add (nth 3 toolbarx-button-props))
|
||
;; split considering also dropdown-group properties
|
||
(button-assq-split
|
||
(toolbarx-separate-options
|
||
button-assq
|
||
(append (nth 1 toolbarx-button-props)
|
||
(nth 1 toolbarx-dropdown-props))))
|
||
(button-split-no-props (car button-assq-split))
|
||
(button-split-props (cdr button-assq-split))
|
||
;; if there is no :image or :command in the props,
|
||
;; try to get them from no-props part
|
||
(button-image-no-prop
|
||
(unless (memq :image button-split-props)
|
||
(when (> (length button-split-no-props) 0)
|
||
(list :image (nth 0 button-split-no-props)))))
|
||
(button-command-no-prop
|
||
(unless (memq :command button-split-props)
|
||
(when (> (length button-split-no-props) 1)
|
||
(list :command (nth 1 button-split-no-props)))))
|
||
(button-props (append button-split-props
|
||
button-image-no-prop
|
||
button-command-no-prop))
|
||
;; merge props
|
||
(merged-props (toolbarx-merge-props button-props props
|
||
props-override
|
||
props-add)))
|
||
;; return:
|
||
(nreverse (cons (cons symbol merged-props) (nreverse switches))))))))
|
||
|
||
(defun toolbarx-process-dropdown-group (dropdown meaning-alist props switches)
|
||
"Process buttons that appear according to dropdown menu.
|
||
Process a dropdown group DROPDOWN with meaning alist
|
||
MEANING-ALIST, external property list PROP and GLOBAL-FLAG
|
||
specifying scope. For a complete description, see documentation
|
||
of `toolbarx-install-toolbar'. The processed buttons are stored
|
||
in the end of SWITCHES, which is returned."
|
||
(let* ((dropdown-group (if (eq (car dropdown) :dropdown-group)
|
||
(cdr dropdown)
|
||
dropdown))
|
||
(dropdown-list-splited
|
||
(toolbarx-separate-options dropdown-group
|
||
(append
|
||
(nth 1 toolbarx-button-props)
|
||
(nth 1 toolbarx-dropdown-props))))
|
||
(dropdown-list (car dropdown-list-splited))
|
||
(dropdown-props (cdr dropdown-list-splited))
|
||
(merged-props
|
||
(toolbarx-merge-props dropdown-props props
|
||
(append (nth 2 toolbarx-button-props)
|
||
(nth 2 toolbarx-dropdown-props))
|
||
(append (nth 3 toolbarx-button-props)
|
||
(nth 3 toolbarx-dropdown-props))))
|
||
(merged-props-button-only
|
||
(let* ((props-button-only)
|
||
(prop))
|
||
(dolist (p (nth 1 toolbarx-button-props) props-button-only)
|
||
(setq prop (memq p merged-props))
|
||
(when prop
|
||
(setq props-button-only
|
||
(append (list p (cadr prop))
|
||
props-button-only))))))
|
||
(merged-props-dropdown-only
|
||
(let* ((props-dropdown-only)
|
||
(prop))
|
||
(dolist (p (nth 1 toolbarx-dropdown-props) props-dropdown-only)
|
||
(setq prop (memq p merged-props))
|
||
(when prop
|
||
(setq props-dropdown-only
|
||
(append (list p (cadr prop))
|
||
props-dropdown-only))))))
|
||
;; get value for each property and check type ONLY for props that do
|
||
;; not concern the dropdown button, like `:type', `:save', etc. The
|
||
;; props that concern the button are going to be handled in refresh
|
||
;; time.
|
||
(filtered-dropdown-group-props-only
|
||
(let* ((filtered-props-temp)
|
||
(prop-good-val)
|
||
(prop))
|
||
(save-match-data
|
||
(dolist (p (nth 0 toolbarx-dropdown-props) filtered-props-temp)
|
||
(unless (string-match "^:dropdown-.*$"
|
||
(symbol-name (car p)))
|
||
;; property -> (car p)
|
||
;; test type function -> (cadr p)
|
||
(setq prop (memq (car p) merged-props-dropdown-only))
|
||
;; if so, check if value is of correct type
|
||
(when prop
|
||
(setq prop-good-val (funcall (cadr p) (cadr prop)))
|
||
(if (car prop-good-val)
|
||
(setq filtered-props-temp
|
||
(append filtered-props-temp
|
||
(list (car p) (cdr prop-good-val))))
|
||
(display-warning
|
||
'toolbarx
|
||
(format (concat "Wrong type for value in "
|
||
"property `%s' in dropdown group")
|
||
(car p))))))))))
|
||
;; properties for the dropdown button from dropdown merged properties
|
||
(dropdown-button-props
|
||
(let* ((props))
|
||
(save-match-data
|
||
(dolist (pr (nth 1 toolbarx-dropdown-props))
|
||
(when (and (memq pr merged-props-dropdown-only)
|
||
(string-match "^:dropdown-\\(.*\\)$"
|
||
(symbol-name pr)))
|
||
(let* ((new-pr (intern (concat ":"
|
||
(substring (symbol-name pr)
|
||
(match-beginning 1)
|
||
(match-end 1)))))
|
||
(val (cadr (memq pr merged-props-dropdown-only))))
|
||
(setq props (append (list new-pr val) props))))))
|
||
(unless (memq :image props)
|
||
(setq props (append (list :image "dropdown") props)))
|
||
props))
|
||
(dropdown-button-without-command
|
||
(cons 'dropdown dropdown-button-props))
|
||
;; `:type' defaults to `radio'
|
||
(type (if (memq :type filtered-dropdown-group-props-only)
|
||
(cadr (memq :type filtered-dropdown-group-props-only))
|
||
'radio))
|
||
;; `:default' defaults to 1 or nil depending on `type'
|
||
;; if type is toggle and default is not a list, but a
|
||
;; integer, set as the list with integer
|
||
(default
|
||
(let* ((memq-default (memq :default
|
||
filtered-dropdown-group-props-only))
|
||
(def-temp (cadr memq-default))
|
||
(default-temp (if memq-default
|
||
def-temp
|
||
(if (eq type 'radio) 1 (list 1)))))
|
||
default-temp))
|
||
;; `:save' defaults to nil and require `:variable'
|
||
(save (let* ((save-temp
|
||
(when (memq :save filtered-dropdown-group-props-only)
|
||
(cadr (memq :save
|
||
filtered-dropdown-group-props-only)))))
|
||
(if (and save-temp
|
||
(not (memq :variable
|
||
filtered-dropdown-group-props-only)))
|
||
(progn
|
||
(display-warning
|
||
'toolbarx
|
||
(concat "`:save' property with non-nil value should "
|
||
"be used only with the `:variable' property; "
|
||
"using value nil for `:save'."))
|
||
nil)
|
||
save-temp)))
|
||
;; `:title' defaults to nil
|
||
(title (when (memq :title filtered-dropdown-group-props-only)
|
||
(cadr (memq :title filtered-dropdown-group-props-only))))
|
||
;; the menu variable is buildt from the `:variable' option or
|
||
;; make a symbol not used
|
||
(variable (if (memq :variable filtered-dropdown-group-props-only)
|
||
(cadr (memq :variable
|
||
filtered-dropdown-group-props-only))
|
||
(let* ((count 0)
|
||
(symb (intern (format
|
||
"toolbarx-internal-menu-var-%d"
|
||
count))))
|
||
(while (boundp symb)
|
||
(setq count (1+ count))
|
||
(setq symb
|
||
(intern (format "toolbarx-internal-menu-var-%d"
|
||
count))))
|
||
symb)))
|
||
;; auxiliary variables
|
||
(list-strings)
|
||
(list-buttons))
|
||
;; setting `variable'
|
||
(if save
|
||
(custom-declare-variable
|
||
variable default
|
||
"Used as variable of dropdown menu defined with `toolbarx'.")
|
||
(when (not (boundp variable))
|
||
(set variable default)))
|
||
;; now check `variable' content
|
||
(set variable
|
||
(let ((val (symbol-value variable)))
|
||
(if (eq type 'toggle)
|
||
(if (listp val)
|
||
val
|
||
(if (integerp val)
|
||
(list val)
|
||
(list 1)))
|
||
;; then, type is radio
|
||
(if (integerp val)
|
||
val
|
||
(if (and val
|
||
(listp val)
|
||
(integerp (car val)))
|
||
(car val)
|
||
1)))))
|
||
;; === buiding `list-strings' and `list-buttons' ===
|
||
;; if only symbols, build `list-strings' and `list-buttons' from symbols
|
||
(if (let ((only-symbols-flag t))
|
||
(dolist (i dropdown-list only-symbols-flag)
|
||
(setq only-symbols-flag (and only-symbols-flag (symbolp i)))))
|
||
(let ((count 0))
|
||
(dolist (i dropdown-list)
|
||
;; list-strings and list-buttons are built reversed
|
||
(setq list-strings (cons (toolbarx-make-string-from-symbol i)
|
||
list-strings))
|
||
(setq count (1+ count))
|
||
(setq list-buttons (cons (list i
|
||
:insert
|
||
(if (eq type 'radio)
|
||
(list 'eq count variable)
|
||
(list 'memq count variable)))
|
||
list-buttons))))
|
||
;; if not, the it must start with string
|
||
(unless (stringp (car dropdown-list))
|
||
(error "%s %s %s"
|
||
"If not all items on dropdown are symbols, then a string"
|
||
"must come before each set of buttons; no string found"
|
||
"in first position"))
|
||
(let ((count 0)
|
||
(elem)
|
||
(temp-list-buttons))
|
||
(while dropdown-list
|
||
(setq elem (car dropdown-list))
|
||
(setq dropdown-list (cdr dropdown-list))
|
||
(if (stringp elem)
|
||
;; if string, output `temp-list-buttons' and prepair it again
|
||
(progn
|
||
;; list-strings and list-buttons are buildt reversed
|
||
(setq list-strings (cons elem list-strings))
|
||
(when temp-list-buttons
|
||
(setq list-buttons (cons (append (nreverse temp-list-buttons)
|
||
(list :insert
|
||
(if (eq type 'radio)
|
||
(list 'eq count
|
||
variable)
|
||
(list 'memq count
|
||
variable))))
|
||
list-buttons)))
|
||
(setq temp-list-buttons nil)
|
||
(setq count (1+ count)))
|
||
;; else, if not string, just insert it to `temp-list-buttons'
|
||
;; which is also buildt reversed
|
||
(setq temp-list-buttons (cons elem temp-list-buttons))))
|
||
;; output last temp list, left behind
|
||
(when temp-list-buttons
|
||
(setq list-buttons (cons (append (nreverse
|
||
temp-list-buttons)
|
||
(list
|
||
:insert (if (eq type 'radio)
|
||
(list 'eq count
|
||
variable)
|
||
(list 'memq count
|
||
variable))))
|
||
list-buttons)))))
|
||
;; lists were made reversed (elements inserted at the beginning)
|
||
(setq list-strings (nreverse list-strings))
|
||
(setq list-buttons (nreverse list-buttons))
|
||
;; now, pass `list-buttons' as a group to `toolbarx-process-group'
|
||
(let ((current-switches switches))
|
||
(setq current-switches
|
||
(toolbarx-process-group list-buttons meaning-alist
|
||
merged-props ; pass non-processed props
|
||
current-switches))
|
||
(setq current-switches
|
||
;; outputing dropdown button
|
||
(toolbarx-process-group (append dropdown-button-without-command
|
||
(list :command
|
||
(toolbarx-mount-popup-menu
|
||
list-strings variable type
|
||
title save)))
|
||
meaning-alist merged-props-button-only
|
||
switches))
|
||
current-switches)))
|
||
|
||
|
||
|
||
;; Still functions `toolbarx-install-toolbar' and `toolbarx-refresh'to
|
||
;; complete the parsing engine. Since they interface with other engines,
|
||
;; they must come in the end.
|
||
|
||
;;; How a image is made, giving a string as (part of) file name.
|
||
|
||
;; look at function `image-type-available-p' for Emacs !!!!
|
||
|
||
(defun toolbarx-find-image (image)
|
||
"Return image descriptor or glyph for IMAGE.
|
||
|
||
IMAGE is string. Usually IMAGE neither contains a directory nor
|
||
an extension. If the extension is omitted, `xpm', `xbm' and
|
||
`pbm' are tried. If the directory is omitted,
|
||
`toolbarx-image-path' is searched."
|
||
(let ((file))
|
||
(dolist (i '("" ".xpm" ".xbm" ".pbm"))
|
||
(unless file
|
||
(setq file (locate-library (concat image i) t toolbarx-image-path))))
|
||
(if file
|
||
(create-image file)
|
||
(find-image `((:type xpm :file ,(concat image ".xpm"))
|
||
(:type xbm :file ,(concat image ".xbm"))
|
||
(:type pbm :file ,(concat image ".pbm")))))))
|
||
|
||
;; next variable interfaces between parsing and display engines
|
||
(defvar toolbarx-internal-button-switches nil
|
||
"Store the list of processed buttons, used by `toolbarx-refresh'.
|
||
This variable can store different values for the different buffers.")
|
||
|
||
|
||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
;;; Second engine: display parsed buttons in Emacs
|
||
|
||
(defun toolbarx-emacs-add-button (button used-keys keymap)
|
||
"Insert a button where BUTTON is its description.
|
||
USED-KEYS should be a list of symbols, where the first element is
|
||
`:used-symbols'. This list should store the symbols of the
|
||
buttons already inserted. This list is changed by side effect.
|
||
KEYMAP is the keymap where the menu-item corresponding to the
|
||
tool-bal button is going to be inserted. Insertion is made in
|
||
the end of KEYMAP.
|
||
|
||
BUTTON should be a list of form (SYMBOL . PROP-LIST). SYMBOL is
|
||
a symbol that \"names\" this button. PROP-LIST is a list in the
|
||
format (PROP VAL ... PROP VAL). The supported properties are
|
||
`:image', `:command', `:append-command', `:prepend-command',
|
||
`:help', `:enable', `:visible', `:button', and `:insert'.
|
||
For a description of properties, see documentation of
|
||
function `toolbar-install-toolbar'."
|
||
(let* ((symbol (nth 0 button))
|
||
(used-keys-list (when used-keys
|
||
(cdr used-keys)))
|
||
(filtered-props
|
||
(let* ((filtered-props-temp)
|
||
(prop-good-val)
|
||
(prop))
|
||
(dolist (p (nth 0 toolbarx-button-props) filtered-props-temp)
|
||
;; property -> (car p)
|
||
;; test type function -> (cadr p)
|
||
;; add-function -> (cddr p)
|
||
(setq prop (memq (car p) button))
|
||
;; if so, check if value is of correct type
|
||
(when prop
|
||
;; if property is of add-type, them the value is a list
|
||
;; (:add-value-list VAL VAL). Each VAL should be checked.
|
||
(if (and (cddr p) (eq :add-value-list (car (cadr prop))))
|
||
(let* ((add-list (list (cddr p))))
|
||
(dolist (val (cdr (cadr prop)))
|
||
(setq prop-good-val (funcall (cadr p) val))
|
||
(when (car prop-good-val)
|
||
(setq add-list (cons (cdr prop-good-val) add-list))))
|
||
(setq add-list (nreverse add-list))
|
||
(when (eq 2 (length add-list)) ; just 1 value, no
|
||
; add-function
|
||
(setq add-list (cadr add-list)))
|
||
(setq filtered-props-temp (append
|
||
(list (car p) add-list)
|
||
filtered-props-temp)))
|
||
;; if override-property
|
||
(setq prop-good-val (funcall (cadr p) (cadr prop)))
|
||
(when (car prop-good-val)
|
||
(setq filtered-props-temp (append
|
||
(list (car p)
|
||
(cdr prop-good-val))
|
||
filtered-props-temp))))))))
|
||
(insert (or (not (memq :insert filtered-props))
|
||
;; (memq :insert filtered-props)
|
||
(eval (nth 1 (memq :insert filtered-props)) t))))
|
||
(when insert
|
||
(cond
|
||
(t
|
||
;; symbol is not :new-line, therefore a normal button
|
||
(let* ((image (cadr (memq :image filtered-props)))
|
||
(image-descriptor
|
||
(when (memq :image filtered-props)
|
||
(cond
|
||
((stringp image) ; string
|
||
(toolbarx-find-image image))
|
||
((and (consp image) ; or image descriptor
|
||
(eq (car image) 'image))
|
||
image)
|
||
((and (symbolp image) ; or a symbol bound to a
|
||
(boundp image) ; image descriptor (defined
|
||
; with `defimage')g
|
||
(consp (symbol-value image))
|
||
(eq (car (symbol-value image)) 'image))
|
||
(symbol-value image))
|
||
(t ; otherwise, must be a list
|
||
; with 4 strings or image
|
||
; descriptors
|
||
(apply #'vector (mapcar (lambda (img)
|
||
(if (stringp img)
|
||
(toolbarx-find-image img)
|
||
img))
|
||
image))))))
|
||
(command
|
||
(let* ((com (nth 1 (memq :command filtered-props)))
|
||
(app (nth 1 (memq :append-command filtered-props)))
|
||
(prep (nth 1 (memq :prepend-command filtered-props))))
|
||
(when (or com app prep)
|
||
(toolbarx-make-command com prep app))))
|
||
(help (cons (memq :help filtered-props)
|
||
(cadr (memq :help filtered-props))))
|
||
(enable (cons (memq :enable filtered-props)
|
||
(cadr (memq :enable filtered-props))))
|
||
(visible (cons (memq :visible filtered-props)
|
||
(cadr (memq :visible filtered-props))))
|
||
(button (cons (memq :button filtered-props)
|
||
(cadr (memq :button filtered-props))))
|
||
(menuitem (if (eq symbol 'separator)
|
||
'(menu-item "--")
|
||
(append
|
||
(list 'menu-item
|
||
(toolbarx-make-string-from-symbol symbol)
|
||
command
|
||
:image image-descriptor)
|
||
(when (car help)
|
||
(list :help (cdr help)))
|
||
(when (car enable)
|
||
(list :enable (cdr enable)))
|
||
(when (car visible)
|
||
(list :visible (cdr visible)))
|
||
(when (car button)
|
||
(list :button (cdr button)))
|
||
'(:vert-only t))))
|
||
(key-not-used
|
||
(let* ((count 0)
|
||
(symb symbol))
|
||
(while (memq symb used-keys-list)
|
||
(setq count (1+ count))
|
||
(setq symb (intern (format "%s-%d" symbol count))))
|
||
symb)))
|
||
(when (and image-descriptor command)
|
||
(setq used-keys-list (cons key-not-used used-keys-list))
|
||
(define-key-after keymap
|
||
(vector key-not-used) menuitem))))))
|
||
(when used-keys (setcdr used-keys used-keys-list))))
|
||
|
||
|
||
(defun toolbarx-emacs-refresh-process-button-or-insert-list (switches
|
||
used-keys
|
||
keymap)
|
||
"Process SWITCHES, inserting buttons in `tool-bar-map'.
|
||
If a button is actually a `:insert' clause group (if `car' is
|
||
`:insert') and evaluation of `cdr' yields non-nil, process `cddr'
|
||
recursively as SWITCHES. USED-KEYS is a list which `car' is
|
||
`:used-symbols' and which `cdr' is a list of symbols that have already
|
||
been used as keys in the keymap `tool-bar-map'."
|
||
(dolist (button switches)
|
||
(if (eq (car button) :insert)
|
||
(when (eval (cadr button) t)
|
||
(toolbarx-emacs-refresh-process-button-or-insert-list (cddr button)
|
||
used-keys
|
||
keymap))
|
||
(toolbarx-emacs-add-button button used-keys keymap))))
|
||
|
||
|
||
|
||
(defun toolbarx-emacs-refresh (&optional global-flag)
|
||
"Refresh and redraw the toolbar in Emacs.
|
||
If GLOBAL-FLAG is non-nil, the default value of toolbar switches
|
||
is used and the default value of `toolbarx-map' is changed."
|
||
(let* ((switches (if global-flag
|
||
(if (default-boundp 'toolbarx-internal-button-switches)
|
||
(default-value 'toolbarx-internal-button-switches)
|
||
toolbarx-internal-button-switches)
|
||
toolbarx-internal-button-switches))
|
||
(used-keys (list :used-symbols nil))
|
||
(tool-bar-map-temp (make-sparse-keymap)))
|
||
(toolbarx-emacs-refresh-process-button-or-insert-list switches used-keys
|
||
tool-bar-map-temp)
|
||
(if global-flag
|
||
(setq-default tool-bar-map tool-bar-map-temp)
|
||
(setq tool-bar-map tool-bar-map-temp))))
|
||
|
||
|
||
|
||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
;;; finishing parsing engine
|
||
|
||
(defun toolbarx-refresh (&optional global-flag)
|
||
"Redraw the toolbar, peviously installed with `toolbarx'.
|
||
Force global refresh if GLOBAL-FLAG is non-nil."
|
||
(interactive "P")
|
||
(toolbarx-emacs-refresh global-flag))
|
||
|
||
;;;###autoload (autoload 'toolbarx-install-toolbar "toolbar-x")
|
||
|
||
(defun toolbarx-install-toolbar (buttons &optional meaning-alist global-flag)
|
||
"Install toolbar buttons given in BUTTONS.
|
||
Button properties are optionally given in MEANING-ALIST. If
|
||
GLOBAL-FLAG is non-nil, toolbar is installed globally (on every
|
||
buffer that does not have a toolbar set locally). BUTTONS is a
|
||
list of format
|
||
(ELEM ... ELEM . PROPS),
|
||
where each ELEM is either
|
||
|
||
- a list in the same format od BUTTONS, which is going to be
|
||
refered as a *group*; groups are used to distribute properties
|
||
recursively to its elements; there are groups with special
|
||
format for special purpose: *dropdown groups* and also *eval
|
||
groups*.
|
||
|
||
- a symbol, which could be associated in MEANING-ALIST with a
|
||
list of button properties (symbol + properties = a *button*)
|
||
or associated to a special kind of group (an *alias group*).
|
||
|
||
Meaning alist
|
||
=============
|
||
|
||
MEANING-ALIST is a list where each element is in one of the
|
||
formats (SYMB . BUTTON-PROPS-LIST) or (SYMB . ALIAS-GROUP).
|
||
BUTTON-PROPS-LIST is a list in one of the formats
|
||
(IMAGE COMMAND PROP VAL PROP VAL ... PROP VAL) or
|
||
(PROP VAL PROP VAL ... PROP VAL).
|
||
The IMAGE is going to be used as the `:image' property of the
|
||
button (see button properties bellow), and COMMAND shall be used
|
||
as the `:command' property of the button. Each PROP is one of
|
||
the button properties, and VAL is its respective value.
|
||
ALIAS-GROUP is a list which first element is the symbol `:alias'
|
||
and the cdr shall be processed as a group.
|
||
|
||
However, a symbol is not required to have an association in
|
||
MEANING-ALIST, which is only a way to specify properties to a
|
||
button. One can use groups to specify properties. Nil is a good
|
||
MEANING-ALIST.
|
||
|
||
Buttons
|
||
=======
|
||
|
||
A toolbar button in `toolbarx' is the set with a symbol and
|
||
properties used to display the button, like a image and a command
|
||
to call when the button is pressed (which are the minimal
|
||
elements that a button should have.) The supported properties
|
||
for buttons and their `basic types' (see note on how values of
|
||
properties are obtained!) are:
|
||
|
||
:image -- either a string or image descriptor (see
|
||
info for a definition), or a variable bound to a image
|
||
descriptor (like those defined with `defimage') or a list of 4
|
||
strings or image descriptors;
|
||
defines the image file displayed by the button. If
|
||
it is a string, the image file found with that name (always
|
||
using the function `toolbarx-find-image' to make the
|
||
\`internal\' image descriptor) is used as button image. For
|
||
the other formats, the button image is handled in the same way
|
||
as it is treated by the editors; see info nodes bellow for a
|
||
description of the capabilities:
|
||
info file \"elisp\", node \"Tool Bar\" (see `:image' property);
|
||
PS: a *vector* of four strings is used in the Emacs
|
||
Lisp documentation as the `more ellaborated' image
|
||
property format, but here we reserve vectors to
|
||
provide editor-dependent values; this motivates our
|
||
choice for a list instead of vector (however,
|
||
internally the list becomes a vector when displaying
|
||
the button).
|
||
|
||
:command -- a form; if the form happens to be a command, it will
|
||
be called with `call-interactively'.
|
||
|
||
:append-command -- a form added to the end of the value of
|
||
`:command'.
|
||
|
||
:prepend-command -- a form added at the beginning of the value
|
||
of `:command'.
|
||
|
||
:help -- either a string or nil; defined the help string of the
|
||
button;
|
||
|
||
:enable -- a form, evaluated constantly by both editors to
|
||
determine if a button is active (enabled) or not.
|
||
|
||
:visible -- a form that is evaluated constantly to
|
||
determine if a button is visible.
|
||
|
||
:button -- a cons cell (TYPE . SELECTED) where the
|
||
TYPE should be `:toggle' or `:radio' and the cdr should be a
|
||
form. SELECTED is evaluated to determine when the button is
|
||
selected.
|
||
|
||
:insert -- a form that is evaluated every time that the toolbar
|
||
is refresh (a call of `toolbarx-refresh') to determine if the
|
||
button is inserted or just ignored (until next refresh).
|
||
|
||
How to specify a button
|
||
=======================
|
||
|
||
One can specify a button by its symbol or by a group to specify
|
||
properties. For example,
|
||
BUTTON =
|
||
( foo
|
||
(bar :image \"bar\"
|
||
:command bar-function :help \"Bar help string\")
|
||
:insert foo-bar )
|
||
MEANING-ALIST = ( (foo :image \"foo\" :command foo-function) )
|
||
specifiy two buttons `foo' and `bar', each one with its necessary
|
||
:image and :command properties, and both use the :insert property
|
||
specified ate the end of BUTTONS (because groups distribute
|
||
properties to all its elements). `foo' and `bar' will be
|
||
inserted only if `foo-bar' evaluation yields non-nil.
|
||
|
||
Note on how values of properties are obtained
|
||
=============================================
|
||
|
||
For each property PROP, its value should be either:
|
||
i) a vector of 2 elements; then each element should be of the
|
||
basic type of PROP.
|
||
ii) an element on the basic type of PROP.
|
||
iii) a function (that does not need arguments); it is evaluated
|
||
and the return should be ot type i) or ii) above
|
||
iv) a symbol bound to a element of type i) or ii).
|
||
|
||
The type is cheched in the order i), ii) iii) and iv). This
|
||
evaluations are done every time that the oolbar is refresh.
|
||
|
||
Ps.: in order to specify a vector as value of a property (like
|
||
the :image in Emacs), it is necessary to provide the vector as
|
||
element of another vector.
|
||
|
||
Special groups
|
||
==============
|
||
|
||
Eval groups
|
||
-----------
|
||
|
||
If the first element of a group is the symbol `:eval-group', each
|
||
element is evaluated (with `eval'), put inside a list and
|
||
processed like a group. Eval groups are useful to store
|
||
definition of buttons in a variable.
|
||
|
||
Dropdown groups
|
||
---------------
|
||
|
||
The idea is to specify a set of buttons that appear when a
|
||
determined menu item of a dropdown menu is active. The dropdown
|
||
menu appears when a button (by default with a triangle pointing
|
||
down) is clicked. This button is called `dropdown button'. The
|
||
dropdown button appears on the left of the currently visible
|
||
buttons of the dropdown group.
|
||
|
||
A dropdown group is a list which first element is the symbol
|
||
`:dropdown-group' and in one of the following formats
|
||
(:dropdown-group SYMBOL-1 ... SYMBOL-n PROP-1 VAL-1 ... PROP-k VAL-k)
|
||
or
|
||
(:dropdown-group
|
||
STRING-1 ITEM-11 ... ITEM-1n
|
||
STRING-2 ITEM-21 ... ITEM-2m
|
||
. . .
|
||
STRING-n ITEM-n1 ... ITEM-np
|
||
PROP-1 VAL-1 ... PROP-j VAL-j)
|
||
where
|
||
SYMBOL-* is a symbol that defines a button in MEANING-ALIST;
|
||
STRING-* is a string that will appear in the dropdown menu;
|
||
ITEM-* is any format that define buttons or groups.
|
||
|
||
\(a dropdown group of first format is internally converted to the
|
||
second by making strings from the symbols and each symbol is the
|
||
item)
|
||
|
||
The same rules for obtaining property values, described above,
|
||
apply here. Properties are also distributed by groups. The
|
||
supported properties and their basic type are:
|
||
|
||
:type -- one of the symbols `radio' (default) or `toggle'; if
|
||
type is radio, only one of the itens may be active, and if
|
||
type is toggle, any item number of itens can be active.
|
||
|
||
:variable -- a symbol; it is the variable that govern the
|
||
dropdown button; every time the value should be an integer
|
||
starting from 1 (if type is radio) or a list of integers (if
|
||
type is toggle). The Nth set of buttons is :insert'ed.
|
||
|
||
:default -- determines the default value when the menu is
|
||
installed; it is ignored if a value was saved with custom; it
|
||
defaults to 1 if type is radio or nil if type is toggle. If
|
||
value is a integer and type is `toggle', value used is a list
|
||
with that integer.
|
||
|
||
:save -- one of the symbols nil (default), `offer' or
|
||
`always'; determined if it is possible for the user to save
|
||
the which menu itens are active, for a next session. If value
|
||
is `offer', a item (offering to save) is added to the
|
||
popup menu. If the value is `always', every time that a item
|
||
is selected, the variable is saved. If value is nil, variable
|
||
shall not be saved. If value is non-nil then `:variable' is
|
||
mandatory.
|
||
|
||
:title -- a string or nil; if a string, the popup menu will show
|
||
is as menu title; if nil, no title is shown.
|
||
|
||
:dropdown-help -- a string or nil; the help string of the
|
||
dropdown button.
|
||
|
||
:dropdown-image -- either a string or a vector of 4 strings;
|
||
defines the image file displayed by the dropdown button;
|
||
by default, it is the string \"dropdown\".
|
||
|
||
:dropdown-append-command,
|
||
:dropdownprepend-command -- a form; append or prepend forms to
|
||
the command that shows the dropdown menu, allowing extra code
|
||
to run before or after the menu appears (remember that every
|
||
menu item clicked refresh the toolbar.)
|
||
|
||
:dropdown-enable -- a form; evaluated constantly by both editors
|
||
to determine if the dropdown button is active (enabled) or
|
||
not.
|
||
|
||
:dropdown-visible -- a form; it is evaluated
|
||
constantly to determine if the dropdown button is visible.
|
||
|
||
Also, if the symbol `dropdown' is associted in MEANING-ALIST
|
||
with some properties, these properties override (or add) with
|
||
higher precedence.
|
||
|
||
Special buttons
|
||
===============
|
||
|
||
If the symbol of a button is `:new-line', it is inserted
|
||
a (faked) return, and the next button will be displayed a next
|
||
line of buttons. The only property supported for this button is
|
||
`:insert'."
|
||
(let ((switches (toolbarx-process-group buttons meaning-alist nil nil)))
|
||
(if global-flag
|
||
(setq-default toolbarx-internal-button-switches
|
||
switches)
|
||
(set (make-local-variable 'toolbarx-internal-button-switches)
|
||
switches)
|
||
(make-local-variable 'tool-bar-map)))
|
||
(toolbarx-refresh global-flag))
|
||
|
||
|
||
(defconst toolbarx-default-toolbar-meaning-alist
|
||
'((separator :image "sep" :command t :enable nil :help "")
|
||
|
||
(new-file
|
||
:image "new"
|
||
:command find-file
|
||
:enable (not (window-minibuffer-p
|
||
(frame-selected-window menu-updating-frame)))
|
||
:help "Specify a new file's name, to edit the file")
|
||
|
||
(open-file :image "open"
|
||
:command menu-find-file-existing
|
||
:enable (not (window-minibuffer-p
|
||
(frame-selected-window menu-updating-frame)))
|
||
:help "Read a file into an Emacs buffer")
|
||
|
||
(dired :image "diropen"
|
||
:command dired
|
||
:help "Read a directory, operate on its files")
|
||
|
||
(save-buffer :image "save"
|
||
:command save-buffer
|
||
:enable (and
|
||
(buffer-modified-p)
|
||
(buffer-file-name)
|
||
(not (window-minibuffer-p
|
||
(frame-selected-window menu-updating-frame))))
|
||
:help "Save current buffer to its file"
|
||
:visible (or buffer-file-name
|
||
(not (eq 'special
|
||
(get major-mode 'mode-class)))))
|
||
|
||
(write-file :image "saveas"
|
||
:command write-file
|
||
:enable (not
|
||
(window-minibuffer-p
|
||
(frame-selected-window menu-updating-frame)))
|
||
:help "Write current buffer to another file"
|
||
:visible (or buffer-file-name
|
||
(not (eq 'special (get major-mode 'mode-class)))))
|
||
|
||
(undo :image "undo"
|
||
:command undo
|
||
:enable (and (not buffer-read-only)
|
||
(not (eq t buffer-undo-list))
|
||
(if (eq last-command #'undo)
|
||
pending-undo-list
|
||
(consp buffer-undo-list)))
|
||
:help "Undo last operation"
|
||
:visible (not (eq 'special (get major-mode 'mode-class))))
|
||
|
||
(cut :image "cut"
|
||
:help "Delete text in region and copy it to the clipboard"
|
||
:command clipboard-kill-region
|
||
:visible (not (eq 'special (get major-mode 'mode-class))))
|
||
|
||
(copy :image "copy"
|
||
:help "Copy text in region to the clipboard"
|
||
:command clipboard-kill-ring-save)
|
||
|
||
(paste :image "paste"
|
||
:help "Paste text from clipboard"
|
||
:command clipboard-yank
|
||
:visible (not (eq 'special (get major-mode 'mode-class))))
|
||
|
||
(search-forward :command nonincremental-search-forward
|
||
:help "Search forward for a string"
|
||
:image "search")
|
||
|
||
(search-replace
|
||
:image "search-replace"
|
||
:command query-replace
|
||
:help "Replace string interactively, ask about each occurrence")
|
||
|
||
(print-buffer :image "print"
|
||
:command print-buffer
|
||
:help "Print current buffer with page headings")
|
||
|
||
(customize :image "preferences"
|
||
:command customize
|
||
:help "Edit preferences (customize)")
|
||
|
||
(help :image "help"
|
||
:command (lambda () (interactive) (popup-menu menu-bar-help-menu))
|
||
:help "Pop up the Help menu")
|
||
|
||
(kill-buffer :command kill-this-buffer
|
||
:enable (kill-this-buffer-enabled-p)
|
||
:help "Discard current buffer"
|
||
:image "close")
|
||
|
||
(exit-emacs :image "exit"
|
||
:command save-buffers-kill-emacs
|
||
:help "Offer to save unsaved buffers, then exit Emacs")
|
||
|
||
(spell-buffer :image "spell"
|
||
:command ispell-buffer
|
||
:help "Check spelling of selected buffer")
|
||
|
||
(info :image "info"
|
||
:command info
|
||
:help "Enter Info, the documentation browser"))
|
||
"A meaning alist with definition of the default buttons.
|
||
The following buttons are available:
|
||
|
||
`open-file', `dired', `save-buffer',
|
||
`undo', `cut', `copy', `paste', `search-replace', `print-buffer',
|
||
`spell-buffer', `info'.
|
||
`new-file', `write-file', `search-forward',
|
||
`customize', `help', `kill-buffer', `exit-emacs'.
|
||
|
||
To reproduce the default toolbar with use as BUTTON
|
||
in `toolbarx-install-toolbar':
|
||
|
||
\(toolbarx-install-toolbar
|
||
\\='((open-file dired kill-buffer save-buffer write-file undo cut
|
||
copy paste search-forward print-buffer customize help))
|
||
toolbarx-default-toolbar-meaning-alist)
|
||
|
||
Ps.: there are more buttons available than suggested in the
|
||
expression above.")
|
||
|
||
(provide 'toolbar-x)
|
||
|
||
;;; toolbar-x.el ends here
|