/*
 * Copyright (c) 1992, The Geometry Center
 *                     University of Minnesota 
 *                     1300 South Second Street
 *                     Minneapolis, MN  55454
 *
 * email address: software@geom.umn.edu
 *
 * This software is copyrighted as noted above.  It may be freely copied,
 * modified, and redistributed, provided that the copyright notice is
 * preserved on all copies.
 *
 * There is no warranty or other guarantee of fitness for this software,
 * it is provided solely "as is".  Bug reports or fixes may be sent
 * to the authors, who may or may not act on them as they desire.
 *
 * You may not include this software in a program or other software product
 * without supplying the source, or without informing the end-user that the
 * source is available for no extra charge.
 *
 * If you modify this software, you should include a notice giving the
 * name of the person performing the modification, the date of modification,
 * and the reason for such modification.
 *
 *     The National Science and Technology Research Center for
 *      Computation and Visualization of Geometric Structures
 */

/* Generated by Interface Builder */

#include <string.h>
#import <dpsclient/wraps.h>
#include <dpsclient/dpsfriends.h>
#import "LabelField.h"
#import "FormatView.h"
#import <appkit/Form.h>
#import <appkit/Pasteboard.h>
#import <appkit/NXImage.h>
#import <objc/List.h>
#import "Distributor.h"
#import "EditBrancher.h"
#import <appkit/Application.h>
#import <appkit/graphics.h>

@implementation FormatView

- (BOOL) acceptsFirstResponder
{
  return YES;
}

- initFrame:(const NXRect *)frameRect
{
  int i;
  NXZone *zone;
  [super initFrame:frameRect];
  zone = [self zone];
  selectedBox = -1;
  rows = columns = 2;
  [self computeBoxes];
  imageList = [[List allocFromZone:zone] init];
  labelList = [[List allocFromZone:zone] init];
  bgimage = [[NXImage allocFromZone:zone] initSize:&bounds.size];
  clearimage = [[NXImage allocFromZone:zone] initSize:&bounds.size];
  empty = NXGetNamedObject("EditBrancherInstance", NXApp);
  /* Load with empties */
  for (i=0; i < rows*columns; i++)
    {
      [imageList addObject:empty];
      [labelList addObject:empty];
    }
  if ([clearimage lockFocus])
    {
      PSclippath();
      PSsetgray(1);
      PSfill();
      [clearimage unlockFocus];
    }
  return self;
}

- computeBoxes
{
  boxWidth = bounds.size.width / columns;
  boxHeight = bounds.size.height / rows;
  if (boxWidth > boxHeight)
    {
      squareLength = boxHeight;
      xoffset = (boxWidth - boxHeight) / 2;
      yoffset = 0;
    }
  else
    {
      squareLength = boxWidth;
      xoffset = 0;
      yoffset = (boxHeight - boxWidth) / 2;
    }
  return self;
}

- drawSelf: (NXRect *)dRects:(int)dCount
{
  float width = bounds.size.width;
  float height = bounds.size.height;
  float x,y;
  id image;
  LabelField *label;
  NXPoint pnt;
  int i;

  [self computeBoxes];

  if ([((NXDrawingStatus == NX_DRAWING) ? bgimage : self) lockFocus])
    {
      /*** First, clear screen ***/
      [clearimage composite:NX_COPY toPoint:&bounds.origin];

      if (NXDrawingStatus == NX_DRAWING)
	{
	  /*** Redraw box boundaries ***/
	  PSsetlinewidth(3);
	  PSsetgray(0.667);
	  /*  horizontal lines */
	  for (y=0; y<=height; y += boxHeight)
	    {
	      PSnewpath();
	      PSmoveto(0,y);
	      PSlineto(width, y);
	      PSstroke();
	    }
	  /*  vertical lines */
	  for (x=0; x<=width; x += boxWidth)
	    {
	      PSnewpath();
	      PSmoveto(x,0);
	      PSlineto(x,height);
	      PSstroke();
	    }
	}
      
      /*** Composite links ***/
      for (i=0; i < [imageList count]; i++)
	{
	  image = [imageList objectAt:i];
	  if (image == empty) continue;
	  pnt.x = (i % columns) * boxWidth + xoffset;
	  pnt.y = (i / columns) * boxHeight + yoffset;
	  [image composite:NX_SOVER toPoint:&pnt];

	  /*  ... and labels */
	  label = [labelList objectAt:i];
	  [label->textField moveTo:pnt.x-xoffset :pnt.y-yoffset];
//	  [label->textField display];
	}

      /* Highlight selected box */
      if (NXDrawingStatus == NX_DRAWING)
	{
	  PSsetlinewidth(3);
	  if (selectedBox != -1)
	    {
	      [self getPoint:&pnt fromNum:selectedBox];
	      pnt.x++; pnt.y++;
	      [self drawBoxAtPnt:&pnt :0];
	    }
	}
      [((NXDrawingStatus == NX_DRAWING) ? bgimage : self) unlockFocus];
    }
  pnt.x = pnt.y = 0;
  if (NXDrawingStatus == NX_DRAWING) [bgimage composite:NX_COPY toPoint:&pnt];
  return self;
}

- unselectBox
{
  selectedBox = -1;
  return self;
}

- getDims:(int *) r :(int *) c
{
  *r = rows; *c = columns;
  return self;
}

- updateDims :(int) newrows :(int) newcolumns :(id *) spill :(id *) spillab
{
  BOOL didChange = NO;
  id extraImages = NULL, extraLabels = NULL;
  int oldCount, i;
  if ((columns != newcolumns) || (rows != newrows))
    didChange = YES;

  /* Remove empties from list (only if the page's overall capacity has
     decreased), then dump extra ones into extraImages */
  if (newcolumns*newrows < columns*rows)
    {
      columns = newcolumns; rows = newrows;
      while ([imageList removeObject:empty] != nil);
      while ([labelList removeObject:empty] != nil);
      if ((oldCount = [imageList count]) > rows*columns)
	{
	  extraImages = [[List alloc] initCount:oldCount-rows*columns+1];
	  extraLabels = [[List alloc] initCount:oldCount-rows*columns+1];
	  for (i = rows*columns; i < oldCount; i++)
	    {
	      [extraImages addObject:[imageList removeObjectAt:rows*columns]];
	      [extraLabels addObject:[labelList removeObjectAt:rows*columns]];
	    }
	}
    }
  else
    {
      columns = newcolumns; rows = newrows;
      extraImages = extraLabels = NULL;
    }

  /* Fill in the rest with empties */
  for (i=0; i < rows*columns; i++)
    {
      if ([imageList objectAt:i] == nil)
	[imageList insertObject: empty at:i];
      if ([labelList objectAt:i] == nil)
	[labelList insertObject: empty at:i];
    }

  [self computeBoxes];
  if (didChange)
    [self resizeLinks];
  *spill = extraImages;
  *spillab = extraLabels;
  [self display];
  return self;
}
  

- place:newlist :lablist
{
  int i;
  id image;
  LabelField *label;
  NXSize viewSize, size;
  viewSize.width = viewSize.height = squareLength;
  
  for (i = [imageList count]; i < rows*columns; i++)
    {
      [imageList insertObject:empty at:[imageList count]];
      [labelList insertObject:empty at:[labelList count]];
    }
  while ([newlist count] && [imageList indexOf:empty] != NX_NOT_IN_LIST)
    {
      image = [newlist removeObjectAt:0];
      label = [lablist removeObjectAt:0];
      [image getSize:&size];
      if (size.width != squareLength || size.height != squareLength)
	[image setSize:&viewSize];
      [label->textField sizeTo:boxWidth :15];
      [self insertInLists :image :label];
    }
  
  [self display];
  return self;
}

/* Fills in first hole (empty) with the image */
- insertInLists:image :(LabelField *) label
{
  if ([imageList replaceObject:empty with:image] == nil)
    [imageList insertObject:image at:[imageList count]];
  if ([labelList replaceObject:empty with:label] == nil)
    [labelList insertObject:label at:[labelList count]];
  [self addSubview:label->textField];
  return self;
}

- mouseDown:(NXEvent *) event
{
  int origBox, newBox, oldMask;
  NXPoint origPoint = event->location, pnt, mousePnt;
  id linkImage;
  LabelField *label;
  NXEvent *evnt;
  NXRect currentRect;
  NXCoord diffx, diffy;

  [window makeFirstResponder:self];
  [self convertPoint:&origPoint fromView:nil];
  origBox = [self findBoxNum:&origPoint];
  linkImage = [imageList objectAt:origBox];
  label = [labelList objectAt:origBox];

  if (linkImage == empty)
    {
      selectedBox = origBox;
      [self display];
      return self;
    }
  selectedBox = -1;
  [self display];
  [linkImage getSize:&currentRect.size];
  [self getPoint:&currentRect.origin fromNum:origBox];
  currentRect.origin.x += xoffset;
  currentRect.origin.y += yoffset;
  diffx = origPoint.x - currentRect.origin.x;
  diffy = origPoint.y - currentRect.origin.y;

  /* Clear rect in bgimage... */
  if ([bgimage lockFocus])
    {
      [clearimage composite:NX_COPY fromRect:&currentRect toPoint:&currentRect.origin];
      [bgimage unlockFocus];
    }

  /* ...and window */
  [self lockFocus];
  [clearimage composite:NX_COPY fromRect:&currentRect toPoint:&currentRect.origin];

  /* Event loop */
  oldMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
  do
    {
      evnt = [NXApp getNextEvent:NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK];
      pnt = evnt->location;
      [self convertPoint:&pnt fromView:nil];
      mousePnt = pnt;
      currentRect.origin.x = (pnt.x = pnt.x - diffx);
      currentRect.origin.y = (pnt.y = pnt.y - diffy);
      [linkImage composite:NX_SOVER toPoint:&pnt];
      [self drawBoxAtPnt:&mousePnt :0];
      [window flushWindow];
      if (!(evnt->flags && NX_ALTERNATEMASK))
	[bgimage composite:NX_COPY fromRect:&currentRect toPoint:&pnt];
      [self drawBoxAtPnt:&mousePnt :0.667];
    }
  while (evnt->type == NX_MOUSEDRAGGED);
  [window setEventMask:oldMask];

  /* Keep box highlighted */
  [self drawBoxAtPnt:&mousePnt :0];
  /* Update list to reflect move */
  selectedBox = newBox = [self findBoxNum:&mousePnt];
  /* Place link at destination into original box */
  [imageList replaceObjectAt:origBox with:[imageList objectAt:newBox]];
  [labelList replaceObjectAt:origBox with:[labelList objectAt:newBox]];
  /* Place moved link in its destination */
  [imageList replaceObjectAt:newBox with:linkImage];
  [labelList replaceObjectAt:newBox with:label];

  [self unlockFocus];
  [window display];
  return self;
}

- drawBoxAtPnt:(NXPoint *) pnt :(float) gray
{
  NXRect rect;
  [self getRect:&rect fromNum:[self findBoxNum:pnt]];
  PSsetlinewidth(3);
  PSnewpath();
  PSsetgray(gray);
  PSmoveto(rect.origin.x, rect.origin.y);
  PSlineto(rect.origin.x+rect.size.width, rect.origin.y);
  PSlineto(rect.origin.x+rect.size.width, rect.origin.y+rect.size.height);
  PSlineto(rect.origin.x, rect.origin.y+rect.size.height);
  PSlineto(rect.origin.x, rect.origin.y);
  PSstroke();
  return self;
}

- (int) findBoxNum:(NXPoint *) pnt
{
  int i;
  NXRect rect;
  for (i=0; i < rows*columns; i++)
    {
      [self getRect:&rect fromNum:i];
      if ([self mouse:pnt inRect:&rect]) return i;
    }
  return -1;
}

- getRect:(NXRect *) rect fromNum:(int) num
{
  [self getPoint:&rect->origin fromNum:num];
  rect->size.height = boxHeight;
  rect->size.width = boxWidth;
  return self;
}

- getPoint:(NXPoint *) pnt fromNum:(int) num
{
  pnt->x = boxWidth    * (num % columns);
  pnt->y = boxHeight   * (num / columns);
  return self;
}
  
- resizeLinks
{
  NXSize size;
  int i, count = [imageList count];
  id image;
  size.width = size.height = squareLength;
  for (i=0; i < count; i++)
    {
      image = [imageList objectAt:i];
      if (image != empty)
	{
	  [image setSize:&size];
	  [((LabelField *) [labelList objectAt:i])->textField
	 sizeTo:boxWidth :15];
	}
    }
  return self;
}


/*** Pasteboard methods ***/

#define DELETE 0x7f
- keyDown:(NXEvent *) event
{
  unsigned short code = event->data.key.charCode;
  if (code == DELETE) [self deleteSelected];
  return self;
}

- deleteSelected
{
  LabelField *oldlabel;
  if (selectedBox == -1) return self;
  [imageList replaceObjectAt:selectedBox with:empty];
  oldlabel = [labelList replaceObjectAt:selectedBox with:empty];
  if (oldlabel == empty) return self;
  [oldlabel->textField removeFromSuperview];
  [self display];
  return self;
}

void makeBonk();
- cut:sender
{
  if (selectedBox == -1)
    {
      makeBonk();
      return self;
    }
  [self copy:sender];
  [self deleteSelected];
  return self;
}

- copy:sender
{
  id pb = [Pasteboard new];
  int length, maxLength;
  NXStream *stream;
  NXTypedStream *tstream;
  LabelField *label;
  static const char *type[1];
  char *labelText, *data;
  id image;

  if (selectedBox == -1)
    {
      makeBonk();
      return self;
    }

  image = [imageList objectAt:selectedBox];
  if (image == empty)
    {
      makeBonk();
      return self;
    }
  type[0] = "Format entry";
  [pb declareTypes:(const char *const *) type num:1 owner:self];
  stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
  tstream = NXOpenTypedStream(stream, NX_WRITEONLY);
  label = [labelList objectAt:selectedBox];
  /* First, write label info */
  labelText = (char *) [label->textField stringValue];
  NXWriteTypes(tstream, "*ii", &labelText, &label->xoffset, &label->yoffset);
  /* Then, the NXImage */
  NXWriteRootObject(tstream, image);
  NXCloseTypedStream(tstream);

  NXGetMemoryBuffer(stream, &data, &length, &maxLength);
  [pb writeType:"Format entry" data:data length:length];
  NXCloseMemory(stream, NX_FREEBUFFER);
  return self;
}

- paste:sender
{
  id pb = [Pasteboard new];
  char **type;
  char *data;
  int length;
  NXStream *stream;
  NXTypedStream *tstream;
  LabelField *label, *oldlabel;
  id image;
  char *labelText;

  if (selectedBox == -1)
    {
      makeBonk();
      return self;
    }
  
  for(type = (char **) [pb types]; *type; type++)
    if(!strcmp(*type, "Format entry"))
      break;
       
  if(*type)
    {
      [pb readType:"Format entry" data:&data length:&length];
      stream = NXOpenMemory(data, length, NX_READONLY);
      tstream = NXOpenTypedStream(stream, NX_READONLY);
      label = [[LabelField alloc] init];
      /* Read label data */
      NXReadTypes(tstream, "*ii", &labelText, &label->xoffset, &label->yoffset);
      [label->textField setStringValue:labelText];
      /* And the NXImage */
      image = NXReadObject(tstream);
      NXCloseTypedStream(tstream);
      NXCloseMemory(stream, NX_FREEBUFFER);

      /* Place label and image in list */
      [imageList replaceObjectAt:selectedBox with:image];
      oldlabel = [labelList replaceObjectAt:selectedBox with:label];
      if (oldlabel != empty)
	[oldlabel->textField removeFromSuperview];
      [label->textField sizeTo :boxWidth :15];
      [self addSubview:label->textField];
      [self resizeLinks];
      [self display];
  }
  return self;
}

- (int) get_squareLength
{
  return squareLength;
}

/*** Archiving methods ***/

- write:(NXTypedStream *) tstream
{
  [super write:tstream];
  NXWriteObject(tstream, imageList);
  NXWriteObject(tstream, labelList);
  NXWriteTypes(tstream, "iifffff", &rows, &columns, &boxWidth, &boxHeight,
	       &squareLength, &xoffset, &yoffset);
  return self;
}

- read:(NXTypedStream *) tstream
{
  int i;
  NXZone *zone = [self zone];
  [super read:tstream];
  imageList = NXReadObject(tstream);
  labelList = NXReadObject(tstream);
  empty = NXGetNamedObject("EditBrancherInstance", NXApp); 
  NXReadTypes(tstream, "iifffff", &rows, &columns, &boxWidth, &boxHeight,
	       &squareLength, &xoffset, &yoffset);
  bgimage = [[NXImage allocFromZone:zone] initSize:&bounds.size];
  clearimage = [[NXImage allocFromZone:zone] initSize:&bounds.size];
  if ([clearimage lockFocus])
    {
      PSclippath();
      PSsetgray(1);
      PSfill();
      [clearimage unlockFocus];
    }
  /* Change all pseudo-empties to real empties */
  for (i=0; i < [imageList count]; i++)
    {
      id obj = [imageList objectAt:i];
      if ([obj class] == [empty class])
	{
	  [imageList replaceObjectAt:i with:empty];
	  [labelList replaceObjectAt:i with:empty];
	}
    }
  return self;
}

@end
