From 9e289574ffb1cf28837fbdcefaac6eec22e1c647 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Thu, 19 Dec 2019 09:08:33 +0100 Subject: [PATCH] Fixed: attachment support was missing in CPTextView (#2872) --- AppKit/CPPasteboard.j | 1 + AppKit/CPText.j | 6 +- AppKit/CPTextView/CPLayoutManager.j | 75 +++++++++++----- AppKit/CPTextView/CPTextStorage.j | 16 +++- AppKit/CPTextView/CPTextView.j | 111 +++++++++++++----------- AppKit/CPTextView/CPTypesetter.j | 60 +++++++++---- Tests/AppKit/CPTextViewTest.j | 6 -- Tests/Manual/CPTextView/AppController.j | 50 +++++------ 8 files changed, 202 insertions(+), 123 deletions(-) diff --git a/AppKit/CPPasteboard.j b/AppKit/CPPasteboard.j index 95daca4a1..384936ba7 100644 --- a/AppKit/CPPasteboard.j +++ b/AppKit/CPPasteboard.j @@ -46,6 +46,7 @@ CPImagesPboardType = @"CPImagesPboardType"; CPVideosPboardType = @"CPVideosPboardType"; CPRTFPboardType = @"CPRTFPboardType"; _CPSmartPboardType = @"_CPSmartPboardType"; +_CPASPboardType = @"_CPASPboardType"; UTF8PboardType = @"public.utf8-plain-text"; diff --git a/AppKit/CPText.j b/AppKit/CPText.j index 09e23ef2f..9f481891b 100644 --- a/AppKit/CPText.j +++ b/AppKit/CPText.j @@ -179,7 +179,11 @@ CPKernAttributeName = @"CPKernAttributeName"; { var pasteboard = [CPPasteboard generalPasteboard], dataForPasting = [pasteboard stringForType:CPRTFPboardType], - stringForPasting = [pasteboard stringForType:CPStringPboardType]; + stringForPasting = [pasteboard stringForType:CPStringPboardType], + attributedStringData = [pasteboard stringForType:_CPASPboardType]; + + if ([self isRichText] && attributedStringData) + return [CPKeyedUnarchiver unarchiveObjectWithData:[CPData dataWithRawString:attributedStringData]]; if (dataForPasting || [stringForPasting hasPrefix:"{\\rtf1\\ansi"]) stringForPasting = [[_CPRTFParser new] parseRTF:dataForPasting ? dataForPasting : stringForPasting]; diff --git a/AppKit/CPTextView/CPLayoutManager.j b/AppKit/CPTextView/CPLayoutManager.j index 4343aaa40..4f5be53df 100644 --- a/AppKit/CPTextView/CPLayoutManager.j +++ b/AppKit/CPTextView/CPLayoutManager.j @@ -259,6 +259,9 @@ _oncontextmenuhandler = function () { return false; }; { if (_lineFragments[i]._isInvalid) { + while (i > 0 && !_lineFragments[i - 1]._isLast) + i--; + startIndex = _lineFragments[i]._range.location; removeRange.location = i; removeRange.length = l - i; @@ -361,7 +364,7 @@ _oncontextmenuhandler = function () { return false; }; if (ABS(rangeOffset) !== ABS(newLength - oldLength)) return NO; - var verticalOffset = _lineFragments[targetLine]._fragmentRect.origin.y - _lineFragmentsForRescue[startLineForDOMRemoval]._fragmentRect.origin.y, + var verticalOffset = CGRectGetMaxY(_lineFragments[targetLine]._fragmentRect) - CGRectGetMaxY(_lineFragmentsForRescue[startLineForDOMRemoval]._fragmentRect), l = _lineFragmentsForRescue.length, newTargetLine = startLineForDOMRemoval + removalSkip; @@ -661,12 +664,10 @@ _oncontextmenuhandler = function () { return false; }; { var j = i; - while (--j > 0 && !_lineFragments[j]._isLast) - { - // body intentionally left empty - } + while (j > 0 && !_lineFragments[j - 1]._isLast) + j--; - return _lineFragments[j + 1]; + return _lineFragments[j]; } } @@ -676,13 +677,16 @@ _oncontextmenuhandler = function () { return false; }; { var l = _lineFragments.length; + if (location >= CPMaxRange(_lineFragments[l - 1]._range)) + return _lineFragments[l - 1]; + for (var i = 0; i < l; i++) { if (CPLocationInRange(location, _lineFragments[i]._range)) { var j = i; - while (!_lineFragments[j]._isLast) + while (j < l && !_lineFragments[j]._isLast) j++; return _lineFragments[j]; @@ -925,15 +929,14 @@ _oncontextmenuhandler = function () { return false; }; var correctedRect = CGRectCreateCopy(frames[j]); correctedRect.size.height -= frames[j]._descent; correctedRect.origin.y -= frames[j]._descent; + if (!rect) rect = CGRectCreateCopy(correctedRect); else rect = CGRectUnion(rect, correctedRect); if (_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, CPMaxRange(selectedCharRange) - 1)])) - { rect.size.width = containerSize.width - rect.origin.x; - } } } @@ -1095,7 +1098,6 @@ var _objectsInRange = function(aList, aRange) span = document.createElement("span"); span.oncontextmenu = span.onmousedown = span.onselectstart = _oncontextmenuhandler; - // span.contentEditable = true; // this unfortunately does not work to make native pasting work on safari style = span.style; style.position = "absolute"; @@ -1138,7 +1140,6 @@ var _objectsInRange = function(aList, aRange) else if (CPFeatureIsCompatible(CPJavaScriptTextContentFeature)) span.textContent = aString; - // FIXME aString.replace(/&/g,'&') return span; #else return nil; @@ -1167,16 +1168,29 @@ var _objectsInRange = function(aList, aRange) effectiveRange = attributes ? CPIntersectionRange(aRange, effectiveRange) : aRange; var string = [textStorage._string substringWithRange:effectiveRange], - font = [textStorage font] || [CPFont systemFontOfSize:12.0], - underline = [attributes objectForKey:CPUnderlineStyleAttributeName] || CPUnderlineStyleNone ; + underline = [attributes objectForKey:CPUnderlineStyleAttributeName] || CPUnderlineStyleNone; - if ([attributes containsKey:CPFontAttributeName]) - font = [attributes objectForKey:CPFontAttributeName]; + // this is an attachment -> create a run for it + if (string === _CPAttachmentCharacterAsString) + { + if (![attributes objectForKey:_CPAttachmentInvisible]) + { + var view = [attributes objectForKey:_CPAttachmentView], + viewCopy = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:view]], + elem = viewCopy._DOMElement, + run = {_range:CPMakeRangeCopy(effectiveRange), color:nil, font:nil, elem:elem, string:nil, view:viewCopy}; - var color = [attributes objectForKey:CPForegroundColorAttributeName], - run = {_range:CPMakeRangeCopy(effectiveRange), color:color, font:font, elem:nil, string:string, underline:underline}; + _runs.push(run); + } + } + else + { + var color = [attributes objectForKey:CPForegroundColorAttributeName], + font = [attributes objectForKey:CPFontAttributeName] || [textStorage font] || [CPFont systemFontOfSize:12.0], + run = {_range:CPMakeRangeCopy(effectiveRange), color:color, font:font, elem:nil, string:string}; - _runs.push(run); + _runs.push(run); + } if (!CPMaxRange(effectiveRange)) break; @@ -1248,7 +1262,12 @@ var _objectsInRange = function(aList, aRange) for (var i = 0; i < l; i++) { if (_runs[i].elem && _runs[i].DOMactive) - _textContainer._textView._DOMElement.removeChild(_runs[i].elem); + { + if (_runs[i].view) + [_runs[i].view removeFromSuperview]; + else + _textContainer._textView._DOMElement.removeChild(_runs[i].elem); + } _runs[i].elem = nil; _runs[i].DOMactive = NO; @@ -1266,9 +1285,7 @@ var _objectsInRange = function(aList, aRange) var run = runs[i]; if (!run.elem && CPRectIntersectsRect([_textContainer._textView exposedRect], _fragmentRect)) - { - run.elem=[self createDOMElementWithText:run.string andFont:run.font andColor:run.color andUnderline:run.underline]; - } + run.elem = [self createDOMElementWithText:run.string andFont:run.font andColor:run.color andUnderline:run.underline]; if (run.DOMactive && !run.DOMpatched) continue; @@ -1285,14 +1302,21 @@ var _objectsInRange = function(aList, aRange) run.elem.style.left = (orig.x) + "px"; run.elem.style.top = (orig.y) + "px"; + if (run.view) + [run.view setFrameOrigin:orig]; + if (!run.DOMactive) - _textContainer._textView._DOMElement.appendChild(run.elem); + { + if (run.view) + [self._textContainer._textView addSubview:run.view]; + else + _textContainer._textView._DOMElement.appendChild(run.elem); + } run.DOMactive = YES; } run.DOMpatched = NO; - } } @@ -1344,6 +1368,9 @@ var _objectsInRange = function(aList, aRange) if (verticalOffset && _runs[i].elem) { + if (_runs[i].view) + _runs[i].view._frame.origin.y += verticalOffset; + _runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px'; _runs[i].DOMpatched = YES; } diff --git a/AppKit/CPTextView/CPTextStorage.j b/AppKit/CPTextView/CPTextStorage.j index ef0fc8d45..c59e5744e 100644 --- a/AppKit/CPTextView/CPTextStorage.j +++ b/AppKit/CPTextView/CPTextStorage.j @@ -42,6 +42,11 @@ CPTextStorageDidProcessEditingNotification = @"CPTextStorageDidProcessEditingNot @end +CPAttachmentCharacter = 65532; // "\ufffc"; +_CPAttachmentCharacterAsString = String.fromCharCode(CPAttachmentCharacter); + +_CPAttachmentView = "_CPAttachmentView"; +_CPAttachmentInvisible = "_CPAttachmentInvisible"; var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1, CPTextStorageDelegate_textStorageDidProcessEditing_ = 1 << 2; @@ -257,6 +262,15 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1, return [super attributedSubstringFromRange:aRange]; } ++ (id)attributedStringWithAttachment:(CPView)someView +{ + var result = [[self alloc] initWithString:_CPAttachmentCharacterAsString]; + + [result setAttributes:@{_CPAttachmentView:someView} range:CPMakeRange(0, 1)]; + + return result; +} + @end @@ -300,4 +314,4 @@ var CPTextStorageDelegate_textStorageWillProcessEditing_ = 1 << 1, [super encodeWithCoder:aCoder]; } -@end \ No newline at end of file +@end diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index 5e7ebab28..dde39feff 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -454,10 +454,11 @@ var kDelegateRespondsTo_textShouldBeginEditing stringForPasting = [[self textStorage] attributedSubstringFromRange:CPMakeRangeCopy(selectedRange)], richData = [_CPRTFProducer produceRTF:stringForPasting documentAttributes:@{}]; - [pasteboard declareTypes:[CPStringPboardType, CPRTFPboardType] owner:nil]; - [pasteboard setString:stringForPasting._string forType:CPStringPboardType]; + [pasteboard declareTypes:[CPStringPboardType, CPRTFPboardType, _CPSmartPboardType, _CPASPboardType] owner:nil]; + [pasteboard setString:[stringForPasting._string stringByReplacingOccurrencesOfString:_CPAttachmentCharacterAsString withString:''] forType:CPStringPboardType]; [pasteboard setString:richData forType:CPRTFPboardType]; [pasteboard setString:_previousSelectionGranularity + '' forType:_CPSmartPboardType]; + [pasteboard setString:[[CPKeyedArchiver archivedDataWithRootObject:stringForPasting] rawString] forType:_CPASPboardType]; } - (void)_pasteString:(id)stringForPasting @@ -1093,6 +1094,17 @@ Sets the selection to a range of characters in response to user action. return YES; } +- (void)_hideRange:(CPRange)rangeToHide inDragPlaceholderString:(CPTextView)placeholderString +{ + if (!rangeToHide.length) + return; + + [placeholderString addAttribute:CPForegroundColorAttributeName + value:[CPColor colorWithRed:1 green:1 blue:1 alpha:0] // invisibleInk + range:rangeToHide]; + [placeholderString addAttribute:_CPAttachmentInvisible value:YES range:rangeToHide]; +} + #pragma mark - #pragma mark Mouse Events @@ -1112,31 +1124,35 @@ Sets the selection to a range of characters in response to user action. // dragging the selection if ([self selectionGranularity] == CPSelectByCharacter && CPLocationInRange(_startTrackingLocation, _selectionRange)) { - var lineBeginningIndex = [_layoutManager _firstLineFragmentForLineFromLocation:_selectionRange.location]._range.location, - placeholderRange = _MakeRangeFromAbs(lineBeginningIndex, CPMaxRange(_selectionRange)), + var visibleRange = [_layoutManager glyphRangeForBoundingRect:_exposedRect inTextContainer:_textContainer], + firstFragment = [_layoutManager _firstLineFragmentForLineFromLocation:_selectionRange.location], + lastFragment = [_layoutManager _lastLineFragmentForLineFromLocation:CPMaxRange(_selectionRange)], + lineBeginningIndex = firstFragment._range.location, + lineEndIndex = CPMaxRange(lastFragment._range), + placeholderRange = CPIntersectionRange(_MakeRangeFromAbs(lineBeginningIndex, lineEndIndex), visibleRange), placeholderString = [_textStorage attributedSubstringFromRange:placeholderRange], placeholderFrame = CGRectIntersection([_layoutManager boundingRectForGlyphRange:placeholderRange inTextContainer:_textContainer], _frame), - rangeToHide = CPMakeRange(0, _selectionRange.location - lineBeginningIndex), + rangeToHideLHS = CPMakeRange(0, _selectionRange.location - lineBeginningIndex), + rangeToHideRHS = _MakeRangeFromAbs(CPMaxRange(_selectionRange) - lineBeginningIndex, lineEndIndex - lineBeginningIndex), dragPlaceholder; - // hide the left part of the first line of the selection that is not included - [placeholderString addAttribute:CPForegroundColorAttributeName - value:[CPColor colorWithRed:1 green:1 blue:1 alpha:0] - range:rangeToHide]; + // hide the left/right parts of the first/last lines of the selection that are not included + [self _hideRange:rangeToHideLHS inDragPlaceholderString:placeholderString]; + [self _hideRange:rangeToHideRHS inDragPlaceholderString:placeholderString]; _movingSelection = CPMakeRange(_startTrackingLocation, 0); - dragPlaceholder = [[CPTextView alloc] initWithFrame:placeholderFrame]; + dragPlaceholder = [[CPTextView alloc] initWithFrame:_frame]; [dragPlaceholder._textStorage replaceCharactersInRange:CPMakeRange(0, 0) withAttributedString:placeholderString]; [dragPlaceholder setBackgroundColor:[CPColor colorWithRed:1 green:1 blue:1 alpha:0]]; [dragPlaceholder setAlphaValue:0.5]; var stringForPasting = [_textStorage attributedSubstringFromRange:CPMakeRangeCopy(_selectionRange)], - richData = [_CPRTFProducer produceRTF:stringForPasting documentAttributes:@{}], draggingPasteboard = [CPPasteboard pasteboardWithName:CPDragPboard]; - [draggingPasteboard declareTypes:[CPRTFPboardType, CPStringPboardType] owner:nil]; - [draggingPasteboard setString:richData forType:CPRTFPboardType]; + [draggingPasteboard declareTypes:[_CPASPboardType, CPStringPboardType] owner:nil]; + [draggingPasteboard setString:[[CPKeyedArchiver archivedDataWithRootObject:stringForPasting] rawString] forType:_CPASPboardType]; + // this is necessary because the drag will not work without data of kind CPStringPboardType [draggingPasteboard setString:stringForPasting._string forType:CPStringPboardType]; [self dragView:dragPlaceholder @@ -2155,38 +2171,23 @@ Sets the selection to a range of characters in response to user action. return (_selectionRange.length === 0 && [self _isFocused] && !_placeholderString); } -- (void)updateInsertionPointStateAndRestartTimer:(BOOL)flag +- (CPRect)_getCaretRect { - var caretRect, - numberOfGlyphs = [_layoutManager numberOfCharacters]; + var numberOfGlyphs = [_layoutManager numberOfCharacters]; - if (_selectionRange.length) - [_caret setVisibility:NO]; - - if (_selectionRange.location >= numberOfGlyphs) // cursor is "behind" the last chacacter + if (!numberOfGlyphs) { - caretRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(MAX(0,_selectionRange.location - 1), 1) inTextContainer:_textContainer]; - - if (!numberOfGlyphs) - { - var font = [_typingAttributes objectForKey:CPFontAttributeName] || [self font]; - - caretRect.size.height = [font size]; - caretRect.origin.y = ([font ascender] - [font descender]) * 0.5 + _textContainerOrigin.y; - } - - caretRect.origin.x += caretRect.size.width; - - if (_selectionRange.location > 0 && [[_textStorage string] characterAtIndex:_selectionRange.location - 1] === '\n') - { - caretRect.origin.y += caretRect.size.height; - caretRect.origin.x = 0; - } + var font = [_typingAttributes objectForKey:CPFontAttributeName] || [self font]; + return CGRectMake(1, ([font ascender] - [font descender]) * 0.5 + _textContainerOrigin.y, 1, [font size]); } - else - caretRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(_selectionRange.location, 1) inTextContainer:_textContainer]; - var loc = (_selectionRange.location === numberOfGlyphs && numberOfGlyphs > 0) ? _selectionRange.location - 1 : _selectionRange.location, + // cursor is at a newline character -> jump to next line + if (_selectionRange.location == numberOfGlyphs && _isNewlineCharacter([[_textStorage string] characterAtIndex:_selectionRange.location - 1])) + return CGRectCreateCopy([_layoutManager extraLineFragmentRect]); + + var caretRect = [_layoutManager boundingRectForGlyphRange:CPMakeRange(_selectionRange.location, 1) inTextContainer:_textContainer]; + + var loc = (_selectionRange.location == numberOfGlyphs) ? _selectionRange.location - 1 : _selectionRange.location, caretOffset = [_layoutManager _characterOffsetAtLocation:loc], oldYPosition = CGRectGetMaxY(caretRect), caretDescend = [_layoutManager _descentAtLocation:loc]; @@ -2196,14 +2197,24 @@ Sets the selection to a range of characters in response to user action. caretRect.origin.y += caretOffset; caretRect.size.height = oldYPosition - caretRect.origin.y; } + if (caretDescend < 0) caretRect.size.height -= caretDescend; + if (_selectionRange.location == numberOfGlyphs) + caretRect.origin.x += caretRect.size.width; + caretRect.origin.x += _textContainerOrigin.x; caretRect.origin.y += _textContainerOrigin.y; - caretRect.size.width = 1; - [_caret setRect:caretRect]; + return caretRect; +} +- (void)updateInsertionPointStateAndRestartTimer:(BOOL)flag +{ + if (_selectionRange.length) + [_caret setVisibility:NO]; + + [_caret setRect:[self _getCaretRect]]; if (flag) [_caret startBlinking]; @@ -2215,6 +2226,10 @@ Sets the selection to a range of characters in response to user action. location = [self _characterIndexFromRawPoint:CGPointCreateCopy(point)]; _movingSelection = CPMakeRange(location, 0); + + if (CPLocationInRange(location, _selectionRange)) + return; + [_caret _drawCaretAtLocation:_movingSelection.location]; [_caret setVisibility:YES]; } @@ -2227,7 +2242,7 @@ Sets the selection to a range of characters in response to user action. var location = [self convertPoint:[aSender draggingLocation] fromView:nil], pasteboard = [aSender draggingPasteboard]; - if ([pasteboard availableTypeFromArray:[CPRTFPboardType, CPStringPboardType]]) + if ([pasteboard availableTypeFromArray:[_CPASPboardType]]) { [_caret setVisibility:NO]; @@ -2244,15 +2259,10 @@ Sets the selection to a range of characters in response to user action. [self _deleteForRange:_selectionRange]; [self setSelectedRange:_movingSelection]; - var dataForPasting = [pasteboard stringForType:CPRTFPboardType] || [pasteboard stringForType:CPStringPboardType]; - + var stringForPasting = [CPKeyedUnarchiver unarchiveObjectWithData:[CPData dataWithRawString:[pasteboard stringForType:_CPASPboardType]]]; // setTimeout is to a work around a transaction issue with the undomanager setTimeout(function(){ - - if ([dataForPasting hasPrefix:"{\\rtf"]) - [self insertText:[[_CPRTFParser new] parseRTF:dataForPasting]]; - else - [self insertText:dataForPasting]; + [self insertText:stringForPasting]; }, 0); } @@ -2525,6 +2535,7 @@ var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey", style.whiteSpace = "pre"; style.backgroundColor = "black"; _caretDOM.style.width = "1px"; + _caretDOM.style.zIndex = 10001; _textView = aView; _textView._DOMElement.appendChild(_caretDOM); } diff --git a/AppKit/CPTextView/CPTypesetter.j b/AppKit/CPTextView/CPTypesetter.j index a52d15e97..183d91405 100644 --- a/AppKit/CPTextView/CPTypesetter.j +++ b/AppKit/CPTextView/CPTypesetter.j @@ -199,13 +199,11 @@ var CPSystemTypesetterFactory, [_layoutManager setLocation:CGPointMake(myX, _lineBase) forStartOfGlyphRange:lineRange]; [_layoutManager _setAdvancements:advancements forGlyphRange:lineRange]; - if (!sameLine) //fix the _lineFragments when fontsizes differ - { - var l = _lineFragments.length; + //fix the _lineFragments when fontsizes differ + var l = _lineFragments.length; - for (var i = 0 ; i < l ; i++) - [_lineFragments[i] _adjustForHeight:_lineHeight]; - } + for (var i = 0 ; i < l ; i++) + [_lineFragments[i] _adjustForHeight:_lineHeight]; if (!lineCount) // do not rescue on first line return NO; @@ -245,6 +243,7 @@ var CPSystemTypesetterFactory, wrapWidth = 0, isNewline = NO, isTabStop = NO, + isAttachment = NO, isWordWrapped = NO, numberOfGlyphs= [_textStorage length], leading, @@ -322,6 +321,34 @@ var CPSystemTypesetterFactory, switch (currentCharCode) // faster than sending actionForControlCharacterAtIndex: called for each char. { + case CPAttachmentCharacter: + { + var attributes = [_textStorage attributesAtIndex:glyphIndex effectiveRange:nil], + view = [attributes objectForKey:_CPAttachmentView], + viewSize = view ? view._frame.size : CGSizeMake(0, 0); + + rangeWidth = prevRangeWidth + viewSize.width; // undo sizing of dummy character + + isAttachment = YES; + wrapRange = CPMakeRange(lineRange.location, lineRange.length - 1); // wrap before image + + // prevent crash when image is larger than text container + if (viewSize.width > containerSizeWidth) + wrapRange.length++; + + wrapWidth = rangeWidth; + wrapRange._height = _lineHeight; + wrapRange._base = _lineBase; + + if (viewSize.height > _lineBase) + _lineBase = viewSize.height; + + if (viewSize.height > _lineHeight) + _lineHeight = viewSize.height - descent + leading; + + ascent = viewSize.height; + break; + } case 9: // '\t' { var nextTab = [self textTabForWidth:rangeWidth + lineOrigin.x writingDirection:0]; @@ -346,7 +373,6 @@ var CPSystemTypesetterFactory, } advancements.push({width: rangeWidth - prevRangeWidth, height: ascent, descent: descent}); - prevRangeWidth = _lineWidth = rangeWidth; if (lineOrigin.x + rangeWidth > containerSizeWidth) @@ -364,16 +390,13 @@ var CPSystemTypesetterFactory, glyphIndex = CPMaxRange(lineRange) - 1; // start the line starts directly at current character } - if (isNewline || isTabStop) + if (isNewline || isTabStop || isAttachment) { if ([self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:!isNewline]) return; - if (isTabStop) - { + if (isTabStop || isAttachment) lineOrigin.x += rangeWidth; - isTabStop = NO; - } if (isNewline) { @@ -404,6 +427,9 @@ var CPSystemTypesetterFactory, _lineBase = ascent; } + isTabStop = NO; + isAttachment = NO; + isWordWrapped = NO; _lineWidth = 0; advancements = []; currentAnchor = 0; @@ -412,18 +438,20 @@ var CPSystemTypesetterFactory, measuringRange = CPMakeRange(glyphIndex + 1, 0); wrapRange = CPMakeRange(0, 0); wrapWidth = 0; - isWordWrapped = NO; } } // this is to "flush" the remaining characters if (lineRange.length) - { [self _flushRange:lineRange lineOrigin:lineOrigin currentContainer:_currentTextContainer advancements:advancements lineCount:numLines sameLine:NO]; - } - var rect = CGRectMake(0, lineOrigin.y, containerSizeWidth, [_layoutManager._lineFragments lastObject]._usedRect.size.height - descent); + var rect = CGRectMake(1, lineOrigin.y - descent, containerSizeWidth, currentFontLineHeight); [_layoutManager setExtraLineFragmentRect:rect usedRect:rect textContainer:_currentTextContainer]; + + var fragment = [_layoutManager._lineFragments lastObject]; + + if (fragment) + fragment._isLast = YES; } @end diff --git a/Tests/AppKit/CPTextViewTest.j b/Tests/AppKit/CPTextViewTest.j index 40777e9fa..aa5534442 100644 --- a/Tests/AppKit/CPTextViewTest.j +++ b/Tests/AppKit/CPTextViewTest.j @@ -28,12 +28,6 @@ [[theWindow contentView] addSubview:textView]; - var button = [[CPButton alloc] initWithFrame:CGRectMake(400,10,75,25)]; - [button setTitle:@"Underline"]; - [button setTarget:textView]; - [button setAction:@selector(underline:)]; - [[theWindow contentView] addSubview:button]; - delegateSpy = spy(self); } diff --git a/Tests/Manual/CPTextView/AppController.j b/Tests/Manual/CPTextView/AppController.j index 3258f11c3..141543a6c 100755 --- a/Tests/Manual/CPTextView/AppController.j +++ b/Tests/Manual/CPTextView/AppController.j @@ -101,36 +101,36 @@ item = [mainMenu insertItemWithTitle:@"Format" action:nil keyEquivalent:nil atIndex:0]; var formatMenu = [[CPMenu alloc] initWithTitle:@"Format Menu"]; [formatMenu addItemWithTitle:@"Font panel" action:@selector(orderFrontFontPanel:) keyEquivalent:@"f"]; + [formatMenu addItemWithTitle:@"Underline" action:@selector(underline:) keyEquivalent:@"u"]; [mainMenu setSubmenu:formatMenu forItem:item]; - // - // var centeredParagraph=[CPParagraphStyle new]; - // [centeredParagraph setAlignment: CPCenterTextAlignment]; - // [_textView insertText:[[CPAttributedString alloc] initWithString:@"Fusce\n" - // attributes:[CPDictionary dictionaryWithObjects:[centeredParagraph, [CPFont boldFontWithName:"Arial" size:18], [CPColor redColor]] - // forKeys:[CPParagraphStyleAttributeName, CPFontAttributeName, CPForegroundColorAttributeName]]]]; - // - // [_textView insertText: [[CPAttributedString alloc] initWithString:@"lectus neque cr as eget lectus neque cr as eget lectus cr as eget lectus" - // attributes:[CPDictionary dictionaryWithObjects:[ [CPFont fontWithName:"Arial" size:12]] forKeys: [CPFontAttributeName]]]]; - // - // [_textView insertText:[[CPAttributedString alloc] initWithString:@" proin, this is text in boldface " - // attributes:[CPDictionary dictionaryWithObjects:[ [CPFont boldFontWithName:"Arial" size:12]] forKeys: [CPFontAttributeName]]]]; - // [_textView insertText:[[CPAttributedString alloc] initWithString:@"111111 neque cr as eget lectus neque cr as eget lectus cr as eget lectus" - // attributes:[CPDictionary dictionaryWithObjects:[ [CPFont fontWithName:"Arial" size:12.0]] forKeys: [CPFontAttributeName]]]]; - // + [_textView insertText:"123"]; + var tempImageView = [[CPImageView alloc] initWithFrame:CGRectMake(0, 0, 32, 32)]; + [tempImageView setImage:[[CPImage alloc] initWithContentsOfFile:@"Resources/spinner.gif" size:CGSizeMake(32, 32)]] + + [_textView insertText:[CPTextStorage attributedStringWithAttachment:tempImageView]]; + [_textView insertText:" 456 "]; + + var tempButton = [[CPButton alloc] initWithFrame:CGRectMake(0, 0, 64, 28)] + [_textView insertText:[CPTextStorage attributedStringWithAttachment:tempButton]]; + +// var centeredParagraph=[CPParagraphStyle new]; +// [centeredParagraph setAlignment: CPCenterTextAlignment]; +// [_textView insertText:[[CPAttributedString alloc] initWithString:@"Fusce\n" +// attributes:[CPDictionary dictionaryWithObjects:[centeredParagraph, [CPFont boldFontWithName:"Arial" size:18], [CPColor redColor]] +// forKeys:[CPParagraphStyleAttributeName, CPFontAttributeName, CPForegroundColorAttributeName]]]]; + [theWindow orderFront:self]; [CPMenu setMenuBarVisible:YES]; } -// -// - (void) makeRTF:sender -// { -// [_textView2 setString: [_CPRTFProducer produceRTF:[_textView textStorage] documentAttributes: @{}] ]; -// var tc = [_CPRTFParser new]; -// var mystr=[tc parseRTF:[_textView2 stringValue]]; -// [_textView selectAll: self]; -// [_textView insertText: mystr]; -// -// } +- (void) makeRTF:sender +{ + [_textView2 setString: [_CPRTFProducer produceRTF:[_textView textStorage] documentAttributes: @{}] ]; + var tc = [_CPRTFParser new]; + var mystr=[tc parseRTF:[_textView2 stringValue]]; + [_textView selectAll: self]; + [_textView insertText: mystr]; +} @end