diff --git a/AppKit/CPTextView/_CPRTFProducer.j b/AppKit/CPTextView/_CPRTFProducer.j index 8d06ad104..1e48afda8 100644 --- a/AppKit/CPTextView/_CPRTFProducer.j +++ b/AppKit/CPTextView/_CPRTFProducer.j @@ -89,12 +89,7 @@ function _points2twips(a) { return (a) * 20.0; } { if (self = [super init]) { - // maintain a dictionary for the used colours - // (for rtf-header generation) colorDict = [CPMutableDictionary new]; - - //maintain a dictionary for the used fonts - //(for rtf-header generation) fontDict = [CPMutableDictionary new]; fgColor = [CPColor blackColor]; @@ -104,7 +99,6 @@ function _points2twips(a) { return (a) * 20.0; } return self; } -// private stuff follows - (CPString)fontTable { if (![fontDict count]) @@ -319,7 +313,6 @@ function _points2twips(a) { return (a) * 20.0; } break; } - // write first line indent and left indent var twips = _points2twips([paraStyle firstLineHeadIndent]); if (twips != 0.0) @@ -392,7 +385,6 @@ function _points2twips(a) { return (a) * 20.0; } var unwrap = function(obj) { if (!obj) return null; - // If the object contains the table structures, bypass unwrapping if ((typeof obj.respondsToSelector === "function" && ([obj respondsToSelector:@selector(headers)] || [obj respondsToSelector:@selector(rows)])) || obj.headers || obj._headers || obj.rows || obj._rows) { return obj; @@ -434,7 +426,6 @@ function _points2twips(a) { return (a) * 20.0; } tableAttachment = unwrap(tableAttachment); - // Ultimate fallback scanner for layout character placeholder sequences if (!tableAttachment && (substring === "\uFFFC" || substring === "")) { var keys = [attributes allKeys], @@ -462,20 +453,48 @@ function _points2twips(a) { return (a) * 20.0; } var headers = null, rows = null; - if (typeof tableAttachment.respondsToSelector === "function") { - if ([tableAttachment respondsToSelector:@selector(headers)]) { - headers = [tableAttachment headers]; + // Try to fetch from the active live view of the attachment first to capture user edits + var activeView = null; + if (typeof tableAttachment.respondsToSelector === "function" && [tableAttachment respondsToSelector:@selector(view)]) { + activeView = [tableAttachment view]; + } + if (!activeView) { + activeView = tableAttachment._view || tableAttachment.view; + } + + if (activeView) { + if (typeof activeView.respondsToSelector === "function") { + if ([activeView respondsToSelector:@selector(headers)]) { + headers = [activeView headers]; + } + if ([activeView respondsToSelector:@selector(rows)]) { + rows = [activeView rows]; + } } - if ([tableAttachment respondsToSelector:@selector(rows)]) { - rows = [tableAttachment rows]; + if (!headers) { + headers = activeView._headers || activeView.headers; + } + if (!rows) { + rows = activeView._rows || activeView.rows; } } - - if (!headers) { - headers = tableAttachment._headers || tableAttachment.headers; - } - if (!rows) { - rows = tableAttachment._rows || tableAttachment.rows; + + // Fall back to the attachment's parsed properties if the view is nil or lacks the properties + if (!headers || !rows) { + if (typeof tableAttachment.respondsToSelector === "function") { + if ([tableAttachment respondsToSelector:@selector(headers)]) { + headers = [tableAttachment headers]; + } + if ([tableAttachment respondsToSelector:@selector(rows)]) { + rows = [tableAttachment rows]; + } + } + if (!headers) { + headers = tableAttachment._headers || tableAttachment.headers; + } + if (!rows) { + rows = tableAttachment._rows || tableAttachment.rows; + } } var getCount = function(arr) { @@ -563,23 +582,12 @@ function _points2twips(a) { return (a) * 20.0; } headerString += [self paragraphStyle:paraStyle]; } - /* - * analyze attributes of current run - * - * FIXME: All the character attributes should be output relative to the font - * attributes of the paragraph. So if the paragraph has underline on it should - * still be possible to switch it off for some characters, which currently is - * not possible. - */ attribEnum = [attributes keyEnumerator]; while ((currAttrib = [attribEnum nextObject]) != nil) { if ([currAttrib isEqualToString:CPFontAttributeName]) { - /* - * handle fonts - */ var font, fontName, traits; @@ -588,15 +596,9 @@ function _points2twips(a) { return (a) * 20.0; } fontName = [font familyName]; traits = [[CPFontManager sharedFontManager] traitsOfFont:font]; - /* - * font name - */ if (currentFont == nil || ![fontName isEqualToString:[currentFont familyName]]) headerString += [self fontToken:fontName]; - /* - * font size - */ if (currentFont == nil || [font size] != [currentFont size]) { var points = [font size] * 2, @@ -605,9 +607,7 @@ function _points2twips(a) { return (a) * 20.0; } pString = [CPString stringWithFormat:@"\\fs%d", points]; headerString += pString; } - /* - * font attributes - */ + if (traits & CPItalicFontMask) { headerString += @"\\i"; @@ -713,7 +713,7 @@ function _points2twips(a) { return (a) * 20.0; } var nobraces; if ([headerString length]) - nobraces = [CPString stringWithFormat:@"%@ %@", headerString, substring]; + nobraces = [CPString stringWithFormat:@"%@ %@}", headerString, substring]; else nobraces = substring; @@ -733,7 +733,7 @@ function _points2twips(a) { return (a) * 20.0; } completeRange = CPMakeRange(0, length), paragraphStart = YES; - while (CPMaxRange(currRange) < CPMaxRange(completeRange)) // save all "runs" + while (CPMaxRange(currRange) < CPMaxRange(completeRange)) { var attributes, substring, @@ -749,7 +749,6 @@ function _points2twips(a) { return (a) * 20.0; } result += runString; - // Dynamically compute paragraph boundaries based on standard line feeds if (substring.length > 0 && substring.charAt(substring.length - 1) === '\n') paragraphStart = YES; else diff --git a/AppKit/CPTextView/_CPTableTextAttachment.j b/AppKit/CPTextView/_CPTableTextAttachment.j index 5984ab11f..7a7ffba92 100644 --- a/AppKit/CPTextView/_CPTableTextAttachment.j +++ b/AppKit/CPTextView/_CPTableTextAttachment.j @@ -28,20 +28,29 @@ CPArray _headers; CPArray _rows; BOOL _isResizing; + BOOL _isEditable; + BOOL _acceptsRichText; } - (id)initWithHeaders:(CPArray)headers rows:(CPArray)rows { - return [self initWithHeaders:headers rows:rows width:500.0]; + return [self initWithHeaders:headers rows:rows width:500.0 isEditable:YES acceptsRichText:YES]; } - (id)initWithHeaders:(CPArray)headers rows:(CPArray)rows width:(float)totalWidth +{ + return [self initWithHeaders:headers rows:rows width:totalWidth isEditable:YES acceptsRichText:NO]; +} + +- (id)initWithHeaders:(CPArray)headers rows:(CPArray)rows width:(float)totalWidth isEditable:(BOOL)isEditable acceptsRichText:(BOOL)acceptsRichText { self = [super initWithFrame:CGRectMake(0, 0, totalWidth, 20)]; if (self) { _headers = headers; _rows = rows; + _isEditable = isEditable; + _acceptsRichText = acceptsRichText; _isResizing = NO; [self _rebuildTableWithWidth:totalWidth]; @@ -91,11 +100,13 @@ [self resizeToWidth:totalWidth]; - // Trigger a parent layout manager re-layout once the table is fully reconstructed and sized. + // Trigger parent layout engine re-calculation var textView = [self superview]; + if (textView && [textView isKindOfClass:[CPTextView class]]) { var layoutManager = [textView layoutManager]; + if (layoutManager) { var charRange = [self _findCharacterRangeInLayoutManager:layoutManager]; @@ -138,22 +149,81 @@ - (CPArray)headers { + var numCols = _headers ? [_headers count] : 0; + if (numCols == 0 && _rows && [_rows count] > 0) + numCols = [[_rows objectAtIndex:0] count]; + + if (numCols == 0) + return _headers; + + var subviews = [self subviews]; + if ([subviews count] < numCols) + return _headers; // Subviews are not yet rendered, return cached fallback + + var currentHeaders = [CPMutableArray array]; + for (var c = 0; c < numCols; c++) + { + var cellView = [subviews objectAtIndex:c]; + var textView = [self getTextViewFromCell:cellView]; + var text = @""; + if (textView) + { + text = _acceptsRichText ? [[textView textStorage] copy] : [textView string]; + } + [currentHeaders addObject:text]; + } + + _headers = currentHeaders; return _headers; } - (CPArray)rows { + var numCols = _headers ? [_headers count] : 0; + if (numCols == 0 && _rows && [_rows count] > 0) + numCols = [[_rows objectAtIndex:0] count]; + + if (numCols == 0 || !_rows) + return _rows; + + var subviews = [self subviews]; + var headerOffset = (_headers && [_headers count] > 0) ? numCols : 0; + var expectedCount = headerOffset + ([_rows count] * numCols); + + if ([subviews count] < expectedCount) + return _rows; // Subviews are not yet rendered, return cached fallback + + var currentRows = [CPMutableArray array]; + var cellIndex = headerOffset; + + for (var r = 0; r < [_rows count]; r++) + { + var rowData = [CPMutableArray array]; + for (var c = 0; c < numCols; c++) + { + var cellView = [subviews objectAtIndex:cellIndex++]; + var textView = [self getTextViewFromCell:cellView]; + var text = @""; + if (textView) + { + text = _acceptsRichText ? [[textView textStorage] copy] : [textView string]; + } + [rowData addObject:text]; + } + [currentRows addObject:rowData]; + } + + _rows = currentRows; return _rows; } - (CPView)viewForWidth:(float)width { - // Always call resize to ensure cell views are constructed, but do not guard here [self resizeToWidth:width]; return self; } -- (CPView)createCellWithText:(CPString)text frame:(CGRect)frame isHeader:(BOOL)isHeader +- (CPView)createCellWithText:(id)text frame:(CGRect)frame isHeader:(BOOL)isHeader { var initialWidth = (frame.size.width > 0) ? frame.size.width : 120.0; var initialHeight = (frame.size.height > 0) ? frame.size.height : 28.0; @@ -176,23 +246,48 @@ 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:YES]; + [textView setEditable:_isEditable]; [textView setSelectable:YES]; [textView setBackgroundColor:[CPColor clearColor]]; [textView setVerticallyResizable:YES]; [textView setHorizontallyResizable:NO]; [[textView textContainer] setWidthTracksTextView:YES]; + // Configure cell rich text mode + [textView setRichText:_acceptsRichText]; + + // Intercept changes within cell TextViews to notify the table + [textView setDelegate:self]; + // Configure cell text style using standard CPTextView APIs var cellFont = isHeader ? [CPFont boldSystemFontOfSize:11.0] : [CPFont systemFontOfSize:11.0]; [textView setFont:cellFont]; [textView setTextColor:[CPColor blackColor]]; - [textView setString:text]; + + // Populate either rich text or plain text safely + if (text && [text isKindOfClass:[CPAttributedString class]]) + { + [[textView textStorage] setAttributedString:text]; + } + else if (text) + { + [textView setString:String(text)]; + } + else + { + [textView setString:@""]; + } [cellContainer addSubview:textView]; return cellContainer; } +- (void)textDidChange:(CPNotification)aNotification +{ + // Live update cell layouts and heights when changes are typed + [self resizeToWidth:CGRectGetWidth([self frame])]; +} + - (CPTextView)getTextViewFromCell:(CPView)cellView { var subviews = [cellView subviews]; @@ -212,9 +307,13 @@ _isResizing = YES; - var numCols = _headers ? [_headers count] : 0; - if (numCols == 0 && _rows && [_rows count] > 0) { - numCols = [[_rows objectAtIndex:0] count]; + // Use dynamic getters to fetch live edited strings + var currentHeaders = [self headers]; + var currentRows = [self rows]; + + var numCols = currentHeaders ? [currentHeaders count] : 0; + if (numCols == 0 && currentRows && [currentRows count] > 0) { + numCols = [[currentRows objectAtIndex:0] count]; } if (numCols == 0) { _isResizing = NO; @@ -236,14 +335,26 @@ var cellFont = isHeader ? [CPFont boldSystemFontOfSize:11.0] : [CPFont systemFontOfSize:11.0]; [measureTextField setFont:cellFont]; - [measureTextField setStringValue:cellText]; + var plainText = (cellText && typeof cellText.string === "function") ? [cellText string] : (cellText || @""); + plainText = String(plainText); + + if (cellText && typeof cellText.string === "function") { + if ([measureTextField respondsToSelector:@selector(setAttributedStringValue:)]) { + [measureTextField setAttributedStringValue:cellText]; + } else { + [measureTextField setStringValue:plainText]; + } + } else { + [measureTextField setStringValue:plainText]; + } + [measureTextField sizeToFit]; var naturalW = CGRectGetWidth([measureTextField frame]) + 24.0; if (naturalW > colNaturalWidths[colIndex]) { colNaturalWidths[colIndex] = naturalW; } - var words = cellText.split(/[\s\-]/); + var words = plainText.split(/[\s\-]/); var maxWordW = 50.0; for (var w = 0; w < words.length; w++) { var word = words[w].trim(); @@ -260,15 +371,15 @@ } }; - if (_headers) { - for (var c = 0; c < [_headers count]; c++) { - measureCell([_headers objectAtIndex:c], YES, c); + if (currentHeaders) { + for (var c = 0; c < [currentHeaders count]; c++) { + measureCell([currentHeaders objectAtIndex:c], YES, c); } } - if (_rows) { - for (var r = 0; r < [_rows count]; r++) { - var rowData = [_rows objectAtIndex:r]; + if (currentRows) { + for (var r = 0; r < [currentRows count]; r++) { + var rowData = [currentRows objectAtIndex:r]; for (var c = 0; c < numCols; c++) { var cellText = @""; if (c < [rowData count]) { @@ -373,26 +484,51 @@ return maxCellHeight; }; - if (_headers && [_headers count] > 0) { + if (currentHeaders && [currentHeaders count] > 0) { var headerHeight = layoutRow(cellIndex); cellIndex += numCols; currentY += headerHeight; } - if (_rows) { - for (var r = 0; r < [_rows count]; r++) { + if (currentRows) { + for (var r = 0; r < [currentRows count]; r++) { var rowHeight = layoutRow(cellIndex); cellIndex += numCols; currentY += rowHeight; } } - // GUARD FRAME SIZE MUTATIONS: Only execute setFrameSize if dimensions actually change. - // This stops infinite layout passes while ensuring subviews are laid out. + // GUARD FRAME SIZE MUTATIONS var currentSize = [self frame].size; if (ABS(currentSize.width - newWidth) > 0.1 || ABS(currentSize.height - currentY) > 0.1) { [self setFrameSize:CGSizeMake(newWidth, currentY)]; + + // we need to re-layout the textview here. + // but this is not easy as the layout engine is not re-entrant + // this does not work: (delay does not matter), layout is always off +// setTimeout(function() { +// 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]; +// } +// } +// } +// }, 0); + } _isResizing = NO;