diff --git a/ChangeLog b/ChangeLog index ff81e1c66..6f38dec11 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,32 @@ +2009-12-17 Wolfgang Lux + + * Source/NSCell.m (-_setupTextWithFrame:inView:editor:delegate:range): + * Source/NSCell.m (-endEditing:): + Properly set up the field editor and its text container to handle + cells whose text is wrapped, cells which truncate their text, and + cells with scrollable contents, respectively, and prepare the + field editor and its enclosing clip view to be resizable. Use a + clip view only when necessary. + * Source/NSCell.m (-_drawEditorWithFrame:inView:): Auxiliary + method to update the frame of a cell's editor when the cell has + been resized or moved. + * Source/NSCell.m (-_setInEditing:): Helper method that allows + control views to flag their edited cell. + + * Source/NSCell.m (-drawInteriorWithFrame:inView): + * Source/NSTextFieldCell.m (-drawInteriorWithFrame:inView): Do not + draw the interior of an edited cell. + + * Source/NSTableView.m (-moveColumn:toColumn:): Update the column + index of the edited cell if necessary. + + * Source/NSControl.m (-drawRect:): + * Source/NSTableView.m (-drawRow:clipRect:): + * Source/NSOutlineView.m (-drawRow:clipRect): Remove obsolete + workarounds to prevent an edited cell from being drawn twice. + + The preceding changes also should fix #22678. + 2009-12-16 Wolfgang Lux * Source/NSTextFieldCell.m (-drawInteriorWithFrame:inView:): diff --git a/Headers/AppKit/NSCell.h b/Headers/AppKit/NSCell.h index d6d054e43..0012939b1 100644 --- a/Headers/AppKit/NSCell.h +++ b/Headers/AppKit/NSCell.h @@ -491,6 +491,9 @@ enum { inView: (NSView*)controlView; - (void) _drawFocusRingWithFrame: (NSRect)cellFrame inView: (NSView*)controlView; +- (void) _drawEditorWithFrame: (NSRect)cellFrame + inView: (NSView*)controlView; +- (void) _setInEditing: (BOOL)flag; - (void) _updateFieldEditor: (NSText*)textObject; @end diff --git a/Source/NSCell.m b/Source/NSCell.m index d6263dde3..45033c643 100644 --- a/Source/NSCell.m +++ b/Source/NSCell.m @@ -1950,23 +1950,26 @@ static NSColor *dtxtCol; */ - (void) drawInteriorWithFrame: (NSRect)cellFrame inView: (NSView*)controlView { - cellFrame = [self drawingRectForBounds: cellFrame]; + NSRect drawingRect = [self drawingRectForBounds: cellFrame]; //FIXME: Check if this is also neccessary for images, // Add spacing between border and inside if (_cell.is_bordered || _cell.is_bezeled) { - cellFrame.origin.x += 3; - cellFrame.size.width -= 6; - cellFrame.origin.y += 1; - cellFrame.size.height -= 2; + drawingRect.origin.x += 3; + drawingRect.size.width -= 6; + drawingRect.origin.y += 1; + drawingRect.size.height -= 2; } switch (_cell.type) { case NSTextCellType: - [self _drawAttributedText: [self _drawAttributedString] - inFrame: cellFrame]; + if (_cell.in_editing) + [self _drawEditorWithFrame: cellFrame inView: controlView]; + else + [self _drawAttributedText: [self _drawAttributedString] + inFrame: drawingRect]; break; case NSImageCellType: @@ -1976,8 +1979,8 @@ static NSColor *dtxtCol; NSPoint position; size = [_cell_image size]; - position.x = MAX(NSMidX(cellFrame) - (size.width/2.),0.); - position.y = MAX(NSMidY(cellFrame) - (size.height/2.),0.); + position.x = MAX(NSMidX(drawingRect) - (size.width/2.),0.); + position.y = MAX(NSMidY(drawingRect) - (size.height/2.),0.); /* * Images are always drawn with their bottom-left corner * at the origin so we must adjust the position to take @@ -2105,39 +2108,68 @@ static NSColor *dtxtCol; delegate: (id)anObject range: (NSRange)selection { + BOOL needsClipView; + BOOL wraps = [self wraps]; + NSTextContainer *ct; + NSSize maxSize; NSRect titleRect = [self titleRectForBounds: aRect]; - NSClipView *cv = [[NSClipView alloc] initWithFrame: titleRect]; - NSTextContainer *ct = [(NSTextView*)textObject textContainer]; - NSRect maxRect; - // A clip view should is only created for scrollable text - if ([self isScrollable]) + /* We always add a clip view if the cell is editable so that the user + can edit the whole contents even if the cell's contents normally is + clipped. */ + needsClipView = [self isScrollable] || [self isEditable]; + if (needsClipView) { - /* See comments in NSStringDrawing.m about the choice of maximum size. */ - maxRect = NSMakeRect(0, 0, 1e6, titleRect.size.height); + NSClipView *cv; + + cv = [[NSClipView alloc] initWithFrame: titleRect]; + [cv setDocumentView: textObject]; + [controlView addSubview: cv]; + RELEASE(cv); } else - { - maxRect = NSMakeRect(0, 0, titleRect.size.width, titleRect.size.height); - } + [controlView addSubview: textObject]; - [controlView addSubview: cv]; - RELEASE(cv); - [cv setAutoresizesSubviews: NO]; - [cv setDocumentView: textObject]; - [ct setContainerSize: maxRect.size]; + /* Note: The order of statements matters here. We must set the text object's + horizontallyResizable and verticallyResizable attributes before setting + its frame size. Otherwise, the text object's width and/or height might + incorrectly be reduced to zero (since the text object has no contents at + this point) if the text object was resizable by its text container before. + Of course we could also have set the text object's minimum size to the + intended frame size, but then we must update the minimum size whenever + the field editor's frame is changed in -_drawEditorWithFrame:inView:. + Note that the minimum size is not relevant when a clip view is used. */ + [textObject setMinSize: NSZeroSize]; + [textObject setMaxSize: NSMakeSize(1e6, 1e6)]; + [textObject setHorizontallyResizable: needsClipView && !wraps]; + [textObject setVerticallyResizable: needsClipView]; + [textObject setFrame: titleRect]; + if (needsClipView) + [textObject setAutoresizingMask: NSViewWidthSizable + NSViewHeightSizable]; + else + [textObject setAutoresizingMask: NSViewNotSizable]; + + /* Note: Order of statements matters again. The heightTracksTextView + and widthTracksTextView container attributes must be set after setting + the horizontallyResizable and verticallyResizable text view attributes + because NSTextView's setter methods include "safety code", which + always updates the container attributes along with the text view + attributes. + FIXME Fix NSTextView to only reset the text container attributes, but + never set them. However note that this may break some sloppily written + code which forgets to set the text container attributes. */ + /* See comments in NSStringDrawing.m about the choice of maximum size. */ + ct = [(NSTextView*)textObject textContainer]; + if (wraps) + maxSize = NSMakeSize(NSWidth(titleRect), 1e6); + else + maxSize = NSMakeSize(1e6, 1e6); + [ct setContainerSize: maxSize]; + [ct setWidthTracksTextView: wraps]; [ct setHeightTracksTextView: NO]; - [ct setWidthTracksTextView: NO]; - - [textObject setFrame: maxRect]; - [textObject setHorizontallyResizable: NO]; - [textObject setVerticallyResizable: NO]; - [textObject setMaxSize: maxRect.size]; - [textObject setMinSize: titleRect.size]; [self _updateFieldEditor: textObject]; - [textObject sizeToFit]; [textObject setSelectedRange: selection]; [textObject scrollRangeToVisible: selection]; @@ -2159,8 +2191,13 @@ static NSColor *dtxtCol; [textObject setDelegate: nil]; clipView = (NSClipView*)[textObject superview]; - [textObject removeFromSuperview]; - [clipView removeFromSuperview]; + if ([clipView isKindOfClass: [NSClipView class]]) + { + [clipView setDocumentView: nil]; + [clipView removeFromSuperview]; + } + else + [textObject removeFromSuperview]; } /* @@ -2854,11 +2891,36 @@ static NSColor *dtxtCol; } } +- (void) _drawEditorWithFrame: (NSRect)cellFrame + inView: (NSView *)controlView +{ + /* Look Ma', no drawing here... */ + + /* Adjust the text editor's frame to match cell's frame (minus the border) */ + NSRect titleRect = [self titleRectForBounds: cellFrame]; + NSText *textObject = [(NSControl*)controlView currentEditor]; + NSView *clipView = [textObject superview]; + + if ([clipView isKindOfClass: [NSClipView class]]) + { + [clipView setFrame: titleRect]; + } + else + { + [textObject setFrame: titleRect]; + } +} + - (BOOL) _sendsActionOn:(int)eventTypeMask { return (_action_mask & eventTypeMask); } +- (void) _setInEditing: (BOOL)flag +{ + _cell.in_editing = flag; +} + - (void) _updateFieldEditor: (NSText*)textObject { if (_formatter != nil) diff --git a/Source/NSControl.m b/Source/NSControl.m index d5b27475d..b6f331cba 100644 --- a/Source/NSControl.m +++ b/Source/NSControl.m @@ -625,23 +625,6 @@ static NSNotificationCenter *nc; - (void) drawRect: (NSRect)aRect { - /* Do nothing if there is already a text editor doing the drawing; - * otherwise, we draw everything twice. That is bad if there are - * any transparency involved (eg, even an anti-alias font!) because - * if the semi-transparent pixels are drawn over themselves they - * become less transparent (eg, an anti-alias font becomes darker - * and gives the impression of being bold). - */ - if ([self currentEditor] != nil) - { - /* Make sure the cell's control view is always set up. - * FIXME Need to draw the cell's border which is not covered by - * the field editor if the cell is bordered. - */ - [_cell setControlView: self]; - return; - } - [self drawCell: _cell]; } diff --git a/Source/NSOutlineView.m b/Source/NSOutlineView.m index 67044f1c0..240fb10c0 100644 --- a/Source/NSOutlineView.m +++ b/Source/NSOutlineView.m @@ -867,88 +867,89 @@ static NSImage *unexpandable = nil; /* Draw the row between startingColumn and endingColumn */ for (i = startingColumn; i <= endingColumn; i++) { - if (i != _editedColumn || rowIndex != _editedRow) + id item = [self itemAtRow: rowIndex]; + + tb = [_tableColumns objectAtIndex: i]; + cell = [tb dataCellForRow: rowIndex]; + if (i == _editedColumn && rowIndex == _editedRow) + [cell _setInEditing: YES]; + [self _willDisplayCell: cell + forTableColumn: tb + row: rowIndex]; + [cell setObjectValue: [_dataSource outlineView: self + objectValueForTableColumn: tb + byItem: item]]; + drawingRect = [self frameOfCellAtColumn: i + row: rowIndex]; + + if (tb == _outlineTableColumn) { - id item = [self itemAtRow: rowIndex]; + NSImage *image = nil; + int level = 0; + float indentationFactor = 0.0; + // float originalWidth = drawingRect.size.width; - tb = [_tableColumns objectAtIndex: i]; - cell = [tb dataCellForRow: rowIndex]; - [self _willDisplayCell: cell - forTableColumn: tb - row: rowIndex]; - [cell setObjectValue: [_dataSource outlineView: self - objectValueForTableColumn: tb - byItem: item]]; - drawingRect = [self frameOfCellAtColumn: i - row: rowIndex]; - - if (tb == _outlineTableColumn) + // display the correct arrow... + if ([self isItemExpanded: item]) { - NSImage *image = nil; - int level = 0; - float indentationFactor = 0.0; - // float originalWidth = drawingRect.size.width; - - // display the correct arrow... - if ([self isItemExpanded: item]) - { - image = expanded; - } - else - { - image = collapsed; - } - - if (![self isExpandable: item]) - { - image = unexpandable; - } - - level = [self levelForItem: item]; - indentationFactor = _indentationPerLevel * level; - imageCell = [[NSCell alloc] initImageCell: image]; - - if (_indentationMarkerFollowsCell) - { - imageRect.origin.x = drawingRect.origin.x + indentationFactor; - imageRect.origin.y = drawingRect.origin.y; - } - else - { - imageRect.origin.x = drawingRect.origin.x; - imageRect.origin.y = drawingRect.origin.y; - } - - if ([_delegate respondsToSelector: @selector(outlineView:willDisplayOutlineCell:forTableColumn:item:)]) - { - [_delegate outlineView: self - willDisplayOutlineCell: imageCell - forTableColumn: tb - item: item]; - } - - /* Do not indent if the delegate set the image to nil. */ - if ([imageCell image]) - { - imageRect.size.width = [image size].width; - imageRect.size.height = [image size].height; - [imageCell drawWithFrame: imageRect inView: self]; - drawingRect.origin.x - += indentationFactor + [image size].width + 5; - drawingRect.size.width - -= indentationFactor + [image size].width + 5; - } - else - { - drawingRect.origin.x += indentationFactor; - drawingRect.size.width -= indentationFactor; - } - - RELEASE(imageCell); + image = expanded; + } + else + { + image = collapsed; } - [cell drawWithFrame: drawingRect inView: self]; + if (![self isExpandable: item]) + { + image = unexpandable; + } + + level = [self levelForItem: item]; + indentationFactor = _indentationPerLevel * level; + imageCell = [[NSCell alloc] initImageCell: image]; + + if (_indentationMarkerFollowsCell) + { + imageRect.origin.x = drawingRect.origin.x + indentationFactor; + imageRect.origin.y = drawingRect.origin.y; + } + else + { + imageRect.origin.x = drawingRect.origin.x; + imageRect.origin.y = drawingRect.origin.y; + } + + if ([_delegate respondsToSelector: @selector(outlineView:willDisplayOutlineCell:forTableColumn:item:)]) + { + [_delegate outlineView: self + willDisplayOutlineCell: imageCell + forTableColumn: tb + item: item]; + } + + /* Do not indent if the delegate set the image to nil. */ + if ([imageCell image]) + { + imageRect.size.width = [image size].width; + imageRect.size.height = [image size].height; + [imageCell drawWithFrame: imageRect inView: self]; + drawingRect.origin.x + += indentationFactor + [image size].width + 5; + drawingRect.size.width + -= indentationFactor + [image size].width + 5; + } + else + { + drawingRect.origin.x += indentationFactor; + drawingRect.size.width -= indentationFactor; + } + + RELEASE(imageCell); } + + [cell drawWithFrame: drawingRect inView: self]; + if (i == _editedColumn && rowIndex == _editedRow) + [cell _setInEditing: NO]; } } diff --git a/Source/NSTableView.m b/Source/NSTableView.m index bf681de5a..e114aa443 100644 --- a/Source/NSTableView.m +++ b/Source/NSTableView.m @@ -2178,6 +2178,16 @@ static void computeNewSelection [_selectedColumns addIndex: newIndex]; } + /* Update edited cell */ + if (_editedColumn == columnIndex) + { + _editedColumn = newIndex; + } + else if ((_editedColumn >= minRange) && (_editedColumn <= maxRange)) + { + _editedColumn += shift; + } + /* Now really move the column */ if (columnIndex < newIndex) { @@ -4948,20 +4958,21 @@ static BOOL selectContiguousRegion(NSTableView *self, /* Draw the row between startingColumn and endingColumn */ for (i = startingColumn; i <= endingColumn; i++) { - if (i != _editedColumn || rowIndex != _editedRow) - { - tb = [_tableColumns objectAtIndex: i]; - cell = [tb dataCellForRow: rowIndex]; - [self _willDisplayCell: cell - forTableColumn: tb - row: rowIndex]; - [cell setObjectValue: [_dataSource tableView: self - objectValueForTableColumn: tb - row: rowIndex]]; - drawingRect = [self frameOfCellAtColumn: i - row: rowIndex]; - [cell drawWithFrame: drawingRect inView: self]; - } + tb = [_tableColumns objectAtIndex: i]; + cell = [tb dataCellForRow: rowIndex]; + if (i == _editedColumn && rowIndex == _editedRow) + [cell _setInEditing: YES]; + [self _willDisplayCell: cell + forTableColumn: tb + row: rowIndex]; + [cell setObjectValue: [_dataSource tableView: self + objectValueForTableColumn: tb + row: rowIndex]]; + drawingRect = [self frameOfCellAtColumn: i + row: rowIndex]; + [cell drawWithFrame: drawingRect inView: self]; + if (i == _editedColumn && rowIndex == _editedRow) + [cell _setInEditing: NO]; } } diff --git a/Source/NSTextFieldCell.m b/Source/NSTextFieldCell.m index 02a85005f..690cc7fad 100644 --- a/Source/NSTextFieldCell.m +++ b/Source/NSTextFieldCell.m @@ -225,15 +225,20 @@ - (void) drawInteriorWithFrame: (NSRect)cellFrame inView: (NSView*)controlView { - NSRect titleRect; + if (_cell.in_editing) + [self _drawEditorWithFrame: cellFrame inView: controlView]; + else + { + NSRect titleRect; - /* Make sure we are a text cell; titleRect might return an incorrect - rectangle otherwise. Note that the type could be different if the - user has set an image on us, which we just ignore (OS X does so as - well). */ - _cell.type = NSTextCellType; - titleRect = [self titleRectForBounds: cellFrame]; - [[self _drawAttributedString] drawInRect: titleRect]; + /* Make sure we are a text cell; titleRect might return an incorrect + rectangle otherwise. Note that the type could be different if the + user has set an image on us, which we just ignore (OS X does so as + well). */ + _cell.type = NSTextCellType; + titleRect = [self titleRectForBounds: cellFrame]; + [[self _drawAttributedString] drawInRect: titleRect]; + } } /*