/**************************************************
 * SynthBuilder
 * Copyright 1993 Nick Porcaro All Rights Reserved
 **************************************************/

/* #define DEBUG_BACKWARD 1 */
/* #define DEBUG_ERASE 1 */
/*
 * FakeUG.m
 * Eric Jordan, independent work, Spring, 1992
 *
 * Nick Porcaro, independent work, Summer 1993
 *
 * - Added new inspector.  
 *
 *   --> Arguments are updated when the user enters return
 *       (left as an excersise for later to do correctly)
 *
 *   --> The buttons next to each field either open a slider
 *       or an envelope editor
 */

#import <stdlib.h>
#import <stdio.h>
#import <sys/file.h>
#import <appkit/Application.h>
#import <appkit/Matrix.h>
#import <ctype.h>
#import <dpsclient/psops.h>
#import <dpsclient/wraps.h>
#import <appkit/publicWraps.h>
#import <strings.h>
#import <musickit/musickit.h>
#import <musickit/unitgenerators/AsympUG.h>
#import <appkit/TextField.h>
#import "FakeSynthPatch.h"
#import "FakePatchPoint.h"
#import "Controller.h"
#import "UGDef.h"
#import "UGArgDef.h"
#import "UGMethodDef.h"
#import "PatchParam.h"
#import "Clavier.h"
#import "UGArgSlider.h"
#import "EnvDelegate.h"
#import "EnvController.h"
#import "FakeUG.h"
#import "Utilities.h"
#import "ElementController.h"
#import "Controller.h"

static char ErrorMsg[1024];
extern char *getAppName();

#define NODESIZE 10
@implementation FakeUG

static char TempString[1024];  
static int uniquename = 0;
+ initialize
 /* Set the version. This can be used in a later version to distinguish older
  * formats when unarchiving documents. 
  */
{
  [FakeUG setVersion:5];
  return self;
}

- awakeFromNib
{
  clavInit = NO;
  clickCount = 0;
  return self;
}

- init
{
  int i;
  [super init];
  allocOrder = 1;
  for (i=0;i<MAXCONNECTIONS;i++)
    {
      connections[i]=nil;
    }

  for (i=0;i<MAXNOTEPARAMETERS;i++)
    {
      arg[i] = NULL;
      envDelegate[i] = nil;
      argEnvelope[i] = nil;
      argEnabled[i] = YES;
      slider[i] = nil;
    }

  isAllocated = NO;
  strcpy(theName, "");
  clavier = nil;
  midi = nil;
  noteFilter = nil;
  patchParam = nil;
  clickCount = 0;
  useInspectorRect = NO;
  inspectorRectJustLoaded = NO;
  return self;
}

- takeArgEnableFrom:sender
{
  argEnabled[currArg] = ([sender intValue] ? YES : NO);
  return self;
}

- (BOOL) isArgEnabled:(int) num
{
  BOOL rval;

  if ((num >=0) && (num < MAXNOTEPARAMETERS))
    {
      rval = argEnabled[num];
    }
  else
    {
      /* Request was out of bounds */
      rval = NO;
    }

  return rval;
}

- takeAllocOrderFrom:sender
{

  int button, order, ival, maxOrder;
  
  maxOrder = [[theSP getFakeUGs] count];
  order = [self getAllocOrder];

  if ([sender isKindOfClassNamed: "TextField"])
    {
      ival = [sender intValue];
      if ((ival <= maxOrder) && (ival >=1))
	{
	  [self setAllocOrder:order];
	  [theSP sortFakeUGsByAllocOrder];
	  [self updateAllocOrder];
	}
      else
	{
	  NXRunAlertPanel(getAppName(), "Invalid order: %d Must be between 1 and %d",
			  NULL, NULL, NULL, ival, maxOrder);
	  [sender setIntValue:order];
	  return self;
	}
    }
  else
    {
      /* Get order from switcher */

      /* button = -1 for top, 1 for bottom */
      button = [[sender selectedCell] tag];
      order += button;
      
      /* Only set a new order if it's within range */
      if ((order <= maxOrder) && (order >= 1))
	{
	  [self setAllocOrder:order];
	  [theSP sortFakeUGsByAllocOrder];
	  [self updateAllocOrder];
	}
    }

  return self;
}

- setAllocOrder:(int) order
{
  allocOrder = order;
  return self;
}

- (int) getAllocOrder
{
  return allocOrder;
}

- updateAllocOrder
{
  if (argCount > 0)
    {
      [allocOrderField setIntValue:allocOrder];
    }
  else
    {
      [allocOrderField2 setIntValue:allocOrder];
    }

  return self;
}

- setSP:aSP
{
  theSP = aSP;
  return self;
}

- getSP
{
  return theSP;
}

- initWithDef: aUGDef WithPatch:aSP
{
  [self setSP: aSP];
  [self initUGDef: aUGDef];
  [self initSubviews];
  [self setDefaultValues];
  [self validate];
  return self;
}

- initUGDef: aUGDef
{
  theUGDef = aUGDef;
  theMethods = [theUGDef methods];
  methodCount = [theMethods count];
  theArgs = [theUGDef arguments];
  argCount = [theArgs count];

  sprintf(theName, "%c%s%i", tolower([self getTypeString][0]),
	  [self getTypeString]+1, uniquename++);

  theImage = [NXImage findImageNamed:[theUGDef getIconFile]];
  if (!theImage)
    {
      sprintf(TempString, "Cannot find image for: %s", [theUGDef getName]);
      NXRunAlertPanel(getAppName(), TempString, NULL, NULL, NULL);
      return nil;
    }

  return self;
}

- setDefaultValues
/* This assumes you called initUGDef first */
{
  int i;
  int len;
  char *aStr;
  UGArgDef *anArg;


  if (! [theSP useDefaultArgValues])
    {
      return self;
    }

  /* Set the default parameter values
   * and the right stuff in the inspector
   */
  for (i=0; i<argCount; i++)
    {
      anArg = [theArgs objectAt:i];
      aStr = [anArg defaultValue];
      if (aStr)
	{
	  len = strlen(aStr);
	  len += 1;
	  if (arg[i])
	    {
	      NXZoneFree([self zone], arg[i]);
	    }
	  
	  /*ME* Observed malloc error here on load: old save new, load old */
	  if (NXMallocCheck())
	    {
	      printf("malloc error!!!!\n");
	    }
	  arg[i] = (char *) NXZoneCalloc([self zone], len, sizeof(char));
	  strcpy(arg[i], aStr);
	}
    }

  return self;
}

- initSubviews
/* This assumes you called initUGDef first */
{
  int i;
  UGMethodDef *aMethod;

  for (i=0; i<methodCount; i++)
    {
      NXRect *newrect = (NXRect *) NXZoneMalloc([self zone], sizeof (*newrect));
      id newnode = [[NodeView alloc] init];
      [newnode setPatch:[self thePatch]];
      aMethod = [theMethods objectAt:i];
      [newnode setTag:i];
      [self addSubview:newnode];
      NXSetRect(newrect, 
		[aMethod xCoord] - NODESIZE/2, 
		[aMethod yCoord] - NODESIZE/2,
		NODESIZE,NODESIZE);

#ifdef DEBUG_PARAM
      DEBUG_PRINT("fakeUG %s:  Created node with tag %d at x: %d y: %d\n",
		  [self getTypeString], [newnode getTag],
		  [aMethod xCoord], [aMethod yCoord]);
#endif DEBUG_PARAM


      [newnode setFrame:newrect];
      [newnode display];
    }

  return self;
}

- checkSubviews
/* This ensures the subview indices are equal to the NodeView tags 
 * This was a problem with old files
 */
{
  int i;
  int nodeViewCount;
  id nodeView;

  nodeViewCount = [subviews count];
  if (nodeViewCount != methodCount)
    {
      sprintf(TempString, "Internal error file: %s line: %d", __FILE__, __LINE__);
      NXRunAlertPanel(getAppName(), TempString, NULL, NULL, NULL);
      return self;
    }

  for (i=0; i<nodeViewCount; i++)
    {
      nodeView = [subviews objectAt:i];
      if ( ![nodeView isKindOfClassNamed: "NodeView"])
	{
	  sprintf(TempString, "Internal error file: %s line: %d", __FILE__, __LINE__);
	  NXRunAlertPanel(getAppName(), TempString, NULL, NULL, NULL);
	  return self;
	}
      [nodeView setTag:i];
    }

  return self;
}


- displayArg:(int) i
{
  [argName setStringValue:[[[theUGDef arguments] objectAt:i] getName]];

  if ([self getArgType:i] == PARTIALS)
    {
      /* Can't inspect partials yet */
      [argButton setEnabled:NO];
    }
  else
    {
      [argButton setEnabled:YES];
    }

  if ([self getArgType:i] == ENV)
    {
      /* Don't let the user futs with the envelope field */
      [argVal setEditable:NO];
      [argVal setSelectable:YES];
    }
  else
    {
      [argVal setEnabled:YES];
      [argVal setEditable:YES];
    }

  if (! arg[i])
    {
      [argVal setStringValue: ""];
    }
  else
    {
      [argVal setStringValue: arg[i]];
    }

  if (argCount > 0)
    {
      [argIndexField setIntValue:i+1];
    }

  if (slider[i])
    {
      [slider[i] takeSliderValueFrom:argVal];
    }

  if (argEnabled[i])
    {
      [argEnabledSwitch setIntValue:1];
    }
  else
    {
      [argEnabledSwitch setIntValue:0];
    }

  return self;
}


- checkConnections
{
  int i, j, k;
  id temp_connections[MAXCONNECTIONS];
  id theToNodes;

  /* Older versions allowed connections[0] 
   * to be non-nil, and the newer versions
   * don't. Deal with this by re-organizing
   * the connections array so it has no empty slots
   * before non-empty slots
   */

  for (i=0; i<MAXCONNECTIONS; i++)
    {
      temp_connections[i] = nil;
    }

  j = 0;
  for (i=0; i<MAXCONNECTIONS; i++)
    {
      if (connections[i])
	{
	  temp_connections[j++] = connections[i];
	}
    }

  if (j > 0)
    {
#ifdef DEBUG_BACKWARD
      printf("** Reprocessing %d connections for: %s\n\n", j, [self getName]);
#endif DEBUG_BACKWARD

      for (i=0; i<j; i++)
	{
	  connections[i] = temp_connections[i];
	  theToNodes = [connections[i] getToNodes];

#ifdef DEBUG_BACKWARD
	  printf("$$ Set connection[%d] = temp_connection[%d]: %s ToNode ct: %d\n", 
		 i, i, [connections[i] getName],
		 (int) [theToNodes count]);
#endif DEBUG_BACKWARD

	  for (k=0; k<[theToNodes count]; k++)
	    {
#ifdef DEBUG_BACKWARD
	      printf(">> Node: %d tag: %d FakeUG: %s\n",
		     k, 
		     [[theToNodes objectAt:k] getTag],
		     [[[theToNodes objectAt:k] superview] getName]);
#endif DEBUG_BACKWARD
	    }
	}

      for (i=j; i<MAXCONNECTIONS; i++)
	{
	  connections[i] = nil;
#ifdef DEBUG_BACKWARD
	  printf("nilling connection: %d\n", i);
#endif DEBUG_BACKWARD
	}
    }

  [self checkSubviews];

#ifdef DEBUG_BACKWARD
  [self printConnections];
#endif DEBUG_BACKWARD
  return self;
}

- printConnections
{
  int i, j;
  id theToNodes, toFakeUG;

  for (i=0; i<MAXCONNECTIONS; i++)
    {
      if (connections[i])
	{
	  printf("%s:  Connection: %d from: %s (tag %d) to:",
		 [self getName], 
		 i, 
		 [[connections[i] getFromFakeUG] getName],
		 [[connections[i] getFromNode] getTag]);

	  theToNodes = [connections[i] getToNodes];
	  for (j=0; j<[theToNodes count]; j++)
	    {
	      toFakeUG = [[theToNodes objectAt:j] superview];
	      printf(" %s tag: %d", 
		     [toFakeUG getName],
		     [ [theToNodes objectAt:j] getTag]);
	    }
	  printf("\n");
	}
    }

  return self;
}


- validate
/* Make sure we are together 
 * This is useful to do if we have
 * just loaded an existing file that may
 * be out of date, and in the case where the fakeUG
 * is a Clavier or PatchParameter
 */
{
  int aType;

  isSoundMaker = NO;
  if (!theUGDef || ![theUGDef isKindOfClassNamed: "UGDef"])
    {
      sprintf(TempString, "Internal error file: %s line: %d", __FILE__, __LINE__);
      NXRunAlertPanel(getAppName(), TempString, NULL, NULL, NULL);
      return self;
    }

  aType = [theUGDef getMetaType];

  /* Check for consistency
   */
  if ( (aType != PP_UG) && (aType != CLAV_UG) && (aType != MIDI_UG))
    {
      /* A little paranoia from a previous bug */
      if (!theUGDef || ![theUGDef isKindOfClassNamed: "UGDef"])
	{
	  sprintf(TempString, "Internal error file: %s line: %d", __FILE__, __LINE__);
	  NXRunAlertPanel(getAppName(), "Internal error file: %s line: %d", NULL, NULL, NULL);
	  return self;
	}
    }

  theMethods = [theUGDef methods];
  methodCount = [theMethods count];
  theArgs = [theUGDef arguments];
  argCount = [theArgs count];

  switch (aType)
    {
      case (MK_UG):
	{
	  isSoundMaker = YES;
	  if (argCount > 0)
	    {
	      currArg = 0;
	    }
	  break;   /* Just a regular audio UG */
	}
      case (CLM_UG):
	{
	  NXRunAlertPanel(getAppName(), 
			  "Sorry I cannot deal with CLM UGs yet", 
			  NULL, NULL, NULL);
	  break;
	}
      case (PP_UG):
	{
	  /* Allocate a PatchParameter if necessary
	   * When the user double clicks on the icon
	   * the editor scene will "just happen" because
	   * we allocate all the cool stuff here.
	   */
	  if (! patchParam)
	    {
	      /* New PP */
	      patchParam = [[PatchParam alloc] init];
	      [patchParam setPatch:[self thePatch]];
	      [patchParam setViewFakeUG:self];
              [patchParam setPatchParamName:theName];
	    }
	  else
	    {
	      /* PP just read from file */
	      [patchParam setPatch:[self thePatch]];
	      [patchParam setViewFakeUG:self];
	    }
	  break;
	}
      case (CLAV_UG):
	{
	  if (! clavier)
	    {
	      clavier = [[Clavier alloc] init];
	      [clavier initConnectionWithPatch:[self thePatch] WithFakeUG: self];
	      [clavier setClavierName:theName];
	    }
	  break;
	}
      case (MIDI_UG):
	{
	  midi = [theSP midi];
	  inspector = nil;
	  noArgInspector = nil;
	  break;
	}
      default:
	{
	  break;
	}
      }

  return self;
}

- getImage
{
  return theImage;
}

- (BOOL)getErasing
{
  return erasing;
}

- (char *)getName
{
  return theName;
}

- changeName:(char *)aName
{
  int len;

  if (! [[theSP utilities] checkName:aName])
    {
      [fakeUGName setStringValue:theName];
      return nil;
    }

  len = strlen(aName) + 1;
  strcpy(theName, aName);
  [self setInspectorName:theName];
  return self;
}

- takeNameFrom:sender
{
  [self changeName: (char *)[sender stringValue]];
  return self;
}

- (char *)getTypeString

/* This returns a string that indentifies
 * the unit generator class used to make sound for this FakeUG
 * The name of the class is based on the root class name
 * plus "x" if its connections (other than data connections)
 * are from x memory, and "y" if they are in y-memory.
 */
{
  int i;
  static char s[1024];


  /* There is a special case for Biquads.  They're output
   * must be y memory, and they only have leaf classes
   * with a single x or y.  Hence, the only issue
   * is what memory location the input of the Biquad uses.
   * If it uses x memory, return BiquadUGx, else return BiquadUGy.
   */

  strcpy(s, [theUGDef getType]);
  if (strcmp(s, "BiquadUG") == 0)
    {
      if (! connections[0] && ! connections[1])
	{
	  /* This means the biquad is unconnected */
	  return "BiquadUG";
	}
      else if ([connections[0] getXMemory] || [connections[1] getXMemory])
	{
	  return "BiquadUGx";
	}
      else
	{
	  return "BiquadUGy";
	}
    }

  for (i=0; i<[theMethods count]; i++)
    {
      if ((connections[i] != nil) && ![self isData])
	{
	  if ([connections[i] getXMemory])
	    {
	      sprintf(s,"%sx",s);
	    }
	  else
	    {
	      sprintf(s,"%sy",s);
	    }
	}
    }

  if ([theUGDef isADSwitch] && connections[1] && connections[2])
    {
      s[strlen(s)-1]='\0';
    }
  
  return s;
}

- (char *)getGenericTypeString
{
  return [theUGDef getType];
}

- (BOOL)isAFakeUG
{
  /* We have to tell the recepient of a message that uses
   * a FakeUG object that the object is in fact a FakeUG (late binding in action!)
   */
  return YES;
}

- (BOOL)isAFakePatchPoint
{
  return NO;
}

- (char *)getConnectionName:(int)connectionNumber
{
  UGMethodDef *aMethod;

  aMethod = [theMethods objectAt:connectionNumber];
  return [aMethod getName];
}

- setConnection:(int)connectionNumber toFakePatchPoint:fakePatchPoint
{
  connections[connectionNumber] = fakePatchPoint;
  return self;
}

- getConnection:(int)connectionNumber
{
  return connections[connectionNumber];
}

- getUG
{
  return ug;
}

- (int)getArgType:(int)i
{
  return [[theArgs objectAt:i] argType];
}

- allocateUGMK
{
  if (isAllocated)
    {
#ifdef DEBUG_PARAM
      DEBUG_PRINT("ug: %s already allocated\n", [self getName]);
#endif DEBUG_PARAM

      return self;
    }

  if (![self isPatchParameter] &&
      ![self isMidi] &&
      ![self isClavier] &&
      ![self isData])
    {
      ug = [[superview getOrch]
	  allocUnitGenerator:objc_getClass([self getTypeString])];
      if (! ug)
	{
	  sprintf(ErrorMsg,
		  "Is DSP being used by another app? Could not allocate %s",
		  [self getName]);

	  NXRunAlertPanel(getAppName(), ErrorMsg, NULL, NULL, NULL);
	  return nil;
	}

#ifdef DEBUG_PARAM
      DEBUG_PRINT("*** ug: %s allocated\n", [self getName]);
#endif DEBUG_PARAM

      isAllocated = YES;
    }

  return self;
}

- runMK
{
  if (isSoundMaker)
    {
      [ug run];
    }

  return self;
}

- deallocUGMK
{
  if (isSoundMaker)
    {
      [ug finish];
      [ug dealloc];
      ug = nil;
      isAllocated = NO;
    }

  return self;
}

- (BOOL)isEnvelopeHandler
{
  return [theUGDef isEnvelopeHandler];
}

- (BOOL)isDSwitch
{
  return [theUGDef isADSwitch];
}

- (BOOL)isData
{
  return [theUGDef isData];
}

- (BOOL)isStorage
{
  return [theUGDef isStorage];
}

- (BOOL)isOscg
{
  return [theUGDef isOscg];
}

- (BOOL)isSineROM
{
  return [theUGDef isSineROM];
}

- (int)getLength
{
  return ([theUGDef hasLength] ?
	  ((arg[0]==NULL) ? 0 : atoi(arg[0])) :
	  16);
}

- (BOOL)hasLength
{
  return [theUGDef hasLength];
}

- (BOOL)hasConstant
{
  if (! [theUGDef hasConstant])
    return NO;

  if (![self getConstant])
    return NO;

  return YES;
}

- (char *)getConstant
{
  if ([theUGDef hasLength])
    return arg[1];
  else
    return arg[0];
}

- setConstantPP:(double) value
{
  int i;
  id aPP;

  /* Set the constant for each connection to this
   * constant PP to value
   */
  for (i=0; i<MAXCONNECTIONS; i++)
    {
      if (connections[i])
	{
	  aPP = [connections[i] getpp];
	  if (aPP)
	    {
	      [aPP setToConstant: DSPDoubleToFix24(value)];
	    }
	}
    }

  return self;
}

- (BOOL)isComplete
/* Returns YES if the fakeUG has all its connections made */
{
  int i;
  BOOL rval = YES;

  if (isSoundMaker)
    {
      for (i=0; i<methodCount; i++)
	{
	  if (!connections[i])
	    {
	      rval = NO;
	    }
	}
    }
  else
    {
      rval = NO;
    }

  return rval;
}

- (BOOL)acceptsFirstMouse
{
  return YES;
}

- (BOOL)acceptsFirstResponder
{
  return NO;
}

- drawSelf:(BOOL)instance
{
  NXPoint *thePoint = (NXPoint *)NXZoneMalloc([self zone], sizeof(*thePoint));
  int i;
  thePoint->x = thePoint->y = 0.0;
  if ([superview getSelected]==self)
    [theImage composite:NX_COPY toPoint:thePoint];
  else
    [theImage composite:NX_SOVER toPoint:thePoint];
  [self unlockFocus];
  for (i=0; i<MAXCONNECTIONS; i++)
    if (connections[i]!=nil)
      {
	[connections[i] lockFocus];
	if (instance)
	  PSsetinstance(YES);
	[connections[i] drawSelf:NULL :0];
	if (instance)
	  PSsetinstance(NO);
	[connections[i] unlockFocus];
      }
  [self lockFocus];
  return self;
}

- eraseSelf
{
  int i;
  NXRect *rect = (NXRect *)NXZoneMalloc ([self zone], sizeof (*rect));
  rect->origin.x = frame.origin.x;
  rect->origin.y = frame.origin.y;
  rect->size.width = frame.size.width;
  rect->size.height = frame.size.height;
  [self convertRectFromSuperview:rect];
  PSsetgray(NX_LTGRAY);
  NXRectFill(rect);
  for (i=0; i<MAXCONNECTIONS; i++)
    if (connections[i]!=nil)
      {
	[self unlockFocus];
	[connections[i] lockFocus];
	[connections[i] eraseSelf];
#ifdef DEBUG_ERASE
	printf("(FakeUG %s - eraseSelf) Erased connection %d pp: %s\n", 
	       [self getName], i, [connections[i] getName]);
#endif DEBUG_ERASE
	[connections[i] unlockFocus];
	[self lockFocus];
      }
  erasing = YES;
  [self unlockFocus];
  [superview display];
  [self lockFocus];
  erasing = NO;
  return self;
}

- closeInspector
{
  int i;

  for (i=0; i<MAXNOTEPARAMETERS; i++)
    {	
      if (slider[i])
	{
	  [slider[i] closeInspector];
	}

      if (envDelegate[i])
	{
	  [envDelegate[i] closeInspector];
	}
    }
	
  if ( (argCount > 0) && inspector)
    {
      [inspector performClose:self];
    }
  else if (noArgInspector)
    {
      [noArgInspector performClose:self];
    }

  if ([self isPatchParameter])
    {
      [patchParam closeInspector];
    }

  if ([self isClavier])
    {
      [clavier closeInspector];
    }

  inspectorDisplayed = NO;
  return self;
}

- removeSelf
{
  int i;
  [self lockFocus];
  [self closeInspector];
  [self eraseSelf];
  [self unlockFocus];

  for (i=0; i<MAXCONNECTIONS; i++)
    if (connections[i]!=nil)
      {
	id temp = connections[i];
	[temp removeSelf];
	[temp free];
      }

  /* Now fix the envelopes
   * The next time an envelope inspector comes 
   * up it will use the new envelope we 
   * just read in with a new envDelegate
   */
  for (i=0; i<MAXNOTEPARAMETERS; i++)
    {
      if (envDelegate[i])
	{
	  [envDelegate[i] free];
	  envDelegate[i] = nil;
	}

      if (argEnvelope[i])
	{
	  [argEnvelope[i] free];
	  argEnvelope[i] = nil;
	}
    }

  if (patchParam)
    {
      [patchParam free];
    }
  
  if ([theUGDef isMidi])
    {
      [theSP setHaveMidi:NO];
    }	

  [superview remFakeUGFromList:self];
  return self;
}

- remConnection:(int)i
{
  connections[i]=nil;
#ifdef DEBUG_ERASE
  printf("(FakeUG %s - remConnection) Nilled connection %d\n", 
	 [self getName], i);
#endif DEBUG_ERASE
  return self;
}

- nilConnectionsWithId:aFakePatchPoint
{
  int i;

  for (i=0; i<MAXCONNECTIONS; i++)
    {
      if (connections[i] && (connections[i] == aFakePatchPoint))
	{
	  connections[i] = nil;
	}
    }

  return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
  if (!erasing)
    {
      [self drawSelf:NO];
      NXPing();
    }
  return self;
}

- mouseDown:(NXEvent *)theEvent
{
  mouseUpRespond = YES;
  [window addToEventMask:NX_LMOUSEDRAGGEDMASK];
  noMovement = YES;
  whereGrabbed = theEvent->location;
  [self convertPoint:&whereGrabbed fromView:nil];
  return self;
}

- mouseDragged:(NXEvent *)theEvent
{
  NXPoint *location = (NXPoint *) NXZoneMalloc([self zone], sizeof (*location));
  NXRect *rect = (NXRect *)NXZoneMalloc ([self zone], sizeof (*rect));
  NXRect limits;

  /* When the user drags on a FakeUG this method gets called twice
   * the first time it's called noMovement = YES, the second time it gets
   * set to NO
   */
  if (noMovement)
    {
      /* This code just draws the fake UG in the same place it was before
       * the drag 
       */
      noMovement = NO;
      [self lockFocus];
      [self eraseSelf];
      PSnewinstance();
      PSsetinstance(YES);
      [self drawSelf:YES];
      PSsetinstance(NO);
      [self unlockFocus];
    }

  location->x = theEvent->location.x;
  location->y = theEvent->location.y;
  [superview convertPoint:location fromView:nil];
  [superview getBounds:&limits];

  if ((location->x >= limits.origin.x) && 
      (location->y >= limits.origin.y) && 
      (location->x <= limits.size.width+limits.origin.x) &&
      (location->y <= limits.size.height+limits.origin.y))
    {
      [self convertPoint:location fromView:superview];
      location->x = location->x-whereGrabbed.x;
      location->y = location->y-whereGrabbed.y;
      [superview convertPoint:location fromView:self];
      [self moveTo:location->x :location->y];
      [self lockFocus];
      PSnewinstance();
      PSsetinstance(YES);
      /* After this line gets done the terd get's left over */
      [self drawSelf:YES];
      PSsetinstance(NO);
      [self unlockFocus];
      NXPing();
      [window flushWindow];
    }
  return self;
}

- mouseUp:(NXEvent *)theEvent
{
  NXPoint *location = (NXPoint *) NXZoneMalloc ([self zone], sizeof (*location));
  NXRect *rect = (NXRect *)NXZoneMalloc ([self zone], sizeof (*rect));
  NXRect limits;
  if (mouseUpRespond)
    {
      mouseUpRespond = NO;
      if (noMovement)
	[superview setSelected:self];
      else
	{
	  NXPing();
	  location->x = theEvent->location.x;
	  location->y = theEvent->location.y;
	  [superview convertPoint:location fromView:nil];
	  [superview getBounds:&limits];
	  if ((location->x>=limits.origin.x) && 
	      (location->y>=limits.origin.y) && 
	      (location->x<=limits.size.width+limits.origin.x) &&
	      (location->y<=limits.size.height+limits.origin.y))
	    {
	      [self convertPoint:location fromView:superview];
	      location->x = location->x-whereGrabbed.x;
	      location->y = location->y-whereGrabbed.y;
	      [superview convertPoint:location fromView:self];
	      [self moveTo:location->x :location->y];
	    }
	  [self lockFocus];
	  [self drawSelf:NO];
	  PSnewinstance();
	  [self unlockFocus];
	  NXPing();
	}
      [window removeFromEventMask:NX_LMOUSEDRAGGEDMASK];
      [window flushWindow];
      [superview display];
    }
  return self;
}
 
- getInspectorRect:anInspector
{
  if (anInspector)
    {
      useInspectorRect = YES;
      [anInspector getFrame:&inspectorRect];
    }
  else
    {
      useInspectorRect = NO;
      inspectorRect.origin.x = -1;
      inspectorRect.origin.y = -1;
      inspectorRect.size.width = -1;
      inspectorRect.size.height = -1;
    }

  return self;
}

- write:(NXTypedStream *) stream
{
  char *aName;
  int i;

  [super write:stream];
  NXWriteTypes(stream, "i", &allocOrder);
  NXWriteTypes(stream, "@", &theUGDef);
  NXWriteArray(stream, "@", MAXCONNECTIONS, &connections);
  NXWriteArray(stream, "*", MAXNOTEPARAMETERS, &arg);
  NXWriteArray(stream, "c", MAXNOTEPARAMETERS, &argEnabled);
  NXWriteTypes(stream, "@", &theImage);
  aName = theName;
  NXWriteTypes(stream, "*", &aName);
  NXWriteTypes(stream, "@", &patchParam);
  NXWriteTypes(stream, "@", &clavier);

  /* Make sure we have the latest envelope happening */
  for (i=0; i<MAXNOTEPARAMETERS; i++)
    {
      if ([self getArgType:i] == ENV)
	{
	  [self syncEnvelope:i];
	  argEnvelope[i] = [envDelegate[i] envelope];
	}
    }

  NXWriteArray(stream, "@", MAXNOTEPARAMETERS, &argEnvelope);
  NXWriteArray(stream, "@", MAXNOTEPARAMETERS, &slider);

  if (argCount > 0)
    {
      [self getInspectorRect:inspector];
    }
  else
    {
      [self getInspectorRect:noArgInspector];
    }
  
  NXWriteTypes(stream, "d", &inspectorRect.origin.x);
  NXWriteTypes(stream, "d", &inspectorRect.origin.y);
  NXWriteTypes(stream, "d", &inspectorRect.size.width);
  NXWriteTypes(stream, "d", &inspectorRect.size.height);
  NXWriteTypes(stream, "c", &useInspectorRect);
  NXWriteTypes(stream, "c", &inspectorDisplayed);
  return self;
}

- read:(NXTypedStream *) stream
{
  int version, old_ug_type, i, len;
  char *old_arg[9], *old_name;
  id elementController;
  [super read:stream];
  isAllocated = NO;
  clavier = nil; 
  midi = nil;
  allocOrder = 1;

  if (! theSP)
    {
	theSP = [[Controller theController] theSP];
    }

  version = NXTypedStreamClassVersion(stream, "FakeUG");
  useInspectorRect = NO;
  inspectorRectJustLoaded = NO;
  switch (version)
    {
      case(5):
	{
	  NXReadTypes(stream, "i", &allocOrder);
	  NXReadTypes(stream, "@", &theUGDef);
	  NXReadArray(stream, "@", MAXCONNECTIONS, &connections);
	  NXReadArray(stream, "*", MAXNOTEPARAMETERS, &arg);
	  NXReadArray(stream, "c", MAXNOTEPARAMETERS, &argEnabled);
	  NXReadTypes(stream, "@", &theImage);
	  NXReadTypes(stream, "*", &old_name);
	  NXReadTypes(stream, "@", &patchParam);
	  NXReadTypes(stream, "@", &clavier);
	  NXReadArray(stream, "@", MAXNOTEPARAMETERS, &argEnvelope);
	  NXReadArray(stream, "@", MAXNOTEPARAMETERS, &slider);
	  NXReadTypes(stream, "d", &inspectorRect.origin.x);
	  NXReadTypes(stream, "d", &inspectorRect.origin.y);
	  NXReadTypes(stream, "d", &inspectorRect.size.width);
	  NXReadTypes(stream, "d", &inspectorRect.size.height);
	  NXReadTypes(stream, "c", &useInspectorRect);
	  NXReadTypes(stream, "c", &inspectorDisplayed);
	  inspectorRectJustLoaded = YES;

	  if (old_name)
	    {
	      strcpy(theName, old_name);
	    }
	  else
	    {
	      strcpy(theName, "");
	    }
//	  [self validate];
	  break;
	}
      case(4):
	{
	  NXReadTypes(stream, "i", &allocOrder);
	  NXReadTypes(stream, "@", &theUGDef);
	  NXReadArray(stream, "@", MAXCONNECTIONS, &connections);
	  NXReadArray(stream, "*", MAXNOTEPARAMETERS, &arg);
	  NXReadArray(stream, "c", MAXNOTEPARAMETERS, &argEnabled);
	  NXReadTypes(stream, "@", &theImage);
	  NXReadTypes(stream, "*", &old_name);
	  NXReadTypes(stream, "@", &patchParam);
	  NXReadTypes(stream, "@", &clavier);
	  NXReadArray(stream, "@", MAXNOTEPARAMETERS, &argEnvelope);
	  NXReadTypes(stream, "d", &inspectorRect.origin.x);
	  NXReadTypes(stream, "d", &inspectorRect.origin.y);
	  NXReadTypes(stream, "d", &inspectorRect.size.width);
	  NXReadTypes(stream, "d", &inspectorRect.size.height);
	  NXReadTypes(stream, "c", &useInspectorRect);
	  NXReadTypes(stream, "c", &inspectorDisplayed);
	  inspectorRectJustLoaded = YES;

	  if (old_name)
	    {
	      strcpy(theName, old_name);
	    }
	  else
	    {
	      strcpy(theName, "");
	    }
//	  [self validate];
	  break;
	}
      case(3):
	{
	  NXReadTypes(stream, "i", &allocOrder);
	  NXReadTypes(stream, "@", &theUGDef);
	  NXReadArray(stream, "@", MAXCONNECTIONS, &connections);
	  NXReadArray(stream, "*", MAXNOTEPARAMETERS, &arg);
	  NXReadArray(stream, "c", MAXNOTEPARAMETERS, &argEnabled);
	  NXReadTypes(stream, "@", &theImage);
	  NXReadTypes(stream, "*", &old_name);
	  NXReadTypes(stream, "@", &patchParam);
	  NXReadTypes(stream, "@", &clavier);
	  NXReadArray(stream, "@", MAXNOTEPARAMETERS, &argEnvelope);
	  if (old_name)
	    {
	      strcpy(theName, old_name);
	    }
	  else
	    {
	      strcpy(theName, "");
	    }
//	  [self validate];
	  break;
	}
      case(2):
	{
	  NXReadTypes(stream, "i", &allocOrder);
	  NXReadTypes(stream, "@", &theUGDef);
	  NXReadArray(stream, "@", MAXCONNECTIONS, &connections);
	  NXReadArray(stream, "*", MAXNOTEPARAMETERS, &arg);
	  NXReadTypes(stream, "@", &theImage);
	  NXReadTypes(stream, "*", &old_name);
	  NXReadTypes(stream, "@", &patchParam);
	  NXReadTypes(stream, "@", &clavier);
	  NXReadArray(stream, "@", MAXNOTEPARAMETERS, &argEnvelope);
	  if (old_name)
	    {
	      strcpy(theName, old_name);
	    }
	  else
	    {
	      strcpy(theName, "");
	    }

	  for (i=0; i<MAXNOTEPARAMETERS; i++)
	    {
	      argEnabled[i] = YES;
	    }

//	  [self validate];
	  break;
	}
      default:
        {
	  /* Old versions didn't have versioning */
	  NXReadArray(stream, "@", 5, &connections);

	  for (i=0; i<9; i++)
	    {
	      old_arg[i] = NULL;
	    }

	  NXReadArray(stream, "*", 9, &old_arg);
	  NXReadTypes(stream, "@i*ccc",
		      &theImage, &old_ug_type, &old_name, &noMovement, &erasing,
		      &mouseUpRespond);

	  if (old_name)
	    {
	      strcpy(theName, old_name);
	    }
	  else
	    {
	      strcpy(theName, "");
	    }

	  /* Convert typeOfFakeUG to a UGDef 
	   * This will set all the default values in theUGDef
	   */
	  elementController = [[theSP theController] elementController];
	  [self initUGDef: [elementController oldUGTypeToUGDef: old_ug_type]];
	  [self setDefaultValues];
	  for (i=0; i<MAXNOTEPARAMETERS; i++)
	    {
	      argEnabled[i] = YES;
	    }

//	  [self validate];

	  /* Now set the old argument values into the new arg */
	  for (i=0; i<9; i++)
	    {
	      if (old_arg[i])
		{
		  len = strlen(old_arg[i]);
		  if (len > 0)
		    {
		      len += 10;  /* for good measure */
		      if (arg[i])
			{
			  NXZoneFree([self zone], arg[i]);
			}
		      arg[i] = (char *) NXZoneCalloc([self zone], len, sizeof(char));
		      strcpy(arg[i], old_arg[i]);
		    }
		  else
		    {  /* Pathological */
		      len = 2;
		      if (arg[i])
			{
			  NXZoneFree([self zone], arg[i]);
			}
		      arg[i] = (char *) NXZoneCalloc([self zone], len, sizeof(char));
		      strcpy(arg[i], "");
		    }
		}
	    }
        }
    }

  /* Make sure all the envelopes are synced */
  for (i=0; i<MAXNOTEPARAMETERS; i++)
    {
      if (envDelegate[i])
	{
	  [envDelegate[i] free];
	  envDelegate[i] = nil;
	}
      [self syncEnvelope:i];
    }

  return self;
}


- (STR)getArgCast:(int)type;
{
  char *cast;
  switch (type)
    {
      case(INT):
	{
	  cast = "(int)";
	  break;
	}
      case(DOUBLE):
	{
	  cast = "(double)";
	  break;
	}
      case(BOOLEAN):
	{
	  cast = "(BOOL)";
	  break;
	}
    default:
      {
	cast = NULL;
      }
    }
  
  return(cast);
}

- (STR)getArgTypeStr:(int)type;
{
  char *cast;
  switch (type)
    {
      case(INT):
	{
	  cast = "int";
	  break;
	}
      case(DOUBLE):
	{
	  cast = "double";
	  break;
	}
      case(BOOLEAN):
	{
	  cast = "BOOL";
	  break;
	}
      case(ENV):
	{
	  cast = "ENV";
	  break;
	}
      case(DSPDATUM):
	{
	  cast = "DSPDATUM";
	  break;
	}
      case(PARTIALS):
	{
	  cast = "PARTIALS";
	  break;
	}
    default:
      {
	cast = NULL;
      }
    }
  
  return(cast);
}

- (int)getUGTarget
{
  return [theUGDef getMetaType];
}

- (BOOL)isPatchParameter
{
  return [theUGDef isPatchParameter];
}

- (BOOL)isClavier
{
  return [theUGDef isClavier];
}

- (BOOL)isMidi
{
  return [theUGDef isMidi];
}

- (BOOL)isNoteFilter
{
  return [theUGDef isNoteFilter];
}

- thePatch
{
  return theSP;
}


- clavier
/* Called by FakeSynthPatch when a clavier icon is clicked on */
{

  if ([self isClavier])
    {
      if (clavier)
	{  
	  /* From an archived object */
	  if (! clavInit)
	    {
	      [clavier initConnectionWithPatch:[self thePatch] WithFakeUG: self];
	      clavInit = YES;
	    }
	}
      else
	{
	  /* A new one */
	  clavier = [[Clavier alloc] init];
	  [clavier initConnectionWithPatch:[self thePatch] WithFakeUG: self];
	  [clavier setClavierName:theName];
	}
      return clavier;
    }
  else
    {
      return nil;
    }

}

- midi
{
  return midi;
}

- noteFilter
{
  return noteFilter;
}

- patchParam
{
  return patchParam;
}

- (BOOL) isSoundMaker
{
  return isSoundMaker;
}

- setInspectorName:(char *) aName
{
  int i;

  if (argCount > 0)
    {
      [inspector setTitle: aName];
    }
  else
    {
      [noArgInspector setTitle: aName];
    }

  for (i=0;i<MAXNOTEPARAMETERS;i++)
    {
      [slider[i] updateInspectorTitle];
    }

  return self;
}

- switchArg:sender
/* Change the argument being displayed on the inspector */
{
  int button;

  /* button = -1 for top, 1 for bottom */
  button = [[sender selectedCell] tag];
  currArg += button;

  if (currArg < 0)
    {
      currArg = argCount - 1;
    }
  else if (currArg == argCount)
    {
      currArg = 0;
    }

  [self displayArg:currArg];
  return self;
}

- displayInspector
{
  char *nibFile;

  if ([self isPatchParameter])
    {
      [[self patchParam] displayInspector];
    }
  else if ([self isClavier])
    {
      [clavier displayInspector];
    }
  else
    {
      if (! inspector)
	{
	  nibFile = [theUGDef getNibFile];
	  if (! nibFile)
	    {
	      if (! [self isMidi])
		{
		  NXRunAlertPanel(getAppName(), "No nib file defined for: %s", 
				  NULL, NULL, NULL, [theUGDef getName]);
		}
	      return nil;
	    }
	  
	  [NXApp loadNibSection:nibFile owner:self withNames:NO];
	  [self displayArg:currArg];
	  [argIndexField setIntValue:argCount];
	}
      
      if (argCount > 0)
	{
	  [self updateAllocOrder];
	  [fakeUGName setStringValue: theName];
	  [fakeUGType setStringValue:[self getTypeString]];
	  [argCountField setIntValue:[self getArgCount]];
	  [argIndexField setIntValue:currArg+1];
	  [self setInspectorName:theName];
	  [self bringUpInspector:inspector];
	}
      else
	{
	  [self updateAllocOrder];
	  [fakeUGNameNoArg setStringValue: theName];
	  [fakeUGTypeNoArg setStringValue:[self getTypeString]];
	  [self setInspectorName:theName];
	  [self bringUpInspector:noArgInspector];
	}
    }      

  inspectorDisplayed = YES;
  return self;
}
  
- displayInspectorIfWasActive
{
  if ([self isPatchParameter])
    {
      [[self patchParam] displayInspectorIfWasActive];
    }
  else if ([self isClavier])
    {
      [clavier displayInspectorIfWasActive];
    }
  else
    {
      if (inspectorDisplayed)
	{
	  [self displayInspector];
	}
    }

  return self;
}

- inspectorClosed
/* Called by the inspector's delegate */
{
  inspectorDisplayed = NO;
  return self;
}

- bringUpInspector:anInspector
{
  int i;

  if (inspectorRectJustLoaded)
    {
      if (useInspectorRect)
	{
	  [anInspector placeWindowAndDisplay:&inspectorRect];
	  useInspectorRect = NO;
	}

      inspectorRectJustLoaded = NO;
      for (i=0; i<MAXNOTEPARAMETERS; i++)
	{
	  if (slider[i])
	    {
	      [slider [i]
               initFromFakeUG: self
               ArgNum: i
               ArgField: argVal
               ArgType: [[theArgs objectAt:i] argType]
	      ];

	      [slider[i] displayInspector];
	    }
	}
    }

  /* doHighlight toggle prevents the instance
   * from being highlighted twice
   */
  doHighlight = NO;
  [anInspector makeKeyAndOrderFront:self];
  doHighlight = YES;
  return self;
}

- takeArgFromInspector:sender
/* This is called when a return is hit in a cell in argVal */
{
  [self setArgWithIndex: currArg To: (char *) [sender stringValue]];
  if (slider[currArg])
    {
      [slider[currArg] takeSliderValueFrom:argVal];
    }

  return self;
}

- inspectArg:sender
{
  if ([[theArgs objectAt:currArg] argType] == ENV)
    {
      [self displayEnvelope:currArg];
      [self displayArg:currArg];
    }
  else
    {
      if (! slider[currArg])
	{
	  slider[currArg] = [[UGArgSlider alloc] init];
	  [slider [currArg]
             initFromFakeUG: self
             ArgNum: currArg
             ArgField: argVal
             ArgType: [[theArgs objectAt:currArg] argType]
	  ];
	}

      [slider[currArg] displayInspector];
    }

  return self;
}

- syncEnvelope:(int) i
{
  if ([self getArgType:i] != ENV)
    {
      return nil;
    }

  if (! envDelegate[i])
    {
      envDelegate[i] = [[EnvDelegate alloc] init];
    }

  [envDelegate[i] setFakeUG:self ArgNum: i];
  [envDelegate[i] setPatchParameter:nil];

  if (! argEnvelope[i] && arg[i])
    {
      /* The argument was set but the Envelope object was
       * never initialized
       */
      argEnvelope[i] = [envDelegate[i] textToEnvelope:arg[i]];
    }

  return self;
}

- createDefaultEnvelope:(int) i
{
  static double x[3];
  static double y[3];
  static double s[3];
  int pointCount = 3;
  int stickPoint = 1;
  double orSamplingPeriod = 1.0;
  double orDefaultSmoothing = 1.0;
  Envelope *anEnv;


  /* Create the default Envelope */
  x[0] = 0;   y[0] = 0;
  x[1] = .5;  y[1] = 1.0;
  x[2] = 1;   y[2] = 0;
  s[0] = 1.0; s[1] = 1.0; s[2] = 1.0;
  anEnv = [[Envelope alloc] init];
  [anEnv setPointCount: pointCount
         xArray: x
	 orSamplingPeriod: orSamplingPeriod
	 yArray: y
	 smoothingArray: s
	 orDefaultSmoothing: orDefaultSmoothing];

  [anEnv setStickPoint:stickPoint];
  argEnvelope[i] = anEnv;
  return self;
}

- displayEnvelope:(int) i
{
  id envController;
  char *ptr;
  int val;

  [self syncEnvelope:i];
  if (argEnvelope[i])
    {
      envController = [envDelegate[i] envController];
      [envController setNewEnvelope:argEnvelope[i]];
      [envController reScaleLimits:self];
      [envController makeGraphType:1];
      [envController reScaleLimits:self];
    }
  else
    {
      val = NXRunAlertPanel(getAppName(),
			    "No envelope defined.  Create default envelope?",
			    "YES",   /* default */
			    "NO",    /* alt */
			    "Cancel"); /* other */
      switch (val)
	{
	  case(NX_ALERTDEFAULT):
	    {
	      [self createDefaultEnvelope:i];
	      envController = [envDelegate[i] envController];
	      [envController setNewEnvelope:argEnvelope[i]];
	      [envController reScaleLimits:self];
	      [envController makeGraphType:1];
	      [envController reScaleLimits:self];
	      break;
	    }
	  case(NX_ALERTALTERNATE):
	    {
	      break;
	    }
	default:
	  {
	    return self;
	  }
	}
    }

  sprintf(ErrorMsg, "%s %s", theName, [self getArgName:i]);
  [envDelegate[i] displayWindow:ErrorMsg];
  if (arg[i])
    {
      NXZoneFree([self zone], arg[i]);
      arg[i] = NULL;
    }

  if (argEnvelope[i])
    {
      ptr = [[theSP utilities] envelopeToText:argEnvelope[i]];
      if (ptr)
	{
	  arg[i] = (char *) NXZoneCalloc ([self zone], 1+strlen(ptr), sizeof(char));
	  strcpy(arg[i], ptr);
	}
    }

  [self displayArg:i];

  return self;
}


- inspector
{
  return inspector;
}

- setArg: (int) argnum To:(char *) value
/* This gets called when the inspector field is touched
 * directly 
 */
{
  currArg = argnum;
  [self setArgWithIndex: currArg To:value];

  /* Update the inspector */
  [self displayArg:argnum];
  return self;
}

- setArgWithIndex:(int) argnum To:(char*) value
{

#ifdef DEBUG_PARAM
    DEBUG_PRINT("FakeUG:setArgWithIndex:%d To:%s\n", argnum, value);
#endif DEBUG_PARAM

    if (! value)
      {
        if (arg[argnum])
	  {
	    NXZoneFree([self zone], arg[argnum]);
	  }
	arg[argnum] = NULL;
      }
    else
      {
	const char *temp = value;

        if (arg[argnum])
	  {
	    NXZoneFree([self zone], arg[argnum]);
	  }

	arg[argnum] = (char *) NXZoneCalloc ([self zone], 1+strlen(temp), sizeof(char));
	strcpy(arg[argnum], temp);
      }

  [self syncEnvelope:argnum];
#ifdef DEBUG_PARAM
	DEBUG_PRINT("setArgWithIndex:To: Sending %s to arg %d and updating inspector\n",
		    arg[argnum], argnum);
#endif DEBUG_PARAM

  [self sendArg:argnum];
  return self;
}

- setArg:(int)i WithEnvelope:anEnv
{
  char *ptr;

  if ([self getArgType:i] == ENV)
    {
      argEnvelope[i] = anEnv;
    }

  if (arg[i])
    {
      NXZoneFree([self zone], arg[i]);
      arg[i] = NULL;
    }
  ptr = [[theSP utilities] envelopeToText:argEnvelope[i]];
  if (ptr)
    {
      arg[i] = (char *) NXZoneCalloc ([self zone], 1+strlen(ptr), sizeof(char));
      strcpy(arg[i], ptr);
    }

  [self displayArg:i];
  [self sendArg:i];
  return self;
}

- sendArg:(int) num
{
  if (isSoundMaker)
    {
      if (argEnabled[num])
	{
	  [theSP setUGParameterFromFakeUG:self argNum:num];
	}
      else
	{
	  if ([self getArgType:num] == ENV)
	    {
	      /* Dis-associate the current envelope from the asymp */
	      if (ug)
		{
		  [ug abortEnvelope];
		}
	    }
	}
    }

  return self;
}

- (STR)getArg:(int)i
{
  return arg[i];
}

- getArgEnvelope:(int)i
{
  id envCopy;

  if ([self getArgType:i] == ENV)
    {
      /* Return a copy of the latest envelope */
      if (argEnvelope[i])
	{
	  envCopy = [argEnvelope[i] copy];
	}
      else
	{
	  envCopy = nil;
	}

      return envCopy;
    }

  return nil;
}

- clearArgEnvelope:(int)i
{
  argEnvelope[i] = nil;
  if (arg[i])
    {
      NXZoneFree([self zone], arg[i]);
      arg[i] = NULL;
    }
  [self displayArg:i];

  return self;
}

- willAddEnvelope:(int)i
{
  if ( !([self getArgType:i] == ENV))
    {
      return self;
    }

  if (ug)
    {
      [ug abortEnvelope];
      [theSP enableNoteOff:NO];
    }
  return self;
}

- willRemoveEnvelope:(int)i
{
  if ( !([self getArgType:i] == ENV))
    {
      return self;
    }

  if (ug)
    {
      [ug abortEnvelope];
      [theSP enableNoteOff:NO];
    }
  return self;
}

- (int) methodCount
{
  return methodCount;
}

- (int) getArgCount
{
  return argCount;
}

- (char *)getMethod:(int)i
{
  return [[theMethods objectAt:i] getName];
}

- (char *)getArgName:(int)i
{
  return [[theArgs objectAt:i] getName];
}

- (BOOL) isMethodAnOutput:(int)i
{
  return [[theMethods objectAt:i] isOutput];
}

- printSelf
{
  int i;
  int connectionCount = 0;

  for (i=0; i<MAXCONNECTIONS; i++)
    {
      if (connections[i]!=nil)
	{
	  connectionCount++;
	}
    }

  printf("+++++++++++++++++++++++++++++++++++++++++++++\n\n");
  printf("- (FakeUG %s) connections: %d\n", [self getName], connectionCount);

  for (i=0; i<methodCount; i++)
    {
      printf("method %d: %s\n", i, [self getMethod:i]);
    }

  for (i=0; i<argCount; i++)
    {
      printf("arg %d %s: %s\n", i, [self getArgName:i], arg[i]);
    }

  for (i=0; i<connectionCount; i++)
    {
      [connections[i] printSelf];
    }

  return self;
}

- displayIfDoubleClick
{

  if ([self isPatchParameter])
    {
    [[self patchParam] displayIfDoubleClick];
    }
  else if ([self isClavier])
    {
    [[self clavier] displayIfDoubleClick];
    }
  else
    {
      clickCount++;
      if (clickCount >= 2)
	{
	  clickCount = 0;
	  [self displayInspector];
	}
    }

  return self;
}


- highlightSelf
{
  
  if ( (![theSP windowMiniaturized]) && doHighlight)
    {
      [theSP setSelected:self];
      [window removeFromEventMask:NX_LMOUSEDRAGGEDMASK];
      [window flushWindow];
      [theSP display];
    }

  return self;
}

- setDoHighlight:(BOOL) flag
{

  doHighlight = flag;
  return self;
}

@end



