1430 lines
44 KiB
C++
1430 lines
44 KiB
C++
/*
|
|
* 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 FloatRect 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, VerticalScrollbar, 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);
|
|
|
|
FloatRect 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.fontMetrics().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(VerticalScrollbar, index);
|
|
return true;
|
|
}
|
|
|
|
if (index >= m_scrollOffset + visibleItems()) {
|
|
ScrollableArea::scrollToOffsetWithoutAnimation(VerticalScrollbar, 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, AllowLeftExpansion, 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.fontMetrics().ascent() + (itemRect.height() - itemFont.fontMetrics().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, ScrollByLine, 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;
|
|
}
|
|
|
|
}
|