;;; -*- Mode: LISP; Syntax: Common-Lisp; Package: GILT; 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. ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; The "Value Filter" extension to Gilt is a tool for assigning dependencies
;;; among objects.  With the Value Control module, the values returned by
;;; gadgets can be defined by a user-supplied expression.  The Enable Control
;;; module allows the user to define expressions that regulate whether gadgets
;;; are enabled and may be operated.  The Error Checking module allows the
;;; definition of an error handling routine, complete with a customized
;;; error dialog box.
;;;
;;; Designed by Brad Myers
;;; Implemented by Andrew Mickish

;;
;;  Enable Control module
;;

;;; CHANGE LOG:
;;; 03/08/93 Brad Myers - invoke from menubar
;;; 02/02/93 Andrew Mickish - opal:set-strings ---> opal:set-text


(in-package "GILT" :use '("LISP" "KR" "KR-DEBUG"))

(proclaim '(special ENABLE-CONTROL))


(defun Set-Enable-Slot (expr en-con)
  (let* ((obj (g-value en-con :for-object))
	 (constant (g-value obj :constant)))
    (s-value obj :active-p (formula expr))
    (if (or (equal '(t) constant) (equal t constant))
	(s-value obj :constant '(t :except :active-p)))))

(defun Enable-Control-OK-Fn (en-con values)
  (let ((expr (g-value en-con :filter-expr)))
    (if expr
	(Set-Enable-Slot expr en-con)))
  (if (string= "OK" (gilt:value-of :ok-apply-cancel values))
      (Close-ENCON-Win en-con)))

(defun Enable-Control-Cancel-Fn (en-con values)
  (declare (ignore values))
  (Close-ENCON-Win en-con))
  
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;  Objects
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defvar *ENCON-Foreground-Color* (create-instance NIL opal:color
				   (:red .8) (:green .8) (:blue 1)))
(defvar *ENCON-Foreground-Fill* (create-instance NIL opal:default-filling-style
				 (:foreground-color *ENCON-Foreground-Color*)))

(defvar *ENCON-Highlight-Color* (create-instance NIL opal:color
				  (:red .75) (:green .8) (:blue 1)))
(defvar *ENCON-Highlight-Fill* (create-instance NIL opal:default-filling-style
				 (:foreground-color *ENCON-Highlight-Color*)))



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Top-level function to display an enable-control panel for an object
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


(defparameter *encon-position-list* NIL)
(defparameter *Used-ENCON-Wins* NIL)

;; These position functions are used to position the next enable control
;; menu appropriately with respect to the menus that are already on the
;; screen.
;;
(defun Next-ENCON-Position-Index ()
  (if *encon-position-list* (1+ (reduce 'max *encon-position-list*)) 0))
(defun Next-ENCON-Left (i)
  (+ (g-value ENABLE-CONTROL :window-left) (* 30 i)))
(defun Next-ENCON-Top (i)
  (+ (g-value ENABLE-CONTROL :window-top) (* 30 i)))

(defun Close-ENCON-Win (encon)
  (inter:abort-interactor (g-value encon :filter-text-obj :parent :edit-it))
  (setf *encon-position-list* (remove (g-value encon :position-index)
				      *encon-position-list*))
  (push (g-value encon :window) *Used-ENCON-Wins*))

(defun Destroy-Encon-Wins ()
  (dolist (ec (g-value ENABLE-CONTROL :is-a-inv))
    (opal:destroy (g-value ec :window)))
  (setf *Used-ENCON-Wins* NIL)
  (setf *encon-position-list* NIL))

(defun Set-ENCON-Win-Position (encon-win)
  (let ((encon (car (g-value encon-win :aggregate :components)))
	(i (next-encon-position-index)))
    (push i *encon-position-list*)
    (s-value encon-win :left (next-vc-left i))
    (s-value encon-win :top (next-vc-top i))
    (s-value encon :position-index i)))
  
(defun Pop-Used-ENCON-Win (for-obj title)
  (let ((encon-win (pop *Used-ENCON-Wins*)))
    (when encon-win
      (Set-ENCON-Win-Position encon-win)
      (s-value encon-win :title title)
      (s-value encon-win :visible T)
      (let ((encon (car (g-value encon-win :aggregate :components))))
	(s-value encon :for-object for-obj)
	(opal:update encon-win)
	encon))))

(defun Create-ENCON-Win (for-obj title)
  (let* ((i (car (push (next-encon-position-index) *encon-position-list*)))
	 (en-con (create-instance NIL Enable-Control
		    (:position-index i)
		    (:for-object for-obj)
		    (:window-title title)))
	 (en-con-win (show-in-window en-con (next-encon-left i)
				     (next-encon-top i)))
	 (filter-win (Generate-Enable-Expr-Win en-con en-con-win))
	 ;; In this editable-text object, there are two strings.  One is in
	 ;; the :string slot, which is displayed and edited by the user.
	 ;; The other is in the :value slot, which is set when the user has
	 ;; finished editing the string in the :string slot.  This allows
	 ;; dependencies to be set up on the :value, which is always a
	 ;; complete expression, versus the dynamic one in :string.
	 (filter-str-agg (create-instance NIL editable-text
			   (:enable-control en-con)
			   (:scrolling-window filter-win)
			   (:final-function
			    #'(lambda (i o e string x y)
				(declare (ignore i e x y))
				(s-value o :user-edited-p T)
				(s-value o :value
					 (opal:text-to-string string))))))
	 (filter-text-obj (g-value filter-str-agg :string)))
    (opal:update filter-win)
    (opal:add-component (g-value filter-win :inner-aggregate) filter-str-agg)
    (s-value en-con :filter-text-obj filter-text-obj)
    (s-value en-con :scrolling-window filter-win)
    en-con))
	

(defun Show-Enable-Control (for-obj)

  ;; Create the enable control menu
  (let* ((title (concatenate 'string
			     "Enable Control:  " (name-for-schema for-obj)))
	 (en-con (or (Pop-Used-ENCON-Win for-obj title)
		     (Create-ENCON-Win for-obj title)))
	 (filter-text-obj (g-value en-con :filter-text-obj)))
    (gg:scroll-win-to (g-value en-con :scrolling-window) 0 0)
    ;; Set the initial filter function string
    (let ((e-val (get-value for-obj :active-p)) ;; e-val = enabled value
	  e-string)
      (if e-val
	  (let ((expr (if (formula-p e-val)
			  (kr::a-formula-lambda e-val)
			  e-val)))
	    (setq e-string (if (stringp expr) expr
			       (write-to-string expr :pretty T
						:case :downcase))))
	  (setq e-string ""))

      (opal:set-text filter-text-obj e-string)
      (s-value filter-text-obj :value e-string))
    en-con))


(defun Enable-Control-Func (&rest args)
  (declare (ignore args))
  (let* ((objs (g-value *Selection-Obj* :value))
	 (obj (car objs))
	 (found-ec NIL))
    (cond ((null objs) (Gilt-Error "Nothing selected"))
	  ((cdr objs) (Gilt-Error "Only one object can be selected"))
	  ((progn
	     (dolist (ec (g-value ENABLE-CONTROL :is-a-inv))
	       (if (and (eq obj (g-value ec :for-object))
			(g-value ec :window)
			(g-value ec :window :visible))
		   (setf found-ec ec)))
	     found-ec)
	   (Gilt-Error (format NIL "An enable control panel is already
being used for ~S" obj)))
          (T (Show-Enable-Control obj)))))



;;
;;  Motif Enable Control objects
;;

(in-package "GILT" :use '("LISP" "KR" "KR-DEBUG"))

(create-instance 'ENABLE-CONTROL opal:aggregadget
  (:window-left 300) (:window-top 400)
  (:window-width 495) (:window-height 225)
  (:window-title "Enable Control")
  (:window-background-color *ENCON-Foreground-Color*)
  (:width (o-formula (gvl :window :width) 495))
  (:height (o-formula (gvl :window :height) 225))
  (:for-object NIL)
  (:slot-to-reference :selected)
  (:filter-expr (o-formula (let* ((string (gvl :filter-text-obj :value))
				 expression error-p)
			    (multiple-value-setq (expression error-p)
			      (gg:careful-read-from-string string
							   *error-gadget*))
			    (unless error-p expression))))
  (:function-for-ok #'Enable-Control-OK-Fn)
  (:function-for-cancel #'Enable-Control-Cancel-Fn)
  (:parts `(
    (:title ,OPAL:TEXT
      (:GILT-REF "TYPE-TEXT")
      (:FONT ,(opal:get-standard-font NIL :bold-italic :large))
      (:BOX (4 4 361 20 ))
      (:STRING ,(o-formula (concatenate 'simple-string
			    "Change my Enable for \""
			    (Get-User-Name (gvl :parent :for-object))
			    "\"")))
      (:LEFT ,(o-formula (FIRST (GVL :BOX)) 4))
      (:TOP ,(o-formula (SECOND (GVL :BOX)) 4)))
    (:ok-apply-cancel ,GARNET-GADGETS:MOTIF-TEXT-BUTTON-PANEL
     (:GILT-REF "TYPE-OKCANCEL")
     (:DIRECTION :HORIZONTAL)
     (:foreground-color ,(o-formula (gvl :parent :window-background-color)))
     (:ITEMS ("OK" "Apply" "Cancel" ))
     (:LEFT 300) (:TOP 35)
     (:SELECTION-FUNCTION GILT:OKCANCEL-FUNCTION)
     (:interactors
      (:press (:key :omit))))
    (:expression-label ,OPAL:TEXT
      (:LEFT 12) (:TOP 78)
      (:STRING "Expression:")
      (:FONT ,(opal:get-standard-font NIL :bold NIL)))
    (:insert-ref ,GARNET-GADGETS:MOTIF-TEXT-BUTTON
     (:selection-function Insert-Ref-Obj-Into-Str)
     (:string "Use Value of Object")
     (:foreground-color ,(o-formula (gvl :parent :window-background-color)))
     (:GILT-REF "TYPE-MOTIF-TEXT-BUTTON")
     (:LEFT ,(o-formula (- (opal:gv-right (gvl :parent :ok-apply-cancel))
			   (gvl :width))))
     (:TOP ,(o-formula (+ 5 (opal:gv-bottom
			     (gvl :parent :ok-apply-cancel)))))
     (:interactors
      (:press (:key :omit))))
    (:result ,gg:motif-scrolling-labeled-box
     (:left 12) (:top 195) (:width 460)
     (:label-string "Resulting Enable Value:")
     (:foreground-color ,(o-formula (gvl :parent :window-background-color)))
     (:value ,(o-formula
	       (let ((for-obj (gvl :parent :For-Object))
		     (expr (gvl :parent :filter-expr)))
		 (if (and for-obj (schema-p for-obj))
		     (prin1-to-string (gg:Careful-Eval-Formula-Lambda
				       expr *error-gadget* NIL for-obj
				       :value kr::*current-formula* NIL))
		     ;; else if no object, use ""
		     ""))))
     (:parts (:label-text :frame
	      (:field-text :modify
			   (:interactors
			    ((:text-edit :omit)))))))
	    )))

(defun Generate-Enable-Expr-Win (en-con en-con-win)
  (create-instance NIL gg:motif-scrolling-window-with-bars
    (:parent-window en-con-win)
    (:enable-control en-con)
    (:left 12) (:top 94) (:width 460) (:height 90)
    (:total-width 600)
    (:total-height (o-formula (+ 15 (gvl :enable-control :filter-text-obj
					 :height)) 29))
    (:foreground-fill *ENCON-Highlight-Fill*)
    (:foreground-color *ENCON-Highlight-Color*)
    (:parts
     `((:v-scroll :modify
	(:interactors
	 (:slide :jump (:key :omit))))
       (:h-scroll :modify
	(:interactors
	 (:slide :jump (:key :omit))))))))