;;; -*- Mode: LISP; Syntax: Common-lisp; Package: XLIB; Base: 10; Lowercase: Yes -*- ;;; GContext ;;; ;;; 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. ;;; ;;; GContext values are usually cached locally in the GContext object. ;;; This is required because the X.11 server doesn't have any requests ;;; for getting GContext values back. ;;; ;;; GContext changes are cached until force-GContext-changes is called. ;;; All the requests that use GContext (including the GContext accessors, ;;; but not the SETF's) call force-GContext-changes. ;;; In addition, the macro WITH-GCONTEXT may be used to provide a ;;; local view if a GContext. ;;; ;;; Each GContext keeps a copy of the values the server has seen, and ;;; a copy altered by SETF, called the LOCAL-STATE (bad name...). ;;; The SETF accessors increment a timestamp in the GContext. ;;; When the timestamp in a GContext isn't equal to the timestamp in ;;; the local-state, changes have been made, and force-GContext-changes ;;; loops through the GContext and local-state, sending differences to ;;; the server, and updating GContext. ;;; ;;; WITH-GCONTEXT works by BINDING the local-state slot in a GContext to ;;; a private copy. This is easy (and fast) for lisp machines, but other ;;; lisps will have problems. Fortunately, most other lisps don't care, ;;; because they don't run in a multi-processing shared-address space ;;; environment. (in-package :xlib) ;; GContext state accessors ;; The state vector contains all card32s to speed server updating (eval-when (eval compile load) (defconstant *gcontext-fast-change-length* #.(length *gcontext-components*)) (macrolet ((def-gc-internals (name &rest extras) (let ((macros nil) (indexes nil) (masks nil) (index 0)) (dolist (name *gcontext-components*) (push `(defmacro ,(xintern 'gcontext-internal- name) (state) `(svref ,state ,,index)) macros) (setf (getf indexes name) index) (push (ash 1 index) masks) (incf index)) (dolist (extra extras) (push `(defmacro ,(xintern 'gcontext-internal- (first extra)) (state) `(svref ,state ,,index)) macros) ;; don't override already correct index entries (unless (or (getf indexes (second extra)) (getf indexes (first extra))) (setf (getf indexes (or (second extra) (first extra))) index)) (push (logior (ash 1 index) (if (second extra) (ash 1 (position (second extra) *gcontext-components*)) 0)) masks) (incf index)) `(within-definition (def-gc-internals ,name) ,@(nreverse macros) (eval-when (eval compile load) (defconstant *gcontext-data-length* ,index) (defconstant *gcontext-indexes* ',indexes) (defconstant *gcontext-masks* ',(coerce (nreverse masks) 'simple-vector))))))) (def-gc-internals ignore (:clip :clip-mask) (:dash :dashes) (:font-obj :font) (:timestamp))) ) ;; end EVAL-WHEN (deftype gcmask () '(unsigned-byte #.*gcontext-fast-change-length*)) (deftype xgcmask () '(unsigned-byte #.*gcontext-data-length*)) (defstruct (gcontext-extension (:type vector) (:copier nil)) ;; un-named (name nil :type symbol :read-only t) (default nil :type t :read-only t) (set-function #'identity :type (function (gcontext t) t) :read-only t) (copy-function #'identity :type (function (gcontext gcontext t) t) :read-only t)) (defvar *gcontext-extensions* nil) ;; list of gcontext-extension ;; Gcontext state Resource (defvar *gcontext-local-state-cache* nil) ;; List of unused gcontext local states (defmacro gcontext-state-next (state) `(svref ,state 0)) (defun allocate-gcontext-state () ;; Allocate a gcontext-state ;; Loop until a local state is found that's large enough to hold ;; any extensions that may exist. (let ((length (index+ *gcontext-data-length* (length *gcontext-extensions*)))) (declare (type array-index length)) (loop (let ((state (or (threaded-atomic-pop *gcontext-local-state-cache* gcontext-state-next gcontext-state) (make-array length :initial-element nil)))) (declare (type gcontext-state state)) (when (index>= (length state) length) (return state)))))) (defun deallocate-gcontext-state (state) (declare (type gcontext-state state)) (fill state nil) (threaded-atomic-push state *gcontext-local-state-cache* gcontext-state-next gcontext-state)) ;; Temp-Gcontext Resource (defvar *temp-gcontext-cache* nil) ;; List of unused gcontexts (defun allocate-temp-gcontext () (or (threaded-atomic-pop *temp-gcontext-cache* gcontext-next gcontext) (make-gcontext :local-state '#() :server-state '#()))) (defun deallocate-temp-gcontext (gc) (declare (type gcontext gc)) (threaded-atomic-push gc *temp-gcontext-cache* gcontext-next gcontext)) ;; For each argument to create-gcontext (except clip-mask and clip-ordering) declared ;; as (type ), there is an accessor: ;(defun gcontext- (gcontext) ; ;; The value will be nil if the last value stored is unknown (e.g., the cache was ; ;; off, or the component was copied from a gcontext with unknown state). ; (declare (type gcontext gcontext) ; (values ))) ;; For each argument to create-gcontext (except clip-mask and clip-ordering) declared ;; as (type (or null ) ), there is a setf for the corresponding accessor: ;(defsetf gcontext- (gcontext) (value) ; ) ;; Generate all the accessors and defsetf's for GContext (defmacro xgcmask->gcmask (mask) `(the gcmask (logand ,mask #.(1- (ash 1 *gcontext-fast-change-length*))))) (defmacro access-gcontext ((gcontext local-state) &body body) `(let ((,local-state (gcontext-local-state ,gcontext))) (declare (type gcontext-state ,local-state)) ,@body)) (defmacro modify-gcontext ((gcontext local-state) &body body) ;; The timestamp must be altered after the modification `(let ((,local-state (gcontext-local-state ,gcontext))) (declare (type gcontext-state ,local-state)) (prog1 (progn ,@body) (setf (gcontext-internal-timestamp ,local-state) 0)))) (defmacro def-gc-accessor (name type) (let* ((gcontext-name (xintern 'gcontext- name)) (internal-accessor (xintern 'gcontext-internal- name)) (internal-setfer (xintern 'set- gcontext-name))) `(within-definition (,name def-gc-accessor) (defun ,gcontext-name (gcontext) (declare (type gcontext gcontext)) (declare (values (or null ,type))) (let ((value (,internal-accessor (gcontext-local-state gcontext)))) (declare (type (or null card32) value)) (when value ;; Don't do anything when value isn't known (let ((%buffer (gcontext-display gcontext))) (declare (type display %buffer)) %buffer (decode-type ,type value))))) (defun ,internal-setfer (gcontext value) (declare (type gcontext gcontext) (type ,type value)) (modify-gcontext (gcontext local-state) (setf (,internal-accessor local-state) (encode-type ,type value)) ,@(when (eq type 'pixmap) ;; write-through pixmaps, because the protocol allows ;; the server to copy the pixmap contents at the time ;; of the store, rather than continuing to share with ;; the pixmap. `((let ((server-state (gcontext-server-state gcontext))) (setf (,internal-accessor server-state) nil)))) value)) (defsetf ,gcontext-name ,internal-setfer)))) (defmacro incf-internal-timestamp (state) (let ((ts (gensym))) `(let ((,ts (the fixnum (gcontext-internal-timestamp ,state)))) (declare (type fixnum ,ts)) ;; the probability seems low enough (setq ,ts (if (= ,ts most-positive-fixnum) 1 (the fixnum (1+ ,ts)))) (setf (gcontext-internal-timestamp ,state) ,ts)))) (def-gc-accessor function boole-constant) (def-gc-accessor plane-mask card32) (def-gc-accessor foreground card32) (def-gc-accessor background card32) (def-gc-accessor line-width card16) (def-gc-accessor line-style (member :solid :dash :double-dash)) (def-gc-accessor cap-style (member :not-last :butt :round :projecting)) (def-gc-accessor join-style (member :miter :round :bevel)) (def-gc-accessor fill-style (member :solid :tiled :stippled :opaque-stippled)) (def-gc-accessor fill-rule (member :even-odd :winding)) (def-gc-accessor tile pixmap) (def-gc-accessor stipple pixmap) (def-gc-accessor ts-x int16) ;; Tile-Stipple-X-origin (def-gc-accessor ts-y int16) ;; Tile-Stipple-Y-origin ;; (def-GC-accessor font font) ;; See below (def-gc-accessor subwindow-mode (member :clip-by-children :include-inferiors)) (def-gc-accessor exposures (member :off :on)) (def-gc-accessor clip-x int16) (def-gc-accessor clip-y int16) ;; (def-GC-accessor clip-mask) ;; see below (def-gc-accessor dash-offset card16) ;; (def-GC-accessor dashes) ;; see below (def-gc-accessor arc-mode (member :chord :pie-slice)) (defun gcontext-clip-mask (gcontext) (declare (type gcontext gcontext)) (declare (values (or null (member :none) pixmap rect-seq) (or null (member :unsorted :y-sorted :yx-sorted :yx-banded)))) (access-gcontext (gcontext local-state) (multiple-value-bind (clip clip-mask) (without-interrupts (values (gcontext-internal-clip local-state) (gcontext-internal-clip-mask local-state))) (if (null clip) (values (let ((%buffer (gcontext-display gcontext))) (declare (type display %buffer)) (decode-type (or (member :none) pixmap) clip-mask)) nil) (values (second clip) (decode-type (or null (member :unsorted :y-sorted :yx-sorted :yx-banded)) (first clip))))))) (defsetf gcontext-clip-mask (gcontext &optional ordering) (clip-mask) ;; A bit strange, but retains setf form. ;; a nil clip-mask is transformed to an empty vector `(set-gcontext-clip-mask ,gcontext ,ordering ,clip-mask)) (defun set-gcontext-clip-mask (gcontext ordering clip-mask) ;; a nil clip-mask is transformed to an empty vector (declare (type gcontext gcontext) (type (or null (member :unsorted :y-sorted :yx-sorted :yx-banded)) ordering) (type (or (member :none) pixmap rect-seq) clip-mask)) (unless clip-mask (x-type-error clip-mask '(or (member :none) pixmap rect-seq))) (multiple-value-bind (clip-mask clip) (typecase clip-mask (pixmap (values (pixmap-id clip-mask) nil)) ((member :none) (values 0 nil)) (sequence (values nil (list (encode-type (or null (member :unsorted :y-sorted :yx-sorted :yx-banded)) ordering) (copy-seq clip-mask)))) (otherwise (x-type-error clip-mask '(or (member :none) pixmap rect-seq)))) (modify-gcontext (gcontext local-state) (let ((server-state (gcontext-server-state gcontext))) (declare (type gcontext-state server-state)) (without-interrupts (setf (gcontext-internal-clip local-state) clip (gcontext-internal-clip-mask local-state) clip-mask) (if (null clip) (setf (gcontext-internal-clip server-state) nil) (setf (gcontext-internal-clip-mask server-state) nil)) (when (and clip-mask (not (zerop clip-mask))) ;; write-through clip-mask pixmap, because the protocol allows the ;; server to copy the pixmap contents at the time of the store, ;; rather than continuing to share with the pixmap. (setf (gcontext-internal-clip-mask server-state) nil)))))) clip-mask) (defun gcontext-dashes (gcontext) (declare (type gcontext gcontext)) (declare (values (or null card8 sequence))) (access-gcontext (gcontext local-state) (multiple-value-bind (dash dashes) (without-interrupts (values (gcontext-internal-dash local-state) (gcontext-internal-dashes local-state))) (if (null dash) dashes dash)))) (defsetf gcontext-dashes set-gcontext-dashes) (defun set-gcontext-dashes (gcontext dashes) (declare (type gcontext gcontext) (type (or card8 sequence) dashes)) (multiple-value-bind (dashes dash) (if (type? dashes 'sequence) (if (zerop (length dashes)) (x-type-error dashes '(or card8 sequence) "non-empty sequence") (values nil (or (copy-seq dashes) (vector)))) (values (encode-type card8 dashes) nil)) (modify-gcontext (gcontext local-state) (let ((server-state (gcontext-server-state gcontext))) (declare (type gcontext-state server-state)) (without-interrupts (setf (gcontext-internal-dash local-state) dash (gcontext-internal-dashes local-state) dashes) (if (null dash) (setf (gcontext-internal-dash server-state) nil) (setf (gcontext-internal-dashes server-state) nil)))))) dashes) (defun gcontext-font (gcontext &optional metrics-p) ;; If the stored font is known, it is returned. If it is not known and ;; metrics-p is false, then nil is returned. If it is not known and ;; metrics-p is true, then a pseudo font is returned. Full metric and ;; property information can be obtained, but the font does not have a name or ;; a resource-id, and attempts to use it where a resource-id is required will ;; result in an invalid-font error. (declare (type gcontext gcontext) (type boolean metrics-p)) (declare (values (or null font))) (access-gcontext (gcontext local-state) (let ((font (gcontext-internal-font-obj local-state))) (or font (when metrics-p ;; XXX this isn't correct (make-font :display (gcontext-display gcontext) :id (gcontext-id gcontext) :name nil)))))) (defsetf gcontext-font set-gcontext-font) (defun set-gcontext-font (gcontext font) (declare (type gcontext gcontext) (type fontable font)) (let* ((font-object (if (font-p font) font (open-font (gcontext-display gcontext) font))) (font (and font-object (font-id font-object)))) ;; XXX need to check font has id (and name?) (modify-gcontext (gcontext local-state) (let ((server-state (gcontext-server-state gcontext))) (declare (type gcontext-state server-state)) (without-interrupts (setf (gcontext-internal-font-obj local-state) font-object (gcontext-internal-font local-state) font) ;; check against font, not against font-obj (if (null font) (setf (gcontext-internal-font server-state) nil) (setf (gcontext-internal-font-obj server-state) font-object)))))) font) (defun force-gcontext-changes-internal (gcontext) ;; Force any delayed changes. (declare (type gcontext gcontext)) #.(declare-buffun) (let ((display (gcontext-display gcontext)) (server-state (gcontext-server-state gcontext)) (local-state (gcontext-local-state gcontext))) (declare (type display display) (type gcontext-state server-state local-state)) ;; Update server when timestamps don't match (unless (= (the fixnum (gcontext-internal-timestamp local-state)) (the fixnum (gcontext-internal-timestamp server-state))) ;; The display is already locked. (macrolet ((with-buffer ((buffer &key timeout) &body body) `(progn (progn ,buffer ,@(and timeout `(,timeout)) nil) ,@body))) ;; Because there is no locking on the local state we have to ;; assume that state will change and set timestamps up front, ;; otherwise by the time we figured out there were no changes ;; and tried to store the server stamp as the local stamp, the ;; local stamp might have since been modified. (setf (gcontext-internal-timestamp local-state) (incf-internal-timestamp server-state)) (block no-changes (let ((last-request (buffer-last-request display))) (with-buffer-request (display *x-changegc*) (gcontext gcontext) (progn (do ((i 0 (index+ i 1)) (bit 1 (the xgcmask (ash bit 1))) (nbyte 12) (mask 0) (local 0)) ((index>= i *gcontext-fast-change-length*) (when (zerop mask) ;; If nothing changed, restore last-request and quit (setf (buffer-last-request display) (if (zerop (buffer-last-request display)) nil last-request)) (return-from no-changes nil)) (card29-put 8 mask) (card16-put 2 (index-ash nbyte -2)) (index-incf (buffer-boffset display) nbyte)) (declare (type array-index i nbyte) (type xgcmask bit) (type gcmask mask) (type (or null card32) local)) (unless (eql (the (or null card32) (svref server-state i)) (setq local (the (or null card32) (svref local-state i)))) (setf (svref server-state i) local) (card32-put nbyte local) (setq mask (the gcmask (logior mask bit))) (index-incf nbyte 4))))))) ;; Update GContext extensions (do ((extension *gcontext-extensions* (cdr extension)) (i *gcontext-data-length* (index+ i 1)) (local)) ((endp extension)) (unless (eql (svref server-state i) (setq local (svref local-state i))) (setf (svref server-state i) local) (funcall (gcontext-extension-set-function (car extension)) gcontext local))) ;; Update clipping rectangles (multiple-value-bind (local-clip server-clip) (without-interrupts (values (gcontext-internal-clip local-state) (gcontext-internal-clip server-state))) (unless (equalp local-clip server-clip) (setf (gcontext-internal-clip server-state) nil) (unless (null local-clip) (with-buffer-request (display *x-setcliprectangles*) (data (first local-clip)) (gcontext gcontext) ;; XXX treat nil correctly (card16 (or (gcontext-internal-clip-x local-state) 0) (or (gcontext-internal-clip-y local-state) 0)) ;; XXX this has both int16 and card16 values ((sequence :format int16) (second local-clip))) (setf (gcontext-internal-clip server-state) local-clip)))) ;; Update dashes (multiple-value-bind (local-dash server-dash) (without-interrupts (values (gcontext-internal-dash local-state) (gcontext-internal-dash server-state))) (unless (equalp local-dash server-dash) (setf (gcontext-internal-dash server-state) nil) (unless (null local-dash) (with-buffer-request (display *x-setdashes*) (gcontext gcontext) ;; XXX treat nil correctly (card16 (or (gcontext-internal-dash-offset local-state) 0) (length local-dash)) ((sequence :format card8) local-dash)) (setf (gcontext-internal-dash server-state) local-dash)))))))) (defun force-gcontext-changes (gcontext) ;; Force any delayed changes. (declare (type gcontext gcontext)) (let ((display (gcontext-display gcontext)) (server-state (gcontext-server-state gcontext)) (local-state (gcontext-local-state gcontext))) (declare (type gcontext-state server-state local-state) (array-register server-state local-state)) ;; Update server when timestamps don't match (unless (= (the fixnum (gcontext-internal-timestamp local-state)) (the fixnum (gcontext-internal-timestamp server-state))) (with-display (display) (force-gcontext-changes-internal gcontext))))) ;;; WARNING: WITH-GCONTEXT WORKS MUCH MORE EFFICIENTLY WHEN THE OPTIONS BEING "BOUND" ARE ;;; SET IN THE GCONTEXT ON ENTRY. BECAUSE THERE'S NO WAY TO GET THE VALUE OF AN ;;; UNKNOWN GC COMPONENT, WITH-GCONTEXT MUST CREATE A TEMPORARY GC, COPY THE UNKNOWN ;;; COMPONENTS TO THE TEMPORARY GC, ALTER THE GC BEING USED, THEN COPY COMPOMENTS ;;; BACK. (defmacro with-gcontext ((gcontext &rest options &key clip-ordering &allow-other-keys) &body body) ;; "Binds" the gcontext components specified by options within the ;; dynamic scope of the body (i.e., indefinite scope and dynamic ;; extent), on a per-process basis in a multi-process environment. ;; The body is not surrounded by a with-display. If cache-p is nil or ;; the some component states are unknown, this will implement ;; save/restore by creating a temporary gcontext and doing ;; copy-gcontext-components to and from it. (declare (arglist (gcontext &rest options &key function plane-mask foreground background line-width line-style cap-style join-style fill-style fill-rule arc-mode tile stipple ts-x ts-y font subwindow-mode exposures clip-x clip-y clip-mask clip-ordering dash-offset dashes &allow-other-keys) &body body)) (remf options :clip-ordering) (let ((gc (gensym)) (saved-state (gensym)) (temp-gc (gensym)) (temp-mask (gensym)) (temp-vars nil) (setfs nil) (indexes nil) ; List of gcontext field indices (extension-indexes nil) ; List of gcontext extension field indices (ts-index (getf *gcontext-indexes* :timestamp))) (do* ((option options (cddr option)) (name (car option) (car option)) (value (cadr option) (cadr option))) ((endp option) (setq setfs (nreverse setfs))) (let ((index (getf *gcontext-indexes* name))) (if index (push index indexes) (let ((extension (find name *gcontext-extensions* :key #'gcontext-extension-name))) (if extension (progn (push (xintern "Internal-" 'gcontext- name "-State-Index") extension-indexes)) (x-type-error name 'gcontext-key))))) (let ((accessor `(,(xintern 'gcontext- name) ,gc ,@(when (eq name :clip-mask) `(,clip-ordering)))) (temp-var (gensym))) (when value (push `(,temp-var ,value) temp-vars) (push `(when ,temp-var (setf ,accessor ,temp-var)) setfs)))) (if setfs `(multiple-value-bind (,gc ,saved-state ,temp-mask ,temp-gc) (copy-gcontext-local-state ,gcontext ',indexes ,@extension-indexes) (declare (type gcontext ,gc) (type gcontext-state ,saved-state) (type xgcmask ,temp-mask) (type (or null resource-id) ,temp-gc)) (with-gcontext-bindings (,gc ,saved-state ,(append indexes extension-indexes) ,ts-index ,temp-mask ,temp-gc) (let ,temp-vars ,@setfs) ,@body)) `(progn ,@body)))) (defun copy-gcontext-local-state (gcontext indexes &rest extension-indices) ;; Called from WITH-GCONTEXT to save the fields in GCONTEXT indicated by MASK (declare (type gcontext gcontext) (type list indexes) (dynamic-extent extension-indices)) (let ((local-state (gcontext-local-state gcontext)) (saved-state (allocate-gcontext-state)) (cache-p (gcontext-cache-p gcontext))) (declare (type gcontext-state local-state saved-state)) (setf (gcontext-internal-timestamp saved-state) 1) (let ((temp-gc nil) (temp-mask 0) (extension-mask 0)) (declare (type xgcmask temp-mask) (type integer extension-mask)) (dolist (i indexes) (when (or (not (setf (svref saved-state i) (svref local-state i))) (not cache-p)) (setq temp-mask (the xgcmask (logior temp-mask (the xgcmask (svref *gcontext-masks* i))))))) (dolist (i extension-indices) (when (or (not (setf (svref saved-state i) (svref local-state i))) (not cache-p)) (setq extension-mask (the xgcmask (logior extension-mask (ash 1 i)))))) (when (or (plusp temp-mask) (plusp extension-mask)) ;; Copy to temporary GC when field unknown or cache-p false (let ((display (gcontext-display gcontext))) (declare (type display display)) (with-display (display) (setq temp-gc (allocate-temp-gcontext)) (setf (gcontext-id temp-gc) (allocate-resource-id display gcontext 'gcontext) (gcontext-display temp-gc) display (gcontext-drawable temp-gc) (gcontext-drawable gcontext) (gcontext-server-state temp-gc) saved-state (gcontext-local-state temp-gc) saved-state) ;; Create a new (temporary) gcontext (with-buffer-request (display *x-creategc*) (gcontext temp-gc) (drawable (gcontext-drawable gcontext)) (card29 0)) ;; Copy changed components to the temporary gcontext (when (plusp temp-mask) (with-buffer-request (display *x-copygc*) (gcontext gcontext) (gcontext temp-gc) (card29 (xgcmask->gcmask temp-mask)))) ;; Copy extension fields to the new gcontext (when (plusp extension-mask) ;; Copy extension fields from temp back to gcontext (do ((bit (ash extension-mask (- *gcontext-data-length*)) (ash bit -1)) (i 0 (index+ i 1))) ((zerop bit)) (let ((copy-function (gcontext-extension-copy-function (elt *gcontext-extensions* i)))) (funcall copy-function gcontext temp-gc (svref local-state (index+ i *gcontext-data-length*)))))) ))) (values gcontext saved-state (logior temp-mask extension-mask) temp-gc)))) (defun restore-gcontext-temp-state (gcontext temp-mask temp-gc) (declare (type gcontext gcontext temp-gc) (type xgcmask temp-mask)) (let ((display (gcontext-display gcontext))) (declare (type display display)) (with-display (display) (with-buffer-request (display *x-copygc*) (gcontext temp-gc) (gcontext gcontext) (card29 (xgcmask->gcmask temp-mask))) ;; Copy extension fields from temp back to gcontext (do ((bit (ash temp-mask (- *gcontext-data-length*)) (ash bit -1)) (extensions *gcontext-extensions* (cdr extensions)) (i *gcontext-data-length* (index+ i 1)) (local-state (gcontext-local-state temp-gc))) ((zerop bit)) (let ((copy-function (gcontext-extension-copy-function (car extensions)))) (funcall copy-function temp-gc gcontext (svref local-state i)))) ;; free gcontext (with-buffer-request (display *x-freegc*) (gcontext temp-gc)) (deallocate-resource-id display (gcontext-id temp-gc) 'gcontext) (deallocate-temp-gcontext temp-gc) ;; Copy saved state back to server state (do ((server-state (gcontext-server-state gcontext)) (bit (xgcmask->gcmask temp-mask) (the gcmask (ash bit -1))) (i 0 (index+ i 1))) ((zerop bit) (incf-internal-timestamp server-state)) (declare (type gcontext-state server-state) (type gcmask bit) (type array-index i)) (when (oddp bit) (setf (svref server-state i) nil)))))) (defun create-gcontext (&rest options &key (drawable (required-arg drawable)) function plane-mask foreground background line-width line-style cap-style join-style fill-style fill-rule arc-mode tile stipple ts-x ts-y font subwindow-mode exposures clip-x clip-y clip-mask clip-ordering dash-offset dashes (cache-p t) &allow-other-keys) ;; Only non-nil components are passed on in the request, but for effective caching ;; assumptions have to be made about what the actual protocol defaults are. For ;; all gcontext components, a value of nil causes the default gcontext value to be ;; used. For clip-mask, this implies that an empty rect-seq cannot be represented ;; as a list. Note: use of stringable as font will cause an implicit open-font. ;; Note: papers over protocol SetClipRectangles and SetDashes special cases. If ;; cache-p is true, then gcontext state is cached locally, and changing a gcontext ;; component will have no effect unless the new value differs from the cached ;; value. Component changes (setfs and with-gcontext) are always deferred ;; regardless of the cache mode, and sent over the protocol only when required by a ;; local operation or by an explicit call to force-gcontext-changes. (declare (type drawable drawable) ; Required to be non-null (type (or null boole-constant) function) (type (or null pixel) plane-mask foreground background) (type (or null card16) line-width dash-offset) (type (or null int16) ts-x ts-y clip-x clip-y) (type (or null (member :solid :dash :double-dash)) line-style) (type (or null (member :not-last :butt :round :projecting)) cap-style) (type (or null (member :miter :round :bevel)) join-style) (type (or null (member :solid :tiled :opaque-stippled :stippled)) fill-style) (type (or null (member :even-odd :winding)) fill-rule) (type (or null (member :chord :pie-slice)) arc-mode) (type (or null pixmap) tile stipple) (type (or null fontable) font) (type (or null (member :clip-by-children :include-inferiors)) subwindow-mode) (type (or null (member :on :off)) exposures) (type (or null (member :none) pixmap rect-seq) clip-mask) (type (or null (member :unsorted :y-sorted :yx-sorted :yx-banded)) clip-ordering) (type (or null card8 sequence) dashes) (dynamic-extent options) (type boolean cache-p)) (declare (values gcontext)) (let* ((display (drawable-display drawable)) (gcontext (make-gcontext :display display :drawable drawable :cache-p cache-p)) (local-state (gcontext-local-state gcontext)) (server-state (gcontext-server-state gcontext)) (gcontextid (allocate-resource-id display gcontext 'gcontext))) (declare (type display display) (type gcontext gcontext) (type resource-id gcontextid) (type gcontext-state local-state server-state)) (setf (gcontext-id gcontext) gcontextid) (unless function (setf (gcontext-function gcontext) boole-1)) ;; using the depth of the drawable would be better, but ... (unless plane-mask (setf (gcontext-plane-mask gcontext) #xffffffff)) (unless foreground (setf (gcontext-foreground gcontext) 0)) (unless background (setf (gcontext-background gcontext) 1)) (unless line-width (setf (gcontext-line-width gcontext) 0)) (unless line-style (setf (gcontext-line-style gcontext) :solid)) (unless cap-style (setf (gcontext-cap-style gcontext) :butt)) (unless join-style (setf (gcontext-join-style gcontext) :miter)) (unless fill-style (setf (gcontext-fill-style gcontext) :solid)) (unless fill-rule (setf (gcontext-fill-rule gcontext) :even-odd)) (unless arc-mode (setf (gcontext-arc-mode gcontext) :pie-slice)) (unless ts-x (setf (gcontext-ts-x gcontext) 0)) (unless ts-y (setf (gcontext-ts-y gcontext) 0)) (unless subwindow-mode (setf (gcontext-subwindow-mode gcontext) :clip-by-children)) (unless exposures (setf (gcontext-exposures gcontext) :on)) (unless clip-mask (setf (gcontext-clip-mask gcontext) :none)) (unless clip-x (setf (gcontext-clip-x gcontext) 0)) (unless clip-y (setf (gcontext-clip-y gcontext) 0)) (unless dashes (setf (gcontext-dashes gcontext) 4)) (unless dash-offset (setf (gcontext-dash-offset gcontext) 0)) ;; a bit kludgy, but ... (replace server-state local-state) (when function (setf (gcontext-function gcontext) function)) (when plane-mask (setf (gcontext-plane-mask gcontext) plane-mask)) (when foreground (setf (gcontext-foreground gcontext) foreground)) (when background (setf (gcontext-background gcontext) background)) (when line-width (setf (gcontext-line-width gcontext) line-width)) (when line-style (setf (gcontext-line-style gcontext) line-style)) (when cap-style (setf (gcontext-cap-style gcontext) cap-style)) (when join-style (setf (gcontext-join-style gcontext) join-style)) (when fill-style (setf (gcontext-fill-style gcontext) fill-style)) (when fill-rule (setf (gcontext-fill-rule gcontext) fill-rule)) (when arc-mode (setf (gcontext-arc-mode gcontext) arc-mode)) (when tile (setf (gcontext-tile gcontext) tile)) (when stipple (setf (gcontext-stipple gcontext) stipple)) (when ts-x (setf (gcontext-ts-x gcontext) ts-x)) (when ts-y (setf (gcontext-ts-y gcontext) ts-y)) (when font (setf (gcontext-font gcontext) font)) (when subwindow-mode (setf (gcontext-subwindow-mode gcontext) subwindow-mode)) (when exposures (setf (gcontext-exposures gcontext) exposures)) (when clip-x (setf (gcontext-clip-x gcontext) clip-x)) (when clip-y (setf (gcontext-clip-y gcontext) clip-y)) (when clip-mask (setf (gcontext-clip-mask gcontext clip-ordering) clip-mask)) (when dash-offset (setf (gcontext-dash-offset gcontext) dash-offset)) (when dashes (setf (gcontext-dashes gcontext) dashes)) (setf (gcontext-internal-timestamp server-state) 1) (setf (gcontext-internal-timestamp local-state) ;; SetClipRectangles or SetDashes request need to be sent? (if (or (gcontext-internal-clip local-state) (gcontext-internal-dash local-state)) ;; Yes, mark local state "modified" to ensure ;; force-gcontext-changes will occur. 0 ;; No, mark local state "unmodified" 1)) (with-buffer-request (display *x-creategc*) (resource-id gcontextid) (drawable drawable) (progn (do* ((i 0 (index+ i 1)) (bit 1 (the xgcmask (ash bit 1))) (nbyte 16) (mask 0) (local (svref local-state i) (svref local-state i))) ((index>= i *gcontext-fast-change-length*) (card29-put 12 mask) (card16-put 2 (index-ash nbyte -2)) (index-incf (buffer-boffset display) nbyte)) (declare (type array-index i nbyte) (type xgcmask bit) (type gcmask mask) (type (or null card32) local)) (unless (eql local (the (or null card32) (svref server-state i))) (setf (svref server-state i) local) (card32-put nbyte local) (setq mask (the gcmask (logior mask bit))) (index-incf nbyte 4))))) ;; Initialize extensions (do ((extensions *gcontext-extensions* (cdr extensions)) (i *gcontext-data-length* (index+ i 1))) ((endp extensions)) (declare (type list extensions) (type array-index i)) (setf (svref server-state i) (setf (svref local-state i) (gcontext-extension-default (car extensions))))) ;; Set extension values (do* ((option-list options (cddr option-list)) (option (car option-list) (car option-list)) (extension)) ((endp option-list)) (declare (type list option-list)) (cond ((getf *gcontext-indexes* option)) ; Gcontext field ((member option '(:drawable :clip-ordering :cache-p))) ; Optional parameter ((setq extension (find option *gcontext-extensions* :key #'gcontext-extension-name)) (funcall (gcontext-extension-set-function extension) gcontext (second option-list))) (t (x-type-error option 'gcontext-key)))) gcontext)) (defun copy-gcontext-components (src dst &rest keys) (declare (type gcontext src dst) (dynamic-extent keys)) ;; you might ask why this isn't just a bunch of ;; (setf (gcontext- dst) (gcontext- src)) ;; the answer is that you can do that yourself if you want, what we are ;; providing here is access to the protocol request, which will generally ;; be more efficient (particularly for things like clip and dash lists). (when keys (let ((display (gcontext-display src)) (mask 0)) (declare (type xgcmask mask)) (with-display (display) (force-gcontext-changes-internal src) (force-gcontext-changes-internal dst) ;; collect entire mask and handle extensions (dolist (key keys) (let ((i (getf *gcontext-indexes* key))) (declare (type (or null array-index) i)) (if i (setq mask (the xgcmask (logior mask (the xgcmask (svref *gcontext-masks* i))))) (multiple-value-bind (extension index) (find key *gcontext-extensions* :key #'gcontext-extension-name) (if extension (funcall (gcontext-extension-copy-function extension) src dst (svref (gcontext-local-state src) (index+ index *gcontext-data-length*))) (x-type-error key 'gcontext-key)))))) (when (plusp mask) (do ((src-server-state (gcontext-server-state src)) (dst-server-state (gcontext-server-state dst)) (dst-local-state (gcontext-local-state dst)) (bit mask (the xgcmask (ash bit -1))) (i 0 (index+ i 1))) ((zerop bit) (incf-internal-timestamp dst-server-state) (setf (gcontext-internal-timestamp dst-local-state) 0)) (declare (type gcontext-state src-server-state dst-server-state dst-local-state) (type xgcmask bit) (type array-index i)) (when (oddp bit) (setf (svref dst-local-state i) (setf (svref dst-server-state i) (svref src-server-state i))))) (with-buffer-request (display *x-copygc*) (gcontext src dst) (card29 (xgcmask->gcmask mask)))))))) (defun copy-gcontext (src dst) (declare (type gcontext src dst)) ;; Copies all components. (apply #'copy-gcontext-components src dst *gcontext-components*) (do ((extensions *gcontext-extensions* (cdr extensions)) (i *gcontext-data-length* (index+ i 1))) ((endp extensions)) (funcall (gcontext-extension-copy-function (car extensions)) src dst (svref (gcontext-local-state src) i)))) (defun free-gcontext (gcontext) (declare (type gcontext gcontext)) (let ((display (gcontext-display gcontext))) (with-buffer-request (display *x-freegc*) (gcontext gcontext)) (deallocate-resource-id display (gcontext-id gcontext) 'gcontext) (deallocate-gcontext-state (gcontext-server-state gcontext)) (deallocate-gcontext-state (gcontext-local-state gcontext)) nil)) (defmacro define-gcontext-accessor (name &key default set-function copy-function) ;; This will define a new gcontext accessor called NAME. ;; Defines the gcontext-NAME accessor function and its defsetf. ;; Gcontext's will cache DEFAULT-VALUE and the last value SETF'ed when ;; gcontext-cache-p is true. The NAME keyword will be allowed in ;; CREATE-GCONTEXT, WITH-GCONTEXT, and COPY-GCONTEXT-COMPONENTS. ;; SET-FUNCTION will be called with parameters (GCONTEXT NEW-VALUE) ;; from create-gcontext, and force-gcontext-changes. ;; COPY-FUNCTION will be called with parameters (src-gc dst-gc src-value) ;; from copy-gcontext and copy-gcontext-components. ;; The copy-function defaults to: ;; (lambda (ignore dst-gc value) ;; (if value ;; (,set-function dst-gc value) ;; (error "Can't copy unknown GContext component ~a" ',name))) (declare (type symbol name) (type t default) (type (function (gcontext t) t) set-function) ;; required (type (or null (function (gcontext gcontext t) t)) copy-function)) (let* ((gc-name (intern (concatenate 'string (string 'gcontext-) (string name)))) ;; in current package (key-name (kintern name)) (setfer (xintern "Set-" gc-name)) (internal-set-function (xintern "Internal-Set-" gc-name)) (internal-copy-function (xintern "Internal-Copy-" gc-name)) (internal-state-index (xintern "Internal-" gc-name "-State-Index"))) (unless copy-function (setq copy-function `(lambda (src-gc dst-gc value) (declare (ignore src-gc)) (if value (,set-function dst-gc value) (error "Can't copy unknown GContext component ~a" ',name))))) `(progn (eval-when (compile load eval) (defparameter ,internal-state-index (add-gcontext-extension ',key-name ,default ',internal-set-function ',internal-copy-function)) ) ;; end eval-when (defun ,gc-name (gcontext) (svref (gcontext-local-state gcontext) ,internal-state-index)) (defun ,setfer (gcontext new-value) (let ((local-state (gcontext-local-state gcontext))) (setf (gcontext-internal-timestamp local-state) 0) (setf (svref local-state ,internal-state-index) new-value))) (defsetf ,gc-name ,setfer) (defun ,internal-set-function (gcontext new-value) (,set-function gcontext new-value) (setf (svref (gcontext-server-state gcontext) ,internal-state-index) (setf (svref (gcontext-local-state gcontext) ,internal-state-index) new-value))) (defun ,internal-copy-function (src-gc dst-gc new-value) (,copy-function src-gc dst-gc new-value) (setf (svref (gcontext-local-state dst-gc) ,internal-state-index) (setf (svref (gcontext-server-state dst-gc) ,internal-state-index) new-value))) ',name))) ;; GContext extension fields are treated in much the same way as normal GContext ;; components. The current value is stored in a slot of the gcontext-local-state, ;; and the value known to the server is in a slot of the gcontext-server-state. ;; The slot-number is defined by its position in the *gcontext-extensions* list. ;; The value of the special variable |Internal-GCONTEXT-name| (where "name" is ;; the extension component name) reflects this position. The position within ;; *gcontext-extensions* and the value of the special value are determined at ;; LOAD time to facilitate merging of seperately compiled extension files. (defun add-gcontext-extension (name default-value set-function copy-function) (declare (type symbol name) (type t default-value) (type (function (gcontext t) t) set-function) (type (function (gcontext gcontext t) t) copy-function)) (let ((number (or (position name *gcontext-extensions* :key #'gcontext-extension-name) (prog1 (length *gcontext-extensions*) (push nil *gcontext-extensions*))))) (setf (nth number *gcontext-extensions*) (make-gcontext-extension :name name :default default-value :set-function set-function :copy-function copy-function)) (+ number *gcontext-data-length*)))