libs-gui/Source/NSText.m
Richard Frith-MacDonald d1f7b7b5ae Remove spuroous code
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@6231 72102866-910b-0410-8b05-ffd578937521
2000-03-09 07:57:45 +00:00

3315 lines
86 KiB
Objective-C

/*
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.
*/
// 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 <gnustep/gui/config.h>
#include <Foundation/NSNotification.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/NSFontManager.h>
#include <AppKit/NSFont.h>
#include <AppKit/NSColor.h>
#include <AppKit/NSParagraphStyle.h>
#include <AppKit/NSPasteboard.h>
#include <AppKit/NSSpellChecker.h>
#include <AppKit/NSClipView.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>
#define HUGE 1e99
/*
* 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);
}
enum {
NSBackspaceKey = 8,
NSCarriageReturnKey = 13,
NSDeleteKey = 0x7f,
NSBacktabKey = 25
};
@interface _GNULineLayoutInfo: NSObject
{
NSRange lineRange;
NSRect lineRect;
float drawingOffset;
BOOL dontDisplay;
unsigned type;
}
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;
+ 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;
- (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;
}
- (void) setLineRange: (NSRange) aRange
{
lineRange = aRange;
}
- (void) setLineRect: (NSRect) aRect
{
//FIXME, line up textEditor with how text in text cell will be placed.
// aRect.origin.y += 2;
lineRect = aRect;
}
- (void) setDrawingOffset: (float) anOffset
{
drawingOffset = anOffset;
}
- (void) setDontDisplay: (BOOL) flag
{
dontDisplay = flag;
}
- (void) setType: (unsigned) aType
{
type = aType;
}
- (NSString*) description
{
return [[NSDictionary dictionaryWithObjectsAndKeys:
NSStringFromRange(lineRange), @"LineRange",
NSStringFromRect(lineRect), @"LineRect",
nil]
description];
}
- (BOOL) isLineTerminatingParagraph
{
// sort of hackish
return type == LineLayoutInfoType_Paragraph && lineRect.origin.x> 0;
}
@end
static NSRange MakeRangeFromAbs (int a1,int a2) // not the same as NSMakeRange!
{
if (a1 < 0)
a1 = 0;
if (a2 < 0)
a2 = 0;
if (a1 < a2)
return NSMakeRange (a1, a2 - a1);
else
return NSMakeRange (a2, a1 - a2);
}
/*
static NSRange MakeRangeFromAbs(int a1,int a2)
{ if (a1< a2) return NSMakeRange(a1,a2 - a1);
else return NSMakeRange(a2,a1 - a2);
}
*/
// end: _GNULineLayoutInfo------------------------------------------------------------------------------------------
@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
// begin: NSText------------------------------------------------------------
@interface NSText(GNUstepPrivate)
//
// these NSLayoutManager- like methods are here only informally (GNU extensions)
//
-(unsigned) characterIndexForPoint:(NSPoint)point;
-(NSRect) rectForCharacterIndex:(unsigned) index;
-(NSRect) boundingRectForLineRange:(NSRange)lineRange;
-(NSRange) characterRangeForBoundingRect:(NSRect)bounds;
-(NSRange) lineRangeForRect:(NSRect) aRect;
// Key movement
- (void) moveCursorUp: sender;
- (void) moveCursorDown: sender;
- (void) moveCursorLeft: sender;
- (void) moveCursorRight: sender;
//
// these are implementation specific (GNU extensions)
//
-(int) rebuildLineLayoutInformation;
-(int) rebuildLineLayoutInformationStartingAtLine:(int) aLine; // returns count of lines actually updated (e.g. drawing optimization)
-(int) rebuildLineLayoutInformationStartingAtLine:(int) aLine delta:(int) insertionDelta actualLine:(int) insertionLine; // override for special layout of text
-(int) lineLayoutIndexForCharacterIndex:(unsigned) anIndex; // return value is identical to the real line number (plus counted newline characters)
-(void) redisplayForLineRange:(NSRange) redrawLineRange;
-(void) drawLinesInLineRange:(NSRange) aRange; // low level, override but never invoke (use redisplayForLineRange:)
// GNU utility methods
// return value is guaranteed to be a NSAttributedString,
// even if data contains only NSString
+(NSAttributedString*) attributedStringForData:(NSData*) aData;
+(NSData*) dataForAttributedString:(NSAttributedString*) aString;
- (BOOL) performPasteOperation: (NSPasteboard*)pboard;
//
// various GNU extensions
//
- (void) rebuildForCharacterRange: (NSRange) aRange;
-(void) setSelectionWordGranularitySet:(NSCharacterSet*) aSet;
-(void) setSelectionParagraphGranularitySet:(NSCharacterSet*) aSet;
//
// private (never invoke, never subclass)
//
-(void) drawRectNoSelection:(NSRect)rect;
- (void) setSelectedRangeNoDrawing: (NSRange)range;
- (void) deleteRange: (NSRange) aRange backspace: (BOOL) flag;
- (NSDictionary*) defaultTypingAttributes;
- (void) drawInsertionPointAtIndex: (unsigned)index
color: (NSColor*)color
turnedOn: (BOOL)flag;
- (void) drawSelectionAsRangeNoCaret: (NSRange) aRange;
- (void) drawSelectionAsRange: (NSRange) aRange;
- (NSSize) _sizeOfRange: (NSRange)range;
@end
@implementation NSText
//
// Class methods
//
+ (void)initialize
{
if (self == [NSText class])
{
NSArray *r;
NSArray *s;
[self setVersion: 1]; // Initial version
r = [NSArray arrayWithObjects: NSStringPboardType, nil];
s = [NSArray arrayWithObjects: NSStringPboardType, nil];
[[NSApplication sharedApplication] registerServicesMenuSendTypes: s
returnTypes: r];
}
}
//
// Instance methods
//
//
// Initialization
//
- (id) init
{
return [self initWithFrame: NSMakeRect (0, 0, 100, 100)];
}
- (id) 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 textBackgroundColor]];
[self setTextColor: [NSColor textColor]];
default_font = RETAIN([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;
}
- (void)dealloc
{
[self unregisterDraggedTypes];
[background_color release];
[default_font release];
[text_color release];
[plainContent release];
[rtfContent release];
[super dealloc];
}
//
// Getting and Setting Contents
//
// low level (no selection handling, relayout or display)
- (void)replaceCharactersInRange:(NSRange)aRange withRTF:(NSData *)rtfData
{
[self replaceRange: aRange withRTF: rtfData];
}
- (void)replaceCharactersInRange:(NSRange)aRange withRTFD:(NSData *)rtfdData
{
[self replaceRange: aRange withRTFD: rtfdData];
}
- (void)replaceCharactersInRange:(NSRange)aRange withString:(NSString *)aString
{
[self replaceRange: aRange withString: aString];
}
- (void) setString: (NSString *)string
{
if ([self isRichText])
{
RELEASE(rtfContent);
rtfContent = [[NSMutableAttributedString alloc]
initWithString: string
attributes: [self defaultTypingAttributes]];
}
else
ASSIGN(plainContent, [NSMutableString stringWithString: string]);
[self rebuildLineLayoutInformation];
[self setSelectedRangeNoDrawing: NSMakeRange (0, 0)];
// [self setNeedsDisplay: YES];
}
- (NSString*) string
{
if ([self isRichText])
return [rtfContent string];
else
return plainContent;
}
- (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];
}
- (void) setText: (NSString *)string
{
[self setString: string];
}
- (NSString*) text
{
return [self string];
}
//
// Graphic attributes
//
- (NSColor*) backgroundColor
{
return background_color;
}
- (BOOL) drawsBackground
{
return draws_background;
}
- (void) setBackgroundColor: (NSColor *)color
{
ASSIGN(background_color, color);
}
- (void)setDrawsBackground: (BOOL)flag
{
draws_background = flag;
}
//
// Managing Global Characteristics
//
- (BOOL) importsGraphics
{
return imports_graphics;
}
- (BOOL) isEditable
{
return is_editable;
}
- (BOOL) isFieldEditor
{
return is_field_editor;
}
- (BOOL) isRichText
{
return is_rich_text;
}
- (BOOL) isSelectable
{
return is_selectable;
}
- (void)setEditable: (BOOL)flag
{
is_editable = flag;
// If we are editable then we are selectable
if (flag)
is_selectable = YES;
}
- (void) setFieldEditor: (BOOL)flag
{
is_field_editor = flag;
}
- (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]];
}
else
{
if (!plainContent)
plainContent = [[NSMutableString
alloc]
initWithString: rtfContent? [rtfContent string]: @""];
}
[self updateDragTypeRegistration];
[self rebuildLineLayoutInformation];
[self sizeToFit];
[self setNeedsDisplay: YES];
}
- (void)setSelectable: (BOOL)flag
{
is_selectable = flag;
// If we are not selectable then we must not be editable
if (!flag)
is_editable = NO;
}
//
// Using the font panel
//
- (BOOL) usesFontPanel
{
return uses_font_panel;
}
- (void)setUsesFontPanel: (BOOL)flag
{
uses_font_panel = flag;
}
//
// Managing the Ruler
//
- (BOOL) isRulerVisible
{
return NO;
}
- (void) toggleRuler: sender
{
}
//
// Managing the Selection
//
- (NSRange)selectedRange
{
return selected_range;
}
- (void) setSelectedRange: (NSRange)range
{
BOOL didLock = NO;
if (!_window)
return;
if (_window && [[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])
{
NSRange longestRange;
currentFont = [rtfContent attribute: NSFontAttributeName
atIndex: range.location
longestEffectiveRange: &longestRange
inRange: range];
if (NSEqualRanges (longestRange, range))
isMultiple = NO;
else
isMultiple = YES;
}
else
currentFont = [[self defaultTypingAttributes]
objectForKey: NSFontAttributeName];
[[NSFontManager sharedFontManager] setSelectedFont: 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];
[_window flushWindow];
}
}
//
// Copy and paste
//
- (void) copy: sender
{
NSMutableArray *types = [NSMutableArray arrayWithObject:
NSStringPboardType];
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: selected_range]
forType: NSStringPboardType];
if ([self isRichText])
[pboard setData: [self RTFFromRange: selected_range]
forType: NSRTFPboardType];
if ([self importsGraphics])
[pboard setData: [self RTFDFromRange: selected_range]
forType: NSRTFDPboardType];
}
// Copy the current font to the font pasteboard
- (void) copyFont: sender
{
NSMutableArray *types = [NSMutableArray arrayWithObject:
NSFontPboardType];
NSPasteboard *pboard = [NSPasteboard pasteboardWithName: NSFontPboard];
// FIXME: We should get the font from the selection
NSFont *font = [self font];
NSData *data = nil;
if (font != nil)
data = [NSArchiver archivedDataWithRootObject: font];
if (data != nil)
{
[pboard declareTypes: types owner: self];
[pboard setData: data forType: NSFontPboardType];
}
}
// Copy the current ruler settings to the ruler pasteboard
- (void) copyRuler: sender
{
NSMutableArray *types = [NSMutableArray arrayWithObject:
NSRulerPboardType];
NSPasteboard *pboard = [NSPasteboard pasteboardWithName: NSRulerPboard];
NSParagraphStyle *style;
NSData * data = nil;
if (![self isRichText])
return;
style = [rtfContent attribute: NSParagraphStyleAttributeName
atIndex: selected_range.location
effectiveRange: &selected_range];
if (style != nil)
data = [NSArchiver archivedDataWithRootObject: style];
if (data != nil)
{
[pboard declareTypes: types owner: self];
[pboard setData: data forType: NSRulerPboardType];
}
}
- (void) delete: sender
{
[self deleteRange: selected_range backspace: NO];
}
- (void) cut: sender
{
if (selected_range.length)
{
[self copy: sender];
[self delete: sender];
}
}
- (void) paste: sender
{
[self performPasteOperation: [NSPasteboard generalPasteboard]];
}
- (void) pasteFont: sender
{
[self performPasteOperation:
[NSPasteboard pasteboardWithName: NSFontPboard]];
}
- (void) pasteRuler: sender
{
[self performPasteOperation:
[NSPasteboard pasteboardWithName: NSRulerPboard]];
}
- (void) selectAll: sender
{
[self setSelectedRange: NSMakeRange(0,[self textLength])];
}
/*
* Managing Font and Color
*/
- (NSFont*) font
{
// FIXME: Should take the font of the first text, if there is some
return default_font;
}
// 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 searchRange = selected_range;
NSRange foundRange;
int maxSelRange;
for (maxSelRange = NSMaxRange(selected_range);
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]]];
}
[self setNeedsDisplay: YES];
}
}
- (void)setFont: (NSFont*)obj
{
ASSIGN(default_font, obj);
}
- (void)setFont: (NSFont *)font ofRange: (NSRange)range
{
if ([self isRichText])
{
if (font != nil)
{
[rtfContent addAttribute: NSFontAttributeName value: font
range: range];
[self rebuildForCharacterRange: range];
}
}
}
//
// Managing Alingment
//
- (NSTextAlignment)alignment
{
return alignment;
}
- (void)setAlignment: (NSTextAlignment)mode
{
alignment = mode;
[self setNeedsDisplay: YES];
}
- (void) alignCenter: sender
{
if ([self isRichText])
{
[rtfContent setAlignment: NSCenterTextAlignment range: selected_range];
[self setNeedsDisplay: YES];
}
else
[self setAlignment: NSCenterTextAlignment];
}
- (void) alignLeft: sender
{
if ([self isRichText])
{
[rtfContent setAlignment: NSLeftTextAlignment range: selected_range];
[self setNeedsDisplay: YES];
}
else
[self setAlignment: NSLeftTextAlignment];
}
- (void) alignRight: sender
{
if ([self isRichText])
{
[rtfContent setAlignment: NSRightTextAlignment range: selected_range];
[self setNeedsDisplay: YES];
}
else
[self setAlignment: NSRightTextAlignment];
}
//
// Text colour
//
- (NSColor*) textColor
{
return text_color;
}
- (void) setTextColor: (NSColor*)color range: (NSRange)range
{
if ([self isRichText])
{
if (color)
[rtfContent addAttribute: NSForegroundColorAttributeName
value: color
range: range];
}
}
- (void) setColor: (NSColor*)color ofRange: (NSRange)range
{
[self setTextColor: color range: range];
}
- (void) setTextColor: (NSColor *)color
{
ASSIGN (text_color, color);
if (![self isRichText])
[self setNeedsDisplay: YES];
}
//
// Text attributes
//
- (void) subscript: sender
{
}
- (void) superscript: sender
{
}
- (void) underline: sender
{
if ([self isRichText])
{
BOOL doUnderline = YES;
if ([[rtfContent attribute: NSUnderlineStyleAttributeName
atIndex: selected_range.location
effectiveRange: NULL] intValue])
doUnderline = NO;
if (selected_range.length)
{
[rtfContent addAttribute: NSUnderlineStyleAttributeName
value: [NSNumber numberWithInt: doUnderline]
range: selected_range];
[self rebuildForCharacterRange: selected_range];
}
else // no redraw necess.
[[self typingAttributes]
setObject: [NSNumber numberWithInt: doUnderline]
forKey: NSUnderlineStyleAttributeName];
}
}
- (void) unscript: sender
{
if ([self isRichText])
{
if (selected_range.length)
{
[rtfContent removeAttribute: NSUnderlineStyleAttributeName
range: selected_range];
[self rebuildForCharacterRange: selected_range];
}
else // no redraw necess.
[[self typingAttributes]
removeObjectForKey: NSUnderlineStyleAttributeName];
}
}
//
// 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];
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;
}
- (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];
}
//
// 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;
}
- (void) sizeToFit
{
NSRect sizeToRect = _frame;
// if we are a field editor we don't have to handle the size.
if ([self isFieldEditor])
return;
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;
float newY;
NSRect tRect;
if (([[lineLayoutInformation lastObject] type]
== LineLayoutInfoType_Paragraph)
&& NSMaxY (rect) <= newHeight)
newHeight += [[lineLayoutInformation lastObject]
lineRect].size.height;
if ([[self superview] isKindOfClass: [NSClipView class]])
tRect = [(NSClipView*)[self superview] documentVisibleRect];
else
tRect = _bounds;
if (currentCursorY < tRect.size.height + tRect.origin.y -
[[lineLayoutInformation lastObject] lineRect].size.height)
newY = sizeToRect.origin.y;
else if (currentCursorY > tRect.size.height + tRect.origin.y -
[[lineLayoutInformation lastObject] lineRect].size.height)
{
newY = currentCursorY - tRect.size.height +
([[lineLayoutInformation lastObject] lineRect].size.height * 2);
[(NSClipView *)[self superview] scrollToPoint:
NSMakePoint (sizeToRect.origin.x, newY)];
}
else
NSLog(@" ========= > Oops!\n");
newHeight = MIN(maxSize.height,MAX(newHeight,minSize.height));
sizeToRect = NSMakeRect(sizeToRect.origin.x,sizeToRect.origin.y,
sizeToRect.size.width,newHeight);
}
else
sizeToRect = NSMakeRect(0,0,minSize.width,minSize.height);
}
if (!NSEqualSizes(_frame.size,sizeToRect.size)) {
[self setFrame: sizeToRect]; //[self setFrameSize: sizeToRect.size];
}
}
//
// Spelling
//
- (void) checkSpelling: sender
{
NSRange errorRange
= [[NSSpellChecker sharedSpellChecker]
checkSpellingOfString: [self string]
startingAt: NSMaxRange (selected_range)];
if (errorRange.length)
[self setSelectedRange: errorRange];
else
NSBeep();
}
- (void) showGuessPanel: sender
{
[[[NSSpellChecker sharedSpellChecker] spellingPanel] orderFront: self];
}
//
// Scrolling
//
- (void) scrollRangeToVisible: (NSRange)range
{
[self scrollRectToVisible:
NSUnionRect ([self rectForCharacterIndex:
selected_range.location],
[self rectForCharacterIndex:
NSMaxRange (selected_range)])];
}
/*
* Managing the Delegate
*/
- (id) delegate
{
return delegate;
}
- (void) setDelegate: (id)anObject
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
if (delegate)
[nc removeObserver: delegate name: nil object: self];
ASSIGN(delegate, anObject);
#define SET_DELEGATE_NOTIFICATION(notif_name) \
if ([delegate respondsToSelector: @selector(text##notif_name:)]) \
[nc addObserver: delegate \
selector: @selector(text##notif_name:) \
name: NSText##notif_name##Notification \
object: self]
SET_DELEGATE_NOTIFICATION(DidBeginEditing);
SET_DELEGATE_NOTIFICATION(DidChange);
SET_DELEGATE_NOTIFICATION(DidEndEditing);
}
//
// Handling Events
//
- (void) mouseDown: (NSEvent *)theEvent
{
NSSelectionGranularity granularity = NSSelectByCharacter;
NSRange chosenRange, prevChosenRange, proposedRange;
NSPoint point, startPoint;
NSEvent *currentEvent;
unsigned startIndex;
BOOL didDragging = NO;
// If not selectable then don't recognize the mouse down
if (!is_selectable)
return;
if (![_window makeFirstResponder: self])
return;
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 (selected_range.length == 0) // remove old cursor
{
[self drawInsertionPointAtIndex: selected_range.location
color: nil turnedOn: NO];
}
else
[self drawSelectionAsRangeNoCaret: selected_range];
//<!> make this non - blocking (or make use of timed entries)
for (currentEvent = [_window
nextEventMatchingMask:
(NSLeftMouseDraggedMask|NSLeftMouseUpMask)];
[currentEvent type] != NSLeftMouseUp;
(currentEvent = [_window
nextEventMatchingMask:
(NSLeftMouseDraggedMask|NSLeftMouseUpMask)]),
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];
[_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)))];
[_window flushWindow];
}
else
{
[self drawRectNoSelection: [self visibleRect]];
[self drawSelectionAsRangeNoCaret: chosenRange];
[_window flushWindow];
}
didDragging = YES;
}
NSDebugLog(@"chosenRange. location = % d, length = %d\n",
(int)chosenRange.location, (int)chosenRange.length);
[self setSelectedRangeNoDrawing: chosenRange];
if (!didDragging)
[self drawSelectionAsRange: chosenRange];
else if (chosenRange.length == 0)
[self drawInsertionPointAtIndex: chosenRange.location
color: [NSColor blackColor] turnedOn: YES];
// remember for column stable cursor up/down
currentCursorX = [self rectForCharacterIndex: chosenRange.location].origin.x;
// remember for column stable cursor up/down
currentCursorY = [self rectForCharacterIndex: chosenRange.location].origin.y;
[self unlockFocus];
[_window flushWindow];
}
- (void) keyDown: (NSEvent *)theEvent
{
unsigned short keyCode;
// If not editable then don't recognize the key down
if (!is_editable)
{
[super keyDown: theEvent];
return;
}
keyCode = [theEvent keyCode];
// Some keys are extremely special if we are field editor
if ([self isFieldEditor])
{
int notNumber = NSIllegalTextMovement;
switch (keyCode)
{
case NSUpArrowFunctionKey:
notNumber = NSUpTextMovement;
break;
case NSDownArrowFunctionKey:
notNumber = NSDownTextMovement;
break;
case NSLeftArrowFunctionKey:
//notNumber = NSLeftTextMovement;
break;
case NSRightArrowFunctionKey:
//notNumber = NSRightTextMovement;
break;
case NSCarriageReturnKey:
notNumber = NSReturnTextMovement;
break;
case 0x09:
if ([theEvent modifierFlags] & NSShiftKeyMask)
notNumber = NSBacktabTextMovement;
else
notNumber = NSTabTextMovement;
break;
}
if (notNumber != NSIllegalTextMovement)
{
// This is similar to [self resignFirstResponder],
// with the difference that in the notification we need
// to put the NSTextMovement, which resignFirstResponder
// does not. Also, if we are ending editing, we are going
// to be removed, so it's useless to update any drawing.
NSNumber *number;
NSDictionary *uiDictionary;
if (([self isEditable])
&& ([delegate respondsToSelector:
@selector(textShouldEndEditing:)])
&& ([delegate textShouldEndEditing: self] == NO))
return;
// Add any clean-up stuff here
number = [NSNumber numberWithInt: notNumber];
uiDictionary = [NSDictionary dictionaryWithObject: number
forKey: @"NSTextMovement"];
[[NSNotificationCenter defaultCenter]
postNotificationName: NSTextDidEndEditingNotification
object: self
userInfo: uiDictionary];
return;
}
}
// Special Characters for generic NSText
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 NSDeleteFunctionKey:
if (selected_range.location != [self textLength])
{
/* Not at the end of text -- delete following character */
[self deleteRange:
[self selectionRangeForProposedRange:
NSMakeRange (selected_range.location, 1)
granularity: NSSelectByCharacter]
backspace: NO];
return;
}
/* end of text: behave the same way as NSBackspaceKey */
case NSBackspaceKey:
[self deleteRange: selected_range backspace: YES];
return;
#if 1
case 0x6d: // end - key: debugging: enforce complete re - layout
[self rebuildLineLayoutInformation];
[self setNeedsDisplay: YES];
return;
#endif
#if 1
// TODO: Choose another key
case 0x45: // num - lock: debugging
NSLog ([lineLayoutInformation description]);
return;
#endif
case NSCarriageReturnKey:
NSLog (@"\bCarriage return.\b\n");
[self insertText: [[self class] newlineString]];
return;
}
// If the character(s) was not a special one, simply insert it.
[self insertText: [theEvent characters]];
}
- (BOOL) acceptsFirstResponder
{
if ([self isSelectable])
return YES;
else
return NO;
}
- (BOOL) resignFirstResponder
{
if (([self isEditable])
&& ([delegate respondsToSelector: @selector(textShouldEndEditing:)])
&& ([delegate textShouldEndEditing: self] == NO))
return NO;
// Add any clean-up stuff here
if ([self shouldDrawInsertionPoint])
{
[self drawInsertionPointAtIndex: selected_range.location
color: nil turnedOn: NO];
//<!> stop timed entry
}
[[NSNotificationCenter defaultCenter]
postNotificationName: NSTextDidEndEditingNotification
object: self];
return YES;
}
- (BOOL) becomeFirstResponder
{
if ([self isSelectable] == NO)
return NO;
if (([delegate respondsToSelector: @selector(textShouldBeginEditing:)])
&& ([delegate textShouldBeginEditing: self] == NO))
return NO;
// Add any initialization stuff here.
//if ([self shouldDrawInsertionPoint])
// {
// [self lockFocus];
// [self drawInsertionPointAtIndex: selected_range.location
// color: [NSColor blackColor] turnedOn: YES];
// [self unlockFocus];
// //<!> restart timed entry
// }
[[NSNotificationCenter defaultCenter]
postNotificationName: NSTextDidBeginEditingNotification
object: self];
return YES;
}
- (void) drawRect: (NSRect)rect
{
if (displayDisabled)
return;
[self drawRectNoSelection: rect];
[self drawSelectionAsRange: selected_range];
}
// text lays out from top to bottom
- (BOOL) isFlipped
{
return YES;
}
- (BOOL) isOpaque
{
if (draws_background == NO
|| background_color == nil
|| [background_color alphaComponent] < 1.0)
return NO;
else
return YES;
}
//
// 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 = RETAIN([aDecoder decodeObject]);
default_font = RETAIN([aDecoder decodeObject]);
[aDecoder decodeValueOfObjCType: @encode(NSRange) at: &selected_range];
return self;
}
//
// NSChangeSpelling protocol
//
- (void) changeSpelling: sender
{
[self insertText: [[(NSControl*)sender selectedCell] stringValue]];
}
//
// NSIgnoreMisspelledWords protocol
//
- (void) ignoreSpelling: sender
{
[[NSSpellChecker sharedSpellChecker]
ignoreWord: [[(NSControl*)sender selectedCell] stringValue]
inSpellDocumentWithTag: [self spellCheckerDocumentTag]];
}
@end
@implementation NSText(GNUstepExtension)
+ (NSString*) newlineString
{
return @"\n";
}
- (void) replaceRange: (NSRange)range
withAttributedString: (NSAttributedString*)attrString
{
int start = [self lineLayoutIndexForCharacterIndex: range.location];
if ([self isRichText])
{
return [rtfContent replaceCharactersInRange: range
withAttributedString: attrString];
}
else
return [plainContent replaceCharactersInRange: range
withString: [attrString string]];
[self rebuildLineLayoutInformationStartingAtLine: start
delta: [attrString length] - range.length
actualLine: start];
[self setNeedsDisplay: YES];
}
- (unsigned) textLength
{
if ([self isRichText])
return [rtfContent length];
else
return [plainContent length];
}
- (void) disableDisplay
{
displayDisabled = YES;
}
- (void) reenableDisplay
{
displayDisabled = NO;
}
- (void) sizeToFit: sender
{
[self sizeToFit];
}
- (int) spellCheckerDocumentTag
{
if (!spellCheckerDocumentTag)
spellCheckerDocumentTag = [NSSpellChecker uniqueSpellDocumentTag];
return spellCheckerDocumentTag;
}
// central text inserting method (takes care
// of optimized redraw/ cursor positioning)
- (void) insertText: insertObjc
{
NSRange selectedRange = selected_range;
int lineIndex = [self lineLayoutIndexForCharacterIndex:
selectedRange.location];
int origLineIndex = lineIndex;
int caretLineIndex = lineIndex;
NSRange redrawLineRange;
NSString *insertString = nil;
if ([insertObjc isKindOfClass: [NSString class]])
insertString = insertObjc;
else
insertString = [insertObjc string];
// 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])
{
[self replaceRange: selected_range
withAttributedString: [insertObjc isKindOfClass:
[NSAttributedString class]]?
insertObjc:
[[[NSAttributedString alloc]
initWithString: insertString
attributes: [self typingAttributes]]
autorelease]];
}
else
{
[self replaceRange: selected_range withString: insertString];
}
redrawLineRange.length = [self rebuildLineLayoutInformationStartingAtLine:
redrawLineRange.location
delta:
[insertString length] - selectedRange.length
actualLine: caretLineIndex];
// ScrollView interaction
[self sizeToFit];
// move cursor <!> [self selectionRangeForProposedRange: ]
[self
setSelectedRange:
NSMakeRange (selected_range.location + [insertString length], 0)];
// remember x for row - stable cursor movements
currentCursorX = [self rectForCharacterIndex:
selected_range.location].origin.x;
// remember x for row - stable cursor movements
currentCursorY = [self rectForCharacterIndex:
selected_range.location].origin.y;
redrawLineRange = NSIntersectionRange (redrawLineRange,
[self lineRangeForRect:
[self visibleRect]]);
[self redisplayForLineRange: redrawLineRange];
// broadcast notification
[[NSNotificationCenter defaultCenter]
postNotificationName: NSTextDidChangeNotification
object: self];
}
- (void) setTypingAttributes: (NSDictionary*) dict
{
if (![dict isKindOfClass: [NSMutableDictionary class]])
{
RELEASE(typingAttributes);
typingAttributes = [[NSMutableDictionary alloc] initWithDictionary: dict];
}
else
ASSIGN(typingAttributes, (NSMutableDictionary*)dict);
}
- (NSMutableDictionary*) typingAttributes
{
if (typingAttributes)
return typingAttributes;
else
return [NSMutableDictionary dictionaryWithDictionary:
[self defaultTypingAttributes]];
}
- (BOOL) shouldDrawInsertionPoint
{
return (selected_range.length == 0) && [self isEditable];
}
- (void) drawInsertionPointInRect: (NSRect)rect
color: (NSColor *)color
turnedOn: (BOOL)flag
{
BOOL didLock = NO;
if (!_window)
return;
if (_window && [[self class] focusView] != self)
{
[self lockFocus];
didLock = YES;
}
if (flag)
{
[color set];
NSRectFill(rect);
}
else
{
[[self backgroundColor] set];
NSRectFill(rect);
}
if (didLock)
{
[self unlockFocus];
[_window flushWindow];
}
}
- (NSRange) selectionRangeForProposedRange: (NSRange)proposedCharRange
granularity: (NSSelectionGranularity)granularity
{
NSCharacterSet *set = nil;
unsigned lastIndex = [self textLength] - 1;
unsigned lpos = MIN(lastIndex, proposedCharRange.location);
// <!>better: rpos = MAX(0,(int)NSMaxRange(proposedCharRange) - 1);
unsigned rpos = NSMaxRange(proposedCharRange);
NSString *string = [self string];
BOOL rmemberstate, lmemberstate;
if (![string length])
{
NSLog(@"We have no length, our range is 0,0\n");
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--;
if ([set characterIsMember: [string characterAtIndex: lpos]] != lmemberstate
&& lpos < proposedCharRange.location)
lpos++;
return MakeRangeFromAbs(lpos,rpos);
}
- (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]];
}
@end
@implementation NSText(GNUstepPrivate)
//<!>
//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;
}
- (NSDictionary*) defaultTypingAttributes
{
return [NSDictionary dictionaryWithObjectsAndKeys:
default_font, NSFontAttributeName,
text_color, NSForegroundColorAttributeName,
nil];
}
- (void) setSelectionWordGranularitySet: (NSCharacterSet*) aSet
{
ASSIGN(selectionWordGranularitySet, aSet);
}
- (void) setSelectionParagraphGranularitySet: (NSCharacterSet*) aSet
{
ASSIGN(selectionParagraphGranularitySet, aSet);
}
/*
* Handle enabling/disabling of services menu items.
*/
- (id) validRequestorForSendType: (NSString*)sendType
returnType: (NSString*)returnType
{
if ((!sendType || [sendType isEqual: NSStringPboardType])
&& (!returnType || [returnType isEqual: NSStringPboardType]))
{
if ((selected_range.length || !sendType)
&& ([self isEditable] || !returnType))
{
return self;
}
}
return [super validRequestorForSendType: sendType
returnType: returnType];
}
// 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---------------------------------
- (BOOL) writeSelectionToPasteboard: (NSPasteboard*)pb
types: (NSArray*)sendTypes
{
NSArray *types;
NSString *string;
if ([sendTypes containsObject: NSStringPboardType] == NO)
{
return NO;
}
types = [NSArray arrayWithObjects: NSStringPboardType, nil];
[pb declareTypes: types owner: nil];
string = [self string];
string = [string substringWithRange: selected_range];
return [pb setString: string forType: NSStringPboardType];
}
// <!>
// 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: selected_range];
}
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;
}
- (BOOL) readSelectionFromPasteboard: (NSPasteboard*)pb
{
return [self performPasteOperation: pb];
}
- (void) redisplayForLineRange: (NSRange) redrawLineRange
{
BOOL didLock = NO;
if (_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 = _frame.size.width - displayRect.origin.x;
[[self backgroundColor] set];
NSRectFill (displayRect);
[self drawLinesInLineRange: redrawLineRange];
[self drawSelectionAsRange: selected_range];
}
if ([self drawsBackground]) // clean up the remaining area under text of us
{
float lowestY = 0;
NSRect myFrame = _frame;
if ([lineLayoutInformation count])
lowestY = NSMaxY ([[lineLayoutInformation lastObject] lineRect]);
if (![lineLayoutInformation count]
|| (lowestY < NSMaxY(myFrame)
&& myFrame.size.height <= [self minSize].height))
{
[[self backgroundColor] set];
NSRectFill (NSMakeRect (0, lowestY, myFrame.size.width,
NSMaxY (myFrame) - lowestY));
if (![lineLayoutInformation count]
|| [[lineLayoutInformation lastObject]
type] == LineLayoutInfoType_Paragraph)
[self drawSelectionAsRange: selected_range];
}
}
if (didLock)
{
[self unlockFocus];
[_window flushWindow];
}
}
- (void) rebuildForCharacterRange: (NSRange) aRange
{
NSRange redrawLineRange;
int start = [self lineLayoutIndexForCharacterIndex: aRange.location];
int count = [self rebuildLineLayoutInformationStartingAtLine: start
delta: aRange.length
actualLine: aRange.location];
redrawLineRange = NSMakeRange (MAX (0, start - 1), count + 1);
redrawLineRange = NSIntersectionRange (redrawLineRange,
[self lineRangeForRect:
[self visibleRect]]);
[self redisplayForLineRange: redrawLineRange];
//NSLog (NSStringFromRange (redrawLineRange));
}
// central text deletion/backspace method
// (takes care of optimized redraw/ cursor positioning)
- (void) deleteRange: (NSRange) aRange backspace: (BOOL) flag
{
int redrawLineIndex, caretLineIndex, firstLineIndex;
int lastLineIndex, linePosition;
NSRange redrawLineRange;
NSRange deleteRange;
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)];
// since first word may move upward
redrawLineIndex = MAX (0, firstLineIndex - 1);
if (firstLineIndex
&& [[lineLayoutInformation objectAtIndex: firstLineIndex - 1]
type] == LineLayoutInfoType_Paragraph)
{
_GNULineLayoutInfo *upperInfo
= [lineLayoutInformation objectAtIndex: firstLineIndex];
_GNULineLayoutInfo *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])
[rtfContent deleteCharactersInRange: deleteRange];
else
[plainContent deleteCharactersInRange: deleteRange];
redrawLineRange.length
= [self rebuildLineLayoutInformationStartingAtLine: redrawLineRange.location
delta: - deleteRange.length
actualLine: caretLineIndex];
// ScrollView interaction
[self sizeToFit];
// move cursor <!> [self selectionRangeForProposedRange: ]
[self setSelectedRange: NSMakeRange (deleteRange.location, 0)];
// remember x for row - stable cursor movements
currentCursorX = [self rectForCharacterIndex:
selected_range.location].origin.x;
// remember x for row - stable cursor movements
currentCursorY = [self rectForCharacterIndex:
selected_range.location].origin.y;
redrawLineRange
= NSIntersectionRange (redrawLineRange,
[self lineRangeForRect: [self visibleRect]]);
[self redisplayForLineRange: redrawLineRange];
// broadcast notification
[[NSNotificationCenter defaultCenter]
postNotificationName: NSTextDidChangeNotification
object: self];
}
- (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;
/* Do nothing if we are at beginning of text */
if (selected_range.location == 0)
return;
if (selected_range.length)
{
currentCursorX = [self rectForCharacterIndex:
selected_range.location].origin.x;
currentCursorY = [self rectForCharacterIndex:
selected_range.location].origin.y;
}
cursorIndex = selected_range.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]];
// FIXME: Terrible hack.
[self insertText: @""];
}
- (void) moveCursorDown: sender
{
unsigned cursorIndex;
NSRect cursorRect;
/* Do nothing if we are at end of text */
if (selected_range.location == [self textLength])
return;
if (selected_range.length)
{
currentCursorX = [self rectForCharacterIndex:
NSMaxRange (selected_range)].origin.x;
currentCursorY = [self rectForCharacterIndex:
NSMaxRange (selected_range)].origin.y;
}
cursorIndex = selected_range.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]];
// FIXME: Terrible hack.
[self insertText: @""];
}
- (void) moveCursorLeft: sender
{
/* Do nothing if we are at beginning of text */
if (selected_range.location == 0)
return;
[self setSelectedRange:
[self selectionRangeForProposedRange:
NSMakeRange (selected_range.location - 1, 0)
granularity: NSSelectByCharacter]];
currentCursorX = [self rectForCharacterIndex:
selected_range.location].origin.x;
}
- (void) moveCursorRight: sender
{
/* Do nothing if we are at end of text */
if (selected_range.location == [self textLength])
return;
[self setSelectedRange:
[self selectionRangeForProposedRange:
NSMakeRange (MIN (NSMaxRange (selected_range) + 1,
[self textLength]), 0)
granularity: NSSelectByCharacter]];
currentCursorX = [self rectForCharacterIndex:
selected_range.location].origin.x;
}
- (NSRange) characterRangeForBoundingRect: (NSRect)boundsRect
{
NSRange lineRange = [self lineRangeForRect: boundsRect];
if (lineRange.length)
return MakeRangeFromAbs ([[lineLayoutInformation
objectAtIndex: lineRange.location]
lineRange].location,
NSMaxRange ([[lineLayoutInformation
objectAtIndex:
NSMaxRange (lineRange)]
lineRange]));
else
return NSMakeRange (0, 0);
}
- (unsigned) characterIndexForPoint: (NSPoint)point
{
int i;
NSEnumerator *lineEnum;
_GNULineLayoutInfo *currentInfo;
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]);
// this loop holds some optimization potential (linear search)
if (NSPointInRect (point, rect))
{
int retPos = 0;
NSRange range = [currentInfo lineRange];
for (retPos = range.location; retPos<= NSMaxRange(range); retPos++)
// this loop holds some optimization potential (linear search)
{
if ([self _sizeOfRange:
NSMakeRange (range.location,
retPos - range.location)].width
>= point.x)
return MAX (0, retPos - 1);
}
return range.location;
}
}
NSLog (@"NSText's characterIndexForPoint: index not found!");
return 0;
}
- (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;
}
// 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, _frame.size.width,
[[[self class] newlineString]
sizeWithAttributes: attributes].height);
if (index >= NSMaxRange([[lineLayoutInformation lastObject] lineRange]))
{
NSRect rect = [[lineLayoutInformation lastObject] lineRect];
if (NSMaxX (rect) >= _frame.size.width)
{
return NSMakeRect (0, NSMaxY(rect),
_frame.size.width, rect.size.height);
}
return NSMakeRect (NSMaxX (rect), rect.origin.y,
_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];
NSSize stringSize
= [self _sizeOfRange:
MakeRangeFromAbs (range.location, index)];
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;
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];
// this loop holds some optimization potential (linear search)
for (retPos = range.location; retPos<= NSMaxRange (range); retPos++)
{
if ([self _sizeOfRange:
NSMakeRange (range.location,
retPos - range.location)].width
>= point.x)
return [lineLayoutInformation indexOfObject: currentInfo];
}
return [lineLayoutInformation indexOfObject: currentInfo];
}
}
return 0;
}
// internal method <!> range is currently not passed as absolute
- (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;
if (0 && isRich)
{
advanceSize = [rtfContent sizeRange:
NSMakeRange (startingLineCharIndex, 1)];
}
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]];
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;
}
}
}
// private helper function
static unsigned
_relocLayoutArray (NSMutableArray *lineLayoutInformation,
NSArray *ghostArray,
int aLine,
int relocOffset,
int rebuildLineDrift,
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;
}
// begin: central line formatting method---------------------------------------
// returns count of lines actually updated
// <!> detachNewThreadSelector: selector toTarget: target withObject: argument;
- (int) rebuildLineLayoutInformationStartingAtLine: (int) aLine
delta: (int) insertionDelta
actualLine: (int) insertionLineIndex
{
NSDictionary *attributes = [self defaultTypingAttributes];
NSPoint drawingPoint = NSZeroPoint;
NSScanner *pScanner;
float width = _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;
if (!lineLayoutInformation)
lineLayoutInformation = [[NSMutableArray alloc] init];
else
{
// 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)
{
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;
}
// 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
&& [lastValidLineInfo lineRect].origin.y == anchorRect.origin.y)
{
drawingPoint = anchorRect.origin;
}
}
[lineLayoutInformation
removeObjectsInRange:
NSMakeRange (aLine, [lineLayoutInformation count] - aLine)];
currentLineIndex = aLine;
// each paragraph
parsedString = [[self 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);
paragraphRange
= scanRange(pScanner, invSelectionParagraphGranularitySet);
trailingNlRange
= scanRange(pScanner, selectionParagraphGranularitySet);
if (leadingNlRange.length > 0)
{
[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;
}
// each line
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 (!isHorizontallyResizable && 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);
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 (!isHorizontallyResizable
&& (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
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);
// 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 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
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
{
// new array obviously longer than the previous one
prevArrayEnum = nil;
}
// end: optimization stuff------------------------------
// -------------------------------------------
}
}
}
}
// add the trailing newlines of current paragraph if any
if (trailingNlRange.length)
{
[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;
}
}
// lines actually updated (optimized drawing)
return [lineLayoutInformation count] - aLine;
}
// end: central line formatting method------------------------------------
- (int) rebuildLineLayoutInformationStartingAtLine: (int) aLine
{
return [self rebuildLineLayoutInformationStartingAtLine: aLine
delta: 0 actualLine: 0];
}
- (int) rebuildLineLayoutInformation
{
// force complete re - layout
RELEASE(lineLayoutInformation);
lineLayoutInformation = nil;
return [self rebuildLineLayoutInformationStartingAtLine: 0
delta: 0
actualLine: 0];
}
// relies on lineLayoutInformation
- (void) drawLinesInLineRange: (NSRange) aRange
{
BOOL isRich = [self isRichText];
if (NSMaxRange (aRange) > MAX (0, [lineLayoutInformation count] - 1))
{
// lay out lines before drawing them
[self rebuildLineLayoutInformationStartingAtLine:
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
if (isRich)
{
[rtfContent drawRange: [currentInfo lineRange]
atPoint: [currentInfo lineRect].origin];
// <!> make this use drawRange: inRect: in the future
// (for proper adoption of layout information [e.g. centering])
}
else
{
[[plainContent substringWithRange: [currentInfo lineRange]]
drawAtPoint: [currentInfo lineRect].origin
withAttributes: attributes];
// <!> make this use drawInRect: withAttributes: in the future
// (for proper adoption of layout information [e.g. centering])
}
}
}
}
- (NSRange) lineRangeForRect: (NSRect) rect
{
NSPoint upperLeftPoint = rect.origin;
NSPoint lowerRightPoint = NSMakePoint (NSMaxX (rect), NSMaxY (rect));
NSRange myTest;
unsigned startLine, endLine;
startLine = [self lineLayoutIndexForPoint: upperLeftPoint];
endLine = [self lineLayoutIndexForPoint: lowerRightPoint];
//FIXME return MakeRangeFromAbs (startLine, endLine + 1);
if ([plainContent length] != 0)
{
myTest = MakeRangeFromAbs (startLine, endLine + 1);
NSDebugLog(@"myTest: length = %d, location = %d\n",
(int)myTest.length,
(int)myTest.location);
return myTest;
}
else
{
myTest = MakeRangeFromAbs (startLine, endLine);
NSDebugLog (@"myTest: length = %d, location = %d\n",
(int)myTest.length,
(int)myTest.location);
return myTest;
}
}
- (void) drawRectNoSelection: (NSRect)rect
{
NSRange redrawLineRange;
// bootstrap layout information
// for [self lineLayoutIndexForCharacterIndex: anIndex] to work initially
if (![lineLayoutInformation count])
{
[self rebuildLineLayoutInformationStartingAtLine: 0];
}
redrawLineRange = [self lineRangeForRect: rect];
if ([self drawsBackground])
{
// clear area under text
[[self backgroundColor] set];
NSRectFill(rect);
}
[self drawLinesInLineRange: redrawLineRange];
}
- (void) drawInsertionPointAtIndex: (unsigned)index
color: (NSColor*)color
turnedOn: (BOOL)flag
{
NSRect startRect = [self rectForCharacterIndex: index];
NSRect drawRect;
drawRect = NSMakeRect (startRect.origin.x, startRect.origin.y,
1, startRect.size.height);
[self drawInsertionPointInRect: drawRect
color: [NSColor blackColor]
turnedOn: flag];
}
- (void) drawSelectionAsRangeNoCaret: (NSRange) aRange
{
if (aRange.length)
{
NSRect startRect = [self rectForCharacterIndex: aRange.location];
NSRect 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
// first line
NSHighlightRect (NSMakeRect (startRect.origin.x, startRect.origin.y,
_frame.size.width - startRect.origin.x,
startRect.size.height));
// second line
NSHighlightRect (NSMakeRect (0, endRect.origin.y, endRect.origin.x,
endRect.size.height));
}
else
{
// 3 Rects: multiline selection
// first line
NSHighlightRect (NSMakeRect (startRect.origin.x, startRect.origin.y,
_frame.size.width - startRect.origin.x,
startRect.size.height));
// intermediate lines
NSHighlightRect (NSMakeRect (0, NSMaxY(startRect),
_frame.size.width,
endRect.origin.y - NSMaxY (startRect)));
// last line
NSHighlightRect (NSMakeRect (0, endRect.origin.y, endRect.origin.x,
endRect.size.height));
}
}
}
- (void) drawSelectionAsRange: (NSRange) aRange
{
if (aRange.length)
{
[self drawSelectionAsRangeNoCaret: aRange];
}
else
{
[self drawInsertionPointAtIndex: aRange.location
color: [NSColor blackColor]
turnedOn: YES];
}
}
// low level selection setting including delegation
- (void) setSelectedRangeNoDrawing: (NSRange)range
{
#if 0
//<!> ask delegate for selection validation
#endif
selected_range = range;
#if 0
[[NSNotificationCenter defaultCenter]
postNotificationName: NSTextViewDidChangeSelectionNotification
object: self
userInfo: [NSDictionary
dictionaryWithObjectsAndKeys:
NSStringFromRange (selected_range),
NSOldSelectedCharacterRange, nil]];
#endif
}
- (NSSize) _sizeOfRange: (NSRange)range
{
if (!range.length)
return NSZeroSize;
if ([self isRichText])
{
return [[rtfContent attributedSubstringFromRange: range] size];
}
else
{
// FIXME: should we use the default or the current typing attribs?
return [[plainContent substringWithRange: range]
sizeWithAttributes: [self defaultTypingAttributes]];
}
}
@end