;;; -*- Mode:Lisp; Package:CLIO-OPEN; Base:10; Lowercase:T; Syntax:Common-Lisp -*-


;;;----------------------------------------------------------------------------------+
;;;                                                                                  |
;;;                          TEXAS INSTRUMENTS INCORPORATED                          |
;;;                                  P.O. BOX 149149                                 |
;;;                                AUSTIN, TEXAS 78714                               |
;;;                                                                                  |
;;;             Copyright (C) 1989, 1990 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 "CLIO-OPEN")

(export '(
	  make-menu
	  menu
	  menu-choice
	  menu-title
	  )
	'clio-open)


;================================================================;
;		           THE PUSHPIN CONTACT  		 ;
;================================================================;





(defcontact pushpin-button (button)
  ((pointer-pressed   :type      boolean
		      :initform  nil))
  (:resources
    (border-width :initform 0)
    
    (switch       :type (member :in :out)
		  :initform :out)))

(defun make-pushpin-button (&rest initargs)
  (apply #'make-contact 'pushpin-button initargs))


(let
  ((last-scale '())      ;These state variables are used to cache the menu spec for the
   (last-spec '()))      ;most recent scale requested.
  (defun get-OL-menu-spec (self)
    (declare (type (or NULL contact) self))
    (if (null self)
	(setf last-scale NIL)     ;This is just in case anyone ever needs to reset state
	(let((this-scale (contact-scale self)))
	  (if (eq this-scale last-scale)
	      last-spec
	      (let
		((spec (cdr (assoc this-scale *OL-menu-spec-alist*))))
		(setf last-scale this-scale)
		(setf last-spec spec)))))))

(defun get-pushpin-spec (self)
  (declare (type pushpin-button self))
  (OL-menu-spec-pushpin (get-OL-menu-spec self)))
  

(defmethod initialize-instance :after ((self pushpin-button)
				       &key switch &allow-other-keys)

  (with-slots (border-width selected) self
    (setf border-width 0)

    (when (eq switch :in)
      (setf selected 2))))





(DEFMETHOD preferred-size ((self pushpin-button) &key width height border-width)
  (declare (ignore width height border-width))
  
  (DECLARE (VALUES preferred-width preferred-height
		   preferred-border-width))
  (let*
    ((menu-spec (get-OL-menu-spec self))
     (pushpin-spec (OL-menu-spec-pushpin menu-spec)))
    
    (with-slots (preferred-width) self
      (VALUES				      
	(OR preferred-width
	    (SETF preferred-width
		  (+ (pushpin-spec-box-width pushpin-spec)
		     (- (OL-menu-spec-pushpin-dx menu-spec)	;Spec left margin
			(pushpin-spec-left-margin pushpin-spec)))))	;Account for (possible) padding in image
	(+ (OL-menu-spec-pushpin-dy menu-spec)	;menu top to pushpin baseline distance
	   (OL-menu-spec-title-bar-dy menu-spec))	;pushpin (title) baseline to title bar
	0))))



;;; =================================================================================== ;;;
;;;											;;;
;;;    	                      Display a Pushpin Button...				;;;
;;;											;;;
;;; =================================================================================== ;;;

(flet
  ((display-pushpin-button (self menu-spec spec &optional completely-p)
      (with-slots (font background foreground width height label) self
	(WHEN (realized-p self)			
	  (using-gcontext (gc
			    :drawable 	self
			    :exposures  :off
;;			    :foreground foreground
;;			    :background background
			    :font 	font
			    :line-width	1)
	    
	    (WHEN completely-p
	      (clear-area self
			  :x 0
			  :y 0
			  :width  width
			  :height height))
	      

	    (let*
	      ((lab-width (drawable-width label))
	       (lab-height (drawable-height label))
	       (x (- (OL-menu-spec-pushpin-dx menu-spec)  ;desired dx to left of pin
		     (pushpin-spec-left-margin spec)))    ;left margin padding in pixmap
	       (y (- (OL-menu-spec-pushpin-dy menu-spec)  ;desired baseline relative menu
		     (pushpin-spec-baseline spec)         ;pin baseline relative box
		     (pushpin-spec-top-margin spec))))    ;top margin padding in pixmap
	      (copy-area label gc 0 0 lab-width lab-height self x y)
	      ))))))


  (DEFMETHOD display-button-highlighted ((self pushpin-button) &optional completely-p)
    (with-slots (last-displayed-as label) self
      (let*
	((menu-spec (get-OL-menu-spec self))
	 (spec (OL-menu-spec-pushpin menu-spec))
	 (screen (contact-screen self)))
	(setf label
	      (contact-image-mask
		self
		(pushpin-spec-image-in spec)
		:foreground (screen-black-pixel screen)
		:background (screen-white-pixel screen)))
	  (display-pushpin-button self menu-spec spec completely-p))
      (SETF last-displayed-as :highlighted)))


  (DEFMETHOD display-button-unhighlighted ((self pushpin-button) &optional completely-p)
    (with-slots (last-displayed-as highlight-default-p label) self
      (let*
	((menu-spec (get-OL-menu-spec self))
	 (spec (OL-menu-spec-pushpin menu-spec))
	 (screen (contact-screen self)))
	(setf label
	      (contact-image-mask
		self
		(pushpin-spec-image-out spec)
		:foreground (screen-black-pixel screen)
		:background (screen-white-pixel screen)))
	  (display-pushpin-button self menu-spec spec completely-p))
      (SETF last-displayed-as :unhighlighted))) )

;;; NOTE: The following choice item methods for pushpins should invoke the choice 
;;; item callbacks, but I haven't done this yet.

(defmethod choice-item-press ((pushpin-button pushpin-button))
  (with-slots (selected) pushpin-button
    (LET((to-selected-p (= selected 1)))
      (SETF selected (- selected))
      (IF to-selected-p
	  (display-button-highlighted pushpin-button)
	  (display-button-unhighlighted pushpin-button))
      T)))

(defmethod choice-item-release ((pushpin-button pushpin-button))
  (with-slots (selected) pushpin-button
    (apply-callback pushpin-button (IF (= 2 (SETF selected (+ 3 selected))) :on :off))))

(defmethod choice-item-leave ((pushpin-button pushpin-button))
  (display-button-unhighlighted pushpin-button))

(defun menu-leave-pushpin-button (self)
   (declare (type pushpin-button self))
   (with-slots (pointer-pressed) self
     (when pointer-pressed
       (choice-item-leave self)
       (setq pointer-pressed nil))))

(defun menu-release-pushpin-button (self)
   (declare (type pushpin-button self))

   ;; Eventually this is where the logic associated with pinning a menu will exist.
;     (format t "~%Sorry - Pinning of menus not yet implemented.")
;     ;; For now we treat a release just like a leave 'cause pinning
;     ;; isn't implemented yet.
;     (menu-leave-pushpin-button self)

   ;;  +++ For now we hack pushpins to just change the pushpin state, and let
   ;;      menu-dismissal fake pinning by not dismissing if the pushpin is in.
   ;;      So, do press-and-release and delete the events.
   (with-slots (pointer-pressed) self
     (when pointer-pressed
       (choice-item-release self)
       (setq pointer-pressed nil))))

(DEFMETHOD press-select ((self pushpin-button))
   (WHEN (choice-item-press self)
     (with-slots (pointer-pressed) self
       (setq pointer-pressed t))))

(DEFMETHOD release-select ((self pushpin-button))
   (with-event (state)
     (with-slots (selected pointer-pressed) self
       (WHEN (AND (> 0 selected)
		  (NOT (ZEROP (LOGAND #.(make-state-mask :button-1) state))))
	 (UNWIND-PROTECT 
	     (choice-item-release self)
	   (setq pointer-pressed nil))))))

(DEFMETHOD menu-enter-pushpin-button ((self pushpin-button))
   (with-event (state)
     (unless (zerop (logand #.(make-state-mask :button-3) state))
       (when (choice-item-press self)
	 ;(display-button-highlighted self)
	 (with-slots (pointer-pressed) self
	   (setq pointer-pressed t))))))

(DEFMETHOD (SETF choice-item-selected-p) (new-value (self pushpin-button))
   ;; Identical to (SETF button-switch) except returns boolean in/out indicator.
   (DECLARE (VALUES new-value))
   (EQ (SETF (button-switch self) (if new-value :in :out)) :in))

;;; ========================================================================== ;;;
;;;									       ;;;
;;;        ( P u s h p i n )   B u t t o n   P r o t o c o l   M e t h o d s   ;;;
;;;									       ;;;
;;; ========================================================================== ;;;

(DEFMETHOD button-switch ((self pushpin-button))
   (with-slots (selected) self
     (NTH (1- (ABS selected)) '(:out :in))))

(DEFMETHOD (SETF button-switch) (new-state (self pushpin-button))
   (check-type new-state (member :in :out))
   (LET ((current-state (button-switch self)))
     (WHEN (NOT (EQ current-state new-state))
       ;; We simulate a button press and release to implement identical
       ;; semantics whether done via API or via gesture.
       (WHEN (choice-item-press self)
	 ;; When toggle press succeeded we follow it
	 ;; with a release.
	 (choice-item-release self)))
     (button-switch self)))


(DEFEVENT pushpin-button
	  :enter-notify
  	  menu-enter-pushpin-button)

(defevent pushpin-button
	  :leave-notify
   menu-leave-pushpin-button)

(defevent pushpin-button
	  (:button-release :button-1)
   pp-maybe-release-select)

(defun pp-maybe-release-select (button)
   (with-slots (pointer-pressed) (the pushpin-button button)
     (when pointer-pressed
       (release-select button))))

;;  These two translations are for Open Look menus, which allow item selection
;;  on both button-1 and button-3 presses.
(DEFEVENT pushpin-button
	  (:button-press :button-3)
   press-select)

(DEFEVENT pushpin-button
	  (:button-release :button-3)
   menu-release-pushpin-button)


;================================================================;
;			   MENU  CONTACT			 ;
;================================================================;

(defcontact menu (core-shell core override-shell)
  (:resources
    (title 	  :type     (or null string)
	  	  :initform nil)
    (pushpin      :type     switch
		  :initform :off)
    (save-under   :initform :on)
    (border-width :initform 0))
  (:documentation "A shell which presents a set of choice items."))
  
(defun make-menu (&rest initargs)
  "Creates and returns a menu instance."
  (declare (values menu)) 
  (apply #'make-contact 'menu initargs))

(defmethod initialize-instance :after ((menu menu) &rest args
				       &key (choice 'make-choices) &allow-other-keys)
  (with-slots (background border-width width height) menu    

;;  Can't do this now that the choice arg is a constructor rather than a type.
;    (let ((choice-class (if (consp choice) (first choice) choice))) 
;      (assert (subtypep choice-class 'composite) nil
;	      "~s is not a composite subclass name." choice-class)) 
    
    (apply #'make-contact
	   'drop-shadow
	   :parent menu
	   :x 0
	   :y 0
	   :width width
	   :height height
	   :content choice
	   args)

    ;; Now that content is created with initial attributes,
    ;; reset background, border-width to accommodate drop-shadow.
    (setf background   :none
	  border-width 0)))

;;  When asked for background, give the background of the container.
(defmethod contact-background ((menu menu))
   (with-slots (children) menu
     (let ((container (and children (menu-container menu))))
       (if container
	   (contact-background container)
	   :none))))

(defmethod (setf contact-background) (new-value (menu menu))
  (setf (contact-background (menu-container menu)) new-value))

(defmethod (setf contact-foreground) (new-value (menu menu))
  (setf (contact-foreground (menu-container menu)) new-value))

(defmethod menu-choice ((menu menu))
  (find :content (composite-children (menu-container menu)) :key #'contact-name))

(defun menu-container (menu)
  (first (composite-children (first (composite-children menu)))))

(defmethod (setf menu-title) (new-title (menu menu))
  (let
    ((title-field (find :menu-title
			(composite-children (menu-container menu))
			:key 'contact-name))
     (title       (convert menu new-title 'string)))
    
    (assert title nil "~a cannot be converted to a title string." new-title)

    (cond
      (title-field
       (change-geometry
	 title-field
	 :width    (text-width (display-text-font title-field) title)
	 :accept-p t)       
       (setf (display-text-source title-field) title))

      (t
       (make-display-text-field
	  :name            :menu-title
	  :parent          (menu-container menu)
	  :display-gravity :center
	  :source          title)))
    title))

(defmethod menu-title ((menu menu))
  (let ((title-field (find :menu-title
			   (composite-children (menu-container menu))
			   :key 'contact-name)))
    (when title-field
      (display-text-source title-field))))


(defmethod preferred-size ((self menu) &key width height border-width)
  (declare (ignore width height border-width))

  (declare (values preferred-width preferred-height
		   preferred-border-width))
  (preferred-size (first (composite-children self))))


(defmethod shell-mapped ((self menu))
  "Invokes :initialize callback function."
  (apply-callback self :map)
  (apply-callback-else (self :initialize)
    (with-slots ((members children)) (menu-choice self)
      (dolist (member members)
	(apply-callback member :initialize)))))


;================================================================;
;			DROP SHADOW CONTACT			 ;
;================================================================;

(defcontact drop-shadow (core composite)
  ((compress-exposures :initform :on))
  (:documentation "A composite containing a content and a drop-shadow.")
  (:resources (event-mask :initform #.(make-event-mask :exposure))))

(defmethod initialize-instance :after ((drop-shadow drop-shadow) &rest args)
  (with-slots (background border-width) drop-shadow
    ;; Ignore background, border-width
    (setf background :none)
    (setf border-width 0))

  ;; Make the menu container to hold the content, title, & pushpin components.
  (apply #'make-contact 'menu-container
    :name :menu-container
    :parent drop-shadow
    args))

(defmethod preferred-size ((self drop-shadow) &key width height border-width)
  (declare (ignore width height border-width))
  (let*
    ((spec (get-OL-menu-spec self))
     (dsw (OL-menu-spec-drop-shadow-width spec)))
    (multiple-value-bind (pw ph pbw)
	(preferred-size (first (composite-children self)))
      (values
	(+ dsw pbw pbw pw)
	(+ dsw pbw pbw ph)
	0))))

(defmethod display ((drop-shadow drop-shadow) &optional x y width height &key)
  (declare (ignore x y width height))
  (with-slots (children) drop-shadow
    (when children
      (with-slots (width height border-width) (first children)
	(let*
	  ((spec (get-OL-menu-spec drop-shadow))
	   (dsw (OL-menu-spec-drop-shadow-width spec))
	   (dso (OL-menu-spec-drop-shadow-offset spec))
	   (menu-container-width (+ width border-width border-width))
	   (menu-container-height (+ height border-width border-width)))
	  (using-gcontext (gc :drawable drop-shadow
			      :foreground (contact-foreground drop-shadow)
			      :fill-style :stippled
			      :stipple (contact-image-mask drop-shadow 50%gray :depth 1))
            ;; We draw a full rectangle, depending on the server to clip the
	    ;; portion covered by the menu container.
	    (draw-rectangle drop-shadow gc
	                    dso dso
			    (- (+ menu-container-width dsw) dso)
			    (- (+ menu-container-height dsw) dso)
			    :fill-p)))))))

(defmethod manage-geometry ((self drop-shadow) child x y width height border-width &key)
  (let*
    ((spec (get-OL-menu-spec self))
     (dsw (OL-menu-spec-drop-shadow-width spec))
     (child-bw (or border-width (contact-border-width child)))
     (width (or width (contact-width child)))
     (height (or height (contact-height child)))
     (drop-shadow-contact-width (+ width child-bw child-bw dsw))
     (drop-shadow-contact-height (+ height child-bw child-bw dsw))

     (self-change-not-required-p
       (and (= (contact-width self) drop-shadow-contact-width)
	    (= (contact-height self) drop-shadow-contact-height)))
     (approved-p
       (and
	 (or (null x) (= x 0))
	 (or (null y) (= y 0))
	 (or self-change-not-required-p
	     (change-geometry self
			      :width drop-shadow-contact-width
			      :height drop-shadow-contact-height
			      :accept-p t)))))

    (values
      approved-p
      0 0
      (- (contact-width self) child-bw child-bw dsw)
      (- (contact-height self) child-bw child-bw dsw)
      child-bw)))

(defmethod change-layout ((self drop-shadow) &optional newly-managed)
  (declare (ignore newly-managed))
  (let((children (composite-children self)))
    (when children
      (let*
	((spec (get-OL-menu-spec self))
	 (dsw (OL-menu-spec-drop-shadow-width spec))
	 (menu-container (first children))
	 (border-width (contact-border-width menu-container))
	 (width (contact-width menu-container))
	 (height (contact-height menu-container)))
	(change-geometry
	  self
	  :width (+ width border-width border-width dsw)
	  :height (+ height border-width border-width dsw)
	  :accept-p t)))))

(defmethod add-child :before ((self drop-shadow) child &key)
  (let((children (composite-children self)))
    (when children
      (error "~s already has child ~s; cannot add child ~s."
	     self
	     (first children)
	     child))))

(defmethod resize :after ((self drop-shadow) width height border-width)
  (declare (ignore border-width))
  (let*
    ((children (composite-children self))
     (menu-container (first children))
     (spec (get-OL-menu-spec self))
     (dsw (OL-menu-spec-drop-shadow-width spec))
     (bw (contact-border-width menu-container)))
    (resize menu-container
	    (max 1 (- width bw bw dsw))
	    (max 1 (- height bw bw dsw))
	    bw)))
    

;================================================================;
;			MENU-CONTAINER CONTACT			 ;
;================================================================;

(defcontact menu-container (core composite)
  ((compress-exposures :initform :on))
  (:resources
    (event-mask :initform #.(make-event-mask :exposure)))
  (:documentation 
    "A composite containing a content and (optionally) a title and pushpin"))

(defmethod initialize-instance :after ((self menu-container)
				       &rest args
				       &key content (pushpin :off) title
				       &allow-other-keys) 

  (let ((menu      (contact-parent (contact-parent self)))
	(menu-spec (get-OL-menu-spec self)))

    ;; Initialize container window attributes.
    (with-slots (background border-width) self
      ;; Inherit background from menu, not drop-shadow.
      (when (eq :parent-relative background)
	(setf background (contact-current-background menu)))

      ;; Inherit border-width from menu
      (setf border-width (max (contact-border-width menu)
			      (point-pixels (contact-screen self)))))
      
      
      ;; Initialize content
      (multiple-value-bind (content-constructor content-args)
	  (if (consp content) (values (first content) (rest content)) content)
	
	(add-callback
	  (if (null content-args)
	      
	      ;; Default choice initialization 
	      (funcall content-constructor
		:name          :content
		:parent        self
		:border-width  0 
		:left-margin   (OL-menu-spec-pushpin-dx menu-spec)
		:right-margin  (OL-menu-spec-pushpin-dx menu-spec)
		:bottom-margin (OL-menu-spec-drop-shadow-offset menu-spec) 
		:top-margin    (OL-menu-spec-drop-shadow-offset menu-spec)
		:columns       1
		:same-width-in-column :on)
	      
	      ;; Else use given content initargs
	      (apply content-constructor
		     :name          :content
		     :parent        self
		     :border-width  0 
		     content-args))
	  
	  :new-choice-item
	  #'add-menu-item-callbacks menu))

      ;; Initialize title field
      (when title
	(setf (menu-title menu) title))

      ;; Initialize pushpin
      (when (eq pushpin :on)
	(add-callback
	  (make-pushpin-button
	    :name :pushpin
	    :parent self
	    :label (pushpin-spec-image-out (get-pushpin-spec self)))
	  :off
	  #'dismiss-menu menu))))

;;  A method so dialog-button can add daemons.
(defmethod add-menu-item-callbacks (item menu)
   (when (typep item 'toggle-button)
     (add-callback item :on #'dismiss-menu menu))
   (add-callback item :off #'dismiss-menu menu))

(defun dismiss-menu (menu)
   (unless (eq (contact-state menu) :withdrawn)	;i.e., already dismissed
     ;;  +++ Pushpin hack:  If the menu has a pushpin and it's :in, don't
     ;;      withdraw the menu.  It doesn't clone, but it does stay up.
     (let ((pushpin (find :pushpin (composite-children
				     (contact-parent (menu-choice menu)))
			  :key #'contact-name)))
       (unless (and pushpin
		    (eq :in (button-switch pushpin)))
	 (setf (contact-state menu) :withdrawn)))))
     

(defmethod display ((self menu-container)
		    &optional exposed-x exposed-y exposed-width exposed-height &key)
  (declare (ignore exposed-x exposed-y exposed-width exposed-height))
  (with-slots (children width foreground) self
    (when (find :menu-title children :key #'contact-name)
      (let ((tbar-x 4) ; Kludge! this is actually a function of scale.
	    (tbar-y (- (contact-y (find :content children :key #'contact-name)) 1)))
	(using-gcontext (gc :drawable self :foreground foreground)
	  (draw-line self gc tbar-x tbar-y (- width tbar-x) tbar-y))))))	


;================================================================;
;		  MENU-CONTAINER GEOMETRY MANAGEMENT		 ;
;================================================================;
(labels
  (
   (disapprove () NIL)
   
   (fail () (error "Unable to layout menu-container." ))

   (shrink/expand-title (title pw ph tw th cw ch failure-thunk)
     (multiple-value-bind (tw1 th1)
	 (preferred-size title :width 0 :height 0)
       (if (<= tw1 tw)
	   ;; We assume it's OK to make title *wider* than preferred width,
	   ;; but avoid making it narrower.
	   (values
	     (+ 2 (max (or (and pw tw (+ pw
					 (ol-menu-spec-title-dx (get-ol-menu-spec title))
					 (ol-menu-spec-title-dx (get-ol-menu-spec title))
					 tw))
			   (and tw
				(+ tw
				   (ol-menu-spec-title-dx (get-ol-menu-spec title))
				   (ol-menu-spec-title-dx (get-ol-menu-spec title))))
			   0)
		       (+ cw (ol-menu-spec-title-dx (get-ol-menu-spec title))
			  (ol-menu-spec-title-dx (get-ol-menu-spec title)))))	;Allow 2 pixels for left & right border
	     (+ 2				                                ;Allow 2 pixels for top & bottom border
		(or (and ph (max ph th1)) th1)
		1				;Allow 1 pixel for title bar
		ch)
	     pw
	     ph
	     tw
	     th
	     cw
	     ch)
	   (funcall failure-thunk))))

   (shrink/expand-content (content pw ph tw th cw ch failure-thunk)
     (multiple-value-bind (cw1 ch1)
	 (preferred-size content :width 0 :height 0) 
       (if (<= cw1 cw)
	   ;; We assume it's OK to make content *wider* than preferred width,
	   ;; but avoid making it narrower.
	   (values
	     (+ 2 (max (or (and pw tw (+ pw
					 (ol-menu-spec-title-dx (get-ol-menu-spec content))
					 (ol-menu-spec-title-dx (get-ol-menu-spec content))
					 tw))
			   (and tw
				(+ tw
				   (ol-menu-spec-title-dx (get-ol-menu-spec content))
				   (ol-menu-spec-title-dx (get-ol-menu-spec content))))
			   0)
		       (+ cw (ol-menu-spec-title-dx (get-ol-menu-spec content))
			     (ol-menu-spec-title-dx (get-ol-menu-spec content)))
		       ))			;Allow 2 pixels for left & right border ;; jba 
	     (+ 2				;Allow 2 pixels for top & bottom border
		(or (and ph th (max ph th)) ph th 0)
		(if th 1 0)			;Title bar only if title
		ch1)
	     pw
	     ph
	     tw
	     th
	     cw1
	     ch)
	   (funcall failure-thunk))))

   (reposition&resize (component cx cy cw ch)
      (let
	((x (contact-x component))
	 (y (contact-y component))
	 (w (contact-width component))
	 (h (contact-height component)))
	(unless (and (= x cx) (= y cy)) (move component cx cy))
	(unless (and (= w cw) (= h ch)) (resize component cw ch 0))))
	
   (execute-layout (self width height ppin pw ph title tw th content cw ch) 
     (assert
       (change-geometry self :width width :height height)
       ()
       "Unable to layout menu ~a" self)
	
     (multiple-value-bind (px py tx ty cx cy)
	 (locate-menu-components pw ph tw th width self)
       (when ppin (reposition&resize ppin px py pw ph))
       (when title 
	 (reposition&resize title tx ty tw th))
       (when content (reposition&resize content cx cy cw ch))) )

   (locate-menu-components (pw ph tw th width self) 
     (cond
       ((and pw tw)
	(values
	  0 0     ; x & y for pushpin
	  (+ pw (ol-menu-spec-title-dx (get-ol-menu-spec self)) ) 0    ; x & y for title
	  (max (ol-menu-spec-title-dx (get-ol-menu-spec self))
	       (pixel-round (- width
			 (contact-width (find :content (composite-children self) :key #'contact-name)))
		   2))
	  (+ (max ph th) 1)))  ;x & y for content
       (pw
	(values
	  0 0
	  NIL NIL
	  (max (ol-menu-spec-title-dx (get-ol-menu-spec self))
	       (pixel-round (- width
			 (contact-width (find :content (composite-children self) :key #'contact-name)))
		   2))
	  ph))
       (tw
	(values
	  NIL NIL 
	  (max (ol-menu-spec-title-dx (get-ol-menu-spec self))
	       (pixel-round (- width
			 (contact-width (find :menu-title (composite-children self) :key #'contact-name)))
		      2))
	  0
	  (max (ol-menu-spec-title-dx (get-ol-menu-spec self))
	       (pixel-round (- width
			 (contact-width (find :content (composite-children self) :key #'contact-name)))
		      2))
	  (+ 1 th)))
       (t
	(values
	  NIL NIL
	  NIL NIL
	  0 0))))
      
   (layout-menu-container (ppin pw ph title tw th content cw ch) 
     (cond
       ((and title ppin)
	; Menu has title and pushpin in addition to content
	(let ((title-area-height (max ph th)))
	  (if (= (+ tw pw) cw)
	      (values
		(+ 2 cw (ol-menu-spec-title-dx (get-ol-menu-spec content))
		   (ol-menu-spec-title-dx (get-ol-menu-spec content)))
		(+ 2 title-area-height 1 ch)
		pw title-area-height
		tw title-area-height
		cw ch)
	      ;; We must expand/shrink title or shrink/expand content.
	      (let((newtw (- cw pw)))
		(if (< newtw 0)
		    (shrink/expand-content content pw ph tw th (+ tw pw) ch #'fail)
		    (shrink/expand-title
		      title
		      pw
		      title-area-height
		      newtw
		      title-area-height
		      cw
		      ch
		      #'(lambda()
			  (shrink/expand-content
			    content
			    pw
			    title-area-height
			    tw
			    title-area-height
			    (+ tw pw)		; Will fail when title is bigger, so width is title width.
			    ch
			    #'fail))))))))
       
       (title
	;Menu has title, but no pushpin
	(if (= tw cw)
	    (values
	      (+ 2 cw (ol-menu-spec-title-dx (get-ol-menu-spec content))
		      (ol-menu-spec-title-dx (get-ol-menu-spec content)))
	      (+ 2 th 1 ch)
	      pw ph
	      tw th
	      cw ch)
	    (shrink/expand-title
	      title
	      pw
	      ph
	      tw
	      th
	      cw
	      ch
	      #'(lambda()
		  (shrink/expand-content content pw ph tw th tw ch #'fail)))
	    ))
       
       (ppin
	;Menu has pushpin, but no title
	(if (> cw pw)
	    (values (+ 2 cw (ol-menu-spec-title-dx (get-ol-menu-spec content))
		       (ol-menu-spec-title-dx (get-ol-menu-spec content)))
		    (+ 2 ph ch) pw ph tw th cw ch)
	    (shrink/expand-content content pw ph tw th pw ch #'fail)))
       
       (t
	;Menu has neither pushpin nor title
	(values (+ 2 cw) (+ 2 ch) pw ph tw th cw ch))))   )


  (defmethod change-layout ((self menu-container) &optional newly-managed)
    (declare (ignore newly-managed))
    (let*
      ((children (composite-children self))
       
       (content (find :content children :key #'contact-name))
       (cw (contact-width content))
       (ch (contact-height content))
       
       (title (find :menu-title children :key #'contact-name))
       (tw (and title (contact-width title)))
       (th (and title (contact-height title)))
       
       (ppin (find :pushpin children :key #'contact-name))
       (pw (and ppin (contact-width ppin)))
       (ph (and ppin (contact-height ppin))))
       (multiple-value-bind (width height pw1 ph1 tw1 th1 cw1 ch1)
	   (layout-menu-container ppin pw ph title tw th content cw ch)
	 (execute-layout self width height ppin pw1 ph1 title tw1 th1 content cw1 ch1))))

  (defmethod preferred-size ((self menu-container) &key width height border-width)
    (declare (ignore width height border-width))
    (let*
      ((children (composite-children self))
       (content (find :content children :key #'contact-name))
       (title (find :menu-title children :key #'contact-name))
       (ppin (find :pushpin children :key #'contact-name)))

      (MULTIPLE-VALUE-BIND (cw ch)
	  (preferred-size content :width 0 :height 0)
	(MULTIPLE-VALUE-BIND (tw th)
	    (AND title (preferred-size title :width 0 :height 0))
	  (MULTIPLE-VALUE-BIND (pw ph)
	      (AND ppin (preferred-size ppin))
	    (MULTIPLE-VALUE-BIND (preferred-width preferred-height)
		(layout-menu-container ppin pw ph title tw th content cw ch)
	      (VALUES preferred-width preferred-height 0)))))))

  (defmethod manage-geometry ((self menu-container) child x y width height bw &key)
    (let*
      ((children (composite-children self))
       
       (content (find :content children :key #'contact-name))
       (cw (contact-width content))
       (ch (contact-height content))
       
       (title (find :menu-title children :key #'contact-name))
       (tw (and title (contact-width title)))
       (th (and title (contact-height title)))
       
       (ppin (find :pushpin children :key #'contact-name))
       (pw (and ppin (contact-width ppin)))
       (ph (and ppin (contact-height ppin)))

       (x (or x (contact-x child)))
       (y (or y (contact-y child)))
       (width (or width (contact-width child)))
       (height (or height (contact-height child))))


      (multiple-value-bind (self-width self-height pw1 ph1 tw1 th1 cw1 ch1)
	  (cond
	    ((eq child content)
	     (cond
	       (title
		;; If there is a title then try to adjust it
		(shrink/expand-title
		  title
		  pw
		  ph
		  (if pw (- width pw) width)
		  th
		  width
		  height
		  ;; If title adjust fails then try to adjust content
		  ;; so can at least offer compromise.
		  #'(lambda()
		      (shrink/expand-content
			content
			pw
			ph
			tw
			th
			(or (and pw (+ pw tw)) tw)
			height
			#'disapprove))))
	       (ppin
		;; If ppin exists must make sure content is at least as wide
		(if (> width pw)
		    (values (+ 2 width) (+ 2 height ph 1) pw ph tw th width height)
		    (shrink/expand-content
		      content
		      pw
		      ph
		      tw
		      th
		      pw
		      height
		      #'disapprove)))
	       (t
		;; Menu has neither pushpin nor title
		(values
		  (+ 2 width) (+ 2 height) pw ph tw th width height))))
	    ((eq child title)
	     (shrink/expand-content
	       content
	       pw
	       ph
	       width
	       height
	       (if ppin (+ pw tw) tw)
	       ch
	       #'(lambda()
		   (shrink/expand-title
		     title
		     pw
		     ph
		     (if ppin (- cw pw) width)
		     height
		     cw
		     ch
		     #'disapprove))))
	    ;; It must be the pushpin which has changed
	    (title    
	     (shrink/expand-title
	       title
	       width
	       height
	       (- cw width)
	       th
	       cw
	       ch
	       #'(lambda()
		   (shrink/expand-content
		     content
		     width
		     height
		     tw
		     th
		     (+ width tw)
		     ch
		     #'disapprove))))
	    ;; Pushpin is being managed, but no title to adjust, so must adjust content.
	    ((< cw width)
	     ;; If the content width is less than the requested pushpin width we try to
	     ;; shrink the content accordingly. (Pretty unlikely case, eh?)
	     (shrink/expand-content
	       content
	       width
	       height
	       tw
	       th
	       width
	       ch
	       #'disapprove))
	    (t
	     ;; Else the content is at least as wide as the pushpin and we can simply
	     ;; accept the pushpin change without any ripple effects.
	     (values
	       (+ 2 cw) (+ 2 ch ph)
	       width height
	       tw th
	       cw ch)))

	(and
	  self-width       ;Width = NIL implies failure without suggesting compromise.
	  (multiple-value-bind (px1 py1 tx1 ty1 cx1 cy1)
	      (locate-menu-components pw1 ph1 tw1 th1 self-width self)
	    (let
	      ((self-change-approved
		 (or
		   (and
		     (= self-width (contact-width self))
		     (= self-height (contact-height self)))
		   (change-geometry self
				  :width self-width
				  :height self-height
				  :accept-p nil)))
	       (approve-p
		 (and
		   (or (null bw) (= 0 bw))
		   (cond
		     ((eq child ppin) (and (= pw1 width) (= ph1 height)
					   (= px1 x)     (= py1 y)))
		     ((eq child title) (and (= tw1 width) (= th1 height)
					    (= tx1 x)     (= ty1 y)
					    )
				       )
		     (t (and (= cw1 width) (= ch1 height)
			     (= cx1 x)     (= cy1 y)))))))
	      (and
		self-change-approved
		(progn
		  (when approve-p
		    (execute-layout
		      self self-width self-height
		      ppin pw1 ph1
		      title tw1 th1
		      content cw1 ch1))
		  (cond
		    ((eq child ppin) (values approve-p px1 py1 pw1 ph1 0))
		    ((eq child title) (values approve-p tx1 ty1 tw1 th1 0))
		    (t (values approve-p cx1 cy1 cw1 ch1 0))))))))))))
