;;; -*- Mode: LISP; Syntax: Common-Lisp; Package: LAPIDARY; 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. ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; -*- Mode: Lisp; Package: LAPIDARY -*-
;;;
;;; This file contains code that supports the multiple
;;; selection of objects via selection boxes and the
;;; selection of covered objects.
;;;
;;; select-multiple-objects: determines if an object intersects
;;;     a selection box
;;;
;;; select-one-object: determines if the user has selected a single 
;;;     object or swept out a selection rectangle, and selects
;;;     objects based on the outcome.
;;;
;;; push-selection-under
;;; push-under-recur:    select a covered object
;;;

;;; CHANGE LOG
;;;
;;; 08/10/93 bvz - modified the multiple object selection routines so that
;;;                  they could support either the selection of leaves or
;;;                  top-level objects.
;;; 08/24/92 amickish - Removed declare ignore of point-list in push-selection-
;;;                     under.

(in-package "LAPIDARY" :use '("LISP" "KR"))

;;;===================================================
;;;
;;; traverse the aggregate hierarchy and select those
;;; objects which intersect the selection box
;;;
;;; Note: sel-fct is the function that makes the 
;;;    selection
;;;
;;;===================================================

(opal::define-method :select-multiple-objects opal:aggregate (agg sel-fct)
  (declare (special *selection-bbox* *selection-info*))
  (let (agg-bbox)
    (when (g-value agg :visible)
      (setf agg-bbox (opal::update-info-old-bbox
		      (get-local-value agg :update-info)))
      (when (opal::bbox-intersect-p *selection-bbox* agg-bbox)
	(if (g-value *selection-info* :leaf)
	    (dovalues (child agg :components)
		  (kr-send child :select-multiple-objects child sel-fct))
	    (funcall sel-fct agg))))))

(opal::define-method :select-multiple-objects opal:graphical-object (obj sel-fct)
  (declare (special *selection-bbox* *selection-info*))
  (let (obj-bbox)
    (when (g-value obj :visible)
      (setf obj-bbox (opal::update-info-old-bbox
		      (get-local-value obj :update-info)))
      (when (opal::bbox-intersect-p *selection-bbox* obj-bbox)
	(funcall sel-fct obj)))))

(opal::define-method :select-multiple-objects 
		     garnet-gadgets:double-arrow-line (agg sel-fct)
  (declare (special *selection-bbox* *selection-info*))
  (let (agg-bbox)
    (when (g-value agg :visible)
      (setf agg-bbox (opal::update-info-old-bbox
		      (get-local-value agg :update-info)))
      (when (opal::bbox-intersect-p *selection-bbox* agg-bbox)
	(funcall sel-fct agg)))))

(opal::define-method :select-multiple-objects 
		     garnet-gadgets:arrow-line (agg sel-fct)
  (declare (special *selection-bbox* *selection-info*))
  (let (agg-bbox)
    (when (g-value agg :visible)
      (setf agg-bbox (opal::update-info-old-bbox
		      (get-local-value agg :update-info)))
      (when (opal::bbox-intersect-p *selection-bbox* agg-bbox)
	(funcall sel-fct agg)))))

;;;===================================================
;;;
;;; Determine if the user has selected a single object
;;; or swept out a selection rectangle, and select
;;; objects based on the outcome.
;;;
;;; Note: sel-fct determines which function makes the
;;;   selection and sel-inter determines which 
;;;   interactor to activate if only one selection
;;;   is being made.
;;;
;;;===================================================

(defun select-one-object (inter point-list)
  (declare (special *current-window* *selection-bbox*))

  ; single selection or selection box?
  (if (null point-list)
      
      ; single selection--find the object that the mouse is
      ; over and select it. if the mouse is over the background,
      ; deselect all objects
      (let ((selection-inter (g-value inter :selection-inter)))
	;; before activating the appropriate selection interactor, set its
	;; start-where to the aggregate of the current window
	(setf (second (g-value selection-inter :start-where))
	      (g-value inter :current-window :editor-agg))
	(inter:start-interactor selection-inter))
      
      ; else
         
      (let ((x (first point-list))
	    (y (second point-list))
	    (wd (third point-list))
	    (ht (fourth point-list)))
	
	; should currently selected objects be deselected?
	(let ((deselect-fct (get-local-value inter :deselect-fct)))
	  (when deselect-fct
	    (funcall deselect-fct :none)))
	
	; turn the selection box into a bounding box that
	; bbox-intersect-p can use
	
	(setf (opal::bbox-x1 *selection-bbox*) x)
	(setf (opal::bbox-y1 *selection-bbox*) y)
	(setf (opal::bbox-x2 *selection-bbox*) (1- (+ x wd)))
	(setf (opal::bbox-y2 *selection-bbox*) (1- (+ y ht)))
	
	; traverse the aggregate hierarchy to determine which 
	; objects should be selected
	
	(dovalues (child (g-value inter :current-window :editor-agg) :components)
		  (kr-send child :select-multiple-objects child 
					   (g-value inter :selection-fct)))))
  )

;;; Find the topmost selected item in a stack of items and deselect it.
;;; Then descend through the stack until an unselected item is found
;;; and select it. If the function reaches the bottom of the stack without
;;; finding an unselected item it does nothing. If none of the items
;;; in the stack are selected, the function selects the topmost item.

(defun push-selection-under (inter point-list)
  (declare (special *selectp*)) ; indicates whether to select or deselect
                                ; an obj
  (declare (special *top-obj*)) ; top obj in the stack
  (declare (special *select-fct* *deselect-fct*)) ; functions used to
                                                  ; select and deselect
                                                  ; objs 
  (declare (special *sel-slot*)) ; slot used to determine if obj currently
                                 ; selected
  
  (setf *selectp* nil)
  (setf *top-obj* nil)
  (setf *select-fct* (g-value inter :select-fct))
  (setf *deselect-fct* (g-value inter :deselect-fct))
  (setf *sel-slot* (g-value inter :sel-slot))
  
  (let ((x (first point-list))
	(y (second point-list)))
    (when (and (not (push-under-recur 
		     (g-value inter :current-window :editor-agg :components) 
		     x y))
	       (not *selectp*)
	       *top-obj*)
      
      ; if the bottom of the stack was reached without finding a selection,
      ; select the top element of the stack
      (funcall *select-fct* *top-obj*)))
  )
	    
(defun push-under-recur (component-list x y)
  (declare (special *selectp* *top-obj*
		    *select-fct* *deselect-fct* *sel-slot*))
  (and component-list
       (or (push-under-recur (cdr component-list) x y)
	   (let ((child (car component-list)))
	     (cond ((is-a-p child opal:aggregate)
		    (when (opal::point-in-gob-method-view-object child x y)
		      (push-under-recur
		       (g-value child :components) x y)))
		   (t
		     (when (opal::point-in-gob child x y) 
		       (let ((selected (g-value child *sel-slot*)))
			 
			 ; if this obj is the topmost selected obj, 
			 ; deselect it
			 (cond ((and (not *selectp*) selected)
				(funcall *deselect-fct* child)
				(setf *selectp* t)
				nil)

			 ; if a topmost selected obj has been found and
			 ; this obj is the first unselected obj found
			 ; since then, select it
			       ((and *selectp* (not selected))
				(funcall *select-fct* child)
				t)

			 ; if this obj is the topmost obj in the stack
			 ; and it is unselected, remember it
			       ((and (not selected) (not *top-obj*))
				(setf *top-obj* child)
				nil)))))))))
  )
