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