/* * Copyright (C) 2004 Apple Computer, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "KWQListBox.h" #import "KWQAssertions.h" #import "KWQExceptions.h" #import "KWQKHTMLPart.h" #import "KWQView.h" #import "WebCoreBridge.h" #import "WebCoreScrollView.h" #import "WebCoreTextRenderer.h" #import "WebCoreTextRendererFactory.h" #import "render_form.h" using khtml::RenderWidget; using khtml::RenderLayer; @interface NSTableView (KWQListBoxKnowsAppKitSecrets) - (NSCell *)_accessibilityTableCell:(int)row tableColumn:(NSTableColumn *)tableColumn; @end const int minLines = 4; /* ensures we have a scroll bar */ const float bottomMargin = 1; const float leftMargin = 2; const float rightMargin = 2; @interface KWQListBoxScrollView : WebCoreScrollView <KWQWidgetHolder> { QListBox *_box; } @end @interface KWQTableView : NSTableView <KWQWidgetHolder> { @public QListBox *_box; BOOL processingMouseEvent; BOOL clickedDuringMouseEvent; BOOL inNextValidKeyView; NSWritingDirection _direction; BOOL isSystemFont; } - (id)initWithListBox:(QListBox *)b; - (void)detach; - (void)_KWQ_setKeyboardFocusRingNeedsDisplay; - (QWidget *)widget; - (void)setBaseWritingDirection:(NSWritingDirection)direction; - (NSWritingDirection)baseWritingDirection; - (void)fontChanged; @end static id <WebCoreTextRenderer> itemScreenRenderer; static id <WebCoreTextRenderer> itemPrinterRenderer; static id <WebCoreTextRenderer> groupLabelScreenRenderer; static id <WebCoreTextRenderer> groupLabelPrinterRenderer; static NSFont *itemFont() { static NSFont *font = [[NSFont systemFontOfSize:[NSFont smallSystemFontSize]] retain]; return font; } static NSFont *groupLabelFont() { static NSFont *font = [[NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]] retain]; return font; } static id <WebCoreTextRenderer> itemTextRenderer() { if ([NSGraphicsContext currentContextDrawingToScreen]) { if (itemScreenRenderer == nil) { itemScreenRenderer = [[[WebCoreTextRendererFactory sharedFactory] rendererWithFont:itemFont() usingPrinterFont:NO] retain]; } return itemScreenRenderer; } else { if (itemPrinterRenderer == nil) { itemPrinterRenderer = [[[WebCoreTextRendererFactory sharedFactory] rendererWithFont:itemFont() usingPrinterFont:YES] retain]; } return itemPrinterRenderer; } } static id <WebCoreTextRenderer> groupLabelTextRenderer() { if ([NSGraphicsContext currentContextDrawingToScreen]) { if (groupLabelScreenRenderer == nil) { groupLabelScreenRenderer = [[[WebCoreTextRendererFactory sharedFactory] rendererWithFont:groupLabelFont() usingPrinterFont:NO] retain]; } return groupLabelScreenRenderer; } else { if (groupLabelPrinterRenderer == nil) { groupLabelPrinterRenderer = [[[WebCoreTextRendererFactory sharedFactory] rendererWithFont:groupLabelFont() usingPrinterFont:YES] retain]; } return groupLabelPrinterRenderer; } } QListBox::QListBox(QWidget *parent) : QScrollView(parent) , _changingSelection(false) , _enabled(true) , _widthGood(false) , _clicked(this, SIGNAL(clicked(QListBoxItem *))) , _selectionChanged(this, SIGNAL(selectionChanged())) { KWQ_BLOCK_EXCEPTIONS; NSScrollView *scrollView = [[KWQListBoxScrollView alloc] initWithListBox:this]; setView(scrollView); [scrollView release]; [scrollView setBorderType:NSBezelBorder]; [scrollView setHasVerticalScroller:YES]; [[scrollView verticalScroller] setControlSize:NSSmallControlSize]; // In WebHTMLView, we set a clip. This is not typical to do in an // NSView, and while correct for any one invocation of drawRect:, // it causes some bad problems if that clip is cached between calls. // The cached graphics state, which clip views keep around, does // cache the clip in this undesirable way. Consequently, we want to // release the GState for all clip views for all views contained in // a WebHTMLView. Here we do it for list boxes used in forms. // See these bugs for more information: // <rdar://problem/3226083>: REGRESSION (Panther): white box overlaying select lists at nvidia.com drivers page [[scrollView contentView] releaseGState]; KWQTableView *tableView = [[KWQTableView alloc] initWithListBox:this]; [scrollView setDocumentView:tableView]; [tableView release]; [scrollView setVerticalLineScroll:[tableView rowHeight]]; KWQ_UNBLOCK_EXCEPTIONS; } QListBox::~QListBox() { NSScrollView *scrollView = static_cast<NSScrollView *>(getView()); KWQ_BLOCK_EXCEPTIONS; KWQTableView *tableView = [scrollView documentView]; [tableView detach]; KWQ_UNBLOCK_EXCEPTIONS; } void QListBox::clear() { _items.clear(); _widthGood = false; } void QListBox::setSelectionMode(SelectionMode mode) { NSScrollView *scrollView = static_cast<NSScrollView *>(getView()); KWQ_BLOCK_EXCEPTIONS; NSTableView *tableView = [scrollView documentView]; [tableView setAllowsMultipleSelection:mode != Single]; KWQ_UNBLOCK_EXCEPTIONS; } void QListBox::appendItem(const QString &text, KWQListBoxItemType type) { _items.append(KWQListBoxItem(text, type)); _widthGood = false; } void QListBox::doneAppendingItems() { KWQ_BLOCK_EXCEPTIONS; NSScrollView *scrollView = static_cast<NSScrollView *>(getView()); NSTableView *tableView = [scrollView documentView]; [tableView reloadData]; KWQ_UNBLOCK_EXCEPTIONS; } void QListBox::setSelected(int index, bool selectIt) { ASSERT(index >= 0); KWQ_BLOCK_EXCEPTIONS; NSScrollView *scrollView = static_cast<NSScrollView *>(getView()); NSTableView *tableView = [scrollView documentView]; _changingSelection = true; if (selectIt) { [tableView selectRow:index byExtendingSelection:[tableView allowsMultipleSelection]]; [tableView scrollRowToVisible:index]; } else { [tableView deselectRow:index]; } KWQ_UNBLOCK_EXCEPTIONS; _changingSelection = false; } bool QListBox::isSelected(int index) const { ASSERT(index >= 0); KWQ_BLOCK_EXCEPTIONS; NSScrollView *scrollView = static_cast<NSScrollView *>(getView()); NSTableView *tableView = [scrollView documentView]; return [tableView isRowSelected:index]; KWQ_UNBLOCK_EXCEPTIONS; return false; } void QListBox::setEnabled(bool enabled) { if (enabled != _enabled) { // You would think this would work, but not until AppKit bug 2177792 is fixed. //KWQ_BLOCK_EXCEPTIONS; //NSTableView *tableView = [(NSScrollView *)getView() documentView]; //[tableView setEnabled:enabled]; //KWQ_UNBLOCK_EXCEPTIONS; _enabled = enabled; NSScrollView *scrollView = static_cast<NSScrollView *>(getView()); NSTableView *tableView = [scrollView documentView]; [tableView reloadData]; } } bool QListBox::isEnabled() { return _enabled; } QSize QListBox::sizeForNumberOfLines(int lines) const { NSSize size = {0,0}; KWQ_BLOCK_EXCEPTIONS; NSScrollView *scrollView = static_cast<NSScrollView *>(getView()); KWQTableView *tableView = [scrollView documentView]; if (!_widthGood) { float width = 0; QValueListConstIterator<KWQListBoxItem> i = const_cast<const QValueList<KWQListBoxItem> &>(_items).begin(); QValueListConstIterator<KWQListBoxItem> e = const_cast<const QValueList<KWQListBoxItem> &>(_items).end(); if (i != e) { WebCoreTextStyle style; WebCoreInitializeEmptyTextStyle(&style); style.rtl = [tableView baseWritingDirection] == NSWritingDirectionRightToLeft; style.applyRunRounding = NO; style.applyWordRounding = NO; id <WebCoreTextRenderer> renderer; id <WebCoreTextRenderer> groupLabelRenderer; if (tableView->isSystemFont) { renderer = itemTextRenderer(); groupLabelRenderer = groupLabelTextRenderer(); } else { NSFont *f = font().getNSFont(); QFont b = font(); b.setWeight(QFont::Bold); NSFont *boldFont = b.getNSFont(); renderer = [[WebCoreTextRendererFactory sharedFactory] rendererWithFont:f usingPrinterFont:![NSGraphicsContext currentContextDrawingToScreen]]; groupLabelRenderer = [[WebCoreTextRendererFactory sharedFactory] rendererWithFont:boldFont usingPrinterFont:![NSGraphicsContext currentContextDrawingToScreen]]; } do { const QString &s = (*i).string; WebCoreTextRun run; int length = s.length(); WebCoreInitializeTextRun(&run, reinterpret_cast<const UniChar *>(s.unicode()), length, 0, length); float textWidth = [(((*i).type == KWQListBoxGroupLabel) ? groupLabelRenderer : renderer) floatWidthForRun:&run style:&style widths:0]; width = kMax(width, textWidth); ++i; } while (i != e); } _width = ceilf(width); _widthGood = true; } NSSize tempSize = { _width, [tableView rowHeight] * MAX(minLines, lines) }; // workaround for 4213314 size = [NSScrollView frameSizeForContentSize:tempSize hasHorizontalScroller:NO hasVerticalScroller:YES borderType:NSBezelBorder]; size.width += [NSScroller scrollerWidthForControlSize:NSSmallControlSize] - [NSScroller scrollerWidth] + leftMargin + rightMargin; KWQ_UNBLOCK_EXCEPTIONS; return QSize(size); } QWidget::FocusPolicy QListBox::focusPolicy() const { FocusPolicy policy = QWidget::focusPolicy(); return policy == TabFocus ? StrongFocus : policy; } bool QListBox::checksDescendantsForFocus() const { return true; } void QListBox::setWritingDirection(QPainter::TextDirection d) { KWQ_BLOCK_EXCEPTIONS; NSScrollView *scrollView = static_cast<NSScrollView *>(getView()); KWQTableView *tableView = [scrollView documentView]; NSWritingDirection direction = d == QPainter::RTL ? NSWritingDirectionRightToLeft : NSWritingDirectionLeftToRight; if ([tableView baseWritingDirection] != direction) { [tableView setBaseWritingDirection:direction]; [tableView reloadData]; } KWQ_UNBLOCK_EXCEPTIONS; } void QListBox::clearCachedTextRenderers() { [itemScreenRenderer release]; itemScreenRenderer = nil; [itemPrinterRenderer release]; itemPrinterRenderer = nil; [groupLabelScreenRenderer release]; groupLabelScreenRenderer = nil; [groupLabelPrinterRenderer release]; groupLabelPrinterRenderer = nil; } void QListBox::setFont(const QFont &font) { QWidget::setFont(font); NSScrollView *scrollView = static_cast<NSScrollView *>(getView()); KWQTableView *tableView = [scrollView documentView]; [tableView fontChanged]; } @implementation KWQListBoxScrollView - (id)initWithListBox:(QListBox *)b { if (!(self = [super init])) return nil; _box = b; return self; } - (QWidget *)widget { return _box; } - (void)setFrameSize:(NSSize)size { [super setFrameSize:size]; NSTableColumn *column = [[[self documentView] tableColumns] objectAtIndex:0]; [column setWidth:[self contentSize].width]; [column setMinWidth:[self contentSize].width]; [column setMaxWidth:[self contentSize].width]; } - (BOOL)becomeFirstResponder { KWQTableView *documentView = [self documentView]; QWidget *widget = [documentView widget]; [KWQKHTMLPart::bridgeForWidget(widget) makeFirstResponder:documentView]; return YES; } @end @implementation KWQTableView - (id)initWithListBox:(QListBox *)b { [super init]; _box = b; NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:nil]; [column setEditable:NO]; [self addTableColumn:column]; [column release]; [self setAllowsMultipleSelection:NO]; [self setHeaderView:nil]; [self setIntercellSpacing:NSMakeSize(0, 0)]; [self setDataSource:self]; [self setDelegate:self]; return self; } - (void)detach { _box = 0; [self setDelegate:nil]; [self setDataSource:nil]; } - (void)mouseDown:(NSEvent *)event { if (!_box) { [super mouseDown:event]; return; } processingMouseEvent = YES; NSView *outerView = [_box->getOuterView() retain]; QWidget::beforeMouseDown(outerView); [super mouseDown:event]; QWidget::afterMouseDown(outerView); [outerView release]; processingMouseEvent = NO; if (clickedDuringMouseEvent) { clickedDuringMouseEvent = false; } else if (_box) { _box->sendConsumedMouseUp(); } } - (void)keyDown:(NSEvent *)event { if (!_box) { return; } WebCoreBridge *bridge = KWQKHTMLPart::bridgeForWidget(_box); if (![bridge interceptKeyEvent:event toView:self]) { [super keyDown:event]; } } - (void)keyUp:(NSEvent *)event { if (!_box) { return; } WebCoreBridge *bridge = KWQKHTMLPart::bridgeForWidget(_box); if (![bridge interceptKeyEvent:event toView:self]) { [super keyUp:event]; } } - (BOOL)becomeFirstResponder { if (!_box) { return NO; } BOOL become = [super becomeFirstResponder]; if (become) { if (_box && !KWQKHTMLPart::currentEventIsMouseDownInWidget(_box)) { RenderWidget *widget = const_cast<RenderWidget *> (static_cast<const RenderWidget *>(_box->eventFilterObject())); RenderLayer *layer = widget->enclosingLayer(); if (layer) layer->scrollRectToVisible(widget->absoluteBoundingBoxRect()); } [self _KWQ_setKeyboardFocusRingNeedsDisplay]; if (_box) { QFocusEvent event(QEvent::FocusIn); if (_box->eventFilterObject()) const_cast<QObject *>(_box->eventFilterObject())->eventFilter(_box, &event); } } return become; } - (BOOL)resignFirstResponder { BOOL resign = [super resignFirstResponder]; if (resign && _box) { QFocusEvent event(QEvent::FocusOut); if (_box->eventFilterObject()) const_cast<QObject *>(_box->eventFilterObject())->eventFilter(_box, &event); } return resign; } - (BOOL)canBecomeKeyView { // Simplified method from NSView; overridden to replace NSView's way of checking // for full keyboard access with ours. return ([self window] != nil) && ![self isHiddenOrHasHiddenAncestor] && [self acceptsFirstResponder]; } - (NSView *)nextKeyView { return _box && inNextValidKeyView ? KWQKHTMLPart::nextKeyViewForWidget(_box, KWQSelectingNext) : [super nextKeyView]; } - (NSView *)previousKeyView { return _box && inNextValidKeyView ? KWQKHTMLPart::nextKeyViewForWidget(_box, KWQSelectingPrevious) : [super previousKeyView]; } - (NSView *)nextValidKeyView { inNextValidKeyView = YES; NSView *view = [super nextValidKeyView]; inNextValidKeyView = NO; return view; } - (NSView *)previousValidKeyView { inNextValidKeyView = YES; NSView *view = [super previousValidKeyView]; inNextValidKeyView = NO; return view; } - (int)numberOfRowsInTableView:(NSTableView *)tableView { return _box ? _box->count() : 0; } - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(int)row { return nil; } - (void)tableViewSelectionDidChange:(NSNotification *)notification { if (_box) { _box->selectionChanged(); } if (_box && !_box->changingSelection()) { if (processingMouseEvent) { clickedDuringMouseEvent = true; _box->sendConsumedMouseUp(); } if (_box) { _box->clicked(); } } } - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row { return _box && _box->itemAtIndex(row).type == KWQListBoxOption; } - (BOOL)selectionShouldChangeInTableView:(NSTableView *)aTableView { return _box && _box->isEnabled(); } - (void)drawRow:(int)row clipRect:(NSRect)clipRect { if (!_box) { return; } const KWQListBoxItem &item = _box->itemAtIndex(row); NSColor *color; if (_box->isEnabled()) { if ([self isRowSelected:row] && [[self window] firstResponder] == self && ([[self window] isKeyWindow] || ![[self window] canBecomeKeyWindow])) { color = [NSColor alternateSelectedControlTextColor]; } else { color = [NSColor controlTextColor]; } } else { color = [NSColor disabledControlTextColor]; } bool RTL = _direction == NSWritingDirectionRightToLeft; NSFont *font = _box->font().getNSFont(); id <WebCoreTextRenderer> renderer; if (isSystemFont) { renderer = (item.type == KWQListBoxGroupLabel) ? groupLabelTextRenderer() : itemTextRenderer(); } else { if (item.type == KWQListBoxGroupLabel) { QFont boldFont = _box->font(); boldFont.setWeight(QFont::Bold); font = boldFont.getNSFont(); } renderer = [[WebCoreTextRendererFactory sharedFactory] rendererWithFont:font usingPrinterFont:![NSGraphicsContext currentContextDrawingToScreen]]; } WebCoreTextStyle style; WebCoreInitializeEmptyTextStyle(&style); style.rtl = RTL; style.applyRunRounding = NO; style.applyWordRounding = NO; style.textColor = color; WebCoreTextRun run; int length = item.string.length(); WebCoreInitializeTextRun(&run, reinterpret_cast<const UniChar *>(item.string.unicode()), length, 0, length); NSRect cellRect = [self frameOfCellAtColumn:0 row:row]; NSPoint point; if (!RTL) { point.x = NSMinX(cellRect) + leftMargin; } else { point.x = NSMaxX(cellRect) - rightMargin - [renderer floatWidthForRun:&run style:&style widths:0]; } point.y = NSMaxY(cellRect) + [font descender] - bottomMargin; WebCoreTextGeometry geometry; WebCoreInitializeEmptyTextGeometry(&geometry); geometry.point = point; [renderer drawRun:&run style:&style geometry:&geometry]; } - (void)_KWQ_setKeyboardFocusRingNeedsDisplay { [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]]; } - (QWidget *)widget { return _box; } - (void)setBaseWritingDirection:(NSWritingDirection)direction { _direction = direction; } - (NSWritingDirection)baseWritingDirection { return _direction; } - (NSCell *)_accessibilityTableCell:(int)row tableColumn:(NSTableColumn *)tableColumn { NSCell *cell = [super _accessibilityTableCell:row tableColumn:tableColumn]; if (_box) { [cell setStringValue:_box->itemAtIndex(row).string.getNSString()]; } return cell; } - (void)fontChanged { NSFont *font = _box->font().getNSFont(); isSystemFont = [[font fontName] isEqualToString:[itemFont() fontName]] && ([font pointSize] == [itemFont() pointSize]); [self setRowHeight:ceilf([font ascender] - [font descender] + bottomMargin)]; [self setNeedsDisplay:YES]; } @end