;;; -*- Mode:LISP; Package:USER; Readtable:CL; Base:10 -*-

;;;TOGGLE-WINDOW.LISP

;;;This example illustrates one of the easiest (but reliable) ways to get
;;;a mouse-click from the user.  It is reliable because you know the
;;;mouse-click is for you, not some other window.
;;;
;;;MAKE-TOGGLE-WINDOW creates a pop-window with a mouse-sensitive
;;;message.  This window can be reused.
;;;
;;;TOGGLE-WINDOW exposes the (previously made) pop-up window with a label
;;;and the mouse-sensitive message. If the user clicks left on the
;;;message, we return T; if the user presses <End>, or clicks right and
;;;selects an alternative right-key option, we return NIL.  This way the
;;;user can tell a calling routine to abort, rather than do something it
;;;was waiting to do.

(defflavor toggle-window
	 (
	  ;;Default refresh function has no items.
	  ;;Initialize this to one that creates mouse items.
	  (refresh-function #'(lambda()(send self :clear-window)))
	  (alias-window nil)			;Our :alias-for-selected-windows, if non-NIL
	  )
	 (tv:basic-mouse-sensitive-items
	  tv:borders-mixin
	  tv:top-box-label-mixin
	  tv:window)
  (:inittable-instance-variables)
  (:settable-instance-variables alias-window)
  (:default-init-plist
    :blinker-p nil				;no cursor needed
    :save-bits t				;like any good pop-up
    :borders 2					;dark outline
    ))

;;Copied from BASIC-NVT (SUPDUP) flavor:

(DEFMETHOD (toggle-window :ALIAS-FOR-SELECTED-WINDOWS) ()
  (OR ALIAS-WINDOW
      (AND TV:SUPERIOR (SEND TV:SUPERIOR ':ALIAS-FOR-INFERIORS))
      SELF))

(defmethod (toggle-window :after :refresh) (&rest ignore)
  "Calls the window's own refresh-function, which should
be a function that initializes the window's items."
  (funcall refresh-function))

(compile-flavor-methods toggle-window)

(defun make-toggle-window(&optional
			  (left-msg "Ready")
			  (right-msg "Abort")
			  (label "Click when ready"))
  "  Create a window that waits for the user to click on a message.
  LEFT-MSG is the string to display that the user clicks on, corresponding
to a left-click.
  If RIGHT-MSG is NIL, the user can only click left, and the menu will
return T.  If RIGHT-MSG is a non-NIL string, the user can also click
right, and the menu will return NIL."
  ;;
  ;;This error-checking is over-kill, not really needed unless
  ;;this is a "published" routine
  ;;
  (assert (typep left-msg 'string)
	  (left-msg)
	  "LEFT-MSG is not a string: ~s" left-msg)
  (assert (typep right-msg '(or null string))
	  (right-msg)
	  "RIGHT-MSG is neither NIL nor a string: ~s" right-msg)
  (assert (typep label 'string)
	  (label)
	  "LABEL is not a string: ~s" label)
  ;;
  ;;Create item-list, then window
  ;;
  (let(itemlist)
    ;;Set up function for left click
    (tv:add-typeout-item-type
      itemlist
      :toggle
      left-msg
      true					;item will return value (TRUE)  T
      t						;this will be the default item
      (format nil "L: ~a" left-msg)		;who-line documentation
      )
    ;;Set up function for right click
    (when right-msg
      (tv:add-typeout-item-type
	itemlist
	:toggle
	right-msg
	false					;always returns (FALSE)  NIL
	nil					;not default item, only on menu
	(format nil "R: ~a" right-msg)		;who-line documentation
	))
    ;;Return the window
    (make-instance 'toggle-window
		   :character-width (1+ (max
					  (+ 2 (string-length left-msg))
					  (+ 2 (string-length right-msg))
					  (string-length label)))
		   :character-height (if right-msg 3 2)
		   :item-type-alist itemlist
		   ;;
		   ;;This is where the item gets created.  When the window
		   ;;gets exposed, this gets called:
		   :refresh-function #'(lambda()
					 (send self :clear-window)
					 (send self :item :toggle left-msg))
		   :label label)))

(defun toggle-window(mywindow &optional (near-mode :mouse) (x 0) (y 0))
  "Expose MYWINDOW, a TOGGLE-WINDOW created by MAKE-TOGGLE-WINDOW, 
according to NEAR-MODE.
  Returns T if the user selected the default mouse item, NIL otherwise."
  (tv:expose-window-near
    mywindow
    ;;This is just for purposes of demonstration, various ways to
    ;;specify where to expose the window
    (cond
      ((eq near-mode :mouse)  '(:mouse))
      ((eq near-mode :window) `(:window ,terminal-io))
      ((eq near-mode :point)  `(:point ,x ,y))
      (t near-mode)))
  (do((input (send mywindow :any-tyi)
	     (send mywindow :any-tyi)))
     (nil)
    (cond
      ((and (listp input) (eq (car input) :typeout-execute))
       (return(funcall(second input))))
      ((char-equal input #\end)
       (return nil))
      ((char-equal input #\page)
       (send mywindow :refresh))
      (t (beep)))))

(defmacro with-toggle-window((var under-window) &body body)
  `(let((under-window ,under-window)
	,var
	window)
     (check-type under-window tv:window "a window")
     (setq window (make-toggle-window))
     (unwind-protect
	(progn
	  (send under-window :set-selection-substitute window)
	  (send window :set-alias-window under-window)
	  (setq ,var
		(multiple-value-bind(x y)
		    (send terminal-io :edges)
		  (toggle-window window `(:point ,(+ x 10) ,(+ y 10)))))
	  ,@body)
       (progn
	 (send under-window :set-selection-substitute nil)
	 (send window :deexpose)))))

(defun test-toggle(&aux result) 
  (with-toggle-window(answer terminal-io)
    ;;You don't want to do much of anything in the body of this macro,
    ;;just set a variable to the answer you get from the toggle window.
    ;;For demonstration purposes, I'll also flash the window:
    (setq result answer)
    (let((beep :flash)) (beep)))
  ;;
  ;;Very important: HERE is where you put the code that uses
  ;;the result you got. 
  (format t "~:[The user aborted~;The user is ready~]." result))

