;;; -*- mode:lisp; package: tv; base:10.; -*- ;;;

;;; TRACKING

;;;  This file contains code to implement TRACKING windows.
;;;
;;;  These are windows that move when the mouse moves out-of-bounds.
;;; Similar to following mixin, but nicer-looking.



;;;  ****************************************************************
;;;  Necessary Loads, Constants, Declarations.
;;;  ****************************************************************





;;;  ****************************************************************
;;;  Internal Macros, Structure Definitions, FLAVOR definitions.
;;;  ****************************************************************

(defflavor tracking-mixin
	((time-out 30)				;half a second
	 (dont-bother nil)
	 (tracking-blinker nil))
	(hysteretic-window-mixin)
  :gettable-instance-variables
  :initable-instance-variables
  :settable-instance-variables
  (:required-flavors window))


;;;  When making such a window, initialize the tracking blinker so we don't
;;; have to make new ones each time.
(defmethod (tracking-mixin :after :init)
	   (&rest ignore)
  (setq hysteresis 30.)
  (setq tracking-blinker (tv:make-blinker tv:main-screen 'box-blinker
					  ':x-pos x-offset ':y-pos y-offset
					  ':width width ':height height
					  ':visibility nil)))



;;;  ****************************************************************
;;;  Internal Code
;;;  ****************************************************************

(defmethod (tracking-mixin :before :select)
	   (&optional ignore)
  (setq dont-bother nil))

(defmethod (tracking-mixin :before :bury)
	   (&rest ignore)
  (setq dont-bother t))
(defmethod (tracking-mixin :before :deactivate)
	   (&rest ignore)
  (setq dont-bother t))
(defmethod (tracking-mixin :before :kill)
	   (&rest ignore)
  (setq dont-bother t))
(defmethod (tracking-mixin :before :deexpose)
	   (&rest ignore)
  (setq dont-bother t))
(defmethod (tracking-mixin :before :deselect)
	   (&rest ignore)
  (setq dont-bother t))

;;; This runs in the mouse process.
(defmethod (tracking-mixin :after :handle-mouse)
	   ()
  ;; Largely stolen from tv:basic-momentary-menu
  (or window-owning-mouse
      dont-bother
      (not exposed-p)
      (and mouse-reconsider (eq self (window-owning-mouse)))
      (process-run-function '(:name "Moving around" :priority 20.)
			     self ':track-mouse))
  )


;;;  Here is where we do the real work.  What basically happens is that once
;;; the mouse goes outside of the window, we want to let the user move the window.
;;; We pop-up a blinker the size of the window, and let it follow the mouse.  As
;;; soon as the mouse "settles down", we move the window.
(defmethod (tracking-mixin :track-mouse)
	   (&aux (OLD-MOUSE-SHEET MOUSE-SHEET) window-edge-alist)
  (UNWIND-PROTECT
    (*CATCH 'EXIT-SCREEN-EDITOR
      (LET-GLOBALLY ((WHO-LINE-PROCESS CURRENT-PROCESS))
	(MOUSE-SET-SHEET MAIN-SCREEN)
	(let ((NEW-ALIST 'FIRST))
	  (DELAYING-SCREEN-MANAGEMENT
	    ;; Now, just before executing the command, pick up the state of the screen
	    ;; We defer it until now so that we see the results of screen management
	    ;; and of things done to the screen by other processes.
	    ;; Also save the state before the previous command for Undo
	    (SETQ WINDOW-EDGE-ALIST (GET-WINDOW-EDGE-ALIST MAIN-SCREEN))
	    (setq new-alist (let* ((window (loop for w in window-edge-alist
						 if (eq (car w) self)
						 return w)))
			      (MULTIPLE-VALUE-BIND (X Y)
				  (MOUSE-SET-WINDOW-POSITION (CAR WINDOW) t)
				(IF X (SETQ WINDOW-EDGE-ALIST
					    (SUBSTQ (LIST (CAR WINDOW) (CADR WINDOW) X Y
							  (+ X (SHEET-WIDTH (CAR WINDOW)))
							  (+ Y (SHEET-HEIGHT (CAR WINDOW))))
						    WINDOW WINDOW-EDGE-ALIST))
				    'ABORT))))
	    
	    (COND ((NEQ NEW-ALIST 'ABORT)	;Don't change history if command aborted
		   (DOLIST (NEW NEW-ALIST)
		     (LET ((OLD (ASSQ (CAR NEW) WINDOW-EDGE-ALIST)))
		       (OR (EQUAL (CDDR OLD) (CDDR NEW))	;Edges not the same?
			   (MULTIPLE-VALUE-BIND (WIN LOSE)
			       (FUNCALL (FIRST NEW) ':SET-EDGES (THIRD NEW) (FOURTH NEW)
					(FIFTH NEW) (SIXTH NEW) ':VERIFY)
			     (IF WIN (LEXPR-FUNCALL (CAR NEW) ':SET-EDGES (CDDR NEW))
				 (BEEP)
				 (POP-UP-FORMAT "Illegal edges for ~S:~%~A"
						(CAR NEW) LOSE))))
		       ;; Try to fix exposure and ordering of de-exposed sheets.
		       ;; This may not be quite right, e.g. if undoing an expose
		       ;; because the window will go in the wrong place in the
		       ;; de-exposed sheets, and Undo twice will not be a no-op.
		       ;; It will just have to do for now though.
		       (COND ((EQ (CADR NEW) T)
			      (OR (CADR OLD) (FUNCALL (CAR NEW) ':EXPOSE)))
			     ((EQ (CADR NEW) ':BURY)
			      (FUNCALL (CAR NEW) ':BURY)))))
		   ;; Doing the buries in a second pass makes the
		   ;; above-mentioned inaccuracy less
		   (DOLIST (NEW NEW-ALIST)
		     (AND (NOT (CADR NEW)) (SHEET-EXPOSED-P (CAR NEW))
			  (FUNCALL (CAR NEW) ':BURY))))))
	  )))
  (MOUSE-SET-SHEET OLD-MOUSE-SHEET)))
