;;; -*- Mode: LISP; Syntax: Common-Lisp; Package: GARNET-GADGETS; 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: GARNET-GADGETS -*-
;;;
;;; This file contains code for handling the percent and offset
;;; buttons in the width and height constraint menus
;;;
;;; l-constraint-offset-action
;;; t-constraint-offset-action: allows the designer to input an absolute
;;;                             amount that should separate the left or
;;;                             tops of two objects
;;;
;;; l-constraint-percent-action
;;; t-constraint-percent-action: allows the designer to input a percentage
;;;                              amount that controls how one object is
;;;                              centered beneath another
;;;
;;; 
;;; offset-action
;;; w-constraint-offset-action
;;; h-constraint-offset-action: allows the designer to input an absolute
;;;                             amount by which the widths or heights of
;;;                             two objects should differ
;;;
;;; percent-action
;;; w-constraint-percent-action
;;; h-constraint-percent-action: allows the designer to input a percentage
;;;                              amount by which the widths or heights of
;;;                              two objects should differ
;;;
;;; apply-box-constraint-p: indicates if it is valid to apply a constraint
;;;
;;; deselect-constraint-button: deselects a constraint button
;;;
;;; Created by Brad Vander Zanden
;;;

#|
============================================================
Change log:
7/14/93  Andrew Mickish - Declared *Box-Constraint-Menu* special in
           L/T-Constraint-Offset-Action functions;  Ignored slot parameter
           in Offset-Action and Scale-Action
6/24/89  Brad Vander Zanden - created
============================================================
|#

(in-package "GARNET-GADGETS" :use '("LISP" "KR"))


;;;============================================================
;;;
;;; determine whether the offset or scale of a constraint
;;; should be changed
;;;============================================================

(defun set-offset-p (slot)
  (declare (special *constraint-gadget*))
  (let ((p-selection (g-value *constraint-gadget* :obj-to-constrain))
	(s-selection (g-value *constraint-gadget* :obj-to-reference)))
    (or (and p-selection (null s-selection))
	(and p-selection s-selection
	     (depends-on-p p-selection s-selection slot)))))

;;;============================================================
;;;
;;; the next two functions allow the designer to input an absolute
;;; amount that should separate the left or tops of two objects.
;;; it insures that the designer inputs a number, and if
;;; there is a constraint between the selected objects,
;;; it incorporates this amount into the constraint.
;;;
;;;============================================================

(defun l-constraint-offset-action (interactor offset)
  (declare (special *box-constraint-menu*))
  (let* ((selected-constraint (g-value *box-constraint-menu* :box-agg 
				   :left-button-panel :buttons
				   :selected)))
    (offset-action interactor offset :left-offset :left 
		   (set-offset-p :left)
		 (or  (not selected-constraint)
		      (not (eq (g-value selected-constraint :known-as) 
			       :center))))))

(defun t-constraint-offset-action (interactor offset)
  (declare (special *box-constraint-menu*))
  (let* ((selected-constraint (g-value *box-constraint-menu* :box-agg 
				   :top-button-panel :buttons
				   :selected)))
  (offset-action interactor offset :top-offset :top 
		 (set-offset-p :top)
		 (or  (not selected-constraint)
		      (not (eq (g-value selected-constraint :known-as) 
			  :center))))))


;;;============================================================
;;;
;;; the next three functions allow the designer to input an absolute
;;; amount by which the width or height of two objects should differ.
;;; it insures that the designer inputs a number, and if
;;; there is a constraint between the selected objects,
;;; it incorporates this amount into the constraint.
;;;
;;;============================================================

(defun w-constraint-difference-action (interactor offset)
  (offset-action interactor offset :width-difference :width
		 (set-offset-p :width)))

(defun h-constraint-difference-action (interactor offset)
  (offset-action interactor offset :height-difference :height
		 (set-offset-p :height)))

;;;============================================================
;;; set-constraint-offset-p indicates whether the offset should
;;; be placed in the appropriate offset slot of the primary
;;; selection (if there is one). offset-p indicates whether
;;; the offset string should be treated as an offset or a
;;; percent
;;;============================================================

(defun offset-action (gadget offset-string offset-slot slot 
			     set-constraint-offset-p &optional (offset-p t))
  (declare (special *box-constraint-menu*)
	   (ignore slot))

  ;; insure that the input is an integer
  (when (not (valid-integer-p gadget offset-string))
	(return-from offset-action))

  (let ((offset (read-from-string offset-string)))

    ;; record the new offset in the appropriate place

    (if offset-p
	(setf offset (round offset))
	(setf offset (/ offset 100)))

    (s-value *box-constraint-menu* offset-slot offset)
    
    ;; if the offset should be placed in the appropriate offset slot
    ;; of the primary selection, do so

    (when set-constraint-offset-p
      (let ((p-selected (g-value *constraint-gadget* :obj-to-constrain)))
	(s-value p-selected offset-slot offset)))))

;;;============================================================
;;;
;;; the next three functions allow the designer to input a percentage
;;; amount by which the width or height of two objects should differ.
;;; it insures that the designer inputs a number, and if
;;; there is a constraint between the selected objects,
;;; it incorporates this amount into the constraint.
;;;
;;;============================================================

(defun w-constraint-scale-action (interactor scale)
  (declare (special *box-constraint-menu*))
  (scale-action interactor scale :width-scale :width
		(set-offset-p :width)))

(defun h-constraint-scale-action (interactor scale)
  (declare (special *box-constraint-menu*))
  (scale-action interactor scale :height-scale :height 
		(set-offset-p :height)))

(defun valid-number-p (gadget value)
  (let ((number (when (not (string= value "")) (read-from-string value))))
    (if (not (numberp number))
	(progn
	  (s-value gadget :value (g-value gadget :old-value))
	  (constraint-gadget-error "the value must be a number")
	  nil)
	(s-value gadget :old-value value))))

;;;============================================================
;;; set-constraint-scale-p indicates whether the scale should
;;; be placed in the appropriate scale slot of the primary
;;; selection (if there is one)
;;;============================================================

(defun scale-action (gadget scale-string scale-slot slot 
			    set-constraint-scale-p)
  (declare (special *box-constraint-menu*)
	   (ignore slot))

  ;; insure that the input is an integer
  (when (not (valid-number-p gadget scale-string))
	(return-from scale-action))

  (let ((scale (read-from-string scale-string)))

    ;; save the scale factor in the appropriate menu
    (s-value *box-constraint-menu* scale-slot scale)

    ;; if the scale should be placed in the appropriate scale slot
    ;; of the primary selection, do so

    (when set-constraint-scale-p
      (let ((p-selected (g-value *constraint-gadget* :obj-to-constrain)))
	(s-value p-selected scale-slot scale)))))

;;;============================================================
;;; the following five functions set the value of the 
;;; appropriate slot in the primary selection
;;;============================================================

(defun set-bbox-slot (gadget string slot)
  (declare (special *box-constraint-menu*))

  ;; insure that the input is an integer
  (when (not (valid-integer-p gadget string))
	(return-from set-bbox-slot))

  (let ((value (read-from-string string)))

    (when (and (g-value *constraint-gadget* :obj-to-constrain)
	       (null (g-value *constraint-gadget* :obj-to-reference)))
      (let ((obj (g-value *constraint-gadget* :obj-to-constrain)))

	;; certain slots should not be altered. if this slot is
	;; one of them, tell the user and do not proceed
	(when (member slot (g-value obj :do-not-alter-slots))
	      (constraint-gadget-error
	       (format nil "cannot change ~S's ~S slot" obj slot))
	      (return-from set-bbox-slot))

	(cg-destroy-constraint obj slot)
	(s-value obj slot value)))))

(defun set-left (inter string)
  (set-bbox-slot inter string :left))

(defun set-top (inter string)
  (set-bbox-slot inter string :top))

(defun set-width (inter string)
  (set-bbox-slot inter string :width))

(defun set-height (inter string)
  (set-bbox-slot inter string :height))

;; deselect a constraint button by setting the :selected slot of the
;; button in obj's :selected slot to nil

(defun deselect-constraint-button (obj)
  (let ((selected-obj (g-value obj :selected)))
    (when selected-obj
	  (when (schema-p selected-obj)
		(s-value selected-obj :selected nil))
	  (s-value obj :selected nil))))

