| /* |
| * Copyright (C) 2006-2008, 2011, 2015 Apple Inc. All rights reserved. |
| * Copyright (C) 2007-2009 Torch Mobile Inc. |
| * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #include "config.h" |
| #include "PopupMenuWin.h" |
| |
| #include "BString.h" |
| #include "BitmapInfo.h" |
| #include "Document.h" |
| #include "FloatRect.h" |
| #include "Font.h" |
| #include "FontSelector.h" |
| #include "Frame.h" |
| #include "FrameView.h" |
| #include "GDIUtilities.h" |
| #include "GraphicsContext.h" |
| #include "GraphicsContextWin.h" |
| #include "HTMLNames.h" |
| #include "HWndDC.h" |
| #include "HostWindow.h" |
| #include "LengthFunctions.h" |
| #include "NotImplemented.h" |
| #include "Page.h" |
| #include "PlatformMouseEvent.h" |
| #include "PlatformScreen.h" |
| #include "RenderMenuList.h" |
| #include "RenderTheme.h" |
| #include "RenderView.h" |
| #include "Scrollbar.h" |
| #include "ScrollbarTheme.h" |
| #include "ScrollbarThemeWin.h" |
| #include "TextRun.h" |
| #include "WebCoreInstanceHandle.h" |
| #include <wtf/HexNumber.h> |
| #include <wtf/WindowsExtras.h> |
| #include <wtf/text/StringBuilder.h> |
| |
| #include <windows.h> |
| #include <windowsx.h> |
| |
| #define HIGH_BIT_MASK_SHORT 0x8000 |
| |
| using std::min; |
| |
| namespace WebCore { |
| |
| using namespace HTMLNames; |
| |
| // Default Window animation duration in milliseconds |
| static const int defaultAnimationDuration = 200; |
| // Maximum height of a popup window |
| static const int maxPopupHeight = 320; |
| |
| const int optionSpacingMiddle = 1; |
| const int popupWindowBorderWidth = 1; |
| |
| static LPCWSTR kPopupWindowClassName = L"PopupWindowClass"; |
| |
| // This is used from within our custom message pump when we want to send a |
| // message to the web view and not have our message stolen and sent to |
| // the popup window. |
| static const UINT WM_HOST_WINDOW_FIRST = WM_USER; |
| static const UINT WM_HOST_WINDOW_CHAR = WM_USER + WM_CHAR; |
| static const UINT WM_HOST_WINDOW_MOUSEMOVE = WM_USER + WM_MOUSEMOVE; |
| |
| // FIXME: Remove this as soon as practical. |
| static inline bool isASCIIPrintable(unsigned c) |
| { |
| return c >= 0x20 && c <= 0x7E; |
| } |
| |
| static void translatePoint(LPARAM& lParam, HWND from, HWND to) |
| { |
| POINT pt; |
| pt.x = (short)GET_X_LPARAM(lParam); |
| pt.y = (short)GET_Y_LPARAM(lParam); |
| ::MapWindowPoints(from, to, &pt, 1); |
| lParam = MAKELPARAM(pt.x, pt.y); |
| } |
| |
| static IntRect monitorFromHwnd(HWND hwnd) |
| { |
| HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); |
| MONITORINFOEX monitorInfo; |
| monitorInfo.cbSize = sizeof(MONITORINFOEX); |
| GetMonitorInfo(monitor, &monitorInfo); |
| return monitorInfo.rcWork; |
| } |
| |
| PopupMenuWin::PopupMenuWin(PopupMenuClient* client) |
| : m_popupClient(client) |
| { |
| } |
| |
| PopupMenuWin::~PopupMenuWin() |
| { |
| if (m_popup) |
| ::DestroyWindow(m_popup); |
| if (m_scrollbar) |
| m_scrollbar->setParent(0); |
| } |
| |
| void PopupMenuWin::disconnectClient() |
| { |
| m_popupClient = 0; |
| } |
| |
| LPCWSTR PopupMenuWin::popupClassName() |
| { |
| return kPopupWindowClassName; |
| } |
| |
| void PopupMenuWin::show(const IntRect& r, FrameView* view, int index) |
| { |
| if (view && view->frame().page()) |
| m_scaleFactor = view->frame().page()->deviceScaleFactor(); |
| |
| calculatePositionAndSize(r, view); |
| if (clientRect().isEmpty()) |
| return; |
| |
| HWND hostWindow = view->hostWindow()->platformPageClient(); |
| |
| if (!m_scrollbar && visibleItems() < client()->listSize()) { |
| // We need a scroll bar |
| m_scrollbar = client()->createScrollbar(*this, ScrollbarOrientation::Vertical, ScrollbarControlSize::Small); |
| m_scrollbar->styleChanged(); |
| } |
| |
| // We need to reposition the popup window to its final coordinates. |
| // Before calling this, the popup hwnd is currently the size of and at the location of the menu list client so it needs to be updated. |
| ::MoveWindow(m_popup, m_windowRect.x(), m_windowRect.y(), m_windowRect.width(), m_windowRect.height(), false); |
| |
| // Determine whether we should animate our popups |
| // Note: Must use 'BOOL' and 'FALSE' instead of 'bool' and 'false' to avoid stack corruption with SystemParametersInfo |
| BOOL shouldAnimate = FALSE; |
| |
| if (client()) { |
| int index = client()->selectedIndex(); |
| if (index >= 0) |
| setFocusedIndex(index); |
| } |
| |
| if (!::SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, &shouldAnimate, 0)) |
| shouldAnimate = FALSE; |
| |
| if (shouldAnimate) { |
| RECT viewRect { }; |
| ::GetWindowRect(hostWindow, &viewRect); |
| if (!::IsRectEmpty(&viewRect)) |
| ::AnimateWindow(m_popup, defaultAnimationDuration, AW_BLEND); |
| } else |
| ::ShowWindow(m_popup, SW_SHOWNOACTIVATE); |
| |
| m_showPopup = true; |
| |
| // Protect the popup menu in case its owner is destroyed while we're running the message pump. |
| RefPtr<PopupMenu> protectedThis(this); |
| |
| ::SetCapture(hostWindow); |
| |
| MSG msg; |
| HWND activeWindow; |
| |
| while (::GetMessage(&msg, 0, 0, 0)) { |
| switch (msg.message) { |
| case WM_HOST_WINDOW_MOUSEMOVE: |
| case WM_HOST_WINDOW_CHAR: |
| if (msg.hwnd == m_popup) { |
| // This message should be sent to the host window. |
| msg.hwnd = hostWindow; |
| msg.message -= WM_HOST_WINDOW_FIRST; |
| } |
| break; |
| |
| // Steal mouse messages. |
| case WM_NCMOUSEMOVE: |
| case WM_NCLBUTTONDOWN: |
| case WM_NCLBUTTONUP: |
| case WM_NCLBUTTONDBLCLK: |
| case WM_NCRBUTTONDOWN: |
| case WM_NCRBUTTONUP: |
| case WM_NCRBUTTONDBLCLK: |
| case WM_NCMBUTTONDOWN: |
| case WM_NCMBUTTONUP: |
| case WM_NCMBUTTONDBLCLK: |
| case WM_MOUSEWHEEL: |
| msg.hwnd = m_popup; |
| break; |
| |
| // These mouse messages use client coordinates so we need to convert them. |
| case WM_MOUSEMOVE: |
| case WM_LBUTTONDOWN: |
| case WM_LBUTTONUP: |
| case WM_LBUTTONDBLCLK: |
| case WM_RBUTTONDOWN: |
| case WM_RBUTTONUP: |
| case WM_RBUTTONDBLCLK: |
| case WM_MBUTTONDOWN: |
| case WM_MBUTTONUP: |
| case WM_MBUTTONDBLCLK: { |
| // Translate the coordinate. |
| translatePoint(msg.lParam, msg.hwnd, m_popup); |
| |
| msg.hwnd = m_popup; |
| break; |
| } |
| |
| // Steal all keyboard messages. |
| case WM_KEYDOWN: |
| case WM_KEYUP: |
| case WM_CHAR: |
| case WM_DEADCHAR: |
| case WM_SYSKEYDOWN: |
| case WM_SYSKEYUP: |
| case WM_SYSCHAR: |
| case WM_SYSDEADCHAR: |
| msg.hwnd = m_popup; |
| break; |
| } |
| |
| ::TranslateMessage(&msg); |
| ::DispatchMessage(&msg); |
| |
| if (!m_popupClient) |
| break; |
| |
| if (!m_showPopup) |
| break; |
| activeWindow = ::GetActiveWindow(); |
| if (activeWindow != hostWindow && !::IsChild(activeWindow, hostWindow)) |
| break; |
| if (::GetCapture() != hostWindow) |
| break; |
| } |
| |
| if (::GetCapture() == hostWindow) |
| ::ReleaseCapture(); |
| |
| // We're done, hide the popup if necessary. |
| hide(); |
| } |
| |
| void PopupMenuWin::hide() |
| { |
| if (!m_showPopup) |
| return; |
| |
| m_showPopup = false; |
| |
| ::ShowWindow(m_popup, SW_HIDE); |
| |
| if (client()) |
| client()->popupDidHide(); |
| |
| // Post a WM_NULL message to wake up the message pump if necessary. |
| ::PostMessage(m_popup, WM_NULL, 0, 0); |
| } |
| |
| // The screen that the popup is placed on should be whichever one the popup menu button lies on. |
| // We fake an hwnd (here we use the popup's hwnd) on top of the button which we can then use to determine the screen. |
| // We can then proceed with our final position/size calculations. |
| void PopupMenuWin::calculatePositionAndSize(const IntRect& r, FrameView* v) |
| { |
| // First get the screen coordinates of the popup menu client. |
| HWND hostWindow = v->hostWindow()->platformPageClient(); |
| IntRect absoluteBounds = ((RenderMenuList*)m_popupClient)->absoluteBoundingBoxRect(); |
| IntRect absoluteScreenCoords(v->contentsToWindow(absoluteBounds.location()), absoluteBounds.size()); |
| POINT absoluteLocation(absoluteScreenCoords.location()); |
| if (!::ClientToScreen(hostWindow, &absoluteLocation)) |
| return; |
| absoluteScreenCoords.setLocation(absoluteLocation); |
| |
| // Now set the popup menu's location temporarily to these coordinates so we can determine which screen the popup should lie on. |
| // We create or move m_popup as necessary. |
| if (!m_popup) { |
| registerClass(); |
| DWORD exStyle = WS_EX_LTRREADING; |
| m_popup = ::CreateWindowExW(exStyle, kPopupWindowClassName, L"PopupMenu", |
| WS_POPUP | WS_BORDER, |
| absoluteScreenCoords.x(), absoluteScreenCoords.y(), absoluteScreenCoords.width(), absoluteScreenCoords.height(), |
| hostWindow, 0, WebCore::instanceHandle(), this); |
| |
| if (!m_popup) |
| return; |
| } else |
| ::MoveWindow(m_popup, absoluteScreenCoords.x(), absoluteScreenCoords.y(), absoluteScreenCoords.width(), absoluteScreenCoords.height(), false); |
| |
| IntRect screen = monitorFromHwnd(m_popup); |
| |
| // Now we determine the actual location and measurements of the popup itself. |
| // r is in absolute document coordinates, but we want to be in screen coordinates. |
| |
| // First, move to WebView coordinates |
| IntRect rScreenCoords(v->contentsToWindow(r.location()), r.size()); |
| if (Page* page = v->frame().page()) |
| rScreenCoords.scale(page->deviceScaleFactor()); |
| |
| // Then, translate to screen coordinates |
| POINT location(rScreenCoords.location()); |
| if (!::ClientToScreen(hostWindow, &location)) |
| return; |
| |
| rScreenCoords.setLocation(location); |
| |
| m_font = client()->menuStyle().font(); |
| auto d = m_font.fontDescription(); |
| d.setComputedSize(d.computedSize() * m_scaleFactor); |
| m_font = FontCascade(WTFMove(d), m_font.letterSpacing(), m_font.wordSpacing()); |
| m_font.update(m_popupClient->fontSelector()); |
| |
| // First, determine the popup's height |
| int itemCount = client()->listSize(); |
| m_itemHeight = m_font.metricsOfPrimaryFont().height() + optionSpacingMiddle; |
| |
| int naturalHeight = m_itemHeight * itemCount; |
| int popupHeight = std::min(maxPopupHeight, naturalHeight); |
| // The popup should show an integral number of items (i.e. no partial items should be visible) |
| popupHeight -= popupHeight % m_itemHeight; |
| |
| // Next determine its width |
| int popupWidth = 0; |
| for (int i = 0; i < itemCount; ++i) { |
| String text = client()->itemText(i); |
| if (text.isEmpty()) |
| continue; |
| |
| FontCascade itemFont = m_font; |
| if (client()->itemIsLabel(i)) { |
| auto d = itemFont.fontDescription(); |
| d.setWeight(d.bolderWeight()); |
| itemFont = FontCascade(WTFMove(d), itemFont.letterSpacing(), itemFont.wordSpacing()); |
| itemFont.update(m_popupClient->fontSelector()); |
| } |
| |
| popupWidth = std::max(popupWidth, static_cast<int>(ceilf(itemFont.width(TextRun(text))))); |
| } |
| |
| if (naturalHeight > maxPopupHeight) |
| // We need room for a scrollbar |
| popupWidth += ScrollbarTheme::theme().scrollbarThickness(ScrollbarControlSize::Small); |
| |
| // Add padding to align the popup text with the <select> text |
| popupWidth += std::max<int>(0, client()->clientPaddingRight() - client()->clientInsetRight()) + std::max<int>(0, client()->clientPaddingLeft() - client()->clientInsetLeft()); |
| |
| // Leave room for the border |
| popupWidth += 2 * popupWindowBorderWidth; |
| popupHeight += 2 * popupWindowBorderWidth; |
| |
| // The popup should be at least as wide as the control on the page |
| popupWidth = std::max(rScreenCoords.width() - client()->clientInsetLeft() - client()->clientInsetRight(), popupWidth); |
| |
| // Always left-align items in the popup. This matches popup menus on the mac. |
| int popupX = rScreenCoords.x() + client()->clientInsetLeft(); |
| |
| IntRect popupRect(popupX, rScreenCoords.maxY(), popupWidth, popupHeight); |
| |
| // Check that we don't go off the screen vertically |
| if (popupRect.maxY() > screen.height()) { |
| // The popup will go off the screen, so try placing it above the client |
| if (rScreenCoords.y() - popupRect.height() < 0) { |
| // The popup won't fit above, either, so place it whereever's bigger and resize it to fit |
| if ((rScreenCoords.y() + rScreenCoords.height() / 2) < (screen.height() / 2)) { |
| // Below is bigger |
| popupRect.setHeight(screen.height() - popupRect.y()); |
| } else { |
| // Above is bigger |
| popupRect.setY(0); |
| popupRect.setHeight(rScreenCoords.y()); |
| } |
| } else { |
| // The popup fits above, so reposition it |
| popupRect.setY(rScreenCoords.y() - popupRect.height()); |
| } |
| } |
| |
| // Check that we don't go off the screen horizontally |
| if (popupRect.x() + popupRect.width() > screen.width() + screen.x()) |
| popupRect.setX(screen.x() + screen.width() - popupRect.width()); |
| if (popupRect.x() < screen.x()) |
| popupRect.setX(screen.x()); |
| |
| m_windowRect = popupRect; |
| return; |
| } |
| |
| bool PopupMenuWin::setFocusedIndex(int i, bool hotTracking) |
| { |
| if (i < 0 || i >= client()->listSize() || i == focusedIndex()) |
| return false; |
| |
| if (!client()->itemIsEnabled(i)) |
| return false; |
| |
| invalidateItem(focusedIndex()); |
| invalidateItem(i); |
| |
| m_focusedIndex = i; |
| |
| if (!hotTracking) |
| client()->setTextFromItem(i); |
| |
| if (!scrollToRevealSelection()) |
| ::UpdateWindow(m_popup); |
| |
| return true; |
| } |
| |
| int PopupMenuWin::visibleItems() const |
| { |
| return clientRect().height() / m_itemHeight; |
| } |
| |
| int PopupMenuWin::listIndexAtPoint(const IntPoint& point) const |
| { |
| return m_scrollOffset + point.y() / m_itemHeight; |
| } |
| |
| int PopupMenuWin::focusedIndex() const |
| { |
| return m_focusedIndex; |
| } |
| |
| void PopupMenuWin::focusFirst() |
| { |
| if (!client()) |
| return; |
| |
| int size = client()->listSize(); |
| |
| for (int i = 0; i < size; ++i) |
| if (client()->itemIsEnabled(i)) { |
| setFocusedIndex(i); |
| break; |
| } |
| } |
| |
| void PopupMenuWin::focusLast() |
| { |
| if (!client()) |
| return; |
| |
| int size = client()->listSize(); |
| |
| for (int i = size - 1; i > 0; --i) |
| if (client()->itemIsEnabled(i)) { |
| setFocusedIndex(i); |
| break; |
| } |
| } |
| |
| bool PopupMenuWin::down(unsigned lines) |
| { |
| if (!client()) |
| return false; |
| |
| int size = client()->listSize(); |
| |
| int lastSelectableIndex, selectedListIndex; |
| lastSelectableIndex = selectedListIndex = focusedIndex(); |
| for (int i = selectedListIndex + 1; i >= 0 && i < size; ++i) |
| if (client()->itemIsEnabled(i)) { |
| lastSelectableIndex = i; |
| if (i >= selectedListIndex + (int)lines) |
| break; |
| } |
| |
| return setFocusedIndex(lastSelectableIndex); |
| } |
| |
| bool PopupMenuWin::up(unsigned lines) |
| { |
| if (!client()) |
| return false; |
| |
| int size = client()->listSize(); |
| |
| int lastSelectableIndex, selectedListIndex; |
| lastSelectableIndex = selectedListIndex = focusedIndex(); |
| for (int i = selectedListIndex - 1; i >= 0 && i < size; --i) |
| if (client()->itemIsEnabled(i)) { |
| lastSelectableIndex = i; |
| if (i <= selectedListIndex - (int)lines) |
| break; |
| } |
| |
| return setFocusedIndex(lastSelectableIndex); |
| } |
| |
| void PopupMenuWin::invalidateItem(int index) |
| { |
| if (!m_popup) |
| return; |
| |
| IntRect damageRect(clientRect()); |
| damageRect.setY(m_itemHeight * (index - m_scrollOffset)); |
| damageRect.setHeight(m_itemHeight); |
| if (m_scrollbar) |
| damageRect.setWidth(damageRect.width() - m_scrollbar->frameRect().width()); |
| |
| RECT r = damageRect; |
| ::InvalidateRect(m_popup, &r, TRUE); |
| } |
| |
| IntRect PopupMenuWin::clientRect() const |
| { |
| IntRect clientRect = m_windowRect; |
| clientRect.inflate(-popupWindowBorderWidth); |
| clientRect.setLocation(IntPoint(0, 0)); |
| return clientRect; |
| } |
| |
| void PopupMenuWin::incrementWheelDelta(int delta) |
| { |
| m_wheelDelta += delta; |
| } |
| |
| void PopupMenuWin::reduceWheelDelta(int delta) |
| { |
| ASSERT(delta >= 0); |
| ASSERT(delta <= abs(m_wheelDelta)); |
| |
| if (m_wheelDelta > 0) |
| m_wheelDelta -= delta; |
| else if (m_wheelDelta < 0) |
| m_wheelDelta += delta; |
| else |
| return; |
| } |
| |
| bool PopupMenuWin::scrollToRevealSelection() |
| { |
| if (!m_scrollbar) |
| return false; |
| |
| int index = focusedIndex(); |
| |
| if (index < m_scrollOffset) { |
| ScrollableArea::scrollToOffsetWithoutAnimation(ScrollbarOrientation::Vertical, index); |
| return true; |
| } |
| |
| if (index >= m_scrollOffset + visibleItems()) { |
| ScrollableArea::scrollToOffsetWithoutAnimation(ScrollbarOrientation::Vertical, index - visibleItems() + 1); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void PopupMenuWin::updateFromElement() |
| { |
| if (!m_popup) |
| return; |
| |
| m_focusedIndex = client()->selectedIndex(); |
| |
| ::InvalidateRect(m_popup, 0, TRUE); |
| scrollToRevealSelection(); |
| } |
| |
| const int separatorPadding = 4; |
| const int separatorHeight = 1; |
| void PopupMenuWin::paint(const IntRect& damageRect, HDC hdc) |
| { |
| if (!m_popup) |
| return; |
| |
| if (!m_DC) { |
| m_DC = adoptGDIObject(::CreateCompatibleDC(HWndDC(m_popup))); |
| if (!m_DC) |
| return; |
| } |
| |
| if (m_bmp) { |
| bool keepBitmap = false; |
| BITMAP bitmap; |
| if (::GetObject(m_bmp.get(), sizeof(bitmap), &bitmap)) |
| keepBitmap = bitmap.bmWidth == clientRect().width() |
| && bitmap.bmHeight == clientRect().height(); |
| if (!keepBitmap) |
| m_bmp.clear(); |
| } |
| if (!m_bmp) { |
| BitmapInfo bitmapInfo = BitmapInfo::createBottomUp(clientRect().size()); |
| void* pixels = 0; |
| m_bmp = adoptGDIObject(::CreateDIBSection(m_DC.get(), &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0)); |
| if (!m_bmp) |
| return; |
| |
| ::SelectObject(m_DC.get(), m_bmp.get()); |
| } |
| |
| GraphicsContextWin context(m_DC.get()); |
| |
| // listRect is the damageRect translated into the coordinates of the entire menu list (which is listSize * m_itemHeight pixels tall) |
| IntRect listRect = damageRect; |
| listRect.move(IntSize(0, m_scrollOffset * m_itemHeight)); |
| |
| for (int y = listRect.y(); y < listRect.maxY(); y += m_itemHeight) { |
| int index = y / m_itemHeight; |
| |
| Color optionBackgroundColor, optionTextColor; |
| PopupMenuStyle itemStyle = client()->itemStyle(index); |
| if (index == focusedIndex()) { |
| optionBackgroundColor = RenderTheme::singleton().activeListBoxSelectionBackgroundColor({ }); |
| optionTextColor = RenderTheme::singleton().activeListBoxSelectionForegroundColor({ }); |
| } else { |
| optionBackgroundColor = itemStyle.backgroundColor(); |
| optionTextColor = itemStyle.foregroundColor(); |
| } |
| |
| // itemRect is in client coordinates |
| IntRect itemRect(0, (index - m_scrollOffset) * m_itemHeight, damageRect.width(), m_itemHeight); |
| |
| // Draw the background for this menu item |
| if (itemStyle.isVisible()) |
| context.fillRect(itemRect, optionBackgroundColor); |
| |
| if (client()->itemIsSeparator(index)) { |
| IntRect separatorRect(itemRect.x() + separatorPadding, itemRect.y() + (itemRect.height() - separatorHeight) / 2, itemRect.width() - 2 * separatorPadding, separatorHeight); |
| context.fillRect(separatorRect, optionTextColor); |
| continue; |
| } |
| |
| String itemText = client()->itemText(index); |
| |
| TextRun textRun(itemText, 0, 0, ExpansionBehavior::allowLeftOnly(), itemStyle.textDirection(), itemStyle.hasTextDirectionOverride()); |
| context.setFillColor(optionTextColor); |
| |
| FontCascade itemFont = m_font; |
| if (client()->itemIsLabel(index)) { |
| auto d = itemFont.fontDescription(); |
| d.setWeight(d.bolderWeight()); |
| itemFont = FontCascade(WTFMove(d), itemFont.letterSpacing(), itemFont.wordSpacing()); |
| itemFont.update(m_popupClient->fontSelector()); |
| } |
| |
| // Draw the item text |
| if (itemStyle.isVisible()) { |
| int textX = 0; |
| if (client()->menuStyle().textDirection() == TextDirection::LTR) { |
| textX = std::max<int>(0, client()->clientPaddingLeft() - client()->clientInsetLeft()); |
| if (RenderTheme::singleton().popupOptionSupportsTextIndent()) |
| textX += minimumIntValueForLength(itemStyle.textIndent(), itemRect.width()); |
| } else { |
| textX = itemRect.width() - client()->menuStyle().font().width(textRun); |
| textX = std::min<int>(textX, textX - client()->clientPaddingRight() + client()->clientInsetRight()); |
| if (RenderTheme::singleton().popupOptionSupportsTextIndent()) |
| textX -= minimumIntValueForLength(itemStyle.textIndent(), itemRect.width()); |
| } |
| int textY = itemRect.y() + itemFont.metricsOfPrimaryFont().ascent() + (itemRect.height() - itemFont.metricsOfPrimaryFont().height()) / 2; |
| context.drawBidiText(itemFont, textRun, IntPoint(textX, textY)); |
| } |
| } |
| |
| if (m_scrollbar) |
| m_scrollbar->paint(context, damageRect); |
| |
| HWndDC hWndDC; |
| HDC localDC = hdc ? hdc : hWndDC.setHWnd(m_popup); |
| |
| ::BitBlt(localDC, damageRect.x(), damageRect.y(), damageRect.width(), damageRect.height(), m_DC.get(), damageRect.x(), damageRect.y(), SRCCOPY); |
| } |
| |
| ScrollPosition PopupMenuWin::scrollPosition() const |
| { |
| return { 0, m_scrollOffset }; |
| } |
| |
| void PopupMenuWin::setScrollOffset(const IntPoint& offset) |
| { |
| scrollTo(offset.y()); |
| } |
| |
| void PopupMenuWin::scrollTo(int offset) |
| { |
| ASSERT(m_scrollbar); |
| |
| if (!m_popup) |
| return; |
| |
| if (m_scrollOffset == offset) |
| return; |
| |
| int scrolledLines = m_scrollOffset - offset; |
| m_scrollOffset = offset; |
| |
| UINT flags = SW_INVALIDATE; |
| |
| #ifdef CAN_SET_SMOOTH_SCROLLING_DURATION |
| BOOL shouldSmoothScroll = FALSE; |
| ::SystemParametersInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, &shouldSmoothScroll, 0); |
| if (shouldSmoothScroll) |
| flags |= MAKEWORD(SW_SMOOTHSCROLL, smoothScrollAnimationDuration); |
| #endif |
| |
| IntRect listRect = clientRect(); |
| if (m_scrollbar) |
| listRect.setWidth(listRect.width() - m_scrollbar->frameRect().width()); |
| RECT r = listRect; |
| ::ScrollWindowEx(m_popup, 0, scrolledLines * m_itemHeight, &r, 0, 0, 0, flags); |
| if (m_scrollbar) { |
| r = m_scrollbar->frameRect(); |
| ::InvalidateRect(m_popup, &r, TRUE); |
| } |
| ::UpdateWindow(m_popup); |
| } |
| |
| void PopupMenuWin::invalidateScrollbarRect(Scrollbar& scrollbar, const IntRect& rect) |
| { |
| IntRect scrollRect = rect; |
| scrollRect.move(scrollbar.x(), scrollbar.y()); |
| RECT r = scrollRect; |
| ::InvalidateRect(m_popup, &r, false); |
| } |
| |
| IntSize PopupMenuWin::visibleSize() const |
| { |
| return IntSize(m_windowRect.width(), m_scrollbar ? m_scrollbar->visibleSize() : m_windowRect.height()); |
| } |
| |
| IntSize PopupMenuWin::contentsSize() const |
| { |
| return IntSize(m_windowRect.width(), m_scrollbar ? m_scrollbar->totalSize() : m_windowRect.height()); |
| } |
| |
| IntRect PopupMenuWin::scrollableAreaBoundingBox(bool*) const |
| { |
| return m_windowRect; |
| } |
| |
| bool PopupMenuWin::onGetObject(WPARAM wParam, LPARAM lParam, LRESULT& lResult) |
| { |
| lResult = 0; |
| |
| if (static_cast<LONG>(lParam) != OBJID_CLIENT) |
| return false; |
| |
| if (!m_accessiblePopupMenu) |
| m_accessiblePopupMenu = new AccessiblePopupMenu(*this); |
| |
| static HMODULE accessibilityLib = nullptr; |
| if (!accessibilityLib) { |
| if (!(accessibilityLib = ::LoadLibraryW(L"oleacc.dll"))) |
| return false; |
| } |
| |
| static LPFNLRESULTFROMOBJECT procPtr = reinterpret_cast<LPFNLRESULTFROMOBJECT>(::GetProcAddress(accessibilityLib, "LresultFromObject")); |
| if (!procPtr) |
| return false; |
| |
| // LresultFromObject returns a reference to the accessible object, stored |
| // in an LRESULT. If this call is not successful, Windows will handle the |
| // request through DefWindowProc. |
| return SUCCEEDED(lResult = procPtr(__uuidof(IAccessible), wParam, m_accessiblePopupMenu.get())); |
| } |
| |
| void PopupMenuWin::registerClass() |
| { |
| static bool haveRegisteredWindowClass = false; |
| |
| if (haveRegisteredWindowClass) |
| return; |
| |
| WNDCLASSEX wcex; |
| wcex.cbSize = sizeof(WNDCLASSEX); |
| wcex.hIconSm = 0; |
| wcex.style = CS_DROPSHADOW; |
| |
| wcex.lpfnWndProc = PopupMenuWndProc; |
| wcex.cbClsExtra = 0; |
| wcex.cbWndExtra = sizeof(PopupMenu*); // For the PopupMenu pointer |
| wcex.hInstance = WebCore::instanceHandle(); |
| wcex.hIcon = 0; |
| wcex.hCursor = LoadCursor(0, IDC_ARROW); |
| wcex.hbrBackground = 0; |
| wcex.lpszMenuName = 0; |
| wcex.lpszClassName = kPopupWindowClassName; |
| |
| haveRegisteredWindowClass = true; |
| |
| RegisterClassEx(&wcex); |
| } |
| |
| |
| LRESULT CALLBACK PopupMenuWin::PopupMenuWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) |
| { |
| if (PopupMenuWin* popup = static_cast<PopupMenuWin*>(getWindowPointer(hWnd, 0))) |
| return popup->wndProc(hWnd, message, wParam, lParam); |
| |
| if (message == WM_CREATE) { |
| LPCREATESTRUCT createStruct = reinterpret_cast<LPCREATESTRUCT>(lParam); |
| |
| // Associate the PopupMenu with the window. |
| setWindowPointer(hWnd, 0, createStruct->lpCreateParams); |
| return 0; |
| } |
| |
| return ::DefWindowProc(hWnd, message, wParam, lParam); |
| } |
| |
| LRESULT PopupMenuWin::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) |
| { |
| LRESULT lResult = 0; |
| |
| switch (message) { |
| case WM_MOUSEACTIVATE: |
| return MA_NOACTIVATE; |
| case WM_SIZE: { |
| if (!scrollbar()) |
| break; |
| |
| IntSize size(LOWORD(lParam), HIWORD(lParam)); |
| scrollbar()->setFrameRect(IntRect(size.width() - scrollbar()->width(), 0, scrollbar()->width(), size.height())); |
| |
| int visibleItems = this->visibleItems(); |
| scrollbar()->setEnabled(visibleItems < client()->listSize()); |
| scrollbar()->setSteps(1, std::max(1, visibleItems - 1)); |
| scrollbar()->setProportion(visibleItems, client()->listSize()); |
| |
| break; |
| } |
| case WM_SYSKEYDOWN: |
| case WM_KEYDOWN: { |
| if (!client()) |
| break; |
| |
| bool altKeyPressed = GetKeyState(VK_MENU) & HIGH_BIT_MASK_SHORT; |
| bool ctrlKeyPressed = GetKeyState(VK_CONTROL) & HIGH_BIT_MASK_SHORT; |
| |
| lResult = 0; |
| switch (LOWORD(wParam)) { |
| case VK_F4: { |
| if (!altKeyPressed && !ctrlKeyPressed) { |
| int index = focusedIndex(); |
| ASSERT(index >= 0); |
| client()->valueChanged(index); |
| hide(); |
| } |
| break; |
| } |
| case VK_DOWN: |
| if (altKeyPressed) { |
| int index = focusedIndex(); |
| ASSERT(index >= 0); |
| client()->valueChanged(index); |
| hide(); |
| } else |
| down(); |
| break; |
| case VK_RIGHT: |
| down(); |
| break; |
| case VK_UP: |
| if (altKeyPressed) { |
| int index = focusedIndex(); |
| ASSERT(index >= 0); |
| client()->valueChanged(index); |
| hide(); |
| } else |
| up(); |
| break; |
| case VK_LEFT: |
| up(); |
| break; |
| case VK_HOME: |
| focusFirst(); |
| break; |
| case VK_END: |
| focusLast(); |
| break; |
| case VK_PRIOR: |
| if (focusedIndex() != m_scrollOffset) { |
| // Set the selection to the first visible item |
| int firstVisibleItem = m_scrollOffset; |
| up(focusedIndex() - firstVisibleItem); |
| } else { |
| // The first visible item is selected, so move the selection back one page |
| up(visibleItems()); |
| } |
| break; |
| case VK_NEXT: { |
| int lastVisibleItem = m_scrollOffset + visibleItems() - 1; |
| if (focusedIndex() != lastVisibleItem) { |
| // Set the selection to the last visible item |
| down(lastVisibleItem - focusedIndex()); |
| } else { |
| // The last visible item is selected, so move the selection forward one page |
| down(visibleItems()); |
| } |
| break; |
| } |
| case VK_TAB: |
| ::SendMessage(client()->hostWindow()->platformPageClient(), message, wParam, lParam); |
| hide(); |
| break; |
| case VK_ESCAPE: |
| hide(); |
| break; |
| default: |
| if (isASCIIPrintable(wParam)) |
| // Send the keydown to the WebView so it can be used for type-to-select. |
| // Since we know that the virtual key is ASCII printable, it's OK to convert this to |
| // a WM_CHAR message. (We don't want to call TranslateMessage because that will post a |
| // WM_CHAR message that will be stolen and redirected to the popup HWND. |
| ::PostMessage(m_popup, WM_HOST_WINDOW_CHAR, wParam, lParam); |
| else |
| lResult = 1; |
| break; |
| } |
| break; |
| } |
| case WM_CHAR: { |
| if (!client()) |
| break; |
| |
| lResult = 0; |
| int index; |
| switch (wParam) { |
| case 0x0D: // Enter/Return |
| hide(); |
| index = focusedIndex(); |
| ASSERT(index >= 0); |
| client()->valueChanged(index); |
| break; |
| case 0x1B: // Escape |
| hide(); |
| break; |
| case 0x09: // TAB |
| case 0x08: // Backspace |
| case 0x0A: // Linefeed |
| default: // Character |
| lResult = 1; |
| break; |
| } |
| break; |
| } |
| case WM_MOUSEMOVE: { |
| IntPoint mousePoint(MAKEPOINTS(lParam)); |
| if (scrollbar()) { |
| IntRect scrollBarRect = scrollbar()->frameRect(); |
| if (scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) { |
| // Put the point into coordinates relative to the scroll bar |
| mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y()); |
| PlatformMouseEvent event(hWnd, message, wParam, makeScaledPoint(mousePoint, m_scaleFactor)); |
| scrollbar()->mouseMoved(event); |
| break; |
| } |
| } |
| |
| BOOL shouldHotTrack = FALSE; |
| if (!::SystemParametersInfo(SPI_GETHOTTRACKING, 0, &shouldHotTrack, 0)) |
| shouldHotTrack = FALSE; |
| |
| RECT bounds; |
| GetClientRect(popupHandle(), &bounds); |
| if (!::PtInRect(&bounds, mousePoint) && !(wParam & MK_LBUTTON) && client()) { |
| // When the mouse is not inside the popup menu and the left button isn't down, just |
| // repost the message to the web view. |
| |
| // Translate the coordinate. |
| translatePoint(lParam, m_popup, client()->hostWindow()->platformPageClient()); |
| |
| ::PostMessage(m_popup, WM_HOST_WINDOW_MOUSEMOVE, wParam, lParam); |
| break; |
| } |
| |
| if ((shouldHotTrack || wParam & MK_LBUTTON) && ::PtInRect(&bounds, mousePoint)) { |
| setFocusedIndex(listIndexAtPoint(mousePoint), true); |
| m_hoveredIndex = listIndexAtPoint(mousePoint); |
| } |
| |
| break; |
| } |
| case WM_LBUTTONDOWN: { |
| IntPoint mousePoint(MAKEPOINTS(lParam)); |
| if (scrollbar()) { |
| IntRect scrollBarRect = scrollbar()->frameRect(); |
| if (scrollBarRect.contains(mousePoint)) { |
| // Put the point into coordinates relative to the scroll bar |
| mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y()); |
| PlatformMouseEvent event(hWnd, message, wParam, makeScaledPoint(mousePoint, m_scaleFactor)); |
| scrollbar()->mouseDown(event); |
| setScrollbarCapturingMouse(true); |
| break; |
| } |
| } |
| |
| // If the mouse is inside the window, update the focused index. Otherwise, |
| // hide the popup. |
| RECT bounds; |
| GetClientRect(m_popup, &bounds); |
| if (::PtInRect(&bounds, mousePoint)) { |
| setFocusedIndex(listIndexAtPoint(mousePoint), true); |
| m_hoveredIndex = listIndexAtPoint(mousePoint); |
| } |
| else |
| hide(); |
| break; |
| } |
| case WM_LBUTTONUP: { |
| IntPoint mousePoint(MAKEPOINTS(lParam)); |
| if (scrollbar()) { |
| IntRect scrollBarRect = scrollbar()->frameRect(); |
| if (scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) { |
| setScrollbarCapturingMouse(false); |
| // Put the point into coordinates relative to the scroll bar |
| mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y()); |
| PlatformMouseEvent event(hWnd, message, wParam, makeScaledPoint(mousePoint, m_scaleFactor)); |
| scrollbar()->mouseUp(event); |
| // FIXME: This is a hack to work around Scrollbar not invalidating correctly when it doesn't have a parent widget |
| RECT r = scrollBarRect; |
| ::InvalidateRect(popupHandle(), &r, TRUE); |
| break; |
| } |
| } |
| // Only hide the popup if the mouse is inside the popup window. |
| RECT bounds; |
| GetClientRect(popupHandle(), &bounds); |
| if (client() && ::PtInRect(&bounds, mousePoint)) { |
| hide(); |
| int index = m_hoveredIndex; |
| if (!client()->itemIsEnabled(index)) |
| index = client()->selectedIndex(); |
| if (index >= 0) |
| client()->valueChanged(index); |
| } |
| break; |
| } |
| |
| case WM_MOUSEWHEEL: { |
| if (!scrollbar()) |
| break; |
| |
| int i = 0; |
| for (incrementWheelDelta(GET_WHEEL_DELTA_WPARAM(wParam)); abs(wheelDelta()) >= WHEEL_DELTA; reduceWheelDelta(WHEEL_DELTA)) { |
| if (wheelDelta() > 0) |
| ++i; |
| else |
| --i; |
| } |
| |
| ScrollableArea::scroll(i > 0 ? ScrollUp : ScrollDown, ScrollGranularity::Line, abs(i)); |
| break; |
| } |
| |
| case WM_PAINT: { |
| PAINTSTRUCT paintInfo; |
| ::BeginPaint(popupHandle(), &paintInfo); |
| paint(paintInfo.rcPaint, paintInfo.hdc); |
| ::EndPaint(popupHandle(), &paintInfo); |
| lResult = 0; |
| break; |
| } |
| case WM_PRINTCLIENT: |
| paint(clientRect(), (HDC)wParam); |
| break; |
| case WM_GETOBJECT: |
| onGetObject(wParam, lParam, lResult); |
| break; |
| default: |
| lResult = DefWindowProc(hWnd, message, wParam, lParam); |
| } |
| |
| return lResult; |
| } |
| |
| String PopupMenuWin::debugDescription() const |
| { |
| return makeString("PopupMenuWin 0x", hex(reinterpret_cast<uintptr_t>(this), Lowercase)); |
| } |
| |
| AccessiblePopupMenu::AccessiblePopupMenu(const PopupMenuWin& popupMenu) |
| : m_popupMenu(popupMenu) |
| { |
| } |
| |
| AccessiblePopupMenu::~AccessiblePopupMenu() = default; |
| |
| HRESULT AccessiblePopupMenu::QueryInterface(_In_ REFIID riid, _COM_Outptr_ void** ppvObject) |
| { |
| if (!ppvObject) |
| return E_POINTER; |
| if (IsEqualGUID(riid, __uuidof(IAccessible))) |
| *ppvObject = static_cast<IAccessible*>(this); |
| else if (IsEqualGUID(riid, __uuidof(IDispatch))) |
| *ppvObject = static_cast<IAccessible*>(this); |
| else if (IsEqualGUID(riid, __uuidof(IUnknown))) |
| *ppvObject = static_cast<IAccessible*>(this); |
| else { |
| *ppvObject = nullptr; |
| return E_NOINTERFACE; |
| } |
| AddRef(); |
| return S_OK; |
| } |
| |
| ULONG AccessiblePopupMenu::AddRef() |
| { |
| return ++m_refCount; |
| } |
| |
| ULONG AccessiblePopupMenu::Release() |
| { |
| int refCount = --m_refCount; |
| if (!refCount) |
| delete this; |
| return refCount; |
| } |
| |
| HRESULT AccessiblePopupMenu::GetTypeInfoCount(_Out_ UINT* count) |
| { |
| if (!count) |
| return E_POINTER; |
| *count = 0; |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::GetTypeInfo(UINT, LCID, _COM_Outptr_opt_ ITypeInfo** ppTInfo) |
| { |
| if (!ppTInfo) |
| return E_POINTER; |
| *ppTInfo = nullptr; |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::GetIDsOfNames(_In_ REFIID, __in_ecount(cNames) LPOLESTR*, UINT cNames, LCID, __out_ecount_full(cNames) DISPID*) |
| { |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::Invoke(DISPID, REFIID, LCID, WORD, DISPPARAMS*, VARIANT*, EXCEPINFO*, UINT*) |
| { |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::get_accParent(_COM_Outptr_opt_ IDispatch** parent) |
| { |
| if (!parent) |
| return E_POINTER; |
| *parent = nullptr; |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::get_accChildCount(_Out_ long* count) |
| { |
| if (!count) |
| return E_POINTER; |
| |
| *count = m_popupMenu.visibleItems(); |
| return S_OK; |
| } |
| |
| HRESULT AccessiblePopupMenu::get_accChild(VARIANT vChild, _COM_Outptr_opt_ IDispatch** ppChild) |
| { |
| if (!ppChild) |
| return E_POINTER; |
| |
| *ppChild = nullptr; |
| |
| if (vChild.vt != VT_I4) |
| return E_INVALIDARG; |
| |
| notImplemented(); |
| return S_FALSE; |
| } |
| |
| HRESULT AccessiblePopupMenu::get_accName(VARIANT vChild, __deref_out_opt BSTR* name) |
| { |
| return get_accValue(vChild, name); |
| } |
| |
| HRESULT AccessiblePopupMenu::get_accValue(VARIANT vChild, __deref_out_opt BSTR* value) |
| { |
| if (!value) |
| return E_POINTER; |
| |
| *value = nullptr; |
| |
| if (vChild.vt != VT_I4) |
| return E_INVALIDARG; |
| |
| int index = vChild.lVal - 1; |
| |
| if (index < 0) |
| return E_INVALIDARG; |
| |
| BString itemText(m_popupMenu.client()->itemText(index)); |
| *value = itemText.release(); |
| |
| return S_OK; |
| } |
| |
| HRESULT AccessiblePopupMenu::get_accDescription(VARIANT, __deref_out_opt BSTR*) |
| { |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::get_accRole(VARIANT vChild, _Out_ VARIANT* pvRole) |
| { |
| if (!pvRole) |
| return E_POINTER; |
| if (vChild.vt != VT_I4) |
| return E_INVALIDARG; |
| |
| // Scrollbar parts are encoded as negative values. |
| if (vChild.lVal < 0) { |
| V_VT(pvRole) = VT_I4; |
| V_I4(pvRole) = ROLE_SYSTEM_SCROLLBAR; |
| } else { |
| V_VT(pvRole) = VT_I4; |
| V_I4(pvRole) = ROLE_SYSTEM_LISTITEM; |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT AccessiblePopupMenu::get_accState(VARIANT vChild, _Out_ VARIANT* pvState) |
| { |
| if (!pvState) |
| return E_POINTER; |
| |
| if (vChild.vt != VT_I4) |
| return E_INVALIDARG; |
| |
| V_VT(pvState) = VT_I4; |
| V_I4(pvState) = 0; // STATE_SYSTEM_NORMAL |
| |
| return S_OK; |
| } |
| |
| HRESULT AccessiblePopupMenu::get_accHelp(VARIANT vChild, __deref_out_opt BSTR* helpText) |
| { |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::get_accKeyboardShortcut(VARIANT vChild, __deref_out_opt BSTR*) |
| { |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::get_accFocus(_Out_ VARIANT* pvFocusedChild) |
| { |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::get_accSelection(_Out_ VARIANT* pvSelectedChild) |
| { |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::get_accDefaultAction(VARIANT vChild, __deref_out_opt BSTR* actionDescription) |
| { |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::accSelect(long selectionFlags, VARIANT vChild) |
| { |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::accLocation(_Out_ long* left, _Out_ long* top, _Out_ long* width, _Out_ long* height, VARIANT vChild) |
| { |
| if (!left || !top || !width || !height) |
| return E_POINTER; |
| |
| if (vChild.vt != VT_I4) |
| return E_INVALIDARG; |
| |
| const IntRect& windowRect = m_popupMenu.windowRect(); |
| |
| // Scrollbar parts are encoded as negative values. |
| if (vChild.lVal < 0) { |
| if (!m_popupMenu.scrollbar()) |
| return E_FAIL; |
| |
| Scrollbar& scrollbar = *m_popupMenu.scrollbar(); |
| WebCore::ScrollbarPart part = static_cast<WebCore::ScrollbarPart>(-vChild.lVal); |
| |
| ScrollbarThemeWin& theme = static_cast<ScrollbarThemeWin&>(scrollbar.theme()); |
| |
| IntRect partRect; |
| |
| switch (part) { |
| case BackTrackPart: |
| case BackButtonStartPart: |
| partRect = theme.backButtonRect(scrollbar, WebCore::BackTrackPart); |
| break; |
| case ThumbPart: |
| partRect = theme.thumbRect(scrollbar); |
| break; |
| case ForwardTrackPart: |
| case ForwardButtonEndPart: |
| partRect = theme.forwardButtonRect(scrollbar, WebCore::ForwardTrackPart); |
| break; |
| case ScrollbarBGPart: |
| partRect = theme.trackRect(scrollbar); |
| break; |
| default: |
| return E_FAIL; |
| } |
| |
| partRect.move(windowRect.x(), windowRect.y()); |
| |
| *left = partRect.x(); |
| *top = partRect.y(); |
| *width = partRect.width(); |
| *height = partRect.height(); |
| |
| return S_OK; |
| } |
| |
| int index = vChild.lVal - 1; |
| |
| if (index < 0) |
| return E_INVALIDARG; |
| |
| *left = windowRect.x(); |
| *top = windowRect.y() + (index - m_popupMenu.m_scrollOffset) * m_popupMenu.itemHeight(); |
| *width = windowRect.width(); |
| *height = m_popupMenu.itemHeight(); |
| |
| return S_OK; |
| } |
| |
| HRESULT AccessiblePopupMenu::accNavigate(long direction, VARIANT vFromChild, _Out_ VARIANT* pvNavigatedTo) |
| { |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::accHitTest(long x, long y, _Out_ VARIANT* pvChildAtPoint) |
| { |
| if (!pvChildAtPoint) |
| return E_POINTER; |
| |
| ::VariantInit(pvChildAtPoint); |
| |
| IntRect windowRect = m_popupMenu.windowRect(); |
| |
| IntPoint pt(x - windowRect.x(), y - windowRect.y()); |
| |
| IntRect scrollRect; |
| |
| if (m_popupMenu.scrollbar()) |
| scrollRect = m_popupMenu.scrollbar()->frameRect(); |
| |
| if (m_popupMenu.scrollbar() && scrollRect.contains(pt)) { |
| Scrollbar& scrollbar = *m_popupMenu.scrollbar(); |
| |
| pt.move(-scrollRect.x(), -scrollRect.y()); |
| |
| WebCore::ScrollbarPart part = scrollbar.theme().hitTest(scrollbar, pt); |
| |
| V_VT(pvChildAtPoint) = VT_I4; |
| V_I4(pvChildAtPoint) = -part; // Scrollbar parts are encoded as negative, to avoid mixup with item indexes. |
| return S_OK; |
| } |
| |
| int index = m_popupMenu.listIndexAtPoint(pt); |
| |
| if (index < 0) { |
| V_VT(pvChildAtPoint) = VT_EMPTY; |
| return S_OK; |
| } |
| |
| V_VT(pvChildAtPoint) = VT_I4; |
| V_I4(pvChildAtPoint) = index + 1; // CHILDID_SELF is 0, need to add 1. |
| |
| return S_OK; |
| } |
| |
| HRESULT AccessiblePopupMenu::accDoDefaultAction(VARIANT vChild) |
| { |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::put_accName(VARIANT, BSTR) |
| { |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::put_accValue(VARIANT, BSTR) |
| { |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| HRESULT AccessiblePopupMenu::get_accHelpTopic(BSTR* helpFile, VARIANT, _Out_ long* topicID) |
| { |
| notImplemented(); |
| return E_NOTIMPL; |
| } |
| |
| } |