// Copyright 1997-2000 Omni Development, Inc.  All rights reserved.
//
// This software may only be used and reproduced according to the
// terms in the file OmniSourceLicense.html, which should be
// distributed with this project and can also be found at
// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.

#import "OHTable.h"

#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <OmniBase/OmniBase.h>
#import <OmniFoundation/OmniFoundation.h>
#import <OmniAppKit/OmniAppKit.h>
#import <OWF/OWF.h>

#import "OHHTMLOwner.h"
#import "OHHTMLView.h"
#import "OHColumnSeparations.h"
#import "OHRowSeparations.h"
#import "OHTableCellHTMLView.h"
#import "OHTableColumnWidthRequest.h"
#import "OHTableContainerCell.h"
#import "OHTextBuilder.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OmniHTML/Tables.subproj/OHTable.m,v 1.26 2000/01/20 00:28:01 kc Exp $")

@interface OHTable (Private)
// Table layout
- (void)doLayout;
- (unsigned int)requestedDisplayWidth;
- (void)calculateWidthRange;
- (void)calculateColumnSeparations;
- (void)applyRequestedWidthsToSeparations;
- (void)setupColumnLayoutWithSeparators;
- (void)applyColumnSeparationsToColumnLayout;
- (void)_layColumnsForWidth:(unsigned int)totalWidth;
- (void)layColumnsUsingWidthProportionalLayoutForWidth:(unsigned int)totalWidth;
- (void)_layRows;
- (void)_positionTableCells;

- (void)drawPlainBordersForRect:(NSRect)rect;
- (void)draw3DBordersForRect:(NSRect)rect;
- (void)drawTableCellBackgroundForRect:(NSRect)rect withBorders:(BOOL)shouldDrawBorders;
// - (void)tableCellFrameChanged:(NSNotification *)notification;

// Find helpers
- (void)findSelectionInSubviews;
- (BOOL)checkIfFirstResponder:(NSView *)firstResponder isInSearchableContentViewAtDataCellIndex:(unsigned int)dataCellIndex;

// Debugging
- (NSArray *)flagsDebugArray;
- (void)debugFlags;

@end

@implementation OHTable

static unsigned int OHTableDebug = 0;

static const float internalBorderWidth = 1.0;

- init;
{
    OFZone *myZone;

    if (![super initWithFrame:NSZeroRect])
	return nil;

    [self setAutoresizingMask:NSViewWidthSizable];
    [self setAutoresizesSubviews:NO];
    myZone = [OFZone zoneForObject:self];
    
    nonretainedContainingTextCell = nil;
    selectionHoldingCellIndexHint = NSNotFound;

    dataCells = [[OFMatrix allocWithZone:[myZone nsZone]] init];
    dataCellOwners = [[NSMutableArray allocWithZone:[myZone nsZone]] init];
    firstBodyRow = 0;
    lastBodyRow = 10000;

    columnRequests = [[OFSparseArray allocWithZone:[myZone nsZone]] initWithCapacity:0];

    // Note --- as a wacky special case, if borderWidth is nonzero but borderBits doesn't specify a border, we allocate space to draw the border even though we don't draw it, so that we can be Just Like Netscape!

    borderBits = OWTBorderNone;
    borderWidth = 1.0;
    cellSpacing = 2.0;

    columnLayout = NULL;
    rowLayout = NULL;
    captionOffset = 5.0;
    
    layout = OWTL_WidthProportional;
    displayStyle = OWTD_Gray;

    tableFlags.columnSeparationsValid = NO;
    tableFlags.widthsValid = NO;
    tableFlags.columnLayoutValid = NO;
    tableFlags.rowLayoutValid = NO;
    tableFlags.subviewsPositioned = NO;
    tableFlags.availableWidthValid = NO;
    tableFlags.displayWidthValid = NO;
    tableFlags.mungingFrames = NO;

    tableFlags.haveRequestedWidth = NO;
    tableFlags.requestedWidthIsFraction = NO;
    tableFlags.haveRequestedHeight = NO;
    tableFlags.requestedHeightIsRelative = NO;

    if (OHTableDebug >= 9)
        [self debugFlags];
    
    return self;
}

- (id)initWithFrame:(NSRect)frameRect;
{
    OBRequestConcreteImplementation(self, _cmd);
    return nil; // Not reached
}

- (void)dealloc;
{
    NSZone *zone = [[OFZone zoneForObject:self] nsZone];
    
    // [[NSNotificationCenter defaultCenter] removeObserver:self];
    [backgroundColor release];
    [dataCells release];
    [dataCellOwners release];
    [columnRequests release];
    [columnSeparations release];

    zone = [self zone];
    if (rowLayout)
        NSZoneFree(zone, rowLayout);
    if (columnLayout)
        NSZoneFree(zone, columnLayout);
    [super dealloc];
}

// NSView subclass

- (void)drawRect:(NSRect)rect;
{
    if ([self isOpaque]) {
        [backgroundColor set];
        NSRectFill(rect);
    }
    
    [self doLayout];

    if (displayStyle == OWTD_Flat || borderBits != OWTBorderAll || borderWidth == 0.0)
        [self drawPlainBordersForRect:rect];
    else
        [self draw3DBordersForRect:rect];
}

- (BOOL)isFlipped;
{
    return YES;
}

- (BOOL)isOpaque;
{
    // In Netscape, tables are never opaque:  the background image shows through the borders between the cells (and through any empty cells).
    return NO; // Used to return backgroundColor != nil
}

- (void)mouseDown:(NSEvent *)event;
{
    // If the user clicks on an empty cell or our border, we just ignore her.
    // Otherwise, we'll pass the mouseDown event on to our superview, which will set its selection but won't be first responder, and so the screen will get messed up.
}

// OHTable methods

- (void)setBackgroundColor:(NSColor *)newBackgroundColor;
{
    if (backgroundColor == newBackgroundColor)
        return;
    [backgroundColor release];
    backgroundColor = [newBackgroundColor retain];
}

- (void)setBorderType:(int)type;
{
    if (borderBits == type)
        return;
    borderBits = type;
    OBASSERT(!tableFlags.displayWidthValid);
}

- (void)setBorderWidth:(float)newWidth;
{
    if (borderWidth == newWidth)
        return;
    borderWidth = newWidth;
    OBASSERT(!tableFlags.displayWidthValid);
}

- (void)setCellSpacing:(float)newSpacing
{
    if (cellSpacing == newSpacing)
        return;
    cellSpacing = newSpacing;
    OBASSERT(!tableFlags.displayWidthValid);
}

- (void)setTableWidth:(float)newWidth isFraction:(BOOL)isFraction;
{
    requestedWidth = newWidth;
    tableFlags.haveRequestedWidth = YES;
    tableFlags.requestedWidthIsFraction = isFraction;
    if (OHTableDebug >= 9)
        [self debugFlags];
    OBASSERT(!tableFlags.displayWidthValid);
}

- (void)setTableHeight:(float)newHeight isFraction:(BOOL)isFraction;
{
    requestedHeight = newHeight;
    tableFlags.haveRequestedHeight = YES;
    tableFlags.requestedHeightIsRelative = isFraction;
    if (OHTableDebug >= 9)
        [self debugFlags];
    OBASSERT(!tableFlags.displayWidthValid);
}

- (void)setWidth:(unsigned int)columnWidth isFraction:(BOOL)isFraction forColumn:(unsigned int)columnIndex span:(unsigned int)columnSpan;
{
    OHTableColumnWidthRequest *widthRequest;

#warning -setWidth:isFraction:forColumn:span: ignores widths when columnSpan != 1.
    if (columnSpan != 1) {
        if (OHTableDebug >= 1)
            NSLog(@"-[%@ setWidth:%d isFraction:%@ forColumn:%d span:%d]: span != 1, ignoring", OBShortObjectDescription(self), (int)columnWidth, isFraction ? @"YES" : @"NO", columnIndex, columnSpan);
        return;
    }

    widthRequest = [columnRequests objectAtIndex:columnIndex];
    if (widthRequest)
        [widthRequest retain];
    else
        widthRequest = [[OHTableColumnWidthRequest allocWithZone:[self zone]] init];
    if (isFraction) {
        [widthRequest requestPercentageWidth:columnWidth];
    } else {
        [widthRequest requestFixedWidth:columnWidth];
    }
    [columnRequests setObject:widthRequest atIndex:columnIndex];
    [widthRequest release];
}   

- (void)setDisplayStyle:(OWTDisplayStyle)newStyle
{
    displayStyle = newStyle;
    [nonretainedContainingTextCell mainThreadPerformSelectorOnce:@selector(redisplayCell)];
}

- (void)acceptTableCellOwner:(OHHTMLOwner *)newCellOwner;
{
    OHTableCellHTMLView *aNewCell;

    ASSERT_IN_MAIN_THREAD(@"-[OHTable acceptTableCellOwner:] can only be called from the main thread");
    aNewCell = (OHTableCellHTMLView *)[newCellOwner htmlView];
    // Check for recursive tables being set up
    OBASSERT(aNewCell != (OHTableCellHTMLView *)[nonretainedContainingTextCell containingView]);

    if ([aNewCell cellType] != CellType_Caption) {
        unsigned int columnIndex, columnSpan;

        columnIndex = [aNewCell columnIndex];
        columnSpan = [aNewCell columnSpan];
        [dataCells setObject:aNewCell atRowIndex:[aNewCell rowIndex] span:[aNewCell rowSpan] columnIndex:columnIndex span:columnSpan];
        switch ([aNewCell widthType]) {
            case OWTCW_None:
                break;
            case OWTCW_Absolute:
                [self setWidth:[aNewCell requestedWidth] isFraction:NO forColumn:columnIndex span:columnSpan];
                break;
            case OWTCW_Relative:
                [self setWidth:[aNewCell requestedWidth] isFraction:YES forColumn:columnIndex span:columnSpan];
                break;
        }
        if (columnIndex + 1 > columnCount)
            columnCount = columnIndex + 1;
    } else {
        if (columnCount < 1)
            columnCount = 1;
    }

    [dataCellOwners addObject:newCellOwner];

    tableFlags.columnSeparationsValid = NO;
    tableFlags.widthsValid = NO;
    tableFlags.columnLayoutValid = NO;
    tableFlags.rowLayoutValid = NO;
    tableFlags.displayWidthValid = NO;
    if (OHTableDebug >= 9)
        [self debugFlags];

    [self addSubview:aNewCell];
    // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tableCellFrameChanged:) name:NSViewFrameDidChangeNotification object:aNewCell];
}

- (OHTableCellHTMLView *)cellAtRow:(unsigned int)rowIndex column:(unsigned int)columnIndex;
{
    return [dataCells objectAtRowIndex:rowIndex columnIndex:columnIndex];
}

// Displaying

- (OHWidthRange)widthRange;
{
    unsigned int requestedDisplayWidth;

    if (!tableFlags.haveRequestedWidth || tableFlags.requestedWidthIsFraction) {
        [self calculateWidthRange];
        return widthRange;
    }
    requestedDisplayWidth = [self requestedDisplayWidth];
    if (requestedDisplayWidth != NSNotFound)
        return OHMakeWidthRange(requestedDisplayWidth, requestedDisplayWidth);
    else
        return widthRange;
}

- (OFMatrix *)dataCells;
{
    return dataCells;
}

- (void)setContainingTextCell:(OHTableContainerCell *)aTextCell;
{
    nonretainedContainingTextCell = aTextCell;
}

- (void)setLayoutSize:(NSSize)newSize;
{
    if (!tableFlags.availableWidthValid || availableWidth != newSize.width) {
        tableFlags.availableWidthValid = YES;
        availableWidth = newSize.width;
        tableFlags.widthsValid = NO;
        tableFlags.displayWidthValid = NO;
    }
    if (visibleHeight != newSize.height) {
        tableFlags.rowLayoutValid = NO;
        visibleHeight = newSize.height;
    }

    if (OHTableDebug >= 9)
        [self debugFlags];
}

- (void)calculateDisplayWidth;
{
    unsigned int newDisplayWidth, requestedDisplayWidth;

    if (tableFlags.displayWidthValid)
        return;

    requestedDisplayWidth = [self requestedDisplayWidth];
    if (requestedDisplayWidth != NSNotFound) {
        if (requestedDisplayWidth < widthRange.minimum)
            newDisplayWidth = widthRange.minimum;
        else
            newDisplayWidth = requestedDisplayWidth;
    } else {
        // No particular width requested, size to the available width (if it fits within our width range).
        if (availableWidth > widthRange.maximum)
            newDisplayWidth = widthRange.maximum;
        else if (availableWidth < widthRange.minimum)
            newDisplayWidth = widthRange.minimum;
        else
            newDisplayWidth = availableWidth;
    }

    tableFlags.displayWidthValid = YES;
    if (displayWidth != newDisplayWidth) {
        displayWidth = newDisplayWidth;
        tableFlags.columnLayoutValid = NO;
//        [nonretainedContainingTextCell relayoutCell];
    }
    if (OHTableDebug >= 9)
        [self debugFlags];
}

- (float)currentDisplayWidth;
{
    if (!tableFlags.displayWidthValid)
        [self calculateDisplayWidth];
    return displayWidth;
}

- (float)currentDisplayHeight;
{
    if (!tableFlags.displayWidthValid)
        [self calculateDisplayWidth];
    if (!tableFlags.widthsValid || !tableFlags.rowLayoutValid || !tableFlags.columnLayoutValid)
	[self _layRows];
    return NSHeight([self bounds]);
}

- (NSRect)cellFrameForTableCell:(OHTableCellHTMLView *)tableCell;
{
    OHTableCellBorders cellBorders;
    NSRect cellFrame;

    cellBorders = [tableCell borders];
    cellFrame.origin.x = columnLayout[cellBorders.left].placement + columnLayout[cellBorders.left].separatorWidth;
    cellFrame.origin.y = rowLayout[cellBorders.top].placement + rowLayout[cellBorders.top].height + topMargin;
    cellFrame.size.width = columnLayout[cellBorders.right].placement - columnLayout[cellBorders.left].placement - columnLayout[cellBorders.left].separatorWidth;
    cellFrame.size.height = rowLayout[cellBorders.bottom].placement - rowLayout[cellBorders.top].placement - rowLayout[cellBorders.top].height;
    return cellFrame;
}

//

#warning -wantsRelayout is not currently called by anyone
- (BOOL)wantsRelayout;
{
    return !tableFlags.widthsValid || !tableFlags.columnLayoutValid;
}

// OHHTMLPageContent protocol

- (OHHTMLPageView *)htmlPageView;
{
    return [[nonretainedContainingTextCell containingView] htmlPageView];
}

- (NSColor *)backgroundColor;
{
    if (backgroundColor) {
        return backgroundColor;
    } else {
        id <OHHTMLPageContent> parentContent;

        // Return our parentContent's background color
        parentContent = (id <OHHTMLPageContent>)[self superview];
        return [parentContent backgroundColor];
    }
}

- (void)relayout;
{
    OBASSERT(tableFlags.availableWidthValid);
    if (tableFlags.mungingFrames)
        return;

    // Let our superview know that we need to be laid out again.
    [nonretainedContainingTextCell relayoutCell];
}

- (void)cachedWidthsAreInvalid;
{
    if (tableFlags.mungingFrames)
        return;
    if (!tableFlags.columnSeparationsValid)
        return;
    tableFlags.columnSeparationsValid = NO;
    tableFlags.widthsValid = NO;
    tableFlags.displayWidthValid = NO;

    if (OHTableDebug >= 9)
        [self debugFlags];

    [self scheduleRelayout];
}

- (void)scheduleRelayout;
{
    [self relayout];
}


// OASearchableContent protocol

- (BOOL)findString:(NSString *)textPattern ignoreCase:(BOOL)ignoreCase backwards:(BOOL)backwards ignoreSelection:(BOOL)ignoreSelection;
{
    NSView <OASearchableContent> *searchableContentView;
    unsigned int dataCellIndex;
    unsigned int cellCount;

    if (ignoreSelection)
        dataCellIndex = NSNotFound;
    else {
        [self findSelectionInSubviews];
        dataCellIndex = selectionHoldingCellIndexHint;
    }
    
    cellCount = [dataCellOwners count];
    if (cellCount == 0)
        return NO;

    if (dataCellIndex == NSNotFound)
        dataCellIndex = backwards ? cellCount - 1 : 0;
    
    do {
        searchableContentView = [[dataCellOwners objectAtIndex:dataCellIndex] htmlView];
        if ([searchableContentView findString:textPattern ignoreCase:ignoreCase backwards:backwards ignoreSelection:ignoreSelection || (dataCellIndex != selectionHoldingCellIndexHint)]) {
            selectionHoldingCellIndexHint = dataCellIndex;
            return YES;
        }
    } while (backwards ? (dataCellIndex-- > 0) : (++dataCellIndex < cellCount));

    // Did not find the string in this table
    return NO;
}


// Informal OmniFindControllerAware protocol

- (id <OAFindControllerTarget>)omniFindControllerTarget;
{
    return [[self superview] omniFindControllerTarget];
}

@end

@implementation OHTable (Private)

// Table layout

- (void)doLayout;
{
    if (!tableFlags.subviewsPositioned || !tableFlags.rowLayoutValid || !tableFlags.columnLayoutValid || !tableFlags.widthsValid)
        [self _positionTableCells];
}

- (unsigned int)requestedDisplayWidth;
{
    unsigned int requestedDisplayWidth;

    OBASSERT(tableFlags.availableWidthValid);

    [self calculateWidthRange];

    if (!tableFlags.haveRequestedWidth)
        return NSNotFound;

    requestedDisplayWidth = ceil(tableFlags.requestedWidthIsFraction ? requestedWidth * availableWidth : requestedWidth);

    if (requestedDisplayWidth < widthRange.minimum) {
        if (!tableFlags.ignoreRequestedColumnWidths) {
            // Netscape throws away all requested widths whenever the requested width is smaller than the minimum usable width.  I think.
            if (OHTableDebug >= 1)
                NSLog(@"%@ Scaling down column widths (%d) to make this table fit in requested width (%d).", [self shortDescription], widthRange.minimum, requestedDisplayWidth);
            tableFlags.widthsValid = NO;
            tableFlags.columnSeparationsValid = NO;
            tableFlags.ignoreRequestedColumnWidths = YES;

            if (OHTableDebug >= 9)
                [self debugFlags];

            return [self requestedDisplayWidth];
        } else {
            // Already threw away all the requested widths, there's still no room.  We'll make ourselves just big enough to hold our content.
            if (OHTableDebug >= 1)
                NSLog(@"%@ Resizing to the minimum width allowed by columns (%d).", [self shortDescription], widthRange.minimum);
            return widthRange.minimum;
        }
    }

    if (requestedDisplayWidth < 2) {
#warning How small can you force tables to be in Netscape?
        if (OHTableDebug >= 1)
            NSLog(@"%@ Table's target width is %d, enforcing minimum width of 2 pixels", [self shortDescription], requestedDisplayWidth);
        // And does it force them to be as small as it can make them when you do so
        return 2;

        // or does it just give up and decide you're crazy?
        // return NSNotFound;
    }

    return requestedDisplayWidth;
}

- (void)calculateWidthRange;
{
    int columnIndex;

    if (tableFlags.widthsValid)
        return;

    [self calculateColumnSeparations];

    [self setupColumnLayoutWithSeparators];
    for (columnIndex = 1; columnIndex <= columnCount; columnIndex++)
        ASSERT_WIDTHRANGE_IS_VALID(columnLayout[columnIndex].positionRange);

    [self applyColumnSeparationsToColumnLayout];
    for (columnIndex = 1; columnIndex <= columnCount; columnIndex++)
        ASSERT_WIDTHRANGE_IS_VALID(columnLayout[columnIndex].positionRange);

    widthRange = OHAddWidthToWidthRange(columnLayout[columnCount].separatorWidth, columnLayout[columnCount].positionRange);
    tableFlags.widthsValid = YES;
    tableFlags.columnLayoutValid = NO;

    if (OHTableDebug >= 9)
        [self debugFlags];
}

- (void)calculateColumnSeparations;
{
    unsigned int cellOwnerIndex, cellOwnerCount;
    OHTableCellBorders cellBorders;

    if (tableFlags.columnSeparationsValid) {
        OBASSERT(columnSeparations && [columnSeparations columnCount] == columnCount);
        return;
    }

    if (!columnSeparations) {
        columnSeparations = [[OHColumnSeparations allocWithZone:[self zone]] initWithColumnCount:columnCount];
    } else {
        [columnSeparations resetSeparationsWithColumnCount:columnCount];
    }

    cellOwnerCount = [dataCellOwners count];
    for (cellOwnerIndex = 0; cellOwnerIndex < cellOwnerCount; cellOwnerIndex++) {
        OHTableCellHTMLView *tableCell;
        OHWidthRange cellWidthRange;
        tableCell = (OHTableCellHTMLView *)[[dataCellOwners objectAtIndex:cellOwnerIndex] htmlView];

        // Fix up caption cells
        if ([tableCell cellType] == CellType_Caption) {
            [tableCell setColumnIndex:0];
            [tableCell setColumnSpan:columnCount];
            [tableCell setRowIndex:0];
            [tableCell setRowSpan:0];
        }

        cellBorders = [tableCell borders];
        if (cellBorders.right > columnCount) {
            if (OHTableDebug >= 1)
                NSLog(@"%@ Table cell has column span larger than the number of remaining columns", [self shortDescription]);
            [tableCell setColumnSpan:columnCount - cellBorders.left];
            cellBorders.right = columnCount;
            OBASSERT([tableCell borders].right == cellBorders.right);
        }
        if (cellBorders.left >= cellBorders.right)
            break;

        // Calculate the column-based
        cellWidthRange = [tableCell widthRange];
        if (OHTableDebug >= 5)
            NSLog(@"%@ Cell %@ pos=(%d,%d,%d,%d) widthRange=%@", [self shortDescription], [tableCell shortDescription], cellBorders.left, cellBorders.right, cellBorders.top, cellBorders.bottom, OHStringFromWidthRange(cellWidthRange));
        OHColumnSeparationsEnsureMinimumSeparationWidthRangeBetweenColumns (columnSeparations, cellWidthRange, cellBorders.left, cellBorders.right);
    }

    if (!tableFlags.ignoreRequestedColumnWidths)
        [self applyRequestedWidthsToSeparations];

    if (OHTableDebug >= 3)
        NSLog(@"%@ columnSeparations = %@", [self shortDescription], columnSeparations);

    tableFlags.columnSeparationsValid = YES;

    if (OHTableDebug >= 9)
        [self debugFlags];
}

- (void)setupColumnLayoutWithSeparators;
{
    OHWidthRange zeroWidthRange;
    unsigned int columnIndex;
    OFZone *myZone = [OFZone zoneForObject:self];

    zeroWidthRange = OHMakeWidthRange(0, 0);

    // Initialize the columnLayout array, setting separatorWidth
    if (columnLayout)
        NSZoneFree([myZone nsZone], columnLayout);
    columnLayout = NSZoneMalloc([myZone nsZone], (columnCount + 1) * sizeof(*columnLayout));

    for (columnIndex = 0; columnIndex <= columnCount; columnIndex++) {
        columnLayout[columnIndex].positionRange = zeroWidthRange;
        columnLayout[columnIndex].separatorWidth = cellSpacing;
        if (columnIndex == 0 || columnIndex == columnCount) {
            if (borderBits & OWTBorderFrame)
                columnLayout[columnIndex].separatorWidth += borderWidth;
            if ((borderBits & OWTBorderColumns) || borderWidth > 0)
                columnLayout[columnIndex].separatorWidth += internalBorderWidth;
        } else if ((borderBits & OWTBorderColumns) || borderWidth > 0) {
            columnLayout[columnIndex].separatorWidth += internalBorderWidth + internalBorderWidth;
        }
        columnLayout[columnIndex].placement = 0;
    }
}

- (void)applyRequestedWidthsToSeparations;
{
    unsigned int columnIndex, columnRequestsCount;

    columnRequestsCount = [columnRequests count];
    for (columnIndex = 0; columnIndex < columnRequestsCount; columnIndex++) {
        OHTableColumnWidthRequest *columnRequest;

        columnRequest = [columnRequests objectAtIndex:columnIndex];
        if (!columnRequest)
            continue;
        if ([columnRequest hasFixedWidth]) {
            OHColumnSeparationsRequestWidthBetweenColumns(columnSeparations, [columnRequest fixedWidth], columnIndex, columnIndex + 1);
        }
    }
}

- (void)applyColumnSeparationsToColumnLayout;
{
    int leftIndex, rightIndex, columnIndex;

    // Go through and heuristically assign widths to the individual columns, this also takes into account the column separator widths assigned previously.
    for (rightIndex = 1; rightIndex <= columnCount; rightIndex++) {
        for (leftIndex = rightIndex - 1; leftIndex >= 0; leftIndex--) {
            unsigned int newMaximum, newMinimum;
            unsigned int totalWidthOfSeparatorsBetweenColumns;

            totalWidthOfSeparatorsBetweenColumns = 0;
            for (columnIndex = leftIndex; columnIndex < rightIndex; columnIndex++)
                totalWidthOfSeparatorsBetweenColumns += columnLayout[columnIndex].separatorWidth;

            // These five lines are just like AccumMax(), but they also proportionally adjust all the intermediate column separators.
            newMaximum = columnLayout[leftIndex].positionRange.maximum + OHColumnSeparationsMaximumWidthBetweenColumns(columnSeparations, leftIndex, rightIndex) + totalWidthOfSeparatorsBetweenColumns;
            if (newMaximum > columnLayout[rightIndex].positionRange.maximum) {
                float amountToAddToMaximum;

                amountToAddToMaximum = newMaximum - columnLayout[rightIndex].positionRange.maximum;
                for (columnIndex = leftIndex + 1; columnIndex <= rightIndex; columnIndex++) {
                    float percentageThroughColumnRange;

                    percentageThroughColumnRange = (float)(columnIndex - leftIndex) / (float)(rightIndex - leftIndex);
                    columnLayout[columnIndex].positionRange.maximum += amountToAddToMaximum * percentageThroughColumnRange;
                    ASSERT_WIDTHRANGE_IS_VALID(columnLayout[columnIndex].positionRange);
                }
                OBASSERT(columnLayout[rightIndex].positionRange.maximum == newMaximum);
            }
            newMinimum = columnLayout[leftIndex].positionRange.minimum + OHColumnSeparationsMinimumWidthBetweenColumns(columnSeparations, leftIndex, rightIndex) + totalWidthOfSeparatorsBetweenColumns;
            OBASSERT(newMinimum <= newMaximum);
            if (newMinimum > columnLayout[rightIndex].positionRange.minimum) {
                float amountToAddToMinimum;

                amountToAddToMinimum = newMinimum - columnLayout[rightIndex].positionRange.minimum;
                for (columnIndex = leftIndex + 1; columnIndex <= rightIndex; columnIndex++) {
                    float percentageThroughColumnRange;

                    percentageThroughColumnRange = (float)(columnIndex - leftIndex) / (float)(rightIndex - leftIndex);
                    columnLayout[columnIndex].positionRange.minimum += amountToAddToMinimum * percentageThroughColumnRange;
                    if (columnLayout[columnIndex].positionRange.minimum > columnLayout[columnIndex].positionRange.maximum) {
                        // Rounding inconsistency
                        columnLayout[columnIndex].positionRange.minimum = columnLayout[columnIndex].positionRange.maximum;
                    }
                    ASSERT_WIDTHRANGE_IS_VALID(columnLayout[columnIndex].positionRange);
                }
                OBASSERT(columnLayout[rightIndex].positionRange.minimum == newMinimum);
            }

            if (OHTableDebug >= 7)
                NSLog(@"%@ %d. Col %d: positionRange=%@", [self shortDescription], leftIndex, rightIndex, OHStringFromWidthRange(columnLayout[rightIndex].positionRange));
        }
        if (OHTableDebug >= 6)
            for (leftIndex = 0; leftIndex <= rightIndex; leftIndex++)
                NSLog(@"%@ *%d Col %d: positionRange=%@", [self shortDescription], rightIndex, leftIndex, OHStringFromWidthRange(columnLayout[leftIndex].positionRange));
    }

}

// Updates the "placement" entries in the column layout array
- (void)_layColumnsForWidth:(unsigned int)totalWidth;
{
    unsigned int totalColumnWidth;

    // First cut: Only do simple columnar layout
    
    if (!tableFlags.widthsValid)
	[self calculateWidthRange];

    totalColumnWidth = totalWidth - columnLayout[columnCount].separatorWidth;
    switch (layout) {
    	case OWTL_WidthProportional:
            [self layColumnsUsingWidthProportionalLayoutForWidth: totalColumnWidth];
	    break;
    }

    if (OHTableDebug >= 5) {
        unsigned int columnIndex;

        for (columnIndex = 0; columnIndex <= columnCount; columnIndex++)
            NSLog(@"%@ Column %d: placement=%d", [self shortDescription], columnIndex, columnLayout[columnIndex].placement);
    }

    tableFlags.columnLayoutValid = YES;
    tableFlags.rowLayoutValid = NO;
    tableFlags.subviewsPositioned = NO;

    if (OHTableDebug >= 9)
        [self debugFlags];
}

- (void)layColumnsUsingWidthProportionalLayoutForWidth:(unsigned int)totalWidth;
{
    unsigned int columnIndex;

    if (totalWidth < columnLayout[columnCount].positionRange.minimum) {
        // Uh oh, impossible to fit in here, not enough room.  By hook or by crook, we need to get that space.  In the mean time, we'll just pretend we have it.
        if (OHTableDebug >= 1)
            NSLog(@"%@ Cannot fit table in provided width (%d), expanding to %d", [self shortDescription], totalWidth, columnLayout[columnCount].positionRange.minimum);
        totalWidth = columnLayout[columnCount].positionRange.minimum;
    }
    if (columnLayout[columnCount].positionRange.maximum != columnLayout[columnCount].positionRange.minimum) {
        float scaleFactor;

        scaleFactor = (float)(totalWidth - columnLayout[columnCount].positionRange.minimum) / (float)(columnLayout[columnCount].positionRange.maximum - columnLayout[columnCount].positionRange.minimum);
        for (columnIndex = 1; columnIndex <= columnCount; columnIndex++) {
            columnLayout[columnIndex].placement = columnLayout[columnIndex].positionRange.minimum + scaleFactor * (columnLayout[columnIndex].positionRange.maximum - columnLayout[columnIndex].positionRange.minimum);
        }
        if (OHTableDebug >= 5)
            NSLog(@"%@ totalWidth=%d, scaleFactor=%g", [self shortDescription], totalWidth, scaleFactor);
    } else {
        float scaleFactor;

        // We have more (or at least as much) space than we can use, and the minimum amount we can use is the same as the maximum, so there's no reason to be preferential to any of the columns.  So, do a simple scale operation on each column.

        OBASSERT(totalWidth >= columnLayout[columnCount].positionRange.maximum);
        scaleFactor = (float)totalWidth / (float)columnLayout[columnCount].positionRange.maximum;
        for (columnIndex = 1; columnIndex <= columnCount; columnIndex++) {
            columnLayout[columnIndex].placement = scaleFactor * columnLayout[columnIndex].positionRange.maximum;
        }
    }
}

- (void)_layRows;
{
    unsigned int rowCount;
    unsigned int cellOwnerIndex, cellOwnerCount;
    OHRowSeparations *rowSeparations;
    float verticalSeparation, delta;
    int rowIndex, topSep, bottomSep;
    OFZone *myZone;

    if (!tableFlags.widthsValid || !tableFlags.columnLayoutValid)
        [self _layColumnsForWidth:[self currentDisplayWidth]];

    rowCount = [dataCells rowCount];

    // 1. Allocate and zero out the row placement array

    myZone = [OFZone zoneForObject:self];
    if (rowLayout)
        NSZoneFree([myZone nsZone], rowLayout);
    rowLayout = NSZoneCalloc([myZone nsZone], rowCount + 1, sizeof(*rowLayout));

    rowLayout[0].placement = 0.0;
    for (rowIndex = 0; rowIndex <= rowCount; rowIndex++) {
        if (rowIndex > 0)
            rowLayout[rowIndex].placement = rowLayout[rowIndex - 1].placement + rowLayout[rowIndex - 1].height;

        rowLayout[rowIndex].height = cellSpacing;
        if ((borderBits & OWTBorderSections) &&
            (rowIndex == firstBodyRow || rowIndex == lastBodyRow + 1) &&
            (rowIndex != 0 && rowIndex != rowCount))
            rowLayout[rowIndex].height += 2.0 * borderWidth;
        else if (rowIndex == 0 || rowIndex == rowCount) {
            if (borderBits & OWTBorderFrame)
                rowLayout[rowIndex].height += borderWidth;
            if ((borderBits & OWTBorderRows) || borderWidth > 0)
                rowLayout[rowIndex].height += internalBorderWidth;
            }
        else if ((borderBits & OWTBorderRows) || borderWidth > 0)
            rowLayout[rowIndex].height += internalBorderWidth + internalBorderWidth;
    }

    // 2. Allocate and zero out the (temporary) row separation array
    rowSeparations = [[OHRowSeparations allocWithZone:[self zone]] initWithRowCount:rowCount];

    topMargin = 0.0;
    bottomMargin = 0.0;

    // 3. Go through the cells, finding the necessary row separations

    // Handle requested height (yet another undocumented Netscape tag)
    if (tableFlags.haveRequestedHeight && rowCount > 0 && (!tableFlags.requestedHeightIsRelative || visibleHeight > 0.0)) {
        float desiredHeight = requestedHeight;

        if (tableFlags.requestedHeightIsRelative)
            desiredHeight *= visibleHeight;

        desiredHeight -= rowLayout[rowCount].height;
        OHRowSeparationsEnsureDistanceBetweenRows(rowSeparations, desiredHeight, 0, rowCount);
    }

    cellOwnerCount = [dataCellOwners count];
    for (cellOwnerIndex = 0; cellOwnerIndex < cellOwnerCount; cellOwnerIndex++) {
        OHTableCellHTMLView *tableCell;
        OHTableCellBorders tableCellBorders;

        tableCell = (OHTableCellHTMLView *)[[dataCellOwners objectAtIndex:cellOwnerIndex] htmlView];

        // A caption cell.
        if ([tableCell cellType] == CellType_Caption) {
            tableFlags.mungingFrames = YES;
            if (OHTableDebug >= 9)
                [self debugFlags];

            [tableCell setWidth:displayWidth];
            verticalSeparation = [tableCell heightForCurrentWidth];

            tableFlags.mungingFrames = NO;
            if (OHTableDebug >= 9)
                [self debugFlags];

            if ([tableCell verticalAlignment] == OHTableCellVerticalAlignBottom)
                bottomMargin += captionOffset + verticalSeparation;
            else
                topMargin += captionOffset + verticalSeparation;
        } else {
            tableCellBorders = [tableCell borders];

            tableFlags.mungingFrames = YES;
            if (OHTableDebug >= 9)
                [self debugFlags];

            [tableCell setWidth:columnLayout[tableCellBorders.right].placement - (columnLayout[tableCellBorders.left].placement +  columnLayout[tableCellBorders.left].separatorWidth)];

            verticalSeparation = [tableCell heightForCurrentWidth];

            tableFlags.mungingFrames = NO;
            if (OHTableDebug >= 9)
                [self debugFlags];

            OHRowSeparationsEnsureDistanceBetweenRows(rowSeparations, verticalSeparation, tableCellBorders.top, tableCellBorders.bottom);
        }
    }

    // 4. Position the rows based on their separations
    for (bottomSep = 1; bottomSep <= rowCount; bottomSep++) {
        for (topSep = bottomSep - 1; topSep >= 0; topSep--) {
            delta = (int)OHRowSeparationsDistanceBetweenRows(rowSeparations, topSep, bottomSep) - ((int)rowLayout[bottomSep].placement - (int)(rowLayout[topSep].placement + rowLayout[topSep].height));
            if (delta > 0.0)
                for (rowIndex = topSep + 1; rowIndex <= bottomSep; rowIndex++)
                    rowLayout[rowIndex].placement = ceil(rowLayout[rowIndex].placement + delta * (double)(rowIndex - topSep) / (double)(bottomSep - topSep));
        }
    }

    // 5. Miscellaneous cleanup
    [rowSeparations release];

    if (OHTableDebug >= 5)
        for (rowIndex = 0; rowIndex <= rowCount; rowIndex++)
            NSLog(@"%@ Row %d: placement=%d", [self shortDescription], rowIndex, rowLayout[rowIndex].placement);

    tableFlags.rowLayoutValid = YES;
    tableFlags.subviewsPositioned = NO;
    if (OHTableDebug >= 9)
        [self debugFlags];

    [self setFrameSize:NSMakeSize(NSWidth([self bounds]), rowLayout[rowCount].placement + rowLayout[rowCount].height + topMargin + bottomMargin)];
//    [nonretainedContainingTextCell relayoutCell];
}

- (void)_positionTableCells;
{
    unsigned int cellOwnerIndex, cellOwnerCount, rowCount;
    float topMarginPosition, bottomMarginPosition;
    NSRect bounds;

    if (!tableFlags.widthsValid || !tableFlags.rowLayoutValid || !tableFlags.columnLayoutValid)
	[self _layRows];

    topMarginPosition = 0.0;
    rowCount = [dataCells rowCount];
    bottomMarginPosition = topMargin + rowLayout[rowCount].placement + rowLayout[rowCount].height;

    bounds = [self bounds];
    cellOwnerCount = [dataCellOwners count];
    for (cellOwnerIndex = 0; cellOwnerIndex < cellOwnerCount; cellOwnerIndex++) {
        OHTableCellHTMLView *tableCell;
        OHTableCellBorders tableCellBorders;
        float currentTableCellHeight;
	NSRect newTableCellFrame;
	
        tableCell = (OHTableCellHTMLView *)[[dataCellOwners objectAtIndex:cellOwnerIndex] htmlView];

	// Handle caption cells
        if ([tableCell cellType] == CellType_Caption) {
            newTableCellFrame.origin.x = NSMinX(bounds);
            newTableCellFrame.size.width = NSWidth(bounds);
            newTableCellFrame.size.height = [tableCell heightForCurrentWidth];
            switch ([tableCell verticalAlignment]) {
                case OHTableCellVerticalAlignTop:
                case OHTableCellVerticalAlignMiddle:
                case OHTableCellVerticalAlignBaseline:
                    newTableCellFrame.origin.y = topMarginPosition;
                    topMarginPosition = NSMaxY(newTableCellFrame) + captionOffset;
                    break;
                case OHTableCellVerticalAlignBottom:
                    newTableCellFrame.origin.y = captionOffset + bottomMarginPosition;
                    bottomMarginPosition = NSMaxY(newTableCellFrame);
                    break;
            }
            tableFlags.mungingFrames = YES;
            if (OHTableDebug >= 9)
                [self debugFlags];

            [tableCell setFrame:newTableCellFrame];
            [tableCell setNeedsDisplay:NO];

            tableFlags.mungingFrames = NO;
            if (OHTableDebug >= 9)
                [self debugFlags];

	    continue;
	}
	
        tableCellBorders = [tableCell borders];
        currentTableCellHeight = [tableCell heightForCurrentWidth];
        newTableCellFrame.origin.x = columnLayout[tableCellBorders.left].placement + columnLayout[tableCellBorders.left].separatorWidth;
        newTableCellFrame.origin.y = rowLayout[tableCellBorders.top].placement + rowLayout[tableCellBorders.top].height + topMargin;
        newTableCellFrame.size.width = columnLayout[tableCellBorders.right].placement - NSMinX(newTableCellFrame);
        newTableCellFrame.size.height = rowLayout[tableCellBorders.bottom].placement - rowLayout[tableCellBorders.top].placement - rowLayout[tableCellBorders.top].height;
        if (NSHeight(newTableCellFrame) > currentTableCellHeight) {
            float extraHeight;

            extraHeight = NSHeight(newTableCellFrame) - currentTableCellHeight;
            switch ([tableCell verticalAlignment]) {
	        case OHTableCellVerticalAlignTop:
                    break;
                case OHTableCellVerticalAlignBaseline:
                    // TODO: Implement baseline
		    break;
		case OHTableCellVerticalAlignMiddle:
                    newTableCellFrame.origin.y += floor(0.5 * extraHeight);
		    break;
		case OHTableCellVerticalAlignBottom:
                    newTableCellFrame.origin.y += extraHeight;
		    break;
	    }
            newTableCellFrame.size.height = currentTableCellHeight;
	}
	if (OHTableDebug >= 7)
            NSLog(@"%@ [tableCell %@: tableCellBorders=(%d, %d, %d, %d) positionedFrame=%@", [self shortDescription], [tableCell shortDescription], tableCellBorders.left, tableCellBorders.right, tableCellBorders.top, tableCellBorders.bottom, NSStringFromRect(newTableCellFrame));
        tableFlags.mungingFrames = YES;
        if (OHTableDebug >= 9)
            [self debugFlags];

        [tableCell setFrame:newTableCellFrame];
        [tableCell setNeedsDisplay:NO];
        tableFlags.mungingFrames = NO;
        if (OHTableDebug >= 9)
            [self debugFlags];

    }

    tableFlags.subviewsPositioned = YES;
    if (OHTableDebug >= 9)
        [self debugFlags];
}

// Drawing

- (void)drawPlainBordersForRect:(NSRect)rect;
{
    NSRect fillRect;
    OHTableCellHTMLView *lastCell, *thisCell;
    unsigned int rowIndex, rowCount, columnIndex;

    [self drawTableCellBackgroundForRect:rect withBorders:NO];

    [[NSColor controlShadowColor] set];
    rowCount = [dataCells rowCount];
    for (rowIndex = 0; rowIndex < rowCount; rowIndex++) {
        lastCell = nil;
        for (columnIndex = 0; columnIndex <= columnCount; columnIndex++) {

            if ((columnIndex == 0 || columnIndex == columnCount) &&
                !(borderBits & OWTBorderFrame))
                continue;
            if (!(columnIndex == 0 || columnIndex == columnCount) &&
                !(borderBits & OWTBorderColumns))
                continue;

            thisCell = (columnIndex < columnCount) ? [self cellAtRow:rowIndex column:columnIndex] : nil;
            if ([thisCell isEmpty])
                thisCell = nil;
            if (thisCell != lastCell) {
                if (columnLayout[columnIndex].separatorWidth < 0.5) continue;
                fillRect.size.width = columnLayout[columnIndex].separatorWidth;
                fillRect.origin.x = columnLayout[columnIndex].placement;
                fillRect.origin.y = topMargin + rowLayout[rowIndex].placement;
                fillRect.size.height = rowLayout[rowIndex + 1].height +
                    rowLayout[rowIndex + 1].placement -
                    rowLayout[rowIndex].placement;
                if (!NSIsEmptyRect(NSIntersectionRect(fillRect, rect)))
                    NSRectFill(fillRect);
            }
            lastCell = thisCell;
        }
    }

    for (columnIndex = 0; columnIndex < columnCount; columnIndex++) {
        lastCell = nil;
        for (rowIndex = 0; rowIndex <= rowCount; rowIndex++) {
            if ((rowIndex == 0 || rowIndex == rowCount) && !(borderBits & OWTBorderFrame))
                continue;
            if (!(rowIndex == 0 || rowIndex == rowCount) && !(borderBits & OWTBorderRows))
                continue;

            thisCell = rowIndex < rowCount ? [self cellAtRow:rowIndex column:columnIndex] : nil;
            if ([thisCell isEmpty])
                thisCell = nil;
            if (thisCell != lastCell) {
                if (rowLayout[rowIndex].height < 0.5)
                    continue;
                fillRect.size.height = rowLayout[rowIndex].height;
                fillRect.origin.y = topMargin + rowLayout[rowIndex].placement;
                fillRect.origin.x = columnLayout[columnIndex].placement;
                fillRect.size.width = columnLayout[columnIndex + 1].placement + columnLayout[columnIndex + 1].separatorWidth - NSMinX(fillRect);
                if (!NSIsEmptyRect(NSIntersectionRect(fillRect, rect)))
                    NSRectFill(fillRect);
            }
            lastCell = thisCell;
        }
    }
}

- (void)draw3DBordersForRect:(NSRect)rect;
{
    float inset;

    // Do nifty 3-D borders

    [self drawTableCellBackgroundForRect:rect withBorders:YES];

#define BEZEL_STEP 1.0
    [[NSColor controlLightHighlightColor] set];
    for (inset = 0.0; inset < borderWidth; inset += BEZEL_STEP) {
        PSrectfill(NSMinX([self bounds]) + inset, NSMinY([self bounds]) + topMargin + inset, BEZEL_STEP, NSHeight([self bounds]) - 2.0 * inset - topMargin - bottomMargin);
        PSrectfill(NSMinX([self bounds]) + inset, NSMinY([self bounds]) + topMargin + inset, NSWidth([self bounds]) - 2.0 * inset, BEZEL_STEP);
    }

    [[NSColor controlShadowColor] set];
    for (inset = 0.0; inset < borderWidth; inset ++) {
        PSrectfill(NSMinX([self bounds]) + NSWidth([self bounds]) - inset - BEZEL_STEP, NSMinY([self bounds]) + inset + topMargin, BEZEL_STEP, NSHeight([self bounds]) - 2.0 * inset - topMargin - bottomMargin);
        PSrectfill(NSMinX([self bounds]) + inset, floor(NSMinY([self bounds]) + NSHeight([self bounds]) - inset - BEZEL_STEP - bottomMargin), NSWidth([self bounds]) - 2.0 * inset, BEZEL_STEP);
    }
}

- (void)drawTableCellBackgroundForRect:(NSRect)rect withBorders:(BOOL)shouldDrawBorders;
{
    unsigned int cellIndex, cellCount;

    cellCount = [dataCellOwners count];
    for (cellIndex = 0; cellIndex < cellCount; cellIndex++) {
	OHTableCellHTMLView *tableCell;
	NSRect cellFrame, cellUpdateRect;

        tableCell = (OHTableCellHTMLView *)[[dataCellOwners objectAtIndex:cellIndex] htmlView];

        if ([tableCell cellType] == CellType_Caption || [tableCell isEmpty])
	    continue;

        cellFrame = [self cellFrameForTableCell:tableCell];
        cellUpdateRect = NSIntersectionRect(rect, cellFrame);
        if (NSIsEmptyRect(cellUpdateRect))
	    continue;

        if ([tableCell usesOwnBackgroundColor]) {
            // cellUpdateRect is larger than the cell (e.g., when the cell is centered within the cellUpdateRect), but the cell's background color should fill the whole region.
            [tableCell drawBackgroundForRect:cellUpdateRect backgroundOrigin:cellFrame.origin];
        }
        
	if (shouldDrawBorders) {
            float left, right, top, bottom, width, height;

            left = NSMinX(cellFrame);
            right = NSMaxX(cellFrame);
            top = NSMinY(cellFrame);
            bottom = NSMaxY(cellFrame);
            width = NSWidth(cellFrame);
            height = NSHeight(cellFrame);

#if defined(RHAPSODY) && (OBOperatingSystemMajorVersion > 5)
            if ([[NSGraphicsContext currentContext] isDrawingToScreen]) {
#else
            if ([[NSDPSContext currentContext] isDrawingToScreen]) {
#endif
                [[NSColor grayColor] set];

                // Top
                PScompositerect(left - internalBorderWidth, top - internalBorderWidth, width + 2.0 * internalBorderWidth, internalBorderWidth, NSCompositePlusDarker);
                // Left doesn't overdraw top
                PScompositerect(left - internalBorderWidth, top, internalBorderWidth, height + internalBorderWidth, NSCompositePlusDarker);

                // Right doesn't overdraw top or bottom
                PScompositerect(right, top, internalBorderWidth, height, NSCompositePlusLighter);
                // Bottom doesn't overdraw left
                PScompositerect(left, bottom, width + internalBorderWidth, internalBorderWidth, NSCompositePlusLighter);

            } else {
                // Printing or copying, so don't use composite

                [[NSColor controlShadowColor] set];
                // Top
                PSrectfill(left - internalBorderWidth, top - internalBorderWidth, width + 2.0 * internalBorderWidth, internalBorderWidth);
                // Left doesn't overdraw top
                PSrectfill(left, top, internalBorderWidth, height + internalBorderWidth);

                [[NSColor controlHighlightColor] set];
                // Right doesn't overdraw top or bottom
                PSrectfill(right, top, internalBorderWidth, height);
                // Bottom doesn't overdraw left
                PSrectfill(left, bottom, width + internalBorderWidth, internalBorderWidth);
            }
        }
    }
}

/*
- (void)tableCellFrameChanged:(NSNotification *)notification;
{
    [self cachedWidthsAreInvalid];
}
*/


// Find helpers

- (void)findSelectionInSubviews;
{
    NSView *firstResponder;
    unsigned int indexToTest;
    unsigned int dataCellIndex, dataCellCount;

    indexToTest = selectionHoldingCellIndexHint;
    selectionHoldingCellIndexHint = NSNotFound;
    
    firstResponder = (NSView *)[[self window] firstResponder];

    // There is a selection in this window, but it isn't in one of our children, so bail
    if (![firstResponder isKindOfClass:[NSView class]] || ![firstResponder isDescendantOf:self])
        return;

    // We think we might know which cell contains the selection, but we have to check
    if (indexToTest != NSNotFound) {
        if ([self checkIfFirstResponder:firstResponder isInSearchableContentViewAtDataCellIndex:indexToTest])
            return; // Yup, the selection is still there
    }

    // We aren't sure which cell contains the selection, so we run through our cells and ask each of 'em the slow way.  Note that this isn't efficient, but it ONLY happens after the user has manually changed the selection (after a find we update the hint index ourselves), so it's just not that common.
    dataCellCount = [dataCellOwners count];
    for (dataCellIndex = 0; dataCellIndex < dataCellCount; dataCellIndex++) {
        if ([self checkIfFirstResponder:firstResponder isInSearchableContentViewAtDataCellIndex:dataCellIndex])
            return; // selectionHoldingCellIndexHint will have been set correctly
    }
}

- (BOOL)checkIfFirstResponder:(NSView *)firstResponder isInSearchableContentViewAtDataCellIndex:(unsigned int)dataCellIndex;
{
    NSView <OASearchableContent> *searchableContentView;

    searchableContentView = [[dataCellOwners objectAtIndex:dataCellIndex] htmlView];
    if (searchableContentView && [firstResponder isDescendantOf:searchableContentView]) {
        selectionHoldingCellIndexHint = dataCellIndex;
        return YES;
    }
    return NO;
}


// Debugging methods

- (NSArray *)flagsDebugArray;
{
    NSMutableArray *flags = [NSMutableArray array];

#define MapFlag(flag, flagname) \
    if (tableFlags.flag) [flags addObject:flagname];

    MapFlag(widthsValid, @"widthsValid");
    MapFlag(columnLayoutValid, @"columnLayoutValid");
    MapFlag(rowLayoutValid, @"rowLayoutValid");
    MapFlag(subviewsPositioned, @"subviewsPositioned");
    MapFlag(displayWidthValid, @"displayWidthValid");
    MapFlag(mungingFrames, @"mungingFrames");
#undef MapFlag

    return flags;
}

- (void)debugFlags;
{
    NSLog(@"%@ tableFlags=%@", OBShortObjectDescription(self), [self flagsDebugArray]);
}

- (NSMutableDictionary *)debugDictionary;
{
    NSMutableDictionary *debugDictionary;

    debugDictionary = [super debugDictionary];

    // We list our subviews in our dataCells array
    [debugDictionary removeObjectForKey:@"subviews"];

    if (dataCells)
        [debugDictionary setObject:dataCells forKey:@"dataCells"];
    if (nonretainedContainingTextCell)
        [debugDictionary setObject:OBShortObjectDescription(nonretainedContainingTextCell) forKey:@"nonretainedContainingTextCell"];
    if (backgroundColor)
        [debugDictionary setObject:backgroundColor forKey:@"backgroundColor"];

    [debugDictionary setObject:[NSNumber numberWithDouble:displayWidth] forKey:@"displayWidth"];

    [debugDictionary setObject:[self flagsDebugArray] forKey:@"tableFlags"];

    if (tableFlags.haveRequestedWidth) {
        if (tableFlags.requestedWidthIsFraction)
            [debugDictionary setObject:[NSString stringWithFormat:@"%g%%", 100.0 * requestedWidth] forKey:@"requestedWidth"];
        else
            [debugDictionary setObject:[NSNumber numberWithDouble:requestedWidth] forKey:@"requestedWidth"];
    }

    if (tableFlags.haveRequestedHeight) {
        if (tableFlags.requestedHeightIsRelative)
            [debugDictionary setObject:[NSString stringWithFormat:@"%g%%", 100.0 * requestedHeight] forKey:@"requestedHeight"];
        else
            [debugDictionary setObject:[NSNumber numberWithDouble:requestedHeight] forKey:@"requestedHeight"];
    }

    return debugDictionary;
}


@end
