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