diff --git a/ChangeLog b/ChangeLog index 2be06950f..209c15a74 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,57 @@ +2002-09-11 Pierre-Yves Rivaille + + * Source/NSText.m ([NSText -initWithCoder:]): + Retain the decoded background color. + + * Source/NSTextView.m : + multiple location: don't ignore textContainer's inset + ([NSTextView -initWithCoder:]) + ([NSTextView -encodeWithCoder:]) + Encode caret color, textContainer's size and whether the + textContainer has widthTracksTextView or heightTracksTextView set. + ([NSTextView -sizeToFit]) Change width (height) only if + is_horizontally_resizable (is_vertically_resizable). + ([NSTextView -scrollRangeToVisible:]) scrolls to the first char of + the range. + ([NSTextView -invalidateTextContainerOrigin]): Don't ignore inset, + set the origin so that the justification is preserved even if the + textContainer's width is larger that the textview's width. + ([NSTextView -updateInsertionPointStateAndRestartTimer:]): + update and place the insertion point properly. + ([NSTextView -moveDown:]) & ([NSTextView -moveUp:]): + Don't ignore inset. Make insertion point visible. + + * Source/NSTextField.m ([NSTextField -acceptsFirstResponder:]): + responds NO if the editing is still in progrss. + + * Source/NSLayoutManager.m + ([NSLayoutManager -drawBackgroundForGlyphRange:atPoint:]): + Don't ignore textContainer's inset. + + * Source/GSSimpleLayoutManager.m + Replaced [-drawLinesInLineRange:] with [-drawLinesInLineRange:atPoint:]. + Replaced [-drawSelectionAsRangeNoCaret:] with + [-drawSelectionAsRangeNoCaret:atPoint:]. + Those replacements make it easier to take inset into account. + ([-lineFragmentUsedRectForGlyphAtIndex:effectiveRange:]) + ([-locationForGlyphAtIndex:]) + ([-boundingRectForGlyphRange:inTextContainer:]) + ([-rectForCharacterIndex:]) + ([-setNeedsDisplayForLineRange:inTextContainer:]): + Various fixes. + ([-textStorage:edited:range:changeInLength:invalidatedRange:]) + Update insertion point if needed (useful when alignment changes). + ([-rebuildForRange:delta:inTextContainer:]): + Update layout mechanism. + + * Source/NSCell.m + ([NSCell -highlight:withFrame:inView:]): + Ask the control to draw the background if we are not opaque. + ([NSCell -editWithFrame:inView:editor:delegate:event:]): + ([NSCell -selectWithFrame:inView:editor:delegate:start:length:]): + ([NSCell -endEditing:]): + instantiate (and remove) the textview so that it can be scrolled. + 2002-09-09 Adam Fedor * configure.ac: Switch to disaple gsnd. diff --git a/Source/GSSimpleLayoutManager.m b/Source/GSSimpleLayoutManager.m index cc11b5ddb..aad1f9376 100644 --- a/Source/GSSimpleLayoutManager.m +++ b/Source/GSSimpleLayoutManager.m @@ -15,6 +15,9 @@ Date: September 2000 Extracted from NSText, reorganised to specification + Author: Pierre-Yves Rivaille + Date: September 2002 + This file is part of the GNUstep GUI Library. This library is free software; you can redistribute it and/or @@ -47,6 +50,7 @@ #include #include #include +#include #include "GSSimpleLayoutManager.h" @@ -309,6 +313,35 @@ static inline float defaultFontHeight () return currentInfo->lineFragmentRect; } +- (NSRect)lineFragmentUsedRectForGlyphAtIndex: (unsigned)index + effectiveRange: (NSRange *)lineFragmentRange +{ + _GNULineLayoutInfo *currentInfo; + + if (![_textStorage length] || ![_lineLayoutInformation count]) + { + // we return the line fragment from the virtual newline + // that we added at the end of the empty text + if (lineFragmentRange) + { + lineFragmentRange->location=0; + lineFragmentRange->length=0; + } + return ((_GNULineLayoutInfo *)[_lineLayoutInformation lastObject])->usedRect; + } + + currentInfo = [_lineLayoutInformation + objectAtIndex: [self lineLayoutIndexForGlyphIndex: + index]]; + + if (lineFragmentRange) + { + *lineFragmentRange = currentInfo->glyphRange; + } + + return currentInfo->usedRect; +} + - (NSPoint)locationForGlyphAtIndex:(unsigned)index { float x; @@ -325,7 +358,8 @@ static inline float defaultFontHeight () index]]; if (index >= NSMaxRange (currentInfo->glyphRange)) { - return NSMakePoint (NSMaxX (currentInfo->usedRect), 0); + x = [self _sizeOfRange: currentInfo->glyphRange].width; + return NSMakePoint (x, 0); } start = currentInfo->glyphRange.location; @@ -341,7 +375,9 @@ static inline float defaultFontHeight () { _GNULineLayoutInfo *currentInfo; unsigned i1, i2; - NSRect rect1; + NSRect rect1, rect2; + NSSize size; + NSRect rect; if (![_textStorage length] || ![_lineLayoutInformation count]) { @@ -351,22 +387,72 @@ static inline float defaultFontHeight () i1 = [self lineLayoutIndexForGlyphIndex: aRange.location]; i2 = [self lineLayoutIndexForGlyphIndex: NSMaxRange(aRange)]; - // This is not exacty what we need, but should be correct enough - currentInfo = [_lineLayoutInformation objectAtIndex: i1]; - rect1 = currentInfo->lineFragmentRect; - if (i1 != i2) + if (i1 == i2) { + // the range is not multiline. + + currentInfo = [_lineLayoutInformation objectAtIndex: i1]; + rect1 = currentInfo->usedRect; + size = [self _sizeOfRange: + NSMakeRange + (currentInfo->glyphRange.location, + aRange.location - + currentInfo->glyphRange.location)]; + rect1.origin.x += size.width; + rect1.size.width = [self _sizeOfRange: + NSMakeRange + (aRange.location - + currentInfo->glyphRange.location, 1)].width; + // rect1 is the rect for the first glyph in the range + currentInfo = [_lineLayoutInformation objectAtIndex: i2]; - rect1 = NSUnionRect(rect1, currentInfo->lineFragmentRect); + rect2 = currentInfo->usedRect; + size = [self _sizeOfRange: + NSMakeRange + (currentInfo->glyphRange.location, + NSMaxRange(aRange) - + currentInfo->glyphRange.location)]; + rect2.origin.x += size.width; + rect2.size.width = [self _sizeOfRange: + NSMakeRange + (NSMaxRange(aRange) - + currentInfo->glyphRange.location, 1)].width; + // rect2 is the rect for the last glyph in the range + + // TODO: it might not work if some glyph draw out of the usedRect. + // (is this really possible ?) } else { - //float width = [aTextContainer containerSize].width; - // FIXME: When we are on one line we may need only part of it + // this is a multiline range, therefore the bounding rect goes + // from the beginning of a line till the end + // getting the lineFragmentRects will give the correct result + currentInfo = [_lineLayoutInformation objectAtIndex: i1]; + rect1 = currentInfo->lineFragmentRect; + + currentInfo = [_lineLayoutInformation objectAtIndex: i2]; + rect2 = currentInfo->lineFragmentRect; } - return rect1; + + + + // this does compute the smallest rect enclosing + // rect1 (or rect1.origin if rect1 is empty) + // and rect2 (or rect2.origin if rect1 is empty) + + rect = NSMakeRect(MIN(NSMinX(rect1), NSMinX(rect2)), + MIN(NSMinY(rect1), NSMinY(rect2)), 0, 0); + + rect = NSMakeRect(NSMinX(rect), + NSMinY(rect), + MAX(NSMaxX(rect1), NSMaxX(rect2)) - NSMinX(rect), + MAX(NSMaxY(rect1), NSMaxY(rect2)) - NSMinY(rect)); + + + return rect; + } - (NSRange)glyphRangeForBoundingRect:(NSRect)aRect @@ -492,9 +578,21 @@ static inline float defaultFontHeight () lineRange = [self rebuildForRange: aRange delta: delta inTextContainer: aTextContainer]; + [[aTextContainer textView] sizeToFit]; [[aTextContainer textView] invalidateTextContainerOrigin]; + { + NSRange sr = [[aTextContainer textView] selectedRange]; + if (sr.length == 0 + && aRange.location <= sr.location + && aRange.location + aRange.length >= sr.location) + { + [[aTextContainer textView] + updateInsertionPointStateAndRestartTimer: YES]; + } + } + /* FIXME - it was reported that at this point lineRange is no longer * correct ... looks like sizeToFit / invalidateTextContainerOrigin * migth cause additional relayout. */ @@ -511,7 +609,8 @@ static inline float defaultFontHeight () unsigned end = [self lineLayoutIndexForGlyphIndex: NSMaxRange(glyphRange)]; NSRange lineRange = NSMakeRange(start, end + 1 - start); - [self drawLinesInLineRange: lineRange]; + [self drawLinesInLineRange: lineRange + atPoint: containerOrigin]; // We have to redraw the part of the selection that is inside // the redrawn lines @@ -520,7 +619,8 @@ static inline float defaultFontHeight () if ((selectedRange.length && NSLocationInRange(newRange.location, selectedRange))) { - [self drawSelectionAsRangeNoCaret: newRange]; + [self drawSelectionAsRangeNoCaret: newRange + atPoint: containerOrigin]; } } @@ -705,7 +805,7 @@ forStartOfGlyphRange: (NSRange)glyphRange currentInfo = [_lineLayoutInformation lastObject]; if (index >= NSMaxRange(currentInfo->glyphRange)) { - NSRect rect = currentInfo->lineFragmentRect; + NSRect rect = currentInfo->usedRect; return NSMakeRect(NSMaxX (rect), rect.origin.y, width - NSMaxX (rect), @@ -717,7 +817,7 @@ forStartOfGlyphRange: (NSRange)glyphRange objectAtIndex: [self lineLayoutIndexForGlyphIndex: index]]; start = currentInfo->glyphRange.location; - rect = currentInfo->lineFragmentRect; + rect = currentInfo->usedRect; x = rect.origin.x + [self _sizeOfRange: NSMakeRange(start, index-start)].width; return NSMakeRect(x, rect.origin.y, NSMaxX (rect) - x, @@ -725,6 +825,7 @@ forStartOfGlyphRange: (NSRange)glyphRange } - (void) drawSelectionAsRangeNoCaret: (NSRange)aRange + atPoint: (NSPoint)containerOrigin { unsigned i, count; NSTextContainer *aTextContainer; @@ -739,6 +840,8 @@ forStartOfGlyphRange: (NSRange)glyphRange for (i = 0; i < count; i++) { + rects[i].origin.x += containerOrigin.x; + rects[i].origin.y += containerOrigin.y; NSHighlightRect (rects[i]); } } @@ -758,7 +861,8 @@ forStartOfGlyphRange: (NSRange)glyphRange } // relies on _lineLayoutInformation -- (void) drawLinesInLineRange: (NSRange)aRange; +- (void) drawLinesInLineRange: (NSRange)aRange + atPoint: (NSPoint)containerOrigin { NSArray *linesToDraw; NSEnumerator *lineEnum; @@ -774,8 +878,12 @@ forStartOfGlyphRange: (NSRange)glyphRange for ((lineEnum = [linesToDraw objectEnumerator]); (currentInfo = [lineEnum nextObject]);) { + NSRect rect; + rect = currentInfo->lineFragmentRect; + rect.origin.x += containerOrigin.x; + rect.origin.y += containerOrigin.y; [_textStorage drawRange: currentInfo->glyphRange - inRect: currentInfo->lineFragmentRect]; + inRect: rect]; } } @@ -792,14 +900,17 @@ forStartOfGlyphRange: (NSRange)glyphRange float width = 0; if (aTextContainer) width = [aTextContainer containerSize].width; - if (redrawLineRange.length > 1) displayRect = NSUnionRect(displayRect, [[_lineLayoutInformation objectAtIndex: (int)NSMaxRange(redrawLineRange) - 1] lineFragmentRect]); - displayRect.size.width = width - displayRect.origin.x; + displayRect.size.width = width; + displayRect.origin.x += + [[aTextContainer textView] textContainerInset].width; + displayRect.origin.y += + [[aTextContainer textView] textContainerInset].height; [[aTextContainer textView] setNeedsDisplayInRect: displayRect]; } } @@ -908,10 +1019,11 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet) { _GNULineLayoutInfo *lastValidLineInfo = [_lineLayoutInformation objectAtIndex: aLine - 1]; - NSRect aRect = lastValidLineInfo->lineFragmentRect; + NSRect aRect = lastValidLineInfo->usedRect; startingIndex = NSMaxRange(lastValidLineInfo->glyphRange); - drawingPoint = aRect.origin; + drawingPoint.x = 0; + drawingPoint.y = aRect.origin.y; drawingPoint.y += aRect.size.height; } @@ -927,11 +1039,39 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet) // FIXME: This should be done via extra line fragment // If there is no text add one empty box - [_lineLayoutInformation - addObject: [_GNULineLayoutInfo - lineLayoutWithRange: NSMakeRange (0, 0) - rect: NSMakeRect (0, 0, width, defaultFontHeight ()) - usedRect: NSMakeRect (0, 0, 1, defaultFontHeight ())]]; + { + NSParagraphStyle *style = + (NSParagraphStyle*)[_textStorage + attribute: NSParagraphStyleAttributeName + atIndex: 0 + effectiveRange: NULL]; + + if ([style alignment] == NSRightTextAlignment) + { + [_lineLayoutInformation + addObject: [_GNULineLayoutInfo + lineLayoutWithRange: NSMakeRange (0, 0) + rect: NSMakeRect (0, 0, width, defaultFontHeight ()) + usedRect: NSMakeRect (width-1, 0, 1, defaultFontHeight ())]]; + } + else if ([style alignment] == NSCenterTextAlignment) + { + [_lineLayoutInformation + addObject: [_GNULineLayoutInfo + lineLayoutWithRange: NSMakeRange (0, 0) + rect: NSMakeRect (0, 0, width, defaultFontHeight ()) + usedRect: NSMakeRect ((int) width/2, 0, 1, defaultFontHeight ())]]; + } + else + { + [_lineLayoutInformation + addObject: [_GNULineLayoutInfo + lineLayoutWithRange: NSMakeRange (0, 0) + rect: NSMakeRect (0, 0, width, defaultFontHeight ()) + usedRect: NSMakeRect (0, 0, 1, defaultFontHeight ())]]; + } + } + return NSMakeRange(0,1); } @@ -1012,7 +1152,7 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet) width = fragmentRect.size.width - 2 * padding; usedLineRect = fragmentRect; - usedLineRect.origin.x += padding; + // usedLineRect.origin.x += padding; usedLineRect.size = NSZeroSize; // scan the individual words to the end of the line @@ -1129,6 +1269,29 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet) // the to big fragmentRect.size.height = usedLineRect.size.height; + // adjust the usedLineRect depending on the justification + { + NSParagraphStyle *style = + (NSParagraphStyle*)[_textStorage + attribute: NSParagraphStyleAttributeName + atIndex: startingLineCharIndex + effectiveRange: NULL]; + + if ([style alignment] == NSLeftTextAlignment) + { + } + else if ([style alignment] == NSRightTextAlignment) + { + usedLineRect.origin.x += + fragmentRect.size.width - usedLineRect.size.width - padding; + } + else if ([style alignment] == NSCenterTextAlignment) + { + usedLineRect.origin.x += + (int) (fragmentRect.size.width - usedLineRect.size.width) / 2; + } + } + // This range is to small, as there are more lines that fit // into the container [self setTextContainer: aTextContainer @@ -1190,17 +1353,58 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet) if (aTextContainer) width = [aTextContainer containerSize].width; - [_lineLayoutInformation - addObject: [_GNULineLayoutInfo - lineLayoutWithRange: NSMakeRange (length, 0) - rect: NSMakeRect (drawingPoint.x, drawingPoint.y, - width, defaultFontHeight ()) - usedRect: NSMakeRect (drawingPoint.x, drawingPoint.y, - 1, defaultFontHeight ())]]; + { + NSParagraphStyle *style = + (NSParagraphStyle*)[_textStorage + attribute: NSParagraphStyleAttributeName + atIndex: length - 1 + effectiveRange: NULL]; + + if ([style alignment] == NSRightTextAlignment) + { + [_lineLayoutInformation + addObject: [_GNULineLayoutInfo + lineLayoutWithRange: NSMakeRange (length, 0) + rect: + NSMakeRect (drawingPoint.x + + (width - drawingPoint.x), + drawingPoint.y, + 1, defaultFontHeight ()) + usedRect: + NSMakeRect (drawingPoint.x + + (width - drawingPoint.x), + drawingPoint.y, + 1, defaultFontHeight ())]]; + } + else if ([style alignment] == NSCenterTextAlignment) + { + [_lineLayoutInformation + addObject: [_GNULineLayoutInfo + lineLayoutWithRange: NSMakeRange (length, 0) + rect: + NSMakeRect (drawingPoint.x + + (int)(width - drawingPoint.x) / 2, + drawingPoint.y, + 1, defaultFontHeight ()) + usedRect: + NSMakeRect (drawingPoint.x + + (int)(width - drawingPoint.x) / 2, + drawingPoint.y, + 1, defaultFontHeight ())]]; + } + else + { + [_lineLayoutInformation + addObject: [_GNULineLayoutInfo + lineLayoutWithRange: NSMakeRange (length, 0) + rect: NSMakeRect (drawingPoint.x, drawingPoint.y, + width, defaultFontHeight ()) + usedRect: NSMakeRect (drawingPoint.x, drawingPoint.y, + 1, defaultFontHeight ())]]; + } + } } - - // lines actually updated (optimized drawing) return NSMakeRange(aLine, MAX(1, [_lineLayoutInformation count] - aLine)); } diff --git a/Source/NSCell.m b/Source/NSCell.m index cb7a54b82..6dc26477f 100644 --- a/Source/NSCell.m +++ b/Source/NSCell.m @@ -54,6 +54,8 @@ #include #include +#include + static Class colorClass; static Class cellClass; static Class fontClass; @@ -1692,7 +1694,11 @@ static NSColor *shadowCol; * NSCell simply draws border+text/image and makes no highlighting, * for easier subclassing. */ - [self drawWithFrame: cellFrame inView: controlView]; + if ([self isOpaque] == NO) + { + [controlView displayRect: cellFrame]; + } + [self drawWithFrame: cellFrame inView: controlView]; } } @@ -1708,8 +1714,32 @@ static NSColor *shadowCol; if (!controlView || !textObject || (_cell.type != NSTextCellType)) return; - [textObject setFrame: [self titleRectForBounds: aRect]]; - [controlView addSubview: textObject]; + { + NSClipView *cv = [[NSClipView alloc] + initWithFrame: + [self titleRectForBounds: aRect]]; + [controlView addSubview: cv]; + RELEASE(cv); + [cv setAutoresizesSubviews: NO]; + [cv setDocumentView: textObject]; + [textObject setFrame: [cv bounds]]; + [textObject setHorizontallyResizable: YES]; + [textObject setVerticallyResizable: NO]; + [textObject + setMaxSize: + NSMakeSize (3000, + [self titleRectForBounds: aRect].size.height)]; + [textObject + setMinSize: + [self titleRectForBounds: aRect].size]; + [[textObject textContainer] + setContainerSize: + NSMakeSize (3000, + [self titleRectForBounds: aRect].size.height)]; + [[textObject textContainer] setHeightTracksTextView: NO]; + [[textObject textContainer] setWidthTracksTextView: NO]; + + } if (_formatter != nil) { @@ -1734,6 +1764,7 @@ static NSColor *shadowCol; [textObject setText: [(NSAttributedString *)_contents string]]; } } + [textObject sizeToFit]; [textObject setDelegate: anObject]; [[controlView window] makeFirstResponder: textObject]; @@ -1748,8 +1779,11 @@ static NSColor *shadowCol; - (void) endEditing: (NSText*)textObject { + NSClipView *cv; [textObject setDelegate: nil]; + cv = (NSClipView*)[textObject superview]; [textObject removeFromSuperview]; + [cv removeFromSuperview]; } - (void) selectWithFrame: (NSRect)aRect @@ -1762,17 +1796,61 @@ static NSColor *shadowCol; if (!controlView || !textObject || (_cell.type != NSTextCellType)) return; - [textObject setFrame: [self titleRectForBounds: aRect]]; - [controlView addSubview: textObject]; - if (_cell.contents_is_attributed_string == NO) + { + NSClipView *cv = [[NSClipView alloc] + initWithFrame: + [self titleRectForBounds: aRect]]; + [controlView addSubview: cv]; + RELEASE(cv); + [cv setAutoresizesSubviews: NO]; + [cv setDocumentView: textObject]; + [textObject setFrame: [cv bounds]]; + [textObject setHorizontallyResizable: YES]; + [textObject setVerticallyResizable: NO]; + [textObject + setMaxSize: + NSMakeSize (3000, + [self titleRectForBounds: aRect].size.height)]; + [textObject + setMinSize: + [self titleRectForBounds: aRect].size]; + [[textObject textContainer] + setContainerSize: + NSMakeSize (3000, + [self titleRectForBounds: aRect].size.height)]; + [[textObject textContainer] setWidthTracksTextView: NO]; + [[textObject textContainer] setWidthTracksTextView: NO]; + + } + + if (_formatter != nil) { - [textObject setText: _contents]; + NSString *contents; + + contents = [_formatter editingStringForObjectValue: _objectValue]; + if (contents == nil) + { + contents = _contents; + } + [textObject setText: contents]; } else { - /* FIXME/TODO make sure this is correct. */ - [textObject setText: [(NSAttributedString *)_contents string]]; + if (_cell.contents_is_attributed_string == NO) + { + [textObject setText: _contents]; + } + else + { + /* FIXME/TODO make sure this is correct. */ + [textObject setText: [(NSAttributedString *)_contents string]]; + } } + [textObject sizeToFit]; + + [textObject setDelegate: anObject]; + [[controlView window] makeFirstResponder: textObject]; + [textObject setSelectedRange: NSMakeRange (selStart, selLength)]; [textObject setDelegate: anObject]; [[controlView window] makeFirstResponder: textObject]; diff --git a/Source/NSLayoutManager.m b/Source/NSLayoutManager.m index bf127fc90..0d94537c6 100644 --- a/Source/NSLayoutManager.m +++ b/Source/NSLayoutManager.m @@ -2762,14 +2762,18 @@ forStartOfGlyphRange: (NSRange)glyphRange atPoint: (NSPoint)containerOrigin { NSTextContainer *aTextContainer; - + NSRect rect; + aTextContainer = [self textContainerForGlyphAtIndex: glyphRange.location effectiveRange: NULL]; [[[aTextContainer textView] backgroundColor] set]; - NSRectFill ([self boundingRectForGlyphRange: glyphRange - inTextContainer: aTextContainer]); + rect = [self boundingRectForGlyphRange: glyphRange + inTextContainer: aTextContainer]; + rect.origin.x += containerOrigin.x; + rect.origin.x += containerOrigin.y; + NSRectFill (rect); } - (void) drawGlyphsForGlyphRange: (NSRange)glyphRange diff --git a/Source/NSText.m b/Source/NSText.m index f8381bbd0..f7ff17298 100644 --- a/Source/NSText.m +++ b/Source/NSText.m @@ -715,7 +715,7 @@ static Class concrete; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag]; _tf.is_ruler_visible = flag; - _background_color = [aDecoder decodeObject]; + _background_color = RETAIN([aDecoder decodeObject]); [aDecoder decodeValueOfObjCType: @encode(NSSize) at: &_minSize]; [aDecoder decodeValueOfObjCType: @encode(NSSize) at: &_maxSize]; diff --git a/Source/NSTextField.m b/Source/NSTextField.m index 0f4482184..6c3160b69 100644 --- a/Source/NSTextField.m +++ b/Source/NSTextField.m @@ -282,7 +282,9 @@ static Class textFieldCellClass; /* This could happen if someone pressed the mouse on the borders. */ if (_text_object) - return; + { + return; + } t = [_window fieldEditor: YES forObject: self]; @@ -297,7 +299,6 @@ static Class textFieldCellClass; // [NSCursor hide]; - _text_object = [_cell setUpFieldEditorAttributes: t]; [_cell editWithFrame: _bounds inView: self @@ -313,7 +314,10 @@ static Class textFieldCellClass; - (BOOL) acceptsFirstResponder { - return [self isSelectable]; + // we do not accept first responder if there is already a + // _text_object, else it would make the _text_object resign + // and end editing + return (_text_object == nil) && [self isSelectable]; } - (BOOL) becomeFirstResponder diff --git a/Source/NSTextView.m b/Source/NSTextView.m index 37e73bb6f..3bab9591a 100644 --- a/Source/NSTextView.m +++ b/Source/NSTextView.m @@ -20,6 +20,9 @@ Author: Nicola Pero Date: 2000, 2001, 2002 + Author: Pierre-Yves Rivaille + Date: September 2002 + This file is part of the GNUstep GUI Library. This library is free software; you can redistribute it and/or @@ -63,6 +66,8 @@ #include #include +static const int currentVersion = 2; + #define HUGE 1e7 /* not the same as NSMakeRange! */ @@ -154,7 +159,7 @@ static NSNotificationCenter *nc; { if ([self class] == [NSTextView class]) { - [self setVersion: 1]; + [self setVersion: currentVersion]; nc = [NSNotificationCenter defaultCenter]; [self registerForServices]; } @@ -266,7 +271,8 @@ static NSNotificationCenter *nc; - (void) encodeWithCoder: (NSCoder *)aCoder { - BOOL flag; + BOOL flag; + NSSize containerSize = [_textContainer containerSize]; [super encodeWithCoder: aCoder]; @@ -274,28 +280,71 @@ static NSNotificationCenter *nc; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag]; flag = _tvf.allows_undo; [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag]; + [aCoder encodeObject: _caret_color]; + [aCoder encodeValueOfObjCType: @encode(NSSize) at: &containerSize]; + flag = [_textContainer widthTracksTextView]; + [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag]; + flag = [_textContainer heightTracksTextView]; + [aCoder encodeValueOfObjCType: @encode(BOOL) at: &flag]; } - (id) initWithCoder: (NSCoder *)aDecoder { - NSTextContainer *aTextContainer; - BOOL flag; + int version = [aDecoder versionForClassName: + @"NSTextView"]; - self = [super initWithCoder: aDecoder]; + if (version == currentVersion) + { + NSTextContainer *aTextContainer; + BOOL flag; + NSSize containerSize; + + self = [super initWithCoder: aDecoder]; + + [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag]; + _tvf.smart_insert_delete = flag; + [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag]; + _tvf.allows_undo = flag; + + _caret_color = RETAIN([aDecoder decodeObject]); + [aDecoder decodeValueOfObjCType: @encode(NSSize) at: &containerSize]; + /* build up the rest of the text system, which doesn't get stored + . */ + aTextContainer = [self buildUpTextNetwork: _frame.size]; + [aTextContainer setTextView: (NSTextView*)self]; + [aTextContainer setContainerSize: containerSize]; - [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag]; - _tvf.smart_insert_delete = flag; - [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag]; - _tvf.allows_undo = flag; - - /* build up the rest of the text system, which doesn't get stored - . */ - aTextContainer = [self buildUpTextNetwork: _frame.size]; - [aTextContainer setTextView: (NSTextView*)self]; - /* See initWithFrame: for comments on this RELEASE */ - RELEASE (self); + [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag]; + [aTextContainer setWidthTracksTextView: flag]; + [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag]; + [aTextContainer setHeightTracksTextView: flag]; - [self setSelectedRange: NSMakeRange (0, 0)]; + /* See initWithFrame: for comments on this RELEASE */ + RELEASE (self); + + [self setSelectedRange: NSMakeRange (0, 0)]; + } + else if (version == 1) + { + NSTextContainer *aTextContainer; + BOOL flag; + + self = [super initWithCoder: aDecoder]; + + [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag]; + _tvf.smart_insert_delete = flag; + [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &flag]; + _tvf.allows_undo = flag; + + /* build up the rest of the text system, which doesn't get stored + . */ + aTextContainer = [self buildUpTextNetwork: _frame.size]; + [aTextContainer setTextView: (NSTextView*)self]; + /* See initWithFrame: for comments on this RELEASE */ + RELEASE (self); + + [self setSelectedRange: NSMakeRange (0, 0)]; + } return self; } @@ -363,11 +412,50 @@ static NSNotificationCenter *nc; this case. So we set the attributes manually. */ NSAttributedString *as; - as = AUTORELEASE ([[NSAttributedString alloc] - initWithString: aString - attributes: _typingAttributes]); - [self replaceRange: aRange withAttributedString: as]; + if ([aString length] == 0) + { + as = AUTORELEASE ([[NSAttributedString alloc] + initWithString: @" " + attributes: _typingAttributes]); + [self replaceRange: aRange withAttributedString: as]; + as = AUTORELEASE ([[NSAttributedString alloc] + initWithString: aString + attributes: _typingAttributes]); + [self replaceRange: NSMakeRange(0, 1) withAttributedString: as]; + } + else + { + as = AUTORELEASE ([[NSAttributedString alloc] + initWithString: aString + attributes: _typingAttributes]); + [self replaceRange: aRange withAttributedString: as]; + } } + // BEGIN: following code added by pyr + else if ([_textStorage length] == 0) + { + NSAttributedString *as; + if ([aString length] == 0) + { + as = AUTORELEASE ([[NSAttributedString alloc] + initWithString: @" " + attributes: _typingAttributes]); + [_textStorage replaceCharactersInRange: aRange withAttributedString: as]; + as = AUTORELEASE ([[NSAttributedString alloc] + initWithString: aString + attributes: _typingAttributes]); + [_textStorage replaceCharactersInRange: NSMakeRange(0, 1) withAttributedString: as]; + } + else + { + as = AUTORELEASE ([[NSAttributedString alloc] + initWithString: aString + attributes: _typingAttributes]); + [_textStorage replaceCharactersInRange: aRange + withAttributedString: as]; + } + } + // END else { [_textStorage replaceCharactersInRange: aRange withString: aString]; @@ -837,18 +925,24 @@ static NSNotificationCenter *nc; - (void) sizeToFit { + NSSize size; if (_tf.is_horizontally_resizable || _tf.is_vertically_resizable) - { - NSSize size; - - size = [_layoutManager usedRectForTextContainer: _textContainer].size; - size.width += 2 * _textContainerInset.width; - size.height += 2 * _textContainerInset.height; + size = [_layoutManager usedRectForTextContainer: _textContainer].size; + + if (!_tf.is_horizontally_resizable) + size.width = _bounds.size.width; + else + size.width += 2 * _textContainerInset.width; - [self setConstrainedFrameSize: size]; - } + if (!_tf.is_vertically_resizable) + size.height = _bounds.size.height; + else + size.height += 2 * _textContainerInset.height; + + [self setConstrainedFrameSize: size]; } + /* * [NSText] Spelling */ @@ -897,11 +991,42 @@ static NSNotificationCenter *nc; */ - (void) scrollRangeToVisible: (NSRange)aRange { - // Don't try scrolling an ancestor clipview if we are field editor. - // This makes things so much simpler and stabler for now. - if (_tf.is_field_editor == NO) + if (aRange.length > 0) { - [self scrollRectToVisible: [self rectForCharacterRange: aRange]]; + aRange.length = 1; + [self scrollRectToVisible: + [self rectForCharacterRange: aRange]]; + } + else + { + /* Update insertion point rect */ + NSRange charRange; + NSRange glyphRange; + unsigned glyphIndex; + NSRect rect; + + charRange = NSMakeRange (aRange.location, 0); + glyphRange = [_layoutManager glyphRangeForCharacterRange: charRange + actualCharacterRange: NULL]; + glyphIndex = glyphRange.location; + + rect = [_layoutManager lineFragmentUsedRectForGlyphAtIndex: glyphIndex + effectiveRange: NULL]; + + rect.origin.x += _textContainerOrigin.x; + rect.origin.y += _textContainerOrigin.y; + if ([self selectionAffinity] != NSSelectionAffinityUpstream) + { + /* Standard case - draw the insertion point just before the + associated glyph index */ + NSPoint loc = [_layoutManager locationForGlyphAtIndex: glyphIndex]; + + rect.origin.x += loc.x; + } + + rect.size.width = 1; + [self scrollRectToVisible: rect]; + } } @@ -1015,6 +1140,8 @@ static NSNotificationCenter *nc; { _textContainerInset = inset; [self invalidateTextContainerOrigin]; + [nc postNotificationName: NSViewFrameDidChangeNotification + object: self]; } - (NSSize) textContainerInset @@ -1042,19 +1169,60 @@ static NSNotificationCenter *nc; information is used when we ask the layout manager to draw - the base point is precisely this `text container origin', which is the origin of the used rect in our own coordinate system. */ + + /* It seems like the `text container origin' is the origin + of the text container rect so that the used rect + is "well positionned" within the textview */ + + + if (usedRect.size.width - _bounds.size.width < 0.0001) + { + NSRect rect; + float diff; + float ratio; + rect.origin = NSMakePoint(0, 0); + rect.size = textContainerSize; + diff = rect.size.width - usedRect.size.width; + + _textContainerOrigin.x = NSMinX(_bounds); + _textContainerOrigin.x += _textContainerInset.width; + + if (diff) + { + ratio = (_bounds.size.width - usedRect.size.width) + / diff; + + if (NSMaxX(rect) == NSMaxX(usedRect)) + { + _textContainerOrigin.x -= rect.size.width - _bounds.size.width; + } + else + { + _textContainerOrigin.x -= + (int) (usedRect.origin.x * (1 - ratio )); + } + } + else + { + } + } + else + { + /* First get the pure text container origin */ + _textContainerOrigin.x = NSMinX (_bounds); + _textContainerOrigin.x += _textContainerInset.width; + /* Then move to the used rect origin */ + _textContainerOrigin.x -= usedRect.origin.x; + } + /* First get the pure text container origin */ - _textContainerOrigin.x = NSMinX (_bounds); - _textContainerOrigin.x += _textContainerInset.width; + _textContainerOrigin.y = NSMinY (_bounds); + _textContainerOrigin.y += _textContainerInset.height; +// _textContainerOrigin.y -= textContainerSize.height; /* Then move to the used rect origin */ - _textContainerOrigin.x += usedRect.origin.x; - - /* First get the pure text container origin */ - _textContainerOrigin.y = NSMaxY (_bounds); - _textContainerOrigin.y -= _textContainerInset.height; - _textContainerOrigin.y -= textContainerSize.height; - /* Then move to the used rect origin */ - _textContainerOrigin.y += usedRect.origin.y; + _textContainerOrigin.y -= usedRect.origin.y; + } - (NSLayoutManager*) layoutManager @@ -1427,8 +1595,13 @@ static NSNotificationCenter *nc; if (flag == NO) { - /* Make the selected range visible */ - [self scrollRangeToVisible: range]; + // FIXME + // Make the selected range visible + // We do not always want to scroll to the beginning of the + // selection + // however we do for sure if the selection's length is 0 + if (range.length == 0 && _tf.is_editable ) + [self scrollRangeToVisible: range]; } /* Try to optimize for overlapping ranges */ @@ -1518,6 +1691,7 @@ static NSNotificationCenter *nc; NSRange glyphRange; unsigned glyphIndex; NSRect rect; + NSRect oldInsertionPointRect; /* Simple case - no insertion point */ if ((_selected_range.length > 0) || _selected_range.location == NSNotFound) @@ -1526,7 +1700,6 @@ static NSNotificationCenter *nc; { [_insertionPointTimer invalidate]; DESTROY (_insertionPointTimer); - _drawInsertionPointNow = YES; } /* FIXME: horizontal position of insertion point */ @@ -1539,9 +1712,11 @@ static NSNotificationCenter *nc; actualCharacterRange: NULL]; glyphIndex = glyphRange.location; - rect = [_layoutManager lineFragmentRectForGlyphAtIndex: glyphIndex + rect = [_layoutManager lineFragmentUsedRectForGlyphAtIndex: glyphIndex effectiveRange: NULL]; - + rect.origin.x += _textContainerOrigin.x; + rect.origin.y += _textContainerOrigin.y; + if ([self selectionAffinity] != NSSelectionAffinityUpstream) { /* Standard case - draw the insertion point just before the @@ -1549,6 +1724,7 @@ static NSNotificationCenter *nc; NSPoint loc = [_layoutManager locationForGlyphAtIndex: glyphIndex]; rect.origin.x += loc.x; + } else /* _affinity == NSSelectionAffinityUpstream - non standard */ { @@ -1580,8 +1756,10 @@ static NSNotificationCenter *nc; } } + rect.size.width = 1; - + + oldInsertionPointRect = _insertionPointRect; _insertionPointRect = rect; /* Remember horizontal position of insertion point */ @@ -1592,6 +1770,22 @@ static NSNotificationCenter *nc; /* Start blinking timer if not yet started */ if (_insertionPointTimer == nil && [self shouldDrawInsertionPoint]) { + _drawInsertionPointNow = NO; + [self _blink: nil]; + _insertionPointTimer = [NSTimer scheduledTimerWithTimeInterval: 0.5 + target: self + selector: @selector(_blink:) + userInfo: nil + repeats: YES]; + RETAIN (_insertionPointTimer); + } + else if (_insertionPointTimer != nil) + { + [_insertionPointTimer invalidate]; + DESTROY (_insertionPointTimer); + [self setNeedsDisplayInRect: oldInsertionPointRect]; + _drawInsertionPointNow = NO; + [self _blink: nil]; _insertionPointTimer = [NSTimer scheduledTimerWithTimeInterval: 0.5 target: self selector: @selector(_blink:) @@ -1610,8 +1804,10 @@ static NSNotificationCenter *nc; { if (_insertionPointTimer != nil) { + [self setNeedsDisplayInRect: oldInsertionPointRect]; [_insertionPointTimer invalidate]; DESTROY (_insertionPointTimer); + } } } @@ -2067,6 +2263,7 @@ replacing the selection. - (void) didChangeText { + [self sizeToFit]; [nc postNotificationName: NSTextDidChangeNotification object: _notifObject]; } @@ -2467,6 +2664,7 @@ afterString in order over charRange. */ - (void) moveUp: (id)sender { float originalInsertionPoint; + float savedOriginalInsertionPoint; float startingY; unsigned newLocation; @@ -2481,10 +2679,16 @@ afterString in order over charRange. */ /* Read from memory the horizontal position we aim to move the cursor at on the next line */ + savedOriginalInsertionPoint = _originalInsertPoint; originalInsertionPoint = _originalInsertPoint; /* Ask the layout manager to compute where to go */ startingY = NSMidY (_insertionPointRect); + + /* consider textContainerInset */ + startingY -= _textContainerInset.height; + originalInsertionPoint -= _textContainerInset.width; + newLocation = [_layoutManager _charIndexForInsertionPointMovingFromY: startingY bestX: originalInsertionPoint @@ -2497,12 +2701,13 @@ afterString in order over charRange. */ /* Restore the _originalInsertPoint (which was changed by setSelectedRange:) because we don't want it to change between moveUp:/moveDown: operations. */ - _originalInsertPoint = originalInsertionPoint; + _originalInsertPoint = savedOriginalInsertionPoint; } - (void) moveDown: (id)sender { float originalInsertionPoint; + float savedOriginalInsertionPoint; float startingY; unsigned newLocation; @@ -2517,10 +2722,16 @@ afterString in order over charRange. */ /* Read from memory the horizontal position we aim to move the cursor at on the next line */ + savedOriginalInsertionPoint = _originalInsertPoint; originalInsertionPoint = _originalInsertPoint; /* Ask the layout manager to compute where to go */ startingY = NSMidY (_insertionPointRect); + + /* consider textContainerInset */ + startingY -= _textContainerInset.height; + originalInsertionPoint -= _textContainerInset.width; + newLocation = [_layoutManager _charIndexForInsertionPointMovingFromY: startingY bestX: originalInsertionPoint @@ -2533,7 +2744,7 @@ afterString in order over charRange. */ /* Restore the _originalInsertPoint (which was changed by setSelectedRange:) because we don't want it to change between moveUp:/moveDown: operations. */ - _originalInsertPoint = originalInsertionPoint; + _originalInsertPoint = savedOriginalInsertionPoint; } - (void) moveLeft: (id)sender @@ -3027,8 +3238,12 @@ afterString in order over charRange. */ - (void) drawRect: (NSRect)rect { /* TODO: Only do relayout if needed */ - NSRange drawnRange = [_layoutManager glyphRangeForBoundingRect: rect - inTextContainer: _textContainer]; + NSRange drawnRange; + NSRect containerRect = rect; + containerRect.origin.x -= _textContainerOrigin.x; + containerRect.origin.y -= _textContainerOrigin.y; + drawnRange = [_layoutManager glyphRangeForBoundingRect: containerRect + inTextContainer: _textContainer]; if (_tf.draws_background) { /* First paint the background with the color. This is necessary @@ -3043,6 +3258,7 @@ afterString in order over charRange. */ /* Then draw the special background of the new glyphs. */ [_layoutManager drawBackgroundForGlyphRange: drawnRange atPoint: _textContainerOrigin]; + } [_layoutManager drawGlyphsForGlyphRange: drawnRange @@ -3934,6 +4150,9 @@ other than copy/paste or dragging. */ unsigned index; float fraction; + point.x -= _textContainerOrigin.x; + point.y -= _textContainerOrigin.y; + index = [_layoutManager glyphIndexForPoint: point inTextContainer: _textContainer fractionOfDistanceThroughGlyph: &fraction]; @@ -4122,12 +4341,15 @@ other than copy/paste or dragging. */ - (NSRect) rectForCharacterRange: (NSRange)aRange { NSRange glyphRange; + NSRect rect; glyphRange = [_layoutManager glyphRangeForCharacterRange: aRange actualCharacterRange: NULL]; - - return [_layoutManager boundingRectForGlyphRange: glyphRange + rect = [_layoutManager boundingRectForGlyphRange: glyphRange inTextContainer: _textContainer]; + rect.origin.x += _textContainerOrigin.x; + rect.origin.y += _textContainerOrigin.y; + return rect; } /** Extension method that copies the current selected text to the