;;; -*- 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 create new objects
;;; defined by two points (e.g., rectangles and lines).  It should be
;;; loaded after Interactor and after MoveGrowInter
;;;
;;; Designed and implemented by Brad A. Myers

#|
============================================================
Change log:
        6/4/92 Brad Myers - added :input-filter like movegrow
                                        - NIL for no filtering
                                        - number - grid by that amount
                                        - (xmod xorigin ymod yorigin)
                                        - Lambda(x y)
        4/1/92  Andrew Mickish - kr::*debug-switch* ---> #-garnet-debug
       10/11/90 Brad Myers - added explicit Stop-Interactor code
        10/5/89 Brad Myers - Add Final-Function, remove create-function
	10/4/89 Roger Dannenberg - Change debugging output
        8/14/89 Brad Myers - Fixed for multiple priority levels
				- added interactor as parameter to create-function
				- made create-function be called even if too small
        7/3/89  Brad Myers - Added minimum lengths for 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
        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/18/89 Lynn Baumeister -- changed x,y to event in func calls
	11/25/88 Brad Myers - started
============================================================
|#

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

;;;============================================================
;;;============================================================
;;;============================================================


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

(defparameter *glo-two-points* (list 0 0 0 0))  ; use this to avoid cons-ing

;;returns a list of x y w h or x1 y1 x2 y2 depending on whether is a
;; rectangle or a line.  or returns NIL if should abort
(defun TwoPCalcPoint (an-interactor whichpoint x y)
  (if (g-value an-interactor :line-p)
      (TwoPCalcLinePoint an-interactor whichpoint x y)
      (TwoPCalcRectPoint an-interactor whichpoint x y)))

(defun TwoPCalcRectPoint (an-interactor whichpoint x y)
  (let ((firstx (g-value an-interactor :first-x))
	(firsty (g-value an-interactor :first-y))
	(flip (g-value an-interactor :flip-if-change-side))
	domin abort-if-too-small minwidth minheight w h)
    (when (and (setq minwidth (g-value an-interactor :Min-width))
	     (setq minheight (g-value an-interactor :Min-height)))
	  (setq domin T)
	  (setq abort-if-too-small (g-value an-interactor :abort-if-too-small)))
    (case whichpoint
      (1 (setf (first *glo-two-points*) x)
	 (setf (second *glo-two-points*) y)
	 (setf (third *glo-two-points*) (if domin minwidth 0))
	 (setf (fourth *glo-two-points*) (if domin minheight 0))
	 (when abort-if-too-small
	   (if-debug an-interactor (format T "** rectangle too small~%"))
	   (return-from TwoPCalcRectPoint NIL)))
      (2 (if flip ; then make sure w and h are always positive
	     (progn (setq w (1+ (abs (- x firstx))))
	       (setq h (1+ (abs (- y firsty)))))
	     (progn (setq w (1+ (- x firstx)))
	       (setq h (1+ (- y firsty)))))
	 (if domin
	     (progn
	       (when (and abort-if-too-small (or (< w minwidth)(< h minheight)))
		 (if-debug an-interactor (format T "** rectangle too small~%"))
		 (return-from TwoPCalcRectPoint NIL))
	       (setq w (max minwidth w))
	       (setq h (max minheight h)))
	     (progn (setq w (max w 0))
	       (setq h (max h 0))))
	 (setf (third *glo-two-points*) w)
	 (setf (fourth *glo-two-points*) h)
	 (when flip  ; then might have to change first and second (left, top)
	   (setf (first *glo-two-points*)
		 (if (< x firstx)
		     (1+ (- firstx w))
		     firstx))
	   (setf (second *glo-two-points*)
		 (if (< y firsty)
		     (1+ (- firsty h))
		     firsty)))))
    *glo-two-points*))

(defun TwoPCalcLinePoint (an-interactor whichpoint x y)
  (let (minlength abort-if-too-small firstx firsty xdist ydist denom)
    (when (setq minlength (g-value an-interactor :Min-length))
      (setq abort-if-too-small (g-value an-interactor :abort-if-too-small))
      (setq firstx (g-value an-interactor :first-x))
      (setq firsty (g-value an-interactor :first-y)))
    (case whichpoint
      (1 (setf (first *glo-two-points*) x)
	 (setf (second *glo-two-points*) y)
	 (setf (third *glo-two-points*)
	       (if minlength (+ x (ceiling minlength sqrt2)) x))
	 (setf (fourth *glo-two-points*)
	       (if minlength (+ y (ceiling minlength sqrt2)) y))
	 (when abort-if-too-small
	   (if-debug an-interactor (format T "** line too small~%"))
	   (return-from TwoPCalcLinePoint NIL)))
      (2 (if minlength
	     (progn  ; time for expensive math
	       (setf xdist (- x firstx))
	       (setf ydist (- y firsty))
	       (setf denom (sqrt (+ (* xdist xdist)(* ydist ydist))))
	       (if (< denom minlength)
		   (progn
		     (when abort-if-too-small
		       (if-debug an-interactor (format T "** line too small~%"))
		       (return-from TwoPCalcLinePoint NIL))
		     (if (zerop denom) ; don't devide by zero
			 (progn
			   (setf (third *glo-two-points*)
				 (+ x (ceiling minlength sqrt2)))
			   (setf (fourth *glo-two-points*)
				 (+ y (ceiling minlength sqrt2))))
			 ; not zero, use calculated points
			 (progn
			   (setf (third *glo-two-points*)
				 (+ firstx (ceiling (* xdist minlength) denom)))
			   (setf (fourth *glo-two-points*)
				 (+ firsty (ceiling (* ydist minlength) denom))))))
		   ; else not less than minimum length
		   (progn
		     (setf (third *glo-two-points*) x)
		     (setf (fourth *glo-two-points*) y))))
		   ; else don't worry about minimum length
	     (progn
	       (setf (third *glo-two-points*) x)
	       (setf (fourth *glo-two-points*) y)))))
    *glo-two-points*))

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

(proclaim '(special Two-Point-Interactor))

(defun Two-Point-Interactor-Initialize (new-two-point-schema)
  (if-debug new-two-point-schema (format T "Two-point initialize ~s~%"
					 new-two-point-schema))
  (Check-Interactor-Type new-two-point-schema inter:Two-Point-Interactor)
  (Check-Required-Slots new-two-point-schema)
  (Set-Up-Defaults new-two-point-schema)
  ) ;end initialize procedure

;;x2 and y2 will be equal to x1 y1 (w, h = 0)
(defun Two-Point-Int-Start-Action (an-interactor first-points)
  (if-debug an-interactor (format T "Two-Point int-start first points=~s~%"
				  first-points))
  ;;change feedback or object first so no flicker when turned visible
  (let ((feedback (g-value an-interactor :feedback-obj)))
    (when (and feedback first-points)
      (set-obj-list4-slot feedback
			  (if (g-value an-interactor :line-p) :points :box)
			  first-points
			  an-interactor T)
      (dbprint-feed :visible feedback T an-interactor)
      (s-value feedback :visible T))))

(defun Two-Point-Int-Running-Action (an-interactor new-points)
  (if-debug an-interactor (format T "Two-Point int-running, points=~s~%"
				  new-points))
  (let ((feedback (g-value an-interactor :feedback-obj))
	 feedbackvis)
    (when feedback
      (setq feedbackvis (g-value feedback :visible))
      (if new-points
	  (progn
	    (set-obj-list4-slot feedback
				(if (g-value an-interactor :line-p)
				    :points :box)
				new-points an-interactor T)
	    (unless feedbackvis
	      (dbprint-feed :visible feedback T an-interactor)
	      (s-value feedback :visible T)))
	  (when feedbackvis
	    (dbprint-feed :visible feedback NIL an-interactor)
	    (s-value feedback :visible NIL))))))

(defun Two-Point-Int-Outside-Action (an-interactor outside-control)
  (if-debug an-interactor (format T "Two-Point int-outside~%"))
  (unless (eq :last outside-control)
    (let ((feedback (g-value an-interactor :feedback-obj)))
      (when feedback
	(dbprint-feed :visible feedback NIL an-interactor)
	(s-value feedback :visible NIL)))))

(defun Two-Point-Int-Back-Inside-Action (an-interactor outside-control
						    new-inside-points) 
  (declare (ignore outside-control))
  (if-debug an-interactor (format T "Two-Point int-back-in, new points=~s~%"
				  new-inside-points))
  ;;first change the feedback or object to the new position, and then make it
  ;; visible
  (let ((feedback (g-value an-interactor :feedback-obj)))
    (when feedback
      (if new-inside-points
	  (progn
	    (set-obj-list4-slot feedback
				(if (g-value an-interactor :line-p) :points :box)
				new-inside-points an-interactor T)
	    (dbprint-feed :visible feedback T an-interactor)
	    (s-value feedback :visible T))
	  (progn
	    (dbprint-feed :visible feedback NIL an-interactor)
	    (s-value feedback :visible NIL))))))

(defun Two-Point-Int-Stop-Action (an-interactor final-points)
  (if-debug an-interactor (format T "Two-Point int-stop, final-points=~s~%"
				  final-points))
  ;;turn off feedback
  (let ((feedback (g-value an-interactor :feedback-obj)))
    (when feedback
      (when final-points
	(set-obj-list4-slot feedback
			    (if (g-value an-interactor :line-p) :points :box)
			    final-points an-interactor T))
      (dbprint-feed :visible feedback NIL an-interactor)
      (s-value feedback :visible NIL)))
  (kr-send an-interactor :final-function an-interactor final-points))

(defun Two-Point-Int-Abort-Action (an-interactor)
  (if-debug an-interactor (format T "Two-Point int-abort~%"))
  (let ((feedback (g-value an-interactor :feedback-obj)))
    (when feedback
      (dbprint-feed :visible feedback NIL an-interactor)
      (s-value feedback :visible NIL))))
  
;;;============================================================
;;; Go procedure utilities
;;;============================================================


;;; if continuous: (remove from start level, add to stop and abort
;;; 		    level, change state to running)
;;; save object over, call start procedure.
(defun two-point-do-start (an-interactor new-obj-over event)
  (if-debug an-interactor (format T "Two-Point starting over ~s~%" new-obj-over))
  ;; get-filtered-input is defined in MoveGrowInter.lisp
  (multiple-value-bind (x y) (Get-Filtered-Input an-interactor event)
    (let ((points (TwoPCalcPoint an-interactor 1 x y)))
      (s-value an-interactor :x x)
      (s-value an-interactor :y y)
      (if (g-value an-interactor :continuous) ;then will go to running state
	  (progn
	    (Fix-Running-Where an-interactor new-obj-over);; not likely to be needed
	    (s-value an-interactor :first-x x)
	    (s-value an-interactor :first-y y)
	    (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 points))
	  ;; else call stop-action
	  (progn
	    (kr-send an-interactor :stop-action an-interactor points)
	    (GoToStartState an-interactor NIL))))))

(defun two-point-do-outside (an-interactor)
  (if-debug an-interactor (format T "Two-Point outside~%"))
  (s-value an-interactor :current-state :outside)
  (kr-send an-interactor :outside-action an-interactor
	       (g-value an-interactor :outside)))
;;;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 two-point-do-back-inside (an-interactor obj event)
  #-garnet-debug (declare (ignore obj))
  (if-debug an-interactor (format T "Two-Point back-inside over ~s~%" obj))
  (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* ((points (TwoPCalcPoint an-interactor 2 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)
		   points))))

(defun two-point-do-running (an-interactor obj event)
  #-garnet-debug (declare (ignore obj))
  (if-debug an-interactor (format T "Two-Point running over ~s~%" obj))
  (multiple-value-bind (x y) (Get-Filtered-Input an-interactor event)
    (s-value an-interactor :x x)
    (s-value an-interactor :y y)
    (let* ((points (TwoPCalcPoint an-interactor 2 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
		   points))))
  
;;; points is the final value Calculated
(defun two-point-do-stop-helper (an-interactor points)
  (GoToStartState an-interactor T)
  (kr-send an-interactor :Stop-Action an-interactor points))

(defun two-point-do-stop (an-interactor obj event)
  #-garnet-debug (declare (ignore obj))
  (if-debug an-interactor (format T "Two-Point stop over ~s~%" obj))
  (multiple-value-bind (x y) (Get-Filtered-Input an-interactor event)
    (s-value an-interactor :x x)
    (s-value an-interactor :y y)
    (two-point-do-stop-helper an-interactor (TwoPCalcPoint an-interactor 2 x y))))

;;; This is used if explicitly call Stop-Interactor.  It uses the last point
(defun two-point-explicit-stop (an-interactor)
  (if-debug an-interactor (format T "Two point explicit stop~%"))
  (let ((x (g-value an-interactor :x))
	(y (g-value an-interactor :y)))
    (two-point-do-stop-helper an-interactor (TwoPCalcPoint an-interactor 2 x y))))

(defun two-point-do-abort (an-interactor become-inactive event)
  (declare (ignore event become-inactive))
  (if-debug an-interactor (format T "Two-Point aborting~%"))
  (GoToStartState an-interactor T)
  (kr-send an-interactor :Abort-Action an-interactor))

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

;;;============================================================
;;; Two-Point schema
;;;============================================================

(Create-Schema 'inter:Two-Point-Interactor
		     (:is-a inter:interactor)
		     (:name :First-Two-Point-interactor)
		     (:start-action 'Two-Point-Int-Start-Action)
		     (:running-action 'Two-Point-Int-Running-Action)
		     (:stop-action 'Two-Point-Int-Stop-Action)
		     (:abort-action 'Two-Point-Int-Abort-Action)
		     (:outside-action 'Two-Point-Int-Outside-Action)
		     (:back-inside-action 'Two-Point-Int-Back-Inside-Action)
		     (:line-p NIL) ; line T, or rectangle NIL
		     (:Min-width NIL); minimum allowed width and height
		     (:Min-height NIL)
		     (:Min-length NIL) ; minimum length for lines
		     (:input-filter NIL)
		     (:abort-if-too-small NIL)
		     (:flip-if-change-side T) ; for rectangles-change left,top
						; if move up left of it
		     (:saved-last-points NIL) ; used if stop and outside and
						; outside control is :last
		     (:Go 'General-Go)  ; proc executed when events happen
		     (:Do-Start 'Two-Point-Do-Start)     ; these are
		     (:Do-Running 'Two-Point-Do-Running) ;   called by GO
		     (:Do-Explicit-Stop 'Two-Point-Explicit-Stop) ;for stop-inter
		     (:Do-Stop 'Two-Point-Do-Stop)       ;   to do
		     (:Do-Abort 'Two-Point-Do-Abort)     ;   the real work.
		     (:Do-Outside 'Two-Point-Do-Outside) ;   They call the
		     (:Do-Back-Inside 'Two-Point-Do-Back-Inside)  ; appropriate
		     (:Do-Outside-Stop 'Two-Point-Do-Outside-Stop); -action
								     ; procedures
		     (:initialize 'Two-Point-Interactor-Initialize))
