mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 04:57:03 +00:00
improved: table generation
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user