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 "KWQComboBox.h"
28 #import "KWQAssertions.h"
30 #import "KWQExceptions.h"
31 #import "KWQKHTMLPart.h"
32 #import "KWQNSViewExtras.h"
34 #import "WebCoreBridge.h"
35 #import "WebCoreTextRenderer.h"
36 #import "WebCoreTextRendererFactory.h"
38 @interface NSCell (KWQComboBoxKnowsAppKitSecrets)
39 - (NSMutableDictionary *)_textAttributes;
48 widthNotIncludingText,
52 @interface KWQComboBoxAdapter : NSObject
56 - (id)initWithQComboBox:(QComboBox *)b;
57 - (void)action:(id)sender;
60 @interface KWQPopUpButtonCell : NSPopUpButtonCell <KWQWidgetHolder>
63 NSWritingDirection baseWritingDirection;
65 - (id)initWithQComboBox:(QComboBox *)b;
66 - (void)setBaseWritingDirection:(NSWritingDirection)direction;
67 - (NSWritingDirection)baseWritingDirection;
70 @interface KWQPopUpButton : NSPopUpButton <KWQWidgetHolder>
72 BOOL inNextValidKeyView;
76 QComboBox::QComboBox()
80 , _menuPopulated(true)
81 , _activated(this, SIGNAL(activated(int)))
85 _adapter = [[KWQComboBoxAdapter alloc] initWithQComboBox:this];
86 KWQPopUpButton *button = [[KWQPopUpButton alloc] init];
90 KWQPopUpButtonCell *cell = [[KWQPopUpButtonCell alloc] initWithQComboBox:this];
91 [button setCell:cell];
94 [button setTarget:_adapter];
95 [button setAction:@selector(action:)];
97 [[button cell] setControlSize:NSSmallControlSize];
98 [button setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
100 KWQ_UNBLOCK_EXCEPTIONS;
103 QComboBox::~QComboBox()
105 KWQ_BLOCK_EXCEPTIONS;
107 KWQPopUpButton *button = (KWQPopUpButton *)getView();
108 [button setTarget:nil];
111 KWQ_UNBLOCK_EXCEPTIONS;
114 void QComboBox::appendItem(const QString &text)
116 KWQ_BLOCK_EXCEPTIONS;
119 if (_menuPopulated) {
120 KWQPopUpButton *button = (KWQPopUpButton *)getView();
121 if (![[button cell] isHighlighted]) {
122 _menuPopulated = false;
124 // We must add the item with no title and then set the title because
125 // addItemWithTitle does not allow duplicate titles.
126 [button addItemWithTitle:@""];
127 [[button lastItem] setTitle:text.getNSString()];
132 KWQ_UNBLOCK_EXCEPTIONS;
135 QSize QComboBox::sizeHint() const
137 KWQ_BLOCK_EXCEPTIONS;
139 KWQPopUpButton *button = (KWQPopUpButton *)getView();
143 QValueListConstIterator<QString> i = const_cast<const QStringList &>(_items).begin();
144 QValueListConstIterator<QString> e = const_cast<const QStringList &>(_items).end();
146 id <WebCoreTextRenderer> renderer = [[WebCoreTextRendererFactory sharedFactory]
147 rendererWithFont:[button font] usingPrinterFont:![NSGraphicsContext currentContextDrawingToScreen]];
148 WebCoreTextStyle style;
149 WebCoreInitializeEmptyTextStyle(&style);
151 const QString &s = *i;
155 int length = s.length();
156 WebCoreInitializeTextRun(&run, reinterpret_cast<const UniChar *>(s.unicode()), length, 0, length);
158 float textWidth = [renderer floatWidthForRun:&run style:&style widths:0];
159 width = kMax(width, textWidth);
162 _width = kMax(static_cast<int>(ceilf(width)), dimensions()[minimumTextWidth]);
166 return QSize(_width + dimensions()[widthNotIncludingText],
167 static_cast<int>([[button cell] cellSize].height) - (dimensions()[topMargin] + dimensions()[bottomMargin]));
169 KWQ_UNBLOCK_EXCEPTIONS;
174 QRect QComboBox::frameGeometry() const
176 QRect r = QWidget::frameGeometry();
177 return QRect(r.x() + dimensions()[leftMargin], r.y() + dimensions()[topMargin],
178 r.width() - (dimensions()[leftMargin] + dimensions()[rightMargin]),
179 r.height() - (dimensions()[topMargin] + dimensions()[bottomMargin]));
182 void QComboBox::setFrameGeometry(const QRect &r)
184 QWidget::setFrameGeometry(QRect(-dimensions()[leftMargin] + r.x(), -dimensions()[topMargin] + r.y(),
185 dimensions()[leftMargin] + r.width() + dimensions()[rightMargin],
186 dimensions()[topMargin] + r.height() + dimensions()[bottomMargin]));
189 int QComboBox::baselinePosition(int height) const
191 // Menu text is at the top.
192 KWQPopUpButton *button = (KWQPopUpButton *)getView();
193 return static_cast<int>(ceilf(-dimensions()[topMargin] + dimensions()[baselineFudgeFactor] + [[button font] ascender]));
196 void QComboBox::clear()
198 KWQPopUpButton *button = (KWQPopUpButton *)getView();
199 [button removeAllItems];
203 _menuPopulated = true;
206 void QComboBox::setCurrentItem(int index)
208 ASSERT(index < (int)_items.count());
210 KWQ_BLOCK_EXCEPTIONS;
212 KWQPopUpButton *button = (KWQPopUpButton *)getView();
213 if (_menuPopulated) {
214 [button selectItemAtIndex:index];
216 [button removeAllItems];
217 [button addItemWithTitle:@""];
218 [[button itemAtIndex:0] setTitle:_items[index].getNSString()];
221 KWQ_UNBLOCK_EXCEPTIONS;
223 _currentItem = index;
226 void QComboBox::itemSelected()
228 ASSERT(_menuPopulated);
230 KWQ_BLOCK_EXCEPTIONS;
232 KWQPopUpButton *button = (KWQPopUpButton *)getView();
233 int i = [button indexOfSelectedItem];
234 if (_currentItem == i) {
239 KWQ_UNBLOCK_EXCEPTIONS;
241 _activated.call(_currentItem);
244 void QComboBox::setFont(const QFont &f)
248 const NSControlSize size = KWQNSControlSizeForFont(f);
249 NSControl * const button = static_cast<NSControl *>(getView());
251 KWQ_BLOCK_EXCEPTIONS;
253 if (size != [[button cell] controlSize]) {
254 [[button cell] setControlSize:size];
255 [button setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:size]]];
259 KWQ_UNBLOCK_EXCEPTIONS;
262 const int *QComboBox::dimensions() const
264 // We empirically determined these dimensions.
265 // It would be better to get this info from AppKit somehow.
266 static const int w[3][7] = {
267 { 2, 3, 3, 3, 4, 34, 9 },
268 { 1, 3, 3, 3, 3, 31, 5 },
269 { 0, 0, 1, 1, 2, 32, 0 }
271 NSControl * const button = static_cast<NSControl *>(getView());
273 KWQ_BLOCK_EXCEPTIONS;
274 return w[[[button cell] controlSize]];
275 KWQ_UNBLOCK_EXCEPTIONS;
277 return w[NSSmallControlSize];
280 QWidget::FocusPolicy QComboBox::focusPolicy() const
282 KWQ_BLOCK_EXCEPTIONS;
284 // Menus are only focused when full keyboard access is turned on.
285 unsigned keyboardUIMode = [KWQKHTMLPart::bridgeForWidget(this) keyboardUIMode];
286 if ((keyboardUIMode & WebCoreKeyboardAccessFull) == 0)
289 KWQ_UNBLOCK_EXCEPTIONS;
291 return QWidget::focusPolicy();
294 void QComboBox::setWritingDirection(QPainter::TextDirection direction)
296 KWQ_BLOCK_EXCEPTIONS;
298 KWQPopUpButton *button = getView();
299 KWQPopUpButtonCell *cell = [button cell];
300 NSWritingDirection d = direction == QPainter::RTL ? NSWritingDirectionRightToLeft : NSWritingDirectionLeftToRight;
301 if ([cell baseWritingDirection] != d) {
302 [cell setBaseWritingDirection:d];
303 [button setNeedsDisplay:YES];
306 KWQ_UNBLOCK_EXCEPTIONS;
309 void QComboBox::populateMenu()
311 if (!_menuPopulated) {
312 KWQ_BLOCK_EXCEPTIONS;
314 KWQPopUpButton *button = getView();
315 [button removeAllItems];
316 QValueListConstIterator<QString> i = const_cast<const QStringList &>(_items).begin();
317 QValueListConstIterator<QString> e = const_cast<const QStringList &>(_items).end();
318 for (; i != e; ++i) {
319 // We must add the item with no title and then set the title because
320 // addItemWithTitle does not allow duplicate titles.
321 [button addItemWithTitle:@""];
322 [[button lastItem] setTitle:(*i).getNSString()];
324 [button selectItemAtIndex:_currentItem];
326 KWQ_UNBLOCK_EXCEPTIONS;
328 _menuPopulated = true;
332 @implementation KWQComboBoxAdapter
334 - (id)initWithQComboBox:(QComboBox *)b
340 - (void)action:(id)sender
347 @implementation KWQPopUpButtonCell
349 - (id)initWithQComboBox:(QComboBox *)b
355 - (BOOL)trackMouse:(NSEvent *)event inRect:(NSRect)rect ofView:(NSView *)view untilMouseUp:(BOOL)flag
357 WebCoreBridge *bridge = [KWQKHTMLPart::bridgeForWidget(box) retain];
358 BOOL result = [super trackMouse:event inRect:rect ofView:view untilMouseUp:flag];
360 // Give KHTML a chance to fix up its event state, since the popup eats all the
361 // events during tracking. [NSApp currentEvent] is still the original mouseDown
363 [bridge part]->sendFakeEventsAfterWidgetTracking(event);
374 - (void)setBaseWritingDirection:(NSWritingDirection)direction
376 baseWritingDirection = direction;
379 - (NSWritingDirection)baseWritingDirection
381 return baseWritingDirection;
384 - (NSMutableDictionary *)_textAttributes
386 NSMutableDictionary *attributes = [super _textAttributes];
387 NSParagraphStyle *style = [attributes objectForKey:NSParagraphStyleAttributeName];
388 ASSERT(style != nil);
389 if ([style baseWritingDirection] != baseWritingDirection) {
390 NSMutableParagraphStyle *mutableStyle = [style mutableCopy];
391 [mutableStyle setBaseWritingDirection:baseWritingDirection];
392 [attributes setObject:mutableStyle forKey:NSParagraphStyleAttributeName];
393 [mutableStyle release];
398 - (void)setHighlighted:(BOOL)highlighted
403 [super setHighlighted:highlighted];
408 @implementation KWQPopUpButton
412 return [(KWQPopUpButtonCell *)[self cell] widget];
415 - (BOOL)becomeFirstResponder
417 BOOL become = [super becomeFirstResponder];
419 QWidget *widget = [self widget];
420 if (!KWQKHTMLPart::currentEventIsMouseDownInWidget(widget)) {
421 [self _KWQ_scrollFrameToVisible];
423 QFocusEvent event(QEvent::FocusIn);
424 const_cast<QObject *>(widget->eventFilterObject())->eventFilter(widget, &event);
429 - (BOOL)resignFirstResponder
431 BOOL resign = [super resignFirstResponder];
433 QWidget *widget = [self widget];
434 QFocusEvent event(QEvent::FocusOut);
435 const_cast<QObject *>(widget->eventFilterObject())->eventFilter(widget, &event);
440 - (NSView *)nextKeyView
442 QWidget *widget = [self widget];
443 return widget && inNextValidKeyView
444 ? KWQKHTMLPart::nextKeyViewForWidget(widget, KWQSelectingNext)
445 : [super nextKeyView];
448 - (NSView *)previousKeyView
450 QWidget *widget = [self widget];
451 return widget && inNextValidKeyView
452 ? KWQKHTMLPart::nextKeyViewForWidget(widget, KWQSelectingPrevious)
453 : [super previousKeyView];
456 - (NSView *)nextValidKeyView
458 inNextValidKeyView = YES;
459 NSView *view = [super nextValidKeyView];
460 inNextValidKeyView = NO;
464 - (NSView *)previousValidKeyView
466 inNextValidKeyView = YES;
467 NSView *view = [super previousValidKeyView];
468 inNextValidKeyView = NO;