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