2011-02-04 Xiaomei Ji <xji@chromium.org>
[WebKit.git] / Source / WebCore / platform / chromium / PopupMenuChromium.cpp
1 /*
2  * Copyright (c) 2008, 2009, Google Inc. All rights reserved.
3  * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include "config.h"
33 #include "PopupMenuChromium.h"
34
35 #include "Chrome.h"
36 #include "ChromeClientChromium.h"
37 #include "Font.h"
38 #include "FontSelector.h"
39 #include "FrameView.h"
40 #include "Frame.h"
41 #include "FramelessScrollView.h"
42 #include "FramelessScrollViewClient.h"
43 #include "GraphicsContext.h"
44 #include "IntRect.h"
45 #include "KeyboardCodes.h"
46 #include "Page.h"
47 #include "PlatformKeyboardEvent.h"
48 #include "PlatformMouseEvent.h"
49 #include "PlatformScreen.h"
50 #include "PlatformWheelEvent.h"
51 #include "PopupMenuClient.h"
52 #include "RenderTheme.h"
53 #include "ScrollbarTheme.h"
54 #include "StringTruncator.h"
55 #include "SystemTime.h"
56 #include "TextRun.h"
57 #include "UserGestureIndicator.h"
58 #include <wtf/CurrentTime.h>
59 #include <wtf/unicode/CharacterNames.h>
60
61 using namespace WTF;
62 using namespace Unicode;
63
64 using std::min;
65 using std::max;
66
67 namespace WebCore {
68
69 typedef unsigned long long TimeStamp;
70
71 static const int kMaxVisibleRows = 20;
72 static const int kMaxHeight = 500;
73 static const int kBorderSize = 1;
74 static const int kTextToLabelPadding = 10;
75 static const int kLabelToIconPadding = 5;
76 static const TimeStamp kTypeAheadTimeoutMs = 1000;
77
78 // The settings used for the drop down menu.
79 // This is the delegate used if none is provided.
80 static const PopupContainerSettings dropDownSettings = {
81     true, // setTextOnIndexChange
82     true, // acceptOnAbandon
83     false, // loopSelectionNavigation
84     false // restrictWidthOfListBox
85 };
86
87 // This class uses WebCore code to paint and handle events for a drop-down list
88 // box ("combobox" on Windows).
89 class PopupListBox : public FramelessScrollView {
90 public:
91     static PassRefPtr<PopupListBox> create(PopupMenuClient* client, const PopupContainerSettings& settings)
92     {
93         return adoptRef(new PopupListBox(client, settings));
94     }
95
96     // FramelessScrollView
97     virtual void paint(GraphicsContext*, const IntRect&);
98     virtual bool handleMouseDownEvent(const PlatformMouseEvent&);
99     virtual bool handleMouseMoveEvent(const PlatformMouseEvent&);
100     virtual bool handleMouseReleaseEvent(const PlatformMouseEvent&);
101     virtual bool handleWheelEvent(const PlatformWheelEvent&);
102     virtual bool handleKeyEvent(const PlatformKeyboardEvent&);
103
104     // ScrollView
105     virtual HostWindow* hostWindow() const;
106
107     // PopupListBox methods
108
109     // Hides the popup.
110     void hidePopup();
111
112     // Updates our internal list to match the client.
113     void updateFromElement();
114
115     // Frees any allocated resources used in a particular popup session.
116     void clear();
117
118     // Sets the index of the option that is displayed in the <select> widget in the page
119     void setOriginalIndex(int index);
120
121     // Gets the index of the item that the user is currently moused over or has selected with
122     // the keyboard. This is not the same as the original index, since the user has not yet
123     // accepted this input.
124     int selectedIndex() const { return m_selectedIndex; }
125
126     // Moves selection down/up the given number of items, scrolling if necessary.
127     // Positive is down.  The resulting index will be clamped to the range
128     // [0, numItems), and non-option items will be skipped.
129     void adjustSelectedIndex(int delta);
130
131     // Returns the number of items in the list.
132     int numItems() const { return static_cast<int>(m_items.size()); }
133
134     void setBaseWidth(int width) { m_baseWidth = width; }
135
136     // Computes the size of widget and children.
137     void layout();
138
139     // Returns whether the popup wants to process events for the passed key.
140     bool isInterestedInEventForKey(int keyCode);
141
142     // Gets the height of a row.
143     int getRowHeight(int index);
144
145     void setMaxHeight(int maxHeight) { m_maxHeight = maxHeight; }
146
147     void disconnectClient() { m_popupClient = 0; }
148
149     const Vector<PopupItem*>& items() const { return m_items; }
150
151 private:
152     friend class PopupContainer;
153     friend class RefCounted<PopupListBox>;
154
155     PopupListBox(PopupMenuClient* client, const PopupContainerSettings& settings)
156         : m_settings(settings)
157         , m_originalIndex(0)
158         , m_selectedIndex(0)
159         , m_acceptedIndexOnAbandon(-1)
160         , m_visibleRows(0)
161         , m_baseWidth(0)
162         , m_maxHeight(kMaxHeight)
163         , m_popupClient(client)
164         , m_repeatingChar(0)
165         , m_lastCharTime(0)
166     {
167         setScrollbarModes(ScrollbarAlwaysOff, ScrollbarAlwaysOff);
168     }
169
170     ~PopupListBox()
171     {
172         clear();
173     }
174
175     // Closes the popup
176     void abandon();
177
178     // Returns true if the selection can be changed to index.
179     // Disabled items, or labels cannot be selected.
180     bool isSelectableItem(int index);
181
182     // Select an index in the list, scrolling if necessary.
183     void selectIndex(int index);
184
185     // Accepts the selected index as the value to be displayed in the <select> widget on
186     // the web page, and closes the popup.
187     void acceptIndex(int index);
188
189     // Clears the selection (so no row appears selected).
190     void clearSelection();
191
192     // Scrolls to reveal the given index.
193     void scrollToRevealRow(int index);
194     void scrollToRevealSelection() { scrollToRevealRow(m_selectedIndex); }
195
196     // Invalidates the row at the given index.
197     void invalidateRow(int index);
198
199     // Get the bounds of a row.
200     IntRect getRowBounds(int index);
201
202     // Converts a point to an index of the row the point is over
203     int pointToRowIndex(const IntPoint&);
204
205     // Paint an individual row
206     void paintRow(GraphicsContext*, const IntRect&, int rowIndex);
207
208     // Test if the given point is within the bounds of the popup window.
209     bool isPointInBounds(const IntPoint&);
210
211     // Called when the user presses a text key.  Does a prefix-search of the items.
212     void typeAheadFind(const PlatformKeyboardEvent&);
213
214     // Returns the font to use for the given row
215     Font getRowFont(int index);
216
217     // Moves the selection down/up one item, taking care of looping back to the
218     // first/last element if m_loopSelectionNavigation is true.
219     void selectPreviousRow();
220     void selectNextRow();
221
222     // The settings that specify the behavior for this Popup window.
223     PopupContainerSettings m_settings;
224
225     // This is the index of the item marked as "selected" - i.e. displayed in the widget on the
226     // page.
227     int m_originalIndex;
228
229     // This is the index of the item that the user is hovered over or has selected using the
230     // keyboard in the list. They have not confirmed this selection by clicking or pressing
231     // enter yet however.
232     int m_selectedIndex;
233
234     // If >= 0, this is the index we should accept if the popup is "abandoned".
235     // This is used for keyboard navigation, where we want the
236     // selection to change immediately, and is only used if the settings
237     // acceptOnAbandon field is true.
238     int m_acceptedIndexOnAbandon;
239
240     // This is the number of rows visible in the popup. The maximum number visible at a time is
241     // defined as being kMaxVisibleRows. For a scrolled popup, this can be thought of as the
242     // page size in data units.
243     int m_visibleRows;
244
245     // Our suggested width, not including scrollbar.
246     int m_baseWidth;
247
248     // The maximum height we can be without being off-screen.
249     int m_maxHeight;
250
251     // A list of the options contained within the <select>
252     Vector<PopupItem*> m_items;
253
254     // The <select> PopupMenuClient that opened us.
255     PopupMenuClient* m_popupClient;
256
257     // The scrollbar which has mouse capture.  Mouse events go straight to this
258     // if non-NULL.
259     RefPtr<Scrollbar> m_capturingScrollbar;
260
261     // The last scrollbar that the mouse was over.  Used for mouseover highlights.
262     RefPtr<Scrollbar> m_lastScrollbarUnderMouse;
263
264     // The string the user has typed so far into the popup. Used for typeAheadFind.
265     String m_typedString;
266
267     // The char the user has hit repeatedly.  Used for typeAheadFind.
268     UChar m_repeatingChar;
269
270     // The last time the user hit a key.  Used for typeAheadFind.
271     TimeStamp m_lastCharTime;
272 };
273
274 static PlatformMouseEvent constructRelativeMouseEvent(const PlatformMouseEvent& e,
275                                                       FramelessScrollView* parent,
276                                                       FramelessScrollView* child)
277 {
278     IntPoint pos = parent->convertSelfToChild(child, e.pos());
279
280     // FIXME: This is a horrible hack since PlatformMouseEvent has no setters for x/y.
281     PlatformMouseEvent relativeEvent = e;
282     IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.pos());
283     relativePos.setX(pos.x());
284     relativePos.setY(pos.y());
285     return relativeEvent;
286 }
287
288 static PlatformWheelEvent constructRelativeWheelEvent(const PlatformWheelEvent& e,
289                                                       FramelessScrollView* parent,
290                                                       FramelessScrollView* child)
291 {
292     IntPoint pos = parent->convertSelfToChild(child, e.pos());
293
294     // FIXME: This is a horrible hack since PlatformWheelEvent has no setters for x/y.
295     PlatformWheelEvent relativeEvent = e;
296     IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.pos());
297     relativePos.setX(pos.x());
298     relativePos.setY(pos.y());
299     return relativeEvent;
300 }
301
302 ///////////////////////////////////////////////////////////////////////////////
303 // PopupContainer implementation
304
305 // static
306 PassRefPtr<PopupContainer> PopupContainer::create(PopupMenuClient* client,
307                                                   PopupType popupType,
308                                                   const PopupContainerSettings& settings)
309 {
310     return adoptRef(new PopupContainer(client, popupType, settings));
311 }
312
313 PopupContainer::PopupContainer(PopupMenuClient* client,
314                                PopupType popupType,
315                                const PopupContainerSettings& settings)
316     : m_listBox(PopupListBox::create(client, settings))
317     , m_settings(settings)
318     , m_popupType(popupType)
319     , m_popupOpen(false)
320 {
321     setScrollbarModes(ScrollbarAlwaysOff, ScrollbarAlwaysOff);
322 }
323
324 PopupContainer::~PopupContainer()
325 {
326     if (m_listBox && m_listBox->parent())
327         removeChild(m_listBox.get());
328 }
329
330 IntRect PopupContainer::layoutAndCalculateWidgetRect(int targetControlHeight, int popupInitialY)
331 {
332     // Reset the max height to its default value, it will be recomputed below
333     // if necessary.
334     m_listBox->setMaxHeight(kMaxHeight);
335
336     // Lay everything out to figure out our preferred size, then tell the view's
337     // WidgetClient about it.  It should assign us a client.
338     layout();
339   
340     // Assume m_listBox size is already calculated.
341     IntSize targetSize(m_listBox->width() + kBorderSize * 2,
342                        m_listBox->height() + kBorderSize * 2);
343
344     IntRect widgetRect;
345     ChromeClientChromium* chromeClient = chromeClientChromium();
346     if (chromeClient) {
347         // If the popup would extend past the bottom of the screen, open upwards
348         // instead.
349         FloatRect screen = screenAvailableRect(m_frameView.get());
350         // Use this::x() for location because RTL position is considered
351         // in layout().
352         widgetRect = chromeClient->windowToScreen(IntRect(x(), popupInitialY, targetSize.width(), targetSize.height()));
353         if (widgetRect.maxY() > static_cast<int>(screen.maxY())) {
354             if (widgetRect.y() - widgetRect.height() - targetControlHeight > 0) {
355                 // There is enough room to open upwards.
356                 widgetRect.move(0, -(widgetRect.height() + targetControlHeight));
357             } else {
358                 // Figure whether upwards or downwards has more room and set the
359                 // maximum number of items.
360                 int spaceAbove = widgetRect.y() - targetControlHeight;
361                 int spaceBelow = screen.maxY() - widgetRect.y();
362                 if (spaceAbove > spaceBelow)
363                     m_listBox->setMaxHeight(spaceAbove);
364                 else
365                     m_listBox->setMaxHeight(spaceBelow);
366                 layout();
367                 // Our size has changed, recompute the widgetRect.
368                 widgetRect = chromeClient->windowToScreen(frameRect());
369                 // And move upwards if necessary.
370                 if (spaceAbove > spaceBelow)
371                     widgetRect.move(0, -(widgetRect.height() + targetControlHeight));
372             }
373         }
374     }
375     return widgetRect;
376 }
377
378 void PopupContainer::showPopup(FrameView* view)
379 {
380     m_frameView = view;
381
382     ChromeClientChromium* chromeClient = chromeClientChromium();
383     if (chromeClient) {
384         IntRect popupRect = frameRect();
385         chromeClient->popupOpened(this, layoutAndCalculateWidgetRect(popupRect.height(), popupRect.y()), false);
386         m_popupOpen = true;
387     }
388
389     if (!m_listBox->parent())
390         addChild(m_listBox.get());
391
392     // Enable scrollbars after the listbox is inserted into the hierarchy,
393     // so it has a proper WidgetClient.
394     m_listBox->setVerticalScrollbarMode(ScrollbarAuto);
395
396     m_listBox->scrollToRevealSelection();
397
398     invalidate();
399 }
400
401 void PopupContainer::showExternal(const IntRect& rect, FrameView* v, int index)
402 {
403     if (!listBox())
404         return;
405
406     listBox()->setBaseWidth(rect.width());
407     listBox()->updateFromElement();
408
409     if (listBox()->numItems() < 1) {
410         hidePopup();
411         return;
412     }
413
414     // Adjust the popup position to account for scrolling.
415     IntPoint location = v->contentsToWindow(rect.location());
416     IntRect popupRect(location, rect.size());
417
418     // Get the ChromeClient and pass it the popup menu's listbox data.
419     m_frameView = v;
420     chromeClientChromium()->popupOpened(this, popupRect, true);
421
422     // The popup sends its "closed" notification through its parent. Set the
423     // parent, even though external popups have no real on-screen widget but a
424     // native menu (see |PopupListBox::hidePopup()|);
425     if (!m_listBox->parent())
426         addChild(m_listBox.get());
427 }
428
429 void PopupContainer::hidePopup()
430 {
431     listBox()->hidePopup();
432 }
433
434 void PopupContainer::notifyPopupHidden()
435 {
436     if (!m_popupOpen)
437         return;
438     m_popupOpen = false;
439     chromeClientChromium()->popupClosed(this);
440 }
441
442 void PopupContainer::layout()
443 {
444     m_listBox->layout();
445
446     // Place the listbox within our border.
447     m_listBox->move(kBorderSize, kBorderSize);
448
449     // popupWidth is the width of <select> element. Record it before resize frame.
450     int popupWidth = frameRect().width();
451     // Size ourselves to contain listbox + border.
452     int listBoxWidth = m_listBox->width() + kBorderSize * 2;
453     resize(listBoxWidth, m_listBox->height() + kBorderSize * 2);
454
455     // Adjust the starting x-axis for RTL dropdown. For RTL dropdown, the right edge
456     // of dropdown box should be aligned with the right edge of <select> element box,
457     // and the dropdown box should be expanded to left if more space needed.
458     PopupMenuClient* popupClient = m_listBox->m_popupClient;
459     if (popupClient) {
460         bool rightAligned = m_listBox->m_popupClient->menuStyle().textDirection() == RTL;
461         if (rightAligned)
462             move(x() + popupWidth - listBoxWidth, y());
463     }
464     invalidate();
465 }
466
467 bool PopupContainer::handleMouseDownEvent(const PlatformMouseEvent& event)
468 {
469     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
470     return m_listBox->handleMouseDownEvent(
471         constructRelativeMouseEvent(event, this, m_listBox.get()));
472 }
473
474 bool PopupContainer::handleMouseMoveEvent(const PlatformMouseEvent& event)
475 {
476     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
477     return m_listBox->handleMouseMoveEvent(
478         constructRelativeMouseEvent(event, this, m_listBox.get()));
479 }
480
481 bool PopupContainer::handleMouseReleaseEvent(const PlatformMouseEvent& event)
482 {
483     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
484     return m_listBox->handleMouseReleaseEvent(
485         constructRelativeMouseEvent(event, this, m_listBox.get()));
486 }
487
488 bool PopupContainer::handleWheelEvent(const PlatformWheelEvent& event)
489 {
490     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
491     return m_listBox->handleWheelEvent(
492         constructRelativeWheelEvent(event, this, m_listBox.get()));
493 }
494
495 bool PopupContainer::handleKeyEvent(const PlatformKeyboardEvent& event)
496 {
497     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
498     return m_listBox->handleKeyEvent(event);
499 }
500
501 void PopupContainer::hide()
502 {
503     m_listBox->abandon();
504 }
505
506 void PopupContainer::paint(GraphicsContext* gc, const IntRect& rect)
507 {
508     // adjust coords for scrolled frame
509     IntRect r = intersection(rect, frameRect());
510     int tx = x();
511     int ty = y();
512
513     r.move(-tx, -ty);
514
515     gc->translate(static_cast<float>(tx), static_cast<float>(ty));
516     m_listBox->paint(gc, r);
517     gc->translate(-static_cast<float>(tx), -static_cast<float>(ty));
518
519     paintBorder(gc, rect);
520 }
521
522 void PopupContainer::paintBorder(GraphicsContext* gc, const IntRect& rect)
523 {
524     // FIXME: Where do we get the border color from?
525     Color borderColor(127, 157, 185);
526
527     gc->setStrokeStyle(NoStroke);
528     gc->setFillColor(borderColor, ColorSpaceDeviceRGB);
529
530     int tx = x();
531     int ty = y();
532
533     // top, left, bottom, right
534     gc->drawRect(IntRect(tx, ty, width(), kBorderSize));
535     gc->drawRect(IntRect(tx, ty, kBorderSize, height()));
536     gc->drawRect(IntRect(tx, ty + height() - kBorderSize, width(), kBorderSize));
537     gc->drawRect(IntRect(tx + width() - kBorderSize, ty, kBorderSize, height()));
538 }
539
540 bool PopupContainer::isInterestedInEventForKey(int keyCode)
541 {
542     return m_listBox->isInterestedInEventForKey(keyCode);
543 }
544
545 ChromeClientChromium* PopupContainer::chromeClientChromium()
546 {
547     return static_cast<ChromeClientChromium*>(m_frameView->frame()->page()->chrome()->client());
548 }
549
550 void PopupContainer::show(const IntRect& r, FrameView* v, int index)
551 {
552     // The rect is the size of the select box. It's usually larger than we need.
553     // subtract border size so that usually the container will be displayed
554     // exactly the same width as the select box.
555     listBox()->setBaseWidth(max(r.width() - kBorderSize * 2, 0));
556
557     listBox()->updateFromElement();
558
559     // We set the selected item in updateFromElement(), and disregard the
560     // index passed into this function (same as Webkit's PopupMenuWin.cpp)
561     // FIXME: make sure this is correct, and add an assertion.
562     // ASSERT(popupWindow(popup)->listBox()->selectedIndex() == index);
563
564     // Convert point to main window coords.
565     IntPoint location = v->contentsToWindow(r.location());
566
567     // Move it below the select widget.
568     location.move(0, r.height());
569
570     setFrameRect(IntRect(location, r.size()));
571     showPopup(v);
572 }
573
574 void PopupContainer::refresh(const IntRect& targetControlRect)
575 {
576     IntPoint location = m_frameView->contentsToWindow(targetControlRect.location());
577     // Move it below the select widget.
578     location.move(0, targetControlRect.height());
579
580     listBox()->updateFromElement();
581     // Store the original height to check if we need to request the location.
582     int originalHeight = height();
583     IntRect widgetRect = layoutAndCalculateWidgetRect(targetControlRect.height(), location.y());
584     if (originalHeight != widgetRect.height())
585         setFrameRect(widgetRect);
586
587     invalidate();
588 }
589
590 int PopupContainer::selectedIndex() const
591 {
592     return m_listBox->selectedIndex();
593 }
594
595 int PopupContainer::menuItemHeight() const
596 {
597     return m_listBox->getRowHeight(0);
598 }
599
600 int PopupContainer::menuItemFontSize() const
601 {
602     return m_listBox->getRowFont(0).size();
603 }
604
605 PopupMenuStyle PopupContainer::menuStyle() const
606 {
607     return m_listBox->m_popupClient->menuStyle();
608 }
609
610 const WTF::Vector<PopupItem*>& PopupContainer:: popupData() const
611 {
612     return m_listBox->items();
613 }
614
615 ///////////////////////////////////////////////////////////////////////////////
616 // PopupListBox implementation
617
618 bool PopupListBox::handleMouseDownEvent(const PlatformMouseEvent& event)
619 {
620     Scrollbar* scrollbar = scrollbarAtPoint(event.pos());
621     if (scrollbar) {
622         m_capturingScrollbar = scrollbar;
623         m_capturingScrollbar->mouseDown(event);
624         return true;
625     }
626
627     if (!isPointInBounds(event.pos()))
628         abandon();
629
630     return true;
631 }
632
633 bool PopupListBox::handleMouseMoveEvent(const PlatformMouseEvent& event)
634 {
635     if (m_capturingScrollbar) {
636         m_capturingScrollbar->mouseMoved(event);
637         return true;
638     }
639
640     Scrollbar* scrollbar = scrollbarAtPoint(event.pos());
641     if (m_lastScrollbarUnderMouse != scrollbar) {
642         // Send mouse exited to the old scrollbar.
643         if (m_lastScrollbarUnderMouse)
644             m_lastScrollbarUnderMouse->mouseExited();
645         m_lastScrollbarUnderMouse = scrollbar;
646     }
647
648     if (scrollbar) {
649         scrollbar->mouseMoved(event);
650         return true;
651     }
652
653     if (!isPointInBounds(event.pos()))
654         return false;
655
656     selectIndex(pointToRowIndex(event.pos()));
657     return true;
658 }
659
660 bool PopupListBox::handleMouseReleaseEvent(const PlatformMouseEvent& event)
661 {
662     if (m_capturingScrollbar) {
663         m_capturingScrollbar->mouseUp();
664         m_capturingScrollbar = 0;
665         return true;
666     }
667
668     if (!isPointInBounds(event.pos()))
669         return true;
670
671     acceptIndex(pointToRowIndex(event.pos()));
672     return true;
673 }
674
675 bool PopupListBox::handleWheelEvent(const PlatformWheelEvent& event)
676 {
677     if (!isPointInBounds(event.pos())) {
678         abandon();
679         return true;
680     }
681
682     // Pass it off to the scroll view.
683     // Sadly, WebCore devs don't understand the whole "const" thing.
684     wheelEvent(const_cast<PlatformWheelEvent&>(event));
685     return true;
686 }
687
688 // Should be kept in sync with handleKeyEvent().
689 bool PopupListBox::isInterestedInEventForKey(int keyCode)
690 {
691     switch (keyCode) {
692     case VKEY_ESCAPE:
693     case VKEY_RETURN:
694     case VKEY_UP:
695     case VKEY_DOWN:
696     case VKEY_PRIOR:
697     case VKEY_NEXT:
698     case VKEY_HOME:
699     case VKEY_END:
700     case VKEY_TAB:
701         return true;
702     default:
703         return false;
704     }
705 }
706
707 static bool isCharacterTypeEvent(const PlatformKeyboardEvent& event)
708 {
709     // Check whether the event is a character-typed event or not.
710     // We use RawKeyDown/Char/KeyUp event scheme on all platforms,
711     // so PlatformKeyboardEvent::Char (not RawKeyDown) type event
712     // is considered as character type event.
713     return event.type() == PlatformKeyboardEvent::Char;
714 }
715
716 bool PopupListBox::handleKeyEvent(const PlatformKeyboardEvent& event)
717 {
718     if (event.type() == PlatformKeyboardEvent::KeyUp)
719         return true;
720
721     if (numItems() == 0 && event.windowsVirtualKeyCode() != VKEY_ESCAPE)
722         return true;
723
724     switch (event.windowsVirtualKeyCode()) {
725     case VKEY_ESCAPE:
726         abandon();  // may delete this
727         return true;
728     case VKEY_RETURN:
729         if (m_selectedIndex == -1)  {
730             hidePopup();
731             // Don't eat the enter if nothing is selected.
732             return false;
733         }
734         acceptIndex(m_selectedIndex);  // may delete this
735         return true;
736     case VKEY_UP:
737         selectPreviousRow();
738         break;
739     case VKEY_DOWN:
740         selectNextRow();
741         break;
742     case VKEY_PRIOR:
743         adjustSelectedIndex(-m_visibleRows);
744         break;
745     case VKEY_NEXT:
746         adjustSelectedIndex(m_visibleRows);
747         break;
748     case VKEY_HOME:
749         adjustSelectedIndex(-m_selectedIndex);
750         break;
751     case VKEY_END:
752         adjustSelectedIndex(m_items.size());
753         break;
754     default:
755         if (!event.ctrlKey() && !event.altKey() && !event.metaKey()
756             && isPrintableChar(event.windowsVirtualKeyCode())
757             && isCharacterTypeEvent(event))
758             typeAheadFind(event);
759         break;
760     }
761
762     if (m_originalIndex != m_selectedIndex) {
763         // Keyboard events should update the selection immediately (but we don't
764         // want to fire the onchange event until the popup is closed, to match
765         // IE).  We change the original index so we revert to that when the
766         // popup is closed.
767         if (m_settings.acceptOnAbandon)
768             m_acceptedIndexOnAbandon = m_selectedIndex;
769
770         setOriginalIndex(m_selectedIndex);
771         if (m_settings.setTextOnIndexChange)
772             m_popupClient->setTextFromItem(m_selectedIndex);
773     }
774     if (event.windowsVirtualKeyCode() == VKEY_TAB) {
775         // TAB is a special case as it should select the current item if any and
776         // advance focus.
777         if (m_selectedIndex >= 0) {
778             acceptIndex(m_selectedIndex); // May delete us.
779             // Return false so the TAB key event is propagated to the page.
780             return false;
781         }
782         // Call abandon() so we honor m_acceptedIndexOnAbandon if set.
783         abandon();
784         // Return false so the TAB key event is propagated to the page.
785         return false;
786     }
787
788     return true;
789 }
790
791 HostWindow* PopupListBox::hostWindow() const
792 {
793     // Our parent is the root ScrollView, so it is the one that has a
794     // HostWindow.  FrameView::hostWindow() works similarly.
795     return parent() ? parent()->hostWindow() : 0;
796 }
797
798 // From HTMLSelectElement.cpp
799 static String stripLeadingWhiteSpace(const String& string)
800 {
801     int length = string.length();
802     int i;
803     for (i = 0; i < length; ++i)
804         if (string[i] != noBreakSpace
805             && (string[i] <= 0x7F ? !isspace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral)))
806             break;
807
808     return string.substring(i, length - i);
809 }
810
811 // From HTMLSelectElement.cpp, with modifications
812 void PopupListBox::typeAheadFind(const PlatformKeyboardEvent& event)
813 {
814     TimeStamp now = static_cast<TimeStamp>(currentTime() * 1000.0f);
815     TimeStamp delta = now - m_lastCharTime;
816
817     // Reset the time when user types in a character. The time gap between
818     // last character and the current character is used to indicate whether
819     // user typed in a string or just a character as the search prefix.
820     m_lastCharTime = now;
821
822     UChar c = event.windowsVirtualKeyCode();
823
824     String prefix;
825     int searchStartOffset = 1;
826     if (delta > kTypeAheadTimeoutMs) {
827         m_typedString = prefix = String(&c, 1);
828         m_repeatingChar = c;
829     } else {
830         m_typedString.append(c);
831
832         if (c == m_repeatingChar)
833             // The user is likely trying to cycle through all the items starting with this character, so just search on the character
834             prefix = String(&c, 1);
835         else {
836             m_repeatingChar = 0;
837             prefix = m_typedString;
838             searchStartOffset = 0;
839         }
840     }
841
842     // Compute a case-folded copy of the prefix string before beginning the search for
843     // a matching element. This code uses foldCase to work around the fact that
844     // String::startWith does not fold non-ASCII characters. This code can be changed
845     // to use startWith once that is fixed.
846     String prefixWithCaseFolded(prefix.foldCase());
847     int itemCount = numItems();
848     int index = (max(0, m_selectedIndex) + searchStartOffset) % itemCount;
849     for (int i = 0; i < itemCount; i++, index = (index + 1) % itemCount) {
850         if (!isSelectableItem(index))
851             continue;
852
853         if (stripLeadingWhiteSpace(m_items[index]->label).foldCase().startsWith(prefixWithCaseFolded)) {
854             selectIndex(index);
855             return;
856         }
857     }
858 }
859
860 void PopupListBox::paint(GraphicsContext* gc, const IntRect& rect)
861 {
862     // adjust coords for scrolled frame
863     IntRect r = intersection(rect, frameRect());
864     int tx = x() - scrollX();
865     int ty = y() - scrollY();
866
867     r.move(-tx, -ty);
868
869     // set clip rect to match revised damage rect
870     gc->save();
871     gc->translate(static_cast<float>(tx), static_cast<float>(ty));
872     gc->clip(r);
873
874     // FIXME: Can we optimize scrolling to not require repainting the entire
875     // window?  Should we?
876     for (int i = 0; i < numItems(); ++i)
877         paintRow(gc, r, i);
878
879     // Special case for an empty popup.
880     if (numItems() == 0)
881         gc->fillRect(r, Color::white, ColorSpaceDeviceRGB);
882
883     gc->restore();
884
885     ScrollView::paint(gc, rect);
886 }
887
888 static const int separatorPadding = 4;
889 static const int separatorHeight = 1;
890
891 void PopupListBox::paintRow(GraphicsContext* gc, const IntRect& rect, int rowIndex)
892 {
893     // This code is based largely on RenderListBox::paint* methods.
894
895     IntRect rowRect = getRowBounds(rowIndex);
896     if (!rowRect.intersects(rect))
897         return;
898
899     PopupMenuStyle style = m_popupClient->itemStyle(rowIndex);
900
901     // Paint background
902     Color backColor, textColor, labelColor;
903     if (rowIndex == m_selectedIndex) {
904         backColor = RenderTheme::defaultTheme()->activeListBoxSelectionBackgroundColor();
905         textColor = RenderTheme::defaultTheme()->activeListBoxSelectionForegroundColor();
906         labelColor = textColor;
907     } else {
908         backColor = style.backgroundColor();
909         textColor = style.foregroundColor();
910         // FIXME: for now the label color is hard-coded. It should be added to
911         // the PopupMenuStyle.
912         labelColor = Color(115, 115, 115);
913     }
914
915     // If we have a transparent background, make sure it has a color to blend
916     // against.
917     if (backColor.hasAlpha())
918         gc->fillRect(rowRect, Color::white, ColorSpaceDeviceRGB);
919
920     gc->fillRect(rowRect, backColor, ColorSpaceDeviceRGB);
921
922     if (m_popupClient->itemIsSeparator(rowIndex)) {
923         IntRect separatorRect(
924             rowRect.x() + separatorPadding,
925             rowRect.y() + (rowRect.height() - separatorHeight) / 2,
926             rowRect.width() - 2 * separatorPadding, separatorHeight);
927         gc->fillRect(separatorRect, textColor, ColorSpaceDeviceRGB);
928         return;
929     }
930
931     if (!style.isVisible())
932         return;
933
934     gc->setFillColor(textColor, ColorSpaceDeviceRGB);
935
936     Font itemFont = getRowFont(rowIndex);
937     // FIXME: http://crbug.com/19872 We should get the padding of individual option
938     // elements.  This probably implies changes to PopupMenuClient.
939     bool rightAligned = m_popupClient->menuStyle().textDirection() == RTL;
940     int textX = 0;
941     int maxWidth = 0;
942     if (rightAligned)
943         maxWidth = rowRect.width() - max(0, m_popupClient->clientPaddingRight() - m_popupClient->clientInsetRight());
944     else {
945         textX = max(0, m_popupClient->clientPaddingLeft() - m_popupClient->clientInsetLeft());
946         maxWidth = rowRect.width() - textX;
947     }
948     // Prepare text to be drawn.
949     String itemText = m_popupClient->itemText(rowIndex);
950     String itemLabel = m_popupClient->itemLabel(rowIndex);
951     String itemIcon = m_popupClient->itemIcon(rowIndex);
952     if (m_settings.restrictWidthOfListBox) { // Truncate strings to fit in.
953         // FIXME: We should leftTruncate for the rtl case.
954         // StringTruncator::leftTruncate would have to be implemented.
955         String str = StringTruncator::rightTruncate(itemText, maxWidth, itemFont);
956         if (str != itemText) {
957             itemText = str;
958             // Don't display the label or icon, we already don't have enough room for the item text.
959             itemLabel = "";
960             itemIcon = "";
961         } else if (!itemLabel.isEmpty()) {
962             int availableWidth = maxWidth - kTextToLabelPadding -
963                 StringTruncator::width(itemText, itemFont);
964             itemLabel = StringTruncator::rightTruncate(itemLabel, availableWidth, itemFont);
965         }
966     }
967
968     // Prepare the directionality to draw text.
969     bool rtl = style.textDirection() == RTL;
970     TextRun textRun(itemText.characters(), itemText.length(), false, 0, 0, TextRun::AllowTrailingExpansion, rtl, style.hasTextDirectionOverride());
971     // If the text is right-to-left, make it right-aligned by adjusting its
972     // beginning position.
973     if (rightAligned)
974         textX += maxWidth - itemFont.width(textRun);
975
976     // Draw the item text.
977     int textY = rowRect.y() + itemFont.fontMetrics().ascent() + (rowRect.height() - itemFont.fontMetrics().height()) / 2;
978     gc->drawBidiText(itemFont, textRun, IntPoint(textX, textY));
979
980     // We are using the left padding as the right padding includes room for the scroll-bar which
981     // does not show in this case.
982     int rightPadding = max(0, m_popupClient->clientPaddingLeft() - m_popupClient->clientInsetLeft());
983     int remainingWidth = rowRect.width() - rightPadding;
984
985     // Draw the icon if applicable.
986     RefPtr<Image> image(Image::loadPlatformResource(itemIcon.utf8().data()));
987     if (image && !image->isNull()) {
988         IntRect imageRect = image->rect();
989         remainingWidth -= (imageRect.width() + kLabelToIconPadding);
990         imageRect.setX(rowRect.width() - rightPadding - imageRect.width());
991         imageRect.setY(rowRect.y() + (rowRect.height() - imageRect.height()) / 2);
992         gc->drawImage(image.get(), ColorSpaceDeviceRGB, imageRect);
993     }
994
995     // Draw the the label if applicable.
996     if (itemLabel.isEmpty())
997         return;
998     TextRun labelTextRun(itemLabel.characters(), itemLabel.length(), false, 0, 0, TextRun::AllowTrailingExpansion, rtl, style.hasTextDirectionOverride());
999     if (rightAligned)
1000         textX = max(0, m_popupClient->clientPaddingLeft() - m_popupClient->clientInsetLeft());
1001     else
1002         textX = remainingWidth - itemFont.width(labelTextRun);
1003
1004     gc->setFillColor(labelColor, ColorSpaceDeviceRGB);
1005     gc->drawBidiText(itemFont, labelTextRun, IntPoint(textX, textY));
1006 }
1007
1008 Font PopupListBox::getRowFont(int rowIndex)
1009 {
1010     Font itemFont = m_popupClient->itemStyle(rowIndex).font();
1011     if (m_popupClient->itemIsLabel(rowIndex)) {
1012         // Bold-ify labels (ie, an <optgroup> heading).
1013         FontDescription d = itemFont.fontDescription();
1014         d.setWeight(FontWeightBold);
1015         Font font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
1016         font.update(0);
1017         return font;
1018     }
1019
1020     return itemFont;
1021 }
1022
1023 void PopupListBox::abandon()
1024 {
1025     RefPtr<PopupListBox> keepAlive(this);
1026
1027     m_selectedIndex = m_originalIndex;
1028
1029     hidePopup();
1030
1031     if (m_acceptedIndexOnAbandon >= 0) {
1032         if (m_popupClient)
1033             m_popupClient->valueChanged(m_acceptedIndexOnAbandon);
1034         m_acceptedIndexOnAbandon = -1;
1035     }
1036 }
1037
1038 int PopupListBox::pointToRowIndex(const IntPoint& point)
1039 {
1040     int y = scrollY() + point.y();
1041
1042     // FIXME: binary search if perf matters.
1043     for (int i = 0; i < numItems(); ++i) {
1044         if (y < m_items[i]->yOffset)
1045             return i-1;
1046     }
1047
1048     // Last item?
1049     if (y < contentsHeight())
1050         return m_items.size()-1;
1051
1052     return -1;
1053 }
1054
1055 void PopupListBox::acceptIndex(int index)
1056 {
1057     // Clear m_acceptedIndexOnAbandon once user accepts the selected index.
1058     if (m_acceptedIndexOnAbandon >= 0)
1059         m_acceptedIndexOnAbandon = -1;
1060
1061     if (index >= numItems())
1062         return;
1063
1064     if (index < 0) {
1065         if (m_popupClient) {
1066             // Enter pressed with no selection, just close the popup.
1067             hidePopup();
1068         }
1069         return;
1070     }
1071
1072     if (isSelectableItem(index)) {
1073         RefPtr<PopupListBox> keepAlive(this);
1074
1075         // Hide ourselves first since valueChanged may have numerous side-effects.
1076         hidePopup();
1077
1078         // Tell the <select> PopupMenuClient what index was selected.
1079         m_popupClient->valueChanged(index);
1080     }
1081 }
1082
1083 void PopupListBox::selectIndex(int index)
1084 {
1085     if (index < 0 || index >= numItems())
1086         return;
1087
1088     bool isSelectable = isSelectableItem(index);
1089     if (index != m_selectedIndex && isSelectable) {
1090         invalidateRow(m_selectedIndex);
1091         m_selectedIndex = index;
1092         invalidateRow(m_selectedIndex);
1093
1094         scrollToRevealSelection();
1095         m_popupClient->selectionChanged(m_selectedIndex);
1096     } else if (!isSelectable) {
1097         clearSelection();
1098     }
1099 }
1100
1101 void PopupListBox::setOriginalIndex(int index)
1102 {
1103     m_originalIndex = m_selectedIndex = index;
1104 }
1105
1106 int PopupListBox::getRowHeight(int index)
1107 {
1108     if (index < 0)
1109         return 0;
1110
1111     if (m_popupClient->itemStyle(index).isDisplayNone())
1112         return 0;
1113
1114     String icon = m_popupClient->itemIcon(index);
1115     RefPtr<Image> image(Image::loadPlatformResource(icon.utf8().data()));
1116
1117     int fontHeight = getRowFont(index).fontMetrics().height();
1118     int iconHeight = (image && !image->isNull()) ? image->rect().height() : 0;
1119
1120     return max(fontHeight, iconHeight);
1121 }
1122
1123 IntRect PopupListBox::getRowBounds(int index)
1124 {
1125     if (index < 0)
1126         return IntRect(0, 0, visibleWidth(), getRowHeight(index));
1127
1128     return IntRect(0, m_items[index]->yOffset, visibleWidth(), getRowHeight(index));
1129 }
1130
1131 void PopupListBox::invalidateRow(int index)
1132 {
1133     if (index < 0)
1134         return;
1135
1136     // Invalidate in the window contents, as FramelessScrollView::invalidateRect
1137     // paints in the window coordinates.
1138     invalidateRect(contentsToWindow(getRowBounds(index)));
1139 }
1140
1141 void PopupListBox::scrollToRevealRow(int index)
1142 {
1143     if (index < 0)
1144         return;
1145
1146     IntRect rowRect = getRowBounds(index);
1147
1148     if (rowRect.y() < scrollY()) {
1149         // Row is above current scroll position, scroll up.
1150         ScrollView::setScrollPosition(IntPoint(0, rowRect.y()));
1151     } else if (rowRect.maxY() > scrollY() + visibleHeight()) {
1152         // Row is below current scroll position, scroll down.
1153         ScrollView::setScrollPosition(IntPoint(0, rowRect.maxY() - visibleHeight()));
1154     }
1155 }
1156
1157 bool PopupListBox::isSelectableItem(int index)
1158 {
1159     ASSERT(index >= 0 && index < numItems());
1160     return m_items[index]->type == PopupItem::TypeOption && m_popupClient->itemIsEnabled(index);
1161 }
1162
1163 void PopupListBox::clearSelection()
1164 {
1165     if (m_selectedIndex != -1) {
1166         invalidateRow(m_selectedIndex);
1167         m_selectedIndex = -1;
1168         m_popupClient->selectionCleared();
1169     }
1170 }
1171
1172 void PopupListBox::selectNextRow()
1173 {
1174     if (!m_settings.loopSelectionNavigation || m_selectedIndex != numItems() - 1) {
1175         adjustSelectedIndex(1);
1176         return;
1177     }
1178
1179     // We are moving past the last item, no row should be selected.
1180     clearSelection();
1181 }
1182
1183 void PopupListBox::selectPreviousRow()
1184 {
1185     if (!m_settings.loopSelectionNavigation || m_selectedIndex > 0) {
1186         adjustSelectedIndex(-1);
1187         return;
1188     }
1189
1190     if (m_selectedIndex == 0) {
1191         // We are moving past the first item, clear the selection.
1192         clearSelection();
1193         return;
1194     }
1195
1196     // No row is selected, jump to the last item.
1197     selectIndex(numItems() - 1);
1198     scrollToRevealSelection();
1199 }
1200
1201 void PopupListBox::adjustSelectedIndex(int delta)
1202 {
1203     int targetIndex = m_selectedIndex + delta;
1204     targetIndex = min(max(targetIndex, 0), numItems() - 1);
1205     if (!isSelectableItem(targetIndex)) {
1206         // We didn't land on an option.  Try to find one.
1207         // We try to select the closest index to target, prioritizing any in
1208         // the range [current, target].
1209
1210         int dir = delta > 0 ? 1 : -1;
1211         int testIndex = m_selectedIndex;
1212         int bestIndex = m_selectedIndex;
1213         bool passedTarget = false;
1214         while (testIndex >= 0 && testIndex < numItems()) {
1215             if (isSelectableItem(testIndex))
1216                 bestIndex = testIndex;
1217             if (testIndex == targetIndex)
1218                 passedTarget = true;
1219             if (passedTarget && bestIndex != m_selectedIndex)
1220                 break;
1221
1222             testIndex += dir;
1223         }
1224
1225         // Pick the best index, which may mean we don't change.
1226         targetIndex = bestIndex;
1227     }
1228
1229     // Select the new index, and ensure its visible.  We do this regardless of
1230     // whether the selection changed to ensure keyboard events always bring the
1231     // selection into view.
1232     selectIndex(targetIndex);
1233     scrollToRevealSelection();
1234 }
1235
1236 void PopupListBox::hidePopup()
1237 {
1238     if (parent()) {
1239         PopupContainer* container = static_cast<PopupContainer*>(parent());
1240         if (container->client())
1241             container->client()->popupClosed(container);
1242         container->notifyPopupHidden();
1243     }
1244
1245     if (m_popupClient)
1246         m_popupClient->popupDidHide();
1247 }
1248
1249 void PopupListBox::updateFromElement()
1250 {
1251     clear();
1252
1253     int size = m_popupClient->listSize();
1254     for (int i = 0; i < size; ++i) {
1255         PopupItem::Type type;
1256         if (m_popupClient->itemIsSeparator(i))
1257             type = PopupItem::TypeSeparator;
1258         else if (m_popupClient->itemIsLabel(i))
1259             type = PopupItem::TypeGroup;
1260         else
1261             type = PopupItem::TypeOption;
1262         m_items.append(new PopupItem(m_popupClient->itemText(i), type));
1263         m_items[i]->enabled = isSelectableItem(i);
1264     }
1265
1266     m_selectedIndex = m_popupClient->selectedIndex();
1267     setOriginalIndex(m_selectedIndex);
1268
1269     layout();
1270 }
1271
1272 void PopupListBox::layout()
1273 {
1274     // Size our child items.
1275     int baseWidth = 0;
1276     int paddingWidth = 0;
1277     int y = 0;
1278     for (int i = 0; i < numItems(); ++i) {
1279         // Place the item vertically.
1280         m_items[i]->yOffset = y;
1281         if (m_popupClient->itemStyle(i).isDisplayNone())
1282             continue;
1283         y += getRowHeight(i);
1284
1285         // Ensure the popup is wide enough to fit this item.
1286         Font itemFont = getRowFont(i);
1287         String text = m_popupClient->itemText(i);
1288         String label = m_popupClient->itemLabel(i);
1289         String icon = m_popupClient->itemIcon(i);
1290         RefPtr<Image> iconImage(Image::loadPlatformResource(icon.utf8().data()));
1291         int width = 0;
1292         if (!text.isEmpty())
1293             width = itemFont.width(TextRun(text));
1294         if (!label.isEmpty()) {
1295             if (width > 0)
1296                 width += kTextToLabelPadding;
1297             width += itemFont.width(TextRun(label));
1298         }
1299         if (iconImage && !iconImage->isNull()) {
1300             if (width > 0)
1301                 width += kLabelToIconPadding;
1302             width += iconImage->rect().width();
1303         }
1304
1305         baseWidth = max(baseWidth, width);
1306         // FIXME: http://b/1210481 We should get the padding of individual option elements.
1307         paddingWidth = max(paddingWidth,
1308             m_popupClient->clientPaddingLeft() + m_popupClient->clientPaddingRight());
1309     }
1310
1311     // Calculate scroll bar width.
1312     int windowHeight = 0;
1313
1314 #if OS(DARWIN)
1315     // Set the popup's window to contain all available items on Mac only, which
1316     // uses native controls that manage their own scrolling. This allows hit
1317     // testing to work when selecting items in popups that have more menu entries
1318     // than the maximum window size.
1319     m_visibleRows = numItems();
1320 #else
1321     m_visibleRows = min(numItems(), kMaxVisibleRows);
1322 #endif
1323
1324     for (int i = 0; i < m_visibleRows; ++i) {
1325         int rowHeight = getRowHeight(i);
1326
1327 #if !OS(DARWIN)
1328         // Only clip the window height for non-Mac platforms.
1329         if (windowHeight + rowHeight > m_maxHeight) {
1330             m_visibleRows = i;
1331             break;
1332         }
1333 #endif
1334
1335         windowHeight += rowHeight;
1336     }
1337
1338     // Set our widget and scrollable contents sizes.
1339     int scrollbarWidth = 0;
1340     if (m_visibleRows < numItems())
1341         scrollbarWidth = ScrollbarTheme::nativeTheme()->scrollbarThickness();
1342
1343     int windowWidth;
1344     int contentWidth;
1345     if (m_settings.restrictWidthOfListBox) {
1346         windowWidth = m_baseWidth;
1347         contentWidth = m_baseWidth - scrollbarWidth;
1348     } else {
1349         windowWidth = baseWidth + scrollbarWidth + paddingWidth;
1350         contentWidth = baseWidth + paddingWidth;
1351
1352         if (windowWidth < m_baseWidth) {
1353             windowWidth = m_baseWidth;
1354             contentWidth = m_baseWidth - scrollbarWidth;
1355         } else
1356             m_baseWidth = baseWidth;
1357     }
1358
1359     resize(windowWidth, windowHeight);
1360     setContentsSize(IntSize(contentWidth, getRowBounds(numItems() - 1).maxY()));
1361
1362     if (hostWindow())
1363         scrollToRevealSelection();
1364
1365     invalidate();
1366 }
1367
1368 void PopupListBox::clear()
1369 {
1370     for (Vector<PopupItem*>::iterator it = m_items.begin(); it != m_items.end(); ++it)
1371         delete *it;
1372     m_items.clear();
1373 }
1374
1375 bool PopupListBox::isPointInBounds(const IntPoint& point)
1376 {
1377     return numItems() != 0 && IntRect(0, 0, width(), height()).contains(point);
1378 }
1379
1380 ///////////////////////////////////////////////////////////////////////////////
1381 // PopupMenuChromium implementation
1382 //
1383 // Note: you cannot add methods to this class, since it is defined above the
1384 //       portability layer. To access methods and properties on the
1385 //       popup widgets, use |popupWindow| above.
1386
1387 PopupMenuChromium::PopupMenuChromium(PopupMenuClient* client)
1388     : m_popupClient(client)
1389 {
1390 }
1391
1392 PopupMenuChromium::~PopupMenuChromium()
1393 {
1394     // When the PopupMenuChromium is destroyed, the client could already have been
1395     // deleted.
1396     if (p.popup)
1397         p.popup->listBox()->disconnectClient();
1398     hide();
1399 }
1400
1401 // The Mac Chromium implementation relies on external control (a Cocoa control)
1402 // to display, handle the input tracking and menu item selection for the popup.
1403 // Windows and Linux Chromium let our WebKit port handle the display, while
1404 // another process manages the popup window and input handling.
1405 void PopupMenuChromium::show(const IntRect& r, FrameView* v, int index)
1406 {
1407     if (!p.popup)
1408         p.popup = PopupContainer::create(client(), PopupContainer::Select, dropDownSettings);
1409 #if OS(DARWIN)
1410     p.popup->showExternal(r, v, index);
1411 #else
1412     p.popup->show(r, v, index);
1413 #endif
1414 }
1415
1416 void PopupMenuChromium::hide()
1417 {
1418     if (p.popup)
1419         p.popup->hide();
1420 }
1421
1422 void PopupMenuChromium::updateFromElement()
1423 {
1424     p.popup->listBox()->updateFromElement();
1425 }
1426
1427
1428 void PopupMenuChromium::disconnectClient()
1429 {
1430     m_popupClient = 0;
1431 }
1432
1433 } // namespace WebCore