#import "textundo.h"

@interface UndoText(PrivateMethods)

- getSelBeforeKeydown:(int *)start :(int *)end;

@end

#define FONT_OPERATION NXLocalStringFromTable("Operations", "Font", NULL, "The user action of changing the font of a bunch of selected graphical entities.")
#define CUT_OPERATION NXLocalStringFromTable("Operations", "Cut", NULL, "The operation of cutting some graphical entities out of the document and putting them on the Pasteboard.")
#define TEXT_COLOR_OPERATION NXLocalStringFromTable("Operations", "Text Color", NULL, "The operation of changing the color of some text.")
#define TEXT_GRAY_OPERATION NXLocalStringFromTable("TextOperations", "Text Gray", NULL, "The operation of changing the gray of some text.")
#define SET_FONT_OPERATION NXLocalStringFromTable("TextOperations", "Set Font", NULL, "The operation of changing the font of some text.")
#define FONT_SIZE_OPERATION NXLocalStringFromTable("TextOperations", "Font Size", NULL, "The operation of changing the size of some text.")
#define PASTE_FONT_OPERATION NXLocalStringFromTable("TextOperations", "Paste Font", NULL, "The operation of setting the font of some text based on the copy/paste font mechanism.")
#define PASTE_RULER_OPERATION NXLocalStringFromTable("TextOperations", "Paste Ruler", NULL, "The operation of setting the paragraph properties of some text based on the copy/paste ruler mechanism.")
#define SUBSCRIPT_OPERATION NXLocalStringFromTable("TextOperations", "Subscript", NULL, "The operation of subscripting some text.")
#define SUPERSCRIPT_OPERATION NXLocalStringFromTable("TextOperations", "Superscript", NULL, "The operation of superscripting some text.")
#define UNDERLINE_OPERATION NXLocalStringFromTable("TextOperations", "Underline", NULL, "The operation of underlining some text.")
#define UNSCRIPT_OPERATION NXLocalStringFromTable("TextOperations", "Unscript", NULL, "The operation of eliminat2fany super or subscripting of some text.")
#define TAB_MOVE_OPERATION NXLocalStringFromTable("TextOperations", "Tab Move", NULL, "The operation of changing the tab stops in some text.")
#define ALIGN_LEFT_OPERATION NXLocalStringFromTable("TextOperations", "Align Left", NULL, "The operation of aligning a paragraph to the left.")
#define ALIGN_RIGHT_OPERATION NXLocalStringFromTable("TextOperations", "Align Right", NULL, "The operation of aligning a paragraph to the right.")
#define CENTER_OPERATION NXLocalStringFromTable("TextOperations", "Center", NULL, "The operation of centering some text.")
#define JUSTIFY_OPERATION NXLocalStringFromTable("TextOperations", "Justify", NULL, "The operation of left and right justifying some text.")
#define FIRST_INDENT_OPERATION NXLocalStringFromTable("TextOperations", "First Indent", NULL, "The operation of changing the first indent of some text.")
#define INDENT_OPERATION NXLocalStringFromTable("TextOperations", "Indent", NULL, "The operation of changing the indentation of some text.")
#define NEW_TAB_OPERATION NXLocalStringFromTable("TextOperations", "New Tab", NULL, "The operation of adding a tab stop to some text.")
#define DELETE_TAB_OPERATION NXLocalStringFromTable("TextOperations", "Delete Tab", NULL, "The operation of deleting a tab stop in some text.")
#define LEFT_MARGIN_OPERATION NXLocalStringFromTable("TextOperations", "Left Margin", NULL, "The operation of changing the left margin on some text.")
#define RIGHT_MARGIN_OPERATION NXLocalStringFromTable("TextOperations", "Right Margin", NULL, "The operation of changing the right margin on some text.")
#define PARAGRAPH_OPERATION NXLocalStringFromTable("TextOperations", "Paragraph Change", NULL, "The operation of changing some general paragraph properties of some text.")
#define REPLACE_OPERATION NXLocalStringFromTable("TextOperations", "Replace", NULL, "The operation of replace a run of text.")

@implementation UndoText

/*
 * This subclass of Text overrides methods that allow the user to change the
 * information stored in a Text object. Before one of these changes, a Change
 * object is created to record what is about to happen. The change object is
 * given control (to record the current state of the text) before and after
 * the change actually happens.
 *
 * Keystroke changes are handled by a special filter fun2gn which looks at
 * the characters as they are entered. This class is plug compatible with
 * the Text class. If you use an UndoText in the presence of a ChangeManager,
 * the undo mechanism will work automatically.
 */

#define DELETE_KEY (0x08)

static char *undoFilterFunc(UndoText *self, unsigned char *text,
			    int *len, int position)
{
    id change;
    unsigned char *p;
    int chars;

    if (self->oldTextFilter) {
	text = (unsigned char *) (*self->oldTextFilter)(self, text,
							len, position);
    }

    change = [[TypingTextChange alloc] initView:self];
    [change startChange];
	for (p = text, chars = *len; chars; ++p, --chars) {
	    if (*p == DELETE_KEY) {
		[change deleteCharacter];
	    } else {
		[change addCharacter:(int) *p];
	    }
	}
    [change endChange];

    return ((char *)text);
}

static BOOL selIsEmpty(UndoText *self)
{    
    return (self->sp0.cp == self->spN.cp);
}

- initFrame:(const NXRect *)frameRect
{
    [super initFrame:frameRect];
    oldTextFilter = [self textFilter];
    [super setTextFilter:&undoFilterFunc];

    return self;
}

/*
 * This is a utility method that deletes the current selection without
 * creating a Change object. This is primarily used by TextSelections so
 * that they can quietly remove themselves.
 */

- eraseSelection
{
    [super delete:self];
    return self;
}

/*
 * We need to provide access to the clickCount because the behavior of
 * delete: changes when clickCount > 1. This has to do with the smart
 * deletion of words. TextSelections get and set the clickCount.
 */

- (int)clickCount
{
    return clickCount;
}

- setClickCount:(int)count
{
    clickCount = count;
    return self;
}

/*
 * The text object will change the selection after the keyDown: but before
 * the filter function gets control. We simply preserve the original selection
 * for later use.
 */

- keyDown:(NXEvent *)event
{
    NXSelPt start, end;

    [self getSel:&start :&end];

    startBeforeKeydown = start.cp;
    endBeforeKeydown = end.cp;

    return [super keyDown:event];
}

- setTextFilter:(NXTextFilterFunc)aFunc
{
    oldTextFilter = aFunc;
    return self;
}

/* Methods that create change objects */

- clear:sender
{
    return [self delete:sender];
}

- cut:sender
{
    id change;
    id retval;

    change = [[TextSelChange alloc] initView:self name:CUT_OPE2hON];

    [change startChange];
        retval = [super cut:sender];
    [change endChange];

    return retval;
}

- delete:sender
{
    id change;
    id retval = nil;

    if (!selIsEmpty(self)) {
	change = [[DeleteTextChange alloc] initView:self];
	[change startChange];
	    retval = [super delete:sender];
	[change endChange];
    }

    return retval;
}

static id pasteboard = nil;

- paste:sender
{
    id change;
    id retval;
    BOOL canPaste;
    const NXAtom *types;

    if (pasteboard == nil) {
	pasteboard = [Pasteboard new];
    }

    for (canPaste = NO, types = [pasteboard types]; *types; types++) {
	if(*types == NXAsciiPboardType || *types == NXRTFPboardType) {
	    canPaste = YES;
	    break;
	}
    }
    
    if (canPaste) {
	change = [[PasteTextChange alloc] initView:self];
	[change startChange];
            retval = [super paste:sender];
	[change endChange];
    } else {
	retval = nil;
    }

    return retval;
}

- setSelGray:(float)value
{
    id change;
    id retval;

    change = [[TextSelColorChange alloc] initView:self name:TEXT_GRAY_OPERATION];
    [change startChange];
        retval = [super setSelGray:value];
    [change endChange];

    return retval;
}

- setSelColor:(NXColor)color
{
    id change;
    id retval;

    change = [[TextSelColorChange alloc] initView:self name:TEXT_COLOR_OPERATION];
    [change startChange];
        retval = [super setSelColor:color];
    [change endChange];

    return retval;
}

- setSelFont:fontId
{
    id change;
    id retval;

    change = [[TextSelChange alloc] initView:self name:SET_FONT_OPERATION];
    [change startChange];
        retval = [super setSelFont:fontId];
    [change endChange];

    return retval;
}

- setSelFontSize:(float)size
{
    id change;
    id retval;

    change = [[TextSelChange alloc] initView:self name:FONT_SIZE_OPERATION];
    [change startChange];
        retval = [super setSelFontSize:size];
    [change endChange];

    return retval;
}

- setSelFontFamily:(const char *)fontName
{
    return [super setSelFontFamily:fontName];
}

- pasteFont:sender
{
    id change;
    id retval;

    change = [[WholeTextChange alloc] initView:self name:PASTE_FONT_OPERATION];

    [change startChange];
        retval = [super pasteFont:sender];
    [change endChange];

    return retval;
}

- pasteRuler:sender
{
    id change2i  id retval;

    change = [[WholeTextChange alloc] initView:self name:PASTE_RULER_OPERATION];

    [change startChange];
        retval = [super pasteRuler:sender];
    [change endChange];

    return retval;
}

- subscript:sender
{
    id change;
    id retval;

    change = [[TextSelChange alloc] initView:self name:SUBSCRIPT_OPERATION];
    [change startChange];
        retval = [super subscript:sender];
    [change endChange];

    return retval;
}

- superscript:sender
{
    id change;
    id retval;

    change = [[TextSelChange alloc] initView:self name:SUPERSCRIPT_OPERATION];
    [change startChange];
        retval = [super superscript:sender];
    [change endChange];

    return retval;
}

- underline:sender
{
    id change;
    id retval;

    change = [[TextSelChange alloc] initView:self name:UNDERLINE_OPERATION];
    [change startChange];
        retval = [super underline:sender];
    [change endChange];

    return retval;
}

- unscript:sender
{
    id change;
    id retval;

    change = [[TextSelChange alloc] initView:self name:UNSCRIPT_OPERATION];
    [change startChange];
        retval = [super unscript:sender];
    [change endChange];

    return retval;
}

- changeFont:sender
{
    id change;
    id retval;

    change = [[TextSelChange alloc] initView:self name:FONT_OPERATION];
    [change startChange];
        retval = [super changeFont:sender];
    [change endChange];

    return retval;
}

- changeTabStopAt:(NXCoord)oldX to:(NXCoord)newX
{
    id change;
    id retval;

    change = [[WholeTextChange alloc] initView:self name:TAB_MOVE_OPERATION];

    [change startChange];
        retval = [super changeTabStopAt:oldX to:newX];
    [change endChange];

    return retval;
}

- setSelProp:(NXParagraphProp)prop to:(NXCoord)val
{
    id change;
    id retval;
    const char *name;

    switch(prop) {
      case NX_LEFTALIGN :
	name = ALIGN_LEFT_OPERATION;
	break;
      case NX_RIGHTALIGN :
	name = ALIGN_RIGHT_OPERATION;
	break;
      case NX_CENTERALIGN :
	name = CENTER_OPERATION;
	break;
      case NX_JUSTALIGN :
	name = JUSTIFY_OPERATION;
	break;
      case NX_FIRSTINDENT :
	name = FIRST_INDENT_OPERATION;
	break;
      case NX_INDENT :
	name = INDENT_OPERATION;
	break;
      case NX_ADDTAB :
	name = NEW_TAB_OPERATION;
	break;
      case NX_REMOVETAB :
	name = DELETE_TAB_OPERATION;
	bre2p      case NX_LEFTMARGIN :
	name = LEFT_MARGIN_OPERATION;
	break;
      case NX_RIGHTMARGIN :
	name = RIGHT_MARGIN_OPERATION;
	break;
      default :
	name = PARAGRAPH_OPERATION;
	break;
    }

    change = [[WholeTextChange alloc] initView:self name:name];

    [change startChange];
        retval = [super setSelProp:prop to:val];
    [change endChange];

    return retval;
}

- replaceSel:(char *)str
{
    id change;
    id retval;
    NXSelPt oldStart, oldEnd;
    NXSelPt newStart, newEnd;

    change = [[TextSelChange alloc] initView:self name:REPLACE_OPERATION];
    [change startChange];
	[super getSel:&oldStart :&oldEnd];
	retval = [super replaceSel:str];
	[super getSel:&newStart :&newEnd];
	[super setSel:oldStart.cp :newEnd.cp];
    [change endChange];
    [super setSel:newStart.cp :newEnd.cp];

    return retval;
}

- getSelBeforeKeydown:(int *)start :(int *)end
{
    *start = startBeforeKeydown;
    *end = endBeforeKeydown;

    return self;
}

@end
