Home | History | Annotate | Download | only in web
      1 /*
      2  * Copyright (c) 2011, Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "PopupContainer.h"
     33 
     34 #include "PopupListBox.h"
     35 #include "core/dom/Document.h"
     36 #include "core/dom/UserGestureIndicator.h"
     37 #include "core/page/Chrome.h"
     38 #include "core/page/ChromeClient.h"
     39 #include "core/page/Frame.h"
     40 #include "core/page/FrameView.h"
     41 #include "core/page/Page.h"
     42 #include "core/platform/PlatformGestureEvent.h"
     43 #include "core/platform/PlatformKeyboardEvent.h"
     44 #include "core/platform/PlatformMouseEvent.h"
     45 #include "core/platform/PlatformScreen.h"
     46 #include "core/platform/PlatformTouchEvent.h"
     47 #include "core/platform/PlatformWheelEvent.h"
     48 #include "core/platform/PopupMenuClient.h"
     49 #include "core/platform/chromium/FramelessScrollView.h"
     50 #include "core/platform/chromium/FramelessScrollViewClient.h"
     51 #include "core/platform/graphics/GraphicsContext.h"
     52 #include "core/platform/graphics/IntRect.h"
     53 #include <limits>
     54 
     55 namespace WebCore {
     56 
     57 static const int borderSize = 1;
     58 
     59 static PlatformMouseEvent constructRelativeMouseEvent(const PlatformMouseEvent& e, FramelessScrollView* parent, FramelessScrollView* child)
     60 {
     61     IntPoint pos = parent->convertSelfToChild(child, e.position());
     62 
     63     // FIXME: This is a horrible hack since PlatformMouseEvent has no setters for x/y.
     64     PlatformMouseEvent relativeEvent = e;
     65     IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.position());
     66     relativePos.setX(pos.x());
     67     relativePos.setY(pos.y());
     68     return relativeEvent;
     69 }
     70 
     71 static PlatformWheelEvent constructRelativeWheelEvent(const PlatformWheelEvent& e, FramelessScrollView* parent, FramelessScrollView* child)
     72 {
     73     IntPoint pos = parent->convertSelfToChild(child, e.position());
     74 
     75     // FIXME: This is a horrible hack since PlatformWheelEvent has no setters for x/y.
     76     PlatformWheelEvent relativeEvent = e;
     77     IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.position());
     78     relativePos.setX(pos.x());
     79     relativePos.setY(pos.y());
     80     return relativeEvent;
     81 }
     82 
     83 // static
     84 PassRefPtr<PopupContainer> PopupContainer::create(PopupMenuClient* client, PopupType popupType, const PopupContainerSettings& settings)
     85 {
     86     return adoptRef(new PopupContainer(client, popupType, settings));
     87 }
     88 
     89 PopupContainer::PopupContainer(PopupMenuClient* client, PopupType popupType, const PopupContainerSettings& settings)
     90     : m_listBox(PopupListBox::create(client, settings))
     91     , m_settings(settings)
     92     , m_popupType(popupType)
     93     , m_popupOpen(false)
     94 {
     95     setScrollbarModes(ScrollbarAlwaysOff, ScrollbarAlwaysOff);
     96 }
     97 
     98 PopupContainer::~PopupContainer()
     99 {
    100     if (m_listBox && m_listBox->parent())
    101         removeChild(m_listBox.get());
    102 }
    103 
    104 IntRect PopupContainer::layoutAndCalculateWidgetRectInternal(IntRect widgetRectInScreen, int targetControlHeight, const FloatRect& windowRect, const FloatRect& screen, bool isRTL, const int rtlOffset, const int verticalOffset, const IntSize& transformOffset, PopupContent* listBox, bool& needToResizeView)
    105 {
    106     ASSERT(listBox);
    107     if (windowRect.x() >= screen.x() && windowRect.maxX() <= screen.maxX() && (widgetRectInScreen.x() < screen.x() || widgetRectInScreen.maxX() > screen.maxX())) {
    108         // First, inverse the popup alignment if it does not fit the screen -
    109         // this might fix things (or make them better).
    110         IntRect inverseWidgetRectInScreen = widgetRectInScreen;
    111         inverseWidgetRectInScreen.setX(inverseWidgetRectInScreen.x() + (isRTL ? -rtlOffset : rtlOffset));
    112         inverseWidgetRectInScreen.setY(inverseWidgetRectInScreen.y() + (isRTL ? -verticalOffset : verticalOffset));
    113         IntRect enclosingScreen = enclosingIntRect(screen);
    114         unsigned originalCutoff = std::max(enclosingScreen.x() - widgetRectInScreen.x(), 0) + std::max(widgetRectInScreen.maxX() - enclosingScreen.maxX(), 0);
    115         unsigned inverseCutoff = std::max(enclosingScreen.x() - inverseWidgetRectInScreen.x(), 0) + std::max(inverseWidgetRectInScreen.maxX() - enclosingScreen.maxX(), 0);
    116 
    117         // Accept the inverse popup alignment if the trimmed content gets
    118         // shorter than that in the original alignment case.
    119         if (inverseCutoff < originalCutoff)
    120             widgetRectInScreen = inverseWidgetRectInScreen;
    121 
    122         if (widgetRectInScreen.x() < screen.x()) {
    123             widgetRectInScreen.setWidth(widgetRectInScreen.maxX() - screen.x());
    124             widgetRectInScreen.setX(screen.x());
    125             listBox->setMaxWidthAndLayout(std::max(widgetRectInScreen.width() - borderSize * 2, 0));
    126         } else if (widgetRectInScreen.maxX() > screen.maxX()) {
    127             widgetRectInScreen.setWidth(screen.maxX() - widgetRectInScreen.x());
    128             listBox->setMaxWidthAndLayout(std::max(widgetRectInScreen.width() - borderSize * 2, 0));
    129         }
    130     }
    131 
    132     // Calculate Y axis size.
    133     if (widgetRectInScreen.maxY() > static_cast<int>(screen.maxY())) {
    134         if (widgetRectInScreen.y() - widgetRectInScreen.height() - targetControlHeight - transformOffset.height() > 0) {
    135             // There is enough room to open upwards.
    136             widgetRectInScreen.move(-transformOffset.width(), -(widgetRectInScreen.height() + targetControlHeight + transformOffset.height()));
    137         } else {
    138             // Figure whether upwards or downwards has more room and set the
    139             // maximum number of items.
    140             int spaceAbove = widgetRectInScreen.y() - targetControlHeight + transformOffset.height();
    141             int spaceBelow = screen.maxY() - widgetRectInScreen.y();
    142             if (spaceAbove > spaceBelow)
    143                 listBox->setMaxHeight(spaceAbove);
    144             else
    145                 listBox->setMaxHeight(spaceBelow);
    146             listBox->layout();
    147             needToResizeView = true;
    148             widgetRectInScreen.setHeight(listBox->popupContentHeight() + borderSize * 2);
    149             // Move WebWidget upwards if necessary.
    150             if (spaceAbove > spaceBelow)
    151                 widgetRectInScreen.move(-transformOffset.width(), -(widgetRectInScreen.height() + targetControlHeight + transformOffset.height()));
    152         }
    153     }
    154     return widgetRectInScreen;
    155 }
    156 
    157 IntRect PopupContainer::layoutAndCalculateWidgetRect(int targetControlHeight, const IntSize& transformOffset, const IntPoint& popupInitialCoordinate)
    158 {
    159     // Reset the max width and height to their default values, they will be
    160     // recomputed below if necessary.
    161     m_listBox->setMaxHeight(PopupListBox::defaultMaxHeight);
    162     m_listBox->setMaxWidth(std::numeric_limits<int>::max());
    163 
    164     // Lay everything out to figure out our preferred size, then tell the view's
    165     // WidgetClient about it. It should assign us a client.
    166     m_listBox->layout();
    167     fitToListBox();
    168     bool isRTL = this->isRTL();
    169 
    170     // Compute the starting x-axis for a normal RTL or right-aligned LTR
    171     // dropdown. For those, the right edge of dropdown box should be aligned
    172     // with the right edge of <select>/<input> element box, and the dropdown box
    173     // should be expanded to the left if more space is needed.
    174     // m_originalFrameRect.width() is the width of the target <select>/<input>
    175     // element.
    176     int rtlOffset = m_controlPosition.p2().x() - m_controlPosition.p1().x() - (m_listBox->width() + borderSize * 2);
    177     int rightOffset = isRTL ? rtlOffset : 0;
    178 
    179     // Compute the y-axis offset between the bottom left and bottom right
    180     // points. If the <select>/<input> is transformed, they are not the same.
    181     int verticalOffset = - m_controlPosition.p4().y() + m_controlPosition.p3().y();
    182     int verticalForRTLOffset = isRTL ? verticalOffset : 0;
    183 
    184     // Assume m_listBox size is already calculated.
    185     IntSize targetSize(m_listBox->width() + borderSize * 2, m_listBox->height() + borderSize * 2);
    186 
    187     IntRect widgetRectInScreen;
    188     if (ChromeClient* client = chromeClient()) {
    189         // If the popup would extend past the bottom of the screen, open upwards
    190         // instead.
    191         FloatRect screen = screenAvailableRect(m_frameView.get());
    192         // Use popupInitialCoordinate.x() + rightOffset because RTL position
    193         // needs to be considered.
    194         widgetRectInScreen = client->rootViewToScreen(IntRect(popupInitialCoordinate.x() + rightOffset, popupInitialCoordinate.y() + verticalForRTLOffset, targetSize.width(), targetSize.height()));
    195 
    196         // If we have multiple screens and the browser rect is in one screen, we
    197         // have to clip the window width to the screen width.
    198         // When clipping, we also need to set a maximum width for the list box.
    199         FloatRect windowRect = client->windowRect();
    200 
    201         bool needToResizeView = false;
    202         widgetRectInScreen = layoutAndCalculateWidgetRectInternal(widgetRectInScreen, targetControlHeight, windowRect, screen, isRTL, rtlOffset, verticalOffset, transformOffset, m_listBox.get(), needToResizeView);
    203         if (needToResizeView)
    204             fitToListBox();
    205     }
    206 
    207     return widgetRectInScreen;
    208 }
    209 
    210 void PopupContainer::showPopup(FrameView* view)
    211 {
    212     m_frameView = view;
    213     listBox()->m_focusedElement = m_frameView->frame()->document()->focusedElement();
    214 
    215     if (ChromeClient* client = chromeClient()) {
    216         IntSize transformOffset(m_controlPosition.p4().x() - m_controlPosition.p1().x(), m_controlPosition.p4().y() - m_controlPosition.p1().y() - m_controlSize.height());
    217         client->popupOpened(this, layoutAndCalculateWidgetRect(m_controlSize.height(), transformOffset, roundedIntPoint(m_controlPosition.p4())), false);
    218         m_popupOpen = true;
    219     }
    220 
    221     if (!m_listBox->parent())
    222         addChild(m_listBox.get());
    223 
    224     // Enable scrollbars after the listbox is inserted into the hierarchy,
    225     // so it has a proper WidgetClient.
    226     m_listBox->setVerticalScrollbarMode(ScrollbarAuto);
    227 
    228     m_listBox->scrollToRevealSelection();
    229 
    230     invalidate();
    231 }
    232 
    233 void PopupContainer::hidePopup()
    234 {
    235     listBox()->hidePopup();
    236 }
    237 
    238 void PopupContainer::notifyPopupHidden()
    239 {
    240     if (!m_popupOpen)
    241         return;
    242     m_popupOpen = false;
    243     chromeClient()->popupClosed(this);
    244 }
    245 
    246 void PopupContainer::fitToListBox()
    247 {
    248     // Place the listbox within our border.
    249     m_listBox->move(borderSize, borderSize);
    250 
    251     // Size ourselves to contain listbox + border.
    252     resize(m_listBox->width() + borderSize * 2, m_listBox->height() + borderSize * 2);
    253     invalidate();
    254 }
    255 
    256 bool PopupContainer::handleMouseDownEvent(const PlatformMouseEvent& event)
    257 {
    258     UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
    259     return m_listBox->handleMouseDownEvent(
    260         constructRelativeMouseEvent(event, this, m_listBox.get()));
    261 }
    262 
    263 bool PopupContainer::handleMouseMoveEvent(const PlatformMouseEvent& event)
    264 {
    265     UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
    266     return m_listBox->handleMouseMoveEvent(
    267         constructRelativeMouseEvent(event, this, m_listBox.get()));
    268 }
    269 
    270 bool PopupContainer::handleMouseReleaseEvent(const PlatformMouseEvent& event)
    271 {
    272     RefPtr<PopupContainer> protect(this);
    273     UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
    274     return m_listBox->handleMouseReleaseEvent(
    275         constructRelativeMouseEvent(event, this, m_listBox.get()));
    276 }
    277 
    278 bool PopupContainer::handleWheelEvent(const PlatformWheelEvent& event)
    279 {
    280     UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
    281     return m_listBox->handleWheelEvent(
    282         constructRelativeWheelEvent(event, this, m_listBox.get()));
    283 }
    284 
    285 bool PopupContainer::handleTouchEvent(const PlatformTouchEvent&)
    286 {
    287     return false;
    288 }
    289 
    290 // FIXME: Refactor this code to share functionality with
    291 // EventHandler::handleGestureEvent.
    292 bool PopupContainer::handleGestureEvent(const PlatformGestureEvent& gestureEvent)
    293 {
    294     switch (gestureEvent.type()) {
    295     case PlatformEvent::GestureTap: {
    296         PlatformMouseEvent fakeMouseMove(gestureEvent.position(), gestureEvent.globalPosition(), NoButton, PlatformEvent::MouseMoved, /* clickCount */ 1, gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), gestureEvent.timestamp());
    297         PlatformMouseEvent fakeMouseDown(gestureEvent.position(), gestureEvent.globalPosition(), LeftButton, PlatformEvent::MousePressed, /* clickCount */ 1, gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), gestureEvent.timestamp());
    298         PlatformMouseEvent fakeMouseUp(gestureEvent.position(), gestureEvent.globalPosition(), LeftButton, PlatformEvent::MouseReleased, /* clickCount */ 1, gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), gestureEvent.timestamp());
    299         // handleMouseMoveEvent(fakeMouseMove);
    300         handleMouseDownEvent(fakeMouseDown);
    301         handleMouseReleaseEvent(fakeMouseUp);
    302         return true;
    303     }
    304     case PlatformEvent::GestureScrollUpdate:
    305     case PlatformEvent::GestureScrollUpdateWithoutPropagation: {
    306         PlatformWheelEvent syntheticWheelEvent(gestureEvent.position(), gestureEvent.globalPosition(), gestureEvent.deltaX(), gestureEvent.deltaY(), gestureEvent.deltaX() / 120.0f, gestureEvent.deltaY() / 120.0f, ScrollByPixelWheelEvent, gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey());
    307         handleWheelEvent(syntheticWheelEvent);
    308         return true;
    309     }
    310     case PlatformEvent::GestureScrollBegin:
    311     case PlatformEvent::GestureScrollEnd:
    312     case PlatformEvent::GestureTapDown:
    313         break;
    314     default:
    315         ASSERT_NOT_REACHED();
    316     }
    317     return false;
    318 }
    319 
    320 bool PopupContainer::handleKeyEvent(const PlatformKeyboardEvent& event)
    321 {
    322     UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
    323     return m_listBox->handleKeyEvent(event);
    324 }
    325 
    326 void PopupContainer::hide()
    327 {
    328     m_listBox->abandon();
    329 }
    330 
    331 void PopupContainer::paint(GraphicsContext* gc, const IntRect& rect)
    332 {
    333     // Adjust coords for scrolled frame.
    334     IntRect r = intersection(rect, frameRect());
    335     int tx = x();
    336     int ty = y();
    337 
    338     r.move(-tx, -ty);
    339 
    340     gc->translate(static_cast<float>(tx), static_cast<float>(ty));
    341     m_listBox->paint(gc, r);
    342     gc->translate(-static_cast<float>(tx), -static_cast<float>(ty));
    343 
    344     paintBorder(gc, rect);
    345 }
    346 
    347 void PopupContainer::paintBorder(GraphicsContext* gc, const IntRect& rect)
    348 {
    349     // FIXME: Where do we get the border color from?
    350     Color borderColor(127, 157, 185);
    351 
    352     gc->setStrokeStyle(NoStroke);
    353     gc->setFillColor(borderColor);
    354 
    355     int tx = x();
    356     int ty = y();
    357 
    358     // top, left, bottom, right
    359     gc->drawRect(IntRect(tx, ty, width(), borderSize));
    360     gc->drawRect(IntRect(tx, ty, borderSize, height()));
    361     gc->drawRect(IntRect(tx, ty + height() - borderSize, width(), borderSize));
    362     gc->drawRect(IntRect(tx + width() - borderSize, ty, borderSize, height()));
    363 }
    364 
    365 bool PopupContainer::isInterestedInEventForKey(int keyCode)
    366 {
    367     return m_listBox->isInterestedInEventForKey(keyCode);
    368 }
    369 
    370 ChromeClient* PopupContainer::chromeClient()
    371 {
    372     return m_frameView->frame()->page()->chrome().client();
    373 }
    374 
    375 void PopupContainer::showInRect(const FloatQuad& controlPosition, const IntSize& controlSize, FrameView* v, int index)
    376 {
    377     // The controlSize is the size of the select box. It's usually larger than
    378     // we need. Subtract border size so that usually the container will be
    379     // displayed exactly the same width as the select box.
    380     listBox()->setBaseWidth(max(controlSize.width() - borderSize * 2, 0));
    381 
    382     listBox()->updateFromElement();
    383 
    384     // We set the selected item in updateFromElement(), and disregard the
    385     // index passed into this function (same as Webkit's PopupMenuWin.cpp)
    386     // FIXME: make sure this is correct, and add an assertion.
    387     // ASSERT(popupWindow(popup)->listBox()->selectedIndex() == index);
    388 
    389     // Save and convert the controlPosition to main window coords. Each point is converted separately
    390     // to window coordinates because the control could be in a transformed webview and then each point
    391     // would be transformed by a different delta.
    392     m_controlPosition.setP1(v->contentsToWindow(IntPoint(controlPosition.p1().x(), controlPosition.p1().y())));
    393     m_controlPosition.setP2(v->contentsToWindow(IntPoint(controlPosition.p2().x(), controlPosition.p2().y())));
    394     m_controlPosition.setP3(v->contentsToWindow(IntPoint(controlPosition.p3().x(), controlPosition.p3().y())));
    395     m_controlPosition.setP4(v->contentsToWindow(IntPoint(controlPosition.p4().x(), controlPosition.p4().y())));
    396 
    397     m_controlSize = controlSize;
    398 
    399     // Position at (0, 0) since the frameRect().location() is relative to the
    400     // parent WebWidget.
    401     setFrameRect(IntRect(IntPoint(), controlSize));
    402     showPopup(v);
    403 }
    404 
    405 IntRect PopupContainer::refresh(const IntRect& targetControlRect)
    406 {
    407     listBox()->setBaseWidth(max(m_controlSize.width() - borderSize * 2, 0));
    408     listBox()->updateFromElement();
    409 
    410     IntPoint locationInWindow = m_frameView->contentsToWindow(targetControlRect.location());
    411 
    412     // Move it below the select widget.
    413     locationInWindow.move(0, targetControlRect.height());
    414 
    415     IntRect widgetRectInScreen = layoutAndCalculateWidgetRect(targetControlRect.height(), IntSize(), locationInWindow);
    416 
    417     // Reset the size (which can be set to the PopupListBox size in
    418     // layoutAndGetRTLOffset(), exceeding the available widget rectangle.)
    419     if (size() != widgetRectInScreen.size())
    420         resize(widgetRectInScreen.size());
    421 
    422     invalidate();
    423 
    424     return widgetRectInScreen;
    425 }
    426 
    427 inline bool PopupContainer::isRTL() const
    428 {
    429     return m_listBox->m_popupClient->menuStyle().textDirection() == RTL;
    430 }
    431 
    432 int PopupContainer::selectedIndex() const
    433 {
    434     return m_listBox->selectedIndex();
    435 }
    436 
    437 int PopupContainer::menuItemHeight() const
    438 {
    439     return m_listBox->getRowHeight(0);
    440 }
    441 
    442 int PopupContainer::menuItemFontSize() const
    443 {
    444     return m_listBox->getRowFont(0).size();
    445 }
    446 
    447 PopupMenuStyle PopupContainer::menuStyle() const
    448 {
    449     return m_listBox->m_popupClient->menuStyle();
    450 }
    451 
    452 const WTF::Vector<PopupItem*>& PopupContainer:: popupData() const
    453 {
    454     return m_listBox->items();
    455 }
    456 
    457 String PopupContainer::getSelectedItemToolTip()
    458 {
    459     // We cannot use m_popupClient->selectedIndex() to choose tooltip message,
    460     // because the selectedIndex() might return final selected index, not
    461     // hovering selection.
    462     return listBox()->m_popupClient->itemToolTip(listBox()->m_selectedIndex);
    463 }
    464 
    465 } // namespace WebCore
    466