2 * Copyright (C) 2004 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 #import "KWQListBox.h"
28 #import "KWQAssertions.h"
29 #import "KWQExceptions.h"
30 #import "KWQKHTMLPart.h"
31 #import "KWQNSViewExtras.h"
33 #import "WebCoreBridge.h"
34 #import "WebCoreScrollView.h"
35 #import "WebCoreTextRenderer.h"
36 #import "WebCoreTextRendererFactory.h"
38 @interface NSTableView (KWQListBoxKnowsAppKitSecrets)
39 - (NSCell *)_accessibilityTableCell:(int)row tableColumn:(NSTableColumn *)tableColumn;
42 const int minLines = 4; /* ensures we have a scroll bar */
43 const float bottomMargin = 1;
44 const float leftMargin = 2;
45 const float rightMargin = 2;
47 @interface KWQListBoxScrollView : WebCoreScrollView <KWQWidgetHolder>
53 @interface KWQTableView : NSTableView <KWQWidgetHolder>
56 BOOL processingMouseEvent;
57 BOOL clickedDuringMouseEvent;
58 BOOL inNextValidKeyView;
59 NSWritingDirection _direction;
61 - (id)initWithListBox:(QListBox *)b;
63 - (void)_KWQ_setKeyboardFocusRingNeedsDisplay;
65 - (void)setBaseWritingDirection:(NSWritingDirection)direction;
66 - (NSWritingDirection)baseWritingDirection;
69 static id <WebCoreTextRenderer> itemScreenRenderer;
70 static id <WebCoreTextRenderer> itemPrinterRenderer;
71 static id <WebCoreTextRenderer> groupLabelScreenRenderer;
72 static id <WebCoreTextRenderer> groupLabelPrinterRenderer;
74 static NSFont *itemFont()
76 static NSFont *font = [[NSFont systemFontOfSize:[NSFont smallSystemFontSize]] retain];
80 static NSFont *groupLabelFont()
82 static NSFont *font = [[NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]] retain];
86 static id <WebCoreTextRenderer> itemTextRenderer()
88 if ([NSGraphicsContext currentContextDrawingToScreen]) {
89 if (itemScreenRenderer == nil) {
90 itemScreenRenderer = [[[WebCoreTextRendererFactory sharedFactory]
91 rendererWithFont:itemFont() usingPrinterFont:NO] retain];
93 return itemScreenRenderer;
95 if (itemPrinterRenderer == nil) {
96 itemPrinterRenderer = [[[WebCoreTextRendererFactory sharedFactory]
97 rendererWithFont:itemFont() usingPrinterFont:YES] retain];
99 return itemPrinterRenderer;
103 static id <WebCoreTextRenderer> groupLabelTextRenderer()
105 if ([NSGraphicsContext currentContextDrawingToScreen]) {
106 if (groupLabelScreenRenderer == nil) {
107 groupLabelScreenRenderer = [[[WebCoreTextRendererFactory sharedFactory]
108 rendererWithFont:groupLabelFont() usingPrinterFont:NO] retain];
110 return groupLabelScreenRenderer;
112 if (groupLabelPrinterRenderer == nil) {
113 groupLabelPrinterRenderer = [[[WebCoreTextRendererFactory sharedFactory]
114 rendererWithFont:groupLabelFont() usingPrinterFont:YES] retain];
116 return groupLabelPrinterRenderer;
120 QListBox::QListBox(QWidget *parent)
121 : QScrollView(parent)
122 , _changingSelection(false)
125 , _clicked(this, SIGNAL(clicked(QListBoxItem *)))
126 , _selectionChanged(this, SIGNAL(selectionChanged()))
128 KWQ_BLOCK_EXCEPTIONS;
130 NSScrollView *scrollView = [[KWQListBoxScrollView alloc] initWithListBox:this];
132 [scrollView release];
134 [scrollView setBorderType:NSBezelBorder];
135 [scrollView setHasVerticalScroller:YES];
136 [[scrollView verticalScroller] setControlSize:NSSmallControlSize];
138 // In WebHTMLView, we set a clip. This is not typical to do in an
139 // NSView, and while correct for any one invocation of drawRect:,
140 // it causes some bad problems if that clip is cached between calls.
141 // The cached graphics state, which clip views keep around, does
142 // cache the clip in this undesirable way. Consequently, we want to
143 // release the GState for all clip views for all views contained in
144 // a WebHTMLView. Here we do it for list boxes used in forms.
145 // See these bugs for more information:
146 // <rdar://problem/3226083>: REGRESSION (Panther): white box overlaying select lists at nvidia.com drivers page
147 [[scrollView contentView] releaseGState];
149 KWQTableView *tableView = [[KWQTableView alloc] initWithListBox:this];
150 [scrollView setDocumentView:tableView];
152 [scrollView setVerticalLineScroll:[tableView rowHeight]];
154 KWQ_UNBLOCK_EXCEPTIONS;
157 QListBox::~QListBox()
159 NSScrollView *scrollView = getView();
161 KWQ_BLOCK_EXCEPTIONS;
162 KWQTableView *tableView = [scrollView documentView];
164 KWQ_UNBLOCK_EXCEPTIONS;
167 void QListBox::clear()
173 void QListBox::setSelectionMode(SelectionMode mode)
175 NSScrollView *scrollView = getView();
177 KWQ_BLOCK_EXCEPTIONS;
178 NSTableView *tableView = [scrollView documentView];
179 [tableView setAllowsMultipleSelection:mode != Single];
180 KWQ_UNBLOCK_EXCEPTIONS;
183 void QListBox::appendItem(const QString &text, bool isLabel)
185 _items.append(KWQListBoxItem(text, isLabel));
189 void QListBox::doneAppendingItems()
191 KWQ_BLOCK_EXCEPTIONS;
193 NSScrollView *scrollView = getView();
194 NSTableView *tableView = [scrollView documentView];
195 [tableView reloadData];
197 KWQ_UNBLOCK_EXCEPTIONS;
200 void QListBox::setSelected(int index, bool selectIt)
204 KWQ_BLOCK_EXCEPTIONS;
206 NSScrollView *scrollView = getView();
207 NSTableView *tableView = [scrollView documentView];
208 _changingSelection = true;
210 [tableView selectRow:index byExtendingSelection:[tableView allowsMultipleSelection]];
211 [tableView scrollRowToVisible:index];
213 [tableView deselectRow:index];
216 KWQ_UNBLOCK_EXCEPTIONS;
218 _changingSelection = false;
221 bool QListBox::isSelected(int index) const
225 KWQ_BLOCK_EXCEPTIONS;
227 NSScrollView *scrollView = getView();
228 NSTableView *tableView = [scrollView documentView];
229 return [tableView isRowSelected:index];
231 KWQ_UNBLOCK_EXCEPTIONS;
236 void QListBox::setEnabled(bool enabled)
238 if (enabled != _enabled) {
239 // You would think this would work, but not until AppKit bug 2177792 if fixed.
240 //KWQ_BLOCK_EXCEPTIONS;
241 //NSTableView *tableView = [(NSScrollView *)getView() documentView];
242 //[tableView setEnabled:enabled];
243 //KWQ_UNBLOCK_EXCEPTIONS;
247 NSScrollView *scrollView = getView();
248 NSTableView *tableView = [scrollView documentView];
249 [tableView reloadData];
253 bool QListBox::isEnabled()
258 QSize QListBox::sizeForNumberOfLines(int lines) const
262 KWQ_BLOCK_EXCEPTIONS;
264 NSScrollView *scrollView = getView();
265 KWQTableView *tableView = [scrollView documentView];
269 QValueListConstIterator<KWQListBoxItem> i = const_cast<const QValueList<KWQListBoxItem> &>(_items).begin();
270 QValueListConstIterator<KWQListBoxItem> e = const_cast<const QValueList<KWQListBoxItem> &>(_items).end();
272 WebCoreTextStyle style;
273 WebCoreInitializeEmptyTextStyle(&style);
274 style.rtl = [tableView baseWritingDirection] == NSWritingDirectionRightToLeft;
275 style.applyRunRounding = NO;
276 style.applyWordRounding = NO;
278 const QString &s = (*i).string;
279 id <WebCoreTextRenderer> renderer = (*i).isGroupLabel ? groupLabelTextRenderer() : itemTextRenderer();
283 int length = s.length();
284 WebCoreInitializeTextRun(&run, reinterpret_cast<const UniChar *>(s.unicode()), length, 0, length);
286 float textWidth = [renderer floatWidthForRun:&run style:&style widths:0];
287 width = kMax(width, textWidth);
290 _width = ceilf(width);
294 size = [NSScrollView frameSizeForContentSize:NSMakeSize(_width, [tableView rowHeight] * MAX(minLines, lines))
295 hasHorizontalScroller:NO hasVerticalScroller:YES borderType:NSBezelBorder];
296 size.width += [NSScroller scrollerWidthForControlSize:NSSmallControlSize] - [NSScroller scrollerWidth] + leftMargin + rightMargin;
298 KWQ_UNBLOCK_EXCEPTIONS;
303 QWidget::FocusPolicy QListBox::focusPolicy() const
305 KWQ_BLOCK_EXCEPTIONS;
307 WebCoreBridge *bridge = KWQKHTMLPart::bridgeForWidget(this);
308 if (!bridge || ![bridge part] || ![bridge part]->tabsToAllControls()) {
312 KWQ_UNBLOCK_EXCEPTIONS;
314 return QScrollView::focusPolicy();
317 bool QListBox::checksDescendantsForFocus() const
322 void QListBox::setWritingDirection(QPainter::TextDirection d)
324 KWQ_BLOCK_EXCEPTIONS;
326 NSScrollView *scrollView = getView();
327 KWQTableView *tableView = [scrollView documentView];
328 NSWritingDirection direction = d == QPainter::RTL ? NSWritingDirectionRightToLeft : NSWritingDirectionLeftToRight;
329 if ([tableView baseWritingDirection] != direction) {
330 [tableView setBaseWritingDirection:direction];
331 [tableView reloadData];
334 KWQ_UNBLOCK_EXCEPTIONS;
337 void QListBox::clearCachedTextRenderers()
339 [itemScreenRenderer release];
340 itemScreenRenderer = nil;
342 [itemPrinterRenderer release];
343 itemPrinterRenderer = nil;
345 [groupLabelScreenRenderer release];
346 groupLabelScreenRenderer = nil;
348 [groupLabelPrinterRenderer release];
349 groupLabelPrinterRenderer = nil;
352 @implementation KWQListBoxScrollView
354 - (id)initWithListBox:(QListBox *)b
356 if (!(self = [super init]))
368 - (void)setFrameSize:(NSSize)size
370 [super setFrameSize:size];
371 NSTableColumn *column = [[[self documentView] tableColumns] objectAtIndex:0];
372 [column setWidth:[self contentSize].width];
373 [column setMinWidth:[self contentSize].width];
374 [column setMaxWidth:[self contentSize].width];
377 - (BOOL)becomeFirstResponder
379 KWQTableView *documentView = [self documentView];
380 QWidget *widget = [documentView widget];
381 [KWQKHTMLPart::bridgeForWidget(widget) makeFirstResponder:documentView];
387 @implementation KWQTableView
389 - (id)initWithListBox:(QListBox *)b
395 NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:nil];
397 [column setEditable:NO];
399 [self addTableColumn:column];
403 [self setAllowsMultipleSelection:NO];
404 [self setHeaderView:nil];
405 [self setIntercellSpacing:NSMakeSize(0, 0)];
406 [self setRowHeight:ceilf([itemFont() ascender] - [itemFont() descender] + bottomMargin)];
408 [self setDataSource:self];
409 [self setDelegate:self];
417 [self setDelegate:nil];
418 [self setDataSource:nil];
421 - (void)mouseDown:(NSEvent *)event
423 processingMouseEvent = YES;
424 NSView *outerView = [_box->getOuterView() retain];
425 QWidget::beforeMouseDown(outerView);
426 [super mouseDown:event];
427 QWidget::afterMouseDown(outerView);
429 processingMouseEvent = NO;
431 if (clickedDuringMouseEvent) {
432 clickedDuringMouseEvent = false;
434 _box->sendConsumedMouseUp();
438 - (void)keyDown:(NSEvent *)event
443 WebCoreBridge *bridge = KWQKHTMLPart::bridgeForWidget(_box);
444 if (![bridge interceptKeyEvent:event toView:self]) {
445 [super keyDown:event];
449 - (void)keyUp:(NSEvent *)event
454 WebCoreBridge *bridge = KWQKHTMLPart::bridgeForWidget(_box);
455 if (![bridge interceptKeyEvent:event toView:self]) {
460 - (BOOL)becomeFirstResponder
466 BOOL become = [super becomeFirstResponder];
469 if (!KWQKHTMLPart::currentEventIsMouseDownInWidget(_box)) {
470 [self _KWQ_scrollFrameToVisible];
472 [self _KWQ_setKeyboardFocusRingNeedsDisplay];
473 QFocusEvent event(QEvent::FocusIn);
474 const_cast<QObject *>(_box->eventFilterObject())->eventFilter(_box, &event);
480 - (BOOL)resignFirstResponder
482 BOOL resign = [super resignFirstResponder];
483 if (resign && _box) {
484 QFocusEvent event(QEvent::FocusOut);
485 const_cast<QObject *>(_box->eventFilterObject())->eventFilter(_box, &event);
490 - (BOOL)canBecomeKeyView
492 // Simplified method from NSView; overridden to replace NSView's way of checking
493 // for full keyboard access with ours.
494 if (!_box || !KWQKHTMLPart::partForWidget(_box)->tabsToAllControls()) {
497 return ([self window] != nil) && ![self isHiddenOrHasHiddenAncestor] && [self acceptsFirstResponder];
500 - (NSView *)nextKeyView
502 return _box && inNextValidKeyView
503 ? KWQKHTMLPart::nextKeyViewForWidget(_box, KWQSelectingNext)
504 : [super nextKeyView];
507 - (NSView *)previousKeyView
509 return _box && inNextValidKeyView
510 ? KWQKHTMLPart::nextKeyViewForWidget(_box, KWQSelectingPrevious)
511 : [super previousKeyView];
514 - (NSView *)nextValidKeyView
516 inNextValidKeyView = YES;
517 NSView *view = [super nextValidKeyView];
518 inNextValidKeyView = NO;
522 - (NSView *)previousValidKeyView
524 inNextValidKeyView = YES;
525 NSView *view = [super previousValidKeyView];
526 inNextValidKeyView = NO;
530 - (int)numberOfRowsInTableView:(NSTableView *)tableView
532 return _box->count();
535 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(int)row
540 - (void)tableViewSelectionDidChange:(NSNotification *)notification
542 _box->selectionChanged();
543 if (_box && !_box->changingSelection()) {
544 if (processingMouseEvent) {
545 clickedDuringMouseEvent = true;
546 _box->sendConsumedMouseUp();
554 - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row
556 return !_box->itemAtIndex(row).isGroupLabel;
559 - (BOOL)selectionShouldChangeInTableView:(NSTableView *)aTableView
561 return _box->isEnabled();
564 - (void)drawRow:(int)row clipRect:(NSRect)clipRect
570 const KWQListBoxItem &item = _box->itemAtIndex(row);
573 if (_box->isEnabled()) {
574 if ([self isRowSelected:row] && [[self window] firstResponder] == self && ([[self window] isKeyWindow] || ![[self window] canBecomeKeyWindow])) {
575 color = [NSColor alternateSelectedControlTextColor];
577 color = [NSColor controlTextColor];
580 color = [NSColor disabledControlTextColor];
583 bool RTL = _direction == NSWritingDirectionRightToLeft;
585 id <WebCoreTextRenderer> renderer = item.isGroupLabel ? groupLabelTextRenderer() : itemTextRenderer();
587 WebCoreTextStyle style;
588 WebCoreInitializeEmptyTextStyle(&style);
590 style.applyRunRounding = NO;
591 style.applyWordRounding = NO;
592 style.textColor = color;
595 int length = item.string.length();
596 WebCoreInitializeTextRun(&run, reinterpret_cast<const UniChar *>(item.string.unicode()), length, 0, length);
598 NSRect cellRect = [self frameOfCellAtColumn:0 row:row];
601 point.x = NSMinX(cellRect) + leftMargin;
603 point.x = NSMaxX(cellRect) - rightMargin - [renderer floatWidthForRun:&run style:&style widths:0];
605 point.y = NSMaxY(cellRect) + [itemFont() descender] - bottomMargin;
607 WebCoreTextGeometry geometry;
608 WebCoreInitializeEmptyTextGeometry(&geometry);
609 geometry.point = point;
611 [renderer drawRun:&run style:&style geometry:&geometry];
614 - (void)_KWQ_setKeyboardFocusRingNeedsDisplay
616 [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]];
624 - (void)setBaseWritingDirection:(NSWritingDirection)direction
626 _direction = direction;
629 - (NSWritingDirection)baseWritingDirection
634 - (NSCell *)_accessibilityTableCell:(int)row tableColumn:(NSTableColumn *)tableColumn
636 NSCell *cell = [super _accessibilityTableCell:row tableColumn:tableColumn];
638 [cell setStringValue:_box->itemAtIndex(row).string.getNSString()];