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
50 @interface KWQTableView : NSTableView <KWQWidgetHolder>
53 BOOL processingMouseEvent;
54 BOOL clickedDuringMouseEvent;
55 BOOL inNextValidKeyView;
56 NSWritingDirection _direction;
58 - (id)initWithListBox:(QListBox *)b;
59 - (void)_KWQ_setKeyboardFocusRingNeedsDisplay;
61 - (void)setBaseWritingDirection:(NSWritingDirection)direction;
62 - (NSWritingDirection)baseWritingDirection;
65 static NSFont *itemFont()
67 static NSFont *font = [[NSFont systemFontOfSize:[NSFont smallSystemFontSize]] retain];
71 static NSFont *groupLabelFont()
73 static NSFont *font = [[NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]] retain];
77 static id <WebCoreTextRenderer> itemTextRenderer()
79 if ([NSGraphicsContext currentContextDrawingToScreen]) {
80 static id <WebCoreTextRenderer> renderer = [[WebCoreTextRendererFactory sharedFactory]
81 rendererWithFont:itemFont() usingPrinterFont:NO];
84 static id <WebCoreTextRenderer> renderer = [[WebCoreTextRendererFactory sharedFactory]
85 rendererWithFont:itemFont() usingPrinterFont:YES];
90 static id <WebCoreTextRenderer> groupLabelTextRenderer()
92 if ([NSGraphicsContext currentContextDrawingToScreen]) {
93 static id <WebCoreTextRenderer> renderer = [[WebCoreTextRendererFactory sharedFactory]
94 rendererWithFont:groupLabelFont() usingPrinterFont:NO];
97 static id <WebCoreTextRenderer> renderer = [[WebCoreTextRendererFactory sharedFactory]
98 rendererWithFont:groupLabelFont() usingPrinterFont:YES];
103 QListBox::QListBox(QWidget *parent)
104 : QScrollView(parent)
105 , _changingSelection(false)
108 , _clicked(this, SIGNAL(clicked(QListBoxItem *)))
109 , _selectionChanged(this, SIGNAL(selectionChanged()))
111 KWQ_BLOCK_EXCEPTIONS;
113 NSScrollView *scrollView = [[KWQListBoxScrollView alloc] init];
115 [scrollView release];
117 [scrollView setBorderType:NSBezelBorder];
118 [scrollView setHasVerticalScroller:YES];
119 [[scrollView verticalScroller] setControlSize:NSSmallControlSize];
121 // In WebHTMLView, we set a clip. This is not typical to do in an
122 // NSView, and while correct for any one invocation of drawRect:,
123 // it causes some bad problems if that clip is cached between calls.
124 // The cached graphics state, which clip views keep around, does
125 // cache the clip in this undesirable way. Consequently, we want to
126 // release the GState for all clip views for all views contained in
127 // a WebHTMLView. Here we do it for list boxes used in forms.
128 // See these bugs for more information:
129 // <rdar://problem/3226083>: REGRESSION (Panther): white box overlaying select lists at nvidia.com drivers page
130 [[scrollView contentView] releaseGState];
132 KWQTableView *tableView = [[KWQTableView alloc] initWithListBox:this];
133 [scrollView setDocumentView:tableView];
135 [scrollView setVerticalLineScroll:[tableView rowHeight]];
137 KWQ_UNBLOCK_EXCEPTIONS;
140 QListBox::~QListBox()
142 NSScrollView *scrollView = getView();
144 KWQ_BLOCK_EXCEPTIONS;
145 NSTableView *tableView = [scrollView documentView];
146 [tableView setDelegate:nil];
147 [tableView setDataSource:nil];
148 KWQ_UNBLOCK_EXCEPTIONS;
151 void QListBox::clear()
157 void QListBox::setSelectionMode(SelectionMode mode)
159 NSScrollView *scrollView = getView();
161 KWQ_BLOCK_EXCEPTIONS;
162 NSTableView *tableView = [scrollView documentView];
163 [tableView setAllowsMultipleSelection:mode != Single];
164 KWQ_UNBLOCK_EXCEPTIONS;
167 void QListBox::appendItem(const QString &text, bool isLabel)
169 _items.append(KWQListBoxItem(text, isLabel));
173 void QListBox::doneAppendingItems()
175 KWQ_BLOCK_EXCEPTIONS;
177 NSScrollView *scrollView = getView();
178 NSTableView *tableView = [scrollView documentView];
179 [tableView reloadData];
181 KWQ_UNBLOCK_EXCEPTIONS;
184 void QListBox::setSelected(int index, bool selectIt)
188 KWQ_BLOCK_EXCEPTIONS;
190 NSScrollView *scrollView = getView();
191 NSTableView *tableView = [scrollView documentView];
192 _changingSelection = true;
194 [tableView selectRow:index byExtendingSelection:[tableView allowsMultipleSelection]];
195 [tableView scrollRowToVisible:index];
197 [tableView deselectRow:index];
200 KWQ_UNBLOCK_EXCEPTIONS;
202 _changingSelection = false;
205 bool QListBox::isSelected(int index) const
209 KWQ_BLOCK_EXCEPTIONS;
211 NSScrollView *scrollView = getView();
212 NSTableView *tableView = [scrollView documentView];
213 return [tableView isRowSelected:index];
215 KWQ_UNBLOCK_EXCEPTIONS;
220 void QListBox::setEnabled(bool enabled)
222 if (enabled != _enabled) {
223 // You would think this would work, but not until AppKit bug 2177792 if fixed.
224 //KWQ_BLOCK_EXCEPTIONS;
225 //NSTableView *tableView = [(NSScrollView *)getView() documentView];
226 //[tableView setEnabled:enabled];
227 //KWQ_UNBLOCK_EXCEPTIONS;
231 NSScrollView *scrollView = getView();
232 NSTableView *tableView = [scrollView documentView];
233 [tableView reloadData];
237 bool QListBox::isEnabled()
242 QSize QListBox::sizeForNumberOfLines(int lines) const
246 KWQ_BLOCK_EXCEPTIONS;
248 NSScrollView *scrollView = getView();
249 KWQTableView *tableView = [scrollView documentView];
253 QValueListConstIterator<KWQListBoxItem> i = const_cast<const QValueList<KWQListBoxItem> &>(_items).begin();
254 QValueListConstIterator<KWQListBoxItem> e = const_cast<const QValueList<KWQListBoxItem> &>(_items).end();
256 WebCoreTextStyle style;
257 WebCoreInitializeEmptyTextStyle(&style);
258 style.rtl = [tableView baseWritingDirection] == NSWritingDirectionRightToLeft;
260 const QString &s = (*i).string;
261 id <WebCoreTextRenderer> renderer = (*i).isGroupLabel ? groupLabelTextRenderer() : itemTextRenderer();
265 int length = s.length();
266 WebCoreInitializeTextRun(&run, reinterpret_cast<const UniChar *>(s.unicode()), length, 0, length);
268 float textWidth = [renderer floatWidthForRun:&run style:&style widths:0];
269 width = kMax(width, textWidth);
272 _width = ceilf(width);
276 size = [NSScrollView frameSizeForContentSize:NSMakeSize(_width, [tableView rowHeight] * MAX(minLines, lines))
277 hasHorizontalScroller:NO hasVerticalScroller:YES borderType:NSBezelBorder];
278 size.width += [NSScroller scrollerWidthForControlSize:NSSmallControlSize] - [NSScroller scrollerWidth] + leftMargin + rightMargin;
280 KWQ_UNBLOCK_EXCEPTIONS;
285 QWidget::FocusPolicy QListBox::focusPolicy() const
287 KWQ_BLOCK_EXCEPTIONS;
289 // Lists are only focused when full keyboard access is turned on.
290 unsigned keyboardUIMode = [KWQKHTMLPart::bridgeForWidget(this) keyboardUIMode];
291 if ((keyboardUIMode & WebCoreKeyboardAccessFull) == 0)
294 KWQ_UNBLOCK_EXCEPTIONS;
296 return QScrollView::focusPolicy();
299 bool QListBox::checksDescendantsForFocus() const
304 void QListBox::setWritingDirection(QPainter::TextDirection d)
306 KWQ_BLOCK_EXCEPTIONS;
308 NSScrollView *scrollView = getView();
309 KWQTableView *tableView = [scrollView documentView];
310 NSWritingDirection direction = d == QPainter::RTL ? NSWritingDirectionRightToLeft : NSWritingDirectionLeftToRight;
311 if ([tableView baseWritingDirection] != direction) {
312 [tableView setBaseWritingDirection:direction];
313 [tableView reloadData];
316 KWQ_UNBLOCK_EXCEPTIONS;
319 @implementation KWQListBoxScrollView
321 - (void)setFrameSize:(NSSize)size
323 [super setFrameSize:size];
324 NSTableColumn *column = [[[self documentView] tableColumns] objectAtIndex:0];
325 [column setWidth:[self contentSize].width];
326 [column setMinWidth:[self contentSize].width];
327 [column setMaxWidth:[self contentSize].width];
330 - (BOOL)becomeFirstResponder
332 KWQTableView *documentView = [self documentView];
333 QWidget *widget = [documentView widget];
334 [KWQKHTMLPart::bridgeForWidget(widget) makeFirstResponder:documentView];
340 @implementation KWQTableView
342 - (id)initWithListBox:(QListBox *)b
348 NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:nil];
350 [column setEditable:NO];
352 [self addTableColumn:column];
356 [self setAllowsMultipleSelection:NO];
357 [self setHeaderView:nil];
358 [self setIntercellSpacing:NSMakeSize(0, 0)];
359 [self setRowHeight:ceilf([itemFont() ascender] - [itemFont() descender] + bottomMargin)];
361 [self setDataSource:self];
362 [self setDelegate:self];
367 - (void)mouseDown:(NSEvent *)event
369 processingMouseEvent = TRUE;
370 [super mouseDown:event];
371 processingMouseEvent = FALSE;
373 if (clickedDuringMouseEvent) {
374 clickedDuringMouseEvent = false;
376 _box->sendConsumedMouseUp();
380 - (void)keyDown:(NSEvent *)event
382 WebCoreBridge *bridge = KWQKHTMLPart::bridgeForWidget(_box);
383 if (![bridge interceptKeyEvent:event toView:self]) {
384 [super keyDown:event];
388 - (void)keyUp:(NSEvent *)event
390 WebCoreBridge *bridge = KWQKHTMLPart::bridgeForWidget(_box);
391 if (![bridge interceptKeyEvent:event toView:self]) {
396 - (BOOL)becomeFirstResponder
398 BOOL become = [super becomeFirstResponder];
401 if (!KWQKHTMLPart::currentEventIsMouseDownInWidget(_box)) {
402 [self _KWQ_scrollFrameToVisible];
404 [self _KWQ_setKeyboardFocusRingNeedsDisplay];
405 QFocusEvent event(QEvent::FocusIn);
406 const_cast<QObject *>(_box->eventFilterObject())->eventFilter(_box, &event);
412 - (BOOL)resignFirstResponder
414 BOOL resign = [super resignFirstResponder];
416 QFocusEvent event(QEvent::FocusOut);
417 const_cast<QObject *>(_box->eventFilterObject())->eventFilter(_box, &event);
422 - (NSView *)nextKeyView
424 return _box && inNextValidKeyView
425 ? KWQKHTMLPart::nextKeyViewForWidget(_box, KWQSelectingNext)
426 : [super nextKeyView];
429 - (NSView *)previousKeyView
431 return _box && inNextValidKeyView
432 ? KWQKHTMLPart::nextKeyViewForWidget(_box, KWQSelectingPrevious)
433 : [super previousKeyView];
436 - (NSView *)nextValidKeyView
438 inNextValidKeyView = YES;
439 NSView *view = [super nextValidKeyView];
440 inNextValidKeyView = NO;
444 - (NSView *)previousValidKeyView
446 inNextValidKeyView = YES;
447 NSView *view = [super previousValidKeyView];
448 inNextValidKeyView = NO;
452 - (int)numberOfRowsInTableView:(NSTableView *)tableView
454 return _box->count();
457 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(int)row
462 - (void)tableViewSelectionDidChange:(NSNotification *)notification
464 _box->selectionChanged();
465 if (!_box->changingSelection()) {
466 if (processingMouseEvent) {
467 clickedDuringMouseEvent = true;
468 _box->sendConsumedMouseUp();
474 - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row
476 return !_box->itemAtIndex(row).isGroupLabel;
479 - (BOOL)selectionShouldChangeInTableView:(NSTableView *)aTableView
481 return _box->isEnabled();
484 - (void)drawRow:(int)row clipRect:(NSRect)clipRect
486 const KWQListBoxItem &item = _box->itemAtIndex(row);
489 if (_box->isEnabled()) {
490 if ([self isRowSelected:row] && [[self window] firstResponder] == self && ([[self window] isKeyWindow] || ![[self window] canBecomeKeyWindow])) {
491 color = [NSColor alternateSelectedControlTextColor];
493 color = [NSColor controlTextColor];
496 color = [NSColor disabledControlTextColor];
499 bool RTL = _direction == NSWritingDirectionRightToLeft;
501 id <WebCoreTextRenderer> renderer = item.isGroupLabel ? groupLabelTextRenderer() : itemTextRenderer();
503 WebCoreTextStyle style;
504 WebCoreInitializeEmptyTextStyle(&style);
506 style.textColor = color;
509 int length = item.string.length();
510 WebCoreInitializeTextRun(&run, reinterpret_cast<const UniChar *>(item.string.unicode()), length, 0, length);
512 NSRect cellRect = [self frameOfCellAtColumn:0 row:row];
515 point.x = NSMinX(cellRect) + leftMargin;
517 point.x = NSMaxX(cellRect) - rightMargin - [renderer floatWidthForRun:&run style:&style widths:0];
519 point.y = NSMaxY(cellRect) + [itemFont() descender] - bottomMargin;
521 [renderer drawRun:&run style:&style atPoint:point];
524 - (void)_KWQ_setKeyboardFocusRingNeedsDisplay
526 [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]];
534 - (void)setBaseWritingDirection:(NSWritingDirection)direction
536 _direction = direction;
539 - (NSWritingDirection)baseWritingDirection
544 - (NSCell *)_accessibilityTableCell:(int)row tableColumn:(NSTableColumn *)tableColumn
546 NSCell *cell = [super _accessibilityTableCell:row tableColumn:tableColumn];
547 [cell setStringValue:_box->itemAtIndex(row).string.getNSString()];