;;; -*- 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)

(export '(force-gcontext-changes
	  with-gcontext
	  create-gcontext
	  copy-gcontext-components
	  copy-gcontext
	  free-gcontext
	  
	  gcontext-function
	  gcontext-plane-mask
	  gcontext-foreground
	  gcontext-background
	  gcontext-line-width
	  gcontext-line-style
	  gcontext-cap-style
	  gcontext-join-style
	  gcontext-fill-style
	  gcontext-fill-rule
	  gcontext-tile
	  gcontext-stipple
	  gcontext-ts-x
	  gcontext-ts-y
	  gcontext-font
	  gcontext-subwindow-mode
	  gcontext-exposures
	  gcontext-clip-x
	  gcontext-clip-y
	  gcontext-clip-mask
	  gcontext-dashes
	  gcontext-arc-mode
	  gcontext-dash-offset
          gcontext-clip-ordering

	  define-gcontext-accessor
	  ))

;; 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 <type> <name>), there is an accessor:

;(defun gcontext-<name> (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 <type>)))

;; For each argument to create-gcontext (except clip-mask and clip-ordering) declared
;; as (type (or null <type>) <name>), there is a setf for the corresponding accessor:

;(defsetf gcontext-<name> (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-<mumble> dst) (gcontext-<mumble> 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*)))
