
 /**
  ** Root
  **
  ** Every glyph window contains an instance of this
  ** class.  In addition to being the root of the
  ** TTree displayed in the window, Root glyphs
  ** do the job of creating the display, managing
  ** its animation, and looking after music kit
  ** performance.
  **/

#import "Root.h"
#import "Nutation.h"
#import <appkit/Window.h>
#import <dpsclient/psops.h>
#import <dpsclient/wraps.h>
#import <appkit/Application.h>
#import <appkit/View.h>
#import <appkit/ScrollView.h>
#import <objc/List.h>
#import <math.h>
#import <musickit/Orchestra.h>
#import <musickit/Conductor.h>
#import <musickit/Note.h>
#import <musickit/Envelope.h>
#import <musickit/Partials.h>

#pragma .h struct rootFlags_
#pragma .h { unsigned isAWindow:1 ;
#pragma .h   unsigned lock:1 ;
#pragma .h   unsigned clone: 1 ;
#pragma .h   unsigned wasMoved: 1 ;
#pragma .h } ;

  // Superclass file goes on next line:
#pragma .h #import "Glyph.h"

 // declare MKManager procedures
#pragma .h @interface MKManager: Object
#pragma .h - (double) beatsPerMinute ;
#pragma .h - (double) deltaT ;
#pragma .h - (BOOL) fastResponse ;
#pragma .h - (double) headRoom ;
#pragma .h - (double) samplingRate ;
#pragma .h - (double) tickPeriod ;
#pragma .h @end

#pragma .h #define MAXENVS 256
#pragma .h #define MAXPARTIALS 64

@implementation Root:Glyph
{ @public
  id fGWin, bGWin, glyphView, tray, instanceManager ;
  Glyph *targetGlyph, *formerTargetGlyph, *superGlyph ;
  NXRect targetFrame ; // targetGlyph's frame in my coord sys
  NXRect clipRect ;    // clipping rectangle around targetGlyph
  NXRect visibleRect ; // visible portion of rootGlyph
  NXPoint mouseOffset ; // distance from mouse hit to target's origin
  struct rootFlags_ rootFlags ;
  id nRForPart[256] ; // array of note receivers (nRs[0] is unused)
  id mKNoteForPart[256] ; // "prototype" notes (mKNoteForPart[0] is unused)
  id envelopes[MAXENVS] ; // array of envelopes
  int envelopeCount ; 
  id partials[MAXPARTIALS] ; // array of wave tables
  int partialCount ;
}

// MusicKit-related methods

- clearArrays ;
{ // free up any  music kit notes, partial, and envelopes
  // allocated by last play, and set both it
  // and the note receiver array to nil
  // Note that we don't free any note receivers;
  // they are freed by instruments
  int i ;
  for(i = 0 ; i < 256 ; i++)
  { if(mKNoteForPart[i]) 
      [mKNoteForPart[i] free] ; 
     mKNoteForPart[i] = nRForPart[i] = nil ;
  }
  for(i = 0 ; i < MAXENVS ; i++)
  { if(envelopes[i])
     [envelopes[i] free] ;
    envelopes[i] = nil ;
  }
  envelopeCount = 0 ;
  for(i = 0 ; i < MAXPARTIALS ; i++)
  { if(partials[i])
      [partials[i] free] ;
    partials[i] = nil ;
  }
  partialCount = 0 ;
  return self ;
}

- envelope ;
{ // return the next unused envelope or, if all envelopes slots
  // are full, return the last envelope
  id anEnv ;
  if(!envelopes[envelopeCount])
     envelopes[envelopeCount] = [[Envelope alloc] init] ;
  anEnv = envelopes[envelopeCount++] ;
  if(envelopeCount == MAXENVS)
  { envelopeCount-- ;
  }
  return anEnv ;
}

- noteForPart: (int) partNum ;
{ // Return music kit noteDur for part partNum,
  // creating one if necessary.  
  // Note: 1 <= partNum < 256.
  if(!mKNoteForPart[partNum])
  { id mKNote ;
    mKNote = mKNoteForPart[partNum] = [[Note alloc] init] ;
    [mKNote setNoteTag: MKNoteTag()] ; 
    [mKNote setNoteType: MK_noteDur] ;
    [mKNote setPar: MK_amp toDouble: 0.5] ;
  }
  return mKNoteForPart[partNum] ;
}

- (id *) nRArray ;
{ // return address of array of notereceivers
  return nRForPart ;
}

- nRForPart: (int) partNum ;
{ // Return current music kit noteReceiver for 
  // part partNum
  // Note: 1 <= partNum < 256.
  return nRForPart[partNum] ;
}


- partial ;
{ // return the next unused partial or, if all partials used,
  // return the last partial
  id aPar ;
  if(!partials[partialCount])
     partials[partialCount] = [[Partials alloc] init] ;
  aPar = partials[partialCount++] ;
  if(partialCount == MAXPARTIALS)
  { partialCount-- ;
  }
  return aPar ;
}

- play:sender ;
{ // Sender must contain the id of the play button.
  // The message is "relayed" from my glyph view
  static id nuPerformer = nil ;
  id mKM ;
  // the musicKit Manager holds the values in the 
  // musicKit settings window
  mKM = [Nu musicKitManager] ;
  if([sender state])
  { // initialize DSP
    [Orchestra new]  ;    
    [Orchestra setHeadroom: (float) [mKM headRoom]] ;        
    [Orchestra setSamplingRate: (float) [mKM samplingRate]];
    [Orchestra setTimed:MK_TIMED];
    [Conductor setThreadPriority:1.0]; // at higher priority than the app         
    [Orchestra setFastResponse: (BOOL) [mKM fastResponse]]; 
    if (![Orchestra open])
    {  NXRunAlertPanel("Nutation","Can't open DSP",
          NULL,NULL,NULL) ;
       return [sender performClick: self] ;
    }
    [self scan] ; // scan entire tree...AFTER orchestra opened     
    [Conductor setFinishWhenEmpty:NO];
    [Conductor setClocked: YES] ; 
    // [Conductor useSeparateThread:YES] ; // haven't dedected any
    // [Conductor setThreadPriority:1.0] ; // performance advantage
    [[Conductor defaultConductor] setTempo:[mKM beatsPerMinute]] ;    
    MKSetDeltaT([mKM deltaT]) ;
   // performances must use a "NuPerformer"...
    nuPerformer = [objc_getClass("NuPerformer") newForGlyph: targetGlyph] ; 
    // treat targetGlyph like the root of a tree, i.e. don't
    // play its is list
    targetGlyph->thenAux = [Nu terminator] ;
    [Orchestra run];	 // away we go...
    [Conductor startPerformance];
    [nuPerformer activate] ;
  }
  else
  { if(nuPerformer)
    { [nuPerformer free] ;
      nuPerformer = nil ;
    }
    [Conductor finishPerformance] ;
    [Orchestra abort];
  }  
  return self ;
}

// Other methods

- basicFree ;
{ // I use the basic free method to free my ground views
  [fGWin free] ;
  [bGWin free] ;
  return [super basicFree] ;
}

- blowUp ; 
{ // Provide visual feedback that the target glyph is
  // dying, then kill it (the target glyph MUST
  // be unlinked at this point), then set the target
  // glyph to myself
  NXRect aRect ;
  float dx, dy ;
  char tmpStr[80] ;
  int i , knt = 40 ;
  sprintf(tmpStr,"Deleting %s",[targetGlyph name]) ;
  [glyphView glyphMsg: tmpStr] ;
  // shrink window to nothing
  [fGWin orderFront: self] ; // may be offscreen
  [fGWin getFrame: &aRect] ;
  dx = aRect.size.width / (float) knt ;
  dy = aRect.size.height / (float) knt ;
  for(i = 0 ; i < knt ; i++)
  { [fGWin sizeWindow: aRect.size.width :aRect.size.height] ;
    NXPing() ;
    aRect.size.width -= dx ;
    aRect.size.height -= dy ;
  }
  [fGWin orderOut: self] ;
  [targetGlyph free] ; 
  targetGlyph = self ;
  if(instanceManager)  
    [instanceManager instance: targetGlyph] ;
  return self ; 
}

- clear ;
{ // delete all my sub-glyphs, then redraw
  Glyph *theGlyph ;
  theGlyph = is ;
  while(!theGlyph->flags.isTerminator)
  { [theGlyph unlink] ;
    [theGlyph basicFree] ;
    theGlyph = is ;
  }
  return [self display: &frame from: nil] ;
}

- clipRect: (NXRect *) aRect toGlyph: (Glyph *) aGlyph ;
 {  // set aRect to the clipping rectangle imposed by aGlyph.
  // This involves calculating intersection rectangles 
  // back to the rootGlyph.  Just calculates the clipping
  // rectangle, does not actually SET it...
  if(aGlyph->flags.isRoot)
  { *aRect = aGlyph->frame ;
    return self ;
  }
  else
  { NXRect bRect = {{0.0,0.0},{0.0,0.0}} ;
    [self clipRect: aRect toGlyph: aGlyph->ancestor] ;
    bRect.size  = aGlyph->frame.size ;
    [aGlyph convertToRootGlyph: &bRect.origin] ;
    NXIntersectionRect(&bRect,aRect) ;
  } 
  return self ; 
}

- convertToScreen: (NXPoint *) aPnt ;
{ // convert aPnt from my coord sys to screen coord sys
  [glyphView convertPoint: aPnt toView: nil] ;
  [[glyphView window] convertBaseToScreen: aPnt] ;
  return self ;
}

- debugWindows ;
{ // bring my grnd windows onscreen to help
  // in debugging
  [bGWin orderFront: self] ;
  [fGWin orderFront: self] ;
  NXPing() ;
  return self ;
}

- delete: (Glyph *) aGlyph ;
{ // If aGlyph is the rootGlyph, does nothing, else
  // deletes and frees aGlyph.  If aGlyph is 
  // linked, it is unlinked and the display
  // is updated. If aGlyph is unlinked, simply
  // frees it. If aGlyph is the target glyph, the
  // target is changed to the rootGlyph.
  NXRect aRect ;
  if(aGlyph->flags.isRoot)
    return nil ; // can't delete the rootGlyph
  // if aGlyph is the target, make rootGlyph the target
  if(aGlyph->flags.isTarget)
    [self becomeTarget] ;
  if(![aGlyph isLinked]) 
    return [aGlyph free] ; // not linked
  aRect = aGlyph->frame ;
  [[aGlyph ancestor] convertToRootGlyph: &aRect.origin] ;
  [aGlyph unlink] ;
  [aGlyph free] ;
  [self display: &aRect from: nil] ;
  return self ;
}


- display: (NXRect *) theRect  from: (Glyph *) startGlyph;
{ // call this to make me redisplay 
  // everything which intersects theRect, which
  // must be in my coords (and NOT startGlyph's coords).
  // The displaying "starts"
  // at startGlyph..(which MUST be linked)...
  // if you know that startGlyph is
  // opaque, you can display from there.  Otherwise,
  // display from the rootGlyph.
  // If startGlyph == nil, the rootGlyph is assumed.
  // If theRect is nil, the startGlyph's bounds are assumed.
  // Displaying works by calling display: (inherited
  // from class Glyph)
  // on all the glyphs which need to be redisplayed.
  // The display: method recurses, "locking focus"
  // and calling [self drawSelf: ...] to do the actual
  // image generation.
  NXRect aRect ;
  if(startGlyph == nil)
    startGlyph = self ;
  if(theRect == nil)
    aRect = startGlyph->frame ;
  else
    aRect = *theRect ;
  // See if background needs to be redisplayed...
  if(startGlyph == self || ![self inTarget: startGlyph]) 
  { // startGlyph displays in the backGround
    [bGWin lockFocus] ;
    if(targetGlyph != self)
      targetGlyph->flags.noDraw = YES ;   
    [startGlyph display: &aRect] ;
    [bGWin unlockFocus] ;
    targetGlyph->flags.noDraw = NO ;
  }
  if(targetGlyph != self && NXIntersectsRect(&targetFrame,&aRect))
  { // then foreground needs to be redisplayed....
    NXRect bRect ;
    bRect = aRect ;
    [targetGlyph convertFromRootGlyph: &bRect.origin] ;
    [fGWin erase: &bRect] ;
    [targetGlyph convertToAncestor: &bRect.origin] ;
    [fGWin lockFocus] ;
    [targetGlyph display: &bRect] ;
    [fGWin unlockFocus] ;
    [targetGlyph convertFromAncestor: &bRect.origin] ;
  }
  [glyphView lockFocus] ;
  [self drawSelf: &aRect :1] ;
  [glyphView unlockFocus] ;
  [[glyphView window] flushWindow] ;
  return self ;
}

- displayRaised ;
{ // create and display a 'raised view' window...
  // a max depth of 256 is assumed
  NXRect aRect, bRect ;
  NXPoint aPnt = {0.0,0.0} ;
  id image[256], theWin, aScrollView, aView ;
  int i ; 
  [[[glyphView superview] superview] getBounds: &aRect] ;
  for(i = 0 ; i < 256 ; i++)
    image[i] = nil ;
  theWin =  
      [Window newContent: &aRect style: NX_TITLEDSTYLE backing: NX_BUFFERED  
        buttonMask: NX_ALLBUTTONS defer: NO ] ;
  [theWin setFreeWhenClosed: YES] ;
  [[glyphView window]  getFrame: &bRect] ;
  [theWin moveTo:bRect.origin.x + 60.0 :bRect.origin.y - 60.0] ;
  [theWin setTitle: [[glyphView window] title]] ;
  aScrollView = [ScrollView newFrame: &aRect] ;
  [aScrollView setHorizScrollerRequired: YES] ;  
  [aScrollView setVertScrollerRequired: YES] ;
  [aScrollView setLineScroll: 1.0] ;
  [aScrollView setBackgroundGray: 1.0] ;
  [[theWin setContentView: aScrollView] free] ;  
  aView = [[View alloc] initFrame: &frame] ;  
  [aScrollView setDocView: aView] ;
  [theWin display] ;
  // update the ttree's 'depth' var so all glyphs know their present depth
  [self calcDepth] ; 
  // pass1: create offscreen windows, paint them transparent white,
  // then draw opaque frame rectangles in light gray
  [self displayRaisedPass1: image size: &frame.size ] ;
  // pass2: frame and draw images into the windows
  [self displayRaisedPass2: image] ;
  // composite offscreen windows to bitmap to build up final picture
  [aView lockFocus] ;
  i = -1 ;
  while(image[++i]) 
  { [image[i] composite:  NX_SOVER toPoint: &aPnt] ;
    [image[i] free] ;
  }
  [aView unlockFocus] ;
  [theWin orderFront: self] ;
  return self ;
}

- drawSelf: (NXRect *) aRect ;
{ // I draw opaque white
  PSsetgray(1.0) ;
  NXRectFill(aRect) ;
  PSsetgray(0.0) ;
  return self ;
}


- drawSelf:(const NXRect *)rects :(int)rectCount ;
{ // assumes focus is locked on my glyphView...composites
  // the ground windows to (re)create the image
  // First, composite background 
  [bGWin composite: NX_COPY fromRect: rects toPoint: &rects->origin] ;
  // and, if necessary, the foreground
  if((targetGlyph != self) && NXIntersectsRect(rects, &targetFrame))
  { NXRect intersectRect, compRect ;
    intersectRect = targetFrame ;
    NXIntersectionRect(rects,&intersectRect) ;
    NXIntersectionRect(&clipRect,&intersectRect) ;
    compRect.origin.x = intersectRect.origin.x  - targetFrame.origin.x ;
    compRect.origin.y = intersectRect.origin.y  - targetFrame.origin.y ;
    compRect.size = intersectRect.size ;
   [fGWin composite: NX_SOVER fromRect: &compRect
         toPoint: &intersectRect.origin] ;
  }
  return self ;
}

- (Glyph *) formerTargetGlyph ;
{ return formerTargetGlyph ;
}

- free ;
{ [self freeArrays] ;
  return [super free] ;
}

- freezeButton: theButton ;
{ // this method gets called whenever the
  // freeze/melt button is pressed.  Note that
  // trays have special behaviour when they are melted...
  [targetGlyph freeze: [theButton state]] ;
  [[glyphView window] setDocEdited: YES] ;
  if(targetGlyph->flags.isTray)
  { // "thawing" a tray means the we must delete the tray...
    Glyph *cursor ;
    Glyph *supGlyph, *aGlyph ;
    NXPoint winPnt ;
    cursor = targetGlyph->is ;
    while(!cursor->flags.isTerminator)
    { [targetGlyph convertToRootGlyph: &cursor->frame.origin] ;
      cursor = cursor->then ;
    }
    cursor = targetGlyph->is ;
    [targetGlyph unlink] ;
    while(!cursor->flags.isTerminator)
    { aGlyph = cursor ;
      cursor = [cursor then] ;
      [aGlyph unlink] ;
      supGlyph = [self hitTest: &aGlyph->frame.origin] ;
      [supGlyph convertFromRootGlyph: &aGlyph->frame.origin] ;
      [supGlyph plant: aGlyph] ;
    }
    [bGWin lockFocus] ;
    [self display: &targetFrame] ;
    [bGWin unlockFocus] ;
    [glyphView lockFocus] ;
    [bGWin composite: NX_COPY fromRect: &targetFrame
         toPoint: &targetFrame.origin] ;
    [glyphView unlockFocus] ;
    winPnt = targetFrame.origin ;
    [self convertToScreen: &winPnt] ;
    [fGWin moveTo: winPnt.x :winPnt.y] ;
    [fGWin orderFront: self] ;
    [[glyphView window] flushWindow] ;
    tray = nil ;
    return [self blowUp] ;
  }
  return self ;
}

- glyphView ;
{ // return my glyphView
  return glyphView ;
}

- handOff: (NXPoint *) aPnt ;
{ // Attempts to handOff the current targetGlyph to another
  // glyphView, if aPnt (the current mouse point in screen coords)
  // is within that view.  Deletes the target if it fails.  In
  // either case, sets the targetGlyph to rootGlyph.
  NXRect visRect ;
  id theView ;
  int globalNum, winFound ;
  NXPoint bPnt ;
  char tmpStr[80] ;
  PSfindwindow(aPnt->x, aPnt->y, NX_BELOW,[fGWin windowNum],
    &bPnt.x, &bPnt.y,&globalNum, &winFound) ;
  if(winFound) 
  { // find out if globalNum refers to a glyph window
    { id theList ;
      int i,gVKnt,otherLocalNum ;
      unsigned int otherGlobalNum ;
      theList = [objc_getClass("GlyphView") glyphViewList] ;
      gVKnt = [theList count] ;
      for(i = 0 ; i < gVKnt ; i++)
      { theView = [theList objectAt: i] ;
        otherLocalNum = [[theView window] windowNum] ;
	if(otherLocalNum <= 0) // window not yet allocated for this view...
	  continue ;
        NXConvertWinNumToGlobal([[theView window] windowNum],&otherGlobalNum) ;
        if(otherGlobalNum == globalNum) // then this is our window...
        { // find out if theView is unlocked 
            if([[theView rootGlyph] isLocked])
              return [self blowUp] ; // can't handOff to locked window
            [theView convertPoint: &bPnt fromView: nil] ;
            [[[theView superview] superview] getDocVisibleRect: &visRect] ;
            if(NXPointInRect(&bPnt,&visRect)) // give it away...
            { id handOffWin, handOffGlyph ;
              NXRect tinyRect = {{0.0,0.0},{1.0,1.0}} ;
              sprintf(tmpStr,"Handing off %s",[targetGlyph name]) ;
              [glyphView glyphMsg: tmpStr] ;
              handOffWin = fGWin ;
              handOffGlyph = targetGlyph ;
              targetGlyph = self ;
              targetGlyph->flags.isTarget = YES ;
              // make new fGrnd window
              fGWin = [[objc_getClass("FGWindow") alloc] frame: &tinyRect] ;
              // finally, handOff the old foreground
             [Nu glyphView: theView] ; // temporarily make this the current view
              [[theView rootGlyph] receive: handOffGlyph inWin: handOffWin 
                      at:&bPnt offset:&mouseOffset ] ;
              [Nu glyphView: self] ;
              return self ;
           }
        }
      }
    }
  }
  return [self blowUp] ;
}


- init: (float) width :(float) height ;
{ //  
  self = [super init: width :height] ;
  // basic initialization
  rootFlags.lock = NO ;
  flags.isRoot = YES ;
  flags.isTarget = YES ; // I am the initial target
  formerTargetGlyph = self ; // I am the former target
  // other initialization...this initialization is
  // run both by alloc and by unarchiving
  [self setup] ;
  return self ;
}

- (BOOL) inTarget: (Glyph *) theGlyph ;
{ // returns YES iff theGlyph is the target glyph
  // or one of its descendants.  If glyph is
  // a descendant of the rootGlyph and not the targetGlyph or
  // a descendant of the targetGlyph, return NO.
  // If glyph is not a descendant of the rootGlyph,
  // return NO.
  Glyph *aGlyph ;
  aGlyph = theGlyph ;
  if(aGlyph->flags.isRoot && aGlyph->flags.isTarget)
    return YES ;
  while(!aGlyph->flags.isTerminator && !aGlyph->flags.isRoot)
  { if(aGlyph->flags.isTarget)
      return YES ;
    aGlyph = aGlyph->ancestor ;
  }
  return NO ;
}


- (BOOL) isLocked ;
{ return rootFlags.lock ;
}

- lock ;
{ // toggle state of "lock" bit
  rootFlags.lock ^= 1 ;
  [[glyphView window] setDocEdited: YES] ;
  return self ;
}
 
  /** useful for debugging ! uncomment if
    you are having lockFocus problems **
static int lf = 0 ;
- (BOOL) lockFocus ;
{ [Nu printf: "lf %d\n",lf] ;
  lf++ ;
  return [super lockFocus] ;
}
- unlockFocus ;
{ --lf ;
  [Nu printf: "ulf %d\n",lf] ;
  return [super unlockFocus] ;
}
***********/

- mouseDown: (NXEvent *) anEvent ;
{ Glyph *tg ;
  NXPoint location = anEvent->location ;
  rootFlags.wasMoved = 0 ;
  [glyphView convertPoint: &location fromView: nil] ;
  formerTargetGlyph = targetGlyph ; // remember last target
  tg = [self hitTest: &location] ;
  // provided the tg is not myself, I
  // send mouseDown: to the tg, and allow
  // that method to return a different glyph
  if(tg != self)
    tg = [tg mouseDown: anEvent] ;
  if(anEvent->flags & NX_SHIFTMASK)
  { tg = [self trayTest: tg] ;
    [[glyphView window] setDocEdited: YES] ;
  }
  else if(tg->flags.isTray)
   tray = tg ;
  else 
   tray = nil ;
  if(tg != targetGlyph)
    [tg becomeTarget] ;
  if(tg == self)
    return self ; 
  if(rootFlags.lock && !rootFlags.clone) 
    return self ;
  // set the clipping rectangle
  [glyphView lockFocus]  ; // unlocked in mouseUp:
  PSgsave() ; // restored in mouseUp:
  NXRectClip(&clipRect) ;
  // remove target glyph from TTree
  [targetGlyph unlink] ;
  // calculate new mouse offset
  mouseOffset.x = location.x - targetFrame.origin.x ;
  mouseOffset.y = location.y - targetFrame.origin.y ;
  rootFlags.isAWindow = NO ; // initially, glyph must be in glyphView
  return self ;
}


- mouseDragged: (NXEvent *) anEvent ;
{ Glyph *aGlyph ;
  if(targetGlyph == self) // can't drag the root!
    return self ; 
  if(rootFlags.lock && !rootFlags.clone) 
    return self ;
  rootFlags.wasMoved = 1 ;
  [targetGlyph mouseDragged: anEvent] ;
  [glyphView convertPoint: &anEvent->location fromView: nil] ;
  // see if we've dragged out of our window
  if(!rootFlags.isAWindow) // i.e. if glyph is in our view...
  {  // erase our old image
    [bGWin composite: NX_COPY fromRect:&targetFrame toPoint: &targetFrame.origin] ;
    if(!NXPointInRect(&anEvent->location,&visibleRect)) // just dragged out of view...
    { NXPoint winPnt ; // turn into a "travelling window"
      winPnt = targetFrame.origin ;
      [self convertToScreen: &winPnt] ;
      [fGWin moveTo: winPnt.x :winPnt.y] ;
      [fGWin orderFront: self] ;
      rootFlags.isAWindow = YES ;
    }
  }
  else // glyph dragged out of my glyphView, so its a "travelling window"
  { if(NXPointInRect(&anEvent->location,&visibleRect)) // dragged back in?
    { [fGWin perform: @selector(orderOut:) with: self
         afterDelay: 1 cancelPrevious: YES] ;
      rootFlags.isAWindow = NO ;
    }
    else // glyph was (and still is) a "travelling window"
    { NXPoint winPnt ; // move the "travelling window"
      winPnt = targetFrame.origin ;
      [glyphView convertPoint: &winPnt toView: nil] ;
      [[glyphView window] convertBaseToScreen:&winPnt] ;
      [fGWin moveTo: winPnt.x :winPnt.y] ;
    }    
  }
  // update the target's location
  targetFrame.origin.x = anEvent->location.x - mouseOffset.x ;
  targetFrame.origin.y = anEvent->location.y - mouseOffset.y ;
  if(!rootFlags.isAWindow) // if not a "travelling window"....
  { // see if clip rectangle must change
    aGlyph = [self hitTest: &anEvent->location] ;
    if(aGlyph && (aGlyph != superGlyph)) 
    { // new superGlyph, hence must reset the clip rectangle
      superGlyph = aGlyph ;
      PSgrestore() ;
      PSgsave() ;
      [self clipRect: &clipRect toGlyph: superGlyph] ;
      NXRectClip(&clipRect) ;
    }
    // draw our new image
    [fGWin composite: NX_SOVER toPoint: &targetFrame.origin] ;
    [[glyphView window] flushWindow] ;
 }
  // update the glyph's location
  [superGlyph convertFromRootGlyph: &anEvent->location] ;
  [targetGlyph moveTo: anEvent->location.x - mouseOffset.x
                     : anEvent->location.y - mouseOffset.y ] ;
  // update the inspector
  if(instanceManager)
   [instanceManager setOrigin] ; 
  return self ;
}

- mouseUp: (NXEvent *) anEvent ;
{ NXPoint loc ;
  if(targetGlyph == self)
    return self ;
  if(rootFlags.lock && !rootFlags.clone) 
    return self ;
  PSgrestore() ;
  [glyphView unlockFocus] ;
  if(rootFlags.isAWindow)
  { [[glyphView window] convertBaseToScreen: &anEvent->location] ;
    [self handOff: &anEvent->location] ; // hand it to another view, or the void!
    return self ;
  }
  if(rootFlags.lock)
  { // can't plant glyph into a locked glyphView
    NXPoint winPnt ;
    winPnt = targetFrame.origin ;
    [glyphView convertPoint: &winPnt toView: nil] ;
    [[glyphView window] convertBaseToScreen: &winPnt] ;
    [fGWin moveTo: winPnt.x :winPnt.y] ;
    // erase old image
    [glyphView lockFocus] ;
    [bGWin composite: NX_COPY fromRect: &targetFrame toPoint: &targetFrame.origin] ;
    [glyphView unlockFocus] ;
    [[glyphView window] flushWindow] ;
    return [self blowUp] ;
  } 
  // note: we don't use anEvent->location here...we use the
  // last location from mouseDragged: (which may be different
  // if mouseDragged: has changed anEvent->location)
  loc.x = targetFrame.origin.x + mouseOffset.x ;
  loc.y = targetFrame.origin.y + mouseOffset.y ;
  superGlyph = [self hitTest: &loc] ;
  // replant the glyph
  [superGlyph convertFromRootGlyph: &loc] ;
  [targetGlyph moveTo: loc.x - mouseOffset.x
                     : loc.y - mouseOffset.y ] ;
  [superGlyph plant: targetGlyph] ;
  if(instanceManager)
    [instanceManager setOrigin] ;
  [targetGlyph mouseUp: anEvent] ;
  if(rootFlags.wasMoved)
  { [targetGlyph wasMoved] ;
    rootFlags.wasMoved = 0 ;
  }
  return self ;
}


- moveGlyph: (Glyph *) theGlyph toPoint: (NXPoint *) aPnt ofGlyph: (Glyph *) aGlyph ;
{ // If aGlyph is myself, do nothing, and return nil,
  // else move theGlyph to aPnt, expressed in
  // aGlyph's coords, of aGlyph.  If aGlyph is
  // nil, then it is determined via hitTesting
  // of the point aPnt, assumed to be in 
  // rootGlyphs coords.  Returns nil if the
  // hitTest fails.
  NXRect aRect, bRect, cRect ;
  BOOL inTarget = NO, wasLinked = YES ;
  if(theGlyph == self)
    return nil ; // can't move the rootGlyph
  if(theGlyph == aGlyph) // chaos could result!
    return nil ;
  if(aGlyph == nil)
  { if((theGlyph = [self hitTest: aPnt]) == nil)
      return nil ; // hitTest failed
  }
  if(!aGlyph->flags.isRoot &&
       aGlyph->ancestor->flags.isTerminator) // aGlyph not linked?
  { return nil ;
  }
  if(theGlyph->ancestor->flags.isTerminator) // theGlyph not linked?
    wasLinked = NO ; 
  else // is linked...
  { inTarget = [self inTarget: theGlyph] ;
    aRect = bRect = theGlyph->frame ;
    [theGlyph->ancestor convertToRootGlyph: &bRect.origin] ;
    if(inTarget)
    { [theGlyph->ancestor convert: &aRect.origin
          toGlyph: targetGlyph] ;
      [theGlyph unlink] ;
      if(!theGlyph->flags.isTarget) 
      { // if not the target, must redraw the foreground
        [fGWin lockFocus] ;
        [fGWin erase: &aRect] ;
        [targetGlyph convertToAncestor: &aRect.origin] ;
        [targetGlyph display: &aRect] ;
        [targetGlyph convertFromAncestor: &aRect.origin] ;
        [fGWin unlockFocus] ;
      }
    }
    else
    { [theGlyph unlink] ;
      [bGWin lockFocus] ;
      targetGlyph->flags.noDraw = YES ;
      [self display: &bRect] ;
      targetGlyph->flags.noDraw = NO ;
      [bGWin unlockFocus] ;
    }
  } // is linked..

  // now move the glyph to its new location and redisplay
  [theGlyph moveTo: aPnt->x :aPnt->y] ;
  [aGlyph plant: theGlyph] ;
  inTarget = [self inTarget: theGlyph] ;
  aRect = cRect = theGlyph->frame ;
  [theGlyph->ancestor convertToRootGlyph: &cRect.origin] ;
  if(inTarget)
  { if(theGlyph->flags.isTarget)
    { // must update targetFrame and clipRect
      targetFrame = cRect ;
      [self clipRect: &clipRect toGlyph: aGlyph] ;    
    }
    else // in target, but not the target
    { [theGlyph->ancestor convert: &aRect.origin
         toGlyph: targetGlyph] ;
      [fGWin lockFocus] ;
      [fGWin erase: &aRect] ;
      [targetGlyph convertToAncestor: &aRect.origin] ;
      [targetGlyph display: &aRect] ;
      [targetGlyph convertFromAncestor: &aRect.origin] ;
      [fGWin unlockFocus] ;
    }
  }
  else
  { [bGWin lockFocus] ;
    if(targetGlyph != self)
      targetGlyph->flags.noDraw = YES ;
    [self display: &cRect] ;
    targetGlyph->flags.noDraw = NO ;
    [bGWin unlockFocus] ;
  }
  
  // now composite the groundViews to offscreen
  [glyphView lockFocus] ;
  if(wasLinked)
    [self drawSelf: &bRect :1] ;
  [self drawSelf: &cRect :1] ;
  [glyphView unlockFocus] ;
  // flush offscreen to screen
  [[glyphView window] flushWindow] ;
  [targetGlyph wasMoved] ;
  return self ;
}


- newTarget: (Glyph *) tg ;
{ // This is called whenever a new target is selected.
  // (Check that the target has actually changed before
  // sending this message, otherwise you're wasting cycles!) 
  char tmpStr[256] ;
  [targetGlyph resignTarget] ;
  // reset the targetGlyph ivars
  targetGlyph = tg ;
  tg->flags.isTarget = YES ;
  sprintf(tmpStr,"Targeting %s",[tg name]) ;
  [glyphView glyphMsg: tmpStr] ;
  // is the inspector onscreen?
  if([Nu inspecting])
  { instanceManager = [Nu instanceManager] ;
    [instanceManager instance: tg] ;
  }
  else
    instanceManager = nil ;
  // update the freezeButton to reflect target's state
  [[glyphView freezeButton] setState: targetGlyph->flags.isFrozen] ;
  [[[glyphView freezeButton] controlView] display] ;
  // life is easy if I am selected!
  if(tg == self) 
  { [[glyphView window] flushWindow] ;
    return self ;
  }
  else 
  { // calculate the new target frame (location of
    // new target in my coord system)...
    targetFrame = tg->frame ;
    [tg->ancestor convertToRootGlyph: &targetFrame.origin] ;
    // redraw background under targetFrame, without
    // redrawing the target Glyph itself...
    [bGWin erase: &targetFrame] ;
    [bGWin lockFocus] ;
    tg->flags.noDraw = YES ;
    [self display: &targetFrame] ;
    tg->flags.noDraw = NO ;
    [bGWin unlockFocus] ;
    // now draw the new target into the fGWin
    [fGWin sizeWindow: targetFrame.size.width
	       :targetFrame.size.height] ;
    [fGWin erase] ;
    [fGWin lockFocus] ;
    [tg display: &tg->frame] ;
    [fGWin unlockFocus] ;
    // repair offscreen window
    [glyphView lockFocus] ;
    [self clipRect: &clipRect toGlyph: tg->ancestor] ;
    [self drawSelf: &clipRect :1] ;
    [glyphView unlockFocus] ;
    [[glyphView window] flushWindow] ;
    return self ;
  }
}

- printSelf:(const NXRect *)rects :(int)rectCount ;
{ // this is invoked when I am asked to Print...
  if(targetGlyph == self)
    [self display: &frame] ;
  else
  { targetGlyph->flags.noDraw = 1 ;
    [self display: &frame] ;
    targetGlyph->flags.noDraw = 0 ;
    PSgsave() ;
    PStranslate(targetFrame.origin.x,targetFrame.origin.y) ;
    targetGlyph->flags.noDraw = 0 ;
    [targetGlyph display: &targetGlyph->frame] ;
    PSgrestore() ;
  }
  return self ;
}

- receive: (Glyph *) aGlyt inWin: (id) aWin  at: (NXPoint *) aPnt offset: (NXPoint *) offset ;
{ // add the Glyt to my TTree at aPnt, given in my coord sys, with the 
  // given mouse offset.  aWin is aGlyt's fGrnd window...we
  // replace our current fGWin with aWin, and make aGlyt the new target.
  Glyph * superG ;
  superG = [self hitTest: aPnt] ;
  if(superG) // this should always be true
  { NXRect aRect ;
    id oldWin  ;
    char tmpStr[80] ;
    sprintf(tmpStr,"Receiving %s",[aGlyt name]) ;
    [glyphView glyphMsg: tmpStr] ;
    [self resignTargetGlyph] ;
    aPnt->x -= offset->x ; // subtract mouse offset 
    aPnt->y -= offset->y ;
    // set the new target frame (location of
    // new target in my coord system)...
    targetFrame.origin = aGlyt->frame.origin = *aPnt ;
    targetFrame.size = aGlyt->frame.size ;
    [superG convertFromRootGlyph: &(aGlyt->frame.origin)] ;
    // now make aWin our new fGrndWin...
    oldWin = fGWin ;
    fGWin = aWin ;
    // make aGlyt our new target
    targetGlyph->flags.isTarget = NO ;
    targetGlyph = aGlyt ;
    aGlyt->flags.isTarget = YES ;
    [superG plant: aGlyt] ;
    // display the result...
    [glyphView lockFocus] ;
    PSgsave() ;
    [self clipRect: &clipRect toGlyph: superG] ;
    NXRectClip(&clipRect) ;
    aRect = targetFrame ;
    aRect.origin.x = aRect.origin.y = 0.0 ;
    [fGWin composite: NX_SOVER fromRect: &aRect toPoint: &targetFrame.origin] ;
    PSgrestore() ;
    [glyphView unlockFocus] ;
    [[glyphView window] flushWindow] ;
    [fGWin orderOut: self] ;
    // let the glyph know it was receveived by me
    [targetGlyph wasReceivedBy: self] ;
    // update the freezeButton to reflect target's state
    [[glyphView freezeButton] setState: targetGlyph->flags.isFrozen] ;
    [[[glyphView freezeButton] controlView] display] ;
    // finally, release our old fGrnd
    [oldWin free] ;
    [[glyphView window] setDocEdited: YES] ;
  }
  return self ;
}

- resignTargetGlyph ;
{ // the target Glyph is no longer the target: composite it
  // into the background. Notes: assumes targetGlyph is
  // not nil.  Makes self the new target.  Does not flush
  // the window, as resignTargetGlyph is usually followed by
  // a becomeTarget message, which will do the flushing.
 if(!targetGlyph->flags.isRoot)
  { // unhighlight the fGWin
    [fGWin setHighlighted: NO] ; 
    // composite visible portion of old target into bGrnd  
    [bGWin lockFocus] ;
    PSgsave() ;
    NXRectClip(&clipRect) ;
    [fGWin composite: NX_SOVER toPoint: &targetFrame.origin] ;
    PSgrestore() ;
    [bGWin unlockFocus] ;
    // restore highlighting of fGWin
    [fGWin setHighlighted: YES] ; 
    // repair the area in offscreen window
    [glyphView lockFocus] ;
    [bGWin composite: NX_COPY fromRect: &targetFrame toPoint: &targetFrame.origin] ;
    [glyphView unlockFocus] ;
  }
  targetGlyph->flags.isTarget = NO ;
  targetGlyph = self ;
  return self ;
}

- resizeTargetBy: (double) w :(double) h pile: (BOOL) pile ;
{ // resize the targetGlyph...multiply its width by x, its
  // height by h, send it the "didResize" message,and if(pile), 
  // then resize all of its subGlyphs
  // as well. Adjusts the hotSpot.
  NXRect repairRect ;
  [targetGlyph resizeBy: w :h pile: pile] ;
  repairRect = targetFrame ;
  targetFrame.size = targetGlyph->frame.size;
  if(targetGlyph == self) // rootGlyphs are special!
  { [glyphView sizeTo: targetGlyph->frame.size.width
                : frame.size.height] ;
    [bGWin sizeWindow:targetGlyph->frame.size.width
  		 :frame.size.height] ; 
    [[[glyphView superview] superview]
        getDocVisibleRect: &visibleRect] ;
    [self display: &frame from: nil] ;
    return self ;
  }  
  [fGWin sizeWindow: targetGlyph->frame.size.width
	:targetGlyph->frame.size.height] ;
  [fGWin erase] ;
  [fGWin lockFocus] ;
  [targetGlyph display: &targetGlyph->frame] ;
  [fGWin unlockFocus] ;
  [glyphView lockFocus] ;
  NXUnionRect(&targetFrame ,&repairRect) ;
  [self drawSelf: &repairRect :1] ;
  [glyphView unlockFocus] ;
  [[glyphView window] flushWindow] ;
  return self ;  
}

- rightMouseDown: (NXEvent *) anEvent ;
{ Glyph *tg ;
  char tmpStr[256] ;
  [glyphView convertPoint: &anEvent->location fromView: nil] ;
  tg = [self hitTest: &anEvent->location] ;
  if(tg != self) // clone tg
  { [self newTarget: [tg clone]] ;
    rootFlags.clone = YES ;
    sprintf(tmpStr,"%s cloned",[targetGlyph name]) ;
    [glyphView glyphMsg: tmpStr] ;
  }
  else // can't clone the root
  { [self newTarget: tg] ;
    return self ;
  }
  [targetGlyph rightMouseDown: anEvent] ; 
  // is the inspector onscreen?
  if([Nu inspecting])
  { instanceManager = [Nu instanceManager] ;
    [instanceManager instance: targetGlyph] ;
  }
  else
    instanceManager = nil ;
  [glyphView lockFocus] ;
  // set the clipping rectangle
  PSgsave() ;
  superGlyph = tg ;
  [self clipRect: &clipRect toGlyph: superGlyph] ;
  NXRectClip(&clipRect) ;
  // remove target glyph from TTree
  [targetGlyph unlink] ;
  // calculate new mouse offset
  mouseOffset.x = anEvent->location.x - targetFrame.origin.x ;
  mouseOffset.y = anEvent->location.y - targetFrame.origin.y ;
  rootFlags.isAWindow = NO ; // initially, glyph must be in our view
  [[glyphView window] setDocEdited: YES] ;
  return self ;
}

- rightMouseDragged: (NXEvent *) anEvent ;
{ return [self mouseDragged: anEvent] ;
}


- rightMouseUp: (NXEvent *) anEvent ;
{ [self mouseUp: anEvent] ;
  rootFlags.clone = NO ; // reset this flag
  return self ;
} 

- scan ;
{ int i ;
  [self clearArrays] ;
  return [super scan] ;
}

- setup ;
{ // inialize myself if I have just been created
  // or unarchived. 
  NXRect tinyRect = {{0.0,0.0},{1.0,1.0}} ;
  bGWin = [[objc_getClass("BGWindow") alloc] frame: &frame] ;
  [bGWin erase] ;
  fGWin = [[objc_getClass("FGWindow") alloc] frame: &tinyRect] ;
  targetFrame = frame ;
  clipRect = frame ;
  tray = superGlyph = nil ;
  targetGlyph = self ;
  glyphView = [Nu glyphView] ;
  [[[glyphView superview] superview] getDocVisibleRect: &visibleRect] ;
  return self ;
}


- sizeTo: (float) width :(float) height ;
{ // when I am resized, I must resize and repair the
  // bGWin
  NXRect aRect ;
  NXSize oldSize, newSize ;
  oldSize = frame.size ;
  [super sizeTo: width :height] ;
  newSize = frame.size ;
  [bGWin sizeWindow:newSize.width :newSize.height] ; 
  // This generates 2 rectangles which need to
  // be repaired in the bGWin....
  if(newSize.width > oldSize.width ||
       newSize.height > oldSize.height) 
  { if(targetGlyph != self)
      [self clipRect: &clipRect toGlyph: targetGlyph->ancestor] ;
    else
      clipRect = frame ;
    if(newSize.width > oldSize.width)
    { aRect.origin.x = oldSize.width ;
      aRect.origin.y = 0.0 ;
      aRect.size.width = newSize.width - oldSize.width ;
      aRect.size.height = newSize.height ;
      [self display: &aRect from: nil] ;
    }
    if(newSize.height > oldSize.height)
    { aRect.origin.x = 0.0 ;
      aRect.origin.y = oldSize.height ;
      aRect.size.width = oldSize.width ;
      aRect.size.height = newSize.height - oldSize.height ;
      [self display: &aRect from:nil] ;
   }
  }  
  // reset the "visibleRect"
  [[[glyphView superview] superview] getDocVisibleRect: &visibleRect] ;
  return self ;
}

- targetGlyph ;
{ return targetGlyph;
}

- trayTest: (Glyph *) selGlyph ;
{ // this is called when user shift-clicks a glyph
  NXRect aRect ;
  char tmpStr[80] ;
  [Nu loadIfNeeded: "Tray"] ;
  if(selGlyph == self)
  { // if selGlyph is me, I entray everything!
    Glyph *cursor ;
    tray = [[objc_getClass("Tray") alloc] init:1.0 :1.0] ;
    tray->frame = frame ;
    tray->is = is ;    
    tray->ancestor = self ;
    is = tray ;
    tray->flags.isFrozen = YES ;
    tray->flags.isTray = YES ;
    cursor = tray->is ;
    while(!cursor->flags.isTerminator)
    { cursor->ancestor = tray ;
      cursor = cursor->then ;
    }
    return tray ;
  } 
  if(selGlyph == tray)
    tray = nil ; // make sure we don't add a tray to itself!
  if(tray) // add selGlyph to existing tray... 
  { Glyph *cursor = tray ;
    // first, make sure selGlyph is not an ancestor of tray!
    while(!cursor->flags.isRoot) 
    { if(cursor == selGlyph) // then don't do anything...
      { sprintf(tmpStr,"Can't 'entray' %s",[selGlyph name]) ;
        [glyphView glyphMsg: tmpStr] ;
        return tray ;
      }
      else
        cursor = cursor->ancestor ;
    }
    // change selGlyph's origin to tray glyph's coords
    [[selGlyph ancestor] convertToRootGlyph: &selGlyph->frame.origin] ;
    [tray convertFromRootGlyph: &selGlyph->frame.origin] ;
    // now add it to the tray
    [selGlyph unlink] ;
    [tray plant: selGlyph] ;
    [tray enclose] ;
  } 
  else  // must create a tray and add selGlyph to it
  { id supGlyph ;
    aRect = selGlyph->frame ; 
    NXInsetRect(&aRect,-5.0,-5.0) ;   
    supGlyph = [selGlyph ancestor] ;
    tray = [[objc_getClass("Tray") alloc] init:1.0 :1.0] ;
    tray->frame = aRect ;
    [selGlyph unlink] ;
    [supGlyph plant: tray] ;
    selGlyph->frame.origin.x = selGlyph->frame.origin.y = 5.0 ;
    [tray plant: selGlyph] ;
    tray->flags.isFrozen = YES ;
    tray->flags.isTray = YES ;
  }
  sprintf(tmpStr,"%s 'entrayed'",[selGlyph name]) ;
  [glyphView glyphMsg: tmpStr] ;
  return tray ; 
}

-read: (NXTypedStream *) stream ;
{ int i,knt ;
  char *className ;
  int version ;
  // unarchive list of glyph subclasses needed
  NXReadType(stream,"i", (void *) &knt) ;
  for(i = 0 ; i < knt ; i++)
  { NXReadType(stream,"*",&className) ;
    [Nu loadIfNeeded: className] ;
  } 
  version = NXTypedStreamClassVersion(stream,[[self class] name]) ;
  // now unarchive myself
  [super read: stream] ;
  glyphView = NXReadObject(stream) ;
  NXReadType(stream,"s",(void *) &rootFlags) ;
  return self ;
}

-write: (NXTypedStream *) stream ;
{ int i,knt ;
  id aList ;
  const char *className ;
  // archive a list of all glyph subclasses needed
  // by my glyphs
  aList = [List new] ;
  [self addClassToListIfAbsent: aList] ; // recursive....
  knt = [aList count] ;
  NXWriteType(stream,"i",(void *) &knt) ;
  for(i = 0 ; i < knt ; i++)
  { className = [[aList objectAt: i] name] ;
    NXWriteType(stream,"*",&className) ; 
  } 
  [aList free] ;
  // now archive myself
  [super write: stream] ;
  NXWriteObjectReference(stream, glyphView) ;
  NXWriteType(stream,"s",(void *) &rootFlags) ;
  return self ;
}

@end

