diff options
Diffstat (limited to 'chromium/third_party/WebKit/Source/web/PopupContainer.cpp')
-rw-r--r-- | chromium/third_party/WebKit/Source/web/PopupContainer.cpp | 465 |
1 files changed, 465 insertions, 0 deletions
diff --git a/chromium/third_party/WebKit/Source/web/PopupContainer.cpp b/chromium/third_party/WebKit/Source/web/PopupContainer.cpp new file mode 100644 index 00000000000..bbd62a188d7 --- /dev/null +++ b/chromium/third_party/WebKit/Source/web/PopupContainer.cpp @@ -0,0 +1,465 @@ +/* + * Copyright (c) 2011, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "PopupContainer.h" + +#include "PopupListBox.h" +#include "core/dom/Document.h" +#include "core/dom/UserGestureIndicator.h" +#include "core/page/Chrome.h" +#include "core/page/ChromeClient.h" +#include "core/page/Frame.h" +#include "core/page/FrameView.h" +#include "core/page/Page.h" +#include "core/platform/PlatformGestureEvent.h" +#include "core/platform/PlatformKeyboardEvent.h" +#include "core/platform/PlatformMouseEvent.h" +#include "core/platform/PlatformScreen.h" +#include "core/platform/PlatformTouchEvent.h" +#include "core/platform/PlatformWheelEvent.h" +#include "core/platform/PopupMenuClient.h" +#include "core/platform/chromium/FramelessScrollView.h" +#include "core/platform/chromium/FramelessScrollViewClient.h" +#include "core/platform/graphics/GraphicsContext.h" +#include "core/platform/graphics/IntRect.h" +#include <limits> + +namespace WebCore { + +static const int borderSize = 1; + +static PlatformMouseEvent constructRelativeMouseEvent(const PlatformMouseEvent& e, FramelessScrollView* parent, FramelessScrollView* child) +{ + IntPoint pos = parent->convertSelfToChild(child, e.position()); + + // FIXME: This is a horrible hack since PlatformMouseEvent has no setters for x/y. + PlatformMouseEvent relativeEvent = e; + IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.position()); + relativePos.setX(pos.x()); + relativePos.setY(pos.y()); + return relativeEvent; +} + +static PlatformWheelEvent constructRelativeWheelEvent(const PlatformWheelEvent& e, FramelessScrollView* parent, FramelessScrollView* child) +{ + IntPoint pos = parent->convertSelfToChild(child, e.position()); + + // FIXME: This is a horrible hack since PlatformWheelEvent has no setters for x/y. + PlatformWheelEvent relativeEvent = e; + IntPoint& relativePos = const_cast<IntPoint&>(relativeEvent.position()); + relativePos.setX(pos.x()); + relativePos.setY(pos.y()); + return relativeEvent; +} + +// static +PassRefPtr<PopupContainer> PopupContainer::create(PopupMenuClient* client, PopupType popupType, const PopupContainerSettings& settings) +{ + return adoptRef(new PopupContainer(client, popupType, settings)); +} + +PopupContainer::PopupContainer(PopupMenuClient* client, PopupType popupType, const PopupContainerSettings& settings) + : m_listBox(PopupListBox::create(client, settings)) + , m_settings(settings) + , m_popupType(popupType) + , m_popupOpen(false) +{ + setScrollbarModes(ScrollbarAlwaysOff, ScrollbarAlwaysOff); +} + +PopupContainer::~PopupContainer() +{ + if (m_listBox && m_listBox->parent()) + removeChild(m_listBox.get()); +} + +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) +{ + ASSERT(listBox); + if (windowRect.x() >= screen.x() && windowRect.maxX() <= screen.maxX() && (widgetRectInScreen.x() < screen.x() || widgetRectInScreen.maxX() > screen.maxX())) { + // First, inverse the popup alignment if it does not fit the screen - + // this might fix things (or make them better). + IntRect inverseWidgetRectInScreen = widgetRectInScreen; + inverseWidgetRectInScreen.setX(inverseWidgetRectInScreen.x() + (isRTL ? -rtlOffset : rtlOffset)); + inverseWidgetRectInScreen.setY(inverseWidgetRectInScreen.y() + (isRTL ? -verticalOffset : verticalOffset)); + IntRect enclosingScreen = enclosingIntRect(screen); + unsigned originalCutoff = std::max(enclosingScreen.x() - widgetRectInScreen.x(), 0) + std::max(widgetRectInScreen.maxX() - enclosingScreen.maxX(), 0); + unsigned inverseCutoff = std::max(enclosingScreen.x() - inverseWidgetRectInScreen.x(), 0) + std::max(inverseWidgetRectInScreen.maxX() - enclosingScreen.maxX(), 0); + + // Accept the inverse popup alignment if the trimmed content gets + // shorter than that in the original alignment case. + if (inverseCutoff < originalCutoff) + widgetRectInScreen = inverseWidgetRectInScreen; + + if (widgetRectInScreen.x() < screen.x()) { + widgetRectInScreen.setWidth(widgetRectInScreen.maxX() - screen.x()); + widgetRectInScreen.setX(screen.x()); + listBox->setMaxWidthAndLayout(std::max(widgetRectInScreen.width() - borderSize * 2, 0)); + } else if (widgetRectInScreen.maxX() > screen.maxX()) { + widgetRectInScreen.setWidth(screen.maxX() - widgetRectInScreen.x()); + listBox->setMaxWidthAndLayout(std::max(widgetRectInScreen.width() - borderSize * 2, 0)); + } + } + + // Calculate Y axis size. + if (widgetRectInScreen.maxY() > static_cast<int>(screen.maxY())) { + if (widgetRectInScreen.y() - widgetRectInScreen.height() - targetControlHeight - transformOffset.height() > 0) { + // There is enough room to open upwards. + widgetRectInScreen.move(-transformOffset.width(), -(widgetRectInScreen.height() + targetControlHeight + transformOffset.height())); + } else { + // Figure whether upwards or downwards has more room and set the + // maximum number of items. + int spaceAbove = widgetRectInScreen.y() - targetControlHeight + transformOffset.height(); + int spaceBelow = screen.maxY() - widgetRectInScreen.y(); + if (spaceAbove > spaceBelow) + listBox->setMaxHeight(spaceAbove); + else + listBox->setMaxHeight(spaceBelow); + listBox->layout(); + needToResizeView = true; + widgetRectInScreen.setHeight(listBox->popupContentHeight() + borderSize * 2); + // Move WebWidget upwards if necessary. + if (spaceAbove > spaceBelow) + widgetRectInScreen.move(-transformOffset.width(), -(widgetRectInScreen.height() + targetControlHeight + transformOffset.height())); + } + } + return widgetRectInScreen; +} + +IntRect PopupContainer::layoutAndCalculateWidgetRect(int targetControlHeight, const IntSize& transformOffset, const IntPoint& popupInitialCoordinate) +{ + // Reset the max width and height to their default values, they will be + // recomputed below if necessary. + m_listBox->setMaxHeight(PopupListBox::defaultMaxHeight); + m_listBox->setMaxWidth(std::numeric_limits<int>::max()); + + // Lay everything out to figure out our preferred size, then tell the view's + // WidgetClient about it. It should assign us a client. + m_listBox->layout(); + fitToListBox(); + bool isRTL = this->isRTL(); + + // Compute the starting x-axis for a normal RTL or right-aligned LTR + // dropdown. For those, the right edge of dropdown box should be aligned + // with the right edge of <select>/<input> element box, and the dropdown box + // should be expanded to the left if more space is needed. + // m_originalFrameRect.width() is the width of the target <select>/<input> + // element. + int rtlOffset = m_controlPosition.p2().x() - m_controlPosition.p1().x() - (m_listBox->width() + borderSize * 2); + int rightOffset = isRTL ? rtlOffset : 0; + + // Compute the y-axis offset between the bottom left and bottom right + // points. If the <select>/<input> is transformed, they are not the same. + int verticalOffset = - m_controlPosition.p4().y() + m_controlPosition.p3().y(); + int verticalForRTLOffset = isRTL ? verticalOffset : 0; + + // Assume m_listBox size is already calculated. + IntSize targetSize(m_listBox->width() + borderSize * 2, m_listBox->height() + borderSize * 2); + + IntRect widgetRectInScreen; + if (ChromeClient* client = chromeClient()) { + // If the popup would extend past the bottom of the screen, open upwards + // instead. + FloatRect screen = screenAvailableRect(m_frameView.get()); + // Use popupInitialCoordinate.x() + rightOffset because RTL position + // needs to be considered. + widgetRectInScreen = client->rootViewToScreen(IntRect(popupInitialCoordinate.x() + rightOffset, popupInitialCoordinate.y() + verticalForRTLOffset, targetSize.width(), targetSize.height())); + + // If we have multiple screens and the browser rect is in one screen, we + // have to clip the window width to the screen width. + // When clipping, we also need to set a maximum width for the list box. + FloatRect windowRect = client->windowRect(); + + bool needToResizeView = false; + widgetRectInScreen = layoutAndCalculateWidgetRectInternal(widgetRectInScreen, targetControlHeight, windowRect, screen, isRTL, rtlOffset, verticalOffset, transformOffset, m_listBox.get(), needToResizeView); + if (needToResizeView) + fitToListBox(); + } + + return widgetRectInScreen; +} + +void PopupContainer::showPopup(FrameView* view) +{ + m_frameView = view; + listBox()->m_focusedElement = m_frameView->frame()->document()->focusedElement(); + + if (ChromeClient* client = chromeClient()) { + IntSize transformOffset(m_controlPosition.p4().x() - m_controlPosition.p1().x(), m_controlPosition.p4().y() - m_controlPosition.p1().y() - m_controlSize.height()); + client->popupOpened(this, layoutAndCalculateWidgetRect(m_controlSize.height(), transformOffset, roundedIntPoint(m_controlPosition.p4())), false); + m_popupOpen = true; + } + + if (!m_listBox->parent()) + addChild(m_listBox.get()); + + // Enable scrollbars after the listbox is inserted into the hierarchy, + // so it has a proper WidgetClient. + m_listBox->setVerticalScrollbarMode(ScrollbarAuto); + + m_listBox->scrollToRevealSelection(); + + invalidate(); +} + +void PopupContainer::hidePopup() +{ + listBox()->hidePopup(); +} + +void PopupContainer::notifyPopupHidden() +{ + if (!m_popupOpen) + return; + m_popupOpen = false; + chromeClient()->popupClosed(this); +} + +void PopupContainer::fitToListBox() +{ + // Place the listbox within our border. + m_listBox->move(borderSize, borderSize); + + // Size ourselves to contain listbox + border. + resize(m_listBox->width() + borderSize * 2, m_listBox->height() + borderSize * 2); + invalidate(); +} + +bool PopupContainer::handleMouseDownEvent(const PlatformMouseEvent& event) +{ + UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture); + return m_listBox->handleMouseDownEvent( + constructRelativeMouseEvent(event, this, m_listBox.get())); +} + +bool PopupContainer::handleMouseMoveEvent(const PlatformMouseEvent& event) +{ + UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture); + return m_listBox->handleMouseMoveEvent( + constructRelativeMouseEvent(event, this, m_listBox.get())); +} + +bool PopupContainer::handleMouseReleaseEvent(const PlatformMouseEvent& event) +{ + RefPtr<PopupContainer> protect(this); + UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture); + return m_listBox->handleMouseReleaseEvent( + constructRelativeMouseEvent(event, this, m_listBox.get())); +} + +bool PopupContainer::handleWheelEvent(const PlatformWheelEvent& event) +{ + UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture); + return m_listBox->handleWheelEvent( + constructRelativeWheelEvent(event, this, m_listBox.get())); +} + +bool PopupContainer::handleTouchEvent(const PlatformTouchEvent&) +{ + return false; +} + +// FIXME: Refactor this code to share functionality with +// EventHandler::handleGestureEvent. +bool PopupContainer::handleGestureEvent(const PlatformGestureEvent& gestureEvent) +{ + switch (gestureEvent.type()) { + case PlatformEvent::GestureTap: { + PlatformMouseEvent fakeMouseMove(gestureEvent.position(), gestureEvent.globalPosition(), NoButton, PlatformEvent::MouseMoved, /* clickCount */ 1, gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), gestureEvent.timestamp()); + PlatformMouseEvent fakeMouseDown(gestureEvent.position(), gestureEvent.globalPosition(), LeftButton, PlatformEvent::MousePressed, /* clickCount */ 1, gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), gestureEvent.timestamp()); + PlatformMouseEvent fakeMouseUp(gestureEvent.position(), gestureEvent.globalPosition(), LeftButton, PlatformEvent::MouseReleased, /* clickCount */ 1, gestureEvent.shiftKey(), gestureEvent.ctrlKey(), gestureEvent.altKey(), gestureEvent.metaKey(), gestureEvent.timestamp()); + // handleMouseMoveEvent(fakeMouseMove); + handleMouseDownEvent(fakeMouseDown); + handleMouseReleaseEvent(fakeMouseUp); + return true; + } + case PlatformEvent::GestureScrollUpdate: + case PlatformEvent::GestureScrollUpdateWithoutPropagation: { + 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()); + handleWheelEvent(syntheticWheelEvent); + return true; + } + case PlatformEvent::GestureScrollBegin: + case PlatformEvent::GestureScrollEnd: + case PlatformEvent::GestureTapDown: + break; + default: + ASSERT_NOT_REACHED(); + } + return false; +} + +bool PopupContainer::handleKeyEvent(const PlatformKeyboardEvent& event) +{ + UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture); + return m_listBox->handleKeyEvent(event); +} + +void PopupContainer::hide() +{ + m_listBox->abandon(); +} + +void PopupContainer::paint(GraphicsContext* gc, const IntRect& rect) +{ + // Adjust coords for scrolled frame. + IntRect r = intersection(rect, frameRect()); + int tx = x(); + int ty = y(); + + r.move(-tx, -ty); + + gc->translate(static_cast<float>(tx), static_cast<float>(ty)); + m_listBox->paint(gc, r); + gc->translate(-static_cast<float>(tx), -static_cast<float>(ty)); + + paintBorder(gc, rect); +} + +void PopupContainer::paintBorder(GraphicsContext* gc, const IntRect& rect) +{ + // FIXME: Where do we get the border color from? + Color borderColor(127, 157, 185); + + gc->setStrokeStyle(NoStroke); + gc->setFillColor(borderColor); + + int tx = x(); + int ty = y(); + + // top, left, bottom, right + gc->drawRect(IntRect(tx, ty, width(), borderSize)); + gc->drawRect(IntRect(tx, ty, borderSize, height())); + gc->drawRect(IntRect(tx, ty + height() - borderSize, width(), borderSize)); + gc->drawRect(IntRect(tx + width() - borderSize, ty, borderSize, height())); +} + +bool PopupContainer::isInterestedInEventForKey(int keyCode) +{ + return m_listBox->isInterestedInEventForKey(keyCode); +} + +ChromeClient* PopupContainer::chromeClient() +{ + return m_frameView->frame()->page()->chrome().client(); +} + +void PopupContainer::showInRect(const FloatQuad& controlPosition, const IntSize& controlSize, FrameView* v, int index) +{ + // The controlSize is the size of the select box. It's usually larger than + // we need. Subtract border size so that usually the container will be + // displayed exactly the same width as the select box. + listBox()->setBaseWidth(max(controlSize.width() - borderSize * 2, 0)); + + listBox()->updateFromElement(); + + // We set the selected item in updateFromElement(), and disregard the + // index passed into this function (same as Webkit's PopupMenuWin.cpp) + // FIXME: make sure this is correct, and add an assertion. + // ASSERT(popupWindow(popup)->listBox()->selectedIndex() == index); + + // Save and convert the controlPosition to main window coords. Each point is converted separately + // to window coordinates because the control could be in a transformed webview and then each point + // would be transformed by a different delta. + m_controlPosition.setP1(v->contentsToWindow(IntPoint(controlPosition.p1().x(), controlPosition.p1().y()))); + m_controlPosition.setP2(v->contentsToWindow(IntPoint(controlPosition.p2().x(), controlPosition.p2().y()))); + m_controlPosition.setP3(v->contentsToWindow(IntPoint(controlPosition.p3().x(), controlPosition.p3().y()))); + m_controlPosition.setP4(v->contentsToWindow(IntPoint(controlPosition.p4().x(), controlPosition.p4().y()))); + + m_controlSize = controlSize; + + // Position at (0, 0) since the frameRect().location() is relative to the + // parent WebWidget. + setFrameRect(IntRect(IntPoint(), controlSize)); + showPopup(v); +} + +IntRect PopupContainer::refresh(const IntRect& targetControlRect) +{ + listBox()->setBaseWidth(max(m_controlSize.width() - borderSize * 2, 0)); + listBox()->updateFromElement(); + + IntPoint locationInWindow = m_frameView->contentsToWindow(targetControlRect.location()); + + // Move it below the select widget. + locationInWindow.move(0, targetControlRect.height()); + + IntRect widgetRectInScreen = layoutAndCalculateWidgetRect(targetControlRect.height(), IntSize(), locationInWindow); + + // Reset the size (which can be set to the PopupListBox size in + // layoutAndGetRTLOffset(), exceeding the available widget rectangle.) + if (size() != widgetRectInScreen.size()) + resize(widgetRectInScreen.size()); + + invalidate(); + + return widgetRectInScreen; +} + +inline bool PopupContainer::isRTL() const +{ + return m_listBox->m_popupClient->menuStyle().textDirection() == RTL; +} + +int PopupContainer::selectedIndex() const +{ + return m_listBox->selectedIndex(); +} + +int PopupContainer::menuItemHeight() const +{ + return m_listBox->getRowHeight(0); +} + +int PopupContainer::menuItemFontSize() const +{ + return m_listBox->getRowFont(0).size(); +} + +PopupMenuStyle PopupContainer::menuStyle() const +{ + return m_listBox->m_popupClient->menuStyle(); +} + +const WTF::Vector<PopupItem*>& PopupContainer:: popupData() const +{ + return m_listBox->items(); +} + +String PopupContainer::getSelectedItemToolTip() +{ + // We cannot use m_popupClient->selectedIndex() to choose tooltip message, + // because the selectedIndex() might return final selected index, not + // hovering selection. + return listBox()->m_popupClient->itemToolTip(listBox()->m_selectedIndex); +} + +} // namespace WebCore |