home *** CD-ROM | disk | FTP | other *** search
- ;;; -*- Mode:Common-Lisp; Package:XLIB; Syntax:COMMON-LISP; Base:10; Lowercase:T -*-
-
- ;; RESOURCE - Lisp version of XLIB's Xrm resource manager
-
- ;;;
- ;;; TEXAS INSTRUMENTS INCORPORATED
- ;;; P.O. BOX 2909
- ;;; AUSTIN, TEXAS 78769
- ;;;
- ;;; Copyright (C) 1987 Texas Instruments Incorporated.
- ;;;
- ;;; Permission is granted to any individual or institution to use, copy, modify,
- ;;; and distribute this software, provided that this complete copyright and
- ;;; permission notice is maintained, intact, in all copies and supporting
- ;;; documentation.
- ;;;
- ;;; Texas Instruments Incorporated provides this software "as is" without
- ;;; express or implied warranty.
- ;;;
-
- (in-package :xlib)
-
- ;; The C version of this uses a 64 entry hash table at each entry.
- ;; Small hash tables lose in Lisp, so we do linear searches on lists.
-
- (defstruct (resource-database (:copier nil) (:predicate nil)
- (:print-function print-resource-database)
- (:constructor make-resource-database-internal)
- #+explorer (:callable-constructors nil)
- )
- (name nil :type stringable :read-only t)
- (value nil)
- (tight nil :type list) ;; List of resource-database
- (loose nil :type list) ;; List of resource-database
- )
-
- (defun print-resource-database (database stream depth)
- (declare (type resource-database database)
- (ignore depth))
- (print-unreadable-object (database stream :type t)
- (write-string (string (resource-database-name database)) stream)
- (when (resource-database-value database)
- (write-string " " stream)
- (prin1 (resource-database-value database) stream))))
-
- ;; The value slot of the top-level resource-database structure is used for a
- ;; time-stamp.
-
- (defun make-resource-database ()
- ;; Make a resource-database with initial timestamp of 0
- (make-resource-database-internal :name "Top-Level" :value 0))
-
- (defun resource-database-timestamp (database)
- (declare (type resource-database database))
- (resource-database-value database))
-
- (defun incf-resource-database-timestamp (database)
- ;; Increment the timestamp
- (declare (type resource-database database))
- (let ((timestamp (resource-database-value database)))
- (setf (resource-database-value database)
- (if (= timestamp most-positive-fixnum)
- most-negative-fixnum
- (1+ timestamp)))))
-
- ;; DEBUG FUNCTION (not exported)
- (defun print-db (entry &optional (level 0) type)
- ;; Debug function to print a resource database
- (format t "~%~v@t~s~:[~; *~]~@[ Value ~s~]"
- level
- (resource-database-name entry)
- (eq type 'loose)
- (resource-database-value entry))
- (when (resource-database-tight entry)
- (dolist (tight (resource-database-tight entry))
- (print-db tight (+ 2 level) 'tight)))
- (when (resource-database-loose entry)
- (dolist (loose (resource-database-loose entry))
- (print-db loose (+ 2 level) 'loose))))
-
- ;; DEBUG FUNCTION
- #+comment
- (defun print-search-table (table)
- (terpri)
- (dolist (dbase-list table)
- (format t "~%~s" dbase-list)
- (dolist (db dbase-list)
- (print-db db)
- (dolist (dblist table)
- (unless (eq dblist dbase-list)
- (when (member db dblist)
- (format t " duplicate at ~s" db))))
- )))
-
- ;;
- ;; If this is true, resource symbols will be compared in a case-insensitive
- ;; manner, and converting a resource string to a keyword will uppercaseify it.
- ;;
- (defparameter *uppercase-resource-symbols* nil)
-
- (defun resource-key (stringable)
- ;; Ensure STRINGABLE is a keyword.
- (declare (type stringable stringable))
- (etypecase stringable
- (symbol
- (if (keywordp (the symbol stringable))
- stringable
- (kintern (symbol-name (the symbol stringable)))))
- (string
- (if *uppercase-resource-symbols*
- (setq stringable (#-allegro string-upcase #+allegro correct-case
- (the string stringable))))
- (kintern (the string stringable)))))
-
- (defun stringable-equal (a b)
- ;; Compare two stringables.
- ;; Ignore case when comparing to a symbol.
- (declare (type stringable a b))
- (declare (values boolean))
- (etypecase a
- (string
- (etypecase b
- (string
- (string= (the string a) (the string b)))
- (symbol
- (if *uppercase-resource-symbols*
- (string-equal (the string a)
- (the string (symbol-name (the symbol b))))
- (string= (the string a)
- (the string (symbol-name (the symbol b))))))))
- (symbol
- (etypecase b
- (string
- (if *uppercase-resource-symbols*
- (string-equal (the string (symbol-name (the symbol a)))
- (the string b))
- (string= (the string (symbol-name (the symbol a)))
- (the string b))))
- (symbol
- (string= (the string (symbol-name (the symbol a)))
- (the string (symbol-name (the symbol b)))))))))
-
-
- ;;;-----------------------------------------------------------------------------
- ;;; Add/delete resource
-
- (defun add-resource (database name-list value)
- ;; name-list is a list of either strings or symbols. If a symbol,
- ;; case-insensitive comparisons will be used, if a string,
- ;; case-sensitive comparisons will be used. The symbol '* or
- ;; string "*" are used as wildcards, matching anything or nothing.
- (declare (type resource-database database)
- (type list name-list) ;; (list stringable)
- (type t value))
- (unless value (error "Null resource values are ignored"))
- (incf-resource-database-timestamp database)
- (do* ((list name-list (cdr list))
- (name (car list) (car list))
- (node database)
- (loose-p nil))
- ((endp list)
- (setf (resource-database-value node) value))
- ;; Key is the first name that isn't *
- (if (stringable-equal name "*")
- (setq loose-p t)
- ;; find the entry associated with name
- (progn
- (do ((entry (if loose-p
- (resource-database-loose node)
- (resource-database-tight node))
- (cdr entry)))
- ((endp entry)
- ;; Entry not found - create a new one
- (setq entry (make-resource-database-internal :name name))
- (if loose-p
- (push entry (resource-database-loose node))
- (push entry (resource-database-tight node)))
- (setq node entry))
- (when (stringable-equal name (resource-database-name (car entry)))
- ;; Found entry - use it
- (return (setq node (car entry)))))
- (setq loose-p nil)))))
-
-
- (defun delete-resource (database name-list)
- (declare (type resource-database database)
- (type list name-list))
- (incf-resource-database-timestamp database)
- (delete-resource-internal database name-list))
-
- (defun delete-resource-internal (database name-list)
- (declare (type resource-database database)
- (type list name-list)) ;; (list stringable)
- (do* ((list name-list (cdr list))
- (string (car list) (car list))
- (node database)
- (loose-p nil))
- ((endp list) nil)
- ;; Key is the first name that isn't *
- (if (stringable-equal string "*")
- (setq loose-p t)
- ;; find the entry associated with name
- (progn
- (do* ((first-entry (if loose-p
- (resource-database-loose node)
- (resource-database-tight node)))
- (entry-list first-entry (cdr entry-list))
- (entry (car entry-list) (car entry-list)))
- ((endp entry-list)
- ;; Entry not found - exit
- (return-from delete-resource-internal nil))
- (when (stringable-equal string (resource-database-name entry))
- (when (cdr list) (delete-resource-internal entry (cdr list)))
- (when (and (null (resource-database-loose entry))
- (null (resource-database-tight entry)))
- (if loose-p
- (setf (resource-database-loose node)
- (delete entry (resource-database-loose node)
- :test #'eq :count 1))
- (setf (resource-database-tight node)
- (delete entry (resource-database-tight node)
- :test #'eq :count 1))))
- (return-from delete-resource-internal t)))
- (setq loose-p nil)))))
-
- ;;;-----------------------------------------------------------------------------
- ;;; Get Resource
-
- (defun get-resource (database value-name value-class full-name full-class)
- ;; Return the value of the resource in DATABASE whose partial name
- ;; most closely matches (append full-name (list value-name)) and
- ;; (append full-class (list value-class)).
- (declare (type resource-database database)
- (type stringable value-name value-class)
- (type list full-name full-class)) ;; (list stringable)
- (declare (values value))
- (let ((names (append full-name (list value-name)))
- (classes (append full-class (list value-class))))
- (let* ((result (get-entry (resource-database-tight database)
- (resource-database-loose database)
- names classes)))
- (when result
- (resource-database-value result)))))
-
- (defun get-entry-lookup (table name names classes)
- (declare (type list table names classes)
- (symbol name))
- (dolist (entry table)
- (declare (type resource-database entry))
- (when (stringable-equal name (resource-database-name entry))
- (if (null (cdr names))
- (return entry)
- (let ((result (get-entry (resource-database-tight entry)
- (resource-database-loose entry)
- (cdr names) (cdr classes))))
- (declare (type (or null resource-database) result))
- (when result
- (return result)
- ))))))
-
- (defun get-entry (tight loose names classes &aux result)
- (declare (type list tight loose names classes))
- (let ((name (car names))
- (class (car classes)))
- (declare (type symbol name class))
- (cond ((and tight
- (get-entry-lookup tight name names classes)))
- ((and loose
- (get-entry-lookup loose name names classes)))
- ((and tight
- (not (stringable-equal name class))
- (get-entry-lookup tight class names classes)))
- ((and loose
- (not (stringable-equal name class))
- (get-entry-lookup loose class names classes)))
- (loose
- (loop
- (pop names) (pop classes)
- (unless (and names classes) (return nil))
- (setq name (car names)
- class (car classes))
- (when (setq result (get-entry-lookup loose name names classes))
- (return result))
- (when (and (not (stringable-equal name class))
- (setq result
- (get-entry-lookup loose class names classes)))
- (return result))
- )))))
-
-
- ;;;-----------------------------------------------------------------------------
- ;;; Get-resource with search-table
-
- (defun get-search-resource (table name class)
- ;; (get-search-resource (get-search-table database full-name full-class)
- ;; value-name value-class)
- ;; is equivalent to
- ;; (get-resource database value-name value-class full-name full-class)
- ;; But since most of the work is done by get-search-table,
- ;; get-search-resource is MUCH faster when getting several resources with
- ;; the same full-name/full-class
- (declare (type list table)
- (type stringable name class))
- (let ((do-class (and class (not (stringable-equal name class)))))
- (dolist (dbase-list table)
- (declare (type list dbase-list))
- (dolist (dbase dbase-list)
- (declare (type resource-database dbase))
- (when (stringable-equal name (resource-database-name dbase))
- (return-from get-search-resource
- (resource-database-value dbase))))
- (when do-class
- (dolist (dbase dbase-list)
- (declare (type resource-database dbase))
- (when (stringable-equal class (resource-database-name dbase))
- (return-from get-search-resource
- (resource-database-value dbase))))))))
-
- (defvar *get-table-result*)
-
- (defun get-search-table (database full-name full-class)
- ;; Return a search table for use with get-search-resource.
- (declare (type resource-database database)
- (type list full-name full-class)) ;; (list stringable)
- (declare (values value))
- (let* ((tight (resource-database-tight database))
- (loose (resource-database-loose database))
- (result (cons nil nil))
- (*get-table-result* result))
- (declare (type list tight loose)
- (type cons result))
- (when (or tight loose)
- (when full-name
- (get-tables tight loose full-name full-class))
-
- ;; Pick up bindings of the form (* name). These are the elements of
- ;; top-level loose without further tight/loose databases.
- ;;
- ;; (Hack: these bindings belong in ANY search table, so recomputing them
- ;; is a drag. True fix involves redesigning entire lookup
- ;; data-structure/algorithm.)
- ;;
- (let ((universal-bindings
- (remove nil loose :test-not #'eq
- :key #'(lambda (database)
- (or (resource-database-tight database)
- (resource-database-loose database))))))
- (when universal-bindings
- (setf (cdr *get-table-result*) (list universal-bindings)))))
- (cdr result)))
-
- (defun get-tables-lookup (dbase name names classes)
- (declare (type list dbase names classes)
- (type symbol name))
- (declare (optimize speed))
- (dolist (entry dbase)
- (declare (type resource-database entry))
- (when (stringable-equal name (resource-database-name entry))
- (let ((tight (resource-database-tight entry))
- (loose (resource-database-loose entry)))
- (declare (type list tight loose))
- (when (or tight loose)
- (if (cdr names)
- (get-tables tight loose (cdr names) (cdr classes))
- (when tight
- (let ((result *get-table-result*))
- ;; Put tight at end of *get-table-result*
- (setf (cdr result)
- (setq *get-table-result* (cons tight nil))))))
- (when loose
- (let ((result *get-table-result*))
- ;; Put loose at end of *get-table-result*
- (setf (cdr result)
- (setq *get-table-result* (cons loose nil))))))))))
-
- (defun get-tables (tight loose names classes)
- (declare (type list tight loose names classes))
- (let ((name (car names))
- (class (car classes)))
- (declare (type symbol name class))
- (when tight
- (get-tables-lookup tight name names classes))
- (when loose
- (get-tables-lookup loose name names classes))
- (when (and tight (not (stringable-equal name class)))
- (get-tables-lookup tight class names classes))
- (when (and loose (not (stringable-equal name class)))
- (get-tables-lookup loose class names classes))
- (when loose
- (loop
- (pop names) (pop classes)
- (unless (and names classes) (return nil))
- (setq name (car names)
- class (car classes))
- (get-tables-lookup loose name names classes)
- (unless (stringable-equal name class)
- (get-tables-lookup loose class names classes))
- ))))
-
-
- ;;;-----------------------------------------------------------------------------
- ;;; Utility functions
-
- (defun map-resource (database function &rest args)
- ;; Call FUNCTION on each resource in DATABASE.
- ;; FUNCTION is called with arguments (name-list value . args)
- (declare (type resource-database database)
- (type (function (list t &rest t) t) function)
- #+clx-ansi-common-lisp
- (dynamic-extent function)
- #+(and lispm (not clx-ansi-common-lisp))
- (sys:downward-funarg function)
- (dynamic-extent args))
- (declare (values nil))
- (labels ((map-resource-internal (database function args name)
- (declare (type resource-database database)
- (type (function (list t &rest t) t) function)
- (type list name)
- #+clx-ansi-common-lisp
- (dynamic-extent function)
- #+(and lispm (not clx-ansi-common-lisp))
- (sys:downward-funarg function))
- (let ((tight (resource-database-tight database))
- (loose (resource-database-loose database)))
- (declare (type list tight loose))
- (dolist (resource tight)
- (declare (type resource-database resource))
- (let ((value (resource-database-value resource))
- (name (append
- name
- (list (resource-database-name resource)))))
- (if value
- (apply function name value args)
- (map-resource-internal resource function args name))))
- (dolist (resource loose)
- (declare (type resource-database resource))
- (let ((value (resource-database-value resource))
- (name (append
- name
- (list "*" (resource-database-name resource)))))
- (if value
- (apply function name value args)
- (map-resource-internal resource function args name)))))))
- (map-resource-internal database function args nil)))
-
- (defun merge-resources (database with-database)
- (declare (type resource-database database with-database))
- (declare (values resource-database))
- (map-resource
- database
- #'(lambda (name value database)
- (add-resource database name value))
- with-database)
- with-database)
-
- (defun char-memq (key char)
- ;; Used as a test function for POSITION
- (declare (type base-char char))
- (member char key))
-
- (defmacro resource-with-open-file ((stream pathname &rest options) &body body)
- ;; Private WITH-OPEN-FILE, which, when pathname is a stream, uses it as the
- ;; stream
- (let ((abortp (gensym))
- (streamp (gensym)))
- `(let* ((,abortp t)
- (,streamp (streamp pathname))
- (,stream (if ,streamp pathname (open ,pathname ,@options))))
- (unwind-protect
- (multiple-value-prog1
- (progn ,@body)
- (setq ,abortp nil))
- (unless ,streamp
- (close stream :abort ,abortp))))))
-
- (defun read-resources (database pathname &key key test test-not)
- ;; Merges resources from a file in standard X11 format with DATABASE.
- ;; KEY is a function used for converting value-strings, the default is
- ;; identity. TEST and TEST-NOT are predicates used for filtering
- ;; which resources to include in the database. They are called with
- ;; the name and results of the KEY function.
- (declare (type resource-database database)
- (type (or pathname string stream) pathname)
- (type (or null (function (string) t)) key)
- (type (or null (function (list t) boolean))
- test test-not))
- (declare (values resource-database))
- (resource-with-open-file (stream pathname)
- (loop
- (let ((string (read-line stream nil :eof)))
- (declare (type (or string keyword) string))
- (when (eq string :eof) (return database))
- (let* ((end (length string))
- (i (position '(#\tab #\space) string
- :test-not #'char-memq :end end))
- (term nil))
- (declare (type array-index end)
- (type (or null array-index) i term))
- (when i ;; else blank line
- (case (char string i)
- (#\! nil) ;; Comment - skip
- (#.(card8->char 0) nil) ;; terminator for C strings - skip
- (#\# ;; Include
- (setq term (position '(#\tab #\space) string :test #'char-memq
- :start i :end end))
- (when (string-equal string "#INCLUDE" :start1 i :end1 term)
- (let ((path (merge-pathnames
- (subseq string (1+ term)) (truename stream))))
- (read-resources database path
- :key key :test test :test-not test-not))))
- (otherwise
- (multiple-value-bind (name-list value)
- (parse-resource string i end)
- (when name-list
- (when key (setq value (funcall key value)))
- (when
- (cond (test (funcall test name-list value))
- (test-not (not (funcall test-not name-list value)))
- (t t))
- (add-resource database name-list value))))))))))))
-
- (defun parse-resource (string &optional (start 0) end)
- ;; Parse a resource specfication string into a list of names and a value
- ;; string
- (declare (type string string)
- (type array-index start)
- (type (or null array-index) end))
- (declare (values name-list value))
- (do ((i start)
- (end (or end (length string)))
- (term)
- (name-list))
- ((>= i end))
- (declare (type array-index end)
- (type (or null array-index) i term))
- (setq term (position '(#\. #\* #\:) string
- :test #'char-memq :start i :end end))
- (case (and term (char string term))
- ;; Name seperator
- (#\. (when (> term i)
- (push (subseq string i term) name-list)))
- ;; Wildcard seperator
- (#\* (when (> term i)
- (push (subseq string i term) name-list))
- (push '* name-list))
- ;; Value separator
- (#\:
- (push (subseq string i term) name-list)
- (return
- (values
- (nreverse name-list)
- (string-trim '(#\tab #\space) (subseq string (1+ term))))))
- (otherwise
- (return
- (values
- (nreverse name-list)
- (subseq string i term)))))
- (setq i (1+ term))))
-
- (defun write-resources (database pathname &key write test test-not)
- ;; Write resources to PATHNAME in the standard X11 format.
- ;; WRITE is a function used for writing values, the default is #'princ
- ;; TEST and TEST-NOT are predicates used for filtering which resources
- ;; to include in the database. They are called with the name and value.
- (declare (type resource-database database)
- (type (or pathname string stream) pathname)
- (type (or null (function (string stream) t)) write)
- (type (or null (function (list t) boolean))
- test test-not))
- (resource-with-open-file (stream pathname :direction :output)
- (map-resource
- database
- #'(lambda (name-list value stream write test test-not)
- (when
- (cond (test (funcall test name-list value))
- (test-not (not (funcall test-not name-list value)))
- (t t))
- (let ((previous (car name-list)))
- (princ previous stream)
- (dolist (name (cdr name-list))
- (unless (or (stringable-equal name "*")
- (stringable-equal previous "*"))
- (write-char #\. stream))
- (setq previous name)
- (princ name stream)))
- (write-string ": " stream)
- (funcall write value stream)
- (terpri stream)))
- stream (or write #'princ) test test-not))
- database)
-
- (defun wm-resources (database window &key key test test-not)
- ;; Takes the resources associated with the RESOURCE_MANAGER property
- ;; of WINDOW (if any) and merges them with DATABASE.
- ;; KEY is a function used for converting value-strings, the default is
- ;; identity. TEST and TEST-NOT are predicates used for filtering
- ;; which resources to include in the database. They are called with
- ;; the name and results of the KEY function.
- (declare (type resource-database database)
- (type window window)
- (type (or null (function (string) t)) key)
- (type (or null (function (list t) boolean))
- test test-not))
- (declare (values resource-database))
- (let ((string (get-property window :RESOURCE_MANAGER :type :STRING
- :result-type 'string
- :transform #'xlib::card8->char)))
- (when string
- (with-input-from-string (stream string)
- (read-resources database stream
- :key key :test test :test-not test-not)))))
-
- (defun set-wm-resources (database window &key write test test-not)
- ;; Sets the resources associated with the RESOURCE_MANAGER property
- ;; of WINDOW.
- ;; WRITE is a function used for writing values, the default is #'princ
- ;; TEST and TEST-NOT are predicates used for filtering which resources
- ;; to include in the database. They are called with the name and value.
- (declare (type resource-database database)
- (type window window)
- (type (or null (function (string stream) t)) write)
- (type (or null (function (list t) boolean))
- test test-not))
- (xlib::set-string-property
- window :RESOURCE_MANAGER
- (with-output-to-string (stream)
- (write-resources database stream :write write
- :test test :test-not test-not))))
-
- (defun root-resources (screen &key database key test test-not)
- "Returns a resource database containing the contents of the root window
- RESOURCE_MANAGER property for the given SCREEN. If SCREEN is a display,
- then its default screen is used. If an existing DATABASE is given, then
- resource values are merged with the DATABASE and the modified DATABASE is
- returned.
-
- TEST and TEST-NOT are predicates for selecting which resources are
- read. Arguments are a resource name list and a resource value. The KEY
- function, if given, is called to convert a resource value string to the
- value given to TEST or TEST-NOT."
-
- (declare (type (or screen display) screen)
- (type (or null resource-database) database)
- (type (or null (function (string) t)) key)
- (type (or null (function (list t) boolean)) test test-not)
- (values resource-database))
- (let* ((screen (if (type? screen 'display)
- (display-default-screen screen)
- screen))
- (window (screen-root screen))
- (database (or database (make-resource-database))))
- (wm-resources database window :key key :test test :test-not test-not)
- database))
-
- (defun set-root-resources (screen &key test test-not (write 'princ) database)
- "Changes the contents of the root window RESOURCE_MANAGER property for the
- given SCREEN. If SCREEN is a display, then its default screen is used.
-
- TEST and TEST-NOT are predicates for selecting which resources from the
- DATABASE are written. Arguments are a resource name list and a resource
- value. The WRITE function is used to convert a resource value into a
- string stored in the property."
-
- (declare (type (or screen display) screen)
- (type (or null resource-database) database)
- (type (or null (function (list t) boolean)) test test-not)
- (type (or null (function (string stream) t)) write)
- (values resource-database))
- (let* ((screen (if (type? screen 'display)
- (display-default-screen screen)
- screen))
- (window (screen-root screen)))
- (set-wm-resources database window
- :write write :test test :test-not test-not)
- database))
-
- (defsetf root-resources set-root-resources)
-
- (defun initialize-resource-database (display)
- ;; This function is (supposed to be) equivalent to the Xlib initialization
- ;; code.
- (declare (type display display))
- (let ((rdb (make-resource-database))
- (rootwin (screen-root (car (display-roots display)))))
- ;; First read the server defaults if present, otherwise from the default
- ;; resource file
- (if (get-property rootwin :RESOURCE_MANAGER)
- (xlib:wm-resources rdb rootwin)
- (let ((path (default-resources-pathname)))
- (when (and path (probe-file path))
- (read-resources rdb path))))
- ;; Next read from the resources file
- (let ((path (resources-pathname)))
- (when (and path (probe-file path))
- (read-resources rdb path)))
- (setf (display-xdefaults display) rdb)))
-