#import "TextGraphic.h"
#import "GraphicView.h"
#import <appkit/Application.h>
#import <appkit/Cell.h>
#import <appkit/Cursor.h>
#import <appkit/Font.h>
#import <appkit/Text.h>
#import <appkit/nextstd.h>
#import <dpsclient/wraps.h>

@implementation TextGraphic
/*
 * This uses a text object to draw and edit text.
 *
 * The one quirky thing to understand here is that growable Text objects
 * in 1.0 must be subviews of flipped view.  Since a GraphicView is not
 * flipped, we must have a flipped view into the view heirarchy when we
 * edit (this editing view is permanently installed as a subview of the
 * GraphicView--see GraphicView's newFrame: method).
 */

+ initialize
{
    [self setVersion:1];	/* class version, see read: */
    return self;
}

static id drawText = nil;	/* shared Text object used for drawing */
static id drawWindow = nil;

static void initClassVars()
/*
 * Create the class variable drawText here.
 */
{
    if (!drawText) {
	drawText = [Text new];
	[drawText setMonoFont:NO];
	[drawText setEditable:NO];
	[drawText setSelectable:NO];
	[drawText setFlipped:YES];
    }
}

- init
{
    initClassVars();
    [super init];
    return self;
}

- free
{
    free(data);
    return [super free];
}

/* Factory methods overridden from superclass */

+ (BOOL)isEditable
{
    return YES;
}

+ cursor
{
    return NXIBeam;
}

/* Factory method used to show/hide the ruler */

+ hideRuler:view
/*
 * Tries to hide any rulers that are lying around.
 * If view is nil, obviously it won't hide the ruler
 * (we use this fact to cancel a previous request that
 * we might have made to hide the ruler).
 */
{
    [view tryToPerform:@selector(hideRuler:) with:nil];
    return self;
}

/* Instance methods overridden from superclass */

- (BOOL)create:(NXEvent *)event in:view
 /*
  * We are only interested in where the mouse goes up, that's
  * where we'll start editing.
  */
{
    NXRect viewBounds;

    event = [NXApp getNextEvent:NX_MOUSEUPMASK];
    bounds.size.width = bounds.size.height = 0.0;
    bounds.origin = event->location;
    [view convertPoint:&bounds.origin fromView:nil];
    [view getBounds:&viewBounds];
    gFlags.selected = NO;

    return NXMouseInRect(&bounds.origin, &viewBounds, NO);
}

- (BOOL)edit:(NXEvent *)event in:view
/*
 * Here we are going to use the shared field editor for the window to
 * edit the text in the TextGraphic.  First, we must end any other editing
 * that is going on with the field editor in this window using endEditingFor:.
 * Next, we get the field editor from the window.  Normally, the field
 * editor ends editing when carriage return is pressed.  This is due to
 * the fact that its character filter is NXFieldFilter.  Since we want our
 * editing to be more like an editor (and less like a Form or TextField),
 * we set the character filter to be NXEditorFilter.  What is more, normally,
 * you can't change the font of a TextField or Form with the FontPanel
 * (since that might interfere with any real editable Text objects), but
 * in our case, we do want to be able to do that.  We also want to be
 * able to edit rich text, so we issue a setMonoFont:NO.  Editing is a bit
 * more efficient if we set the Text object to be opaque.  Note that
 * in textDidEnd:endChar: we will have to set the character filter,
 * FontPanelEnabled and mono-font back so that if there were any forms
 * or TextFields in the window, they would have a correctly configured
 * field editor.
 *
 * To let the field editor know exactly where editing is occurring and how
 * large the editable area may grow to, we must calculate and set the frame
 * of the field editor as well as its minimum and maximum size.
 *
 * We load up the field editor with our rich text (if any).
 *
 * Finally, we set self as the delegate (so that it will receive the
 * textDidEnd:endChar: message when editing is completed) and either
 * pass the mouse-down event onto the Text object, or, if a mouse-down
 * didn't cause editing to occur (i.e. we just created it), then we
 * simply put the blinking caret at the beginning of the editable area.
 *
 * The line marked with the "ack!" is kind of strange, but is necessary
 * since growable Text objects only work when they are subviews of a flipped
 * view.
 *
 * This is why GraphicView has an "editView" which is a flipped view that it
 * inserts as a subview of itself for the purposes of providing a superview
 * for the Text object.  The "ack!" line converts the bounds of the TextGraphic
 * (which are in GraphicView coordinates) to the coordinates of the Text
 * object's superview (the editView).  This limitation of the Text object
 * will be fixed post-1.0.  Note that the "ack!" line is the only one
 * concession we need to make to this limitation in this method (there are
 * two more such lines in textDidEnd:endChar:).
 */
{
    id fe;
    NXSize maxSize;
    NXStream *stream;
    NXRect viewBounds, frame;

    /* Get the field editor in this window. */

    [[view window] endEditingFor:self];
    fe = [[view window] getFieldEditor:YES for:self];
    if (!fe) return NO;
    [fe setFont:[[FontManager new] selFont]];

    /* Show text ruler and abort hiding the ruler if we have recently ended editing. */

    [[self class] perform:@selector(hideRuler:) with:nil afterDelay:0 cancelPrevious:YES];

    /* Modify it so that it will edit Rich Text and use the FontPanel. */

    [fe setCharFilter:NXEditorFilter];
    [fe setFontPanelEnabled:YES];
    [fe setMonoFont:NO];
    [fe setOpaque:YES];

    /*
     * Determine the minimum and maximum size that the Text object can be.
     * We let the Text object grow out to the edges of the GraphicView,
     * but no further.
     */

    [view getBounds:&viewBounds];
    maxSize.width = viewBounds.origin.x+viewBounds.size.width-bounds.origin.x;
    maxSize.height = bounds.origin.y+bounds.size.height-viewBounds.origin.y;
    if (!bounds.size.height && !bounds.size.width) {
	bounds.origin.y -= floor([fe lineHeight] / 2.0);
	bounds.size.height = [fe lineHeight];
	bounds.size.width = 5.0;
    }
    frame = bounds;
    [view convertRect:&frame fromView:[view superview]];	// ack!
    [fe setMinSize:&bounds.size];
    [fe setMaxSize:&maxSize];
    [fe setFrame:&frame];
    [fe setVertResizable:YES];

    /*
     * If we already have text, then put it in the Text object (allowing
     * the Text object to grow downward if necessary), otherwise, put
     * no text in, set some initial parameters, and allow the Text object
     * to grow horizontally as well as vertically
     */

    if (data) {
	[fe setHorizResizable:NO];
	stream = NXOpenMemory(data, length, NX_READONLY);
	[fe readRichText:stream];
	NXCloseMemory(stream, NX_SAVEBUFFER);
    } else {
	[fe setHorizResizable:YES];
	[fe setText:""];
	[fe setAlignment:NX_LEFTALIGNED];
	[fe setSelColor:[self textColor]];
	[fe unscript:self];
    }

    /*
     * Add the Text object to the view heirarchy and set self as its delegate
     * so that we will receive the textDidEnd:endChar: message when editing
     * is finished.
     */

    [fe setDelegate:self];
    [view addSubview:fe];

    /*
     * Make it the first responder.
     */

    [[view window] makeFirstResponder:fe];

    /* Change the ruler to be a text ruler. */

    [fe tryToPerform:@selector(showTextRuler:) with:fe];

    /*
     * Either pass the mouse-down event on to the Text object, or set
     * the selection at the beginning of the text.
     */

    if (event) {
	[fe selectNull];	/* eliminates any existing selection */
	[fe mouseDown:event];
    } else {
	[fe setSel:0:0];
    }

    return YES;
}

- draw
 /*
  * If the region has already been created, then we must draw the text.
  * To do this, we first load up the shared drawText Text object with
  * our rich text.  We then set the frame of the drawText object
  * to be our bounds.  Finally, we add the Text object as a subview of
  * the view that is currently being drawn in ([NXApp focusView])
  * and tell the Text object to draw itself.  We then remove the Text
  * object view from the view heirarchy.
  */
{
    NXStream *stream;

    if (data) {
	stream = NXOpenMemory(data, length, NX_READONLY);
	[drawText readRichText:stream];
	NXCloseMemory(stream, NX_SAVEBUFFER);
	[drawText setFrame:&bounds];
	[[NXApp focusView] addSubview:drawText];
	[drawText display];
	[drawText removeFromSuperview];
	if (DrawStatus == Resizing) {
	    PSsetgray(NX_LTGRAY);
	    NXFrameRect(&bounds);
	}
    }

    return self;
}

- performTextMethod:(SEL)aSelector with:(void *)anArgument
{
    int maxlen;
    char *buffer;
    NXStream *stream;

    if (data) {
	stream = NXOpenMemory(data, length, NX_READONLY);
	[drawText readRichText:stream];
	NXCloseMemory(stream, NX_SAVEBUFFER);
	[drawText setFrame:&bounds];
	if (!drawWindow) drawWindow = [Window new];
	[[drawWindow contentView] addSubview:drawText];
	[drawText selectAll:self];
	[drawText perform:aSelector with:anArgument];
	[drawText removeFromSuperview];
	[drawText setSel:0 :0];
	font = [drawText font];
	stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
	[drawText writeRichText:stream];
	NXGetMemoryBuffer(stream, &buffer, &length, &maxlen);
	NX_ZONEMALLOC([self zone], data, char, length);
	bcopy(buffer, data, length);
	NXCloseMemory(stream, NX_FREEBUFFER);
    }

    return self;
}

- changeFont:sender
{
    [self performTextMethod:@selector(changeFont:) with:sender];
    return self;
}

- font
{
    NXStream *stream;

    if (!font && data) {
	stream = NXOpenMemory(data, length, NX_READONLY);
	[drawText readRichText:stream];
	NXCloseMemory(stream, NX_SAVEBUFFER);
	[drawText setSel:0 :0];
	font = [drawText font];
    }

    return font;
}

- (BOOL)isOpaque
 /*
  * We are never opaque.
  */
{
    return NO;
}

- (BOOL)isValid
 /*
  * Any size TextGraphic is valid (since we fix up the size if it is
  * too small in our override of create:in:).
  */
{
    return YES;
}

- (NXColor)textColor
{
    return NX_COLORBLACK;
}

- (NXColor)lineColor
{
    return NX_COLORCLEAR;
}

- (NXColor)fillColor
{
    return NX_COLORCLEAR;
}

- (BOOL)wantsTextColor
{
    return YES;
}

- (BOOL)wantsLineColor
{
    return NO;
}

- (BOOL)wantsFillColor
{
    return NO;
}

/* Text object delegate methods */

/*
 * If we have more than one line, turn off horizontal resizing.
 */
- textDidResize:textObject oldBounds:(const NXRect *)oldBounds invalid:(NXRect *)invalidRect
{
NXSelPt start,end;

     [textObject getSel:&start :&end];
     if(start.line || end.line)
	[textObject setHorizResizable:NO];
    return self;
}

- textDidEnd:textObject endChar:(unsigned short)endChar
/*
 * This method is called when ever first responder is taken away from a
 * currently editing TextGraphic (i.e. when the user is done editing and
 * chooses to go do something else).  We must extract the rich text the user
 * has typed from the Text object, and store it away.  We also need to
 * get the frame of the Text object and make that our bounds (but,
 * remember, since the Text object must be a subview of a flipped view,
 * we need to convert the bounds rectangle to the coordinates of the
 * unflipped GraphicView).  If the Text object is empty, then we remove
 * this TextGraphic from the GraphicView and delayedFree: it.
 * We must remove the Text object from the view heirarchy and, since
 * this Text object is going to be reused, we must set its delegate
 * back to nil.
 *
 * For further explanation of the two "ack!" lines, see edit:in: above.
 */
{
    int maxlen;
    char *buffer;
    NXStream *stream;
    NXRect oldBounds;
    id editView, graphicView;

    if (data) {
	NX_FREE(data);
	data = NULL;
    }

    editView = [textObject superview];
    graphicView = [editView superview];				// ack!

    if ([textObject textLength]) {
	stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
	[textObject writeRichText:stream];
	NXGetMemoryBuffer(stream, &buffer, &length, &maxlen);
	NX_ZONEMALLOC([self zone], data, char, length);
	bcopy(buffer, data, length);
	NXCloseMemory(stream, NX_FREEBUFFER);
	oldBounds = bounds;
	[textObject getFrame:&bounds];
	[editView convertRect:&bounds toView:graphicView];	// ack!
	NXUnionRect(&bounds, &oldBounds);
	[graphicView cache:&oldBounds];
	[[graphicView window] flushWindow];
	[graphicView dirty];
    } else {
	[graphicView removeGraphic:self];
    }

    [[self class] perform:@selector(hideRuler:) with:graphicView afterDelay:500 cancelPrevious:YES];

    [textObject removeFromSuperview];
    [textObject setDelegate:nil];
    [textObject setSel:0 :0];
    font = [textObject font];

    return self;
}

/* Archiving methods */

- awake
{
    initClassVars();
    return [super awake];
}

- write:(NXTypedStream *)stream
 /*
  * Writes the TextGraphic out to the typed stream.
  */
{
    [super write:stream];
    NXWriteTypes(stream, "i", &length);
    NXWriteArray(stream, "c", length, data);
    return self;
}

- read:(NXTypedStream *)stream
 /*
  * Reads the TextGraphic in from the typed stream.
  * This is versioned.  The old way we used to implement
  * this class included using a Cell object.  Now we
  * use the Text object directly.
  */
{
    id cell;
    int maxlen;
    NXStream *s;
    char *buffer;

    [super read:stream];
    if (NXTypedStreamClassVersion(stream, [self name]) < 1) {
	NXReadTypes(stream, "@", &cell);
	[drawText setText:[cell stringValue]];
	font = [cell font];
	[drawText setFont:[cell font]];
	[drawText setTextColor:[self lineColor]];
	s = NXOpenMemory(NULL, 0, NX_WRITEONLY);
	[drawText writeRichText:s];
	NXGetMemoryBuffer(s, &buffer, &length, &maxlen);
	NX_ZONEMALLOC([self zone], data, char, length);
	bcopy(buffer, data, length);
	NXCloseMemory(s, NX_FREEBUFFER);
    } else {
	NXReadTypes(stream, "i", &length);
	NX_ZONEMALLOC([self zone], data, char, length);
	NXReadArray(stream, "c", length, data);
    }

    return self;
}

@end
