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