196377a78835a0308ca37f97181fe602138bf40d
[WebKit-https.git] / WebCore / kwq / KWQListBox.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 "KWQListBox.h"
27
28 #import "KWQAssertions.h"
29 #import "KWQExceptions.h"
30 #import "KWQKHTMLPart.h"
31 #import "KWQNSViewExtras.h"
32 #import "KWQView.h"
33 #import "WebCoreBridge.h"
34 #import "WebCoreScrollView.h"
35
36 #define MIN_LINES 4 /* ensures we have a scroll bar */
37
38 @interface KWQListBoxScrollView : WebCoreScrollView
39 @end
40
41 @interface KWQTableView : NSTableView <KWQWidgetHolder>
42 {
43     QListBox *_box;
44     NSArray *_items;
45     BOOL processingMouseEvent;
46     BOOL clickedDuringMouseEvent;
47     BOOL inNextValidKeyView;
48     NSWritingDirection _direction;
49 }
50 - initWithListBox:(QListBox *)b items:(NSArray *)items;
51 - (void)_KWQ_setKeyboardFocusRingNeedsDisplay;
52 - (QWidget *)widget;
53 - (void)setBaseWritingDirection:(NSWritingDirection)direction;
54 - (NSWritingDirection)baseWritingDirection;
55 @end
56
57 static NSFont *itemFont()
58 {
59     static NSFont *font = [[NSFont systemFontOfSize:[NSFont smallSystemFontSize]] retain];
60     return font;
61 }
62
63 static NSFont *groupLabelFont()
64 {
65     static NSFont *font = [[NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]] retain];
66     return font;
67 }
68
69 static NSParagraphStyle *paragraphStyle(NSWritingDirection direction)
70 {
71     static NSParagraphStyle *leftStyle;
72     static NSParagraphStyle *rightStyle;
73     NSParagraphStyle **style = direction == NSWritingDirectionRightToLeft ? &rightStyle : &leftStyle;
74     if (*style == nil) {
75         NSMutableParagraphStyle *mutableStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
76         [mutableStyle setBaseWritingDirection:direction];
77         *style = [mutableStyle copy];
78         [mutableStyle release];
79     }
80     return *style;
81 }
82
83 static NSDictionary *stringAttributes(NSWritingDirection direction, bool isGroupLabel)
84 {
85     static NSDictionary *attributeGlobals[4];
86     NSDictionary **attributes = &attributeGlobals[(direction == NSWritingDirectionRightToLeft ? 0 : 1) + (isGroupLabel ? 0 : 2)];
87     if (*attributes == nil) {
88         *attributes = [[NSDictionary dictionaryWithObjectsAndKeys:
89             isGroupLabel ? groupLabelFont() : itemFont(), NSFontAttributeName,
90             paragraphStyle(direction), NSParagraphStyleAttributeName,
91             nil] retain];
92     }
93     return *attributes;
94 }
95
96 QListBox::QListBox(QWidget *parent)
97     : QScrollView(parent)
98     , _insertingItems(false)
99     , _changingSelection(false)
100     , _enabled(true)
101     , _widthGood(false)
102     , _clicked(this, SIGNAL(clicked(QListBoxItem *)))
103     , _selectionChanged(this, SIGNAL(selectionChanged()))
104 {
105     KWQ_BLOCK_EXCEPTIONS;
106
107     _items = [[NSMutableArray alloc] init];
108     NSScrollView *scrollView = [[KWQListBoxScrollView alloc] init];
109     setView(scrollView);
110     [scrollView release];
111     
112     [scrollView setBorderType:NSBezelBorder];
113     [scrollView setHasVerticalScroller:YES];
114     [[scrollView verticalScroller] setControlSize:NSSmallControlSize];
115
116     // In WebHTMLView, we set a clip. This is not typical to do in an
117     // NSView, and while correct for any one invocation of drawRect:,
118     // it causes some bad problems if that clip is cached between calls.
119     // The cached graphics state, which clip views keep around, does
120     // cache the clip in this undesirable way. Consequently, we want to 
121     // release the GState for all clip views for all views contained in 
122     // a WebHTMLView. Here we do it for list boxes used in forms.
123     // See these bugs for more information:
124     // <rdar://problem/3226083>: REGRESSION (Panther): white box overlaying select lists at nvidia.com drivers page
125     [[scrollView contentView] releaseGState];
126     
127     KWQTableView *tableView = [[KWQTableView alloc] initWithListBox:this items:_items];
128     [scrollView setDocumentView:tableView];
129     [tableView release];
130     [scrollView setVerticalLineScroll:[tableView rowHeight]];
131     
132     KWQ_UNBLOCK_EXCEPTIONS;
133 }
134
135 QListBox::~QListBox()
136 {
137     NSScrollView *scrollView = getView();
138     
139     KWQ_BLOCK_EXCEPTIONS;
140     NSTableView *tableView = [scrollView documentView];
141     [tableView setDelegate:nil];
142     [tableView setDataSource:nil];
143     [_items release];
144     KWQ_UNBLOCK_EXCEPTIONS;
145 }
146
147 uint QListBox::count() const
148 {
149     KWQ_BLOCK_EXCEPTIONS;
150     return [_items count];
151     KWQ_UNBLOCK_EXCEPTIONS;
152
153     return 0;
154 }
155
156 void QListBox::clear()
157 {
158     KWQ_BLOCK_EXCEPTIONS;
159     [_items removeAllObjects];
160     if (!_insertingItems) {
161         NSScrollView *scrollView = getView();
162         NSTableView *tableView = [scrollView documentView];
163         [tableView reloadData];
164     }
165     KWQ_UNBLOCK_EXCEPTIONS;
166     _widthGood = NO;
167 }
168
169 void QListBox::setSelectionMode(SelectionMode mode)
170 {
171     NSScrollView *scrollView = getView();
172
173     KWQ_BLOCK_EXCEPTIONS;
174     NSTableView *tableView = [scrollView documentView];
175     [tableView setAllowsMultipleSelection:mode != Single];
176     KWQ_UNBLOCK_EXCEPTIONS;
177 }
178
179 void QListBox::insertItem(const QString &text, int index, bool isLabel)
180 {
181     ASSERT(index >= 0);
182
183     KWQ_BLOCK_EXCEPTIONS;
184
185     NSScrollView *scrollView = getView();
186     KWQTableView *tableView = [scrollView documentView];
187
188     NSAttributedString *s = [[NSAttributedString alloc] initWithString:text.getNSString()
189         attributes:stringAttributes([tableView baseWritingDirection], isLabel)];
190
191     int c = count();
192     if (index >= c) {
193         [_items addObject:s];
194     } else {
195         [_items replaceObjectAtIndex:index withObject:s];
196     }
197  
198     [s release];
199
200     if (!_insertingItems) {
201         [tableView reloadData];
202     }
203
204     KWQ_UNBLOCK_EXCEPTIONS;
205
206     _widthGood = NO;
207 }
208
209 void QListBox::beginBatchInsert()
210 {
211     ASSERT(!_insertingItems);
212     _insertingItems = true;
213 }
214
215 void QListBox::endBatchInsert()
216 {
217     ASSERT(_insertingItems);
218     _insertingItems = false;
219
220     KWQ_BLOCK_EXCEPTIONS;
221
222     NSScrollView *scrollView = getView();
223     NSTableView *tableView = [scrollView documentView];
224     [tableView reloadData];
225
226     KWQ_UNBLOCK_EXCEPTIONS;
227 }
228
229 void QListBox::setSelected(int index, bool selectIt)
230 {
231     ASSERT(index >= 0);
232     ASSERT(!_insertingItems);
233
234     KWQ_BLOCK_EXCEPTIONS;
235
236     NSScrollView *scrollView = getView();
237     NSTableView *tableView = [scrollView documentView];
238     _changingSelection = true;
239     if (selectIt) {
240         [tableView selectRow:index byExtendingSelection:[tableView allowsMultipleSelection]];
241         [tableView scrollRowToVisible:index];
242     } else {
243         [tableView deselectRow:index];
244     }
245
246     KWQ_UNBLOCK_EXCEPTIONS;
247
248     _changingSelection = false;
249 }
250
251 bool QListBox::isSelected(int index) const
252 {
253     ASSERT(index >= 0);
254     ASSERT(!_insertingItems);
255
256     KWQ_BLOCK_EXCEPTIONS;
257
258     NSScrollView *scrollView = getView();
259     NSTableView *tableView = [scrollView documentView];
260     return [tableView isRowSelected:index]; 
261
262     KWQ_UNBLOCK_EXCEPTIONS;
263
264     return false;
265 }
266
267 void QListBox::setEnabled(bool enabled)
268 {
269     _enabled = enabled;
270     // You would think this would work, but not until AK fixes 2177792
271     //KWQ_BLOCK_EXCEPTIONS;
272     //NSTableView *tableView = [(NSScrollView *)getView() documentView];
273     //[tableView setEnabled:enabled];
274     //KWQ_UNBLOCK_EXCEPTIONS;
275 }
276
277 bool QListBox::isEnabled()
278 {
279     return _enabled;
280 }
281
282 QSize QListBox::sizeForNumberOfLines(int lines) const
283 {
284     ASSERT(!_insertingItems);
285
286     NSScrollView *scrollView = getView();
287
288     NSSize size = {0,0};
289
290     KWQ_BLOCK_EXCEPTIONS;
291     NSTableView *tableView = [scrollView documentView];
292     
293     float width;
294     if (_widthGood) {
295         width = _width;
296     } else {
297         width = 0;
298         NSCell *cell = [[[tableView tableColumns] objectAtIndex:0] dataCell];
299         NSEnumerator *e = [_items objectEnumerator];
300         NSString *text;
301         while ((text = [e nextObject])) {
302             [cell setStringValue:text];
303             NSSize size = [cell cellSize];
304             width = MAX(width, size.width);
305         }
306         _width = width;
307         _widthGood = YES;
308     }
309     
310     NSSize contentSize;
311     contentSize.width = ceil(width);
312     contentSize.height = ceil(([tableView rowHeight] + [tableView intercellSpacing].height) * MAX(MIN_LINES, lines));
313     size = [NSScrollView frameSizeForContentSize:contentSize
314         hasHorizontalScroller:NO hasVerticalScroller:YES borderType:NSBezelBorder];
315     KWQ_UNBLOCK_EXCEPTIONS;
316
317     return QSize(size);
318 }
319
320 QWidget::FocusPolicy QListBox::focusPolicy() const
321 {
322     KWQ_BLOCK_EXCEPTIONS;
323     
324     // Add an additional check here.
325     // For now, selects are only focused when full
326     // keyboard access is turned on.
327     unsigned keyboardUIMode = [KWQKHTMLPart::bridgeForWidget(this) keyboardUIMode];
328     if ((keyboardUIMode & WebCoreKeyboardAccessFull) == 0)
329         return NoFocus;
330     
331     KWQ_UNBLOCK_EXCEPTIONS;
332     
333     return QScrollView::focusPolicy();
334 }
335
336 bool QListBox::checksDescendantsForFocus() const
337 {
338     return true;
339 }
340
341 void QListBox::setWritingDirection(QPainter::TextDirection d)
342 {
343     KWQ_BLOCK_EXCEPTIONS;
344
345     NSScrollView *scrollView = getView();
346     KWQTableView *tableView = [scrollView documentView];
347     NSWritingDirection direction = d == QPainter::RTL ? NSWritingDirectionRightToLeft : NSWritingDirectionLeftToRight;
348     if ([tableView baseWritingDirection] != direction) {
349         int n = count();
350         for (int i = 0; i < n; i++) {
351             NSAttributedString *o = [_items objectAtIndex:i];
352             NSAttributedString *s = [[NSAttributedString alloc] initWithString:[o string]
353                 attributes:stringAttributes([tableView baseWritingDirection], itemIsGroupLabel(i))];
354             [_items replaceObjectAtIndex:i withObject:s];
355             [s release];
356         }
357         [tableView setBaseWritingDirection:direction];
358         [tableView reloadData];
359     }
360
361     KWQ_UNBLOCK_EXCEPTIONS;
362 }
363
364 bool QListBox::itemIsGroupLabel(int index) const
365 {
366     ASSERT(index >= 0);
367
368     KWQ_BLOCK_EXCEPTIONS;
369
370     NSAttributedString *s = [_items objectAtIndex:index];
371     NSFont *f = [s attribute:NSFontAttributeName atIndex:0 effectiveRange:NULL];
372     return f == groupLabelFont();
373
374     KWQ_UNBLOCK_EXCEPTIONS;
375
376     return false;
377 }
378
379 @implementation KWQListBoxScrollView
380
381 - (void)setFrameSize:(NSSize)size
382 {
383     [super setFrameSize:size];
384     NSTableColumn *column = [[[self documentView] tableColumns] objectAtIndex:0];
385     [column setWidth:[self contentSize].width];
386     [column setMinWidth:[self contentSize].width];
387     [column setMaxWidth:[self contentSize].width];
388 }
389
390 - (BOOL)becomeFirstResponder
391 {
392     KWQTableView *documentView = [self documentView];
393     QWidget *widget = [documentView widget];
394     [KWQKHTMLPart::bridgeForWidget(widget) makeFirstResponder:documentView];
395     return YES;
396 }
397
398 @end
399
400 @implementation KWQTableView
401
402 - initWithListBox:(QListBox *)b items:(NSArray *)i
403 {
404     [super init];
405     _box = b;
406     _items = i;
407
408     NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:nil];
409
410     [column setEditable:NO];
411     [[column dataCell] setFont:itemFont()];
412
413     [self addTableColumn:column];
414
415     [column release];
416     
417     [self setAllowsMultipleSelection:NO];
418     [self setHeaderView:nil];
419     [self setIntercellSpacing:NSMakeSize(0, 0)];
420     [self setRowHeight:ceil([[column dataCell] cellSize].height)];
421     
422     [self setDataSource:self];
423     [self setDelegate:self];
424
425     return self;
426 }
427
428 -(void)mouseDown:(NSEvent *)event
429 {
430     processingMouseEvent = TRUE;
431     [super mouseDown:event];
432     processingMouseEvent = FALSE;
433
434     if (clickedDuringMouseEvent) {
435         clickedDuringMouseEvent = false;
436     } else {
437         _box->sendConsumedMouseUp();
438     }
439 }
440
441 - (void)keyDown:(NSEvent *)event
442 {
443     WebCoreBridge *bridge = KWQKHTMLPart::bridgeForWidget(_box);
444     if (![bridge interceptKeyEvent:event toView:self]) {
445         [super keyDown:event];
446     }
447 }
448
449 - (void)keyUp:(NSEvent *)event
450 {
451     WebCoreBridge *bridge = KWQKHTMLPart::bridgeForWidget(_box);
452     if (![bridge interceptKeyEvent:event toView:self]) {
453         [super keyUp:event];
454     }
455 }
456
457 - (BOOL)becomeFirstResponder
458 {
459     BOOL become = [super becomeFirstResponder];
460     
461     if (become) {
462         if (!KWQKHTMLPart::currentEventIsMouseDownInWidget(_box)) {
463             [self _KWQ_scrollFrameToVisible];
464         }        
465         [self _KWQ_setKeyboardFocusRingNeedsDisplay];
466         QFocusEvent event(QEvent::FocusIn);
467         const_cast<QObject *>(_box->eventFilterObject())->eventFilter(_box, &event);
468     }
469
470     return become;
471 }
472
473 - (BOOL)resignFirstResponder
474 {
475     BOOL resign = [super resignFirstResponder];
476     if (resign) {
477         QFocusEvent event(QEvent::FocusOut);
478         const_cast<QObject *>(_box->eventFilterObject())->eventFilter(_box, &event);
479     }
480     return resign;
481 }
482
483 -(NSView *)nextKeyView
484 {
485     return _box && inNextValidKeyView
486         ? KWQKHTMLPart::nextKeyViewForWidget(_box, KWQSelectingNext)
487         : [super nextKeyView];
488 }
489
490 -(NSView *)previousKeyView
491 {
492     return _box && inNextValidKeyView
493         ? KWQKHTMLPart::nextKeyViewForWidget(_box, KWQSelectingPrevious)
494         : [super previousKeyView];
495 }
496
497 -(NSView *)nextValidKeyView
498 {
499     inNextValidKeyView = YES;
500     NSView *view = [super nextValidKeyView];
501     inNextValidKeyView = NO;
502     return view;
503 }
504
505 -(NSView *)previousValidKeyView
506 {
507     inNextValidKeyView = YES;
508     NSView *view = [super previousValidKeyView];
509     inNextValidKeyView = NO;
510     return view;
511 }
512
513 - (int)numberOfRowsInTableView:(NSTableView *)tableView
514 {
515     return [_items count];
516 }
517
518 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(int)row
519 {
520     return [_items objectAtIndex:row];
521 }
522
523 - (void)tableViewSelectionDidChange:(NSNotification *)notification
524 {
525     _box->selectionChanged();
526     if (!_box->changingSelection()) {
527         if (processingMouseEvent) {
528             clickedDuringMouseEvent = true;
529             _box->sendConsumedMouseUp();
530         }
531         _box->clicked();
532     }
533 }
534
535 - (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row
536 {
537     return !_box->itemIsGroupLabel(row);
538 }
539
540 - (BOOL)selectionShouldChangeInTableView:(NSTableView *)aTableView
541 {
542     return _box->isEnabled();
543 }
544
545 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(int)row
546 {
547     ASSERT([cell isKindOfClass:[NSCell class]]);
548     [(NSCell *)cell setEnabled:_box->isEnabled()];
549 }
550
551 - (void)_KWQ_setKeyboardFocusRingNeedsDisplay
552 {
553     [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]];
554 }
555
556 - (QWidget *)widget
557 {
558     return _box;
559 }
560
561 - (void)setBaseWritingDirection:(NSWritingDirection)direction
562 {
563     _direction = direction;
564 }
565
566 - (NSWritingDirection)baseWritingDirection
567 {
568     return _direction;
569 }
570
571 @end