;; custom-menu.gwm --- User-friendlier package customizations ;; ;; Author: Anders Holst (aho@sans.kth.se) ;; Copyright (C) 1996 Anders Holst ;; Last change: 23/3 1996 ;; ;; This file is copyrighted under the same terms as the rest of GWM ;; (see the X Inc license for details). There is no warranty that it ;; works. ;; ;; --------------------------------------------------------------------- ;; ;; This file implements a way for other packages to provide easy ;; customization via menus, without spending too much effort on it ;; themselves. ;; ;; ;; The main function is '(custom-menu )'. ;; ;; In the simplest case its argument is a list of strings and variables, ;; and it constructs a menu where the user can edit the values of the ;; given variables. Typically the first element in the list should be ;; the name of the menu, but any strings in the list show up as labels. ;; ;; The list can also contain other "menu descriptions" as elements. ;; They will turn up as buttons leading to these "sub-menus". Note that ;; then the first element in each sub-menu description *must* be ;; the name of it (and not a variable or a sub-description). ;; ;; Actually, the first element in each menu-description can also be a ;; list where the first element is the name of it and the rest an ;; association list. Currently the only supported field in this ;; association list is 'hook', which should be some code to run whenever ;; the value of a variable is changed in the menu. ;; ;; To start edit a value in the menu, you click in the corresponding ;; field. If the variable 'custom-immediate-change' is non-nil, the ;; variable is assigned to the new value as soon you finish editing. ;; There is also a special construct, to allow more complex values. If ;; the field starts with a comma (,) the following expression is ;; evaluated before the assignment. ;; ;; But that's all there is to it really. ;; ;; The rest of the code in this file is to provide a simple way ;; for packages to install their customizable variables in a global ;; custom-menu hierarchy, and to maintain the user's variable settings ;; between GWM sessions. ;; ;; ;; There are two functions intended to be used by the package developer: ;; (custom-install-symbols ) ;; (custom-install-hook ) ;; ;; The is either a string, or a list of strings serving ;; as a "path" through the menu hierarchy. is the list of ;; all customizable variables in the package. is a piece of ;; code (quoted) to run whenever any of the customizable variables ;; have changed. It is of course not necessary to provide such a hook, ;; but it is very nice to have one. ;; ;; Another thing to consider for the package developer is to make the ;; variables suitable for customization in this way. Colors and fonts ;; should *not* be stored as numbers but as the strings used when ;; creating them. The contents of variables should preferably not be ;; very long lists, since these are hard to edit. If values are cached ;; there should be some way of updating the cache when the values have ;; changed, and if values are not cached the package should not assume ;; that the contents of variables will remain the same during the whole ;; session. ;; ;; ;; There are three functions intended for the GWM user: ;; (custom-global-menu) ;; (custom-load-preferences) ;; (custom-save-preferences) ;; ;; '(custom-global-menu)' brings you into the top of the custom-menu ;; hierarchy. It is most suitably called from the root menu. ;; ;; The function '(custom-load-preferences)' can be called either when ;; loading the profile, or from the 'screen-opening' hook and ;; '(custom-save-preferences)' can be called from the 'screen-closing' ;; hook. They try to maintain a file ".customize.gwm" where the ;; variables the user have changed through the custom menus are saved. ;; (declare-screen-dependent custom-font custom-symbol-font custom-value-font custom-value-font-slant custom-background custom-foreground custom-button-background custom-value-background custom-value-frame custom-immediate-feedback custom-immediate-change custom-keep-comma-string custom-preference-file custom-global-preferences custom-preferences-changed custom-global-menu-descr ) ;; ;; USER CUSTOMIZABLE VARIABLES ;; --------------------------- ;; Adjust these in your own profile ;; (with (wob wob) (for screen (list-of-screens) (defaults-to custom-font "6x13bold" custom-symbol-font "6x13" custom-value-font "6x13" custom-value-font-slant 0 custom-foreground "black" custom-background "lightcyan" custom-button-background "white" custom-value-background "white" custom-value-frame t custom-immediate-feedback t custom-immediate-change t custom-keep-comma-string t ))) (with (wob wob) (for screen (list-of-screens) (setq custom-global-preferences ()) (setq custom-preferences-changed ()) (setq custom-global-menu-descr '("Customizations")) )) (with (tmp (getenv "GWM_CUSTOM_FILE")) (if (= tmp "") (setq custom-preference-file "./.customize.gwm") (match "/" tmp) (setq custom-preference-file tmp) (setq custom-preference-file (+ "./" tmp)))) (if (not (boundp 'edit-plug-make)) (load "edit-plug")) (defun custom-print-to-string (val) (if (member (type val) '(subr fsubr expr fexpr)) "" (= (type val) 'string) (+ "\"" val "\"") (= (type val) 'list) (with (parts (mapfor ele val (custom-print-to-string ele)) len (length parts) res (list-make (+ (* len 2) 1)) i 0 j 1) (## 0 res "(") (while (< i len) (## j res (# i parts)) (## (+ j 1) res " ") (setq i (+ i 1)) (setq j (+ j 2))) (## (max 1 (- j 1)) res ")") (apply + res)) (with-output-to-string (? val)))) (defun custom-read-from-string (_str_) (with (_res_ () _bad_ t) (error-occurred (if (match "^," _str_) (execute-string (+ "(setq _bad_ (error-occurred (setq _res_ " (match "^.\\(.*\\)$" _str_ 1) ")))")) (execute-string (+ "(setq _bad_ (error-occurred (setq _res_ (quote " _str_ "))))")))) (if _bad_ () (list _res_)))) (defun custom-substitute-in-string (from to str) (with (res () mlst () head str) (while (> (length (setq mlst (match (+ "\\(.*\\)\\(" from "\\)\\(.*\\)") head 1 2 3))) 0) (setq res (+ (list to (# 2 mlst)) res)) (setq head (# 0 mlst))) (eval (+ '(+) (list head) res)))) ;; Specialized normal state, how to start editing. (: custom-edit-normal-state (state-make (on (button any any) (progn (send-user-event 'edit-plug-start wob) (edit-plug-xposition (current-event-relative-x)))) edit-plug-normal-state )) ;; Specialized active state, when to stop or abort editing. (: custom-edit-active-state (state-make (on focus-out (send-user-event 'edit-plug-done wob)) edit-plug-active-state )) ;; Things to do when starting editing. (defun custom-start-edit () (set-focus wob) (process-events)) ; To let the focus change take effect before the ; fsm enters the active state. Otherwise the fsm ; may immediately leave the active state again, ; due to some spurious focus-out event. ;; Things to do when editing is done. (defun custom-end-edit () (with (menuwob (custom-top-parent) immch (# 'immch menuwob) sym (# 'symbol wob) str (edit-plug-get) comma (and custom-keep-comma-string (match "^," str)) res (custom-read-from-string str)) (if res (if (not (equal (# 2 sym) (if comma str res))) (progn (if (not (# 3 sym)) (## 3 sym (or (# 2 sym) t))) (## 2 sym (if comma str res)) (if immch (progn (set (# 0 sym) (# 0 res)) (## 4 sym t) (eval (# 'hook wob)))) (if (and (not comma) custom-immediate-feedback) (edit-plug-change (custom-print-to-string (# 0 res)))))) (if custom-immediate-feedback (edit-plug-change (if (# 2 sym) (if (= (type (# 2 sym)) 'string) (# 2 sym) (custom-print-to-string (# 0 (# 2 sym)))) "")))))) (defun custom-abort-edit () ()) (defun custom-plug-separator (size) (with (fsm () foreground background) (plug-make (pixmap-make size 1)))) (defun custom-symbol-plug (label) (with (fsm () font (if (= (type custom-symbol-font) 'number) custom-symbol-font (font-make custom-symbol-font)) label-horizontal-margin 2 label-vertical-margin 2) (plug-make (label-make label)))) (defun custom-label-plug (label) (with (fsm () font (if (= (type custom-font) 'number) custom-font (font-make custom-font)) label-horizontal-margin 2 label-vertical-margin 2) (plug-make (label-make label)))) (defun custom-value-plug (item) (with (font (if (= (type custom-value-font) 'number) custom-value-font (font-make custom-value-font)) font-slant (or custom-value-font-slant 0) label-horizontal-margin 4 label-vertical-margin 1 edit-plug-start-hook '(custom-start-edit) edit-plug-done-hook '(custom-end-edit) edit-plug-abort-hook '(custom-abort-edit) edit-plug-normal-state custom-edit-normal-state edit-plug-active-state custom-edit-active-state property (+ (list 'symbol item 'hook hook) property)) (with (borderwidth (if custom-value-frame 1 0) borderpixel foreground) (bar-make (with (borderwidth 0) (bar-make (with (borderwidth 1 background (if custom-value-background (if (= (type custom-value-background) 'number) custom-value-background (color-make custom-value-background)) background) borderpixel background active-borderpixel foreground) (edit-plug-make (if (# 2 item) (if (= (type (# 2 item)) 'string) (# 2 item) (custom-print-to-string (# 0 (# 2 item)))) ""))))))))) (defun custom-button-plug (label action) (with (font (if (= (type custom-font) 'number) custom-font (font-make custom-font)) outer-background background background (if custom-button-background (if (= (type custom-button-background) 'number) custom-button-background (color-make custom-button-background)) background) label-horizontal-margin 5 label-vertical-margin 3 pix (label-make label) dim (dimensions pix)) (draw-rectangle pix 2 2 (- (# 2 dim) 4) (- (# 3 dim) 4) 2 1) (with (foreground outer-background) (draw-line pix 0 0 0 1) (draw-line pix 1 0 1 0) (draw-line pix 0 (- (# 3 dim) 2) 0 (- (# 3 dim) 1)) (draw-line pix 1 (- (# 3 dim) 1) 1 (- (# 3 dim) 1)) (draw-line pix (- (# 2 dim) 1) 0 (- (# 2 dim) 1) 1) (draw-line pix (- (# 2 dim) 2) 0 (- (# 2 dim) 2) 0) (draw-line pix (- (# 2 dim) 1) (- (# 3 dim) 2) (- (# 2 dim) 1) (- (# 3 dim) 1)) (draw-line pix (- (# 2 dim) 2) (- (# 3 dim) 1) (- (# 2 dim) 2) (- (# 3 dim) 1))) (draw-line pix 2 2 2 2) (draw-line pix (- (# 2 dim) 3) 2 (- (# 2 dim) 3) 2) (draw-line pix 2 (- (# 3 dim) 3) 2 (- (# 3 dim) 3)) (draw-line pix (- (# 2 dim) 3) (- (# 3 dim) 3) (- (# 2 dim) 3) (- (# 3 dim) 3)) (with (fsm (fsm-make (state-make (on-eval '(button any any) action)))) (plug-make pix)))) (defun custom-top-parent () (with (wob wob par wob-parent) (while (not (= par window)) (setq wob wob-parent) (setq par wob-parent)) wob)) (defun custom-done () (with (menuwob (custom-top-parent) descr (# 'descr menuwob) hook (# 'hook menuwob) change () res ()) (set-focus) (process-events) (for sym descr (if (and (# 0 sym) (# 2 sym) (# 3 sym) (not (# 4 sym))) (if (= (type (# 2 sym)) 'string) (if (setq res (custom-read-from-string (# 2 sym))) (progn (set (# 0 sym) (# 0 res)) (setq change t))) (progn (set (# 0 sym) (# 0 (# 2 sym))) (setq change t))))) (if change (with (custom-refresh-menu ()) (eval hook))) (for sym descr (if (and (# 0 sym) (# 2 sym) (# 3 sym)) (custom-put-preference (# 0 sym) (# 2 sym)))) (if (window-is-valid window) (delete-window window)))) (defun custom-cancel () (with (menuwob (custom-top-parent) descr (# 'descr menuwob) hook (# 'hook menuwob) change () res ()) (set-focus) (process-events) (for sym descr (if (and (# 0 sym) (# 3 sym) (# 4 sym)) (if (= (# 3 sym) t) (unbind (# 0 sym)) (= (type (# 3 sym)) 'string) (if (setq res (custom-read-from-string (# 3 sym))) (progn (set (# 0 sym) (# 0 res)) (setq change t))) (progn (set (# 0 sym) (# 0 (# 3 sym))) (setq change t))))) (if change (with (custom-refresh-menu ()) (eval hook))) (delete-window window))) (defun custom-construct-menu (descr) (with (fsm () bordertile () bar-max-width 3000 bar-min-width 0 background (if (= (type custom-background) 'number) custom-background (color-make custom-background)) foreground (if (= (type custom-foreground) 'number) custom-foreground (color-make custom-foreground)) borderpixel background borderwidth 1 direction vertical align-column 0 bar-list () hook (if (not (# 0 (# 0 descr))) (# 'hook (# 2 (# 0 descr))))) (with (m 0 sz ()) (for ele descr (if (# 0 ele) (with (font (if (= (type custom-symbol-font) 'number) custom-symbol-font (font-make custom-symbol-font))) (setq sz (# 2 (dimensions (+ (# 0 ele))))) (## 1 ele sz) (setq m (max m sz))))) (setq align-column (+ m 2))) (setq bar-list (mapfor ele descr (if (# 0 ele) (bar-make (custom-plug-separator 5) (custom-symbol-plug (# 0 ele)) (custom-plug-separator (- align-column (# 1 ele))) (custom-value-plug ele) (custom-plug-separator 10)) (= (type (# 1 ele)) 'list) (bar-make (custom-plug-separator 10) (custom-button-plug (if (= (type (# 0 (# 1 ele))) 'string) (# 0 (# 1 ele)) (# 0 (# 0 (# 1 ele)))) (list 'custom-menu (list 'quote (# 1 ele)) '(+ window-x 15) '(+ window-y 15)))) (= (type (# 1 ele)) 'string) (bar-make (custom-plug-separator 10) (custom-label-plug (# 1 ele)))))) (setq bar-list (+ bar-list (list (bar-make (custom-plug-separator 10) (custom-button-plug "Done" '(custom-done)) (custom-plug-separator 15) (custom-button-plug "Cancel" '(custom-cancel)) (custom-plug-separator 10))))) (with (property (+ (list 'descr descr 'hook hook 'immch custom-immediate-change) property) borderpixel foreground) (apply menu-make bar-list)))) (defun custom-menu _args_ (with (descr (mapfor _cele_ (# 0 _args_) (if (not (member (type _cele_) '(string list))) (list _cele_ () (or (with (res (custom-get-preference _cele_)) (if (and res (= (type res) 'string)) res ())) (if (boundp _cele_) (list (eval _cele_)) ())) () ()) (list () _cele_))) xpos (if (= (type (# 1 _args_)) 'number) (# 1 _args_)) ypos (if (= (type (# 2 _args_)) 'number) (# 2 _args_)) mn ()) (if (and (not (# 0 (# 0 descr))) (= (type (# 1 (# 0 descr))) 'list) (= (type (# 0 (# 1 (# 0 descr)))) 'string)) (## 0 descr (list () (# 0 (# 1 (# 0 descr))) (sublist 1 (length (# 1 (# 0 descr))) (# 1 (# 0 descr)))))) (setq mn (custom-construct-menu descr)) (if mn (with (reenter-on-opening () dim (dimensions mn) x (max 10 (/ (- screen-width (# 2 dim)) 3)) y (max 10 (/ (- screen-height (# 3 dim)) 3))) (if (and xpos ypos) (place-menu 'custom mn xpos ypos) (place-menu 'custom mn x y)))))) (defun custom-refresh-menu () (with (menuwob (custom-top-parent) descr (# 'descr menuwob) hook (# 'hook menuwob) immch (# 'immch menuwob) xpos (+ window-x window-client-x (with (wob window) wob-borderwidth) (with (wob menuwob) (- wob-borderwidth)) window-client-borderwidth) ypos (+ window-y window-client-y (with (wob window) wob-borderwidth) (with (wob menuwob) (- wob-borderwidth)) window-client-borderwidth) mn ()) (if (not (= immch custom-immediate-change)) (for sym descr (if (and (# 0 sym) (# 4 sym)) (## 4 sym ())))) (setq mn (custom-construct-menu descr)) (if mn (with (reenter-on-opening ()) (delete-window window) (place-menu 'custom mn xpos ypos))))) (defun custom-save-file (file lst) (with-output-to-file (+ file ":") (? "(setq tmp '(\n") (with (i 0) (while (< i (length lst)) (? (custom-print-to-string (# i lst)) " " (custom-print-to-string (if (= (type (# (+ i 1) lst)) 'string) (custom-substitute-in-string "\"" "\\\"" (# (+ i 1) lst)) (# (+ i 1) lst))) "\n") (setq i (+ i 2)))) (? "))\n")) (! "sh" "-c" (+ "mv " file " " file "~ ; mv " file ": " file " ;")) ) (defun custom-read-file (file) (with (tmp 'magicatom) (error-occurred (load file)) (if (= tmp 'magicatom) () tmp))) (defun custom-put-preference (var val) (if (member var custom-global-preferences) (## var custom-global-preferences val) (setq custom-global-preferences (+ custom-global-preferences (list var val)))) (setq custom-preferences-changed t)) (defun custom-get-preference (var) (# var custom-global-preferences)) (defun custom-apply-preferences (prefs) (with (len (length prefs) i 0 res ()) (while (< i len) (if (= (type (# (+ i 1) prefs)) 'string) (if (setq res (custom-read-from-string (# (+ i 1) prefs))) (set (# i prefs) (# 0 res))) (set (# i prefs) (# 0 (# (+ i 1) prefs)))) (setq i (+ i 2))))) (defun custom-save-preferences () (if custom-preferences-changed (custom-save-file custom-preference-file custom-global-preferences)) (setq custom-preferences-changed ())) (defun custom-load-preferences () (setq custom-global-preferences (custom-read-file custom-preference-file)) (custom-apply-preferences custom-global-preferences) (setq custom-preferences-changed ())) (defun custom-insert-symbols (lst syms) (with (len (length lst) i (- len 1) syms (copy syms) s1 () s2 ()) (while (and (> i 0) (= (type (# i lst)) 'list)) (setq i (- i 1))) (setq s1 (sublist 0 (+ i 1) lst)) (setq s2 (sublist (+ i 1) len lst)) (setq i (- (length syms) 1)) (while (> i -1) (if (member (# i syms) s1) (delete-nth i syms)) (setq i (- i 1))) (+ s1 syms s2))) (defun custom-insert-hook (head hook) (if (not (= (type head) 'list)) (list head 'hook hook) (not (member 'hook head)) (+ head (list 'hook hook)) (with (oh (# 'hook head)) (## 'hook head (if (not oh) hook (and (= (type oh) 'list) (= (# 0 oh) 'progn)) (+ oh (list hook)) (list 'progn oh hook)))))) (defun custom-find-name (lst name) (with (len (length lst) i 1) (while (and (< i len) (not (and (= (type (# i lst)) 'list) (or (and (= (type (# 0 (# i lst))) 'string) (= (# 0 (# i lst)) name)) (and (= (type (# 0 (# i lst))) 'list) (= (type (# 0 (# 0 (# i lst)))) 'string) (= (# 0 (# 0 (# i lst))) name)))))) (setq i (+ i 1))) (if (< i len) i ()))) (defun custom-install-symbols (name syms) (if (not name) (setq custom-global-menu-descr (custom-insert-symbols custom-global-menu-descr syms)) (with (name (if (not (= (type name) 'list)) (list name) name) len (length name) i 0 lst1 () pos1 () lst custom-global-menu-descr pos ()) (while (< i len) (setq pos (custom-find-name lst (# i name))) (if (not pos) (progn (if pos1 (## pos1 lst1 (setq lst (+ lst (list (list (# i name)))))) (setq lst (setq custom-global-menu-descr (+ custom-global-menu-descr (list (list (# i name))))))) (setq pos (- (length lst) 1)))) (setq pos1 pos) (setq lst1 lst) (setq lst (# pos1 lst1)) (setq i (+ i 1))) (## pos1 lst1 (custom-insert-symbols lst syms))))) (defun custom-install-hook (name hook) (if (not name) (## 0 custom-global-menu-descr (custom-insert-hook (# 0 custom-global-menu-descr) hook)) (with (name (if (not (= (type name) 'list)) (list name) name) len (length name) i 0 lst1 () pos1 () lst custom-global-menu-descr pos ()) (while (< i len) (setq pos (custom-find-name lst (# i name))) (if (not pos) (progn (if pos1 (## pos1 lst1 (setq lst (+ lst (list (list (# i name)))))) (setq lst (setq custom-global-menu-descr (+ custom-global-menu-descr (list (list (# i name))))))) (setq pos (- (length lst) 1)))) (setq pos1 pos) (setq lst1 lst) (setq lst (# pos1 lst1)) (setq i (+ i 1))) (## 0 lst (custom-insert-hook (# 0 lst) hook))))) (defun custom-global-menu () (custom-menu custom-global-menu-descr)) ;; Now, let this package install itself (custom-install-symbols "custom-menu" '(custom-font custom-symbol-font custom-value-font custom-value-font-slant custom-foreground custom-background custom-button-background custom-value-background custom-value-frame custom-immediate-feedback custom-immediate-change custom-keep-comma-string) ) (custom-install-hook "custom-menu" '(custom-refresh-menu))