;;; -*- 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 '(
	  table
	  make-table
	  table-column-alignment
	  table-column-width
	  table-columns
	  table-delete-policy
	  table-layout-size-policy
	  table-member
	  table-row-alignment
	  table-row-height
	  table-same-height-in-row
	  table-same-width-in-column
	  table-separator
	  table-row
	  table-column
	  )
	'clio-open)

;;;
;;;  Call-Tree...
;;;


;;;   Preferred-Size (Table)
;;;   .  check-for-existing-wis
;;;   .  place-children-physically
;;;   .  .  put-kids-into-maximum-unaligned-columns
;;;   .  .  .  find-first-parents-width 
;;;   .  .  .  assign-kids-to-rows-and-columns
;;;   .  .  .  preferred-size (child)
;;;   .  .  .  move (child)
;;;   .  .  .  resize (child)
;;;   .  .  put-kids-into-maximum-aligned-columns
;;;   .  .  .  assign-kids-to-rows-and-columns
;;;   .  .  .  .  assign-a-kid-to-a-row-and-column
;;;   .  .  .  .  build-sorted-list-of-children
;;;   .  .  .  get-maximum-possible-ncolumns
;;;   .  .  .  .  preferred-size (child)
;;;   .  .  .  preferred-size (child)
;;;   .  .  .  adjust-column-widths-so-child-fits
;;;   .  .  put-kids-into-specified-number-of-columns   
;;;   .  .  .  assign-kids-to-rows-and-columns
;;;   .  .  .  preferred-size (child)
;;;   .  .  scan-for-largest-children
;;;   .  .  .  preferred-size (child)
;;;   .  .  determine-a-rows-height
;;;   .  .  preferred-size (child)
;;;   .  .  move (child)
;;;   .  .  resize (child)
;;;   .  .  calculate-preferred-height
;;;   .  .  determine-a-rows-height
;;;   .  .  .  preferred-size (child)
;;;   .  .  calculate-preferred-width
;;;   .
;;;   Change-Layout(Table)
;;;   .  check-for-existing-wis
;;;   .  place-children-physically
;;;   .  change-geometry (Table)
;;;   .
;;;   Resize :after (Table)
;;;   .  change-layout (Table)
;;;   .
;;;   Manage-Geometry (Table)
;;;   .  Change-Geometry (Table)

;;;  Basic Organization and Flow:
;;;	The Table contact lays out its children per the values of its policy resources and the
;;;	row/column constraints of its children, with the resource values always taking precedence
;;;	over the children's constraint values.  
;;;
;;;	The function place-children-physically does the real work of Table.  
;;;
;;;	The differences in Table's logical flow for the possible values for the :columns resource
;;;	are embodied primarily in the three routines
;;;
;;;		put-kids-into-maximum-unaligned-columns
;;;		put-kids-into-maximum-aligned-columns
;;;		put-kids-into-specified-number-of-columns
;;;
;;;	There are 5 ways into the Table contact's logic:
;;;
;;;		Preferred-Size (Table)
;;;		Change-Layout (Table)
;;;		Resize :after (Table)
;;;		Manage-Geometry (Table)
;;;		(SETF layout-policy-resource)
;;;


;;;  ===========================================================================
;;;		T h e   T A B L E   L a y o u t   C o n t a c t 
;;;  ===========================================================================

(DEFCONTACT table (gravity-mixin spacing-mixin core composite)
  ((column-alignment	:type		(MEMBER :left :center :right)
			:reader  	table-column-alignment	; SETF method defined below.
			:initarg	:column-alignment
			:initform	:left)
   
   (column-width	:type		(OR (MEMBER :maximum) cons (integer 1 *))
			:reader  	table-column-width	; SETF method defined below.
			:initarg	:column-width
			:initform	:maximum)
   
   (columns		:type		(OR (integer 1 *) (MEMBER :maximum :none))
			:reader  	table-columns	        ; SETF method defined below.
			:initarg	:columns
			:initform	:maximum)

   (delete-policy	:type	      (MEMBER :shrink-list :shrink-column :shrink-none :shrink-row)
			:reader  	table-delete-policy	; SETF method defined below.
			:initarg	:delete-policy
			:initform	:shrink-none)

   (layout-size-policy	:type		(MEMBER :maximum :minimum :none)
			:reader  	table-layout-size-policy ; SETF method defined below.
			:initarg	layout-size-policy
			:initform	:maximum)

   (row-height		:type		(OR (MEMBER :maximum) cons (integer 1 *))
			:reader  	table-row-height	; SETF method defined below.
			:initarg	:row-height
			:initform	:maximum)

   (row-alignment	:type		(MEMBER :top :center :bottom)
			:reader  	table-row-alignment	; SETF method defined below.
			:initarg	:row-alignment
			:initform	:bottom)

   (same-height-in-row
     			:type		(MEMBER :on :off)
			:reader  	table-same-height-in-row ; SETF method defined below.
			:initarg	:same-height-in-row
			:initform       :off)

   (same-width-in-column
     			:type		(MEMBER :on :off)
			:reader  	table-same-width-in-column ; SETF method defined below.
			:initarg	:same-width-in-column
			:initform	:off)

   (separators		:type		list
			:initarg	:separators
			:initform	nil))

  (:resources
    (border-width :initform 0)
    column-alignment
    column-width
    columns
    delete-policy
    layout-size-policy
    row-alignment    
    row-height
    same-height-in-row
    same-width-in-column
    separators)


  (:constraints
    (row 		:type 		(integer 0 *))
    (column 		:type 		(integer 0 *)))


  (:documentation
    "Arranges its children in an array of rows and columns."
    ))



(DEFUN make-table (&rest initargs &key &allow-other-keys)
  (APPLY #'make-contact 'table initargs))

;;;  ===========================================================================  ;;; 
;;;	      ORG-ENTRY: the entries on the what-if-organization list		  ;;;
;;;  ===========================================================================  ;;;

(DEFSTRUCT (org-entry :named (:type vector) (:conc-name "ORG-ENTRY-"))
  kid
  row
  column
  width
  height
  border-width)

(DEFUN establish-org-entry (kid row column)
  (MULTIPLE-VALUE-BIND (p-w p-h p-b-w)
      (preferred-size kid)
    (make-org-entry :kid kid :row row :column column
		    :width p-w :height p-h :border-width p-b-w)))


;;;  ===========================================================================  ;;; 
;;;		       What-if Structures and Their management			  ;;;
;;;  ===========================================================================  ;;;

;;;
;;;   Structures of this kind are placed on the Table's plist under the :what-if-structures
;;;   property to record already-performed preferred-size calculations for the current set of
;;;   policy resource values but different widths/heights.  Any change to a policy resource
;;;   destroys this cache of what-if structures, as does a call to change-layout.
;;;

;;;   Hmmmm...  We must keep the children's sizes here, have all the layout logic look here
;;;   rather than at the kids' preferred-size methods.  Where to keep this info?  In organization
;;;   (which is already a list of the kids) or in another list of kids, widths, heights, and
;;;   border-widths.  Or in an array...

(DEFSTRUCT (what-if-structure :named (:type vector) (:conc-name "WHAT-IF-"))
  width
  height
  border-width
  organization					; org-entrys for :mapped children only!
  column-widths
  nrows
  ncolumns
  (preferred-width 0)
  (preferred-height 0)
  in-use
  )

(DEFUN check-for-existing-wis (table width height border-width &optional dont-create-p)
  ;;  Returns the first (newest) wis found with width/height.
  ;;  If no wis satisfying width/height exists, create a new one unless DONT-CREATE-P
  ;;  is true, in which case return NIL.
  (DECLARE (VALUES (OR what-if-structure NULL)))
  (LET ((old-wis-list (GETF (window-plist table) :what-if-structures)) wis)
    (SETF wis (FIND-IF #'(lambda (wis)
			   (AND (EQL (what-if-width wis) width)
				(EQL (what-if-height wis) height)
				(EQL (what-if-border-width wis) border-width)))
		       old-wis-list))
    (UNLESS (OR wis dont-create-p)
      (SETF (GETF (window-plist table) :what-if-structures)
	    (PUSH (SETF wis (make-what-if-structure :width width
						    :height height
						    :border-width border-width
						    :preferred-width 0
						    :preferred-height 0))
		  old-wis-list))
      )
    wis))


;;;  ===========================================================================  ;;; 
;;;		         A Table's Constraint's Accessors			  ;;;
;;;  ===========================================================================  ;;;


(defun table-row (member)
  (declare (values (or null (integer 0 *))))
  (contact-constraint member :row))

(defsetf table-row setf-table-row)
(defun setf-table-row (member row)
  (check-type row (or null (integer 0 *)))
  (setf (contact-constraint member :row) row))

(defun table-column (member)
  (declare (values (or null (integer 0 *))))
  (contact-constraint member :column))

(defsetf table-column setf-table-column)
(defun setf-table-column (member column)
  (check-type column (or null (integer 0 *)))
  (setf (contact-constraint member :column) column))




;;;  ===========================================================================  ;;; 
;;;		       SETF functions for a Table's Resources			  ;;;
;;;  ===========================================================================  ;;; 

(defmethod (setf display-left-margin) :after (new-value (table table))
  (declare (ignore new-value))
  (change-layout table))

(defmethod (setf display-right-margin) :after (new-value (table table))
  (declare (ignore new-value))
  (change-layout table))

(defmethod (setf display-top-margin) :after (new-value (table table))
  (declare (ignore new-value))
  (change-layout table))

(defmethod (setf display-bottom-margin) :after (new-value (table table))
  (declare (ignore new-value))
  (change-layout table))



(defun force-relayout (table)
  (SETF (GETF (window-plist table) :what-if-structures) nil)
  (change-layout table))

(DEFMETHOD (SETF display-horizontal-space) :after (new-value (table table))
  (DECLARE (IGNORE new-value))
  (force-relayout table))

(DEFMETHOD (SETF table-column-alignment) (new-value (table table))
  (with-slots (column-alignment)
    table
    (SETF column-alignment new-value)
    (force-relayout table)
    new-value))
  
(DEFMETHOD (SETF table-column-width) (new-value (table table))
  (with-slots (column-width)
    table
    (SETF column-width new-value)
    (force-relayout table)
    new-value))
  
(DEFMETHOD (SETF table-columns) (new-value (table table))
  (with-slots (columns)
    table
    (SETF columns new-value)
    (DOLIST (kid (composite-children table))
      (SETF (table-column kid) nil
	    (table-row kid) nil))
    (force-relayout table)
    new-value))
  
(DEFMETHOD (SETF table-delete-policy) (new-value (table table))
  (with-slots (delete-policy)
    table
    (SETF delete-policy new-value)
    (force-relayout table)
    new-value))
  
(DEFMETHOD (SETF table-layout-size-policy) (new-value (table table))
  (with-slots (layout-size-policy)
    table
    (SETF layout-size-policy new-value)
    (force-relayout table)
    new-value))
  
(DEFMETHOD (SETF table-row-height) (new-value (table table))
  (with-slots (row-height)
    table
    (SETF row-height new-value)
    (force-relayout table)
    new-value))
  
(DEFMETHOD (SETF table-row-alignment) (new-value (table table))
  (with-slots (row-alignment)
    table
    (SETF row-alignment new-value)
    (force-relayout table)
    new-value))
  
(DEFMETHOD (SETF table-same-width-in-column) (new-value (table table))
  (CHECK-TYPE new-value (MEMBER :on :off))
  (with-slots (same-width-in-column)
    table
    (SETF same-width-in-column new-value)
    (force-relayout table)
    new-value))
  
(DEFMETHOD (SETF table-same-height-in-row) (new-value (table table))
  (CHECK-TYPE new-value (MEMBER :on :off))
  (with-slots (same-height-in-row)
    table
    (SETF same-height-in-row new-value)
    (force-relayout table)
    new-value))
  
  
;;;  ===========================================================================  ;;; 
;;;		         A Table's Separator Methods 			  	  ;;;
;;;  ===========================================================================  ;;;
  
;;;  Note: The physical size of an OL UI separator (white-space) will be defined 
;;;	   to be half the height of the row it follows.
  
(DEFMETHOD table-separator ((table table) row-number)
  (DECLARE (type integer row-number)
	   (VALUES (MEMBER :on :off)))
  (check-type row-number (integer 0 *))
  (with-slots (separators)
    table
    (IF (MEMBER row-number separators) :on :off)))
  
  
(DEFMETHOD (SETF table-separator) (on-or-off (table table) row-number)
  (DECLARE (type integer row-number)
	   (VALUES (MEMBER :on :off)))
  (check-type row-number (integer 0 *))
  (with-slots (separators)
    table
    (LET ((already-there-p (MEMBER row-number separators)))
      (ECASE on-or-off
	(:on (UNLESS already-there-p
	       (PUSH row-number separators)
	       (force-relayout table)))
	(:off (WHEN already-there-p
		(SETF separators (DELETE row-number separators))
		(force-relayout table))))))	
  on-or-off)
  
  
  
  
;;;  ===========================================================================  ;;; 
;;;		         A Table's Table-Member Method 			  	  ;;;
;;;  ===========================================================================  ;;;
  
(DEFMETHOD table-member ((table table) row column)
  ;;  Return NIL if there is no child at position row/column.
  (DECLARE (VALUES (OR contact NULL)))
  (LET ((wis (check-for-existing-wis table (contact-width table) (contact-height table)
				     (contact-border-width table))))
    (WHEN wis
      (org-entry-kid (FIND-IF #'(lambda (x)
				  (AND (= (org-entry-row x) row)
				       (= (org-entry-column x) column)))
			      (REST (what-if-organization wis)))))))
  
(DEFMETHOD (SETF table-member) (new-value (table table) row column)
  ;;  What should we do with the child currently at position row/column?
  ;;  Set its constraints to NIL?  Set just one of its constraints to NIL?
  ;;  Error if there's one there?  I've chosen to blast its constraints.
  (LET ((existing-child-at-that-position (table-member table row column)))
    (WHEN existing-child-at-that-position
      (SETF (table-row existing-child-at-that-position) nil
	    (table-column existing-child-at-that-position) nil))
    (SETF (table-row new-value) row)
    (SETF (table-column new-value) column)
    (force-relayout table)
    new-value))



;;;  ===========================================================================  ;;; 
;;;		         A Table's Preferred-Size Method                          ;;;
;;;  ===========================================================================  ;;; 

(DEFMETHOD preferred-size ((table table) &key width height border-width)

  ;;
  ;;  Handle the case where we have no children...
  ;;
  (with-slots (children) table
    (UNLESS children
      (RETURN-FROM preferred-size
	(VALUES (+ (display-left-margin table) (display-right-margin table))
		(+ (display-top-margin table) (display-bottom-margin table))
		(contact-border-width table)))))
    
  
  (with-slots ((old-width width) (old-height height) (old-border-width border-width)) table    

    ;;
    ;;  When the caller specifies no what-if values and we have a good width & height, always
    ;;  return our current values...
    ;;
    (WHEN (AND (NULL width) (NULL height) (/= 0 old-width) (/= 0 old-height))

      (RETURN-FROM preferred-size (VALUES old-width old-height old-border-width)))

    ;;
    ;;  We need to what-if.  Figure out the width, height, and border-width to use...
    ;;
    (SETF width (OR width old-width)
	  height (OR height old-height)
	  border-width (OR border-width old-border-width))

    
    (LET ((wis (check-for-existing-wis table width height border-width)))

      (UNLESS (AND (what-if-organization wis)
		   (= (what-if-preferred-width wis) width)
		   (= (what-if-preferred-height wis) height))
	(place-children-physically table wis nil))

      (VALUES (what-if-preferred-width wis)
	      (what-if-preferred-height wis)
	      border-width))))


;;;  ===========================================================================  ;;; 
;;;		         A Table's Change-Layout Method                           ;;;
;;;  ===========================================================================  ;;; 

(DEFMETHOD change-layout ((table table) &optional newly-managed)
  (declare (type (or null contact) newly-managed))
  (DECLARE (SPECIAL *called-from-resize-method*))
  
  (with-slots (width height border-width) table
    
    ;;  Just update the current wis if a single child is being withdrawn...
    (when (AND newly-managed (EQ (contact-state newly-managed) :withdrawn))
      (LET ((wis (check-for-existing-wis table width height border-width)))
	(WHEN wis
	  (SETF (REST (what-if-organization wis))
		(DELETE newly-managed (REST (what-if-organization wis))
			:key #'org-entry-kid)))))
    
    (LET (p-width p-height
	  (wis (check-for-existing-wis table width height border-width)))
      ;;  With a change in layout we must really re-layout our children...
      (unless (what-if-in-use wis)
	(SETF (what-if-in-use wis) t)
	(place-children-physically table wis t)
	
	;;
	;;  Update the children's row/column constraints...
	;;
	(DOLIST (o-e (REST (what-if-organization wis)))
	  (SETF (table-row (org-entry-kid o-e)) (org-entry-row o-e)
		(table-column (org-entry-kid o-e)) (org-entry-column o-e)))
	
	(UNLESS (AND (BOUNDP '*called-from-resize-method*) *called-from-resize-method*)
	  (SETF p-width (what-if-preferred-width wis)
		p-height (what-if-preferred-height wis))     
	  
	  (UNLESS (AND (= height p-height) (= width p-width))
	    (SETF (what-if-width wis) p-width
		  (what-if-height wis) p-height)	      
	    (change-geometry table :width p-width :height p-height :accept-p t)))
	(SETF (what-if-in-use wis) nil)))))


;;;  ===========================================================================  ;;; 
;;;		         A Table's Resize :after Method                           ;;;
;;;  ===========================================================================  ;;; 

(DEFMETHOD resize :after ((table table) width height b-width)
  (DECLARE (IGNORE  width height b-width))
  (LET ((*called-from-resize-method* t))
    (DECLARE (SPECIAL *called-from-resize-method*))
    (change-layout table)))


						
;;;  ===========================================================================  ;;; 
;;;		         A Table's Manage-Geometry Method                         ;;;
;;;  ===========================================================================  ;;; 

;;;  This is not right yet.  It should run a what-if to get a Table size for the child's 
;;;  size change, but this is not possible yet -- the wis doesn't keep all children's
;;;  sizes.  Then it must call change-geometry to see if its parent will let it be that
;;;  size.  If so, it should return a thunk that invokes resize, not change-geometry.

(defmethod manage-geometry ((table table) child x y width height border-width &key)
  (values
      (if
	(or (and x (/= x (contact-x child)))
	    (and y (/= y (contact-y child)))
	    (and width (/= width (contact-width child)))
	    (and height (/= height (contact-height child)))
	    (and border-width (/= border-width (contact-border-width child))))
	#'(lambda (self)
	    (multiple-value-bind (p-w p-h p-b-w)
		(preferred-size self)
	      (change-geometry self
			       :width p-w
			       :height p-h
			       :border-width p-b-w
			       :accept-p t)
	      (change-layout self)
	      (display-force-output (contact-display self))))
	t)
      (or x (contact-x child))
      (or y (contact-y child))
      (or width (contact-width child))
      (or height (contact-height child))
      (or border-width (contact-border-width child))))



;;;
;;;   Internal routines that calculate the width/height of a table, given a What-if-Structure...
;;;		Calculate-Preferred-Width 
;;;		Calculate-Preferred-Height 

(DEFUN calculate-preferred-width (table wis)
  (LET* ((ncolumns (what-if-ncolumns wis))
	 (column-widths (what-if-column-widths wis))
	 (table-width (+ (display-left-margin table)
			 (display-right-margin table)
			 (* (1- ncolumns) (display-horizontal-space table)))))
    (DOTIMES (column ncolumns)
      (INCF table-width (AREF column-widths column 0)))
    table-width))


(DEFUN calculate-preferred-height (table wis)
  (with-slots (row-height separators) (THE table table)

    (LET* ((nrows (what-if-nrows wis))
	   (organization (what-if-organization wis))
	   (table-height (+ (display-top-margin table)
			    (display-bottom-margin table)
			    (* (1- nrows) (display-vertical-space table))))
	   (org-list (REST organization))
	   (fixed-row-heights row-height) height-for-this-row)

      (DO ((row 0 (1+ row)))
	  ((= row nrows))

	(MULTIPLE-VALUE-SETQ (height-for-this-row fixed-row-heights org-list)
	  (determine-a-rows-height row fixed-row-heights org-list))
	
	(INCF table-height height-for-this-row)

	;;  Note:  The physical size of an OL UI separator (white-space) will be defined 
	;;	   to be half the height of the row it follows.  A separator placed after
	;;	   the last row will result in extra white-space at the bottom of the table.
	(WHEN (MEMBER row separators)
	  (INCF table-height (FLOOR (+ height-for-this-row (display-vertical-space table)) 2))))
      
      table-height)))


(DEFUN determine-a-rows-height (row fixed-row-heights org-list1)
  (LET (fixed-height-for-this-row (height-for-this-row 0) found-a-kid-in-this-row-p)
    
    (TYPECASE fixed-row-heights
      (integer
       (SETF fixed-height-for-this-row fixed-row-heights))
      (cons
       (SETF fixed-height-for-this-row (FIRST fixed-row-heights))
       (SETF fixed-row-heights (REST fixed-row-heights))))
    
    (IF fixed-height-for-this-row
	(SETF height-for-this-row fixed-height-for-this-row)
	
	;;else find the tallest element and the largest border width in this row...
	(progn
	  (DO ((org-list1 org-list1 (REST org-list1))
	       kid1 org-entry1 (kid1s-row row))
	      ((OR (NULL org-list1) (AND found-a-kid-in-this-row-p (/= row kid1s-row))))
	    (SETF org-entry1 (FIRST org-list1))
	    (SETF kid1 (org-entry-kid org-entry1)
		  kid1s-row (org-entry-row org-entry1))
	    (WHEN (= row kid1s-row)
	      (SETF found-a-kid-in-this-row-p t)
	      (SETF height-for-this-row
		    (MAX height-for-this-row
			 (+ (org-entry-height org-entry1)
			    (org-entry-border-width org-entry1)
			    (org-entry-border-width org-entry1))))))))
    ;;
    ;;  Because all the members of a row may be withdrawn (and therefore not on the
    ;;  what-if-organization list) it is quite possible to find no children in a row.  For now
    ;;  such a row collapses to zero-height...
    (VALUES height-for-this-row fixed-row-heights org-list1)))






;;;  ===========================================================================  ;;; 
;;;	          The Guts of Table: Place-Children-Physically			  ;;;
;;;  ===========================================================================  ;;; 

(DEFUN place-children-physically (table wis really-p)  
  
  (with-slots (children same-width-in-column same-height-in-row columns
			column-alignment row-alignment
			column-width row-height
			separators) (THE table table)
    
    (LET (kid last-kid-processed height-for-this-row x1 y1
	  (fixed-row-heights (UNLESS (EQ row-height :maximum) row-height))
	  fixed-column-widths
	  width-for-this-column
	  childs-horizontal-size       ; Including border-widths.
	  childs-vertical-size	       ; Including border-widths.
	  max-child-heights-by-row
	  max-child-widths-by-columns
	  org-entry kids-row kids-column
	  y)

      (UNLESS children
	(RETURN-FROM place-children-physically))

      (CASE columns
	(:none
	 (put-kids-into-maximum-unaligned-columns table wis really-p)
	 (RETURN-FROM place-children-physically))

	(:maximum
	 ;; XtNmaximumColumns.
	 ;; Must scan the kids to figure out what width each column should be.
	 (put-kids-into-maximum-aligned-columns table wis))
	
	(otherwise
	 (UNLESS (INTEGERP columns)
	   (ERROR "~s is not a legal value for :columns" columns))
	 ;; XtNrequestedColumns.
	 (put-kids-into-specified-number-of-columns table wis)))
      
      ;;
      ;;  Position the children on the test sheet per the columnarization...
      ;;
      (WHEN really-p
	(MULTIPLE-VALUE-SETQ (max-child-heights-by-row max-child-widths-by-columns)
	  (scan-for-largest-children wis))

	(LET ((org-list (REST (what-if-organization wis)))
	      (column-widths (what-if-column-widths wis)))
	  (SETF y (display-top-margin table))
	  (CATCH 'out-of-kids
	    (DOTIMES (row (what-if-nrows wis))
	      (SETF fixed-column-widths (UNLESS (EQ column-width :maximum) column-width))
	      
	      (MULTIPLE-VALUE-SETQ (height-for-this-row fixed-row-heights)
		(determine-a-rows-height row fixed-row-heights org-list))
	      
	      (LET ((fixed-width-for-this-column
		      (AND (INTEGERP fixed-column-widths) fixed-column-widths))
		    (x (display-left-margin table)))
		
		;;  Now set the row's elements' geometries...
		(DOTIMES (column (what-if-ncolumns wis))
		  (WHEN (EQ kid last-kid-processed)
		    (SETF org-entry (FIRST org-list))
		    (WHEN (NULL org-entry)
		      (THROW 'out-of-kids t))
		    (SETF kid (org-entry-kid org-entry)
			  kids-row (org-entry-row org-entry)
			  kids-column (org-entry-column org-entry)))
		  
		  ;;  Figure out what width WE want this column to be...
		  (WHEN (CONSP fixed-column-widths)
		    (SETF fixed-width-for-this-column (FIRST fixed-column-widths)))
		  (SETF width-for-this-column
			(OR fixed-width-for-this-column (AREF column-widths column 0)))
		  (WHEN (AND (= row kids-row) (= column kids-column))		      
		    (SETF childs-horizontal-size (+ (org-entry-width org-entry)
						    (org-entry-border-width org-entry)
						    (org-entry-border-width org-entry))
			  childs-vertical-size (+ (org-entry-height org-entry)
						  (org-entry-border-width org-entry)
						  (org-entry-border-width org-entry)))
		    
		    (IF (EQ same-width-in-column :on)
			(SETF childs-horizontal-size width-for-this-column
			      x1 x)
		      ;; else...
		      (SETF childs-horizontal-size (MIN childs-horizontal-size
							width-for-this-column)
			    x1 (CASE column-alignment
				 (:left x)
				 (:right (+ x (- width-for-this-column
						 childs-horizontal-size)))
				 (:center (+ x (FLOOR (- width-for-this-column
							 childs-horizontal-size) 2))))))
		    
		    (IF (EQ same-height-in-row :on)
			(SETF childs-vertical-size height-for-this-row
			      y1 y)
		      ;; else...
		      (SETF childs-vertical-size (MIN childs-vertical-size
						      height-for-this-row)
			    y1 (CASE row-alignment
				 (:top y)
				 (:bottom (+ y (- height-for-this-row
						  childs-vertical-size)))
				 (:center (+ y (FLOOR (- height-for-this-row
							 childs-vertical-size) 2))))))
		    
		    ;;
		    ;;   Reposition and/or resize the child iff needed...
		    ;;
		    (LET ((desired-width (- childs-horizontal-size
					    (org-entry-border-width org-entry)
					    (org-entry-border-width org-entry)))
			  (desired-height (- childs-vertical-size
					     (org-entry-border-width org-entry)
					     (org-entry-border-width org-entry))))
		      (with-state (kid)
			(UNLESS (AND (= x1 (contact-x kid))
				     (= y1 (contact-y kid)))
			  (move kid x1 y1))
			(UNLESS (AND (= desired-width (contact-width kid))
				     (= desired-height (contact-height kid))
				     (= (org-entry-border-width org-entry)
					 (contact-border-width kid)))
			  (resize kid desired-width desired-height
				  (org-entry-border-width org-entry))))

		      ;;
		      ;;   Done with this child, move on to the next...
		      ;;
		      (SETF org-list (REST org-list))
		      (SETF last-kid-processed kid)))	  

		    ;;
		    ;;   Whether or not a kid was placed at this row/column, move on to the
		    ;;   next column...
		    (INCF x (+ width-for-this-column
			       (display-horizontal-space table)))
		    (WHEN (CONSP fixed-column-widths)
		      (SETF fixed-column-widths (REST fixed-column-widths))))
		    
		;;
		;;   Get vertical position of top of borders of next row's elements...
		;;
		(INCF y (+ height-for-this-row
			   (display-vertical-space table)))
		(WHEN (MEMBER row separators)
		  (INCF y (FLOOR (+ height-for-this-row
				    (display-vertical-space table)) 2))))))
	  ))
	
	;;
	;;   Having finished placing the kids we can put our preferred size into our wis...
	;;
      (SETF (what-if-preferred-height wis) (calculate-preferred-height table wis)
	    (what-if-preferred-width wis) (calculate-preferred-width table wis))
	)))

    
  
(DEFUN scan-for-largest-children (wis)
  
  (LET* ((max-child-heights-by-row (MAKE-ARRAY (what-if-nrows wis) :initial-element 0))
	 (max-child-widths-by-column (MAKE-ARRAY (what-if-ncolumns wis) :initial-element 0)))
    
    (DOLIST (org-entry (REST (what-if-organization wis)))
      (LET ((row (org-entry-row org-entry))
	    (column (org-entry-column org-entry))
	    (total-child-width (+ (org-entry-width org-entry)
				  (org-entry-border-width org-entry)
				  (org-entry-border-width org-entry)))
	    (total-child-height (+ (org-entry-height org-entry)
				   (org-entry-border-width org-entry)
				   (org-entry-border-width org-entry))))
	(SETF (SVREF max-child-heights-by-row row)
	      (MAX (SVREF max-child-heights-by-row row) total-child-height))
	(SETF (SVREF max-child-widths-by-column column)
	      (MAX (SVREF max-child-widths-by-column column) total-child-width))))
    
    (VALUES max-child-heights-by-row max-child-widths-by-column)))



(DEFUN put-kids-into-specified-number-of-columns (table wis)

  (DECLARE (VALUES widths-for-columns))
  
  (with-slots (column-width columns children) (THE table table)
    (LET* (fixed-width-for-this-column total-kid-width 
	   (fixed-widths-for-columns column-width))

      (SETF (what-if-ncolumns wis) 	 columns
	    (what-if-nrows wis)	  	(CEILING (LENGTH children) columns)
	    (what-if-column-widths wis) (MAKE-ARRAY `(,columns 2) :initial-element 0))

      ;;  Construct the organization list by assigning the children to specific row/column
      ;;  positions in the Table...
      (assign-kids-to-rows-and-columns table wis)

      ;;  Ncolumns was specified by the user.  Nrows was determined from this and by
      ;;  assign-kids-to-rows-and-columns.  This routine scans the organization and builds the array
      ;;  of (list column-width width-of-widest-entry-column) entries.  This array is left in the
      ;;  column-widths slot.

      ;;
      ;;  Find the widest child in each row, set the 2nd element of each width-of-columns
      ;;  entry to the width of the widest child in that column...
      ;;
      (DO ((org-list1 (REST (what-if-organization wis)) (REST org-list1))
	   kid1 org-entry1 kid1s-column kid1s-row)
	  ((NULL org-list1))
	(SETF org-entry1 (FIRST org-list1))
	(SETF kid1 (org-entry-kid org-entry1)
	      kid1s-row (org-entry-row org-entry1)
	      kid1s-column (org-entry-column org-entry1))
	(SETF total-kid-width (+ (org-entry-width org-entry1)
				 (org-entry-border-width org-entry1)
				 (org-entry-border-width org-entry1)))
	(Setf (AREF (what-if-column-widths wis) kid1s-column 1)
	      (MAX (AREF (what-if-column-widths wis) kid1s-column 1) total-kid-width)))
      

      ;;
      ;;  Now go through the columns looking for those with pre-set widths.  Use any pre-set
      ;;  width as the column's width, otherwise use the width of the column's widest child.
      ;;
      (SETF fixed-widths-for-columns column-width)
      (DOTIMES (current-column (what-if-ncolumns wis))
	;;  Get current-column's fixed width, if any...
	(SETF fixed-width-for-this-column 
	      (TYPECASE fixed-widths-for-columns
		(integer fixed-widths-for-columns)
		(CONS (PROG1 (FIRST fixed-widths-for-columns)
			     (SETF fixed-widths-for-columns (REST fixed-widths-for-columns))))))
	(SETF (AREF (what-if-column-widths wis) current-column 0)
	      (OR fixed-width-for-this-column (AREF (what-if-column-widths wis) current-column 1)))))))

(DEFUN find-first-parents-width (table)
  (DO ((parent (contact-parent table) (contact-parent parent)))
      ((NULL parent))
    (UNLESS (ZEROP (contact-width parent))
      (RETURN (contact-width parent)))))

(DEFUN put-kids-into-maximum-unaligned-columns (table wis really-p)

  (with-slots (children same-width-in-column) (THE table table)
      
    (LET* ((org-list (LIST nil))
	   (working-width (what-if-width wis))
	   (border-width (what-if-border-width wis)))
      
      (WHEN (ZEROP working-width)
	  (SETF working-width (- (find-first-parents-width table) border-width border-width)))
      
      ;;  Start by sorting the list of children by their row/column constraints.  Once this is
      ;;  done we ignore the constraints from here on for :none layout policy...
      (LET ((nkids (LENGTH children)))
	(SETF (what-if-nrows wis) nkids
	      (what-if-ncolumns wis) nkids)
	(assign-kids-to-rows-and-columns table wis))
      
      (LET ((next-x-pos (display-left-margin table))
	    (next-y-pos (display-top-margin table))
	    (largest-height-this-row 0)
	    (columns-this-row 0)
	    (ncolumns-in-table 0)
	    (nrows-in-table 0)
	    (preferred-width-of-table 0))
	
	(FLET
	  ((handle-the-end-of-a-row ()
	     (SETF ncolumns-in-table (MAX ncolumns-in-table columns-this-row))
	     (SETF preferred-width-of-table
		   (MAX preferred-width-of-table
			(+ next-x-pos
			   (- (display-right-margin table)
			      (display-horizontal-space table)))))
	     (SETF next-x-pos (display-left-margin table))
	     (INCF nrows-in-table)
	     (INCF next-y-pos (+ largest-height-this-row
				 (display-vertical-space table)))
	     (SETF columns-this-row 0
		   largest-height-this-row 0))
	   )
	  
	  (DOLIST (child children)
	    (UNLESS (EQ (contact-state child) :withdrawn)
	      (MULTIPLE-VALUE-BIND (childs-p-width childs-p-height childs-p-border-width)
		  (preferred-size child)
		(LET ((childs-total-width (+ childs-p-width (* 2  childs-p-border-width)))
		      (childs-total-height (+ childs-p-height (* 2  childs-p-border-width))))
		  
		  ;;
		  ;;  If cannot place this child at the end of this row, finish off this row and move
		  ;;  on to the next row...
		  ;;
		  (WHEN (< (- working-width next-x-pos (display-right-margin table))
			   childs-total-width)
		    (handle-the-end-of-a-row))
		  ;;
		  ;;  Position this child where we've decided it should go...
		  ;;
		  (WHEN really-p
		    (with-state (child)
		      (UNLESS (AND (= next-x-pos (contact-x child))
				   (= next-y-pos (contact-y child)))
			(move child next-x-pos next-y-pos))
		      (UNLESS (AND (= childs-p-width (contact-width child))
				   (= childs-p-height (contact-height child))
				   (= childs-p-border-width (contact-border-width child)))
			(resize child childs-p-width childs-p-height childs-p-border-width))))
		  
		  ;;
		  ;;  Done with this child, move on to the next child and the next position in this
		  ;;  row...
		  ;;
		  (PUSH (make-org-entry :kid child
					:row  nrows-in-table
					:column columns-this-row
					:width childs-p-width
					:height childs-p-height
					:border-width childs-p-border-width) org-list)
		  (INCF next-x-pos (+ childs-total-width
				      (display-horizontal-space table)))
		  (SETF largest-height-this-row (MAX largest-height-this-row childs-total-height))
		  (INCF columns-this-row)))))

	  ;;
	  ;;  Set into the what-if structure the height, width, and organization just calculated...
	  ;;
	  (handle-the-end-of-a-row)
	  (SETF (what-if-nrows wis) nrows-in-table)
	  (SETF (what-if-ncolumns wis) ncolumns-in-table)
	  (SETF (what-if-preferred-height wis)
		(+ next-y-pos (- (display-vertical-space table))
		   (display-bottom-margin table)))
	  (SETF (what-if-preferred-width wis) preferred-width-of-table)
	  (SETF (what-if-organization wis) (NREVERSE org-list))
	  ;;
	  ;;  Set up a fake column-widths array for others...
	  ;;
	  (SETF (what-if-column-widths wis)
		(MAKE-ARRAY `(,ncolumns-in-table 2) :initial-element 0))
	  
	  (SETF (AREF (what-if-column-widths wis) 0 0) (what-if-preferred-width wis)))))))


(DEFUN put-kids-into-maximum-aligned-columns (table wis)
  ;; This is a guessing procedure that implements the XtNmaximumColumns policy for row and column
  ;; layout.  Keep an array of items (column-width max-width-of-columns-items).  Create and
  ;; initialize it from the 1st child: identical column widths = 1st child's preferred width,
  ;; max-width-of-columns-items = 0.  Set NROWS to 0.  Then start trying to place the children
  ;; into these columns.  The 1st child will fit for sure, updating the 1st column's max-width.
  ;; The 2nd-Nth children may or may not fit.  If it does, update max-width.  If not, see if
  ;; other columns' can be made narrower to allow this column to be made wide enough for him to
  ;; fit.  If so, do it.  If not, we must reduce the number of columns by one, assigning them
  ;; equal widths, then start the layout process from the top.  Each time we try to place a child
  ;; in the first column, increment NROWS.
  
  ;; Note that while this routine tends to give about the same amount of space to each column,
  ;; the slack space for the columns may differ considerably.  After we find a child the cannot
  ;; fit in a column and reduce the number of columns to get more space, we give each column the
  ;; same, new, enlarged space.  If one column is actually fairly narrow and doesn't need more
  ;; space it'll end up with extra slack space around it.  A slack-space-smoothing routine should
  ;; be written to improve this.
  
  (DECLARE (VALUES nrows ncolumns column-widths))
  
  (with-slots (children column-width) (THE table table)
    
      (LET ((nkids (LENGTH children))
	    (working-width (what-if-width wis))
	    (working-border-width (what-if-border-width wis)))
	
	(WHEN (<= working-width 0)
	  (SETF working-width (- (find-first-parents-width table)
				 working-border-width working-border-width)))

	;;
	;;  Start by sorting the list of children by their row/column constraints.  Once this is
	;;  done we ignore the constraints from here on for :maximum layout policy...
	;;
	(SETF (what-if-nrows wis) nkids
	      (what-if-ncolumns wis) nkids)
	(assign-kids-to-rows-and-columns table wis)
	
	
	;;  Start with an upper bound on the number of columns...
	(LET* ((ncolumns (MIN nkids (get-maximum-possible-ncolumns table working-width)))
	       (column-widths (MAKE-ARRAY `(,ncolumns 2)))
	       (column-widths-vector (MAKE-ARRAY (* 2 ncolumns) :displaced-to column-widths)))
					    

	  ;;
	  ;;  Each execution of this outer loop represents an attempt at fitting the children
	  ;;  into a given number of columns.  The inner loop below does the actual laying out of
	  ;;  the children; if it succeeds, it sets FINISHED to T as it exits.  If it fails, it
	  ;;  decrements NCOLUMNS and leaves FINISHED NIL.
	  ;;
	  (DO* (finished
		(org-list (LIST nil))
		(org-tail org-list)
		next-row next-column)
	       (finished
		 ;;
		 ;;  Make each column's real width equal to the widest child we've placed in it,
		 ;;  adjust ncolumns by the number of unused columns...
		 ;;
		 (DOTIMES (column ncolumns)
		   (IF (ZEROP (AREF column-widths column 1))
		       (DECF ncolumns)
		       (SETF (AREF column-widths column 0) (AREF column-widths column 1))))
		 
		 (SETF (what-if-column-widths wis) column-widths)
		 (SETF (what-if-ncolumns wis) ncolumns)
		 (SETF (what-if-organization wis) org-list)
		 (SETF (what-if-nrows wis) (1+ next-row)))
	       
	    ;;  Initialize the first ncolumns elements of the column-widths array...
	    ;;  Total horizontal space available for the columns:
	    ;;  	width - right-margin - left-margin - (n - 1)*horizontal-space.
	    ;;  This total is divided into ncolumns equal chunks, with any extra white space
	    ;;  being given a pixel at a time to the left-most columns.
	    
	    ;;  But not quite.  We need to handle fixed-width columns specially.  At this point
	    ;;  we know how many columns we're (tentatively) giving the table, call it N.  We
	    ;;  need to see how much of our space is occupied by fixed-width columns in the
	    ;;  first N columns and how many there are, call it M.  The remaining N-M columns
	    ;;  each gets 1/(N-M) of the remaining space.  Be careful abaout N=M!  And each
	    ;;  fixed-width column gets *both* of its column-width entries initialized here to
	    ;;  its fixed width so it'll look like there's no slack in that column (which there
	    ;;  isn't).  Unlike a variable-width column, a fixed-width column never gets its
	    ;;  2nd column-widths entry changed as we place kids in it.

	    (LET ((total-fixed-width 0) (n-fixed-width-columns 0)
		  (fixed-column-widths (UNLESS (EQ column-width :maximum) column-width)))

	      ;;  Forget the column widths calculated last time through the loop...
	      (FILL (THE vector column-widths-vector) nil)
	      
	      ;;  Calculate how much of the total table width is allocated to fixed-width
	      ;;  columns...
	      (COND
		((NULL fixed-column-widths))
		((INTEGERP fixed-column-widths)
		 (SETF total-fixed-width (* ncolumns fixed-column-widths)
		       n-fixed-width-columns ncolumns)
		 (DOTIMES (column-number ncolumns)
		   (SETF (AREF column-widths column-number 0)
			 (SETF (AREF column-widths column-number 1) fixed-column-widths))))
		((CONSP fixed-column-widths)
		 (DO ((fixed-column-widths fixed-column-widths (REST fixed-column-widths))
		      (column-number 0 (1+ column-number))
		      fixed-width)
		     ((OR (= column-number ncolumns)
			  (ENDP fixed-column-widths)))
		   (SETF fixed-width (FIRST fixed-column-widths))
		   (WHEN fixed-width
		     (INCF n-fixed-width-columns)
		     (INCF total-fixed-width fixed-width)
		     (SETF (AREF column-widths column-number 0)
			   (SETF (AREF column-widths column-number 1) fixed-width)))))
		(t (ERROR "column-width is ~a." fixed-column-widths)))
	      
	      ;;  Now n-fixed-width-columns = # of fixed width columns in first ncolumns
	      ;;      total-fixed-width     = # of pixels occupied by those columns
	      ;;  and for each fixed-width column both column-widths entries = the fixed width.

	      ;;  Take the remaining space and give it to the non-fixed-width columns...	      
	      (UNLESS  (ZEROP (- ncolumns n-fixed-width-columns))
		(MULTIPLE-VALUE-BIND (horizontal-space-for-each-var-column extra-white-space)
		    (FLOOR (- working-width
			      (display-left-margin table)
			      (display-right-margin table)
			      (* (1- ncolumns) (display-horizontal-space table))
			      total-fixed-width)
			   (- ncolumns n-fixed-width-columns))
		  
		  ;;  Assign the non-fixed-width space to the non-fixed-width columns.  Because
		  ;;  we FILL column-widths with NIL each time through the main loop, only
		  ;;  fixed-width columns will have none-NIL values in them.  Give the extra
		  ;;  white-space to the left-most variable-width columns a pixel at a time.
		  (DOTIMES (i ncolumns)
		    (WHEN (NULL (AREF column-widths i 0))
		      (SETF (AREF column-widths i 0)
			    (+ horizontal-space-for-each-var-column
			       (IF (ZEROP extra-white-space)
				   0
				   (PROGN (DECF extra-white-space) 1))))
		      (SETF (AREF column-widths i 1) 0)))))

	       
	       (SETF org-list (LIST nil)
		     org-tail org-list
		     next-row -1
		     next-column (1- ncolumns))
	       
	       ;;
	       ;;  Try to lay the children into the columns sized as they are now...
	       ;;
	       (DOLIST (child children (SETF finished t))
		 
		 (UNLESS (EQ (contact-state child) :withdrawn)
		   ;;
		   ;;  If the column this child's to go in is beyond ncolumns, wrap to the first
		   ;;  column of the next row...
		   ;;
		   (INCF next-column)
		   (WHEN (= next-column ncolumns)
		     (SETF next-column 0)
		     (INCF next-row)
		     (SETF fixed-column-widths (UNLESS (EQ column-width :maximum) column-width)))
		   
		   (LET* ((columns-width-right-now (AREF column-widths next-column 0))
			  (fixed-width-for-this-column
			    (IF (LISTP fixed-column-widths)      ;; ERCM
				(FIRST fixed-column-widths)
				fixed-column-widths)))

		     (UNLESS fixed-width-for-this-column			
			;;  Find out what width the child thinks he should be...
			(MULTIPLE-VALUE-BIND (childs-width childs-height childs-border-width)
			    (preferred-size child :width columns-width-right-now)
			  (DECLARE (IGNORE childs-height))
			  
			  ;;  Calculate how much horizontal space this child needs...
			  (LET ((horizontal-space-for-this-child
				  (+ childs-width childs-border-width childs-border-width)))
			    
			    (COND
			      ((OR (<= horizontal-space-for-this-child columns-width-right-now)
				   (adjust-column-widths-so-child-fits
				     column-widths horizontal-space-for-this-child
				     next-column ncolumns))
			       (SETF (AREF column-widths next-column 1)
				     (MAX (AREF column-widths next-column 1)
					  horizontal-space-for-this-child)))
			      (t			   
			       ;; else child can't fit in this column.  Reduce the number of
			       ;; columns and try again.
			       (DECF ncolumns)
			       (RETURN nil)))))))

		   ;;  To get here we must have decided we can successfully place this kid at
		   ;;  this position, so add an entry for it onto the org-list...
		   (SETF (REST org-tail)
			 (LIST (establish-org-entry child next-row next-column)))
		   (SETF org-tail (REST org-tail))
		   
		   ;;  Advance to the next column's entry in the fixed-width list if there is
		   ;;  one...
		   (WHEN (CONSP fixed-column-widths)
		     (SETF fixed-column-widths (REST fixed-column-widths)))))))))))


(DEFUN adjust-column-widths-so-child-fits (column-widths childs-width next-column ncolumns)
  
  (DO ((npixels-needed (- childs-width (AREF column-widths next-column 0))))
      ((ZEROP npixels-needed)
       (SETF (AREF column-widths next-column 0) childs-width)
       t)
    
    ;; Find column with greatest slack, if any...
    (LET ((max-slack 0) (max-slack-col nil))
      (DOTIMES (col ncolumns)
	(UNLESS (= next-column col)		; Don't look at column child goes in
	  (LET ((slack (- (AREF column-widths col 0) (AREF column-widths col 1))))
	    (WHEN (> slack max-slack)
	      (SETF max-slack slack
		    max-slack-col col)))))
      
      ;;  If no column had any slack, return NIL...
      (UNLESS max-slack-col (RETURN nil))

      ;;  Otherwise take a pixel from the max-slack-col's width, reduce our goal by one, try
      ;;  again...
      (DECF (AREF column-widths max-slack-col 0))
      (DECF npixels-needed))))



(DEFUN get-maximum-possible-ncolumns (table width)
  "Returns the maximum number of columns possible given the specified constraints."
  (with-slots (children column-width) (THE table table)
    
    (LET* ((fixed-column-widths (UNLESS (EQ column-width :maximum) column-width))
	   (minimum-column-width
	     (- width (display-left-margin table) (display-right-margin table))))

      ;;
      ;;  If the caller specified a single fixed width for all columns, then that's it...
      ;;
      (IF (INTEGERP fixed-column-widths)
	  (SETF minimum-column-width (MIN minimum-column-width fixed-column-widths))

	;; else...
	(PROGN
	  ;;
	  ;;  If the caller specified a list of fixed widths (and nil's) for (some of) the
	  ;;  columns, first find the minimum of these fixed column widths...
	  ;;
	  (WHEN (CONSP fixed-column-widths)
	    (DOLIST (this-fixed-column-width fixed-column-widths)
	      (WHEN this-fixed-column-width
		(SETF minimum-column-width
		      (MIN minimum-column-width this-fixed-column-width)))))
	  
	  ;;
	  ;;  Then as a crude approximation, find the narrowest child, not knowing what column
	  ;;  the child will go in...
	  ;;
	  (DOLIST (kid children)
	    (UNLESS (EQ (contact-state kid) :withdrawn)
	      (MULTIPLE-VALUE-BIND (preferred-width preferred-height preferred-border-width)
		  (preferred-size kid)
		(DECLARE (IGNORE preferred-height))
		(SETF minimum-column-width
		      (MIN minimum-column-width
			   (+ preferred-width preferred-border-width preferred-border-width))))))))
      
      ;;  Now that we have the smallest column width we could ever get, calculate and return the
      ;;  maximum number of columns we could ever have...
      (MIN (LENGTH children)
	   (FLOOR (+ (- width
			(display-left-margin table)
			(display-right-margin table))
		     (display-horizontal-space table))
		  (+ minimum-column-width (display-horizontal-space table)))))))



;;;
;;;   These routines construct the ORGANIZATION list by placing each child at a specific
;;;   row/column position
;;;
;;;.  Lexical variables:
;;;	hole-pointer	where in the existing organization list to rplacd-in an entry for an
;;;			unconstrained child -- the current "hole".  All entries in the
;;;			organization list preceding this one are contiguous starting from row 0,
;;;			column 0, so all attempts at child placement, regardless of the
;;;			constraints, start from here.  Hole-row & hole-column are one row/col
;;;			position beyond the row/col of (FIRST hole-pointer), unless (first
;;;			hole-pointer) is NIL, in which case they are (0,0).
;;;	hole-row	the row-number of the current hole.
;;;	hole-column	the column-number of the current hole.
;;;	ncolumns	the number of columns in the table.  Fixed.
;;;	nrows		the number of rows in the table.  Can change if a child specifies a big
;;;			row-constraint.
;;;

(DEFUN assign-kids-to-rows-and-columns (table wis)
  (LET (hole-pointer hole-row hole-column ncolumns nrows)
    
    
    (DECLARE (inline insert-into-organization-list))
    (LABELS
      (
       ;;
       ;;   Makes sure the hole-pointer/row/column actually point at a hole.  If they currently
       ;;   point at an allocated table row/column, moves them over until they point at an
       ;;   unallocated one. 
       ;;
       (find-next-hole
	 ()
	 (DO* (org-entry org-row org-column
	       (org-list hole-pointer))
	      (nil)
	   ;;
	   ;;  Look at the next org-entry, the one just beyond the hole pointer.  The second -
	   ;;  Nth times through the loop this also advances the hole-pointer...
	   ;;
	   (SETF hole-pointer org-list
		 org-list (REST org-list))
	   (WHEN org-list
	     (SETF org-entry (FIRST org-list)
		   org-row (org-entry-row org-entry)
		   org-column (org-entry-column org-entry)))
	   (WHEN (OR (NULL org-list)		; Exhausted org-list.  Leave hole pointing at
						;    row/col one beyond the last org-entry.
		     (/= org-row hole-row)	; There's space between the previous org-entry
		     (/= org-column hole-column))	;    and this one.  Leave hole pointing
						;    at row/col one beyond the previous
						;    org-entry. 
	     (RETURN))
	   ;;
	   ;;   The row/column position of the hole is occupied.  Move the row/column of the hole
	   ;;   over one position, try again...
	   ;;
	   (WHEN (= (INCF hole-column) ncolumns)
	     (INCF hole-row)
	     (SETF hole-column 0))))

       ;;
       ;;   Insert KID into the organization list at INSERTION-POINT at ROW/COLUMN...
       ;;
       (insert-into-organization-list
	 (kid insertion-point row column)
	 (RPLACD insertion-point
		 (CONS (establish-org-entry kid row column)
		       (REST insertion-point)))
	 (find-next-hole)	 
	 (WHEN (>= row nrows)			; Update nrows if necessary.
	   (SETF nrows (1+ row))))		;   *
       
       ;;
       ;;   Inserts a kid with no constraints in the next hole, moves the hole pointers.  Always
       ;;   successful, so always returns T.
       ;;
       (place-a-kid-at-any-row-and-column
	 (kid)
	 (insert-into-organization-list kid hole-pointer hole-row hole-column)
	 t)
       
       ;;
       ;;   Tries to insert a kid into a specific row/column, returning T if successful, NIL if
       ;;   not.  Fails if that row/column is already occupied or specified column is outside
       ;;   ncolumns.
       ;;
       (place-a-kid-at-a-specific-row-and-column
	 (kid kid-row kid-column)
	 
	 (LET ((kid-position (+ (* ncolumns kid-row) kid-column))
	       (last-occupied-position
		 (IF (FIRST hole-pointer)
		     (+ (* ncolumns (org-entry-row (FIRST hole-pointer)))
			(org-entry-column (FIRST hole-pointer)))
		     -1)))
	   (WHEN (OR (>= kid-column ncolumns)
		     (>= last-occupied-position kid-position))
	     (RETURN-FROM place-a-kid-at-a-specific-row-and-column nil))
	   
	   
	   (DO ((org-list hole-pointer) insertion-point org-position)
	       (nil)
	     
	     (SETF insertion-point org-list
		   org-list (REST org-list))
	     
	     (SETF org-position
		   (IF org-list
		       (+ (* ncolumns (org-entry-row (FIRST org-list)))
			  (org-entry-column (FIRST org-list)))
		       (1+ kid-position)))
	     
	     (COND
	       ((= org-position kid-position)	; Kid's row/column occupied: failure.
		(RETURN-FROM place-a-kid-at-a-specific-row-and-column nil))
	       ((> org-position kid-position)	; Kid's row/column free: success.
		(insert-into-organization-list kid insertion-point kid-row kid-column)
		(RETURN-FROM place-a-kid-at-a-specific-row-and-column t))
	       (t nil)))))
       
       ;;
       ;;   Tries to insert a kid into a specific row.
       ;;   Fails if row is full, returns NIL, otherwise is successful, returns T.
       ;;
       (place-a-kid-in-a-specific-row
	 (kid kid-row)

	 (WHEN (< kid-row hole-row)
	   (RETURN-FROM place-a-kid-in-a-specific-row nil))
	 
	 (DO ((org-list hole-pointer) insertion-point
	      (last-occupied-column
		(IF (FIRST hole-pointer) (org-entry-column (FIRST hole-pointer)) -1) org-column)
	      org-entry (org-row kid-row) org-column)
	     ((OR (NULL org-list)
		  (> org-row kid-row))
	      ;; Failure -- exit here iff couldn't insert child
	      nil)
	   (SETF insertion-point org-list
		 org-list (REST org-list))
	   (IF org-list
	       (SETF org-entry (FIRST org-list)
		     org-row (org-entry-row org-entry)
		     org-column (org-entry-column org-entry))
	     ;; else no more org-entries so fake one way out there...
	     (SETF org-row (1+ kid-row)))
	   
	   (WHEN (OR (AND (= org-row kid-row)	        ; In kid's row and there's a hole.
			  (< (1+ last-occupied-column)	;   *
			     org-column))		;   *
		     (AND (> org-row kid-row)		; First org-entry beyond kid's row
			  (< last-occupied-column	;   and there's a hole at the end
			     (1- ncolumns))))		;   of the kid's row.
	     (insert-into-organization-list
	       kid insertion-point kid-row (1+ last-occupied-column))
	     (RETURN-FROM place-a-kid-in-a-specific-row t))))
       
       ;;
       ;;   Inserts a kid into a specific column.
       ;;   Fails if column is not within ncolumns, returns NIL, otherwise always successful,
       ;;   returns T.
       ;;
       (place-a-kid-in-a-specific-column
	 (kid kids-column)
	 	   
	 (WHEN (>= kids-column ncolumns)
	   (RETURN-FROM place-a-kid-in-a-specific-column nil))
	 
	 (DO* ((org-list hole-pointer) insertion-point
	       (last-org-position -1 org-position) org-position
	       (insertion-row (IF (< kids-column hole-column) (1+ hole-row) hole-row))
	       (position-of-next-occurrence-of-kids-column
		 (+ (* ncolumns insertion-row) kids-column)))
	      (nil)
	   
	   (SETF insertion-point org-list
		 org-list (REST org-list))
	   
	   (SETF org-position
		 (IF org-list
		     (+ (* ncolumns (org-entry-row (FIRST org-list)))
			(org-entry-column (FIRST org-list)))
		     (1+ position-of-next-occurrence-of-kids-column)))
	   
	   (WHEN (< last-org-position
		    position-of-next-occurrence-of-kids-column
		    org-position)
	     (insert-into-organization-list kid insertion-point insertion-row kids-column)
	     (RETURN-FROM place-a-kid-in-a-specific-column t))
	   ;; Calculate a new position-of-next-occurrence-of-kids-column if this org-entry is at
	   ;; or beyond the current value...
	   (WHEN (>= org-position position-of-next-occurrence-of-kids-column)
	     (INCF position-of-next-occurrence-of-kids-column ncolumns)
	     (INCF insertion-row))))
       
       ;;
       ;;  This is called by assign-kids-to-rows-and-columns when it realizes it is dealing with
       ;;  a :maximum or :none table.  The Table's children list is rebuilt to be
       ;;  the (already sorted) kids in the org-list followed by the kids in the free-list.
       ;;  Where unconstrained kids would normally be used to fill in holes in a
       ;;  fixed-number-of-columns table, there really are no holes for a :maximum or
       ;;  :none table so such children are just placed at the end of the Table's
       ;;  children list.
       ;;
       (build-sorted-list-of-children
	 (table org-list free-list withdrawn-children)
	 (with-slots (children) (THE table table)
	   (LET* ((sorted-children-list (MAKE-LIST (LENGTH org-list)))	; includes leading NIL.
		  (next-sorted-children-list sorted-children-list)
		  (last-sorted-children-list sorted-children-list))
	     
	     (DOLIST (org-entry (REST org-list))
	       (SETF last-sorted-children-list next-sorted-children-list
		     next-sorted-children-list (REST next-sorted-children-list))
	       (RPLACA next-sorted-children-list (org-entry-kid org-entry)))
	     
	     (WHEN free-list
	       (RPLACD last-sorted-children-list (NCONC free-list withdrawn-children)))
	     (SETF children (REST sorted-children-list)))))

       )					; ...end of labels...
      
      ;; ====================================================================================
      ;;   The code for assign-kids-to-rows-and-columns (table wis):
      ;;   Constructs the what-if-organization list by assigning each kid to a specific
      ;;   row/column position in the table.
      ;;
      (with-slots (children) (THE table table)
	(LET (free-row free-col free (old-org-list (REST (what-if-organization wis)))
	      withdrawn-children)
	  (SETF (what-if-organization wis) (LIST nil)
		hole-pointer (what-if-organization wis)
		hole-row 0
		hole-column 0
		ncolumns (what-if-ncolumns wis)
		nrows (what-if-nrows wis))
	  ;;  First try to place all the kids with definite row/column constraints.
	  ;;  Any child specifying only a row goes on the free-col list.
	  ;;  Any child specifying only a column goes on the free-row list.
	  ;;  Any child specifying neither row nor column, or any child unable to be placed where
	  ;;      its definite row/column constraints placed it, goes on the free list.
	  (DOLIST (kid children)
	    (COND
	      ((NOT (EQ (contact-state kid) :withdrawn))
	       (UNLESS (OR (NULL old-org-list)
			   (EQ kid (org-entry-kid (FIRST old-org-list))))
		 (CERROR "continue" "children and org-list don't match"))
	       (LET ((row (OR (table-row kid)
			      (AND old-org-list (org-entry-row (FIRST old-org-list)))))
		     (column (OR (table-column kid)
				 (AND old-org-list (org-entry-column (FIRST old-org-list))))))
		 (SETF old-org-list (REST old-org-list))
		 (COND
		   ((AND row column)
		    (UNLESS (place-a-kid-at-a-specific-row-and-column kid row column)
		      (PUSH kid free)))
		   (row
		    (PUSH `(,kid ,row) free-col))
		   (column
		    (PUSH `(,kid ,column) free-row))
		   (t
		    (PUSH kid free)))))
	      (t
	       (PUSH kid withdrawn-children))))
	  
	  ;;  Now try to place all the kids specifying only a column.  Since it is always OK to
	  ;;  create a new row, such kids can always be placed...
	  (DOLIST (kid-and-column (NREVERSE free-row))
	    (place-a-kid-in-a-specific-column (FIRST kid-and-column) (SECOND kid-and-column)))
	  
	  ;;  Now try to place all the kids specifying only a row.  If that row is full, place
	  ;;  the child on the free list...
	  (DOLIST (kid-and-row (NREVERSE free-col))
	    (UNLESS (place-a-kid-in-a-specific-row (FIRST kid-and-row) (SECOND kid-and-row))
	      (PUSH (FIRST kid-and-row) free)))
	  
	  ;;  Finally, place the kids that are on the free list.  These kids have no constraints,
	  ;;  so they'll all be placed in holes scanning from top-left to bottom-right or new
	  ;;  rows will be created to hold them...
	  (IF (SYMBOLP (table-columns table))
	      (build-sorted-list-of-children
		table (what-if-organization wis) (NREVERSE free) withdrawn-children)
	      
	      ;; else...
	      (PROGN 
		(DOLIST (kid (NREVERSE free))
		  (place-a-kid-at-any-row-and-column kid))
		;;
		;;  Rebuild the children list in the order of the what-if-organization
		;;  followed by any :withdrawn children not on the what-if-organization list.
		;;
		(DO ((children children (REST children))
		     (organization (REST (what-if-organization wis)) (REST organization)))
		    ((NULL organization)
		     (DOLIST (withdrawn-child withdrawn-children)
		       (RPLACA children withdrawn-child)
		       (SETF children (REST children))))
		  (RPLACA children (org-entry-kid (FIRST organization))))))
	  
	  (SETF (what-if-nrows wis) nrows))))))

;;  This is called by assign-kids-to-rows-and-columns when it realizes it is dealing with a
;;  :maximum or :none table.  The Table's children list is rebuilt to be the
;;  (already sorted) kids in the org-list followed by the kids in the free-list.  Where
;;  unconstrained kids would normally be used to fill in holes in a fixed-number-of-columns
;;  table, there really are no holes for a :maximum or :none table so such children
;;  are just placed at the end of the Table's children list.

(DEFUN build-sorted-list-of-children (table org-list free-list withdrawn-children)
  (with-slots (children) (THE table table)
    (LET* ((sorted-children-list (MAKE-LIST (LENGTH org-list)))	; includes leading NIL.
	   (next-sorted-children-list sorted-children-list)
	   (last-sorted-children-list sorted-children-list))
      
      (DOLIST (org-entry (REST org-list))
	(SETF last-sorted-children-list next-sorted-children-list
	      next-sorted-children-list (REST next-sorted-children-list))
	(RPLACA next-sorted-children-list (org-entry-kid org-entry)))
      
      (WHEN free-list
	(RPLACD last-sorted-children-list (NCONC free-list withdrawn-children)))
      (SETF children (REST sorted-children-list)))))

