diff --git a/Source/NSText.m b/Source/NSText.m index 77f44833f..9d76d0bbb 100644 --- a/Source/NSText.m +++ b/Source/NSText.m @@ -34,15 +34,13 @@ */ // toDo: - caret blinking -// - formatting routine: broader than 1.5x width cause display problems -// - optimization: 1.deletion of single char in paragraph [opti hook 1] -// - optimization: 2.newline in first line -// - optimization: 3.paragraph made one less line due to delition -// of single char [opti hook 1; diff from 1.] #include #include #include +#include +#include +#include #include #include @@ -57,106 +55,42 @@ #include #include -#include #include #include -#include -#include -#include -#include -#include +#include "GSSimpleLayoutManager.h" + #define HUGE 1e99 -static NSCharacterSet *selectionWordGranularitySet; -static NSCharacterSet *selectionParagraphGranularitySet; -static NSCharacterSet *invSelectionWordGranularitySet; -static NSCharacterSet *invSelectionParagraphGranularitySet; -@interface _GNULineLayoutInfo: NSObject -{ -@public - NSRange lineRange; - NSRect lineRect; - unsigned type; -} +static NSNotificationCenter *nc; -typedef enum -{ - // do not use 0 in order to secure calls to nil (calls to nil return 0)! - LineLayoutInfoType_Text = 1, - LineLayoutInfoType_Paragraph = 2 -} _GNULineLayoutInfo_t; -+ (id) lineLayoutWithRange: (NSRange)aRange - rect: (NSRect)aRect - type: (unsigned)aType; +@interface NSText(GNUstepPrivate) +/* + * these NSLayoutManager- like methods are here only informally + */ +- (unsigned) characterIndexForPoint: (NSPoint)point; +- (NSRect) rectForCharacterIndex: (unsigned)index; +- (NSRect) rectForCharacterRange: (NSRange)aRange; +- (void) _buildUpLayout; -- (NSRange) lineRange; -- (NSRect) lineRect; -- (unsigned) type; +/* + * various GNU extensions + */ ++ (NSDictionary*) defaultTypingAttributes; -- (void) setLineRange: (NSRange)aRange; -- (void) setLineRect: (NSRect)aRect; -- (void) setType: (unsigned)aType; - -- (NSString*) description; -@end - -@implementation _GNULineLayoutInfo - -+ (_GNULineLayoutInfo *) lineLayoutWithRange: (NSRange)aRange - rect: (NSRect)aRect - type: (unsigned)aType -{ - _GNULineLayoutInfo *ret = AUTORELEASE([_GNULineLayoutInfo new]); - - ret->lineRange = aRange; - ret->lineRect =aRect; - ret->type = aType; - return ret; -} - -- (unsigned) type -{ - return type; -} - -- (NSRange) lineRange -{ - return lineRange; -} - -- (NSRect) lineRect -{ - return lineRect; -} - -- (void) setLineRange: (NSRange)aRange -{ - lineRange = aRange; -} - -- (void) setLineRect: (NSRect)aRect -{ - lineRect = aRect; -} - -- (void) setType: (unsigned)aType -{ - type = aType; -} - -- (NSString*) description -{ - return [[NSDictionary dictionaryWithObjectsAndKeys: - NSStringFromRange(lineRange), @"LineRange", - NSStringFromRect(lineRect), @"LineRect", - nil] - description]; -} +// +// GNU utility methods +// +- (void) setAttributes: (NSDictionary*) attributes range: (NSRange) aRange; +- (void) _illegalMovement: (int) notNumber; +- (void) deleteRange: (NSRange)aRange backspace: (BOOL)flag; +- (void) drawInsertionPointAtIndex: (unsigned)index + color: (NSColor*)color + turnedOn: (BOOL)flag; @end static NSRange MakeRangeFromAbs (int a1,int a2) // not the same as NSMakeRange! @@ -171,1216 +105,6 @@ static NSRange MakeRangeFromAbs (int a1,int a2) // not the same as NSMakeRange! return NSMakeRange (a2, a1 - a2); } -// end: _GNULineLayoutInfo------------------------------------------------------ - -@interface _GNUSeekableArrayEnumerator: NSObject -{ - unsigned currentIndex; - NSArray *array; -} -- (id) initWithArray: (NSArray*)anArray; -- (id) nextObject; -- (id) previousObject; -- (id) currentObject; -@end - -@implementation _GNUSeekableArrayEnumerator - -- (id) initWithArray: (NSArray*)anArray -{ - self = [super init]; - array = RETAIN(anArray); - return self; -} - -- (id) nextObject -{ - if (currentIndex >= [array count]) - return nil; - return [array objectAtIndex: currentIndex++]; -} - -- (id) previousObject -{ - if (currentIndex == 0) - return nil; - return [array objectAtIndex: --currentIndex]; -} - -- (id) currentObject -{ - return [array objectAtIndex: currentIndex]; -} - -- (void) dealloc -{ - RELEASE(array); - [super dealloc]; -} -@end - -@interface NSArray(SeekableEnumerator) -- (_GNUSeekableArrayEnumerator*) seekableEnumerator; -@end -@implementation NSArray(SeekableEnumerator) -- (_GNUSeekableArrayEnumerator*) seekableEnumerator -{ - return AUTORELEASE([[_GNUSeekableArrayEnumerator alloc] initWithArray: self]); -} -@end - - - -@interface NSText(GNUstepPrivate) -/* - * these NSLayoutManager- like methods are here only informally (GNU extensions) - */ -- (unsigned) characterIndexForPoint: (NSPoint)point; -- (NSRect) rectForCharacterIndex: (unsigned)index; -- (NSRect) rectForCharacterRange: (NSRange)aRange; -- (void) _buildUpLayout; -- (NSRect) _textBounds; - -/* - * various GNU extensions - */ -+ (void) setSelectionWordGranularitySet: (NSCharacterSet*)aSet; -+ (void) setSelectionParagraphGranularitySet: (NSCharacterSet*)aSet; - -+ (NSDictionary*) defaultTypingAttributes; - -// -// GNU utility methods -// -- (void) setAttributes: (NSDictionary*) attributes range: (NSRange) aRange; -- (void) _illegalMovement: (int) notNumber; -- (void) deleteRange: (NSRange)aRange backspace: (BOOL)flag; - -- (void) drawInsertionPointAtIndex: (unsigned)index - color: (NSColor*)color - turnedOn: (BOOL)flag; -- (void) drawSelectionAsRangeNoCaret: (NSRange)aRange; - -@end - - -@interface GSSimpleLayoutManager: NSObject -{ - // contains private _GNULineLayoutInfo objects - NSMutableArray *lineLayoutInformation; - NSText *_textHolder; - NSTextStorage *_textStorage; - NSRect _rects[4]; -} - -- (id) initForText: (NSText*) aTextHolder; -- (NSTextStorage*) textStorage; -- (void) setTextStorage: (NSTextStorage*) aTextStorage; -- (void)textStorage:(NSTextStorage *)aTextStorage - edited:(unsigned int)mask - range:(NSRange)range - changeInLength:(int)lengthChange - invalidatedRange:(NSRange)invalidatedCharRange; -/* -- (NSRect *) rectArrayForCharacterRange: (NSRange)charRange - withinSelectedCharacterRange: (NSRange)selCharRange - inTextContainer: (NSTextContainer *)aTextContainer - rectCount: (unsigned int *)rectCount; -*/ - -- (NSSize) _sizeOfRange: (NSRange) range; -- (NSRect) _textBounds; - -- (unsigned) characterIndexForPoint: (NSPoint)point; -- (NSRect*) rectArrayForCharacterRange:(NSRange)charRange - withinSelectedCharacterRange:(NSRange)selCharRange - inTextContainer:(NSTextContainer *)aTextContainer - rectCount:(unsigned *)rectCount; -- (NSRect) rectForCharacterIndex: (unsigned) index; -- (NSRect) rectForCharacterRange: (NSRange) aRange; -- (NSRange) characterRangeForBoundingRect: (NSRect)bounds; -- (NSRange) lineRangeForRect: (NSRect) aRect; - -// return value is identical to the real line number -// (plus counted newline characters) -- (int) lineLayoutIndexForCharacterIndex: (unsigned) anIndex; -// returns the full character range for a line range -- (NSRange) characterRangeForLineLayoutRange: (NSRange) aRange; - -- (void) setNeedsDisplayForLineRange: (NSRange) redrawLineRange; -// override for special layout of text -- (NSRange) rebuildForRange: (NSRange)aRange - delta: (int)insertionDelta; -// low level, override but never invoke (use setNeedsDisplayForLineRange:) -- (void) drawLinesInLineRange: (NSRange)aRange; -- (NSRange) drawRectCharacters: (NSRect)rect; -@end - -@implementation GSSimpleLayoutManager -- (id) initForText: (NSText*)aTextHolder -{ - _textHolder = aTextHolder; - return self; -} - -- (void) setTextStorage: (NSTextStorage*)aTextStorage -{ - unsigned length = [aTextStorage length]; - NSRange aRange = NSMakeRange(0, length); - ASSIGN(_textStorage, aTextStorage); - // force complete re - layout - RELEASE(lineLayoutInformation); - lineLayoutInformation = nil; - [self textStorage: aTextStorage - edited: NSTextStorageEditedCharacters | NSTextStorageEditedAttributes - range: aRange - changeInLength: length - invalidatedRange: aRange]; -} - -- (NSTextStorage*) textStorage -{ - return _textStorage; -} - -- (NSSize) _sizeOfRange: (NSRange)aRange -{ - if (!aRange.length || _textStorage == nil || - NSMaxRange(aRange) > [_textStorage length]) - return NSZeroSize; - - return [[_textStorage attributedSubstringFromRange: aRange] size]; -} - -// Returns the currently used bounds for all the text -- (NSRect) _textBounds -{ - if ([lineLayoutInformation count]) - { - NSEnumerator *lineEnum; - _GNULineLayoutInfo *currentInfo; - NSRect retRect = NSMakeRect (0, 0, 0, 0); - - for ((lineEnum = [lineLayoutInformation objectEnumerator]); - (currentInfo = [lineEnum nextObject]);) - { - retRect = NSUnionRect (retRect, [currentInfo lineRect]); - } - return retRect; - } - else - return NSZeroRect; -} - -- (NSRect) frame -{ - NSRect aRect = [_textHolder frame]; -/* - if ([_textHolder isHorizontallyResizable]) - aRect.size.width = HUGE; - if ([_textHolder isVerticallyResizable]) - aRect.size.height = HUGE; -*/ - return aRect; -} - -- (float) width -{ - return [_textHolder frame].size.width; -} - -- (float) maxWidth -{ - NSRect aRect = [_textHolder frame]; - - if ([_textHolder isHorizontallyResizable]) - return HUGE; - else - return aRect.size.width; -} - -- (NSRange) characterRangeForLineLayoutRange: (NSRange)aRange; -{ - _GNULineLayoutInfo *currentInfo; - unsigned startLine = aRange.location; - unsigned endLine = NSMaxRange(aRange); - unsigned startIndex; - unsigned endIndex; - - if (startLine >= [lineLayoutInformation count]) - currentInfo = [lineLayoutInformation lastObject]; - else - currentInfo = [lineLayoutInformation objectAtIndex: startLine]; - startIndex = [currentInfo lineRange].location; - - if (endLine >= [lineLayoutInformation count]) - currentInfo = [lineLayoutInformation lastObject]; - else - currentInfo = [lineLayoutInformation objectAtIndex: endLine]; - endIndex = NSMaxRange([currentInfo lineRange]); - - return NSMakeRange(startIndex, endIndex - startIndex); -} - -- (NSRange) characterRangeForBoundingRect: (NSRect)boundsRect -{ - NSRange lineRange = [self lineRangeForRect: boundsRect]; - - if (lineRange.length) - return [self characterRangeForLineLayoutRange: lineRange]; - else - return NSMakeRange (0, 0); -} - -- (unsigned) lineLayoutIndexForPoint: (NSPoint)point -{ - int i; - int min = 0; - int max = MAX(0, [lineLayoutInformation count] - 1); - float y = point.y; - float fmin = NSMinY([[lineLayoutInformation objectAtIndex: 0] lineRect]); - float fmax = NSMaxY([[lineLayoutInformation lastObject] lineRect]); - NSRect rect; - - if (y >= fmax) - return max; - - if (y <= fmin) - return min; - - // this should give a good starting index for binary search - i = (int)((max - min) * (y - fmin) / (fmax - fmin)) + min; - while (min < max) - { - _GNULineLayoutInfo *ci = [lineLayoutInformation objectAtIndex: i]; - - rect = [ci lineRect]; - - if (NSMaxY(rect) < y) - { - min = i + 1; - i = (max + min) / 2; - continue; - } - if (NSMinY(rect) > y) - { - max = i - 1; - i = (max + min) / 2; - continue; - } - - // As the newline char generates its own lineLayoutinfo box - // there may be two in one line, we have to check for this - if ((NSMinX(rect) > point.x) && (i > 0) && - ([ci type] == LineLayoutInfoType_Paragraph)) - { - _GNULineLayoutInfo *bi = [lineLayoutInformation objectAtIndex: i - 1]; - rect = [bi lineRect]; - if (NSPointInRect(point, rect)) - return i - 1; - } - if ((NSMaxX(rect) < point.x) && (i < [lineLayoutInformation count] - 1) && - ([ci type] == LineLayoutInfoType_Text)) - { - _GNULineLayoutInfo *bi = [lineLayoutInformation objectAtIndex: i + 1]; - rect = [bi lineRect]; - if (NSPointInRect(point, rect)) - return i + 1; - } - - return i; - } - return min; -} - -- (unsigned) characterIndexForPoint: (NSPoint)point -{ - _GNULineLayoutInfo *currentInfo = [lineLayoutInformation - objectAtIndex: - [self lineLayoutIndexForPoint: point]]; - NSRect rect = [currentInfo lineRect]; - NSRange range = [currentInfo lineRange]; - int i; - int min = range.location; - int max = NSMaxRange(range); - float x = point.x; - float fmin = rect.origin.x; - float fmax = NSMaxX(rect); - float w1, w2; - - if (x <= fmin) - return MAX(0, min - 1); - if (x >= fmax) - return MAX(0, max - 1); - if (range.length == 1) - return min; - - // this should give a good starting index for binary search - i = (int)((max - min) * (x - fmin) / (fmax - fmin)) + min; - while (min < max) - { - w1 = [self _sizeOfRange: - NSMakeRange (range.location, - i - range.location)].width + fmin; - if (i > range.location) - w2 = [self _sizeOfRange: - NSMakeRange (range.location, - i-1 - range.location)].width + fmin; - else - w2 = fmin; - - if (w1 < x) - { - min = i + 1; - i = (max + min) / 2; - continue; - } - if (w2 > x) - { - max = i - 1; - i = (max + min) / 2; - continue; - } - return MAX(0, i-1); - } - return MAX(0, min - 1); -} - -- (int) lineLayoutIndexForCharacterIndex: (unsigned)anIndex -{ - int i; - int min = 0; - int max = MAX(0, [lineLayoutInformation count] - 1); - unsigned y = anIndex; - unsigned fmin = [[lineLayoutInformation objectAtIndex: 0] lineRange].location; - unsigned fmax = NSMaxRange([[lineLayoutInformation lastObject] lineRange]); - NSRange range; - - if (y >= fmax) - return max; - - if (y <= fmin) - return min; - - // this should give a good starting index for binary search - i = (int)((max - min) * (y - fmin) / (fmax - fmin)) + min; - while (min < max) - { - _GNULineLayoutInfo *ci = [lineLayoutInformation objectAtIndex: i]; - - range = [ci lineRange]; - - if (NSMaxRange(range) <= y) - { - min = i + 1; - i = (max + min) / 2; - continue; - } - if (range.location > y) - { - max = i - 1; - i = (max + min) / 2; - continue; - } - // Do we need a special treatment for paragraph infos? - return i; - } - return min; -} - -// rect to the end of line -- (NSRect) rectForCharacterIndex: (unsigned)index -{ - float maxWidth = [self width]; - _GNULineLayoutInfo *currentInfo; - unsigned start; - NSRect rect; - float x; - - if (![_textStorage length]) - { - return NSMakeRect(0, 0, maxWidth, 12); - } - - if (index >= NSMaxRange([[lineLayoutInformation lastObject] lineRange])) - { - NSRect rect = [[lineLayoutInformation lastObject] lineRect]; - if (NSMaxX(rect) >= maxWidth) - { - return NSMakeRect(0, NSMaxY(rect), - maxWidth, rect.size.height); - } - return NSMakeRect(NSMaxX (rect), rect.origin.y, - maxWidth - NSMaxX (rect), - rect.size.height); - } - - - currentInfo = [lineLayoutInformation - objectAtIndex: [self lineLayoutIndexForCharacterIndex: - index]]; - start = [currentInfo lineRange].location; - rect = [currentInfo lineRect]; - x = rect.origin.x + [self _sizeOfRange: MakeRangeFromAbs(start, - index)].width; - - return NSMakeRect (x, rect.origin.y, NSMaxX (rect) - x, - rect.size.height); -} - -- (NSRect) rectForCharacterRange: (NSRange)aRange -{ - float maxWidth = [self width]; - _GNULineLayoutInfo *currentInfo; - unsigned i1, i2; - NSRect rect1, rect2; - - if (![_textStorage length]) - { - return NSMakeRect(0, 0, maxWidth, 12); - } - - i1 = [self lineLayoutIndexForCharacterIndex: aRange.location]; - i2 = [self lineLayoutIndexForCharacterIndex: NSMaxRange(aRange)]; - - // This is not exacty what we need, but should be correct enought - currentInfo = [lineLayoutInformation - objectAtIndex: i1]; - rect1 = [currentInfo lineRect]; - - if (i1 == i2) - { - return rect1; - } - - currentInfo = [lineLayoutInformation - objectAtIndex: i2]; - rect2 = [currentInfo lineRect]; - return NSUnionRect(rect1, rect2); -} - -- (NSRect*) rectArrayForCharacterRange:(NSRange)aRange - withinSelectedCharacterRange:(NSRange)selCharRange - inTextContainer:(NSTextContainer *)aTextContainer - rectCount:(unsigned *)rectCount; -{ - //FIXME: This currently ignores most of its arguments - - if (!rectCount) - return _rects; - - if (aRange.length) - { - NSRect startRect = [self rectForCharacterIndex: aRange.location]; - NSRect endRect = [self rectForCharacterIndex: NSMaxRange (aRange)]; - float maxWidth = [self width]; - - if (startRect.origin.y == endRect.origin.y) - { - // single line selection - _rects[0] = NSMakeRect (startRect.origin.x, startRect.origin.y, - endRect.origin.x - startRect.origin.x, - startRect.size.height); - *rectCount = 1; - } - else if (startRect.origin.y == endRect.origin.y - endRect.size.height) - { - // two line selection - - // first line - _rects[0] = NSMakeRect (startRect.origin.x, startRect.origin.y, - maxWidth - startRect.origin.x, - startRect.size.height); - // second line - _rects[1] = NSMakeRect (0, endRect.origin.y, endRect.origin.x, - endRect.size.height); - *rectCount = 2; - } - else - { - // 3 Rects: multiline selection - - // first line - _rects[0] = NSMakeRect (startRect.origin.x, startRect.origin.y, - maxWidth - startRect.origin.x, - startRect.size.height); - // intermediate lines - _rects[1] = NSMakeRect (0, NSMaxY(startRect), - maxWidth, - endRect.origin.y - NSMaxY (startRect)); - // last line - _rects[2] = NSMakeRect (0, endRect.origin.y, endRect.origin.x, - endRect.size.height); - *rectCount = 3; - } - } - else - *rectCount = 0; - - return _rects; -} - -- (void) setNeedsDisplayForLineRange: (NSRange)redrawLineRange -{ - float maxWidth = [self width]; - - if ([lineLayoutInformation count] - && redrawLineRange.location < [lineLayoutInformation count] - && redrawLineRange.length) - { - _GNULineLayoutInfo *firstInfo - = [lineLayoutInformation objectAtIndex: redrawLineRange.location]; - NSRect displayRect = [firstInfo lineRect]; - - if ([firstInfo type] == LineLayoutInfoType_Paragraph - && displayRect.origin.x >0 && redrawLineRange.location) - { - displayRect = [[lineLayoutInformation objectAtIndex: redrawLineRange.location] lineRect]; - } - - if (redrawLineRange.length > 1) - displayRect = NSUnionRect(displayRect, - [[lineLayoutInformation - objectAtIndex: (int)NSMaxRange(redrawLineRange) - 1] - lineRect]); - - displayRect.size.width = maxWidth - displayRect.origin.x; - [_textHolder setNeedsDisplayInRect: displayRect]; - } - - - // clean up the remaining area below the text - { - NSRect myFrame = [self frame]; - float lowestY = 0; - - if ([lineLayoutInformation count]) - lowestY = NSMaxY ([[lineLayoutInformation lastObject] lineRect]); - - if (![lineLayoutInformation count] - || (lowestY < NSMaxY(myFrame))) - { - [_textHolder setNeedsDisplayInRect: NSMakeRect(0, lowestY, - maxWidth, NSMaxY(myFrame) - lowestY)]; - } - } - -} - -- (void)textStorage:(NSTextStorage *)aTextStorage - edited:(unsigned int)mask - range:(NSRange)aRange - changeInLength:(int)delta - invalidatedRange:(NSRange)invalidatedCharRange; -{ - NSRange lineRange; - - // No editing - if (!mask) - return; - - lineRange = [self rebuildForRange: aRange - delta: delta]; - [self setNeedsDisplayForLineRange: lineRange]; -} - -// internal method range is currently not passed as absolute -- (void) addNewlines: (NSRange)aRange - intoLayoutArray: (NSMutableArray*)anArray - atPoint: (NSPoint*)aPointP - width: (float)width - characterIndex: (unsigned)startingLineCharIndex - ghostEnumerator: (_GNUSeekableArrayEnumerator*)prevArrayEnum - didShift: (BOOL*)didShift -verticalDisplacement: (float*)verticalDisplacement -{ - NSSize advanceSize = [self _sizeOfRange: - NSMakeRange (startingLineCharIndex, 1)]; - int count = aRange.length; - int charIndex; - _GNULineLayoutInfo *ghostInfo = nil; - - (*didShift) = NO; - - for (charIndex = aRange.location; --count >= 0; charIndex++) - { - [anArray addObject: - [_GNULineLayoutInfo - lineLayoutWithRange: - NSMakeRange (startingLineCharIndex, 1) - rect: NSMakeRect (aPointP->x, aPointP->y, - width - aPointP->x, advanceSize.height) - type: LineLayoutInfoType_Paragraph]]; - - startingLineCharIndex++; - aPointP->x = 0; - aPointP->y += advanceSize.height; - - if (prevArrayEnum && !(ghostInfo = [prevArrayEnum nextObject])) - prevArrayEnum = nil; - - if (ghostInfo && ([ghostInfo type] != LineLayoutInfoType_Paragraph)) - { - _GNULineLayoutInfo *prevInfo = [prevArrayEnum previousObject]; - prevArrayEnum = nil; - (*didShift) = YES; - (*verticalDisplacement) += aPointP ->y - [prevInfo lineRect].origin.y; - } - } -} - -// private helper function -- (unsigned) _relocLayoutArray: (NSArray*)ghostArray - atLine: (int) aLine - offset: (int) relocOffset - lineTrift: (int) rebuildLineDrift - floatTrift: (float) yReloc -{ - // lines actually updated (optimized drawing) - unsigned ret = [lineLayoutInformation count] - aLine; - 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; -} - -/* - * A little utility function to determine the range of characters in a scanner - * that are present in a specified character set. - */ -static inline NSRange -scanRange(NSScanner *scanner, NSCharacterSet* aSet) -{ - unsigned start = [scanner scanLocation]; - unsigned end = start; - - if ([scanner scanCharactersFromSet: aSet intoString: 0] == YES) - { - end = [scanner scanLocation]; - } - return NSMakeRange(start, end - start); -} - -// begin: central line formatting method--------------------------------------- -// returns count of lines actually updated -// detachNewThreadSelector: selector toTarget: target withObject: argument; - -- (NSRange) rebuildForRange: (NSRange)aRange - delta: (int)insertionDelta -{ - int aLine = 0; - int insertionLineIndex = 0; - //unsigned oldMax = NSMaxRange(aRange); - //unsigned newMax = oldMax + insertionDelta; - NSPoint drawingPoint = NSZeroPoint; - NSScanner *pScanner; - float width = [self maxWidth]; - unsigned startingIndex = 0; - unsigned currentLineIndex; - // for optimization detection - NSArray *ghostArray; - _GNUSeekableArrayEnumerator *prevArrayEnum; - NSString *parsedString; - int lineDriftOffset = 0, rebuildLineDrift = 0; - BOOL frameshiftCorrection = NO, nlDidShift = NO; - float yDisplacement = 0; - - // sanity check that it is possible to do the layout - if (width == 0.0) - { - NSLog(@"NSText formatting with empty frame"); - return NSMakeRange(0,0); - } - - if (lineLayoutInformation == nil) - { - lineLayoutInformation = [[NSMutableArray alloc] init]; - aLine = 0; - ghostArray = nil; - prevArrayEnum = nil; - } - else - { - insertionLineIndex = [self lineLayoutIndexForCharacterIndex: - aRange.location]; - aLine = MAX(0, insertionLineIndex - 1); - - // remember old array for optimization purposes - ghostArray = [lineLayoutInformation - subarrayWithRange: - NSMakeRange (aLine, [lineLayoutInformation count] - aLine)]; - // every time an object is added to lineLayoutInformation - // a nextObject has to be performed on prevArrayEnum! - prevArrayEnum = [ghostArray seekableEnumerator]; - - if (aLine) - { - _GNULineLayoutInfo *lastValidLineInfo = [lineLayoutInformation - objectAtIndex: aLine - 1]; - NSRect aRect = [lastValidLineInfo lineRect]; - - startingIndex = NSMaxRange([lastValidLineInfo lineRange]); - drawingPoint = aRect.origin; - drawingPoint.y += aRect.size.height; - if ([lastValidLineInfo type] == LineLayoutInfoType_Paragraph) - { - drawingPoint.x = 0; - } - - // keep paragraph - terminating space on same line as paragraph - if ((((int)[lineLayoutInformation count]) - 1) >= aLine) - { - _GNULineLayoutInfo *anchorLine - = [lineLayoutInformation objectAtIndex: aLine]; - NSRect anchorRect = [anchorLine lineRect]; - - if (anchorRect.origin.x > drawingPoint.x - && aRect.origin.y == anchorRect.origin.y) - { - drawingPoint = anchorRect.origin; - } - } - } - - [lineLayoutInformation - removeObjectsInRange: - NSMakeRange (aLine, [lineLayoutInformation count] - aLine)]; - } - - if (![_textStorage length]) - { - // If there is no text add one empty box - [lineLayoutInformation - addObject: [_GNULineLayoutInfo - lineLayoutWithRange: NSMakeRange (0, 0) - rect: NSMakeRect (0, 0, 0, 12) - type: LineLayoutInfoType_Text]]; - return NSMakeRange(0,1); - } - - - currentLineIndex = aLine; - - // each paragraph - parsedString = [[_textStorage string] substringFromIndex: startingIndex]; - pScanner = [NSScanner scannerWithString: parsedString]; - [pScanner setCharactersToBeSkipped: nil]; - while ([pScanner isAtEnd] == NO) - { - NSScanner *lScanner; - NSString *paragraph; - NSRange paragraphRange, leadingNlRange, trailingNlRange; - unsigned currentLoc = [pScanner scanLocation]; - unsigned startingParagraphIndex = currentLoc + startingIndex; - unsigned startingLineCharIndex = startingParagraphIndex; - BOOL isBuckled = NO, inBuckling = NO; - - leadingNlRange - = scanRange(pScanner, selectionParagraphGranularitySet); - if (leadingNlRange.length > 0) - { - [self addNewlines: leadingNlRange - intoLayoutArray: lineLayoutInformation - 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; - } - - // each line - paragraphRange - = scanRange(pScanner, invSelectionParagraphGranularitySet); - paragraph = [parsedString substringWithRange: paragraphRange]; - lScanner = [NSScanner scannerWithString: paragraph]; - [lScanner setCharactersToBeSkipped: nil]; - while ([lScanner isAtEnd] == NO) - { - NSRect currentLineRect = NSMakeRect (0, drawingPoint.y, 0, 0); - // starts with zero, do not confuse with startingLineCharIndex - unsigned localLineStartIndex = [lScanner scanLocation]; - NSSize advanceSize = NSZeroSize; - - // scan the individual words to the end of the line - for (; ![lScanner isAtEnd]; drawingPoint.x += advanceSize.width) - { - NSRange currentStringRange, trailingSpacesRange; - NSRange leadingSpacesRange; - unsigned scannerPosition = [lScanner scanLocation]; - - // snack next word - - // leading spaces: only first time - leadingSpacesRange - = scanRange(lScanner, selectionWordGranularitySet); - currentStringRange - = scanRange(lScanner, invSelectionWordGranularitySet); - trailingSpacesRange - = scanRange(lScanner, selectionWordGranularitySet); - - if (leadingSpacesRange.length) - currentStringRange = NSUnionRange(leadingSpacesRange, - currentStringRange); - if (trailingSpacesRange.length) - currentStringRange = NSUnionRange(trailingSpacesRange, - currentStringRange); - - // evaluate size of current word and line so far - advanceSize = [self _sizeOfRange: - NSMakeRange (currentStringRange.location + - paragraphRange.location + - startingIndex, - currentStringRange.length)]; - - currentLineRect = NSUnionRect (currentLineRect, - NSMakeRect (drawingPoint.x, - drawingPoint.y, - advanceSize.width, - advanceSize.height)); - - // handle case where single word is broader than width - // (buckle word) unfinished and untested - // for richText (absolute position see above) - if (advanceSize.width > width) - { - if (isBuckled) - { - NSSize currentSize = NSMakeSize (HUGE, 0); - unsigned lastVisibleCharIndex; - - for (lastVisibleCharIndex - = startingLineCharIndex + currentStringRange.length; - currentSize.width>= width - && lastVisibleCharIndex> startingLineCharIndex; - lastVisibleCharIndex--) - { - currentSize = [self _sizeOfRange: - MakeRangeFromAbs (startingLineCharIndex, - lastVisibleCharIndex)]; - } - isBuckled = NO; - inBuckling = YES; - scannerPosition - = localLineStartIndex - + (lastVisibleCharIndex - startingLineCharIndex)+1; - currentLineRect.size.width = advanceSize.width = width; - } - else - { - // undo layout of extralarge word - // (will be done the next line [see above]) - isBuckled = YES; - currentLineRect.size.width -= advanceSize.width; - } - } - - // end of line -> word wrap - - // >= : wichtig för abknicken (isBuckled) - if (currentLineRect.size.width >= width || isBuckled) - { - _GNULineLayoutInfo *ghostInfo = nil, *thisInfo; - - // undo layout of last word - [lScanner setScanLocation: scannerPosition]; - - 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 - 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))) - { - unsigned erg = [self _relocLayoutArray: ghostArray - atLine: aLine - offset: insertionDelta - lineTrift: rebuildLineDrift - floatTrift: yDisplacement]; - - // y displacement: redisplay all remaining lines - if (frameshiftCorrection) - erg = [lineLayoutInformation count] - aLine; - else if (currentLineIndex - 1 == insertionLineIndex - && ABS(insertionDelta) == 1) - { - // return 2: redisplay only this and previous line - erg = 2; - } -#if 0 - NSLog(@"opti for: %d",erg); -#endif - return NSMakeRange(aLine, MAX(1, erg)); - } - } - // end: optimization stuff-------------------------- - // ----------------------------------------------- - break; - - // newline - induced premature lineending: flush - } - else if ([lScanner isAtEnd]) - { - _GNULineLayoutInfo *thisInfo; - scannerPosition = [lScanner scanLocation]; - [lineLayoutInformation - addObject: (thisInfo - = [_GNULineLayoutInfo - lineLayoutWithRange: - NSMakeRange (startingLineCharIndex, - scannerPosition - localLineStartIndex) - rect: currentLineRect - 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 - { - // new array obviously longer than the previous one - prevArrayEnum = nil; - } - // end: optimization stuff------------------------------ - // ------------------------------------------- - } - } - } - } - // add the trailing newlines of current paragraph if any - trailingNlRange - = scanRange(pScanner, selectionParagraphGranularitySet); - if (trailingNlRange.length) - { - [self addNewlines: trailingNlRange - intoLayoutArray: lineLayoutInformation - 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; - } - } - - // lines actually updated (optimized drawing) - return NSMakeRange(aLine, MAX(1, [lineLayoutInformation count] - aLine)); -} -// end: central line formatting method------------------------------------ - -// relies on lineLayoutInformation -- (void) drawLinesInLineRange: (NSRange)aRange; -{ - NSArray *linesToDraw = [lineLayoutInformation subarrayWithRange: aRange]; - NSEnumerator *lineEnum; - _GNULineLayoutInfo *currentInfo; - - for ((lineEnum = [linesToDraw objectEnumerator]); - (currentInfo = [lineEnum nextObject]);) - { - if ([currentInfo type] == LineLayoutInfoType_Paragraph) - continue; // e.g. for nl - - [_textStorage drawRange: [currentInfo lineRange] - atPoint: [currentInfo lineRect].origin]; - // make this use drawRange: inRect: in the future - // (for proper adoption of layout information [e.g. centering]) - } -} - -// Draws the lines in the given rectangle and hands back the drawn -// character range. -- (NSRange) drawRectCharacters: (NSRect)rect -{ - NSRange aRange = [self lineRangeForRect: rect]; - - [self drawLinesInLineRange: aRange]; - return [self characterRangeForLineLayoutRange: aRange]; -} - -- (NSRange) lineRangeForRect: (NSRect)rect -{ - NSPoint upperLeftPoint = rect.origin; - NSPoint lowerRightPoint = NSMakePoint (NSMaxX (rect), NSMaxY (rect)); - unsigned startLine, endLine; - - startLine = [self lineLayoutIndexForPoint: upperLeftPoint]; - endLine = [self lineLayoutIndexForPoint: lowerRightPoint]; - if (++endLine > [lineLayoutInformation count]) - endLine = [lineLayoutInformation count]; - - return NSMakeRange(startLine, endLine - startLine); -} - -@end - - - - -// begin: NSText------------------------------------------------------------ - -static NSNotificationCenter *nc; - @implementation NSText // @@ -1396,11 +120,6 @@ static NSNotificationCenter *nc; nc = [NSNotificationCenter defaultCenter]; - [self setSelectionWordGranularitySet: - [NSCharacterSet whitespaceCharacterSet]]; - [self setSelectionParagraphGranularitySet: - [NSCharacterSet characterSetWithCharactersInString: - [self newlineString]]]; types = [NSArray arrayWithObjects: NSStringPboardType, NSRTFPboardType, NSRTFDPboardType, nil]; [[NSApplication sharedApplication] registerServicesMenuSendTypes: types @@ -1444,7 +163,11 @@ static NSNotificationCenter *nc; [self setBackgroundColor: [NSColor textBackgroundColor]]; // sets up the contents object - [self setString: @""]; + _textStorage = [[NSTextStorage alloc] + initWithString: @"" + attributes: [self typingAttributes]]; + [self _buildUpLayout]; + //[self setSelectedRange: NSMakeRange (0, 0)]; return self; } @@ -1499,20 +222,15 @@ static NSNotificationCenter *nc; - (void) setString: (NSString*)aString { - RELEASE(_textStorage); - _textStorage = [[NSTextStorage alloc] - initWithString: aString - attributes: [self typingAttributes]]; - - [self _buildUpLayout]; - [self sizeToFit]; - [self setSelectedRange: NSMakeRange (0, 0)]; - [self setNeedsDisplay: YES]; + [self replaceCharactersInRange: NSMakeRange(0, [_textStorage length]) + withString: aString]; } - (NSString*) string { - return [_textStorage string]; + // FIXME: As NSTextStorage returns the real characters we have to copy them. + // This should also remove all the attachement characters. + return [[_textStorage string] copy]; } // old methods @@ -1678,7 +396,7 @@ static NSNotificationCenter *nc; NSRange oldRange = _selected_range; NSRange overlap; - // Do ntohing if the range is still the same + // Do nothing to do, if the range is still the same if (NSEqualRanges(range, oldRange)) return; @@ -2218,7 +936,7 @@ static NSNotificationCenter *nc; NSSize oldSize = _frame.size; float newWidth = oldSize.width; float newHeight = oldSize.height; - NSRect textRect = [self _textBounds]; + NSRect textRect = [_layoutManager usedRectForTextContainer: [self textContainer]]; NSSize newSize; if (_tf.is_horizontally_resizable) @@ -2519,7 +1237,13 @@ static NSNotificationCenter *nc; /* Do nothing if we are at beginning of text with no selection */ if (_selected_range.location == 0 && _selected_range.length == 0) - return; + { + if (_tf.is_field_editor) + { + [self _illegalMovement: NSLeftTextMovement]; + } + return; + } if (_selected_range.location == 0) newSelectedRange.location = 0; @@ -2540,9 +1264,15 @@ static NSNotificationCenter *nc; /* Do nothing if we are at end of text */ if (_selected_range.location == length) - return; + { + if (_tf.is_field_editor) + { + [self _illegalMovement: NSRightTextMovement]; + } + return; + } - newSelectedRange.location = MIN (NSMaxRange (_selected_range) + 1, length); + newSelectedRange.location = MIN(NSMaxRange(_selected_range) + 1, length); newSelectedRange.length = 0; [self setSelectedRange: newSelectedRange]; @@ -2606,29 +1336,25 @@ static NSNotificationCenter *nc; - (void) drawRect: (NSRect)rect { - NSRange drawnRange; - NSRange newRange; - + NSRange drawnRange = [_layoutManager glyphRangeForBoundingRect: rect + inTextContainer: [self textContainer]]; +/* + NSLog(@"frame: %@", NSStringFromRect([self frame])); + NSLog(@"drawRect: %@", NSStringFromRect(rect)); + NSLog(@"drawRange: %@", NSStringFromRange(drawnRange)); +*/ if (_tf.draws_background) { - // clear area under text - [[self backgroundColor] set]; - NSRectFill(rect); + [_layoutManager drawBackgroundForGlyphRange: drawnRange + atPoint: [self textContainerOrigin]]; } - drawnRange = [_layoutManager drawRectCharacters: rect]; + [_layoutManager drawGlyphsForGlyphRange: drawnRange + atPoint: [self textContainerOrigin]]; - // We have to redraw the part of the selection that is inside - // the redrawn lines - newRange = NSIntersectionRange(_selected_range, drawnRange); - // Was there any overlapping with the selection? - if ((_selected_range.length && - NSLocationInRange(newRange.location, _selected_range))) - { - [self drawSelectionAsRangeNoCaret: newRange]; - } - else if ([self shouldDrawInsertionPoint] && - (_selected_range.location == newRange.location)) + if ([self shouldDrawInsertionPoint] && + (NSLocationInRange(_selected_range.location, drawnRange) || + _selected_range.location == NSMaxRange(drawnRange))) { [self drawInsertionPointAtIndex: _selected_range.location color: _caret_color @@ -2829,6 +1555,16 @@ static NSNotificationCenter *nc; @implementation NSText(NSTextView) +- (NSTextContainer *)textContainer +{ + return nil; +} + +- (NSPoint)textContainerOrigin +{ + return NSZeroPoint; +} + - (void) setRulerVisible: (BOOL)flag { NSScrollView *sv = [self enclosingScrollView]; @@ -2857,8 +1593,7 @@ static NSNotificationCenter *nc; [nc postNotificationName: NSTextDidChangeNotification object: self]; } -// central text inserting method (takes care -// of optimized redraw/ cursor positioning) +// central text inserting method - (void) insertText: (NSString*) insertString { NSRange insertRange = [self rangeForUserTextChange]; @@ -2938,6 +1673,9 @@ static NSNotificationCenter *nc; if (flag) { + if (color == nil) + color = _caret_color; + [color set]; NSRectFill(rect); } @@ -3382,18 +2120,6 @@ other than copy/paste or dragging. */ @implementation NSText(GNUstepPrivate) -+ (void) setSelectionWordGranularitySet: (NSCharacterSet*) aSet -{ - ASSIGN(selectionWordGranularitySet, aSet); - ASSIGN(invSelectionWordGranularitySet, [aSet invertedSet]); -} - -+ (void) setSelectionParagraphGranularitySet: (NSCharacterSet*) aSet -{ - ASSIGN(selectionParagraphGranularitySet, aSet); - ASSIGN(invSelectionParagraphGranularitySet, [aSet invertedSet]); -} - + (NSDictionary*) defaultTypingAttributes { return [NSDictionary dictionaryWithObjectsAndKeys: @@ -3526,34 +2252,47 @@ other than copy/paste or dragging. */ - (unsigned) characterIndexForPoint: (NSPoint) point { - return [_layoutManager characterIndexForPoint: point]; + unsigned glyphIndex = [_layoutManager glyphIndexForPoint: point + inTextContainer: [self textContainer]]; + + return [_layoutManager characterIndexForGlyphAtIndex: glyphIndex]; } - (NSRect) rectForCharacterIndex: (unsigned) index { - return [_layoutManager rectForCharacterIndex: index]; + NSRange glyphRange = [_layoutManager glyphRangeForCharacterRange: NSMakeRange(index, 1) + actualCharacterRange: NULL]; + unsigned glyphIndex = glyphRange.location; + NSRect rect = [_layoutManager lineFragmentRectForGlyphAtIndex: glyphIndex + effectiveRange: NULL]; + NSPoint loc = [_layoutManager locationForGlyphAtIndex: glyphIndex]; + + rect.origin.x += loc.x; + rect.size.width -= loc.x; + + return rect; } - (NSRect) rectForCharacterRange: (NSRange) aRange { - return [_layoutManager rectForCharacterRange: aRange]; + NSRange glyphRange = [_layoutManager glyphRangeForCharacterRange: aRange + actualCharacterRange: NULL]; + + return [_layoutManager boundingRectForGlyphRange: glyphRange + inTextContainer: [self textContainer]]; } - (void) _buildUpLayout { if (_layoutManager == nil) - _layoutManager = [[GSSimpleLayoutManager alloc] - initForText: self]; - + { + _layoutManager = [[GSSimpleLayoutManager alloc] init]; + [_layoutManager setFirstTextView: (NSTextView*)self]; + } + [_textStorage addLayoutManager: _layoutManager]; } -// Returns the currently used bounds for all the text -- (NSRect) _textBounds -{ - return [_layoutManager _textBounds]; -} - - (void) drawInsertionPointAtIndex: (unsigned) index color: (NSColor*) color turnedOn: (BOOL) flag @@ -3564,26 +2303,9 @@ other than copy/paste or dragging. */ if (drawRect.size.height == 0) drawRect.size.height = 12; - if (flag && color == nil) - color = _caret_color; - [self drawInsertionPointInRect: drawRect color: color turnedOn: flag]; } -- (void) drawSelectionAsRangeNoCaret: (NSRange) aRange -{ - int i; - unsigned count; - NSRect *rects = [_layoutManager rectArrayForCharacterRange: aRange - withinSelectedCharacterRange: aRange - inTextContainer: nil - rectCount: &count]; - for (i = 0; i < count; i++) - { - NSHighlightRect(rects[i]); - } -} - @end