;;; -*- Mode: LISP; Syntax: Common-Lisp; Package: INTERACTORS; Base: 10 -*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;         The Garnet User Interface Development Environment.      ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; This code was written as part of the Garnet project at          ;;;
;;; Carnegie Mellon University, and has been placed in the public   ;;;
;;; domain.  If you are using this code or any part of Garnet,      ;;;
;;; please contact garnet@cs.cmu.edu to be put on the mailing list. ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; This file contains the mouse and keyboard interactors to select objects
;;; and move them around or grow them.  It should be loaded after
;;; Interactors.lisp
;;;
;;; Designed and implemented by Brad A. Myers

#|
============================================================
Change log:
      6/15/93 Brad Myers - safe-functionp for input-filter
     10/11/92 Brad Myers - :input-filter takes inter as first param --Szekely
     7/16/92 Brad Myers - fixed small bug where :continuous NIL
                   meant couldn't destroy in final-function: Robert Schnelbach
     4/1/92  Andrew Mickish - kr::*debug-switch* ---> #-garnet-debug
     1/29/92 Brad Myers - added :slots-to-set parameter
		          :slots-to-set = :box or :points
                            or a list of slots for l,t,w,h or x1,y1,x2,y2
                           - allow filtering of input values
		          :input-filter - NIL for no filtering
                                        - number - grid by that amount
                                        - (xmod xorigin ymod yorigin)
                                        - Lambda(x y)
        09/11/91 Andrew Mickish - Changed Clip-And-Map to check if val-1 and
                                  val-2 are equal.
        12/31/90 Brad Myers - used Pavan's version of Clip-and-map that
                              works for real numbers also.
        10/11/90 Brad Myers - added explicit Stop-Interactor code
        10/6/89 Brad Myers - Export the function Clip-And-Map
			     Allow lines to be moved as well as changing ends
        10/5/89 Brad Myers - Add Final-Function,
				New default running-where is T
				Remove :new-obj-over slot (use :first-obj-over)
				Remove slots :x and :y
	10/4/89 Roger Dannenberg - Change debugging output
        9/20/89 Brad Myers - Added error message for bad attach
        8/14/89 Brad Myers - Fixed for multiple priority levels
        7/7/89  Brad Myers - Minimum size for growing objects and have ability to
				change end points of lines
        6/26/89  Brad Myers - Fixed to have quote for create-schema
        5/30/89  Brad Myers -  call-method -> kr-send;
			allow running-where to be set after initialized; changed name
        4/20/89  Brad Myers - schema-call -> call-method
        4/14/89  Brad Myers - fixed self-deactivate
        4/7/89 Brad Myers and Dario Giuse - fixed for new KR
        2/11/89 Lynn Baumeister - changed x,y to event in func calls
	11/8/88 Brad Myers - started adding the grow interactor
	8/30/88 Brad Myers - moved to constraint version of KR
	8/8/88 Brad Myers - started
============================================================
|#

(in-package "INTERACTORS" :use '("LISP" "KR") :nicknames '("INTER"))

;;;============================================================
;;; Filtering
;;;============================================================

(defun Get-Filtered-Input (inter event)
  (let ((filter (g-value inter :input-filter))
	(x (event-x event))
	(y (event-y event)))
    (when filter
      (if-debug inter (format T "Filtering input, old ~s,~s~%" x y)))
    (cond ((null filter)(values x y))
	  ((numberp filter) (values (* filter (round x filter))
				    (* filter (round y filter))))
	  ((listp filter)
	   (let ((xmod (first filter))
		 (xorigin (second filter))
		 (ymod (third filter))
		 (yorigin (fourth filter)))
	     (values (+ xorigin (* xmod (round (- x xorigin) xmod)))
		     (+ yorigin (* ymod (round (- y yorigin) ymod))))))
	  ((garnet-utils:safe-functionp filter) (funcall filter inter x y))
	  (t (error ":input-filter of ~s should be NIL, number, list of 4 numbers or functionp" inter)))))
		   

;;;============================================================
;;; Clip-and-Map
;;;============================================================

;;; The Clip-and-Map procedure works as follows:
;;;    (Clip-and-Map (val val-1 val-2 target-val-1 target-val-2) takes val,
;;;    clips it to be in the range val-1 .. val-2, and if target-val-1 and
;;;    target-val-2 are provided, then scales and
;;;    translates the value (using linear-interpolation) to be between
;;;    target-val-1 and target-val-2.  Unless target-val-1 and target-val-2
;;;    are both integers, the mapping will be to a float.
;;; Val-1 is allowed to be less than or greater than Val-2.
;;;
(defun Clip-and-Map (val val-1 val-2 &optional target-val-1 target-val-2)
  (if (and target-val-1 target-val-2)
      ;; then do clip and map
      (if (eq val-1 val-2)
	  (cond ((< val val-1) target-val-1)
		(t target-val-2))
	  (cond ((< val val-1 val-2) target-val-1)
		((< val-1 val-2 val) target-val-2)
		((< val val-2 val-1) target-val-2)
		((< val-2 val-1 val) target-val-1)
		(t (+ target-val-1
		      (if (and (integerp target-val-1) (integerp target-val-2))
			  ; integer targets
			  (round (* (- val val-1)
				    (- target-val-2 target-val-1))
				 (- val-2 val-1))
		          ; float targets
			  (/ (* (- val val-1) (- target-val-2 target-val-1))
			     (- val-2 val-1)))))))

      ;; else, just do clip (no map)
      (cond ((< val val-1 val-2) val-1)
	    ((< val-1 val-2 val) val-2)
	    ((< val val-2 val-1) val-2)
	    ((< val-2 val-1 val) val-1)
	    ; now make sure that return value is integer if val-1 and val-2
	    ; are both integers (this comes in real handy sometimes)
	    (t (if (and (integerp val-1) (integerp val-2))
		   (round val) val)))))


;;;============================================================
;;; Move-Grow-Interactor
;;;============================================================

;;;============================================================
;;; Helper procedures for the default procedures to go into the slots
;;;============================================================

			     ;left top width height
(defparameter *glo-points* (list 0 0 0 0))  ; use this to avoid cons-ing


;;; Calculates an object's position.
(defun CalcPosition (an-interactor obj x y)
  (let ((attach (g-value an-interactor :attach-point)))
    (if-debug an-interactor (format T "   CalcPosition attach=~s, x,y=(~s,~s)~%"
				    attach x y))
;; use a global to avoid cons-ing
    (setf (first *glo-points*)
	  (case attach
	    ((:nw :sw :w) x)
	    ((:ne :se :e) (1+ (- x (g-value obj :width))))
	    ((:n :s) (- x (floor (g-value obj :width) 2)))
	    (:center (- x (floor (g-value obj :width) 2))) ; use integer divide
	    (:where-hit (- x (g-value an-interactor :x-off)))
	    (t (error "bad attach ~s on interactor ~s" attach an-interactor))))
    (setf (second *glo-points*)
	  (case attach
	    ((:nw :ne :n) y)
	    ((:sw :se :s) (1+ (- y (g-value obj :height))))
	    ((:e :w) (- y (floor (g-value obj :height) 2)))
	    (:center (- y (floor (g-value obj :height) 2)))
	    (:where-hit (- y (g-value an-interactor :y-off)))))
    (setf (third *glo-points*) (g-value obj :width))
    (setf (fourth *glo-points*) (g-value obj :height))
    *glo-points*))
    
;; Deals with changing an objects size, not position, returns a new
;; left top w h.  x and y in are the new mouse point
(defun CalcSizeAndPosition (an-interactor obj x y)
  (let* ((attach (g-value an-interactor :attach-point))
	 (minwidth (g-value an-interactor :Min-width))
	 (minheight (g-value an-interactor :Min-height))
	 (left (g-value obj :left))
	 (top (g-value obj :top))
	 (width (g-value obj :width))
	 (height (g-value obj :height))
	 rightp1 bottomp1)
    (if-debug an-interactor
	      (format T "   CalcSizeAndPosition attach=~s, obj=~s, x,y=(~s,~s)~%"
		      attach obj x y))
    (when minwidth (setq rightp1 (1+ (opal:right obj))))
    (when minheight (setq bottomp1 (1+ (opal:bottom obj))))
    (when (eq attach :where-hit)
      (setq attach (g-value an-interactor :where-hit-attach))
      (setq x (+ x (g-value an-interactor :x-off)))  ;these are + or - as needed
      (setq y (+ y (g-value an-interactor :y-off)))) ;set by CalcWhereHitAttach
 ;; use a global to avoid cons-ing
    ;; first do left and width
    (case attach
      ((:nw :sw :w) 
       (when (and minwidth
		(< (- rightp1 x) minwidth))
	 (setq x (- rightp1 minwidth)))
       (setf (first *glo-points*) x)
       (setf (third *glo-points*) (+ width (- left x))))
      ((:s :n) ; no changes for these
       (setf (first *glo-points*) left)
       (setf (third *glo-points*) width))
      ((:ne :se :e)
       (setf (first *glo-points*) left)
       (setf (third *glo-points*)
	     (if (and minwidth
		      (< (- x left) minwidth))
		 minwidth ; use minwidth if too small
		 (1+ (- x left)))))  ; otherwise, get new width
      (t (error "bad attach ~s on interactor ~s" attach an-interactor)))

    ;; now do top and height
    (case attach
      ((:nw :ne :n)
       (when (and minheight
		(< (- bottomp1 y) minheight))
	 (setq y (- bottomp1 minheight)))
       (setf (second *glo-points*) y)
       (setf (fourth *glo-points*) (+ height (- top y))))
      ((:e :w) ; no changes for these
       (setf (second *glo-points*) top)
       (setf (fourth *glo-points*) height))
      ((:se :sw :s)
       (setf (second *glo-points*) top)
       (setf (fourth *glo-points*)
	     (if (and minheight
		      (< (- y top) minheight))
		 minheight ; use minheight if too small
		 (1+ (- y top)))))  ; otherwise, get new height
      (t (error "bad attach ~s on interactor ~s" attach an-interactor)))
    *glo-points*))

(defconstant sqrt2 (sqrt 2))

;; Deals with changing a line object's end point.  The point to change is
;; determined by the value of :attach-point, which should be 1, 2 or :where-hit
;; returns a new points lines, for both sets of end points: (x1 y1 x2 y2)
;; but only one of the points will have changed.
(defun CalcLineEndPoint (an-interactor x y)
  (let ((attach (g-value an-interactor :attach-point))
	(minlength (g-value an-interactor :Min-length))
	(origpoints (g-value an-interactor :saved-original-points)))
    (if-debug an-interactor
	      (format T "   CalcLineEndPoint attach=~s, x,y=(~s,~s)~%"
		      attach x y))
    (when (eq attach :where-hit)
      (setq attach (g-value an-interactor :where-hit-attach))) ;set by
    						       ;CalcLineWhereHitAttach
    (if minlength
	; time for expensive math
	(let (firstx firsty movingx movingy xdist ydist denom)
	  (case attach
	    (1 (setf firstx (third origpoints))
	       (setf firsty (fourth origpoints)))
	    (2 (setf firstx (first origpoints))
	       (setf firsty (second origpoints)))
	    (t (error "bad attach for line ~s, should be 1, 2, or :where-hit" attach)))
	  (setf xdist (- x firstx))
	  (setf ydist (- y firsty))
	  (setf denom (sqrt (+ (* xdist xdist)(* ydist ydist))))
	  (if (< denom minlength)
	      (progn
		(if (zerop denom) ; don't devide by zero
		    (progn
		      (setf movingx (+ x (ceiling minlength sqrt2)))
		      (setf movingy (+ y (ceiling minlength sqrt2))))
		    ; not zero, use calculated points
		    (progn
		      (setf movingx (+ firstx (ceiling (* xdist minlength) denom)))
		      (setf movingy (+ firsty (ceiling (* ydist minlength) denom))))))
	      ; else not less than minimum length
	      (progn
		(setf movingx x)
		(setf movingy y)))
	  ; now set point-list
	  (case attach
	    (1 (setf (first *glo-points*) movingx)
	       (setf (second *glo-points*) movingy)
	       (setf (third *glo-points*) firstx)
	       (setf (fourth *glo-points*) firsty))

	    (2 (setf (first *glo-points*) firstx)
	       (setf (second *glo-points*) firsty)
	       (setf (third *glo-points*) movingx)
	       (setf (fourth *glo-points*) movingy))))

	; else don't worry about minimum length because no minimum length
	(case attach
	  (1 (setf (first *glo-points*) x)
	     (setf (second *glo-points*) y)
	     (setf (third *glo-points*) (third origpoints))
	     (setf (fourth *glo-points*) (fourth origpoints)))
	  
	  (2 (setf (first *glo-points*) (first origpoints))
	     (setf (second *glo-points*) (second origpoints))
	     (setf (third *glo-points*) x)
	     (setf (fourth *glo-points*) y))
	  (t (error "bad attach for line ~s, should be 1, 2, or :where-hit" attach))))
    *glo-points*))



;;; Calculates an line's position as it is moved without changing length or slope
(defun CalcLineMove (an-interactor x y)
  (let ((attach (g-value an-interactor :attach-point))
	(origxdist (g-value an-interactor :orig-x-dist))
	(origydist (g-value an-interactor :orig-y-dist))
	xoff yoff)
    (if-debug an-interactor (format T "   CalcLineMove attach=~s, x,y=(~s,~s)~%"
				    attach x y))
    (when (eq attach :where-hit)
      (setq xoff (g-value an-interactor :x-off))  ;these are + or - as needed
      (setq yoff (g-value an-interactor :y-off)))

;; use a global to avoid cons-ing
    (setf (first *glo-points*)
	  (case attach
	    (1 x)
	    (2 (- x origxdist))
	    (:center (- x origxdist))
	    (:where-hit (- x xoff))
	    (t (error "bad attach ~s on interactor ~s" attach an-interactor))))
    (setf (second *glo-points*)
	  (case attach
	    (1 y)
	    (2 (- y origydist))
	    (:center (- y origydist))
	    (:where-hit (- y yoff))))
    (setf (third *glo-points*)
	  (case attach
	    (1 (+ x origxdist))
	    (2 x)
	    (:center (+ x origxdist))
	    (:where-hit (+ (- x xoff) origxdist))))
    (setf (fourth *glo-points*)
	  (case attach
	    (1 (+ y origydist))
	    (2 y)
	    (:center (+ y origydist))
	    (:where-hit (+ (- y yoff) origydist))))
    *glo-points*))
    


;; ----------------------------------------------------------------------
;; functions to deal with :where-hit and initialize the interactor
;; ----------------------------------------------------------------------
;; Orig-?-dist is the distance from x2 to x1, unless centered in which case it
;; is half the distance.
(defun SetLineInitialSlots (an-interactor obj x y)
  (let ((dx (- (g-value obj :x2) (g-value obj :x1)))
	(dy (- (g-value obj :y2) (g-value obj :y1)))
	(attach (g-value an-interactor :attach-point)))
    (s-value an-interactor :orig-x-dist (if (eq attach :center)
					    (floor dx 2)
					    dx))
    (s-value an-interactor :orig-y-dist (if (eq attach :center)
					    (floor dy 2)
					    dy))
    (when (eq :where-hit attach)
      ;; then also set up where to grow from or offsets
      (if (g-value an-interactor :grow-p)
	  (CalcLineWhereHitAttach an-interactor x y) ; for growing lines
	  (progn
	    (s-value an-interactor :x-off (- x (g-value obj :x1)))
	    (s-value an-interactor :y-off (- y (g-value obj :y1))))))))

;; Call this when press and attach-point is :where-hit and moving an
;; end-point of a line to set the
;; interactor's :where-hit-attach slot based on hit position.
;; Returns :where-hit-attach
(defun CalcLineWhereHitAttach (an-interactor x y)
  (let* ((origpoints (g-value an-interactor :saved-original-points))
	 (x1 (first origpoints))
	 (y1 (second origpoints))
	 (x2 (third origpoints))
	 (y2 (fourth origpoints))
	 d1 d2 attach)
    (unless (and x1 y1 x2 y2)
      (error
       "Move-Grow a line (:line-p is T), but object has no X1,Y1,X2,Y2"))
    (setq d1 (+ (* (- x1 x)(- x1 x)) (* (- y1 y)(- y1 y))))
    (setq d2 (+ (* (- x2 x)(- x2 x)) (* (- y2 y)(- y2 y))))
    (setq attach (if (< d1 d2) 1 2))
    (s-value an-interactor :where-hit-attach attach)
    (if-debug an-interactor
	      (format T "Calculated attach point for line is endpoint ~s~%" attach))
    attach))

;; Call this when press and attach-point is :where-hit and growing a
;; rectangle to set the
;; interactor's :where-hit-attach slot based on hit position.  Also sets
;; x-off and y-off.  Returns :where-hit-attach
(defun CalcWhereHitAttach (an-interactor x y)
  (let* ((origbox (g-value an-interactor :saved-original-points))
	 (x-off (- (first origbox) x))  ; should be negative numbers
	 (y-off (- (second origbox) y))   ; if point is inside the box
	 (w3 (floor (third origbox) 3))
	 (h3 (floor (fourth origbox) 3))
	 (xcontrol (cond ((< x (+ (first origbox) w3))
			  (s-value an-interactor :x-off x-off)
			  :w)
			 ((> x (+ (first origbox) w3 w3))
			  (s-value an-interactor :x-off
				   (+ -1 (third origbox) x-off))
			  :e)
			 (T (s-value an-interactor :x-off 0)
			    :c)))
	 (control (cond ((< y (+ (second origbox) h3))
			 (s-value an-interactor :y-off y-off)
			 (case xcontrol 
			   (:w :nw)
			   (:e :ne)
			   (:c :n)))
			((> y (+ (second origbox) h3 h3)) 
			 (s-value an-interactor :y-off
				   (+ -1 (fourth origbox) y-off))
			 (case xcontrol 
			   (:w :sw)
			   (:e :se)
			   (:c :s)))
			(T
			 (s-value an-interactor :y-off 0)
			 (case xcontrol 
			      (:w :w)
			      (:e :e)
			      (:c     ;; *Hack* for center, use :nw
			       (s-value an-interactor :x-off x-off)
			       (s-value an-interactor :y-off y-off)
			       :nw))))))
    (if-debug an-interactor
	      (format T "Calculated attach point is  ~s~%" control))
    (s-value an-interactor :where-hit-attach control)
    control))

;; makes the feedback for interactor be visible if vis = T or invisible if
;; vis = NIL
(defun sel-change-feedback-visible (an-interactor feedback
						  object-being-changed vis)
  #-garnet-debug (declare (ignore an-interactor))
  (when feedback
    (let ((val (if vis object-being-changed NIL)))
      (dbprint-feed :obj-over feedback val an-interactor)
      (s-value feedback :obj-over val))))
  
;; old-list4 and new-list4 should both be lists of length four.  Copies the values
;; from the old one into the new one without consing.  Useful for box slots
;; and x1 x2 slots
(defun Copy-List4 (old-list4 new-list4)
  (setf (first old-list4) (first new-list4))
  (setf (second old-list4) (second new-list4))
  (setf (third old-list4) (third new-list4))
  (setf (fourth old-list4) (fourth new-list4)))

;; Copies the 4 values into an existing list if there, otherwise creates one
(defun set-obj-list4-slot (obj slot new-list4 inter feedbackp)
  #-garnet-debug (declare (ignore inter feedbackp))
  (dbprint-either slot obj new-list4 inter feedbackp)
  (set-obj-list4-slot-no-db obj slot new-list4))

(defun set-obj-list4-slot-no-db (obj slot new-list4)
  (when obj
    (let ((oldval (get-local-value obj slot)))
      (if (and oldval (listp oldval) (eq 4 (length oldval)))
	  ; then re-use old slots so no cons-ing
	  (progn (Copy-List4 oldval new-list4)
	    (Mark-As-Changed obj slot)) ; do this to get constraints to go
	  ; else create a new one
	  (s-value obj slot (copy-list new-list4))))))

;; Top level call to set the appropriate object slots
(defun obj-or-feedback-change (feedback object-being-changed new-points line-p
					inter)
  (if feedback
      (set-obj-slots-for-movegrow feedback line-p new-points inter T)
      (set-obj-slots-for-movegrow object-being-changed
				  line-p new-points inter NIL)))

(defun set-obj-slots-for-movegrow (obj line-p new-list4 inter feedbackp)
  (when obj 
    (let ((slots-to-set (g-value inter :slots-to-set)))
      (cond ((eq slots-to-set :box)
	     (set-obj-list4-slot obj (if line-p :points :box) new-list4
				 inter NIL))
	    ((eq slots-to-set :points)
	     (set-obj-list4-slot obj (if line-p :points :box) new-list4
				 inter NIL))
	    ((null slots-to-set) (error "slots-to-set nil in ~s" inter))
	    ((listp slots-to-set)
	     (Slot-set obj (first slots-to-set) (if line-p :x1 :left)
		       (first new-list4) inter feedbackp)
	     (Slot-set obj (second slots-to-set) (if line-p :y1 :top)
		       (second new-list4) inter feedbackp)
	     (Slot-set obj (third slots-to-set) (if line-p :x2 :width)
		       (third new-list4) inter feedbackp)
	     (Slot-set obj (fourth slots-to-set) (if line-p :y2 :height)
		       (fourth new-list4) inter feedbackp))
	    (t (error "bad :slots-to-set in ~s.  Should be :box :points or list of slots" inter))))))
	     
(defun slot-set (obj slot default-slot new-val inter feedbackp)
  #-garnet-debug (declare (ignore feedbackp))
  (cond ((null slot)) ; don't set
	((eq slot T)
	 (dbprint-either default-slot obj new-val inter feedbackp)
	 (s-value obj default-slot new-val))
	((keywordp slot)
	 (dbprint-either slot obj new-val inter feedbackp)
	 (s-value obj slot new-val))
	(t (error "slots-to-set value bad ~s in inter ~s" slot inter))))


;;;============================================================
;;; Default Procedures to go into the slots
;;;============================================================

(proclaim '(special Move-Grow-Interactor))

(defun Move-Grow-Interactor-Initialize (new-Move-Grow-schema)
  (if-debug new-Move-Grow-schema (format T "Select change initialize ~s~%"
					 new-Move-Grow-schema))
  (Check-Interactor-Type new-Move-Grow-schema inter:Move-Grow-Interactor)
  (Check-Required-Slots new-Move-Grow-schema)
  (Set-Up-Defaults new-Move-Grow-schema)
  ) ;end initialize procedure

(defun Move-Grow-Int-Start-Action (an-interactor object-being-changed
						  first-points)
  (if-debug an-interactor (format T "Move-Grow int-start moving ~s firstpoints=~s~%"
				  object-being-changed first-points))
  ;;change feedback or object first so no flicker when turned visible
  (let ((feedback (g-value an-interactor :feedback-obj)))
    (obj-or-feedback-change feedback object-being-changed
			    first-points
			    (g-value an-interactor :line-p)
			    an-interactor)
    (when feedback (sel-change-feedback-visible 
		    an-interactor feedback object-being-changed T)
      )))

(defun Move-Grow-Int-Running-Action (an-interactor object-being-changed
						    new-points)
  (if-debug an-interactor
	    (format T "Move-Grow int-running, obj = ~s, points=~s~%"
				  object-being-changed new-points))
  (obj-or-feedback-change (g-value an-interactor :feedback-obj)
			object-being-changed new-points
			(g-value an-interactor :line-p)
			an-interactor))

(defun Move-Grow-Int-Outside-Action (an-interactor outside-control
						  object-being-changed) 
  (if-debug an-interactor (format T "Move-Grow int-outside, mov = ~s~%"
				  object-being-changed))
  (unless (eq :last outside-control)
    (let ((feedback (g-value an-interactor :feedback-obj)))
      (if feedback
	  (sel-change-feedback-visible
	   an-interactor feedback object-being-changed NIL)
	  (set-obj-slots-for-movegrow object-being-changed
				 (g-value an-interactor :line-p)
			         (g-value an-interactor :saved-original-points)
				 an-interactor
				 NIL)))))

(defun Move-Grow-Int-Back-Inside-Action (an-interactor outside-control
					       object-being-changed
					       new-inside-points) 
  (if-debug an-interactor 
	    (format T "Move-Grow int-back-in, obj = ~s, new points=~s~%"
		    object-being-changed new-inside-points))
  ;;first change the feedback or object to the new position, and then make it
  ;; visible, if necessary
  (let ((feedback (g-value an-interactor :feedback-obj)))
    (obj-or-feedback-change feedback object-being-changed
			  new-inside-points
			  (g-value an-interactor :line-p)
			  an-interactor)
    (when (and feedback
	       (null outside-control))
      (sel-change-feedback-visible an-interactor feedback object-being-changed T))))

(defun Move-Grow-Int-Stop-Action (an-interactor object-being-changed
						 final-points)
  (if-debug an-interactor (format T "Move-Grow int-stop obj ~s final-points=~s~%"
				  object-being-changed final-points))
  ;;turn off feedback
  (sel-change-feedback-visible an-interactor (g-value an-interactor :feedback-obj)
			       object-being-changed NIL)

  ;;set object to final position
  (set-obj-slots-for-movegrow object-being-changed
			      (g-value an-interactor :line-p)
			      final-points an-interactor NIL)

  (KR-Send an-interactor :final-function an-interactor object-being-changed
	   final-points))

(defun Move-Grow-Int-Abort-Action (an-interactor object-being-changed)
  (if-debug an-interactor (format T "Move-Grow int-abort moving ~s~%"
				  object-being-changed))
  (let ((feedback (g-value an-interactor :feedback-obj)))
    (if feedback
	(sel-change-feedback-visible an-interactor feedback
				     object-being-changed NIL)
	(set-obj-slots-for-movegrow object-being-changed
				 (g-value an-interactor :line-p)
			         (g-value an-interactor :saved-original-points)
				 an-interactor
				 NIL))))
  
;;;============================================================
;;; Go procedure utilities
;;;============================================================

;;Want a non-standard default running-where so call this instead of calling
;;Fix-Up-Running-where.    Default here is T (anywhere).
;;; probably it doesn't really make much sence to use '* with movegrow, but it
;;; is supported anyway
(defun Move-Grow-Fix-Running-where (an-interactor new-obj-over)
  (if (g-value an-interactor :running-where)
      ;; fix it up normally in case have '(:xxx *)
      (Fix-Running-Where an-interactor new-obj-over)
      ;; otherwise use T
      (s-value an-interactor :generated-running-where T)))

(defun CalcChangeBoxOrLine (an-interactor obj x y)
  (if (g-value an-interactor :line-p)
      (if (g-value an-interactor :grow-p)
	  (CalcLineEndPoint an-interactor x y)
	  (CalcLineMove an-interactor x y))
      (if (g-value an-interactor :grow-p)
	  (CalcSizeAndPosition an-interactor obj x y)
	  (CalcPosition an-interactor obj x y))))

;;; if continuous: (remove from start level, add to stop and abort
;;; 		    level, change state to running)
;;; save object over, call start procedure.
(defun Move-Grow-do-start (an-interactor new-obj-over event)
  (if-debug an-interactor (format T "Move-Grow starting over ~s~%" new-obj-over))
        ;; if obj-to-change supplied, then use that, otherwise use whatever was
	;; under the mouse when started
  (let ((obj (or (g-value an-interactor :obj-to-change) new-obj-over))
	points line-p)
    (multiple-value-bind (x y) (Get-Filtered-Input an-interactor event)
      (if-debug an-interactor (format T "   Move-Grow moving ~s~%" obj))
      (s-value an-interactor :obj-being-changed obj)
      ;; don't check line-p until the previous slots have been set, in case
      ;; there are formulas
      (setq line-p (g-value an-interactor :line-p))
      (s-value an-interactor :saved-original-points
	       (if line-p
		   (list (g-value obj :x1) (g-value obj :y1)
			 (g-value obj :x2) (g-value obj :y2))
		   (list (g-value obj :left) (g-value obj :top)
			 (g-value obj :width) (g-value obj :height))))
      (if (and obj (not (eq obj T)))
	(progn
	  (if (g-value an-interactor :line-p)
	      (SetLineInitialSlots an-interactor obj x y)
	      ;; otherwise, left,top,width,height
	      (when (eq :where-hit (g-value an-interactor :attach-point))
		(if (g-value an-interactor :grow-p)
		    (CalcWhereHitAttach an-interactor x y) ; for growing
		    (progn 		               ; for moving
		      (s-value an-interactor :x-off (- x (g-value obj :left)))
		      (s-value an-interactor :y-off (- y (g-value obj :top)))))))
	  (setf points (CalcChangeBoxOrLine an-interactor obj x y)))
	;else no object, just return x y
	(setf points (list x y 10 10)))  ; what use here for w h?
      (if (g-value an-interactor :continuous) ;then will go to running state
	(progn
	  (Move-Grow-Fix-Running-where an-interactor new-obj-over)
	  (when (g-value an-interactor :outside) ;needed if stop while outside
	    (set-obj-list4-slot-no-db an-interactor :saved-last-points points))
	  (GoToRunningState an-interactor T)
	  (kr-send an-interactor :start-action an-interactor obj points))
	;; else call stop-action
	(progn
	  (GoToStartState an-interactor NIL)
	  (kr-send an-interactor :stop-action an-interactor obj points))))))

#| ----------------------------------------------------------------------
---- Pedro's proposal for :where-hit when filter doesn't work because
---- object jumps to off-grid even if it was on grid to start with
(defun Move-Grow-do-start (an-interactor new-obj-over event)
  (if-debug an-interactor (format T "Move-Grow starting over ~s~%" new-obj-over))
        ;; if obj-to-change supplied, then use that, otherwise use whatever was
	;; under the mouse when started
  (let ((obj (or (g-value an-interactor :obj-to-change) new-obj-over))
	(eventx (event-x event))
	(eventy (event-y event))
	points line-p)
    (multiple-value-bind (x y) (Get-Filtered-Input an-interactor event)
      (if-debug an-interactor (format T "   Move-Grow moving ~s~%" obj))
      (s-value an-interactor :obj-being-changed obj)
      ;; don't check line-p until the previous slots have been set, in case
      ;; there are formulas
      (setq line-p (g-value an-interactor :line-p))
      (s-value an-interactor :saved-original-points
	       (if line-p
		   (list (g-value obj :x1) (g-value obj :y1)
			 (g-value obj :x2) (g-value obj :y2))
		   (list (g-value obj :left) (g-value obj :top)
			 (g-value obj :width) (g-value obj :height))))
      (if (and obj (not (eq obj T)))
	(progn
	  ;; use the orig, unfiltered x,y to compute for :where-hit -Szekely
	  (if (g-value an-interactor :line-p)
	      (SetLineInitialSlots an-interactor obj eventx eventy)
	      ;; otherwise, left,top,width,height
	      (when (eq :where-hit (g-value an-interactor :attach-point))
		(if (g-value an-interactor :grow-p)
		    (CalcWhereHitAttach an-interactor  ; for growing
					eventx eventy)
		    (progn 		               ; for moving
		      (s-value an-interactor :x-off
			       (- eventx (g-value obj :left)))
		      (s-value an-interactor :y-off
			       (- eventy (g-value obj :top)))))))
	  (setf points (CalcChangeBoxOrLine an-interactor obj x y)))
	;else no object, just return x y
	(setf points (list x y 10 10)))  ; what use here for w h?
      (if (g-value an-interactor :continuous) ;then will go to running state
	(progn
	  (Move-Grow-Fix-Running-where an-interactor new-obj-over)
	  (when (g-value an-interactor :outside) ;needed if stop while outside
	    (set-obj-list4-slot-no-db an-interactor :saved-last-points points))
	  (GoToRunningState an-interactor T)
	  (kr-send an-interactor :start-action an-interactor obj points))
	;; else call stop-action
	(progn
	  (GoToStartState an-interactor NIL)
	  (kr-send an-interactor :stop-action an-interactor obj points))))))
----------------------------------------------------------------------
|#

(defun Move-Grow-do-outside (an-interactor)
  (if-debug an-interactor (format T "Move-Grow outside~%"))
  (s-value an-interactor :current-state :outside)
  (kr-send an-interactor :outside-action an-interactor
	       (g-value an-interactor :outside)
	       (g-value an-interactor :obj-being-changed)))
;;;filtering based on :last is handled by the :outside-action procedure
;;;  (unless (eq :last (g-value an-interactor :outside))
;;;    (s-value an-interactor :remembered-last-object NIL)))

(defun Move-Grow-do-back-inside (an-interactor obj event)
  #-garnet-debug (declare (ignore obj))
  (if-debug an-interactor (format T "Move-Grow back-inside over ~s at:~s~%"
				  obj event))
  (multiple-value-bind (x y) (Get-Filtered-Input an-interactor event)
    ;;  (s-value an-interactor :x x)
    ;;  (s-value an-interactor :y y)
    (s-value an-interactor :current-state :running)
    (let* ((moving-obj (g-value an-interactor :obj-being-changed))
	   (points (CalcChangeBoxOrLine an-interactor moving-obj x y)))
      (when (g-value an-interactor :outside) ;needed if stop while outside
	(set-obj-list4-slot-no-db an-interactor :saved-last-points points))
      (kr-send an-interactor :back-inside-action an-interactor
		   (g-value an-interactor :outside)
		   moving-obj points))))

(defun Move-Grow-do-running (an-interactor obj event)
  #-garnet-debug (declare (ignore obj))
  (if-debug an-interactor (format T "Move-Grow running over ~s at:~s~%" obj event))
  (multiple-value-bind (x y) (Get-Filtered-Input an-interactor event)
    ;;  (s-value an-interactor :x x)
    ;;  (s-value an-interactor :y y)
    (let* ((moving-obj (g-value an-interactor :obj-being-changed))
	   (points (CalcChangeBoxOrLine an-interactor moving-obj x y)))
      (when (g-value an-interactor :outside) ;needed if stop while outside
	(set-obj-list4-slot-no-db an-interactor :saved-last-points points))
      (kr-send an-interactor :running-action an-interactor
		   moving-obj points))))

;;; points is the final value calculated
(defun Move-Grow-do-stop-helper (an-interactor points)
  (GoToStartState an-interactor T)
  (kr-send an-interactor :Stop-Action an-interactor
	       (g-value an-interactor :obj-being-changed) points))

(defun Move-Grow-do-stop (an-interactor obj event)
  #-garnet-debug (declare (ignore obj))
  (if-debug an-interactor (format T "Move-Grow stop over ~s at:~s~%"
				  obj event))
  (multiple-value-bind (x y) (Get-Filtered-Input an-interactor event)
    (s-value an-interactor :prev-x x) ; used in case explicit stop
    (s-value an-interactor :prev-y y)
    (Move-Grow-do-stop-helper an-interactor
			      (CalcChangeBoxOrLine
			       an-interactor (g-value an-interactor
						      :obj-being-changed) x y))))
(defun Move-Grow-Explicit-stop (an-interactor)
  (if-debug an-interactor (format T "Move-Grow explicit stop~%"))
  (let ((x (g-value an-interactor :prev-x))
	(y (g-value an-interactor :prev-y)))
    (Move-Grow-do-stop-helper an-interactor
			      (CalcChangeBoxOrLine
			       an-interactor (g-value an-interactor
						      :obj-being-changed) x y))))

(defun Move-Grow-do-abort (an-interactor become-inactive event)
  (declare (ignore event become-inactive))
  (if-debug an-interactor (format T "Move-Grow aborting~%"))
  (GoToStartState an-interactor T)
  (kr-send an-interactor :Abort-Action an-interactor
	       (g-value an-interactor :obj-being-changed)))

;;;check to see if need to stop or abort based on whether :outside = :last
(defun Move-Grow-do-outside-stop (an-interactor event)
  (if-debug an-interactor (format T "Move-Grow stop outside~%"))
  (if (eq :last (g-value an-interactor :outside))
      (Move-Grow-do-stop-helper an-interactor
			   (g-value an-interactor :saved-last-points))
      (Move-Grow-do-abort an-interactor NIL event)))

;;;============================================================
;;; Move-Grow schema
;;;============================================================

(Create-Schema 'inter:Move-Grow-Interactor
		     (:is-a inter:interactor)
		     (:name :First-Move-Grow-interactor)
		     (:start-action 'Move-Grow-Int-Start-Action)
		     (:running-action 'Move-Grow-Int-Running-Action)
		     (:stop-action 'Move-Grow-Int-Stop-Action)
		     (:abort-action 'Move-Grow-Int-Abort-Action)
		     (:outside-action 'Move-Grow-Int-Outside-Action)
		     (:back-inside-action 'Move-Grow-Int-Back-Inside-Action)
		     (:obj-to-change NIL)  ;supplied by application program
		     (:Min-width 0); minimum allowed width and height
		     (:Min-height 0)
		     (:attach-point :where-hit) ; where attach to object
		     (:grow-p NIL) ; if T then grow, else move
		     (:line-p NIL) ; if T, then move an end of the line,
			        ; else move left,top,width,height of rectangle
		     (:x-off 0) ; needed for :where-hit.  Offset from where
		     (:y-off 0)    ;    hit to top left of object
		     (:slots-to-set :box)
		     (:input-filter NIL)
		     (:saved-original-points NIL) ; used for ABORT or outside
		     (:saved-last-points NIL) ; used if stop and outside and
						; outside control is :last
		     (:obj-being-changed NIL) ; saved object under the mouse
		     (:Go 'General-Go)  ; proc executed when events happen
		     (:Do-Start 'Move-Grow-Do-Start)     ; these are
		     (:Do-Running 'Move-Grow-Do-Running) ;   called by GO
		     (:Do-Stop 'Move-Grow-Do-Stop)       ;   to do
		     (:Do-Explicit-Stop 'Move-Grow-Explicit-Stop);for stop-interactor
		     (:Do-Abort 'Move-Grow-Do-Abort)     ;   the real work.
		     (:Do-Outside 'Move-Grow-Do-Outside) ;   They call the
		     (:Do-Back-Inside 'Move-Grow-Do-Back-Inside)  ; appropriate
		     (:Do-Outside-Stop 'Move-Grow-Do-Outside-Stop); -action
								     ; procedures
		     (:initialize 'Move-Grow-Interactor-Initialize))


