From 1cad83aba8b7812064a06ea1f55478c84313b115 Mon Sep 17 00:00:00 2001 From: daboe01 Date: Sun, 14 Jun 2026 21:06:51 +0200 Subject: [PATCH] initial display of the table --- AppKit/CPTextView/CPLayoutManager.j | 179 ++++++++++++------- AppKit/CPTextView/CPTextView.j | 24 ++- AppKit/CPTextView/_CPTableTextAttachment.j | 193 ++++++++++++++------- Tests/Manual/CPTextView/AppController.j | 34 +++- 4 files changed, 297 insertions(+), 133 deletions(-) diff --git a/AppKit/CPTextView/CPLayoutManager.j b/AppKit/CPTextView/CPLayoutManager.j index fd0accf43..22fb6ba19 100644 --- a/AppKit/CPTextView/CPLayoutManager.j +++ b/AppKit/CPTextView/CPLayoutManager.j @@ -292,7 +292,6 @@ _oncontextmenuhandler = function () { return false; }; // We erased all lines if (!startIndex) [self setExtraLineFragmentRect:CGRectMake(0, 0) usedRect:CGRectMake(0, 0) textContainer:nil]; - // document.title=startIndex; [_typesetter layoutGlyphsInLayoutManager:self startingAtGlyphIndex:startIndex maxNumberOfLineFragments:-1 nextGlyphIndex:nil]; @@ -532,20 +531,26 @@ _oncontextmenuhandler = function () { return false; }; var frames = [fragment glyphFrames], len = fragment._range.length; - for (var j = 0; j < len; j++) + if (frames) { - if (CGRectContainsPoint(frames[j], point)) - { - if (partialFraction) - partialFraction[0] = (point.x - frames[j].origin.x) / frames[j].size.width; + var maxLen = MIN(len, frames.length); - return fragment._range.location + j; + for (var j = 0; j < maxLen; j++) + { + var frame = frames[j]; + + if (frame && CGRectContainsPoint(frame, point)) + { + if (partialFraction) + partialFraction[0] = (point.x - frame.origin.x) / frame.size.width; + + return fragment._range.location + j; + } } } } } - // Not found, maybe a point left to the last character was clicked -> search again with broader constraints if ([[_textStorage string] length]) { for (var i = 0; i < c; i++) @@ -554,30 +559,33 @@ _oncontextmenuhandler = function () { return false; }; if (fragment._textContainer === container) { - // Within the horizontal territory of the current (not-empty) line? - if (fragment._range.length > 0 && point.y > fragment._fragmentRect.origin.y && - point.y <= fragment._fragmentRect.origin.y + fragment._fragmentRect.size.height) + if (fragment._range.length > 0 && point.y > fragment._fragmentRect.origin.y && + point.y <= fragment._fragmentRect.origin.y + fragment._fragmentRect.size.height) + { + if (i < c - 1 && _lineFragments[i + 1]._fragmentRect.origin.y === fragment._fragmentRect.origin.y) + continue; + + var nlLoc = CPMaxRange(fragment._range), + frames = [fragment glyphFrames]; + + if (frames && frames.length > 0) { - // Skip tabs and move on the last fragment in this line - if (i < c - 1 && _lineFragments[i + 1]._fragmentRect.origin.y === fragment._fragmentRect.origin.y) - continue; + var lastFrame = frames[frames.length - 1], + firstFrame = frames[0]; - var nlLoc = CPMaxRange(fragment._range), - lastFrame = [fragment glyphFrames][fragment._range.length - 1], - firstFrame = [fragment glyphFrames][0]; + if (lastFrame && firstFrame) + { + if (_isNewlineCharacter([[_textStorage string] characterAtIndex:nlLoc > 0 ? nlLoc - 1 : 0])) + nlLoc--; - // stay on the line the newline character belongs to - if (_isNewlineCharacter([[_textStorage string] characterAtIndex:nlLoc > 0 ? nlLoc - 1 : 0])) - nlLoc--; - - // Clicked right to the last character - if (point.x > CGRectGetMaxX(lastFrame)) - return nlLoc; - // Clicked left to the last character - else if (point.x <= CGRectGetMinX(firstFrame)) - return fragment._range.location; - else - return nlLoc; + if (point.x > CGRectGetMaxX(lastFrame)) + return nlLoc; + else if (point.x <= CGRectGetMinX(firstFrame)) + return fragment._range.location; + else + return nlLoc; + } + } } } } @@ -707,7 +715,11 @@ _oncontextmenuhandler = function () { return false; }; var index = location - lineFragment._range.location; - return lineFragment._glyphsOffsets[index]; + if (index < 0 || !lineFragment._glyphsOffsets || index >= lineFragment._glyphsOffsets.length) + return 0.0; + + var offset = lineFragment._glyphsOffsets[index]; + return (offset === undefined) ? 0.0 : offset; } - (double)_descentAtLocation:(unsigned)location @@ -719,7 +731,11 @@ _oncontextmenuhandler = function () { return false; }; var index = location - lineFragment._range.location; - return lineFragment._glyphsFrames[index]._descent; + if (index < 0 || !lineFragment._glyphsFrames || index >= lineFragment._glyphsFrames.length) + return 0.0; + + var frame = lineFragment._glyphsFrames[index]; + return (frame && frame._descent !== undefined) ? frame._descent : 0.0; } - (void)setLineFragmentRect:(CGRect)fragmentRect forGlyphRange:(CPRange)glyphRange usedRect:(CGRect)usedRect @@ -843,11 +859,16 @@ _oncontextmenuhandler = function () { return false; }; { if (_lineFragments.length > 0 && index >= [self numberOfGlyphs] - 1) { - var lineFragment= _lineFragments[_lineFragments.length - 1], + var lineFragment = _lineFragments[_lineFragments.length - 1], glyphFrames = [lineFragment glyphFrames]; - if (glyphFrames.length > 0) - return CGPointCreateCopy(glyphFrames[glyphFrames.length - 1].origin); + if (glyphFrames && glyphFrames.length > 0) + { + var frame = glyphFrames[glyphFrames.length - 1]; + + if (frame) + return CGPointCreateCopy(frame.origin); + } } var lineFragment = _objectWithLocationInRange(_lineFragments, index); @@ -858,8 +879,17 @@ _oncontextmenuhandler = function () { return false; }; return CGPointCreateCopy(lineFragment._location); var glyphFrames = [lineFragment glyphFrames]; + var relativeIndex = index - lineFragment._range.location; - return CGPointCreateCopy(glyphFrames[index - lineFragment._range.location].origin); + if (glyphFrames && relativeIndex >= 0 && relativeIndex < glyphFrames.length) + { + var frame = glyphFrames[relativeIndex]; + + if (frame) + return CGPointCreateCopy(frame.origin); + } + + return CGPointCreateCopy(lineFragment._location); } return CGPointMakeZero(); @@ -905,7 +935,6 @@ _oncontextmenuhandler = function () { return false; }; inTextContainer:(CPTextContainer)container rectCount:(CGRectPointer)rectCount { - var rectArray = [], lineFragments = _objectsInRange(_lineFragments, selectedCharRange); @@ -924,21 +953,29 @@ _oncontextmenuhandler = function () { return false; }; rect = nil, len = fragment._range.length; - for (var j = 0; j < len; j++) + if (frames) { - if (CPLocationInRange(fragment._range.location + j, selectedCharRange)) + for (var j = 0; j < len; j++) { - var correctedRect = CGRectCreateCopy(frames[j]); - correctedRect.size.height -= frames[j]._descent; - correctedRect.origin.y -= frames[j]._descent; + if (j < frames.length && CPLocationInRange(fragment._range.location + j, selectedCharRange)) + { + var frame = frames[j]; - if (!rect) - rect = CGRectCreateCopy(correctedRect); - else - rect = CGRectUnion(rect, correctedRect); + if (frame) + { + var correctedRect = CGRectCreateCopy(frame); + correctedRect.size.height -= frame._descent; + correctedRect.origin.y -= frame._descent; - if (_isNewlineCharacter([[_textStorage string] characterAtIndex:MAX(0, CPMaxRange(selectedCharRange) - 1)])) - rect.size.width = containerSize.width - rect.origin.x; + 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; + } + } } } @@ -949,7 +986,7 @@ _oncontextmenuhandler = function () { return false; }; var len = rectArray.length; - for (var i = 0; i < len - 1; i++) // extend the width of all but the last one + for (var i = 0; i < len - 1; i++) { if (FLOOR(CGRectGetMaxY(rectArray[i])) == FLOOR(CGRectGetMaxY(rectArray[i + 1]))) continue; @@ -1182,13 +1219,9 @@ var _objectsInRange = function(aList, aRange) { 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 view = [attributes objectForKey:_CPAttachmentView]; + var run = {_range:CPMakeRangeCopy(effectiveRange), color:nil, font:nil, elem:nil, string:nil, view:view}; } _runs.push(run); - } } else { @@ -1269,13 +1302,11 @@ var _objectsInRange = function(aList, aRange) for (var i = 0; i < l; i++) { + if (_runs[i].view && _runs[i].DOMactive) + [_runs[i].view removeFromSuperview]; + if (_runs[i].elem && _runs[i].DOMactive) - { - if (_runs[i].view) - [_runs[i].view removeFromSuperview]; - else - _textContainer._textView._DOMElement.removeChild(_runs[i].elem); - } + _textContainer._textView._DOMElement.removeChild(_runs[i].elem); _runs[i].elem = nil; _runs[i].DOMactive = NO; @@ -1288,6 +1319,9 @@ var _objectsInRange = function(aList, aRange) c = runs.length, orig = CGPointMake(_fragmentRect.origin.x, _fragmentRect.origin.y); + if (_runs.length === 0) + return; + for (var i = 0; i < c; i++) { var run = runs[i]; @@ -1302,13 +1336,21 @@ var _objectsInRange = function(aList, aRange) continue; var loc = run._range.location - _runs[0]._range.location; + + // Safety bounds check to protect against uninitialized/empty glyph frames or offsets + if (loc < 0 || loc >= _glyphsFrames.length || !_glyphsFrames[loc] || !_glyphsOffsets || loc >= _glyphsOffsets.length) + continue; + orig.x = _glyphsFrames[loc].origin.x + aPoint.x; orig.y = _glyphsFrames[loc].origin.y + aPoint.y + _glyphsOffsets[loc]; - if(run.elem) + if(run.elem || run.view) { - run.elem.style.left = (orig.x) + "px"; - run.elem.style.top = (orig.y) + "px"; + if (run.elem) + { + run.elem.style.left = (orig.x) + "px"; + run.elem.style.top = (orig.y) + "px"; + } if (run.view) [run.view setFrameOrigin:orig]; @@ -1316,8 +1358,9 @@ var _objectsInRange = function(aList, aRange) if (!run.DOMactive) { if (run.view) - [self._textContainer._textView addSubview:run.view]; - else + [_textContainer._textView addSubview:run.view]; + + if (run.elem) _textContainer._textView._DOMElement.appendChild(run.elem); } @@ -1373,12 +1416,14 @@ var _objectsInRange = function(aList, aRange) { _runs[i]._range.location += rangeOffset; - if (verticalOffset && _runs[i].elem) + if (verticalOffset) { if (_runs[i].view) _runs[i].view._frame.origin.y += verticalOffset; - _runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px'; + if (_runs[i].elem) + _runs[i].elem.top = (_runs[i].elem.top + verticalOffset) + 'px'; + _runs[i].DOMpatched = YES; } } diff --git a/AppKit/CPTextView/CPTextView.j b/AppKit/CPTextView/CPTextView.j index cfde9194a..16151f9e0 100644 --- a/AppKit/CPTextView/CPTextView.j +++ b/AppKit/CPTextView/CPTextView.j @@ -401,6 +401,7 @@ var kDelegateRespondsTo_textShouldBeginEditing - (void)superviewFrameChanged:(CPNotification)aNotification { _exposedRect = nil; + [self sizeToFit]; } - (void)viewWillMoveToSuperview:(CPView)aView @@ -1397,7 +1398,10 @@ Sets the selection to a range of characters in response to user action. - (void)moveLeftAndModifySelection:(id)sender { if ([self isSelectable]) + { [self _extendSelectionIntoDirection:-1 granularity:CPSelectByCharacter]; + [self scrollRangeToVisible:_selectionRange]; + } } - (void)moveBackward:(id)sender @@ -1419,7 +1423,10 @@ Sets the selection to a range of characters in response to user action. - (void)moveLeft:(id)sender { if ([self isSelectable]) + { [self _establishSelection:CPMakeRange(_selectionRange.location - (_selectionRange.length ? 0 : 1), 0) byExtending:NO]; + [self scrollRangeToVisible:_selectionRange]; + } } - (void)moveToEndOfParagraph:(id)sender @@ -2134,9 +2141,15 @@ Sets the selection to a range of characters in response to user action. if (CPEmptyRange(aRange)) { if (aRange.location >= [_layoutManager numberOfCharacters]) - rect = [_layoutManager extraLineFragmentRect]; + { + rect = CGRectCreateCopy([_layoutManager extraLineFragmentRect]); + rect.size.width = 1.0; + } else - rect = [_layoutManager lineFragmentRectForGlyphAtIndex:aRange.location effectiveRange:nil]; + { + rect = CGRectCreateCopy([_layoutManager boundingRectForGlyphRange:CPMakeRange(aRange.location, 1) inTextContainer:_textContainer]); + rect.size.width = 1.0; + } } else { @@ -2851,11 +2864,16 @@ var CPTextViewAllowsUndoKey = @"CPTextViewAllowsUndoKey", _typingAttributes = [[_textStorage attributesAtIndex:0 effectiveRange:nil] copy]; + if (!_typingAttributes) + _typingAttributes = [CPMutableDictionary dictionary]; + if (![_typingAttributes valueForKey:CPForegroundColorAttributeName]) [_typingAttributes setObject:[CPColor blackColor] forKey:CPForegroundColorAttributeName]; _textColor = [_typingAttributes valueForKey:CPForegroundColorAttributeName]; - [self setFont:[_typingAttributes valueForKey:CPFontAttributeName]]; + + var decodedFont = [_typingAttributes valueForKey:CPFontAttributeName] || [CPFont systemFontOfSize:12.0]; + [self setFont:decodedFont]; [self setString:[_textStorage string]]; diff --git a/AppKit/CPTextView/_CPTableTextAttachment.j b/AppKit/CPTextView/_CPTableTextAttachment.j index 8f56f684e..5984ab11f 100644 --- a/AppKit/CPTextView/_CPTableTextAttachment.j +++ b/AppKit/CPTextView/_CPTableTextAttachment.j @@ -16,8 +16,7 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -*/ + */ @import "CPView.j" @import "CPTextView.j" @@ -28,6 +27,7 @@ { CPArray _headers; CPArray _rows; + BOOL _isResizing; } - (id)initWithHeaders:(CPArray)headers rows:(CPArray)rows @@ -42,30 +42,41 @@ { _headers = headers; _rows = rows; + _isResizing = NO; - var numCols = [headers count]; + [self _rebuildTableWithWidth:totalWidth]; + } - if (numCols == 0 && [rows count] > 0) - numCols = [[rows objectAtIndex:0] count]; + return self; +} - // Apply borders on the outer left and top container edges - if (self._DOMElement) { - self._DOMElement.style.borderTop = "1px solid #e0e0e0"; - self._DOMElement.style.borderLeft = "1px solid #e0e0e0"; +- (void)_rebuildTableWithWidth:(float)totalWidth +{ + var numCols = _headers ? [_headers count] : 0; + + if (numCols == 0 && _rows && [_rows count] > 0) + numCols = [[_rows objectAtIndex:0] count]; + + // Apply borders on the outer left and top container edges + if (self._DOMElement) { + self._DOMElement.style.borderTop = "1px solid #e0e0e0"; + self._DOMElement.style.borderLeft = "1px solid #e0e0e0"; + self._DOMElement.style.boxSizing = "border-box"; + } + + // Initialize header cells + if (_headers && [_headers count] > 0) { + for (var c = 0; c < numCols; c++) { + var headerText = [_headers objectAtIndex:c]; + var cellView = [self createCellWithText:headerText frame:CGRectMakeZero() isHeader:YES]; + [self addSubview:cellView]; } - - // Initialize header cells - if ([headers count] > 0) { - for (var c = 0; c < numCols; c++) { - var headerText = [headers objectAtIndex:c]; - var cellView = [self createCellWithText:headerText frame:CGRectMakeZero() isHeader:YES]; - [self addSubview:cellView]; - } - } - - // Initialize body rows - for (var r = 0; r < [rows count]; r++) { - var rowData = [rows objectAtIndex:r]; + } + + // Initialize body rows + if (_rows) { + for (var r = 0; r < [_rows count]; r++) { + var rowData = [_rows objectAtIndex:r]; for (var c = 0; c < numCols; c++) { var cellText = @""; @@ -76,10 +87,53 @@ [self addSubview:cellView]; } } - - [self resizeToWidth:totalWidth]; } - return self; + + [self resizeToWidth:totalWidth]; + + // Trigger a parent layout manager re-layout once the table is fully reconstructed and sized. + var textView = [self superview]; + if (textView && [textView isKindOfClass:[CPTextView class]]) + { + var layoutManager = [textView layoutManager]; + if (layoutManager) + { + var charRange = [self _findCharacterRangeInLayoutManager:layoutManager]; + if (charRange && charRange.location !== CPNotFound) + { + [layoutManager invalidateLayoutForCharacterRange:charRange isSoft:NO actualCharacterRange:nil]; + [layoutManager invalidateDisplayForGlyphRange:charRange]; + [layoutManager _validateLayoutAndGlyphs]; + [textView sizeToFit]; + } + } + } +} + +- (CPRange)_findCharacterRangeInLayoutManager:(CPLayoutManager)layoutManager +{ + var lineFragments = layoutManager._lineFragments; + if (lineFragments) + { + var l = lineFragments.length; + for (var i = 0; i < l; i++) + { + var fragment = lineFragments[i]; + var runs = fragment._runs; + if (runs) + { + var rc = runs.length; + for (var j = 0; j < rc; j++) + { + if (runs[j].view === self) + { + return runs[j]._range; + } + } + } + } + } + return CPMakeRange(CPNotFound, 0); } - (CPArray)headers @@ -94,6 +148,7 @@ - (CPView)viewForWidth:(float)width { + // Always call resize to ensure cell views are constructed, but do not guard here [self resizeToWidth:width]; return self; } @@ -110,7 +165,9 @@ var borderView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, initialWidth, initialHeight)]; [borderView setBackgroundColor:[CPColor clearColor]]; [borderView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; - if (borderView._DOMElement) { + + if (borderView._DOMElement) + { borderView._DOMElement.style.borderBottom = "1px solid #e0e0e0"; borderView._DOMElement.style.borderRight = "1px solid #e0e0e0"; borderView._DOMElement.style.boxSizing = "border-box"; @@ -119,29 +176,18 @@ var textContainer = [[CPTextContainer alloc] initWithContainerSize:CGSizeMake(initialWidth - 8, 1e7)]; var textView = [[CPTextView alloc] initWithFrame:CGRectMake(4, 2, initialWidth - 8, initialHeight - 4) textContainer:textContainer]; - [textView setEditable:NO]; + [textView setEditable:YES]; [textView setSelectable:YES]; [textView setBackgroundColor:[CPColor clearColor]]; [textView setVerticallyResizable:YES]; [textView setHorizontallyResizable:NO]; [[textView textContainer] setWidthTracksTextView:YES]; - // Construct cell text using standard fonts + // Configure cell text style using standard CPTextView APIs var cellFont = isHeader ? [CPFont boldSystemFontOfSize:11.0] : [CPFont systemFontOfSize:11.0]; - var parsedText = [[CPAttributedString alloc] initWithString:text attributes:@{ - CPFontAttributeName: cellFont, - CPForegroundColorAttributeName: [CPColor blackColor] - }]; - - var storage = [textView textStorage]; - if (storage && [storage respondsToSelector:@selector(setAttributedString:)]) { - [storage setAttributedString:parsedText]; - } else { - [textView setEditable:YES]; - [textView setString:@""]; - [textView insertText:parsedText]; - [textView setEditable:NO]; - } + [textView setFont:cellFont]; + [textView setTextColor:[CPColor blackColor]]; + [textView setString:text]; [cellContainer addSubview:textView]; return cellContainer; @@ -161,11 +207,19 @@ - (void)resizeToWidth:(float)newWidth { - var numCols = [_headers count]; - if (numCols == 0 && [_rows count] > 0) { + if (_isResizing) + return; + + _isResizing = YES; + + var numCols = _headers ? [_headers count] : 0; + if (numCols == 0 && _rows && [_rows count] > 0) { numCols = [[_rows objectAtIndex:0] count]; } - if (numCols == 0) return; + if (numCols == 0) { + _isResizing = NO; + return; + } var subviews = [self subviews]; var colNaturalWidths = []; @@ -206,18 +260,22 @@ } }; - for (var c = 0; c < [_headers count]; c++) { - measureCell([_headers objectAtIndex:c], YES, c); + if (_headers) { + for (var c = 0; c < [_headers count]; c++) { + measureCell([_headers objectAtIndex:c], YES, c); + } } - for (var r = 0; r < [_rows count]; r++) { - var rowData = [_rows objectAtIndex:r]; - for (var c = 0; c < numCols; c++) { - var cellText = @""; - if (c < [rowData count]) { - cellText = [rowData objectAtIndex:c]; + if (_rows) { + for (var r = 0; r < [_rows count]; r++) { + var rowData = [_rows objectAtIndex:r]; + for (var c = 0; c < numCols; c++) { + var cellText = @""; + if (c < [rowData count]) { + cellText = [rowData objectAtIndex:c]; + } + measureCell(cellText, NO, c); } - measureCell(cellText, NO, c); } } @@ -279,8 +337,9 @@ var targetWidth = Math.max(10.0, colWidths[c] - 8); [[textView textContainer] setContainerSize:CGSizeMake(targetWidth, 1e7)]; - var usedRect = [[textView layoutManager] usedRectForTextContainer:[textView textContainer]]; - var wrappedHeight = CGRectGetHeight(usedRect) + 12.0; + var layoutManager = [textView layoutManager]; + var usedRect = layoutManager ? [layoutManager usedRectForTextContainer:[textView textContainer]] : nil; + var wrappedHeight = (usedRect ? CGRectGetHeight(usedRect) : 0.0) + 12.0; if (wrappedHeight > maxCellHeight) { maxCellHeight = wrappedHeight; } @@ -314,19 +373,29 @@ return maxCellHeight; }; - if ([_headers count] > 0) { + if (_headers && [_headers count] > 0) { var headerHeight = layoutRow(cellIndex); cellIndex += numCols; currentY += headerHeight; } - for (var r = 0; r < [_rows count]; r++) { - var rowHeight = layoutRow(cellIndex); - cellIndex += numCols; - currentY += rowHeight; + if (_rows) { + for (var r = 0; r < [_rows count]; r++) { + var rowHeight = layoutRow(cellIndex); + cellIndex += numCols; + currentY += rowHeight; + } } - [self setFrameSize:CGSizeMake(newWidth, currentY)]; + // GUARD FRAME SIZE MUTATIONS: Only execute setFrameSize if dimensions actually change. + // This stops infinite layout passes while ensuring subviews are laid out. + var currentSize = [self frame].size; + if (ABS(currentSize.width - newWidth) > 0.1 || ABS(currentSize.height - currentY) > 0.1) + { + [self setFrameSize:CGSizeMake(newWidth, currentY)]; + } + + _isResizing = NO; } @end diff --git a/Tests/Manual/CPTextView/AppController.j b/Tests/Manual/CPTextView/AppController.j index 7abb6d277..f35d0730c 100755 --- a/Tests/Manual/CPTextView/AppController.j +++ b/Tests/Manual/CPTextView/AppController.j @@ -14,6 +14,8 @@ @import @import @import +@import +@import @implementation AppController : CPObject { @@ -90,6 +92,25 @@ [_textView insertText:@" "]; } +- (void)insertTable:(id)sender +{ + var headers = [@"Item Description", @"Quantity", @"Unit Price"]; + var rows = [ + [@"Cappuccino Web Framework Lic.", @"2", @"$199.00"], + [@"Objective-J Development Support", @"5", @"$150.00"], + [@"Cloud Compilation VM Server", @"1", @"$49.00"] + ]; + + var tableAttachment = [[_CPTableTextAttachment alloc] initWithHeaders:headers rows:rows width:500.0]; + + // Insert single-character atomic text attachment + var tableAttrStr = [CPTextStorage attributedStringWithAttachment:tableAttachment]; + + [_textView insertText:@"\ntable (own line)\n"]; + [_textView insertText:tableAttrStr]; + [_textView insertText:@"\nend table"]; +} + - (void)applicationDidFinishLaunching:(CPNotification)aNotification { var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask], @@ -145,6 +166,14 @@ [toolbarView addSubview:attachButton]; currentX += 150; + // Add Table Trigger + var tableButton = [[CPButton alloc] initWithFrame:CGRectMake(currentX, 15, 100, 30)]; + [tableButton setTitle:@"Add Table"]; + [tableButton setTarget:self]; + [tableButton setAction:@selector(insertTable:)]; + [toolbarView addSubview:tableButton]; + currentX += 110; + // Text Alignment Group var labelAlign = [[CPTextField alloc] initWithFrame:CGRectMake(currentX, 22, 45, 20)]; [labelAlign setStringValue:@"Align:"]; @@ -210,9 +239,10 @@ [leftLabel setAutoresizingMask:CPViewWidthSizable]; [leftContainer addSubview:leftLabel]; - _textView = [[CPTextView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([leftContainer bounds]) - 30, CGRectGetHeight([leftContainer bounds]) - 70)]; + _textView = [[CPTextView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([leftContainer bounds]) - 30, 0)]; [_textView setRichText:YES]; [_textView setBackgroundColor:[CPColor whiteColor]]; + [[_textView textContainer] setWidthTracksTextView:YES]; _scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(15, 40, CGRectGetWidth([leftContainer bounds]) - 30, CGRectGetHeight([leftContainer bounds]) - 65)]; [_scrollView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; @@ -321,6 +351,8 @@ [_textView insertText:[[CPAttributedString alloc] initWithString:@"This paragraph has a first-line indent of 30pt, a head indent of 50pt, and a tail indent of -30pt. Check the horizontal ruler above to see how the indent markers align with this paragraph, and adjust them directly!\n" attributes:[CPDictionary dictionaryWithObject:indentParagraph forKey:CPParagraphStyleAttributeName]]]; + [_textView insertText:@"\n"]; + [theWindow orderFront:self]; [CPMenu setMenuBarVisible:YES]; }