// GraphicImage.m
// By Jayson Adams
// NeXT Strategic Developer Engineer
//
// You may freely copy, distribute and reuse the code in this example.
// NeXT disclaims any warranty of any kind, expressed or implied, as to its
// fitness for any particular use.

#import <dpsclient/psops.h>
#import <dpsclient/wraps.h>
#import <appkit/NXImage.h>
#import <appkit/Text.h>
#import <appkit/Window.h>
#import <appkit/Application.h>

#import "Controller.h"

#import "GraphicImage.h"


@implementation GraphicImage


- initForImage:anImage
{
    [super init];
    
  /* save our graphic image */
    image = anImage;
    
    return self;
}

- free
{
    [image free];
    return [super free];
}

- calcCellSize:(NXSize *)theSize
{
  /* our graphic image determines our size */
    [image getSize:theSize];

    return self;
}

- highlight:(const NXRect *)cellFrame inView:controlView lit:(BOOL)flag
{
    if (highlighted != flag) {
	highlighted = flag;
	
      /* toggle highlighting */
	NXHighlightRect(cellFrame);
	
      /* make change visible */
	[[controlView window] flushWindow];
    }
    
    return self;
}

- drawSelf:(const NXRect *)cellFrame inView:controlView
{
    NXPoint	point;
    
  /*
   * the text object expects us not to modify the current graphics state, so
   * we'll save it
   */
    PSgsave();

  /* fill our bounds with the text object's background gray (if opaque) */
    if ([controlView isOpaque]) {
	PSsetgray([controlView backgroundGray]);
	NXRectFill(cellFrame);
    }
    
  /* we're in a flipped coordinate system */
    point = cellFrame->origin;
    point.y += cellFrame->size.height;
    
  /* draw the image */
    [image composite:NX_SOVER toPoint:&point];
    
  /* restore the graphics state */
    PSgrestore();

    return self;
}

#define DRAG_MASK (NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK)

- trackMouse:(NXEvent *)theEvent inRect:(const NXRect *)cellFrame
  ofView:controlView
{
    int    	oldMask;
    NXEvent	*event;
    NXPoint	mouseLocation;
    BOOL	mouseInCell = NO;
    
  /* we want to grab mouse dragged events */
    oldMask = [[controlView window] addToEventMask:NX_MOUSEDRAGGEDMASK];
    
  /* start our event loop, looking for mouse-dragged or mouse-up events */
    event = [NXApp getNextEvent:DRAG_MASK];
    while (event->type != NX_MOUSEUP) {

      /* mouse-dragged event;  highlight if mouse is in cell bounds */
	mouseLocation = event->location;
	[controlView convertPoint:&mouseLocation fromView:NULL];
	mouseInCell = NXPointInRect(&mouseLocation, cellFrame);
	
	if (mouseInCell != highlighted) {
	  /* we have to lock focus before calling hightlight:inView:lit: */
	    [controlView lockFocus];
	    [self highlight:cellFrame inView:controlView lit:mouseInCell];
	    [controlView unlockFocus];
	}
	event = [NXApp getNextEvent:DRAG_MASK];
    }
    
  /* turn off any highlighting */
    [controlView lockFocus];
    [self highlight:cellFrame inView:controlView lit:NO];
    [controlView unlockFocus];
    
  /* reset the event mask */
    [[controlView window] setEventMask:oldMask];
    
  /* if a double-click and the mouse is over us, do something */
    mouseLocation = event->location;
    [controlView convertPoint:&mouseLocation fromView:NULL];
    if (NXPointInRect(&mouseLocation, cellFrame) &&
    	event->data.mouse.click == 2) {
	[self performDoubleClickAction];
    }
    
    return self;
}

- readRichText:(NXStream *)stream forView:view
{
    NXStream	*memoryStream;
    int		length, i = 0;
    char	highNibble, lowNibble;
    
  /* find out how bytes there are */
    NXScanf(stream, "%d ", &length);
    
  /* create a place to temporarily store the reconstituted image data */
    memoryStream = NXOpenMemory(NULL, 0, NX_READWRITE);
    while (i < length) {
        highNibble = NXGetc(stream) - ' ';
        lowNibble = NXGetc(stream) - ' ';
	NXPutc(memoryStream, ((highNibble << 4) + lowNibble));
	i++;
    }
    
  /* rewind and feed the data to our NXImage */
    NXSeek(memoryStream, 0, NX_FROMSTART);
    image = [[NXImage alloc] initFromStream:memoryStream];

    NXCloseMemory(memoryStream, NX_FREEBUFFER);

    return self;
}

- writeRichText:(NXStream *)stream forView:view
{
    NXStream	*memoryStream;
    char	nextByte, *buffer;
    int		length, maxLength, i;
    
  /*
   * This method gives us a chance to write out whatever information we
   * need to recreate ourself when the text object loads the RTF back in.
   * There are a couple different strategies for doing this:  placing the
   * data "inline" or writing out the filename and saving the file somewhere
   * else.  This method takes the first approach, converting the TIFF data to
   * ASCII (7-bits) since the RTF specification requires 8-bit data to be
   * escaped.  This isn't the best solution because every time we read the RTF
   * file in, we have to read the data as well (which we've expanded to twice
   * its original size in order to store in the RTF).  Additionally, the data's
   * imbedded within the RTF (there might be occassions where we want to read
   * the data, a TIFF in this case, but not the RTF).  The best approach is to
   * place the data in a BTree or some other external storage and to leave some
   * identifier in the RTF stream.
   */

  /* get some temporary storage */
    memoryStream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
    [image writeTIFF:memoryStream];

  /* get a pointer to the TIFF data and remember its length in bytes */
    NXGetMemoryBuffer(memoryStream, &buffer, &length, &maxLength);
    NXPrintf(stream, "%d ", length);

  /* convert each byte to two ASCII characters */
    for (i = 0; i < length; i++) {
        nextByte = buffer[i];
        NXPrintf(stream, "%c%c", ((nextByte & 0xF0) >> 4) + ' ',
		 (nextByte & 0x0F) + ' ');
    }
    
    NXCloseMemory(memoryStream, NX_FREEBUFFER);
    
    return self;
}

- performDoubleClickAction
{
  /* we don't do anything when the user double-clicks on us */
    return self;
}

@end