;;; -*- Mode: LISP; Syntax: Common-Lisp; Package: DEMO-GRAPH; 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. ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;;  DEMO-GRAPH
;;;
;;;  Designed and written by Andrew Mickish and Bryan Loyall

#|
     CHANGE LOG:
     06/01/93 Andrew Mickish - Removed Verify-Binding and called Careful-Eval
                instead;  fixed problem with setting new root caused by
                kr-send being an unsafe macro; made error-gadget window be
                top-level
     03/25/92 Andrew Mickish - Get-Values ---> G-Value
     03/14/91 Andrew Mickish - Created
|#


(in-package "DEMO-GRAPH" :use '("LISP" "KR" "GARNET-DEBUG"))

(export '(DO-GO DO-STOP SCHEMA-GRAPH DEMO-GRAPH-ERROR-GADGET ROOT-BOX
	  RELAYOUT DEMO-GRAPH-WIN))


;; Load necessary files
;;
(defvar DEMO-GRAPH-INIT
  (progn
    (load user::Garnet-Aggregraphs-Loader)
    (dolist (file '("text-buttons-loader" "scrolling-labeled-box-loader"
		    "error-gadget-loader"))
      (load (merge-pathnames file
			     #+cmu "gadgets:"
			     #+(not cmu) user::Garnet-Gadgets-PathName)))))


(defvar *bold-font* (create-instance NIL opal:font (:face :bold)))


;; Called by the graph interactor to delete a node and all its children.
;;
(defun RECURSIVE-NODE-DELETE (graph node)
  (let* ((links (g-value node :links-from-me))
	 (child-nodes (mapcar #'(lambda (link)
				  (g-value link :to))
			      links)))
    ;; Recursively delete children
    (mapcar #'(lambda (child)
		(recursive-node-delete graph child))
	    child-nodes)
    ;; Now delete current node
    (kr-send graph :delete-node graph node)))


;; Resets the :reachable slot of all nodes to be NIL, then sets the :reachable
;; slot of the root and all its children to be T, then deletes all nodes of the
;; graph whose :reachable slot is still NIL.
;;
(defun REMOVE-ALL-BUT-REACHABLE (graph root)
  (opal:do-components (g-value graph :nodes)
    #'(lambda (n) (s-value n :reachable NIL)))
  (MARK-REACHABLE-FROM-ROOT root)
  ; Now delete all nodes that were set
  (let ((nodes (copy-list (g-value graph :nodes :components))))
    ; Can't do do-components and a destructive operation on the :components
    ; slot simultaneously, so use dolist instead
    (dolist (n nodes)
      (unless (g-value n :reachable)
	(kr-send graph :delete-node graph n)))))


;; Sets the :reachable slot of root and all its children
;;
(defun MARK-REACHABLE-FROM-ROOT (root)
  (s-value root :reachable t)
  (dolist (l (g-value root :links-from-me))
    (let ((n (g-value l :to)))
      (unless (g-value n :reachable)
	(MARK-REACHABLE-FROM-ROOT n)))))


;;;
;;;  DO-GO
;;;

(defun DO-GO (&key dont-enter-main-event-loop double-buffered-p)

  (create-instance 'DEMO-GRAPH-WIN inter:interactor-window
     (:left 750)(:top 10)(:width 400)(:height 400)
     (:double-buffered-p double-buffered-p)
     (:title "Aggregraph Demo") (:icon-title "Demo-Graph"))
  (s-value DEMO-GRAPH-WIN
	   :aggregate
	   (create-instance 'DEMO-GRAPH-TOP-AGG opal:aggregate))
  (opal:update DEMO-GRAPH-WIN)


  (create-instance 'SCHEMA-GRAPH opal:aggregraph
     (:left 10) (:top 50)
     (:children-function #'(lambda (obj depth)
			     (if (> depth 0) NIL
				 (g-value obj :is-a-inv))))
     (:info-function #'(lambda (obj)
			 (string-capitalize
			  (kr:name-for-schema obj))))
     (:source-roots (list opal:view-object))
     (:add-back-pointer-to-nodes-function #'(lambda (source-node graph-node)
					      (s-value source-node :graph-node
						       graph-node)))
     (:node-prototype 
      (create-instance NIL opal:aggregraph-node-prototype
	 (:has-children-not-in-graph-p
	  (o-formula
	   (let* ((graph (gvl :parent))
		  (source-node (gvl :source-node))
		  ; Source-Children should be computed using kr-send, but this
		  ; doesn't seem to work:
		  ; (source-children (kr-send graph :children-function
		  ;                           source-node 0))
		  (source-children (g-value source-node :is-a-inv))
		  (graph-children (mapcar #'(lambda (link)
					      (g-value link :to))
					  (gvl :links-from-me))))
	     (gv graph :children-function)
	     (> (length source-children) (length graph-children)))))
         (:parts
	  `((:box :modify
	     (:filling-style ,(o-formula (if (gvl :parent :interim-selected)
					     opal:black-fill
					     opal:white-fill)))
	     (:draw-function :xor) (:fast-redraw-p T))
	    (:text-al :modify
	     (:font ,(o-formula (if (gvl :parent :has-children-not-in-graph-p)
				    *bold-font*
				    opal:default-font))))))))
     (:interactors
      `((:press ,inter:menu-interactor
	 (:window ,(o-formula (gv-local :self :operates-on :window)))
	 (:start-where ,(o-formula (list :element-of (gvl :operates-on :nodes))))
	 (:start-event (:leftdown :rightdown))
	 (:final-function
	  ;;; Mnemonics:
	  ;;;     "Source" is a node in the original graph that we are modeling
	  ;;;   (the Garnet hierarchy) such as opal:graphical-object.
	  ;;;     "Source Children" is a list of nodes in the original graph
	  ;;;   that are children of "Source".
	  ;;;     
	  ,#'(lambda (inter node)
	       (let* ((graph (g-value node :parent :parent))
		      (source (g-value node :source-node))
		      (char (g-value inter :start-char)))
		 (case char
		   ;; Add first child that has not already been added to graph
		   (:leftdown
		    (let* ((source-children (kr-send graph :children-function
						     source 0))
			   (new-child
			    (find-if-not
			      #'(lambda (source-child)
				  (kr-send graph :source-to-graph-node
					   graph source-child))
			      source-children)))
		      (when new-child
			(kr-send graph :add-node
				 graph new-child (list node) NIL))))

		   ;; Delete the current node and all its children
		   (:rightdown
		    ; Don't delete the root
		    (unless (eq source (first (g-value graph :source-roots)))
		      (RECURSIVE-NODE-DELETE graph node)))))))))))

  
  ;;; An error-gadget to tell the user when a non-schema was typed into
  ;;; the labeled box as a new root
  ;;;
  (create-instance 'DEMO-GRAPH-ERROR-GADGET garnet-gadgets:error-gadget
    (:window-left (o-formula (opal::gv-center-x-is-center-of DEMO-GRAPH-WIN)))
    (:window-top (o-formula (opal::gv-center-y-is-center-of DEMO-GRAPH-WIN)))
    )

  
  ;;; Press this button to relayout the graph
  ;;;
  (create-instance 'RELAYOUT garnet-gadgets:text-button
     (:left 10) (:top 10)
     (:string "Relayout")
     (:final-feedback-p NIL)
     (:shadow-offset 5)
     (:gray-width 3)
     (:text-offset 3)
     (:selection-function #'(lambda (gadget value)
			      (declare (ignore gadget value))
			      (kr-send SCHEMA-GRAPH :layout-graph
				       SCHEMA-GRAPH))))


  ;;; Used to set the root of the graph
  ;;; 
  (create-instance 'ROOT-BOX garnet-gadgets:scrolling-labeled-box
     (:left 100) (:top 10) (:width 250)
     (:label-string "Schema Root:")
     (:value "opal:view-object")
     (:old-value "opal:view-object")
     (:selection-function
      #'(lambda (gadget string)
	  (multiple-value-bind (value errorp)
	      (gg:careful-string-eval string DEMO-GRAPH-ERROR-GADGET
				      (concatenate 'string
						   "Unable to access schema "
						   string))
	    (if (and (not errorp) (schema-p value))
	        ;;; The user typed a valid schema, so make it the new root
		(let* ((old-root (first (g-value SCHEMA-GRAPH :source-roots)))
		       (existing-node-to-be-root
			; This will get a graph node if one exists for value
			(opal:source-to-graph-node SCHEMA-GRAPH value)))
		  ;; Put new root in root lists and remove all nodes that are
		  ;; not children of the new root.
		  (cond
		    (existing-node-to-be-root
		     (unless (eq old-root existing-node-to-be-root)
		       (opal:remove-root SCHEMA-GRAPH old-root)
		       (opal:make-root SCHEMA-GRAPH existing-node-to-be-root)
		       (REMOVE-ALL-BUT-REACHABLE SCHEMA-GRAPH
						 existing-node-to-be-root)))
		    (t
		     (REMOVE-ALL-BUT-REACHABLE SCHEMA-GRAPH old-root)
		     (kr-send schema-graph :add-node schema-graph value NIL NIL)
		     (let ((new-root (g-value value :graph-node)))
		       (opal:make-root SCHEMA-GRAPH new-root))))
		  (opal:layout-graph SCHEMA-GRAPH)
		  (s-value gadget :old-value string))
		;; The value was invalid, so restore old value
		(s-value gadget :value (g-value gadget :old-value)))
	    ))))

  
  (opal:add-components DEMO-GRAPH-TOP-AGG
		       RELAYOUT ROOT-BOX SCHEMA-GRAPH)
  (opal:update DEMO-GRAPH-WIN)
  

  (format t "~%Demo-Graph:
      This graph shows the Garnet schema hierarchy.
      Press on a node with the left mouse button to display
   another child of the node (if possible).  Nodes that can be
   expanded further appear in bold font.
      Press on a node with the right mouse button to delete the
   node and all its children.
      Pushing the relayout button will reorganize the graph neatly.
      Typing the name of a garnet object in the labeled box will
   put that garnet object at the root.~%")

  (unless dont-enter-main-event-loop #-cmu (inter:main-event-loop))
  
  )


(defun DO-STOP ()
  (opal:destroy DEMO-GRAPH-WIN))
