cbe0ff9649c5d3a211e3b64abbf0a903d38290e2
[WebKit-https.git] / WebCore / kwq / KWQComboBox.mm
1 /*
2  * Copyright (C) 2003 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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. 
24  */
25
26 #import "KWQComboBox.h"
27
28 #import "KWQButton.h"
29 #import "KWQExceptions.h"
30 #import "KWQKHTMLPart.h"
31 #import "KWQView.h"
32 #import "KWQKHTMLPart.h"
33 #import "KWQNSViewExtras.h"
34 #import "WebCoreBridge.h"
35 #import "khtmlview.h"
36 #import "render_replaced.h"
37
38 using khtml::RenderWidget;
39
40 @interface NSCell (KWQComboBoxKnowsAppKitSecrets)
41 - (NSMutableDictionary *)_textAttributes;
42 @end
43
44 enum {
45     topMargin,
46     bottomMargin,
47     leftMargin,
48     rightMargin,
49     baselineFudgeFactor,
50     widthNotIncludingText,
51     minimumTextWidth
52 };
53
54 @interface KWQComboBoxAdapter : NSObject
55 {
56     QComboBox *box;
57 }
58 - initWithQComboBox:(QComboBox *)b;
59 - (void)action:(id)sender;
60 @end
61
62 @interface KWQPopUpButtonCell : NSPopUpButtonCell <KWQWidgetHolder>
63 {
64     QWidget *widget;
65     NSWritingDirection baseWritingDirection;
66 }
67
68 - (id)initWithWidget:(QWidget *)widget;
69 - (void)setBaseWritingDirection:(NSWritingDirection)direction;
70 - (NSWritingDirection)baseWritingDirection;
71
72 @end
73
74 @interface KWQPopUpButton : NSPopUpButton <KWQWidgetHolder>
75 {
76     BOOL inNextValidKeyView;
77 }
78 @end
79
80 QComboBox::QComboBox()
81     : _adapter(0)
82     , _widthGood(false)
83     , _activated(this, SIGNAL(activated(int)))
84 {
85     KWQ_BLOCK_EXCEPTIONS;
86
87     _adapter = [[KWQComboBoxAdapter alloc] initWithQComboBox:this];
88     KWQPopUpButton *button = [[KWQPopUpButton alloc] init];
89     setView(button);
90     [button release];
91     
92     KWQPopUpButtonCell *cell = [[KWQPopUpButtonCell alloc] initWithWidget:this];
93     [button setCell:cell];
94     [cell release];
95
96     [button setTarget:_adapter];
97     [button setAction:@selector(action:)];
98
99     [[button cell] setControlSize:NSSmallControlSize];
100     [button setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
101
102     updateCurrentItem();
103
104     KWQ_UNBLOCK_EXCEPTIONS;
105 }
106
107 QComboBox::~QComboBox()
108 {
109     KWQ_BLOCK_EXCEPTIONS;
110
111     KWQPopUpButton *button = (KWQPopUpButton *)getView();
112     [button setTarget:nil];
113     [_adapter release];
114
115     KWQ_UNBLOCK_EXCEPTIONS;
116 }
117
118 void QComboBox::insertItem(const QString &text, int i)
119 {
120     KWQ_BLOCK_EXCEPTIONS;
121
122     int index = i;
123
124     KWQPopUpButton *button = (KWQPopUpButton *)getView();
125     int numItems = [button numberOfItems];
126     if (index < 0) {
127         index = numItems;
128     }
129     while (index >= numItems) {
130         [button addItemWithTitle:@""];
131         ++numItems;
132     }
133     // It's convenient that we added the item with an empty title,
134     // because addItemWithTitle will not allow multiple items with the
135     // same title. But this way, we can have such duplicate items.
136     [[button itemAtIndex:index] setTitle:text.getNSString()];
137     _widthGood = false;
138
139     updateCurrentItem();
140
141     KWQ_UNBLOCK_EXCEPTIONS;
142 }
143
144 QSize QComboBox::sizeHint() const 
145 {
146     NSSize size = {0,0};
147
148     KWQ_BLOCK_EXCEPTIONS;
149
150     KWQPopUpButton *button = (KWQPopUpButton *)getView();
151     
152     float width;
153     if (_widthGood) {
154         width = _width;
155     } else {
156         width = 0;
157         NSDictionary *attributes = [NSDictionary dictionaryWithObject:[button font] forKey:NSFontAttributeName];
158         NSEnumerator *e = [[button itemTitles] objectEnumerator];
159         NSString *text;
160         while ((text = [e nextObject])) {
161             NSSize size = [text sizeWithAttributes:attributes];
162             width = MAX(width, size.width);
163         }
164         _width = ceil(width);
165         if (_width < dimensions()[minimumTextWidth]) {
166             _width = dimensions()[minimumTextWidth];
167         }
168         _widthGood = true;
169     }
170     
171     size = [[button cell] cellSize];
172
173     KWQ_UNBLOCK_EXCEPTIONS;
174
175     return QSize((int)_width + dimensions()[widthNotIncludingText],
176         (int)size.height - (dimensions()[topMargin] + dimensions()[bottomMargin]));
177 }
178
179 QRect QComboBox::frameGeometry() const
180 {
181     QRect r = QWidget::frameGeometry();
182     return QRect(r.x() + dimensions()[leftMargin], r.y() + dimensions()[topMargin],
183         r.width() - (dimensions()[leftMargin] + dimensions()[rightMargin]),
184         r.height() - (dimensions()[topMargin] + dimensions()[bottomMargin]));
185 }
186
187 void QComboBox::setFrameGeometry(const QRect &r)
188 {
189     QWidget::setFrameGeometry(QRect(-dimensions()[leftMargin] + r.x(), -dimensions()[topMargin] + r.y(),
190         dimensions()[leftMargin] + r.width() + dimensions()[rightMargin],
191         dimensions()[topMargin] + r.height() + dimensions()[bottomMargin]));
192 }
193
194 int QComboBox::baselinePosition(int height) const
195 {
196     // Menu text is at the top.
197     KWQPopUpButton *button = (KWQPopUpButton *)getView();
198     return (int)ceil(-dimensions()[topMargin] + dimensions()[baselineFudgeFactor] + [[button font] ascender]);
199 }
200
201 void QComboBox::clear()
202 {
203     KWQPopUpButton *button = (KWQPopUpButton *)getView();
204     [button removeAllItems];
205     _widthGood = false;
206     updateCurrentItem();
207 }
208
209 void QComboBox::setCurrentItem(int index)
210 {
211     KWQ_BLOCK_EXCEPTIONS;
212
213     KWQPopUpButton *button = (KWQPopUpButton *)getView();
214     [button selectItemAtIndex:index];
215
216     KWQ_UNBLOCK_EXCEPTIONS;
217
218     updateCurrentItem();
219 }
220
221 bool QComboBox::updateCurrentItem() const
222 {
223     KWQPopUpButton *button = (KWQPopUpButton *)getView();
224
225     KWQ_BLOCK_EXCEPTIONS;
226     int i = [button indexOfSelectedItem];
227
228     if (_currentItem == i) {
229         return false;
230     }
231     _currentItem = i;
232     KWQ_UNBLOCK_EXCEPTIONS;
233
234     return true;
235 }
236
237 void QComboBox::itemSelected()
238 {
239     if (updateCurrentItem()) {
240         _activated.call(_currentItem);
241     }
242 }
243
244 void QComboBox::setFont(const QFont &f)
245 {
246     QWidget::setFont(f);
247
248     const NSControlSize size = KWQNSControlSizeForFont(f);
249     NSControl * const button = static_cast<NSControl *>(getView());
250
251     KWQ_BLOCK_EXCEPTIONS;
252
253     if (size != [[button cell] controlSize]) {
254         [[button cell] setControlSize:size];
255         [button setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:size]]];
256         _widthGood = false;
257     }
258
259     KWQ_UNBLOCK_EXCEPTIONS;
260 }
261
262 const int *QComboBox::dimensions() const
263 {
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 }
270     };
271     NSControl * const button = static_cast<NSControl *>(getView());
272
273     KWQ_BLOCK_EXCEPTIONS;
274     return  w[[[button cell] controlSize]];
275     KWQ_UNBLOCK_EXCEPTIONS;
276
277     return w[NSSmallControlSize];
278 }
279
280 QWidget::FocusPolicy QComboBox::focusPolicy() const
281 {
282     KWQ_BLOCK_EXCEPTIONS;
283     
284     // Add an additional check here.
285     // For now, selects are only focused when full
286     // keyboard access is turned on.
287     unsigned keyboardUIMode = [KWQKHTMLPart::bridgeForWidget(this) keyboardUIMode];
288     if ((keyboardUIMode & WebCoreKeyboardAccessFull) == 0)
289         return NoFocus;
290     
291     KWQ_UNBLOCK_EXCEPTIONS;
292     
293     return QWidget::focusPolicy();
294 }
295
296 void QComboBox::setWritingDirection(QPainter::TextDirection direction)
297 {
298     KWQ_BLOCK_EXCEPTIONS;
299
300     KWQPopUpButton *button = getView();
301     KWQPopUpButtonCell *cell = [button cell];
302     NSWritingDirection d = direction == QPainter::RTL ? NSWritingDirectionRightToLeft : NSWritingDirectionLeftToRight;
303     if ([cell baseWritingDirection] != d) {
304         [cell setBaseWritingDirection:d];
305         [button setNeedsDisplay:YES];
306     }
307
308     KWQ_UNBLOCK_EXCEPTIONS;
309 }
310
311 @implementation KWQComboBoxAdapter
312
313 - initWithQComboBox:(QComboBox *)b
314 {
315     box = b;
316     return [super init];
317 }
318
319 - (void)action:(id)sender
320 {
321     box->itemSelected();
322 }
323
324 @end
325
326 @implementation KWQPopUpButtonCell
327
328 - initWithWidget:(QWidget *)w
329 {
330     [super init];
331     widget = w;
332     return self;
333 }
334
335 - (BOOL)trackMouse:(NSEvent *)event inRect:(NSRect)rect ofView:(NSView *)view untilMouseUp:(BOOL)flag
336 {
337     WebCoreBridge *bridge = [KWQKHTMLPart::bridgeForWidget(widget) retain];
338     BOOL result = [super trackMouse:event inRect:rect ofView:view untilMouseUp:flag];
339     if (result) {
340         // Give KHTML a chance to fix up its event state, since the popup eats all the
341         // events during tracking.  [NSApp currentEvent] is still the original mouseDown
342         // at this point!
343         [bridge part]->sendFakeEventsAfterWidgetTracking(event);
344     }
345     [bridge release];
346     return result;
347 }
348
349 - (QWidget *)widget
350 {
351     return widget;
352 }
353
354 - (void)setBaseWritingDirection:(NSWritingDirection)direction
355 {
356     baseWritingDirection = direction;
357 }
358
359 - (NSWritingDirection)baseWritingDirection
360 {
361     return baseWritingDirection;
362 }
363
364 - (NSMutableDictionary *)_textAttributes
365 {
366     NSMutableDictionary *attributes = [super _textAttributes];
367     NSParagraphStyle *style = [attributes objectForKey:NSParagraphStyleAttributeName];
368     ASSERT(style != nil);
369     if ([style baseWritingDirection] != baseWritingDirection) {
370         NSMutableParagraphStyle *mutableStyle = [style mutableCopy];
371         [mutableStyle setBaseWritingDirection:baseWritingDirection];
372         [attributes setObject:mutableStyle forKey:NSParagraphStyleAttributeName];
373         [mutableStyle release];
374     }
375     return attributes;
376 }
377
378 @end
379
380 @implementation KWQPopUpButton
381
382 - (QWidget *)widget
383 {
384     return [(KWQPopUpButtonCell *)[self cell] widget];
385 }
386
387 - (BOOL)becomeFirstResponder
388 {
389     BOOL become = [super becomeFirstResponder];
390     if (become) {
391         QWidget *widget = [self widget];
392         if (!KWQKHTMLPart::currentEventIsMouseDownInWidget(widget)) {
393             [self _KWQ_scrollFrameToVisible];
394         }
395         QFocusEvent event(QEvent::FocusIn);
396         const_cast<QObject *>(widget->eventFilterObject())->eventFilter(widget, &event);
397     }
398     return become;
399 }
400
401 - (BOOL)resignFirstResponder
402 {
403     BOOL resign = [super resignFirstResponder];
404     if (resign) {
405         QWidget *widget = [self widget];
406         QFocusEvent event(QEvent::FocusOut);
407         const_cast<QObject *>(widget->eventFilterObject())->eventFilter(widget, &event);
408     }
409     return resign;
410 }
411
412 -(NSView *)nextKeyView
413 {
414     QWidget *widget = [self widget];
415     return widget && inNextValidKeyView
416         ? KWQKHTMLPart::nextKeyViewForWidget(widget, KWQSelectingNext)
417         : [super nextKeyView];
418 }
419
420 -(NSView *)previousKeyView
421 {
422     QWidget *widget = [self widget];
423     return widget && inNextValidKeyView
424         ? KWQKHTMLPart::nextKeyViewForWidget(widget, KWQSelectingPrevious)
425         : [super previousKeyView];
426 }
427
428 -(NSView *)nextValidKeyView
429 {
430     inNextValidKeyView = YES;
431     NSView *view = [super nextValidKeyView];
432     inNextValidKeyView = NO;
433     return view;
434 }
435
436 -(NSView *)previousValidKeyView
437 {
438     inNextValidKeyView = YES;
439     NSView *view = [super previousValidKeyView];
440     inNextValidKeyView = NO;
441     return view;
442 }
443
444 @end