;;; -*- Mode:LISP; Package: COMPOSER; Readtable:CL; Base:10 -*-
;;;
;;; Window system user interface for composer system
;;;
;;;

(defflavor composer-frame (music-pane)
	   (tv:process-mixin
	    tv:select-mixin
	    tv:inferiors-not-in-select-menu-mixin
	    tv:alias-for-inferiors-mixin
	    tv:essential-mouse
	    tv:bordered-constraint-frame-with-shared-io-buffer
	    tv:top-box-label-mixin)
  (:default-init-plist
    :save-bits :delayed
    :io-buffer (tv:make-io-buffer #o512 nil 'tv:kbd-default-output-function)
    :process '(composer-frame-process :regular-pdl-size 16000 :special-pdl-size 2000)
    :borders 1
    :label '(:string "Music Composer" :font fonts:metsi :centered)
    :configuration 'basic
    :panes `((music-pane music-pane)
	     (interaction-pane interaction-pane))
    :constraints
    `((basic
	(music-pane interaction-pane)
	((interaction-pane 30 :lines)
	 (music-pane :even)))))
  :gettable-instance-variables
  :settable-instance-variables)

(defmethod (composer-frame :after :init) (&rest ignore)
  (send self :set-selection-substitute
	(send self :get-pane 'interaction-pane))
  (setq music-pane
	(send self :get-pane 'music-pane)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Lisp listener pane
;;;
;;;

(defflavor interaction-pane ()
	   (tv:notification-mixin
	    tv:list-mouse-buttons-mixin
	    tv:window))

(defmethod (interaction-pane :package) ()
  (pkg-find-package 'composer))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Music graphics pane
;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defflavor music-pane ((blinker-array (make-array 256 :type 'art-q))
		       (blinker-char 0)
		       (script-pointer))
	   (tv:graphics-mixin tv:window)
  (:default-init-plist
    :blinker-p nil)
  :gettable-instance-variables
  :settable-instance-variables)

(defmethod (music-pane :after :init) (&rest ignore)
  (setup-mouse-note-blinkers-for-window blinker-array self))

(defmethod (music-pane :mouse-standard-blinker) ()
  (tv:mouse-set-blinker (aref blinker-array blinker-char)))

(defmethod (music-pane :set-mouse-blinker) (number)
  (check-type number (integer 0 255))
  (setq blinker-char number)
  (send self :mouse-standard-blinker))

(tv:add-system-key #\Z 'composer-frame "Music Composition Frame")


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Mouse note blinkers
;;;
;;;

(defflavor mouse-note-blinker ()
	   (tv:mouse-character-blinker))

(defun setup-mouse-note-blinkers-for-window (array window)
  (dotimes (c 256)
    (setf (aref array c)
	  (make-instance 'tv:mouse-character-blinker
			 :char c
			 :font fonts:music-font
			 :sheet window))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Process stuff
;;;
;;;

(defvar *composer-closure* :unbound)

(defvar *composer-closure-variables* nil)

(defvar *whole-frame* :unbound)

(defvar *default-menu-font* fonts:medfnb
  "This is the default font for the command menu")

(defmacro define-closed-variable (symbol &optional reset-form documentation)
  `(progn 'compile
	  (defvar ,symbol :unbound ,documentation)
	  (putprop ',symbol ',reset-form :reset-form)
	  (setq *composer-closure-variables*
		(nunion *composer-closure-variables* (ncons ',symbol)))))

(defun reset-closure-variables (closure)
  (dolist (c (closure-variables closure))
    (set-in-closure closure c (si:eval-special-ok (get c :reset-form)))))

(defun make-composer-closure (bindings &optional (init-p t))
  (let ((closure (closure (copylist bindings) #'funcall)))
    (when init-p
      (reset-closure-variables closure))
    closure))

(define-closed-variable *interaction* (send *whole-frame* :get-pane 'interaction-pane))
(define-closed-variable *music* (send *whole-frame* :get-pane 'music-pane))

(defun composer-frame-process (window)
  (let ((*package* (pkg-find-package 'tape)))
    (error-restart-loop (restart "Back to Toplevel COMPOSER loop")
      (let* ((*whole-frame* window)
	     (*composer-closure* (make-composer-closure *composer-closure-variables* nil)))
	(reset-closure-variables *composer-closure*)
	(funcall *composer-closure* #'composer-toplevel)))))

(defun composer-toplevel ()
  (let ((terminal-io (send *whole-frame* :get-pane 'interaction-pane)))
    (error-restart-loop (sys:abort "Back to Toplevel COMPOSER loop")
      (composer-toplevel-1-loop))))

(defun composer-toplevel-1-loop ()		;stolen from SI:LISP-TOP-LEVEL1
  si:(let (old-package w-pkg (top-level-p t))
       (FORMAT T "~&;Reading~:[~; at top level~]~@[ in ~A~]."
	       TOP-LEVEL-P (SEND-IF-HANDLES *TERMINAL-IO* :NAME))
       (PUSH NIL *VALUES*)
       (DO ((*READTABLE* si:common-lisp-readtable)
	    (LAST-TIME-READTABLE NIL)
	    THROW-FLAG)				;Gets non-NIL if throw to COMMAND-LEVEL (e.g. quitting from an error)
	   (NIL)				;Do forever
	 ;; If *PACKAGE* has changed, set OLD-PACKAGE and tell our window.
	 ;; Conversely, if the window's package has changed, change ours.
	 ;; The first iteration, we always copy from the window.
	 (COND
	   ;; User set the package during previous iteration of DO
	   ;; => tell the window about it.
	   ((AND OLD-PACKAGE (NEQ *PACKAGE* OLD-PACKAGE))
	    (SEND-IF-HANDLES *TERMINAL-IO* :SET-PACKAGE *PACKAGE*)
	    (SETQ OLD-PACKAGE *PACKAGE*))
	   ;; Window's package has been changed, or first iteration through DO,
	   ;; => set our package to the window's -- if the window has one.
	   ((SETQ W-PKG (SEND-IF-HANDLES *TERMINAL-IO* :PACKAGE))
	    (AND (NEQ W-PKG *PACKAGE*)
		 (SETQ *PACKAGE* W-PKG))
	    (SETQ OLD-PACKAGE *PACKAGE*))
	   ;; First time ever for this window => set window's package
	   ;; to the global value of *PACKAGE*.
	   ((NULL OLD-PACKAGE)
	    (SETQ OLD-PACKAGE *PACKAGE*)
	    (SEND-IF-HANDLES *TERMINAL-IO* :SET-PACKAGE *PACKAGE*)))
	 (CHECK-FOR-READTABLE-CHANGE LAST-TIME-READTABLE)
	 (SETQ LAST-TIME-READTABLE *READTABLE*)
	 (SETQ THROW-FLAG T)
	 (CATCH-ERROR-RESTART ((SYS:ABORT DBG:DEBUGGER-CONDITION) "Return to top level in ~A."
			       (OR (SEND-IF-HANDLES *TERMINAL-IO* :NAME) "current process."))
	   (FRESH-LINE)
	   (SETQ +++ ++ ++ + + -)		;Save last three input forms
	   (SETQ - (read-for-top-level nil nil '((:preemptable) (:activation char= #\end))))
	   (if (and (listp -) (eq (car -) :mouse-button))
	       (send-if-handles (third -) :process-mouse-blip -)
	     (LET ((LISP-TOP-LEVEL-INSIDE-EVAL T)
		   VALUES)
	       (UNWIND-PROTECT
		   (SETQ VALUES (MULTIPLE-VALUE-LIST (EVAL-ABORT-TRIVIAL-ERRORS -)))
		 ;; Always push SOMETHING -- NIL if evaluation is aborted.
		 (PUSH VALUES *VALUES*))
	       (SETQ /// // // / / VALUES)
	       (SETQ *** ** ** * * (CAR /)))
	     (DOLIST (VALUE / (terpri))
	       (FRESH-LINE)
	       (FUNCALL (OR PRIN1 #'PRIN1) VALUE)))
	   (SETQ THROW-FLAG NIL))
	 (WHEN THROW-FLAG
	   ;; Inform user of return to top level.
	   (FORMAT T "~&;Back to top level~@[ in ~A~]."
		   (SEND-IF-HANDLES *TERMINAL-IO* :NAME))))))
