;;; -*- mode:lisp; package:(graph global 1000); base:10.; -*- ;;;

;;;

;;; This file contains code to implement


;;; ****************
;;; Comments about Nodes
;;; ****************

;;; Nodes are one of the primary objects of interest in the graph
;;; display system.  You must have nodes before you can add any
;;; arcs.  The graph display system imposes the restriction that nodes
;;; may not overlap as displayed

;;; There are several flavors of graph nodes that you can use, and you
;;; may define others to suit your needs.  An anticipated application
;;; (soon to be built) will build nodes to represent conses.

;;;  &&&&  Changes on 10-Feb-84.

;;;  We talked about this for a while, and decided that it is REALLY
;;; IMPORTANT to allow the user to dynamically change the shape of nodes.
;;; The way we are going to do this is to let each node have a SHAPE
;;; instance variable, and dispatch on the shape when doing certain
;;; shape-critical operations, like :DRAW or :ERASE.

;;;  To handle the dispatching, we are using a relatively new addition to the 
;;; flavor system, CASE method combination.  This allows a second level
;;; of dispatch, where you send a message with a PRIMARY method name, and
;;; a secondary method name.  For more details, see the GREEN lisp machine
;;; manual, or the Slymebolics Release 4.0 release notes.
;;;
;;; To further aid in this, we define several MACROS for shape-critical
;;; operations.  They work like this:
;;;
;;;  (draw <obj>)  ==>  (send <obj> ':draw (send <obj> ':shape))
;;;  
;;;  This should make it very easy for users to use, and avoids the 
;;; problems of having :draw have to have a big Selectq.

;;;  &&&&  More 10-Feb-84 CHanges.

;;;  Instance variables that should not be printed to a file should
;;; have a ':dont-print-to-file property.



;;;  ****************************************************************
;;;  Necessary Loads, Constants, Declarations. 
;;;  ****************************************************************

;;; A constant which controls the separation of parallel arcs.
(defconst *delta-separation* 4. "The separation between adjacent parallel arcs")

;;;  The nearness to the screen factor for determining whether
;;; a node is visable. 
(defvar *chocolate-fudge* 500. "How far the center of a node may be from the
 screen and still be considered viewable.")





;;;  ****************************************************************
;;;  Internal Macros, Structure Definitions, FLAVOR definitions. 
;;;  ****************************************************************

;;;  Rounds NUMBER to the nearest GRID-SIZE.  Works equally well for negative
;;; numbers.  E.g. (round-grid -14 10) --> -10, while (round-grid -15 10) --> -20. 
(defmacro round-grid (number grid-size)
  `(let ((my-number ,number)
	 (my-grid-size ,grid-size))
     (* (// (+ (abs my-number) (// my-grid-size 2.)) my-grid-size)
	my-grid-size
	(signum my-number))))


;;;  A basic node encompasses the concepts of a mathematical node, plus
;;; it knows where it is, and has a (possibly null) label that can be
;;; displayed.

	   ;;;;;;;;;;
(defflavor basic-node
	   ;;;;;;;;;;

	((arcs nil)		       ;;;What arcs touch this node
	 (x 50.)				;These positions are Not critical. 
	 (y 50.)
	 (width 900.)
	 (height 600.)
	 (label ':unlabelled)
	 (name (format nil "Node-~A-~D" (time:print-current-time nil) (time)))
	 (drawn-p nil)
	 (shape ':rectangle)	       ;; Default shape to draw this.
	 (flash-blinker nil)			; Blinker to use for flashing
	 (window nil)			;pointer back to instance of
						;graph-display-mixin
	 )
	(editable-attributes-mixin)
  (:method-combination (:case :base-flavor-last :draw :erase :periphery-point))
  :gettable-instance-variables
  :settable-instance-variables
  :initable-instance-variables)
(graph-putprop 'basic-node 'arcs t ':read-only)
(graph-putprop 'basic-node 'arcs '(lambda (arcs)
			       (format nil "This node has ~D arc~:P." (length arcs)))
	       ':pretty-print)
(graph-putprop 'basic-node 'arcs t ':dont-print-to-file)
(graph-putprop 'basic-node 'x t ':read-only)
(graph-putprop 'basic-node 'y t ':read-only)
(graph-putprop 'basic-node 'width ':width ':type)
(graph-putprop 'basic-node 'width t ':printable)
(graph-putprop 'basic-node 'width t ':copyable)
(graph-putprop 'basic-node 'height ':height ':type)
(graph-putprop 'basic-node 'height t ':printable)
(graph-putprop 'basic-node 'height t ':copyable)
(graph-putprop 'basic-node 'label ':string ':type)
(graph-putprop 'basic-node 'label t ':printable)
(graph-putprop 'basic-node 'name t ':read-only)
(graph-putprop 'basic-node 'name t ':printable)
(graph-putprop 'basic-node 'drawn-p t ':dont-edit)
(graph-putprop 'basic-node 'drawn-p t ':dont-print-to-file)
(graph-putprop 'basic-node 'flash-blinker t ':dont-edit)
(graph-putprop 'basic-node 'flash-blinker t ':dont-print-to-file)
(graph-putprop 'basic-node 'window t ':dont-edit)
(graph-putprop 'basic-node 'window t ':dont-print-to-file)
(graph-putprop 'basic-node 'window nil ':dont-save-this-object-to-file)
(graph-putprop 'basic-node 'window nil ':dont-retrieve-this-object-from-file)
(graph-putprop 'basic-node 'shape
	       '(:rectangle :parallelogram :diamond :ellipse :sausage) ':type)
(graph-putprop 'basic-node 'shape t ':printable)	
(graph-putprop 'basic-node 'shape t ':copyable)



;;;  ****************************************************************
;;;  Internal Code
;;;  ****************************************************************

;;;  Make the node have a label.  If you supply one, it will override
;;; whatever damage this method might cause.
(defmethod (basic-node :before :init)
    (&rest ignore)
    (if (eq label ':unlabelled) (setq label name)))   ; Copy the name over to the label.

;;;  ****************
;;;  General graph node stuff. 
;;;  More specific node types follow. 
;;;  ****************

;;; Send this message to a node to find the point on its periphery
;;; to which you would draw an arc.  This could be a midpoint of some
;;; closest side, or whatever.  You need to supply the centerpoint 
;;; of the node of the originating arc, so the node to which this
;;; message is sent can tell the direction of the arc. 
;;;  This default message just returns the center of the node.  Most
;;; of the time we can do MUCH better. 
;;;

;;;  To position a node. 
	    ;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod (basic-node :set-position)
	    ;;;;;;;;;;;;;;;;;;;;;;;;
	   (new-x new-y)
  (erase self)
  (setq x new-x  y new-y)
  (send self ':guarantee-no-overlaps)
  ;; Tell the arcs to recompute their positions. 
  (loop for arc in arcs do (send arc ':compute-joint-points))
  (draw self))

(defmethod (basic-node :before :set-width)
	   (ignore)
  (erase self))

(defmethod (basic-node :after :set-width)
	   (new-width)
  (setq width new-width)
  (send self ':guarantee-no-overlaps)
  ;; Tell the arcs to recompute their positions. 
  (loop for arc in arcs do (send arc ':compute-joint-points))
  (draw self))

(defmethod (basic-node :before :set-height)
	   (ignore)
  (erase self))

(defmethod (basic-node :after :set-height)
	   (new-height)
  (setq height new-height)
  (send self ':guarantee-no-overlaps)
  ;; Tell the arcs to recompute their positions. 
  (loop for arc in arcs do (send arc ':compute-joint-points))
  (draw self))


(defmethod (basic-node :before :set-shape)
	   (ignore)
  (erase self))

(defmethod (basic-node :after :set-shape)
	   (ignore)
  (loop for arc in arcs do (send arc ':compute-joint-points))
  (draw self))

;;;  Here is a tough one.  To guarantee a node doesn't overlap, we
;;; need to compare it with all other nodes.  We also compensate for
;;;the grid size in this method by rounding each coordinate to the
;;;nearest grid point.  The :before daemon snaps the x and y
;;;coordinates to the nearest grid point if the grid is
;;;being used on the window.
(defmethod (basic-node :before :guarantee-no-overlaps)
	   (&optional ignore)
  (let ((grid-size (send window ':grid-size)))
    (when (send window ':grid-on)
      (setq y (round-grid y grid-size))
      (setq x (round-grid x grid-size)))))

(defmethod (basic-node :guarantee-no-overlaps)
	   (&optional other-nodes)
  (loop for node in (send window ':nodes)
	unless (eq self node)
	if (send node ':overlap-p self)
	return (progn
		 ;; Move over about half the width of the node in a crude
		 ;; attempt to find a better place for it. 
		 (setq x (+ x (// width 2)))
		 (send self ':guarantee-no-overlaps other-nodes)))
  (loop for node in other-nodes
	unless (eq self node)
	if (send node ':overlap-p self)
	return (progn
		 ;; Move over about half the width of the node in a crude
		 ;; attempt to find a better place for it. 
		 (setq x (+ x (// width 2)))
		 (send self ':guarantee-no-overlaps other-nodes))))


;;;  Special feature used by :read message in graph-editor windows.
;;;  This will check to see if the node overlaps any of the other nodes
;;; presented in a list.  If it does overlap, it will take a MODE dependent
;;; action, and return a new MODE.  If no overlap, just returns the current
;;; mode.  This thing interacts with the user via a menu.
(defmethod (basic-node :maybe-overlap)
	   (node-list other-node-list mode)
  (cond ((or (loop for node in other-node-list
		   if (send self ':overlap-p node) return t finally nil)
	     (loop for node in node-list
		   if (send self ':overlap-p node)
		   return t
		   finally nil))
	 (selectq mode
	   (:query
	    (let ((action (tv:menu-choose
			    '(("Move this node" :move)
			      ("Let node stay" :leave)
			      ("Move this and all other overlapping nodes" :move-all)
			      ("Let this and all other overlapping nodes stay" :leave-all))
			    (format nil "This node, ~A, overlaps another." name))))
	      (selectq action
		(:move (send self ':guarantee-no-overlaps other-node-list)
		       :query)
		(:leave :query)
		(:move-all (send self ':guarantee-no-overlaps other-node-list)
		           :move)
		(:leave-all :leave)
		(otherwise :query))))
	   (:leave ':leave)
	   (:move (send self :guarantee-no-overlaps other-node-list)
	          :move)))
	(t mode)))

;;; Check to see if a node is viewable before we attempt to draw 
;;; or erase it. 
(defwrapper (basic-node :draw) (() . body)
  `(when (send self ':viewable-p) ,@body))

(defwrapper (basic-node :erase) (() . body)
  `(when (send self ':viewable-p) ,@body))


;;;  Before drawing any node, check to see if it is already on the screen,
;;; and if so, erase it first.

(defwhopper (basic-node :draw)
	    (suboperation)
  (if drawn-p (erase self))
  (continue-whopper suboperation)
  (setq drawn-p t)
  (loop for arc in arcs
	if (not (send arc ':drawn-p))
	 do (draw arc)))

;;;  After erasing a node, let it know it is no longer on the screen.

(defwhopper (basic-node :erase)
	    (suboperation)
  (continue-whopper suboperation)
  (setq drawn-p nil)
  (loop for arc in arcs do (erase arc)))

;;;  See if this node overlaps another. 
(defmethod (basic-node :overlap-p)
	   (node)
  (multiple-value-bind  (ulx1 uly1 lrx1 lry1)
      (send self ':containing-rectangle)
    (multiple-value-bind  (ulx2 uly2 lrx2 lry2)
	(send node ':containing-rectangle)
      (overlapping-boxes-p ulx1 uly1 lrx1 lry1 ulx2 uly2 lrx2 lry2))))

(defun overlapping-boxes-p (ulx1 uly1 lrx1 lry1 ulx2 uly2 lrx2 lry2)
      (and
	;; First, see if the x's overlap
	(or ( ulx1 ulx2 lrx1)
	    ( ulx1 lrx2 lrx1)
	    ( ulx2 ulx1 lrx2)
	    ( ulx2 lrx1 lrx2))
	;; Now, see if the y's overlap.  Both x and y must overlap for the
	;; rectangles to overlap. 
	(or ( uly1 uly2 lry1)
	    ( uly1 lry2 lry1)
	    ( uly2 uly1 lry2)
	    ( uly2 lry1 lry2))))

;;;return when this graph-node is currently viewable on the screen
;;;return t if is, nil otherwise.  This is a VERY simple algorithm
;;; that is only approximate.  If the node's center is NEAR the screen
;;; it is considered viewable. 
(defmethod (basic-node :viewable-p)
	   ()
  ;; Get the screen's coordinates. 
    (multiple-value-bind (sulx suly slrx slry)
	(send window ':containing-rectangle)
      (and ( (- sulx *chocolate-fudge*) x (+ slrx *chocolate-fudge*))
	   ( (- suly *chocolate-fudge*) y (+ slry *chocolate-fudge*)))))

;;;  Returns T if the node "contains" the specified point. 
(defmethod (basic-node :contains-point)
	   (w z)
  (multiple-value-bind (ulx uly lrx lry)
      (send self ':containing-rectangle)
    (and ( ulx w lrx) ( uly z lry))))


(defmethod (basic-node :containing-rectangle)
	   ()
  (let ((hh (// height 2))
	(hw (// width 2)))
    (values (- x hw) (- y hh) (+ x hw) (+ y hh))))


;;; Default; just wipes out the entire containing rectangle
(defmethod (basic-node :otherwise :erase)
	   (ignore)
  (multiple-value-bind (ulx uly nil nil)
      (send self ':containing-rectangle)
    (send window ':draw-white-box  ulx uly width height)))

;;; Use this to save a node.  This produces code that make-instance should
;;; be pretty happy with, EXCEPT that the :ARC value returns a list of numbers. 
;;; These numbers are the relative positions of the arcs in the total list
;;; of arcs.  During restoration, we need to translate these back. 
(defmethod (basic-node :display)
	   (stream)
  (format stream "(graph:~s . ~s)~%"
	  (typep self)
	  (loop for iv in (si:flavor-all-instance-variables
			    (get (typep self) 'si:flavor))
		for uiv = (intern iv :keyword)
		for val = (send self uiv)
		unless (graph-get (typep self) iv ':dont-print-to-file)
		collect uiv
		AND collect (cond
			      ((eq uiv ':font)
			       (zwei:font-name val))
			      (t val)))))


;;;  ****************
;;;  Node FLASHING
;;;  ****************

;;;  See also (in utils) the macro with-object-flashing for an easy interface to this.

(defmethod (basic-node :flash)
	   (status)
  ;; If there isn't a blinker yet, make one.
  (unless flash-blinker
    (setq flash-blinker (tv:make-blinker window 'tv:rectangular-blinker)))
  ;; If turning on, set size and position
  (when status
    (let ((wid (// width (send window ':zoomx)))
	  (hei (// height (send window ':zoomy))))
      (send flash-blinker ':set-size-and-cursorpos
	    wid hei	  
	    (- (send window ':translate-x x) (// wid 2))
	    (- (send window ':translate-y y) (// hei 2)))))
  ;; Either start blinker blinking, or turn it off.
  (send flash-blinker ':set-visibility (if status ':blink)))

;;;  ****************
;;;  Stuff for interactive menus.
;;;  ****************

;;;  
(defmethod (basic-node :add-arc-to-self)
	   ()
  (send window ':user-add-arc nil self))

(defmethod (basic-node :automatic-add-arc-to-self)
	   ()
  (send window ':user-add-arc t self))

(defmethod (basic-node :move-self)
	   ()
  (with-object-flashing self
    (let ((new-pos (send window ':get-position)))
      (send self ':set-position (first new-pos) (second new-pos)))))

(defmethod (basic-node :move-relative)
	   (deltax deltay)
  (send self ':set-position (+ x deltax) (+ y deltay)))

;;;  This code moved to the editable-attributes-mixin.
;;;;  Send this to a node to copy instance variables from another node.
;(defmethod (basic-node :copy-attributes)
;	   (source-node)
;  (loop for iv in (si:flavor-all-instance-variables (get (typep self) 'si:flavor))
;	if (graph-get (typep self) iv ':copyable)
;	do (send self (intern (format nil "SET-~A" iv) :keyword)
;		 (send source-node (intern iv :keyword ))))
;  (setq drawn-p nil)
;  (draw self))

(defmethod (basic-node :delete-self)
	   ()
#+cadr  (sys:%slide 50 2 200 1000000)
  (send window ':delete-node self))

;;;  ****************
;;;  Rectangle nodes. 
;;;  ****************


;;;  Drawing a rectangle node is one of the simplest things. 
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod (basic-node :case :draw :rectangle)
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;
	   (&aux (hw (// width 2)))
    (send window ':draw-graph-rectangle
	  (- x hw)  (- y (// height 2)) width height)
    (if label (send window ':draw-centered-graph-string
		    label (- x hw) (+ x hw) (- y (// height 4)))))


;;;  To erase a rectangle node, just draw a white box in the
;;; right place. 
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod (basic-node :case :erase :rectangle)
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;
	   ()
  (when drawn-p
    (send window ':draw-white-box
	  (- x (// width 2))  (- y (// height 2)) width height)))

;;;  Returns a point on the edge of the rectangle that is
;;; on a line from the remote point to the center of the
;;; rectangle. 
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod (basic-node :case :periphery-point :rectangle)
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	   (remote-x remote-y &optional (delta 0) ignore)
  (setq delta (* (expt -1 delta) delta *delta-separation* *default-mag*))
  (let* ((lratan (safe-atan height width))
	 (ulatan (+ *pi* lratan))
	 (llatan (safe-atan height (- width)))
	 (uratan (+ *pi* llatan))
	 (remote-atan (safe-atan (- remote-y y) (- remote-x x))))
  (cond
    (( lratan remote-atan llatan)
     (values (+ delta x) (+ y (// height 2))))
    (( llatan remote-atan ulatan)
     (values (- x (// width 2)) (+ delta y)))
    (( ulatan remote-atan uratan)
     (values (+ delta x) (- y (// height 2))))
    (t
     (values (+ x (// width 2)) (+ delta y))))))


;;;  ****************
;;;  Parallelogram nodes. 
;;;  ****************

(defconst *parallelogramslant* 1.2 "Slant used for parallelograms")
(defconst *cos-parallelogramslant* (cos *parallelogramslant*))
(defconst *sin-parallelogramslant* (sin *parallelogramslant*))
;;; Draw a parallelogram

	    ;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod (basic-node :case :draw :parallelogram)
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;
	   (&aux (inside (- (// width 2.)
			    (fixr (// (* height *cos-parallelogramslant*)
				      *sin-parallelogramslant*)))))
    (send window ':draw-graph-parallelogram x y width height *parallelogramslant*)
    (if label (send window ':draw-centered-graph-string
		    label (- x inside) (+ x inside) (- y (// height 4)))))


;;; Erase a parallelogram

	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod (basic-node :case :erase :parallelogram)
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;
	   ()
  (when drawn-p
    (send window ':erase-graph-parallelogram x y width height *parallelogramslant*)))

;;;  Returns a point on the edge of the parallelogram that is
;;; on a line from the remote point to the center of the
;;; parallelogram. 
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod (basic-node :case :periphery-point :parallelogram)
	   ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	   (remote-x remote-y &optional (delta 0) ignore)
  (setq delta (* (expt -1 delta) delta *delta-separation* *default-mag*))
  (let* ((uratan (safe-atan height width))
	 (llatan (+ *pi* uratan))
	 (ulatan (safe-atan height (- width)))
	 (lratan (+ *pi* ulatan))
	 (remote-atan (safe-atan (- y remote-y) (- remote-x x)))
	 (offset (fixr (// (* height *cos-parallelogramslant*) *sin-parallelogramslant*)))
	 (width2 (// width 2.))
	 (height2 (// height 2.))
	 (upper (- y height2))
	 (lower (+ y height2))
	 (xleft (- x width2))
	 (xmidleft (+ xleft offset))
	 (xright (+ x width2))
	 (xmidright (- xright offset)))
    (cond
      (( uratan remote-atan ulatan)
       (values (+ delta (// (+ xmidleft xright) 2.)) upper))
      (( ulatan remote-atan llatan)
       (values (fixr (- (// (+ xleft xmidleft) 2.) (* delta *cos-parallelogramslant*)))
	       (fixr (+ y (* delta *sin-parallelogramslant*)))))
      (( llatan remote-atan lratan)
       (values (+ delta (// (+ xleft xmidright) 2.)) lower))
      (t
       (values (fixr (- (// (+ xmidright xright) 2.) (* delta *cos-parallelogramslant*)))
	       (fixr (+ y (* delta *sin-parallelogramslant*))))))))


;;;  ****************
;;;  Ellipse nodes
;;;  ****************

(defmethod (basic-node :case :draw :ellipse)
	   (&aux (x-radius (// width 2)) (y-radius (// height 2)))
  (send window ':draw-graph-ellipse x y x-radius y-radius)
  (if label (send window ':draw-centered-graph-string
		  label (- x x-radius) (+ x x-radius) y)))

(defmethod (basic-node :case :erase :ellipse)
	   (&aux (x-radius (// width 2)) (y-radius (// height 2)))
  (when drawn-p
    (send window ':erase-graph-ellipse x y x-radius y-radius)))

;;; Compute the periphery point of an ellipse node. This is the intersection of the
;;; line from (remote-x, remote-y) to (x, y) with  the perimeter of the ellipse.
;;; Four major cases are possible. First we check that every thing looks "good". If 
;;; not then we break. Two more cases are occur if the line is perfectly vertical
;;; or horizantal. The interesting case occurs when we really have to do the
;;; intersection computation. This is done by substituting the equation for the line
;;; (y = mx +q) into the equation for the ellipse (ax^2 + by^2 = r^2). This results
;;; in another equation (c1x^2 + c2x + c3 = 0) which we solve with the quadratic
;;; formula.

(defmethod (basic-node :case :periphery-point :ellipse)
       (remote-x remote-y &optional ignore ignore)
  (let* ((dx (- x remote-x))
	 (dy (- y remote-y))
	 (w2  (// width 2.0))
	 (h2 (// height 2.0))
	 (maxr (max w2 h2))
	 m q a b r c1 c2 c3 b2m4ac rp rm)
;    (format t "~%x=~d y=~d remote-x=~d remote-y=~d~%" x y remote-x remote-y)
    (cond ((and (equal dx 0.) (equal dy 0.)) (break))	;should also check for inside node
	  ((equal dx 0.)
	   (if (> remote-y y) (values x (fix (+ y h2))) (values x (fix (- y h2)))))
	  ((equal dy 0.)
	   (if (> remote-x x) (values (fixr (+ x w2)) y) (values (fixr (- x w2)) y)))
	  (t
	   (setq m (// (float dy) (float dx))
		 q (- y (* m x)))
	   (if (equal width height)
	       (setq a 1.
		     b 1.
		     r w2)
	       (setq a (quotient (* maxr maxr) (* w2 w2))
		     b (quotient (* maxr maxr) (* h2 h2))
		     r maxr))
;	  (format t "~%m=~d, q=~d, a= ~d, b=~d, r=~d~%" m q a b r)
	  (setq c1 (+ a (* b m m))
		c2 (* 2. (- (* b m q) (* b m y) (* a x)))
		c3 (- (+ (* a x x) (* b q q) (* b y y)) (* 2. b q y) (* r r)))
	  (setq b2m4ac (- (* c2 c2) (* 4. c1 c3)))
;	  (format t "~%c1=~d c2=~d c3=~d b2m4ac=~d~%" c1 c2 c3 b2m4ac)
	  (if (< b2m4ac 0.) (break))
	  (setq rp (// (- (sqrt b2m4ac) c2) 2.0 c1)
		rm (// (+ (sqrt b2m4ac) c2) -2.0 c1))
;	  (format t "rp=~d rm=~d" rp rm)
	  (if (< (abs (- remote-x rp)) (abs (- remote-x rm)))
	      (values (fixr rp) (fixr (+ (* m rp) q)))
	      (values (fixr rm) (fixr (+ (* m rm) q))))))))


;;;  ****************
;;;  Diamond nodes
;;;  ****************


(defmethod (basic-node :case :draw :diamond)
	   ()
  (let ((hw (// width 2.))
	(hh (// height 2.)))
    (send window ':draw-graph-lines
	  (- x hw) y
	  x (- y hh)
	  (+ x hw) y
	  x (+ y hh)
	  (- x hw) y)
    (if label (send window ':draw-centered-graph-string
		    label (- x hw) (+ x hw) (- y 5.)))))

;;;  We don't need an erase method; just blat out the rectangle.


;;;  Almost identical to rectangle nodes.
(defmethod (basic-node :case :periphery-point :diamond)
	    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
	   (remote-x remote-y &optional ignore ignore)
  (let* ((lratan (safe-atan height width))
	 (ulatan (+ *pi* lratan))
	 (llatan (safe-atan height (- width)))
	 (uratan (+ *pi* llatan))
	 (remote-atan (safe-atan (- remote-y y) (- remote-x x))))
  (cond
    (( lratan remote-atan llatan)
     (values x (+ y (// height 2))))
    (( llatan remote-atan ulatan)
     (values (- x (// width 2)) y))
    (( ulatan remote-atan uratan)
     (values x (- y (// height 2))))
    (t
     (values (+ x (// width 2)) y)))))


;;;  ****************************************************************
;;;  "Sausages"  (left-half-circle, parallel-lines, right-half-circle)
;;;  ****************************************************************

(defmethod (basic-node :case :draw :sausage)
	   (&aux (hw (// width 2)))
  (send window ':draw-graph-sausage x y (// height 2) hw)
  (if label (send window ':draw-centered-graph-string
		  label (- x hw) (+ x hw) (- y 5))))

;;; Don't need erase; use the default erase.

(defmethod (basic-node :case :periphery-point :sausage)
	   (remote-x remote-y &optional ignore ignore)
  (let* ((lratan (safe-atan height width))
	 (ulatan (+ *pi* lratan))
	 (llatan (safe-atan height (- width)))
	 (uratan (+ *pi* llatan))
	 (remote-atan (safe-atan (- remote-y y) (- remote-x x))))
  (cond
    (( lratan remote-atan llatan)
     (values x (+ y (// height 2))))
    (( llatan remote-atan ulatan)
     (values (- x (// width 2)) y))
    (( ulatan remote-atan uratan)
     (values x (- y (// height 2))))
    (t
     (values (+ x (// width 2)) y)))))


