/* 
   NSText.m

   The RTFD text class

   Copyright (C) 1996 Free Software Foundation, Inc.

   Author:  Scott Christley <scottc@net-community.com>
   Date: 1996
   Author:  Felipe A. Rodriguez <far@ix.netcom.com>
   Date: July 1998
   Author:  Daniel Bðhringer <boehring@biomed.ruhr-uni-bochum.de>
   Date: August 1998
  
   This file is part of the GNUstep GUI Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
   
   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/ 

//	doDo:	contact autor

#if !defined(ABS)	 
    #define ABS(A)	({ typeof(A) __a = (A); __a < 0 ? -__a : __a; })
#endif		// the definition in gstep-base produces warnings FIX ME FAR

#include <gnustep/gui/config.h>
#include <Foundation/NSString.h>

#include <AppKit/NSFileWrapper.h>
#include <AppKit/NSControl.h>
#include <AppKit/NSText.h>
#include <AppKit/NSApplication.h>
#include <AppKit/NSWindow.h>
#include <AppKit/NSFontPanel.h>
#include <AppKit/NSFont.h>
#include <AppKit/NSColor.h>
#include <AppKit/NSPasteboard.h>
#include <AppKit/NSSpellChecker.h>
#include <AppKit/NSDragging.h>
#include <AppKit/NSStringDrawing.h>

#include <Foundation/NSNotification.h>
#include <Foundation/NSArchiver.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSScanner.h>
#include <Foundation/NSData.h>

static NSRange MakeRangeFromAbs(int a1,int a2) // not the same as NSMakeRange!
{	if(a1< a2)	return NSMakeRange(a1,a2-a1);	
	else		return NSMakeRange(a2,a1-a2);
}

extern BOOL	NSEqualRanges(NSRange range1, NSRange range2);	// should define in base FIX ME FAR

#define HUGE 1e99

enum {
    NSBackspaceKey			= 8,
    NSCarriageReturnKey			= 13,
    NSDeleteKey				= 0x7f,
    NSBacktabKey			= 25
};

//*****************************************************************************
//
// 		_GNULineLayoutInfo 
//
//*****************************************************************************

@interface _GNULineLayoutInfo:NSObject
{	NSRange lineRange;
	NSRect	lineRect;
	float	drawingOffset;
	BOOL	dontDisplay;
	unsigned	type;
	NSString	*fingerprintString;	// used in lines before paragraphs for determining if update optimization is possible
}

typedef enum
{	// do not use 0 in order to secure calls to nil!
	LineLayoutInfoType_Text=1,
	LineLayoutInfoType_Paragraph=2
} _GNULineLayoutInfo_t;

+ lineLayoutWithRange:(NSRange) aRange rect:(NSRect) aRect drawingOffset:(float) anOffset type:(unsigned) aType;

-(NSRange)	lineRange;
-(NSRect)	lineRect;
-(float)	drawingOffset;
-(BOOL)		isDontDisplay;
-(unsigned)	type;

-(void)	setLineRange:(NSRange) aRange;
-(void)	setLineRect:(NSRect) aRect;
-(void)	setDrawingOffset:(float) anOffset;
-(void)	setDontDisplay:(BOOL) flag;
-(void)	setType:(unsigned) aType;
-(NSString*) fingerprintString;
-(void) setFingerprintString:(NSString*) aString;
-(BOOL) isLineTerminatingParagraph;

-(NSString*) description;
@end

@implementation _GNULineLayoutInfo

+ lineLayoutWithRange:(NSRange) aRange rect:(NSRect) aRect drawingOffset:(float) anOffset type:(unsigned) aType
{	id ret=[[[_GNULineLayoutInfo alloc] init] autorelease];
	[ret setLineRange:aRange]; [ret setLineRect:aRect]; [ret setDrawingOffset:anOffset]; [ret setType:aType];
	return ret;
}

-(BOOL)		isDontDisplay	{return dontDisplay;}
-(unsigned)		type	{return type;}
-(NSRange)	lineRange {return lineRange;}
-(NSRect)	lineRect {return lineRect;}
-(float)	drawingOffset {return drawingOffset;}
-(NSString*) fingerprintString {return fingerprintString;}

-(void)	setLineRange:(NSRange) aRange {lineRange= aRange;}
-(void)	setLineRect:(NSRect) aRect {lineRect= aRect;}
-(void)	setDrawingOffset:(float) anOffset {drawingOffset= anOffset;}
-(void)	setDontDisplay:(BOOL) flag		{dontDisplay=flag;}
-(void)	setType:(unsigned) aType		{type=aType;}

-(void) setFingerprintString:(NSString*) aString
{	ASSIGN(fingerprintString,aString);
}

-(NSString*) description
{	return [[NSDictionary dictionaryWithObjectsAndKeys:	NSStringFromRange(lineRange),@"LineRange",
														NSStringFromRect(lineRect),@"LineRect",
														fingerprintString,@"fingerprint",
														nil] description];
}

-(BOOL) isLineTerminatingParagraph {return type == LineLayoutInfoType_Paragraph && lineRect.origin.x> 0;}	// sort of hackish

-(void) dealloc
{	if(fingerprintString) [fingerprintString release];
	[super dealloc];
}
@end

// end: _GNULineLayoutInfo ----------------------------------------------------

//*****************************************************************************
//
// 		_GNUTextScanner 
//
//*****************************************************************************

// NeXT's NSScanner's scanCharactersFromSet and friends seem to be a bit sluggish on whitespaces and newlines
//(have not tried GNUstep-base implementation though). so here is a more pedantic implementation:

// ithis class should be considered private since it is not polished at all!


@interface _GNUTextScanner:NSObject
{	NSString	   *string;
	NSCharacterSet *set,*iSet;
	unsigned		stringLength;
	NSRange	  		activeRange;
}
+(_GNUTextScanner*) scannerWithString:(NSString*) aStr set:(NSCharacterSet*) aSet invertedSet:(NSCharacterSet*) anInvSet;
-(void)				setString:(NSString*) aString set:(NSCharacterSet*) aSet invertedSet:(NSCharacterSet*) anInvSet;
-(NSRange)			_scanCharactersInverted:(BOOL) inverted;
-(NSRange)			scanSetCharacters;
-(NSRange)			scanNonSetCharacters;
-(BOOL)				isAtEnd;
-(unsigned)			scanLocation;
-(void)				setScanLocation:(unsigned) aLoc;
@end

@implementation _GNUTextScanner
+(_GNUTextScanner*) scannerWithString:(NSString*) aStr set:(NSCharacterSet*) aSet invertedSet:(NSCharacterSet*) anInvSet
{	_GNUTextScanner *ret=[[self alloc] init];
	[ret setString:aStr set:aSet invertedSet:anInvSet];
	return [ret autorelease];
}

-(void) setString:(NSString*) aString set:(NSCharacterSet*) aSet invertedSet:(NSCharacterSet*) anInvSet
{	ASSIGN(string,aString); stringLength=[string length]; activeRange=NSMakeRange(0,stringLength);
	ASSIGN(set,aSet); ASSIGN(iSet,anInvSet);
}

-(NSRange) _scanCharactersInverted:(BOOL) inverted
{	NSRange range=NSMakeRange(activeRange.location,0);
	NSCharacterSet *currentSet= inverted? iSet:set;
	NSCharacterSet *currentISet= inverted? set:iSet;

	if(activeRange.location>= stringLength) return range;
	if([currentSet characterIsMember:[string characterAtIndex:activeRange.location]])
	{	range=[string rangeOfCharacterFromSet:currentSet options:0 range:activeRange];
	}
	if (range.length)
	{	NSRange iRange=range;
		iRange=[string rangeOfCharacterFromSet:currentISet options:0 range:MakeRangeFromAbs(NSMaxRange(range),stringLength)];
		if(iRange.length)	range=MakeRangeFromAbs(range.location,iRange.location);
		else				range=MakeRangeFromAbs(range.location,stringLength);
		activeRange=MakeRangeFromAbs(NSMaxRange(range),stringLength);
	}
	return range;
}

-(NSRange) scanSetCharacters
{	return [self _scanCharactersInverted:NO];
}
-(NSRange) scanNonSetCharacters
{	return [self _scanCharactersInverted:YES];
}

-(BOOL) isAtEnd
{
	return activeRange.location>= stringLength;
}
-(unsigned) scanLocation {return activeRange.location;}
-(void) setScanLocation:(unsigned) aLoc 
{ 
	activeRange=MakeRangeFromAbs(aLoc,stringLength);
}

-(void) dealloc
{	[string release];
	[set release];
	[iSet release];
	[super dealloc];
}
@end

// end: _GNUTextScanner implementation ----------------------------------------

@interface NSAttributedString(DrawingAddition)
-(NSSize) sizeRange:(NSRange) aRange;
-(void) drawRange:(NSRange) aRange atPoint:(NSPoint) aPoint;
@end

@implementation NSAttributedString(DrawingAddition)
-(NSSize) sizeRange:(NSRange) currRange
{	NSRect				 retRect=NSZeroRect;
	NSRange				 lineRange=currRange;
	NSPoint				 currPoint;
	NSString			*string=[self string];

	for(; NSMaxRange(currRange)< NSMaxRange(lineRange);)	// draw all "runs"
	{	NSDictionary *attributes=[self attributesAtIndex:NSMaxRange(currRange) longestEffectiveRange:&currRange inRange:lineRange];
		NSString	 *substring=[string substringWithRange:currRange];
		NSRect		  sizeRect=NSZeroRect;
		sizeRect.size=[substring sizeWithAttributes:attributes];
		retRect=NSUnionRect(retRect,sizeRect);
		currPoint.x+=sizeRect.size.width;
	} return retRect.size;
}
-(void) drawRange:(NSRange) currRange atPoint:(NSPoint) aPoint
{	NSRange				 lineRange=currRange;
	NSPoint				 currPoint;
	NSString			*string=[self string];

	for(currPoint=aPoint; NSMaxRange(currRange)< NSMaxRange(lineRange);)	// draw all "runs"
	{	NSDictionary *attributes=[self attributesAtIndex:NSMaxRange(currRange) longestEffectiveRange:&currRange inRange:lineRange];
		NSString	 *substring=[string substringWithRange:currRange];
		[substring drawAtPoint:currPoint withAttributes:attributes];
		currPoint.x+=[substring sizeWithAttributes: attributes].width;
	}
}
@end

@interface _GNUSeekableArrayEnumerator:NSObject
{	unsigned currentIndex;
	NSArray	*array;
}
- initWithArray:(NSArray*) anArray;
- nextObject;
- previousObject;
- currentObject;
@end

@implementation _GNUSeekableArrayEnumerator

- initWithArray:(NSArray*) anArray
{	[super init];
	array=[anArray retain];
	return self;
}
- nextObject
{	if(currentIndex >= [array count]) return nil;
	return [array objectAtIndex:currentIndex++];
}
- previousObject
{	if(!currentIndex) return nil;
	return [array objectAtIndex:--currentIndex];
}
- currentObject
{	return [array objectAtIndex:currentIndex];
}

-(void) dealloc
{	[array release];
	[super dealloc];
}
@end
@interface NSArray(SeekableEnumerator)
-(_GNUSeekableArrayEnumerator*) seekableEnumerator;
@end
@implementation NSArray(SeekableEnumerator)
-(_GNUSeekableArrayEnumerator*) seekableEnumerator
{	return [[[_GNUSeekableArrayEnumerator alloc] initWithArray:self] autorelease];
}
@end

//*****************************************************************************
//
// 		NSText 
//
//*****************************************************************************

@implementation NSText

//
// Class methods
//
+ (void)initialize
{
	if (self == [NSText class])
	{	[self setVersion:1];						// Initial version
	}
}

//<!>
//this is sort of a botch up: rtf should be spilled out here in order to be OPENSTEP-compatible
// the way to go is surely implementing -(NSData *)RTFDFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict; and friends.
// (NeXT OPENSTEP additions to NSAttributedString)
//
// but on the other hand: since rtf is MS-technology, simply let us declare the NSArchiver generated data stream output being the GNU rich text format ;-)
+(NSData*) dataForAttributedString:(NSAttributedString*) aString
{	return [NSArchiver archivedDataWithRootObject:aString];
}

//this is sort of a botch up: a rtf parser should come in here in order to be OPENSTEP-compatible
// but on the other hand: since rtf is MS-technology, simply let us declare the NSArchiver generated data stream output being the GNU rich text format ;-)
// return value is guaranteed to be a NSAttributedString even if data is only NSString
+(NSAttributedString*) attributedStringForData:(NSData*) aData
{	id erg=[NSUnarchiver unarchiveObjectWithData:aData];
	if(![erg isKindOfClass:[NSAttributedString class]])
		return [[[NSAttributedString alloc] initWithString:erg] autorelease];
	else return erg;
}

+(NSString*) newlineString {return @"\n";}

//
// Instance methods
//
//
// Initialization
//

- init
{
	return [self initWithFrame:NSMakeRect(0,0,100,100)];
}

- initWithFrame:(NSRect)frameRect
{	[super initWithFrame:frameRect];
	
	alignment = NSLeftTextAlignment;
	is_editable = YES;
	[self setRichText:NO];	// sets up the contents object
	is_selectable = YES;
	imports_graphics = NO;
	uses_font_panel = NO;
	is_horizontally_resizable =NO;
	is_vertically_resizable = YES;
	is_ruler_visible = NO;
	is_field_editor = NO;
	draws_background = YES;
	[self setBackgroundColor:[NSColor whiteColor]];
	[self setTextColor:[NSColor blackColor]];
	default_font = [NSFont userFontOfSize:12];

	[self setSelectionWordGranularitySet:[NSCharacterSet characterSetWithCharactersInString:@" "]];	//[NSCharacterSet whitespaceCharacterSet]
	[self setSelectionParagraphGranularitySet:[NSCharacterSet characterSetWithCharactersInString:[[self class] newlineString]]];

	[self setMinSize:frameRect.size];
	[self setMaxSize:NSMakeSize(HUGE,HUGE)];

	[self setString:@"Text"];
	[self setSelectedRange:NSMakeRange(0,0)];
	return self;
}
-(NSDictionary*) defaultTypingAttributes
{	return [NSDictionary dictionaryWithObjectsAndKeys:	default_font,NSFontAttributeName,
														text_color,NSForegroundColorAttributeName,
														nil];
}


// <!>
// handle font pasteboard as well!
// handle ruler pasteboard as well!
-(BOOL) performPasteOperation:(NSPasteboard	*)pboard
{
// color accepting
	if([pboard availableTypeFromArray:[NSArray arrayWithObject:NSColorPboardType]])
	{	NSColor	*color=[NSColor colorFromPasteboard:pboard];
		if([self isRichText])
		{	[self setTextColor:color range:[self selectedRange]];
		} else [self setTextColor:color];
		return YES;
	}

	if([self importsGraphics])
	{	NSArray *types=[NSArray arrayWithObjects:NSFileContentsPboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, NSTIFFPboardType, nil];
		if([[pboard availableTypeFromArray:types] isEqualToString:NSRTFDPboardType])
		{	[self insertText:[[self class] attributedStringForData:[pboard dataForType:NSRTFDPboardType]]];
		} else if([[pboard availableTypeFromArray:types] isEqualToString:NSRTFPboardType])
		{	[self insertText:[[self class] attributedStringForData:[pboard dataForType:NSRTFPboardType]]];
		} else if([[pboard availableTypeFromArray:types] isEqualToString:NSStringPboardType])
		{	[self insertText:[pboard stringForType:NSStringPboardType]];
			return YES;
		}
	} else if([self isRichText])
	{	NSArray *types=[NSArray arrayWithObjects:NSRTFPboardType, NSStringPboardType,nil];
		if([[pboard availableTypeFromArray:types] isEqualToString:NSRTFPboardType])
		{	[self insertText:[[self class] attributedStringForData:[pboard dataForType:NSRTFPboardType]]];
		} else if([[pboard availableTypeFromArray:types] isEqualToString:NSStringPboardType])
		{	[self insertText:[pboard stringForType:NSStringPboardType]];
			return YES;
		}
	} else	// plain text
	{	NSArray *types=[NSArray arrayWithObjects:NSStringPboardType, nil];
		if([[pboard availableTypeFromArray:types] isEqualToString:NSStringPboardType])
		{	[self insertText:[pboard stringForType:NSStringPboardType]];
			return YES;
		}
	} return NO;
}


// begin: dragging of colors and files ---------------
-(unsigned int) draggingEntered:(id <NSDraggingInfo>)sender
{	return NSDragOperationGeneric;
}
-(unsigned int) draggingUpdated:(id <NSDraggingInfo>)sender
{	return NSDragOperationGeneric;
}
-(void) draggingExited:(id <NSDraggingInfo>)sender
{
}
-(BOOL) prepareForDragOperation:(id <NSDraggingInfo>)sender
{	return YES;
}

-(BOOL) performDragOperation:(id <NSDraggingInfo>)sender
{	return [self performPasteOperation:[sender draggingPasteboard]];
}
-(void) concludeDragOperation:(id <NSDraggingInfo>)sender
{
}
// end: drag accepting ---------------------------------

- (void)dealloc 
{	[self unregisterDraggedTypes];
	[background_color release];
	[default_font release];
	[text_color release];

	[plainContent release];
	[rtfContent release];

    [super dealloc];
}

-(NSArray*) acceptableDragTypes
{	NSMutableArray *ret=[NSMutableArray arrayWithObjects:NSStringPboardType, NSColorPboardType, nil];

	if([self isRichText])			[ret addObject:NSRTFPboardType];
	if([self importsGraphics])		[ret addObject:NSRTFDPboardType];
	return ret;
}

-(void) updateDragTypeRegistration
{	[self registerForDraggedTypes:[self acceptableDragTypes]];
}

-(NSRange) selectionRangeForProposedRange:(NSRange)proposedCharRange granularity:(NSSelectionGranularity)granularity
{	NSCharacterSet	*set=nil;
	unsigned		 lastIndex=[self textLength]-1,lpos=MIN(lastIndex,proposedCharRange.location),
					 rpos=NSMaxRange(proposedCharRange);
	NSString		*string=[self string];
	BOOL			 rmemberstate,lmemberstate;

	if(![string length]) return NSMakeRange(0,0);

	switch(granularity)
	{	case NSSelectByCharacter: return NSIntersectionRange(proposedCharRange,NSMakeRange(0,[self textLength]+1));
		case NSSelectByWord:
			set=selectionWordGranularitySet;
		break;
		case NSSelectByParagraph:
			set=selectionParagraphGranularitySet;
		break;
	}
	// now work on set...
	lmemberstate=[set characterIsMember:[string characterAtIndex:lpos]];
	rmemberstate=[set characterIsMember:[string characterAtIndex:MIN(rpos,lastIndex)]];
	while (rpos<= lastIndex && [set characterIsMember:[string characterAtIndex:rpos]]== rmemberstate) rpos++;

	while(lpos && [set characterIsMember:[string characterAtIndex:lpos]]== lmemberstate) lpos--;
	return MakeRangeFromAbs(lpos,rpos);
}


//
// Getting and Setting Contents 
//

// low level (no selection handling, relayout or display)
-(void) replaceRange:(NSRange)range withAttributedString:(NSAttributedString*)attrString
{	if([self isRichText])
	{	return [rtfContent replaceCharactersInRange:range withAttributedString:attrString];
	} else return [plainContent replaceCharactersInRange:range withString:[attrString string]];
}
-(void) replaceRange:(NSRange)range withRTFD:(NSData *)rtfdData
{	return [self replaceRange:range withAttributedString:[[self class] attributedStringForData:rtfdData]];
}

-(void) replaceRange:(NSRange)range withRTF:(NSData*) rtfData
{	[self replaceRange:range withRTFD:rtfData];
}

-(void) replaceRange:(NSRange)range withString:(NSString*) aString
{	if([self isRichText])
	{	return [rtfContent replaceCharactersInRange:range withString:aString];
	} else return [plainContent replaceCharactersInRange:range withString:aString];
}

-(void) setText:(NSString*) aString range:(NSRange) aRange
{	[self replaceRange:(NSRange)aRange withString:aString];
}

-(NSData*) RTFDFromRange:(NSRange)range
{	if([self isRichText])
	{	return [[self class] dataForAttributedString:[rtfContent attributedSubstringFromRange:range]];
	} else return nil;
}

-(NSData*) RTFFromRange:(NSRange)range
{	return [self RTFDFromRange:range];
}

-(void) setString:(NSString *)string
{	[plainContent release];
	plainContent=[[NSMutableString stringWithString:string] retain];
	[lineLayoutInformation autorelease]; lineLayoutInformation=nil;	// force complete re-layout
	[self setRichText:NO];

	//[self display];
}
-(void) setText:(NSString *)string {[self setString:string];}


-(NSString*) string
{	if([self isRichText])	return [rtfContent string];
	else					return plainContent;
}
-(NSString*) text		{return [self string];}

//
// Managing Global Characteristics
//
-(NSTextAlignment)alignment		{ return alignment; }
-(BOOL) drawsBackground				{ return draws_background; }
-(BOOL) importsGraphics				{ return imports_graphics; }
-(BOOL) isEditable					{ return is_editable; }
-(BOOL) isRichText					{ return is_rich_text; }
-(BOOL) isSelectable				{ return is_selectable; }

- (void)setAlignment:(NSTextAlignment)mode
{	alignment = mode;
}

- (void)setDrawsBackground:(BOOL)flag
{	draws_background = flag;
}

- (void)setEditable:(BOOL)flag
{	is_editable = flag;
	if (flag) is_selectable = YES;	// If we are editable then we  are selectable
}

- (void)setImportsGraphics:(BOOL)flag
{	imports_graphics = flag;

	[self updateDragTypeRegistration];
}

-(void) setRichText:(BOOL)flag
{	is_rich_text = flag;
	if(flag)
	{	if(!rtfContent) rtfContent=[[NSMutableAttributedString alloc] initWithString:plainContent? (NSString*)plainContent:@"" attributes:[self defaultTypingAttributes]];
		[self rebuildRichLineLayoutInformationStartingAtLine:0];
	} else
	{	if(!plainContent) plainContent=[[NSMutableString alloc] initWithString:rtfContent? [rtfContent string]:@""];
		[self rebuildPlainLineLayoutInformationStartingAtLine:0];
	}

	[self updateDragTypeRegistration];

	[self sizeToFit];
	[self display];
}

- (void)setSelectable:(BOOL)flag
{	is_selectable = flag;
	if (!flag) is_editable = NO;					// If we are not selectable then we must not be editable
}

//
// Managing Font and Color
//
-(NSColor*) backgroundColor		{ return background_color; }
-(NSFont*) font					{ return default_font; }
-(NSColor*) textColor				{ return text_color; }
-(BOOL) usesFontPanel				{ return uses_font_panel; }


// This action method changes the font of the selection for a rich text object, or of all text for a plain text object. If the receiver doesn't use the Font Panel, however, this method does nothing.
-(void) changeFont:sender
{	if([self usesFontPanel])
	{	if([self isRichText])
		{	NSRange selectedRange=[self selectedRange], searchRange=selectedRange,foundRange;
			int maxSelRange;

			for(maxSelRange=NSMaxRange(selectedRange); searchRange.location< maxSelRange;
				searchRange=NSMakeRange(NSMaxRange(foundRange),maxSelRange-NSMaxRange(foundRange)))
			{	NSFont *font=[rtfContent attribute:NSFontAttributeName atIndex:searchRange.location longestEffectiveRange:&foundRange inRange:searchRange];
				if(font)
				{	[self setFont:[sender convertFont:font] ofRange:foundRange];
				}
			}
		} else
		{	[self setFont:[sender convertFont:[[self defaultTypingAttributes] objectForKey:NSFontAttributeName]]];
		}
	}
}

-(void) setSelectionWordGranularitySet:(NSCharacterSet*) aSet
{	ASSIGN(selectionWordGranularitySet, aSet);
}
-(void) setSelectionParagraphGranularitySet:(NSCharacterSet*) aSet
{	ASSIGN(selectionParagraphGranularitySet, aSet);
}


- (void)setBackgroundColor:(NSColor *)color
{	ASSIGN(background_color, color);
}
-(void) setTypingAttributes:(NSDictionary*) dict
{	if(![dict isKindOfClass:[NSMutableDictionary class]])
	{	[typingAttributes autorelease];
		typingAttributes=[[NSMutableDictionary alloc] initWithDictionary:dict];	// do not autorelease!
	} else ASSIGN(typingAttributes, (NSMutableDictionary*)dict);
}
-(NSMutableDictionary*) typingAttributes
{	if(typingAttributes) return typingAttributes;
	else return [NSMutableDictionary dictionaryWithDictionary:[self defaultTypingAttributes]];
}

- (void)setTextColor:(NSColor *)color range:(NSRange)range
{	if([self isRichText])
	{	if(color) [rtfContent addAttribute:NSForegroundColorAttributeName value:color range:range];
	} else {}

}
-(void) setColor:(NSColor *)color ofRange:(NSRange)range
{	[self setTextColor:color range:range];
}

- (void)setFont:(NSFont *)obj
{	ASSIGN(default_font, obj);
}

- (void)setFont:(NSFont *)font ofRange:(NSRange)range
{	if([self isRichText])
	{	if(font) [rtfContent addAttribute:NSFontAttributeName value:font range:range];
	} else {}
}

- (void)setTextColor:(NSColor *)color
{	ASSIGN(text_color,color);

	if(![self isRichText]) [self display];
}

- (void)setUsesFontPanel:(BOOL)flag
{	uses_font_panel = flag;
}

//
// Managing the Selection
//
- (NSRange)selectedRange			{ return selected_range; }



-(BOOL) shouldDrawInsertionPoint
{	return ([self selectedRange].length==0) && [self isEditable];
}
-(void) drawInsertionPointInRect:(NSRect)rect color:(NSColor *)color turnedOn:(BOOL)flag
{	if(flag)
	{	[color set]; NSRectFill(rect);
	} else
	{	[[self backgroundColor] set]; NSRectFill(rect);
	}
}
-(void) drawInsertionPointAtIndex:(unsigned)index color:(NSColor *)color turnedOn:(BOOL)flag
{	NSRect		startRect=[self rectForCharacterIndex:index];
	[self drawInsertionPointInRect:NSMakeRect(startRect.origin.x, startRect.origin.y,1.0,startRect.size.height)
							 color:[NSColor blackColor] turnedOn:flag];
}


-(void) drawSelectionAsRangeNoCaret:(NSRange) aRange
{	if(aRange.length)
	{	NSRect		startRect=[self rectForCharacterIndex:aRange.location],
					endRect=[self rectForCharacterIndex:NSMaxRange(aRange)];
		if(startRect.origin.y == endRect.origin.y)	// single line selection
		{	NSHighlightRect(NSMakeRect(startRect.origin.x,startRect.origin.y,endRect.origin.x-startRect.origin.x, startRect.size.height));
		} else if(startRect.origin.y == endRect.origin.y-endRect.size.height)	// two line selection
		{	NSHighlightRect((NSMakeRect(startRect.origin.x,startRect.origin.y,[self frame].size.width-startRect.origin.x,startRect.size.height)));	// first line
			NSHighlightRect(NSMakeRect(0,endRect.origin.y,endRect.origin.x,endRect.size.height));	// second line
		} else	//   3 Rects: multiline selection
		{	NSHighlightRect(((NSMakeRect(startRect.origin.x,startRect.origin.y,[self frame].size.width-startRect.origin.x,startRect.size.height))));	// first line
			NSHighlightRect(NSMakeRect(0,NSMaxY(startRect),[self frame].size.width,endRect.origin.y-NSMaxY(startRect)));	// intermediate lines
			NSHighlightRect(NSMakeRect(0,endRect.origin.y,endRect.origin.x,endRect.size.height));	// last line
		}
	}
}
-(void) drawSelectionAsRange:(NSRange) aRange
{	if(aRange.length)
	{	[self drawSelectionAsRangeNoCaret:aRange];
	} else [self drawInsertionPointAtIndex:aRange.location color:[NSColor blackColor] turnedOn:YES];
}

-(void) setSelectedRangeNoDrawing:(NSRange)range
{
#if 0
	[[NSNotificationCenter defaultCenter] postNotificationName:NSTextViewDidChangeSelectionNotification object:self
											userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSStringFromRange(selected_range),NSOldSelectedCharacterRange,
																								nil]];
#endif
	selected_range = range;
}


-(void) setSelectedRange:(NSRange)range
{	BOOL	didLock=NO;

	if(![self window])
		return;

	if([[self class] focusView] != self)
	{	[self lockFocus];
		didLock=YES;
	}

	if(selected_range.length== 0)	// remove old cursor
	{	[self drawInsertionPointAtIndex:selected_range.location color:nil turnedOn:NO];
	} else
	{	[self drawSelectionAsRange:selected_range];
	}

	[self setSelectedRangeNoDrawing:range];

	if([self usesFontPanel])	// update fontPanel
	{	BOOL isMultiple=NO;
		NSFont	*currentFont=nil;
		if([self isRichText])
		{	// if(are multiple fonts in selection) isMultiple=YES;
			// else currentFont=[rtfContent attribute:NSFontAttributeName atIndex:range.location longestEffectiveRange:NULL inRange:range]
		} else currentFont=[[self defaultTypingAttributes] objectForKey:NSFontAttributeName];
		[[NSFontPanel sharedFontPanel] setPanelFont:currentFont isMultiple:isMultiple];
	}
	// display
	if(range.length)
	{	
		// <!>disable caret timed entry
	} else	// no selection
	{	if([self isRichText])
		{	[self setTypingAttributes:[NSMutableDictionary dictionaryWithDictionary:[rtfContent attributesAtIndex:range.location effectiveRange:NULL]]];
		}
		// <!>enable caret timed entry
	}
	[self drawSelectionAsRange:range];
	[self scrollRangeToVisible:range];

	if(didLock)
	{	[self unlockFocus];
		[[self window] flushWindow];
	}
}

//
// Sizing the Frame Rectangle
//
-(void) setFrame:(NSRect)frameRect
{	[super setFrame:frameRect];
}

-(BOOL) isHorizontallyResizable		{ return is_horizontally_resizable; }
-(BOOL) isVerticallyResizable		{ return is_vertically_resizable; }
-(NSSize) maxSize 					{ return maxSize; }
-(NSSize) minSize					{ return minSize; }

- (void)setHorizontallyResizable:(BOOL)flag
{	is_horizontally_resizable = flag;
}

- (void)setMaxSize:(NSSize)newMaxSize
{	maxSize=newMaxSize;
}

- (void)setMinSize:(NSSize)newMinSize
{	minSize=newMinSize;
}

-(void) setVerticallyResizable:(BOOL)flag
{	is_vertically_resizable = flag;
}

-(unsigned) textLength
{	if([self isRichText]) return [rtfContent length];
	else return [plainContent length];
}

-(NSRect) boundingRectForLineRange:(NSRange)lineRange
{	NSArray *linesToDraw=[lineLayoutInformation subarrayWithRange:lineRange];
	NSEnumerator		*lineEnum;
	_GNULineLayoutInfo	*currentInfo;
	NSRect			 retRect=NSMakeRect(0,0,0,0);

	for(lineEnum=[linesToDraw objectEnumerator]; (currentInfo=[lineEnum nextObject]);)
	{	retRect=NSUnionRect(retRect,[currentInfo lineRect]);
	} return retRect;
}

-(void) disableDisplay {displayDisabled=YES;}
-(void) reenableDisplay {displayDisabled=NO;}

-(void) sizeToFit
{	NSRect sizeToRect=[self frame];
	if([self isHorizontallyResizable])
	{	if([lineLayoutInformation count])
		{	sizeToRect=[self boundingRectForLineRange:NSMakeRange(0,[lineLayoutInformation count])];
		} else sizeToRect.size=minSize;
	} else if([self isVerticallyResizable])
	{	if([lineLayoutInformation count])
		{	NSRect rect=NSUnionRect([[lineLayoutInformation objectAtIndex:0] lineRect],
									[[lineLayoutInformation lastObject] lineRect]);
			float	newHeight=rect.size.height;

			if([[lineLayoutInformation lastObject] type] == LineLayoutInfoType_Paragraph && NSMaxY(rect)<= newHeight)
				newHeight+=[[lineLayoutInformation lastObject] lineRect].size.height;

			newHeight=MIN(maxSize.height,MAX(newHeight,minSize.height));
			sizeToRect=NSMakeRect(sizeToRect.origin.x,sizeToRect.origin.y-(newHeight-sizeToRect.size.height),
									sizeToRect.size.width,newHeight);
		} else sizeToRect=NSMakeRect(0,0,minSize.width,minSize.height);
	}
	if(!NSEqualSizes([self frame].size,sizeToRect.size))
	{	[self setFrame:sizeToRect];	//[self setFrameSize:sizeToRect.size];
	}
}
-(void) sizeToFit:sender {[self sizeToFit];}

//
// Responding to Editing Commands
//
-(void) alignCenter:sender
{
}

-(void) alignLeft:sender
{
}

-(void) alignRight:sender
{
}

-(void) selectAll:sender
{	[self setSelectedRange:NSMakeRange(0,[self textLength])];
}

-(void) subscript:sender
{
}

-(void) superscript:sender
{
}

-(void) underline:sender
{	if([self isRichText])
	{	BOOL doUnderline=YES;
		if([[rtfContent attribute:NSUnderlineStyleAttributeName atIndex:[self selectedRange].location effectiveRange:NULL] intValue])	doUnderline=NO;

		if([self selectedRange].length)
		{	[rtfContent addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt:doUnderline] range:[self selectedRange]];
			//[self rebuildLineLayoutInformationStartingAtLine:lineIndex];
			//[self displayRect:NSUnionRect([[lineLayoutInformation objectAtIndex:lineIndex] lineRect],[[lineLayoutInformation lastObject] lineRect])];
		} else [[self typingAttributes] setObject:[NSNumber numberWithInt:doUnderline] forKey: NSUnderlineStyleAttributeName];
	}
}

-(void) unscript:sender
{	if([self isRichText])
	{	if([self selectedRange].length)
		{	[rtfContent removeAttribute:NSUnderlineStyleAttributeName range:[self selectedRange]];
			//[self rebuildLineLayoutInformationStartingAtLine:lineIndex];
			//[self displayRect:NSUnionRect([[lineLayoutInformation objectAtIndex:lineIndex] lineRect],[[lineLayoutInformation lastObject] lineRect])];
		} else [[self typingAttributes] removeObjectForKey:NSUnderlineStyleAttributeName];
	}
}

//
// Managing the Ruler
//
-(BOOL) isRulerVisible				{ return NO; }

-(void) toggleRuler:sender
{
}


//
// Scrolling
//

-(void) scrollRangeToVisible:(NSRange)range
{	[self scrollRectToVisible:NSUnionRect([self rectForCharacterIndex:[self selectedRange].location],
										  [self rectForCharacterIndex:NSMaxRange([self selectedRange])])];
}

//
// Reading and Writing RTFD Files
//
-(BOOL) readRTFDFromFile:(NSString *)path
{	NSData *data=[NSData dataWithContentsOfFile:path];
	id		peek;
	if(data && (peek=[[self class] attributedStringForData:data]))
	{	is_rich_text=YES;	// not [self setRichText:YES] for efficiancy reasons
		[self updateDragTypeRegistration];
		[self replaceRange:NSMakeRange(0,[self textLength]) withAttributedString:peek];
		[self rebuildLineLayoutInformationStartingAtLine:0];
		[self display];
		return YES;
	}
	return NO;
}

-(BOOL) writeRTFDToFile:(NSString *)path atomically:(BOOL)flag
{	if([self isRichText])
	{	NSFileWrapper *wrapper=[[[NSFileWrapper alloc] initRegularFileWithContents:[[self class] dataForAttributedString:rtfContent]] autorelease];
		return [wrapper writeToFile:path atomically:flag updateFilenames:YES];
	} return NO;
}

//
// Managing the Field Editor
//
-(BOOL) isFieldEditor				{ return is_field_editor; }

-(void) setFieldEditor:(BOOL)flag
{	is_field_editor = flag;
}

-(int) lineLayoutIndexForCharacterIndex:(unsigned) anIndex
{	NSEnumerator		*lineEnum;
	_GNULineLayoutInfo	*currentInfo;

	if([lineLayoutInformation count] && anIndex>= NSMaxRange([[lineLayoutInformation lastObject] lineRange]))
		return [lineLayoutInformation count]-1;

	for(lineEnum=[lineLayoutInformation objectEnumerator]; (currentInfo=[lineEnum nextObject]);)
	{	NSRange lineRange=[currentInfo lineRange];
		if(lineRange.location<= anIndex && anIndex<= NSMaxRange(lineRange)-([currentInfo type] == LineLayoutInfoType_Paragraph? 1:0))
			return [lineLayoutInformation indexOfObject:currentInfo];
	}
	if([lineLayoutInformation count]) NSLog(@"NSText's lineLayoutIndexForCharacterIndex: index out of bounds!");
	return 0;
}

//<!> choose granularity according to keyboard modifier flags

-(void) moveCursorUp:sender
{	unsigned	cursorIndex;
	NSPoint		cursorPoint;

	if([self selectedRange].length) currentCursorX=[self rectForCharacterIndex:[self selectedRange].location].origin.x;
	cursorIndex=[self selectedRange].location;
	cursorPoint=[self rectForCharacterIndex:cursorIndex].origin;
	cursorIndex=[self characterIndexForPoint:NSMakePoint(currentCursorX+0.001,MAX(0,cursorPoint.y-0.001))];
	[self setSelectedRange:[self selectionRangeForProposedRange:NSMakeRange(cursorIndex,0) granularity:NSSelectByCharacter]];
}
-(void) moveCursorDown:sender
{	unsigned			cursorIndex;
	NSRect				cursorRect;

	if([self selectedRange].length) currentCursorX=[self rectForCharacterIndex:NSMaxRange([self selectedRange])].origin.x;
	cursorIndex=[self selectedRange].location;
	cursorRect=[self rectForCharacterIndex:cursorIndex];
	cursorIndex=[self characterIndexForPoint:NSMakePoint(currentCursorX+0.001,NSMaxY(cursorRect)+0.001)];
	[self setSelectedRange:[self selectionRangeForProposedRange:NSMakeRange(cursorIndex,0) granularity:NSSelectByCharacter]];
}
-(void) moveCursorLeft:sender
{	[self setSelectedRange:[self selectionRangeForProposedRange:NSMakeRange([self selectedRange].location-1,0) granularity:NSSelectByCharacter]];
	currentCursorX=[self rectForCharacterIndex:[self selectedRange].location].origin.x;
}
-(void) moveCursorRight:sender
{	[self setSelectedRange:[self selectionRangeForProposedRange:NSMakeRange(MIN(NSMaxRange([self selectedRange])+1,[self textLength]),0)
			   granularity:NSSelectByCharacter]];
	currentCursorX=[self rectForCharacterIndex:[self selectedRange].location].origin.x;
}

//
// Handling Events 
//
-(void) mouseDown:(NSEvent *)theEvent
{	NSSelectionGranularity granularity= NSSelectByCharacter;
	NSRange					chosenRange,prevChosenRange,proposedRange;
	NSPoint					point,startPoint;
	NSEvent				   *currentEvent;
	unsigned				startIndex;
	BOOL					didDragging=NO;

	if (!is_selectable) return;						// If not selectable then don't recognize the mouse down

	[[self window] makeFirstResponder:self];

	switch([theEvent clickCount])
	{	case 1: granularity=NSSelectByCharacter;
		break;
		case 2: granularity=NSSelectByWord;
		break;
		case 3: granularity=NSSelectByParagraph;
		break;
	}

	startPoint=[self convertPoint:[theEvent locationInWindow] fromView:nil];
	startIndex=[self characterIndexForPoint:startPoint];

	proposedRange=NSMakeRange(startIndex,0);
	chosenRange=prevChosenRange=[self selectionRangeForProposedRange:proposedRange granularity:granularity];

	[self lockFocus];

// clean up before doing the dragging
	if([self selectedRange].length== 0)	// remove old cursor
	{	[self drawInsertionPointAtIndex:[self selectedRange].location color:nil turnedOn:NO];
	} else [self drawSelectionAsRangeNoCaret:[self selectedRange]];

//<!> make this non-blocking (or make use of timed entries)
	for(currentEvent= [[self window] nextEventMatchingMask:NSLeftMouseDraggedMask|NSLeftMouseUpMask|NSMouseMovedMask]; [currentEvent type] != NSLeftMouseUp;
	   (currentEvent= [[self window] nextEventMatchingMask:NSLeftMouseDraggedMask|NSLeftMouseUpMask|NSMouseMovedMask]), prevChosenRange=chosenRange)	// run modal loop
	{	BOOL	didScroll=[self autoscroll:currentEvent];
		point = [self convertPoint:[currentEvent locationInWindow] fromView:nil];
		proposedRange=MakeRangeFromAbs([self characterIndexForPoint:point],startIndex);
		chosenRange=[self selectionRangeForProposedRange:proposedRange granularity:granularity];

		if(NSEqualRanges(prevChosenRange,chosenRange))
		{	if(!didDragging)
			{	[self drawSelectionAsRangeNoCaret:chosenRange];
				[[self window] flushWindow];
			}
			else continue;
		}

	// this  changes the selection without needing instance drawing (carefully thought out ;-) 
		if(!didScroll)
		{	[self drawSelectionAsRangeNoCaret:MakeRangeFromAbs(MIN(chosenRange.location, prevChosenRange.location),
				MAX(chosenRange.location, prevChosenRange.location))];
			[self drawSelectionAsRangeNoCaret:MakeRangeFromAbs(MIN(NSMaxRange(chosenRange),NSMaxRange(prevChosenRange)),
				MAX(NSMaxRange(chosenRange),NSMaxRange(prevChosenRange)))];
			[[self window] flushWindow];
		} else
		{	[self drawRectNoSelection:[self visibleRect]];
			[self drawSelectionAsRangeNoCaret:chosenRange];
			[[self window] flushWindow];
		}

		didDragging=YES;
	}
	[self setSelectedRangeNoDrawing:chosenRange];
	if(!didDragging) [self drawSelectionAsRange:chosenRange];
	else if(chosenRange.length== 0) [self drawInsertionPointAtIndex:chosenRange.location color:[NSColor blackColor] turnedOn:YES];

	currentCursorX=[self rectForCharacterIndex:chosenRange.location].origin.x;	// remember for column stable cursor up/down

	[self unlockFocus];
	[[self window] flushWindow];
}


-(void) redisplayForLineRange:(NSRange) redrawLineRange
{	BOOL	didLock=NO;

	if([self window] && [[self class] focusView] != self)
	{	[self lockFocus]; didLock=YES;
	}

	if([lineLayoutInformation count] && redrawLineRange.location < [lineLayoutInformation count] && redrawLineRange.length)
	{	_GNULineLayoutInfo *firstInfo=[lineLayoutInformation objectAtIndex:redrawLineRange.location];
		NSRect displayRect,firstRect=[firstInfo lineRect];
		if([firstInfo type] == LineLayoutInfoType_Paragraph && firstRect.origin.x >0 && redrawLineRange.location)
		{	redrawLineRange.location--;redrawLineRange.length++;
		}
		displayRect=NSUnionRect([[lineLayoutInformation objectAtIndex:redrawLineRange.location] lineRect],
								[[lineLayoutInformation objectAtIndex:MAX(0,(int)NSMaxRange(redrawLineRange)-1)] lineRect]);

		displayRect.size.width=[self frame].size.width-displayRect.origin.x;
		[[self backgroundColor] set]; NSRectFill(displayRect);

		if([self isRichText])
		{	[self drawRichLinesInLineRange:redrawLineRange];
		} else
		{	[self drawPlainLinesInLineRange:redrawLineRange];
		}
		[self drawSelectionAsRange:[self selectedRange]];
	}

	if([self drawsBackground])	// clean up the remaining area under text of us
	{	float	lowestY=0;
		NSRect	frame=[self frame];

		if([lineLayoutInformation count]) lowestY=NSMaxY([[lineLayoutInformation lastObject] lineRect]);

		if(![lineLayoutInformation count] || (lowestY < NSMaxY(frame) && frame.size.height<= [self minSize].height))
		{	[[self backgroundColor] set];
			NSRectFill(NSMakeRect(0,lowestY,frame.size.width,NSMaxY(frame)-lowestY));
			if(![lineLayoutInformation count] || [[lineLayoutInformation lastObject] type] == LineLayoutInfoType_Paragraph)
				[self drawSelectionAsRange:[self selectedRange]];
		}
	} if(didLock)
	{	[self unlockFocus];
		[[self window] flushWindow];
	}
}

// central text inserting method (takes care of optimized redraw/ cursor positioning)

-(void) insertText:insertObjc
{	NSRange		selectedRange=[self selectedRange];
	int			lineIndex=[self lineLayoutIndexForCharacterIndex:selectedRange.location],origLineIndex=lineIndex,caretLineIndex=lineIndex;
	NSRange		redrawLineRange;
	BOOL		layoutIncomplete=YES;
	NSString	*insertString=nil;

	if([insertObjc isKindOfClass:[NSString class]]) insertString=insertObjc;
	else											insertString=[insertObjc string];


#if 1
	// speed optimization: newline insert: try patching change into layoutArray without actually relayouting the whole document
	// this is a "tour de force" but performance really counts here!

	if([insertString isEqualToString:[[self class] newlineString]] && lineIndex< [lineLayoutInformation count]-1 && !selectedRange.length)
	{	BOOL	isLeftToPar=[[lineLayoutInformation objectAtIndex:lineIndex+1] type] == LineLayoutInfoType_Paragraph &&
							[[lineLayoutInformation objectAtIndex:lineIndex+1] lineRange].location == selectedRange.location,
				isRightToPar=(lineIndex && [[lineLayoutInformation objectAtIndex:lineIndex-1] type] == LineLayoutInfoType_Paragraph) &&
							([[lineLayoutInformation objectAtIndex:lineIndex] type] == LineLayoutInfoType_Paragraph),
				isLeftToLine=(selectedRange.location== [[lineLayoutInformation objectAtIndex:lineIndex] lineRange].location && 
							 (lineIndex>1 && [[lineLayoutInformation objectAtIndex:lineIndex-1] type] == LineLayoutInfoType_Paragraph &&
							  [[lineLayoutInformation objectAtIndex:lineIndex-2] type] == LineLayoutInfoType_Paragraph));

		if(isRightToPar || isLeftToPar || isLeftToLine)
		{	int insertIndex=lineIndex;
			_GNULineLayoutInfo *templateInfo=nil,*newInfo;

			if(!isRightToPar && isLeftToPar)
			{	templateInfo=[lineLayoutInformation objectAtIndex:lineIndex+1];
				insertIndex++; lineIndex+=2;
			} else if(isRightToPar)
			{	templateInfo=[lineLayoutInformation objectAtIndex:lineIndex];
				lineIndex++;
			} else if(isLeftToLine)
			{	templateInfo=[lineLayoutInformation objectAtIndex:lineIndex-1];
				insertIndex--;
			}

			[lineLayoutInformation insertObject:newInfo=[_GNULineLayoutInfo lineLayoutWithRange:
							[templateInfo lineRange] rect:[templateInfo lineRect] drawingOffset:[templateInfo drawingOffset]
							type:[templateInfo type]] atIndex:insertIndex];

		//	remodel copied paragraph object to be a normal one (not line-terminating)
			if(!isRightToPar && isLeftToPar)
			{	_GNULineLayoutInfo *changeInfo=[lineLayoutInformation objectAtIndex:lineIndex];
				NSRect				rect=[changeInfo lineRect];
				[changeInfo setLineRect:NSMakeRect(0,rect.origin.y,[self frame].size.width,rect.size.height)];
			}
			// relocate (ylocation  and linerange) the lines below
			{	NSArray		 *relocArray=[lineLayoutInformation subarrayWithRange:NSMakeRange(lineIndex,[lineLayoutInformation count]-lineIndex)];
				NSEnumerator *relocEnum;
				_GNULineLayoutInfo	*currReloc;
				int	relocOffset=[insertString length];
				NSSize advanceSize=[insertString sizeWithAttributes:[self isRichText]? (NSDictionary*)[self typingAttributes]: [self defaultTypingAttributes]];

				for(relocEnum=[relocArray objectEnumerator]; (currReloc=[relocEnum nextObject]);)
				{	NSRange range=[currReloc lineRange];
					NSRect	rect=[currReloc lineRect];
					[currReloc setLineRange:NSMakeRange(range.location+relocOffset,range.length)];
					[currReloc setLineRect:NSMakeRect(rect.origin.x,rect.origin.y+advanceSize.height,rect.size.width,rect.size.height)];
				}
			} layoutIncomplete=NO;
		}
	}
	// end: speed optimization ------------------------------------------------------------
#endif

	// in case e.g a space is inserted and a word actually shortened: redraw previous line to give it the chance to move up 
	if([lineLayoutInformation count] && [[lineLayoutInformation objectAtIndex:origLineIndex] type] != LineLayoutInfoType_Paragraph &&
		origLineIndex && [[lineLayoutInformation objectAtIndex:origLineIndex-1] type] != LineLayoutInfoType_Paragraph)
		origLineIndex--;

	redrawLineRange=MakeRangeFromAbs(origLineIndex,[lineLayoutInformation count]);

	if([self isRichText])
	{	NSDictionary *attributes=[rtfContent attributesAtIndex:[self selectedRange].location effectiveRange:NULL]; 
		[self replaceRange:[self selectedRange]
			withAttributedString:[insertObjc isKindOfClass:[NSAttributedString class]]? insertObjc:
				[[[NSAttributedString alloc] initWithString:insertString attributes:[self typingAttributes]] autorelease]];
		if(layoutIncomplete)
			redrawLineRange.length=[self rebuildRichLineLayoutInformationStartingAtLine:redrawLineRange.location
																				  delta:[insertString length]-selectedRange.length
																			 actualLine:caretLineIndex];
		[self setTypingAttributes:attributes];
	} else
	{	[self replaceRange:[self selectedRange] withString:insertString];
		if(layoutIncomplete)
			redrawLineRange.length=[self rebuildPlainLineLayoutInformationStartingAtLine:redrawLineRange.location
																				   delta:[insertString length]-selectedRange.length
																			  actualLine:caretLineIndex];
	}

	[self sizeToFit];			// ScrollView interaction

	[self setSelectedRange:NSMakeRange([self selectedRange].location+[insertString length],0)];	// move cursor <!> [self selectionRangeForProposedRange:]
	currentCursorX=[self rectForCharacterIndex:[self selectedRange].location].origin.x;		// remember x for row-stable cursor movements

	redrawLineRange=NSIntersectionRange(redrawLineRange,[self lineRangeForRect:[self visibleRect]]);
	[self redisplayForLineRange:redrawLineRange];
	[self textDidChange:nil];	// broadcast notification
}

// central text deletion/backspace method (takes care of optimized redraw/ cursor positioning)
-(void) deleteRange:(NSRange) aRange backspace:(BOOL) flag
{	int		redrawLineIndex,caretLineIndex,firstLineIndex,lastLineIndex,linePosition;
	NSRange	redrawLineRange;
	NSRange deleteRange;
	BOOL	layoutIncomplete=YES;

	if(!aRange.length && !flag) return;
	if(!aRange.location && ! aRange.length) return;

	if(aRange.length) {	deleteRange=aRange;linePosition=deleteRange.location;}
	else			  {	deleteRange=NSMakeRange(MAX(0,aRange.location-1),1);linePosition=NSMaxRange(deleteRange); }

	firstLineIndex=caretLineIndex=[self lineLayoutIndexForCharacterIndex:linePosition];
	lastLineIndex=[self lineLayoutIndexForCharacterIndex:NSMaxRange(deleteRange)];
	redrawLineIndex=MAX(0,firstLineIndex-1);	// since first word may move upward

	if(firstLineIndex && [[lineLayoutInformation objectAtIndex:firstLineIndex-1] type] == LineLayoutInfoType_Paragraph)
	{	_GNULineLayoutInfo  *upperInfo=[lineLayoutInformation objectAtIndex:firstLineIndex],
							*prevInfo=[lineLayoutInformation objectAtIndex:firstLineIndex-1];

		if(linePosition> [upperInfo lineRange].location)		 redrawLineIndex++;	// no danger of word moving up
		else if([prevInfo lineRect].origin.x > 0)				 redrawLineIndex--;	// remove newline: skip paragraph-terminating infoObject
		redrawLineIndex=MAX(0,redrawLineIndex);
	}

	redrawLineIndex=MIN(redrawLineIndex,[lineLayoutInformation count]-1);
	redrawLineRange=MakeRangeFromAbs(redrawLineIndex,[lineLayoutInformation count]);

	if([self isRichText])
	{	NSDictionary *attributes=[rtfContent attributesAtIndex:deleteRange.location effectiveRange:NULL]; 
		[rtfContent deleteCharactersInRange:deleteRange];
		if(layoutIncomplete)
			redrawLineRange.length=[self rebuildRichLineLayoutInformationStartingAtLine:redrawLineRange.location
																				  delta:-deleteRange.length
																			 actualLine:caretLineIndex];
		[self setTypingAttributes:attributes];
	} else
	{	[plainContent deleteCharactersInRange:deleteRange];
		if(layoutIncomplete)
			redrawLineRange.length=[self rebuildPlainLineLayoutInformationStartingAtLine:redrawLineRange.location
																				   delta:-deleteRange.length
																			  actualLine:caretLineIndex];
	}

	[self sizeToFit];			// ScrollView interaction

	[self setSelectedRange:NSMakeRange(deleteRange.location,0)];	// move cursor <!> [self selectionRangeForProposedRange:]
	currentCursorX=[self rectForCharacterIndex:[self selectedRange].location].origin.x;		// remember x for row-stable cursor movements
	redrawLineRange=NSIntersectionRange(redrawLineRange,[self lineRangeForRect:[self visibleRect]]);
	[self redisplayForLineRange:redrawLineRange];

	[self textDidChange:nil];	// broadcast notification
}


-(void) keyDown:(NSEvent *)theEvent
{	
unsigned short keyCode;

	if(!is_editable) 
		return; 		// If not editable then don't  recognize the key down

fprintf(stderr, " NSText keyDown \n");	
	
	if((keyCode=[theEvent keyCode])) 
		switch(keyCode)
			{	
			case NSUpArrowFunctionKey:
				[self moveCursorUp:self];
				return;
			case NSDownArrowFunctionKey:
				[self moveCursorDown:self];
				return;
			case NSLeftArrowFunctionKey:
				[self moveCursorLeft:self];
				return;
			case NSRightArrowFunctionKey:
				[self moveCursorRight:self];
				return;
			case NSBackspaceKey:
				[self deleteRange:[self selectedRange] backspace:YES];
				return;
#if 1
		case 0x6d:	// ende-taste för debugging: enforce complete re-layout

			[lineLayoutInformation autorelease]; lineLayoutInformation=nil;
			[self rebuildLineLayoutInformationStartingAtLine:0];
			[self display];
			return;
#endif
#if 1
			case 0x45:	// num-lock: debugging
				NSLog([lineLayoutInformation description]);
				return;
#endif
			case NSCarriageReturnKey:	// return
				if([self isFieldEditor])	//textShouldEndEditing delegation 
					{					// is handled in resignFirstResponder
#if 0
// Movement codes for movement between fields; these codes are the intValue of the NSTextMovement key in NSTextDidEndEditing notifications
					[NSNumber numberWithInt:NSIllegalTextMovement]
					[NSNumber numberWithInt:NSReturnTextMovement]
					[NSNumber numberWithInt:NSTabTextMovement]
					[NSNumber numberWithInt:NSBacktabTextMovement]
					[NSNumber numberWithInt:NSLeftTextMovement]
					[NSNumber numberWithInt:NSRightTextMovement]
					[NSNumber numberWithInt:NSUpTextMovement]
					[NSNumber numberWithInt:NSDownTextMovement]
#endif

					[[self window] makeFirstResponder:[self nextResponder]];
					[self textDidEndEditing:nil];	//[NSNumber numberWithInt:NSReturnTextMovement]
					} 
				else
					{	
					[self insertText:[[self class] newlineString]];
fprintf(stderr, " NSText keyDown return \n");		
			[lineLayoutInformation autorelease]; lineLayoutInformation=nil;
			[self rebuildLineLayoutInformationStartingAtLine:0];
					return;
					}
				break;
			}
#if 0
NSLog(@"keycode:%x",keyCode);
#endif
	{
fprintf(stderr, " NSText keyDown insertText \n");		
		// else
		[self insertText:[theEvent characters]];
	}
}

-(void) keyDownO:(NSEvent *)theEvent
{	unsigned short keyCode;
	if(!is_editable) return; 					// If not editable then don't  recognize the key down

fprintf(stderr, " NSText keyDown \n");		
	if((keyCode=[theEvent keyCode])) switch(keyCode)
	{	case 	0x64:	//NSUpArrowFunctionKey:
			[self moveCursorUp:self];
		return;
		case 	0x65:	//NSDownArrowFunctionKey:
			[self moveCursorDown:self];
		return;
		case 	0x66:	//NSLeftArrowFunctionKey:
			[self moveCursorLeft:self];
		return;
		case 	0x67:	//NSRightArrowFunctionKey:
			[self moveCursorRight:self];
		return;
		case 0x0e:	// backspace
			[self deleteRange:[self selectedRange] backspace:YES];
		return;
#if 1
		case 0x6d:	// ende-taste för debugging: enforce complete re-layout

			[lineLayoutInformation autorelease]; lineLayoutInformation=nil;
			[self rebuildLineLayoutInformationStartingAtLine:0];
			[self display];
		return;
#endif
#if 1
		case 0x45:	// num-lock: debugging
			NSLog([lineLayoutInformation description]);
		return;
#endif
		case 0x1c:	// return
			if([self isFieldEditor])	//textShouldEndEditing delegation is handled in resignFirstResponder
			{
#if 0
// Movement codes for movement between fields; these codes are the intValue of the NSTextMovement key in NSTextDidEndEditing notifications
				[NSNumber numberWithInt:NSIllegalTextMovement]
				[NSNumber numberWithInt:NSReturnTextMovement]
				[NSNumber numberWithInt:NSTabTextMovement]
				[NSNumber numberWithInt:NSBacktabTextMovement]
				[NSNumber numberWithInt:NSLeftTextMovement]
				[NSNumber numberWithInt:NSRightTextMovement]
				[NSNumber numberWithInt:NSUpTextMovement]
				[NSNumber numberWithInt:NSDownTextMovement]
#endif

				[[self window] makeFirstResponder:[self nextResponder]];
				[self textDidEndEditing:nil];	//[NSNumber numberWithInt:NSReturnTextMovement]
			} else
			{	[self insertText:[[self class] newlineString]];
fprintf(stderr, " NSText keyDown return \n");		
				return;
			}
		break;
	}
#if 0
NSLog(@"keycode:%x",keyCode);
#endif
	{
		// else
		[self insertText:[theEvent characters]];
	}
}

-(BOOL) acceptsFirstResponder
{	if ([self isSelectable]) return YES;
	else return NO;
}

-(BOOL) resignFirstResponder
{	if([self shouldDrawInsertionPoint])
	{	[self drawInsertionPointAtIndex:[self selectedRange].location color:nil turnedOn:NO];
		//<!> stop timed entry
	}
	if([self isEditable]) return [self textShouldEndEditing:(NSText*)self];
	return YES;
}
-(BOOL) becomeFirstResponder
{	if([self shouldDrawInsertionPoint])
	{	[self drawInsertionPointAtIndex:[self selectedRange].location color:[NSColor blackColor] turnedOn:YES];
		//<!> restart timed entry
	}
	if([self isEditable] && [self textShouldBeginEditing:(NSText*)self]) return YES;
	else return NO;
}

//
// Managing the Delegate
//
- delegate						{ return delegate; }

-(void) setDelegate:anObject
{	delegate = anObject;
}

//
// Implemented by the Delegate
//

-(void) textDidBeginEditing:(NSNotification *)aNotification
{	if ([delegate respondsToSelector:@selector(textDidBeginEditing:)])
    	[delegate textDidBeginEditing:aNotification? aNotification:[NSNotification notificationWithName:NSTextDidBeginEditingNotification object:self]];

	[[NSNotificationCenter defaultCenter] postNotificationName:NSTextDidBeginEditingNotification object:self];
}

- (void)textDidChange:(NSNotification *)aNotification
{	if ([delegate respondsToSelector:@selector(textDidChange:)])
		[delegate textDidChange:aNotification? aNotification:[NSNotification notificationWithName:NSTextDidChangeNotification object:self]];

	[[NSNotificationCenter defaultCenter] postNotificationName:NSTextDidChangeNotification object:self];
}

-(void)textDidEndEditing:(NSNotification *)aNotification
{	if ([delegate respondsToSelector:@selector(textDidEndEditing:)])
    	[delegate textDidEndEditing:aNotification? aNotification:[NSNotification notificationWithName:NSTextDidEndEditingNotification object:self]];

	[[NSNotificationCenter defaultCenter] postNotificationName:NSTextDidEndEditingNotification object:self];
}

-(BOOL) textShouldBeginEditing:(NSText *)textObject
{	if ([delegate respondsToSelector:@selector(textShouldBeginEditing:)])
    	return [delegate textShouldBeginEditing:(NSText*)self];
	else return YES;
}

-(BOOL) textShouldEndEditing:(NSText *)textObject
{	if ([delegate respondsToSelector:@selector(textShouldEndEditing:)])
    	return [delegate textShouldEndEditing:(NSText*)self];
	else return YES;
}


//
// Displaying
//

//<!>(currently unused; use code from drawRect:)
-(NSRange) characterRangeForBoundingRect:(NSRect)bounds
{	if([self isRichText]) return NSMakeRange(0,[plainContent length]);
	else return NSMakeRange(0,[rtfContent length]);
}


-(unsigned) characterIndexForPoint:(NSPoint)point
{	int					 i;
	NSEnumerator		*lineEnum;
	_GNULineLayoutInfo	*currentInfo;
	NSDictionary		*attributes=[self defaultTypingAttributes];

	if(point.y >= NSMaxY([[lineLayoutInformation lastObject] lineRect])) return [self textLength];

	point.x=MAX(0,point.x); point.y=MAX(0,point.y);

	for(i=0,lineEnum=[lineLayoutInformation objectEnumerator]; (currentInfo=[lineEnum nextObject]);i++)
	{	NSRect rect=[currentInfo lineRect];
		if(NSMaxY(rect)>=point.y && rect.origin.y<point.y && rect.origin.x< point.x && point.x >= NSMaxX(rect) ) return NSMaxRange([currentInfo lineRange]);
		if(NSPointInRect(point,rect))	// this loop holds some optimization potential (linear search)
		{	int		retPos=0;
			NSRange range=[currentInfo lineRange];

			for(retPos=range.location; retPos<=NSMaxRange(range); retPos++)		// this loop holds some optimization potential (linear search)
			{	NSString *evalString=nil;

				if([self isRichText])	// has to be done character wise
				{	if([rtfContent sizeRange:NSMakeRange(range.location,retPos-range.location)].width >= point.x) return MAX(0,retPos-1);
				} else
				{	evalString=[plainContent substringWithRange:NSMakeRange(range.location,retPos-range.location)];
					if([evalString sizeWithAttributes:attributes].width >= point.x) return MAX(0,retPos-1);
				}
			} return range.location;
		}
	} NSLog(@"NSText's characterIndexForPoint: index not found!");
	return 0;
}

// rect to the end of line
-(NSRect) rectForCharacterIndex:(unsigned) index
{	int					 i;
	NSEnumerator		*lineEnum;
	_GNULineLayoutInfo	*currentInfo;
	NSDictionary		*attributes=[self defaultTypingAttributes];

	if(![lineLayoutInformation count]) return NSMakeRect(0,0,[self frame].size.width,[[[self class] newlineString] sizeWithAttributes:attributes].height);

	if(index >= NSMaxRange([[lineLayoutInformation lastObject] lineRange]))
	{	NSRect rect=[[lineLayoutInformation lastObject] lineRect];
		if(NSMaxX(rect)>= [self frame].size.width)
		{	return NSMakeRect(0, NSMaxY(rect),[self frame].size.width,rect.size.height);
		}
		return NSMakeRect(NSMaxX(rect), rect.origin.y,[self frame].size.width-NSMaxX(rect),rect.size.height);
	}

	for(i=0,lineEnum=[lineLayoutInformation objectEnumerator]; (currentInfo=[lineEnum nextObject]);i++)
	{	NSRange	range=[currentInfo lineRange];
		if(NSLocationInRange(index,range))
		{	NSRect rect=[currentInfo lineRect];
			if([self isRichText])	// has to be done character wise
			{	NSSize	  stringSize=[rtfContent sizeRange:MakeRangeFromAbs(range.location,index)];
				float	  x=rect.origin.x+stringSize.width;
				return NSMakeRect(x,rect.origin.y,NSMaxX(rect)-x,rect.size.height);
			} else
			{	NSString *evalString=[plainContent substringWithRange:MakeRangeFromAbs(range.location,index)];
				NSSize	  stringSize=[evalString sizeWithAttributes:attributes];
				float	  x=rect.origin.x+stringSize.width;
				return NSMakeRect(x,rect.origin.y,NSMaxX(rect)-x,rect.size.height);
			}
		}
	} NSLog(@"NSText's rectForCharacterIndex: rect not found!");
	return NSZeroRect;
}

-(unsigned) lineLayoutIndexForPoint:(NSPoint)point
{	int					 i;
	NSEnumerator		*lineEnum;
	_GNULineLayoutInfo	*currentInfo;
	NSDictionary		*attributes=[self defaultTypingAttributes];

	if(point.y >= NSMaxY([[lineLayoutInformation lastObject] lineRect])) return [lineLayoutInformation count]-1;

	point.x=MAX(0,point.x); point.y=MAX(0,point.y);

	for(i=0,lineEnum=[lineLayoutInformation objectEnumerator]; (currentInfo=[lineEnum nextObject]);i++)
	{	NSRect rect=[currentInfo lineRect];
		if(NSMaxY(rect)> point.y && rect.origin.y<= point.y && rect.origin.x< point.x && point.x >= NSMaxX(rect) )
			return [lineLayoutInformation indexOfObject:currentInfo];
		if(NSPointInRect(point,rect))	// this loop holds some optimization potential (linear search)
		{	int		retPos=0;
			NSRange range=[currentInfo lineRange];

			for(retPos=range.location; retPos<=NSMaxRange(range); retPos++)		// this loop holds some optimization potential (linear search)
			{	NSString *evalString=nil;

				if([self isRichText])	// has to be done character wise
				{	if([rtfContent sizeRange:NSMakeRange(range.location,retPos-range.location)].width >= point.x)
						return [lineLayoutInformation indexOfObject:currentInfo];
				} else
				{	evalString=[plainContent substringWithRange:NSMakeRange(range.location,retPos-range.location)];
					if([evalString sizeWithAttributes:attributes].width >= point.x) return [lineLayoutInformation indexOfObject:currentInfo];
				}
			} return [lineLayoutInformation indexOfObject:currentInfo];
		}
	} return 0;
}

// internal method (not rich-text save: give it a range instead of a count)
-(void) addNewlines:(int) count intoLayoutArray:(NSMutableArray*) anArray attributes:(NSDictionary*) attributes atPoint:(NSPoint*) aPointP
		width:(float) width characterIndex:(unsigned) startingLineIndex ghostEnumerator:(NSEnumerator*) prevArrayEnum
{	NSSize advanceSize=[[[self class] newlineString] sizeWithAttributes:attributes];

	while(--count>=0)
	{	NSRect		currentLineRect=NSMakeRect(aPointP->x,aPointP->y,width-aPointP->x,advanceSize.height);
		[anArray addObject:[_GNULineLayoutInfo lineLayoutWithRange:
						NSMakeRange(startingLineIndex,1) rect:currentLineRect drawingOffset:0 type:LineLayoutInfoType_Paragraph]];
		//[[anArray lastObject] setDontDisplay:YES];
		startingLineIndex++; aPointP->x=0; aPointP->y+= advanceSize.height;

		if(prevArrayEnum && ![prevArrayEnum nextObject]) prevArrayEnum=nil;
	}
}


// begin: central line formatting method ---------------------------------------

// <!>
-(int) rebuildRichLineLayoutInformationStartingAtLine:(int) aLine delta:(int) insertionDelta actualLine:(int) insertionLineIndex
{
//- (NSDictionary *)attributesAtIndex:(unsigned)location longestEffectiveRange:(NSRange *)range inRange:(NSRange)rangeLimit;
// or even better: [[rtfContent substringWithRange:aRange] size] and the algorithm from plain text

	return 0;
}

// internal method
-(void) addNewlines:(NSRange) aRange intoLayoutArray:(NSMutableArray*) anArray attributes:(NSDictionary*) attributes atPoint:(NSPoint*) aPointP
		width:(float) width characterIndex:(unsigned) startingLineCharIndex ghostEnumerator:(_GNUSeekableArrayEnumerator*) prevArrayEnum
		didShift:(BOOL*) didShift verticalDisplacement:(float*) verticalDisplacement
{	NSSize advanceSize=[[[self class] newlineString] sizeWithAttributes:attributes];
	int		count=aRange.length,charIndex;
	_GNULineLayoutInfo *thisInfo,*ghostInfo=nil;
	BOOL	isRich=[self isRichText];

	(*didShift)=NO;

	for(charIndex=aRange.location;--count>=0;charIndex++)
	{	NSRect		currentLineRect=NSMakeRect(aPointP->x,aPointP->y,width-aPointP->x,advanceSize.height);
		[anArray addObject:thisInfo=[_GNULineLayoutInfo lineLayoutWithRange:
						NSMakeRange(startingLineCharIndex,1) rect:currentLineRect drawingOffset:0 type:LineLayoutInfoType_Paragraph]];

		if(isRich)
		{	advanceSize=[rtfContent sizeRange:NSMakeRange(charIndex,1)];
		}
		startingLineCharIndex++; aPointP->x=0; aPointP->y+= advanceSize.height;

		if(prevArrayEnum && !(ghostInfo=[prevArrayEnum nextObject])) prevArrayEnum=nil;

		if(ghostInfo && ([thisInfo type] != [ghostInfo type]))
		{	_GNULineLayoutInfo *prevInfo=[prevArrayEnum previousObject];
			prevArrayEnum=nil;
			(*didShift)=YES;
			(*verticalDisplacement)+=aPointP->y-[prevInfo lineRect].origin.y;
		}
	}
}

static unsigned _relocLayoutArray(NSMutableArray *lineLayoutInformation,NSArray *ghostArray,int aLine,int relocOffset,int rebuildLineDrift,float yReloc)
{	unsigned	  ret=[lineLayoutInformation count]-aLine;	// lines actually updated (optimized drawing)
	NSArray		 *relocArray=[ghostArray subarrayWithRange:MakeRangeFromAbs(MAX(0,ret+rebuildLineDrift),[ghostArray count])];
	NSEnumerator *relocEnum;
	_GNULineLayoutInfo	*currReloc;

	if(![relocArray count]) return ret;

	for(relocEnum=[relocArray objectEnumerator]; (currReloc=[relocEnum nextObject]);)
	{	NSRange range=[currReloc lineRange];
		[currReloc setLineRange:NSMakeRange(range.location+relocOffset,range.length)];
		if(yReloc)
		{	NSRect rect=[currReloc lineRect];
			[currReloc setLineRect:NSMakeRect(rect.origin.x,rect.origin.y+yReloc,rect.size.width,rect.size.height)];
		}
	}
	[lineLayoutInformation addObjectsFromArray:relocArray];
	return ret;
}


// returns count of lines actually updated
// actually only implemented for [self isVerticallyResizable] (but i think this is ok)
// <!> detachNewThreadSelector:selector toTarget:target withObject:argument;

-(int) rebuildPlainLineLayoutInformationStartingAtLine:(int) aLine delta:(int) insertionDelta actualLine:(int) insertionLineIndex
{	NSDictionary	   *attributes=[self defaultTypingAttributes];
	NSPoint				drawingPoint=NSZeroPoint;
   _GNUTextScanner	   *parscanner;
	float				width=[self frame].size.width;
	unsigned			startingIndex=0,currentLineIndex;
   _GNULineLayoutInfo  *lastValidLineInfo=nil;
	NSArray			   *ghostArray=nil;	// for optimization detection
   _GNUSeekableArrayEnumerator	   *prevArrayEnum=nil;
	NSCharacterSet	   *invSelectionWordGranularitySet=[selectionWordGranularitySet invertedSet];
	NSCharacterSet	   *invSelectionParagraphGranularitySet=[selectionParagraphGranularitySet invertedSet];
	NSString		   *parsedString;
	BOOL				isHorizontallyResizable=[self isHorizontallyResizable];
	int					lineDriftOffset=0,rebuildLineDrift=0;
	BOOL				frameshiftCorrection=NO,nlDidShift=NO,enforceOpti=NO;
	float				yDisplacement=0;
	BOOL				isRich=[self isRichText];

	if(!lineLayoutInformation) lineLayoutInformation=[[NSMutableArray alloc] init];
	else
	{	ghostArray=[lineLayoutInformation subarrayWithRange:NSMakeRange(aLine,[lineLayoutInformation count]-aLine)];	// remember old array for optimization purposes
		prevArrayEnum=[ghostArray seekableEnumerator];	// every time an object is added to lineLayoutInformation a nextObject has to be performed on prevArrayEnum!
	}

	if(aLine)
	{	lastValidLineInfo=[lineLayoutInformation objectAtIndex: aLine-1];
		drawingPoint=[lastValidLineInfo lineRect].origin;
		drawingPoint.y+=[lastValidLineInfo lineRect].size.height;
		startingIndex=NSMaxRange([lastValidLineInfo lineRange]);
	}
	if([lastValidLineInfo type]== LineLayoutInfoType_Paragraph)
	{	drawingPoint.x=0;
	} if((((int)[lineLayoutInformation count])-1) >= aLine)	// keep paragraph-terminating space on same line as paragraph
	{	_GNULineLayoutInfo *anchorLine=[lineLayoutInformation objectAtIndex:aLine];
		NSRect				anchorRect=[anchorLine lineRect];
		if(anchorRect.origin.x> drawingPoint.x && [lastValidLineInfo lineRect].origin.y == anchorRect.origin.y)
		{	drawingPoint= anchorRect.origin;
		}
	}

	[lineLayoutInformation removeObjectsInRange:NSMakeRange(aLine,[lineLayoutInformation count]-aLine)];
	currentLineIndex=aLine;

// each paragraph
	for(parscanner=[_GNUTextScanner scannerWithString:parsedString=[plainContent substringFromIndex:startingIndex]
												  set:selectionParagraphGranularitySet invertedSet:invSelectionParagraphGranularitySet];
	   ![parscanner isAtEnd];)
	{	_GNUTextScanner	*linescanner;
		NSString	*paragraph;
		NSRange		 paragraphRange,leadingNlRange,trailingNlRange;
		unsigned	 startingParagraphIndex=[parscanner scanLocation]+startingIndex,startingLineCharIndex=startingParagraphIndex;
		BOOL		 isBuckled=NO,inBuckling=NO;

		leadingNlRange=[parscanner scanSetCharacters];
		if(leadingNlRange.length)	// add the leading newlines of current paragraph if any (only the first time)
		{	[self addNewlines:leadingNlRange intoLayoutArray:lineLayoutInformation attributes:attributes atPoint:&drawingPoint width:width
													characterIndex:startingLineCharIndex ghostEnumerator:prevArrayEnum
														didShift:&nlDidShift verticalDisplacement:&yDisplacement];
			if(nlDidShift)
			{	if(insertionDelta == 1)
				{	frameshiftCorrection=YES;
					rebuildLineDrift--;
				} else if(insertionDelta == -1)
				{	frameshiftCorrection=YES;
					rebuildLineDrift++;
				} else nlDidShift=NO;
			}

			startingLineCharIndex+=leadingNlRange.length; currentLineIndex+=leadingNlRange.length;
		}
		paragraphRange=[parscanner scanNonSetCharacters];

		trailingNlRange=[parscanner scanSetCharacters];

	// each line
		for(linescanner=[_GNUTextScanner scannerWithString:paragraph=[parsedString substringWithRange:paragraphRange]
													   set:selectionWordGranularitySet invertedSet:invSelectionWordGranularitySet];
		  ![linescanner isAtEnd];)
		{	NSRect		currentLineRect=NSMakeRect(0,drawingPoint.y,0,0);
			unsigned	localLineStartIndex=[linescanner scanLocation];		// starts with zero, do not confuse with startingLineCharIndex
			NSSize	 	advanceSize=NSZeroSize;

		// scan the individual words to the end of the line
			for(;![linescanner isAtEnd]; drawingPoint.x+=advanceSize.width)
			{	NSRange		currentStringRange,trailingSpacesRange,leadingSpacesRange;
				unsigned	scannerPosition=[linescanner scanLocation];

			// snack next word
				leadingSpacesRange= [linescanner scanSetCharacters];	// leading spaces: only first time
				currentStringRange= [linescanner scanNonSetCharacters];
				trailingSpacesRange=[linescanner scanSetCharacters];
				if (leadingSpacesRange.length) currentStringRange=NSUnionRange (leadingSpacesRange,currentStringRange);
				if(trailingSpacesRange.length) currentStringRange=NSUnionRange(trailingSpacesRange,currentStringRange);

			// evaluate size of current word and line so far
				if(isRich)	advanceSize=[rtfContent sizeRange:NSMakeRange(currentStringRange.location+startingLineCharIndex,currentStringRange.length)];
				else		advanceSize=[[paragraph substringWithRange:currentStringRange] sizeWithAttributes:attributes];
				currentLineRect=NSUnionRect(currentLineRect,NSMakeRect(drawingPoint.x,drawingPoint.y, advanceSize.width, advanceSize.height));

			// handle case where single word is broader than width (buckle word) <!> unfinished
				if(!isHorizontallyResizable && advanceSize.width >= width)
				{	if(isBuckled)
					{	NSSize		currentSize=NSMakeSize(HUGE,0);
						unsigned	lastVisibleCharIndex;

						for(lastVisibleCharIndex=startingLineCharIndex+currentStringRange.length;
							currentSize.width>= width && lastVisibleCharIndex> startingLineCharIndex;
							lastVisibleCharIndex--)
						{	if(isRich)
							{	currentSize=[rtfContent sizeRange:MakeRangeFromAbs(startingLineCharIndex,lastVisibleCharIndex)];
							} else
							{	NSString *evalString=[plainContent substringWithRange:MakeRangeFromAbs(startingLineCharIndex,lastVisibleCharIndex)];
								currentSize=[evalString sizeWithAttributes:attributes];
							}
						}
						isBuckled=NO; inBuckling=YES;
						scannerPosition=localLineStartIndex+(lastVisibleCharIndex-startingLineCharIndex);
						currentLineRect.size.width=advanceSize.width=width;
					} else	// undo layout of extralarge word for now
					{	isBuckled=YES;
						currentLineRect.size.width-=advanceSize.width;
					}
				}

			// end of line-> word wrap
				if(!isHorizontallyResizable && (currentLineRect.size.width >= width || isBuckled))		// >= :wichtig för abknicken (isBuckled)
				{	_GNULineLayoutInfo	*ghostInfo=nil,*thisInfo;

					[linescanner setScanLocation:scannerPosition];	// undo layout of last word

					currentLineRect.origin.x=0; currentLineRect.origin.y=drawingPoint.y;
					drawingPoint.y+= currentLineRect.size.height; drawingPoint.x=0;

					[lineLayoutInformation addObject:thisInfo=[_GNULineLayoutInfo lineLayoutWithRange:
							NSMakeRange(startingLineCharIndex,scannerPosition-localLineStartIndex)
							rect:currentLineRect drawingOffset:0 type:LineLayoutInfoType_Text]];
					currentLineIndex++;
					startingLineCharIndex=NSMaxRange([thisInfo lineRange]);

					if(prevArrayEnum && !(ghostInfo=[prevArrayEnum nextObject])) prevArrayEnum=nil;

				// optimization stuff (do relayout only as much lines as necessary and patch the rest) ---------
					if(ghostInfo)
					{	if([ghostInfo type] != [thisInfo type])	// frameshift correction
						{	frameshiftCorrection=YES;
							if(insertionDelta == -1)			// deletition of newline
							{	_GNULineLayoutInfo *nextObject;
								if(!(nextObject=[prevArrayEnum nextObject])) prevArrayEnum=nil;
								else
								{	if(nlDidShift && frameshiftCorrection)
									{//	frameshiftCorrection=NO;
#if 0
NSLog(@"opti hook 1 (preferred)");
#endif
									} else
									{	lineDriftOffset+=([thisInfo lineRange].length-[ghostInfo lineRange].length-[nextObject lineRange].length);
										yDisplacement+=[thisInfo lineRect].origin.y-[nextObject lineRect].origin.y;
										rebuildLineDrift++;
									}
								}
							}
						} else lineDriftOffset+=([thisInfo lineRange].length-[ghostInfo lineRange].length);

					// is it possible to simply patch layout changes into layout array instead of doing a time consuming re-layout of the whole doc?
						if((currentLineIndex-1 > insertionLineIndex && !inBuckling && !isBuckled) &&
						  (!(lineDriftOffset-insertionDelta) || (nlDidShift && !lineDriftOffset) || enforceOpti))
						{	unsigned erg=_relocLayoutArray(lineLayoutInformation,ghostArray,aLine,insertionDelta, rebuildLineDrift,
															yDisplacement);

							if(frameshiftCorrection) erg=[lineLayoutInformation count]-aLine;	// y displacement: redisplay all remaining lines
							else if(currentLineIndex-1 == insertionLineIndex && ABS(insertionDelta)== 1)
							{	erg=2;	// return 2: redisplay only this and previous line
							}
#if 0
NSLog(@"opti for:%d",erg);
#endif
							return erg;
						}
					}
				// end: optimization stuff-------------------------------------------------------------------------
					break;

			// newline-induced premature lineending: flush
				} else if([linescanner isAtEnd])
				{	_GNULineLayoutInfo	*thisInfo;
					scannerPosition=[linescanner scanLocation];
					[lineLayoutInformation addObject:thisInfo=[_GNULineLayoutInfo lineLayoutWithRange:
							NSMakeRange(startingLineCharIndex,scannerPosition-localLineStartIndex)
							rect:currentLineRect drawingOffset:0 type:LineLayoutInfoType_Text]];
					currentLineIndex++;
					startingLineCharIndex=NSMaxRange([thisInfo lineRange]);

				// check for optimization (lines after paragraph are unchanged and do not need redisplay/relayout)------
					if(prevArrayEnum)
					{	_GNULineLayoutInfo	*ghostInfo=nil;

						ghostInfo=[prevArrayEnum nextObject];

						if(ghostInfo)
						{	if([ghostInfo type] != [thisInfo type])	// frameshift correction for inserted newline
							{	frameshiftCorrection=YES;

								if(insertionDelta == 1)
								{
									[prevArrayEnum previousObject];
									lineDriftOffset+=([thisInfo lineRange].length-[ghostInfo lineRange].length)+insertionDelta;
									rebuildLineDrift--;
									yDisplacement+= [thisInfo lineRect].origin.y-[ghostInfo lineRect].origin.y;
								} else if(insertionDelta == -1)
								{	if(nlDidShift && frameshiftCorrection)
									{//	frameshiftCorrection=NO;
#if 0
NSLog(@"opti hook 2");
#endif
									}										
								}
							} else lineDriftOffset+=([thisInfo lineRange].length-[ghostInfo lineRange].length);
						} else {prevArrayEnum=nil;}	// new array obviously longer than the previous one
					// end: optimization stuff-------------------------------------------------------------------------
					}
				}
			}
		}
		if(trailingNlRange.length)	// add the trailing newlines of current paragraph if any
		{	[self addNewlines:trailingNlRange intoLayoutArray:lineLayoutInformation attributes:attributes atPoint:&drawingPoint width:width
													characterIndex:startingLineCharIndex ghostEnumerator:prevArrayEnum
														  didShift:&nlDidShift verticalDisplacement:&yDisplacement];
			if(nlDidShift)
			{	if(insertionDelta == 1)
				{	frameshiftCorrection=YES;
					rebuildLineDrift--;
				} else if(insertionDelta == -1)
				{	frameshiftCorrection=YES;
					rebuildLineDrift++;
				} else nlDidShift=NO;
			}
			currentLineIndex+=trailingNlRange.length;
		}
	}

	return [lineLayoutInformation count]-aLine;	// lines actually updated (optimized drawing)
}
// end: central line formatting method ------------------------------------

-(int) rebuildPlainLineLayoutInformationStartingAtLine:(int) aLine
{	return [self rebuildPlainLineLayoutInformationStartingAtLine:aLine delta:0 actualLine:0];
}
-(int) rebuildRichLineLayoutInformationStartingAtLine:(int) aLine
{	return [self rebuildRichLineLayoutInformationStartingAtLine:aLine delta:0 actualLine:0];
}

-(int) rebuildLineLayoutInformationStartingAtLine:(int) aLine
{	if([self isRichText])
	{	return [self rebuildRichLineLayoutInformationStartingAtLine:0 delta:0 actualLine:0];
	} else
	{	return [self rebuildPlainLineLayoutInformationStartingAtLine:0 delta:0 actualLine:0];
	}
}

// relies on lineLayoutInformation
-(void) drawPlainLinesInLineRange:(NSRange) aRange
{	if(NSMaxRange(aRange) > MAX(0,[lineLayoutInformation count]-1))	// lay out lines before drawing them
	{	[self rebuildPlainLineLayoutInformationStartingAtLine:MAX(0,[lineLayoutInformation count]-1)];
	}
	{	NSArray				*linesToDraw=[lineLayoutInformation subarrayWithRange:aRange];
		NSEnumerator		*lineEnum;
		_GNULineLayoutInfo	*currentInfo;
		NSDictionary		*attributes=[self defaultTypingAttributes];

		for(lineEnum=[linesToDraw objectEnumerator]; (currentInfo=[lineEnum nextObject]);)
		{	if([currentInfo isDontDisplay] || [currentInfo type]== LineLayoutInfoType_Paragraph) continue;	// e.g. for nl
			[[plainContent substringWithRange:[currentInfo lineRange]] drawAtPoint:[currentInfo lineRect].origin withAttributes:attributes];
		}
	}
}

-(void) drawRichLinesInLineRange:(NSRange) aRange
{	if(NSMaxRange(aRange) > [lineLayoutInformation count]-1)	// lay out lines before drawing them
	{	[self rebuildRichLineLayoutInformationStartingAtLine:[lineLayoutInformation count]-1];
	}
	{	NSArray *linesToDraw=[lineLayoutInformation subarrayWithRange:aRange];
		NSEnumerator		*lineEnum;
		_GNULineLayoutInfo	*currentInfo;

		for(lineEnum=[linesToDraw objectEnumerator]; (currentInfo=[lineEnum nextObject]);)
		{	if([currentInfo isDontDisplay] || [currentInfo type] == LineLayoutInfoType_Paragraph) continue;	// e.g. for nl
			[rtfContent drawRange:[currentInfo lineRange] atPoint:[currentInfo lineRect].origin];
		}
	}
}

-(NSRange) lineRangeForRect:(NSRect) rect
{	NSPoint		upperLeftPoint=rect.origin, lowerRightPoint=NSMakePoint(NSMaxX(rect),NSMaxY(rect));
	unsigned	startLine,endLine;
	startLine=[self lineLayoutIndexForPoint:upperLeftPoint],
	endLine=[self lineLayoutIndexForPoint:lowerRightPoint];
	return MakeRangeFromAbs(startLine,endLine+1);
}

-(void) drawRectNoSelection:(NSRect)rect
{	NSRange		redrawLineRange;

	if(![lineLayoutInformation count])	// bootstrap layout information for [self lineLayoutIndexForCharacterIndex:anIndex] to work initially
	{	[self rebuildLineLayoutInformationStartingAtLine:0];
	}

	redrawLineRange=[self lineRangeForRect:rect];

	if([self drawsBackground])	// clear area under text
	{	[[self backgroundColor] set]; NSRectFill(rect);
	}
	if([self isRichText])
	{	[self drawRichLinesInLineRange:redrawLineRange];
	} else
	{	[self drawPlainLinesInLineRange:redrawLineRange];
	}

}

-(void) drawRect:(NSRect)rect
{	if(displayDisabled) return;

	[self drawRectNoSelection:rect];
	[self drawSelectionAsRange:[self selectedRange]];
}

// text lays out from top to bottom
-(BOOL) isFlipped {return YES;}

//
// Copy and paste
//
-(void) copy:sender
{	NSMutableArray *types=[NSMutableArray arrayWithObjects:NSStringPboardType, nil];
	NSPasteboard *pboard=[NSPasteboard generalPasteboard];
	if([self isRichText])			[types addObject:NSRTFPboardType];
	if([self importsGraphics])		[types addObject:NSRTFDPboardType];
	[pboard declareTypes:types owner:self];
	[pboard setString:[[self string] substringWithRange:[self selectedRange]] forType:NSStringPboardType];
	if([self isRichText])		[pboard setData:[self RTFFromRange:[self selectedRange]] forType:NSRTFPboardType];
	if([self importsGraphics])	[pboard setData:[self RTFDFromRange:[self selectedRange]] forType:NSRTFDPboardType];
}

// <!>
-(void) copyFont:sender
{
}

// <!>
-(void) copyRuler:sender
{
}

-(void) delete:sender
{	[self deleteRange:[self selectedRange] backspace:NO];
}
-(void) cut:sender
{	if([self selectedRange].length)
	{	[self copy:self];
		[self delete:self];
	}
}


-(void) paste:sender
{	[self performPasteOperation:[NSPasteboard generalPasteboard]];
}

-(void) pasteFont:sender
{	[self performPasteOperation:[NSPasteboard pasteboardWithName:NSFontPboard]];
}

-(void) pasteRuler:sender
{	[self performPasteOperation:[NSPasteboard pasteboardWithName:NSRulerPboard]];
}




//
// NSCoding protocol
//
- (void)encodeWithCoder:aCoder
{	[super encodeWithCoder:aCoder];

	[aCoder encodeConditionalObject:delegate];

	[aCoder encodeObject: plainContent];
	[aCoder encodeObject: rtfContent];

	[aCoder encodeValueOfObjCType: "I" at: &alignment];
	[aCoder encodeValueOfObjCType: @encode(BOOL) at: &is_editable];
	[aCoder encodeValueOfObjCType: @encode(BOOL) at: &is_rich_text];
	[aCoder encodeValueOfObjCType: @encode(BOOL) at: &is_selectable];
	[aCoder encodeValueOfObjCType: @encode(BOOL) at: &imports_graphics];
	[aCoder encodeValueOfObjCType: @encode(BOOL) at: &uses_font_panel];
	[aCoder encodeValueOfObjCType: @encode(BOOL) at: &is_horizontally_resizable];
	[aCoder encodeValueOfObjCType: @encode(BOOL) at: &is_vertically_resizable];
	[aCoder encodeValueOfObjCType: @encode(BOOL) at: &is_ruler_visible];
	[aCoder encodeValueOfObjCType: @encode(BOOL) at: &is_field_editor];
	[aCoder encodeObject: background_color];
	[aCoder encodeObject: text_color];
	[aCoder encodeObject: default_font];
	[aCoder encodeValueOfObjCType: @encode(NSRange) at: &selected_range];
}

- initWithCoder:aDecoder
{	[super initWithCoder:aDecoder];

	delegate = [aDecoder decodeObject];

	plainContent= [aDecoder decodeObject];
	rtfContent= [aDecoder decodeObject];

	[aDecoder decodeValueOfObjCType: "I" at: &alignment];
	[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &is_editable];
	[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &is_rich_text];
	[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &is_selectable];
	[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &imports_graphics];
	[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &uses_font_panel];
	[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &is_horizontally_resizable];
	[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &is_vertically_resizable];
	[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &is_ruler_visible];
	[aDecoder decodeValueOfObjCType: @encode(BOOL) at: &is_field_editor];
	background_color = [aDecoder decodeObject];
	text_color = [aDecoder decodeObject];
	default_font = [aDecoder decodeObject];
	[aDecoder decodeValueOfObjCType: @encode(NSRange) at: &selected_range];

	return self;
}

//
// Spelling
//

-(void) checkSpelling:sender
{	NSRange errorRange=[[NSSpellChecker sharedSpellChecker] checkSpellingOfString:[self string] startingAt:NSMaxRange([self selectedRange])];
	if(errorRange.length) [self setSelectedRange:errorRange];
	else NSBeep();
}

-(void) showGuessPanel:sender
{	[[[NSSpellChecker sharedSpellChecker] spellingPanel] orderFront:self];
}

//
// NSChangeSpelling protocol
//

-(void) changeSpelling:sender
{	[self insertText:[[(NSControl*)sender selectedCell] stringValue]];
}

-(int) spellCheckerDocumentTag
{	if(!spellCheckerDocumentTag) spellCheckerDocumentTag=[NSSpellChecker uniqueSpellDocumentTag];
	return spellCheckerDocumentTag;
}

//
// NSIgnoreMisspelledWords protocol
//
-(void) ignoreSpelling:sender
{	[[NSSpellChecker sharedSpellChecker] ignoreWord:[[(NSControl*)sender selectedCell] stringValue] inSpellDocumentWithTag:[self spellCheckerDocumentTag]];
}

@end
