/* * Copyright (C) 2010 Apple 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: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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. */ // NOTE: This implementation is very similar to the implementation of popups in WebCore::PopupMenuWin. // We should try and factor out the common bits and share them. #include "config.h" #include "WebPopupMenuProxyWin.h" #include "NativeWebMouseEvent.h" #include "WebView.h" #include <WebCore/WebCoreInstanceHandle.h> #include <WebCore/ScrollbarTheme.h> #include <WebCore/BitmapInfo.h> #include <WebCore/PlatformMouseEvent.h> #include <windowsx.h> using namespace WebCore; using namespace std; namespace WebKit { static const LPCWSTR kWebKit2WebPopupMenuProxyWindowClassName = L"WebKit2WebPopupMenuProxyWindowClass"; static const int defaultAnimationDuration = 200; static const int maxPopupHeight = 320; static const int popupWindowBorderWidth = 1; static const int separatorPadding = 4; static const int separatorHeight = 1; // 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; static inline bool isASCIIPrintable(unsigned c) { return c >= 0x20 && c <= 0x7E; } static void translatePoint(LPARAM& lParam, HWND from, HWND to) { POINT pt; pt.x = static_cast<short>(GET_X_LPARAM(lParam)); pt.y = static_cast<short>(GET_Y_LPARAM(lParam)); ::MapWindowPoints(from, to, &pt, 1); lParam = MAKELPARAM(pt.x, pt.y); } LRESULT CALLBACK WebPopupMenuProxyWin::WebPopupMenuProxyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { LONG_PTR longPtr = ::GetWindowLongPtr(hWnd, 0); if (WebPopupMenuProxyWin* popupMenuProxy = reinterpret_cast<WebPopupMenuProxyWin*>(longPtr)) return popupMenuProxy->wndProc(hWnd, message, wParam, lParam); if (message == WM_CREATE) { LPCREATESTRUCT createStruct = reinterpret_cast<LPCREATESTRUCT>(lParam); // Associate the WebView with the window. ::SetWindowLongPtr(hWnd, 0, (LONG_PTR)createStruct->lpCreateParams); return 0; } return ::DefWindowProc(hWnd, message, wParam, lParam); } LRESULT WebPopupMenuProxyWin::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { LRESULT lResult = 0; bool handled = true; switch (message) { case WM_MOUSEACTIVATE: lResult = onMouseActivate(hWnd, message, wParam, lParam, handled); break; case WM_SIZE: lResult = onSize(hWnd, message, wParam, lParam, handled); break; case WM_KEYDOWN: lResult = onKeyDown(hWnd, message, wParam, lParam, handled); break; case WM_CHAR: lResult = onChar(hWnd, message, wParam, lParam, handled); break; case WM_MOUSEMOVE: lResult = onMouseMove(hWnd, message, wParam, lParam, handled); break; case WM_LBUTTONDOWN: lResult = onLButtonDown(hWnd, message, wParam, lParam, handled); break; case WM_LBUTTONUP: lResult = onLButtonUp(hWnd, message, wParam, lParam, handled); break; case WM_MOUSEWHEEL: lResult = onMouseWheel(hWnd, message, wParam, lParam, handled); break; case WM_PAINT: lResult = onPaint(hWnd, message, wParam, lParam, handled); break; case WM_PRINTCLIENT: lResult = onPrintClient(hWnd, message, wParam, lParam, handled); break; default: handled = false; break; } if (!handled) lResult = ::DefWindowProc(hWnd, message, wParam, lParam); return lResult; } bool WebPopupMenuProxyWin::registerWindowClass() { static bool haveRegisteredWindowClass = false; if (haveRegisteredWindowClass) return true; haveRegisteredWindowClass = true; WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_DROPSHADOW; wcex.lpfnWndProc = WebPopupMenuProxyWin::WebPopupMenuProxyWndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = sizeof(WebPopupMenuProxyWin*); wcex.hInstance = instanceHandle(); wcex.hIcon = 0; wcex.hCursor = ::LoadCursor(0, IDC_ARROW); wcex.hbrBackground = 0; wcex.lpszMenuName = 0; wcex.lpszClassName = kWebKit2WebPopupMenuProxyWindowClassName; wcex.hIconSm = 0; return !!::RegisterClassEx(&wcex); } WebPopupMenuProxyWin::WebPopupMenuProxyWin(WebView* webView, WebPopupMenuProxy::Client* client) : WebPopupMenuProxy(client) , m_webView(webView) , m_newSelectedIndex(0) , m_popup(0) , m_DC(0) , m_bmp(0) , m_itemHeight(0) , m_scrollOffset(0) , m_wheelDelta(0) , m_focusedIndex(0) , m_wasClicked(false) , m_scrollbarCapturingMouse(false) , m_showPopup(false) { } WebPopupMenuProxyWin::~WebPopupMenuProxyWin() { if (m_bmp) ::DeleteObject(m_bmp); if (m_DC) ::DeleteDC(m_DC); if (m_popup) ::DestroyWindow(m_popup); if (m_scrollbar) m_scrollbar->setParent(0); } void WebPopupMenuProxyWin::showPopupMenu(const IntRect& rect, TextDirection, double, const Vector<WebPopupItem>& items, const PlatformPopupMenuData& data, int32_t selectedIndex) { m_items = items; m_data = data; m_newSelectedIndex = selectedIndex; calculatePositionAndSize(rect); if (clientRect().isEmpty()) return; HWND hostWindow = m_webView->window(); if (!m_scrollbar && visibleItems() < m_items.size()) { m_scrollbar = Scrollbar::createNativeScrollbar(this, VerticalScrollbar, SmallScrollbar); m_scrollbar->styleChanged(); } if (!m_popup) { registerWindowClass(); DWORD exStyle = WS_EX_LTRREADING; m_popup = ::CreateWindowEx(exStyle, kWebKit2WebPopupMenuProxyWindowClassName, TEXT("PopupMenu"), WS_POPUP | WS_BORDER, m_windowRect.x(), m_windowRect.y(), m_windowRect.width(), m_windowRect.height(), hostWindow, 0, instanceHandle(), this); if (!m_popup) return; } BOOL shouldAnimate = FALSE; ::SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, &shouldAnimate, 0); if (shouldAnimate) { RECT viewRect = {0}; ::GetWindowRect(hostWindow, &viewRect); if (!::IsRectEmpty(&viewRect)) { // Popups should slide into view away from the <select> box // NOTE: This may have to change for Vista DWORD slideDirection = (m_windowRect.y() < viewRect.top + rect.location().y()) ? AW_VER_NEGATIVE : AW_VER_POSITIVE; ::AnimateWindow(m_popup, defaultAnimationDuration, AW_SLIDE | slideDirection); } } else ::ShowWindow(m_popup, SW_SHOWNOACTIVATE); int index = selectedIndex; if (index >= 0) setFocusedIndex(index); m_showPopup = true; // Protect the popup menu in case its owner is destroyed while we're running the message pump. RefPtr<WebPopupMenuProxyWin> protect(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_SYSKEYUP: case WM_SYSCHAR: case WM_SYSDEADCHAR: msg.hwnd = m_popup; break; } ::TranslateMessage(&msg); ::DispatchMessage(&msg); if (!m_showPopup) break; activeWindow = ::GetActiveWindow(); if (activeWindow != hostWindow && !::IsChild(activeWindow, hostWindow)) break; if (::GetCapture() != hostWindow) break; } if (::GetCapture() == hostWindow) ::ReleaseCapture(); m_showPopup = false; ::ShowWindow(m_popup, SW_HIDE); if (!m_client) return; m_client->valueChangedForPopupMenu(this, m_newSelectedIndex); // <https://bugs.webkit.org/show_bug.cgi?id=57904> In order to properly call the onClick() // handler on a <select> element, we need to fake a mouse up event in the main window. // The main window already received the mouse down, which showed this popup, but upon // selection of an item the mouse up gets eaten by the popup menu. So we take the mouse down // event, change the message type to a mouse up event, and post that in the message queue. // Thus, we are virtually clicking at the // same location where the mouse down event occurred. This allows the hit test to select // the correct element, and thereby call the onClick() JS handler. if (!m_client->currentlyProcessedMouseDownEvent()) return; const MSG* initiatingWinEvent = m_client->currentlyProcessedMouseDownEvent()->nativeEvent(); MSG fakeEvent = *initiatingWinEvent; fakeEvent.message = WM_LBUTTONUP; ::PostMessage(fakeEvent.hwnd, fakeEvent.message, fakeEvent.wParam, fakeEvent.lParam); } void WebPopupMenuProxyWin::hidePopupMenu() { if (!m_showPopup) return; m_showPopup = false; ::ShowWindow(m_popup, SW_HIDE); // Post a WM_NULL message to wake up the message pump if necessary. ::PostMessage(m_popup, WM_NULL, 0, 0); } void WebPopupMenuProxyWin::calculatePositionAndSize(const IntRect& rect) { // Convert the rect (which is in view cooridates) into screen coordinates. IntRect rectInScreenCoords = rect; POINT location(rectInScreenCoords .location()); if (!::ClientToScreen(m_webView->window(), &location)) return; rectInScreenCoords.setLocation(location); int itemCount = m_items.size(); m_itemHeight = m_data.m_itemHeight; int naturalHeight = m_itemHeight * itemCount; int popupHeight = 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 = m_data.m_popupWidth; if (naturalHeight > maxPopupHeight) { // We need room for a scrollbar popupWidth += ScrollbarTheme::nativeTheme()->scrollbarThickness(SmallScrollbar); } popupHeight += 2 * popupWindowBorderWidth; // The popup should be at least as wide as the control on the page popupWidth = max(rectInScreenCoords.width() - m_data.m_clientInsetLeft - m_data.m_clientInsetRight, popupWidth); // Always left-align items in the popup. This matches popup menus on the mac. int popupX = rectInScreenCoords.x() + m_data.m_clientInsetLeft; IntRect popupRect(popupX, rectInScreenCoords.maxY(), popupWidth, popupHeight); // The popup needs to stay within the bounds of the screen and not overlap any toolbars HMONITOR monitor = ::MonitorFromWindow(m_webView->window(), MONITOR_DEFAULTTOPRIMARY); MONITORINFOEX monitorInfo; monitorInfo.cbSize = sizeof(MONITORINFOEX); ::GetMonitorInfo(monitor, &monitorInfo); FloatRect screen = monitorInfo.rcWork; // 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 (rectInScreenCoords.y() - popupRect.height() < 0) { // The popup won't fit above, either, so place it whereever's bigger and resize it to fit if ((rectInScreenCoords.y() + rectInScreenCoords.height() / 2) < (screen.height() / 2)) { // Below is bigger popupRect.setHeight(screen.height() - popupRect.y()); } else { // Above is bigger popupRect.setY(0); popupRect.setHeight(rectInScreenCoords.y()); } } else { // The popup fits above, so reposition it popupRect.setY(rectInScreenCoords.y() - popupRect.height()); } } // Check that we don't go off the screen horizontally if (popupRect.x() < screen.x()) { popupRect.setWidth(popupRect.width() - (screen.x() - popupRect.x())); popupRect.setX(screen.x()); } m_windowRect = popupRect; } IntRect WebPopupMenuProxyWin::clientRect() const { IntRect clientRect = m_windowRect; clientRect.inflate(-popupWindowBorderWidth); clientRect.setLocation(IntPoint(0, 0)); return clientRect; } void WebPopupMenuProxyWin::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); } int WebPopupMenuProxyWin::scrollSize(ScrollbarOrientation orientation) const { return ((orientation == VerticalScrollbar) && m_scrollbar) ? (m_scrollbar->totalSize() - m_scrollbar->visibleSize()) : 0; } int WebPopupMenuProxyWin::scrollPosition(Scrollbar*) const { return m_scrollOffset; } void WebPopupMenuProxyWin::setScrollOffset(const IntPoint& offset) { scrollTo(offset.y()); } void WebPopupMenuProxyWin::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 WebPopupMenuProxyWin::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect) { IntRect scrollRect = rect; scrollRect.move(scrollbar->x(), scrollbar->y()); RECT r = scrollRect; ::InvalidateRect(m_popup, &r, false); } // Message pump messages. LRESULT WebPopupMenuProxyWin::onMouseActivate(HWND hWnd, UINT message, WPARAM, LPARAM, bool& handled) { handled = true; return MA_NOACTIVATE; } LRESULT WebPopupMenuProxyWin::onSize(HWND hWnd, UINT message, WPARAM, LPARAM lParam, bool& handled) { handled = true; if (!scrollbar()) return 0; 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 < m_items.size()); scrollbar()->setSteps(1, max(1, visibleItems - 1)); scrollbar()->setProportion(visibleItems, m_items.size()); return 0; } LRESULT WebPopupMenuProxyWin::onKeyDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled) { handled = true; LRESULT lResult = 0; switch (LOWORD(wParam)) { case VK_DOWN: case VK_RIGHT: down(); break; case VK_UP: case VK_LEFT: up(); break; case VK_HOME: focusFirst(); break; case VK_END: focusLast(); break; case VK_PRIOR: if (focusedIndex() != scrollOffset()) { // Set the selection to the first visible item int firstVisibleItem = 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 = 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(m_webView->window(), 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; } return lResult; } LRESULT WebPopupMenuProxyWin::onChar(HWND hWnd, UINT message, WPARAM wParam, LPARAM, bool& handled) { handled = true; LRESULT lResult = 0; int index; switch (wParam) { case 0x0D: // Enter/Return hide(); index = focusedIndex(); ASSERT(index >= 0); // FIXME: Do we need to send back the index right away? m_newSelectedIndex = index; break; case 0x1B: // Escape hide(); break; case 0x09: // TAB case 0x08: // Backspace case 0x0A: // Linefeed default: // Character lResult = 1; break; } return lResult; } LRESULT WebPopupMenuProxyWin::onMouseMove(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled) { handled = true; 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, MAKELPARAM(mousePoint.x(), mousePoint.y())); scrollbar()->mouseMoved(event); return 0; } } BOOL shouldHotTrack = FALSE; ::SystemParametersInfo(SPI_GETHOTTRACKING, 0, &shouldHotTrack, 0); RECT bounds; ::GetClientRect(m_popup, &bounds); if (!::PtInRect(&bounds, mousePoint) && !(wParam & MK_LBUTTON)) { // 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, m_webView->window()); ::PostMessage(m_popup, WM_HOST_WINDOW_MOUSEMOVE, wParam, lParam); return 0; } if ((shouldHotTrack || wParam & MK_LBUTTON) && ::PtInRect(&bounds, mousePoint)) setFocusedIndex(listIndexAtPoint(mousePoint), true); return 0; } LRESULT WebPopupMenuProxyWin::onLButtonDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled) { handled = true; 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, MAKELPARAM(mousePoint.x(), mousePoint.y())); scrollbar()->mouseDown(event); setScrollbarCapturingMouse(true); return 0; } } // 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); else hide(); return 0; } LRESULT WebPopupMenuProxyWin::onLButtonUp(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled) { handled = true; 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, MAKELPARAM(mousePoint.x(), mousePoint.y())); scrollbar()->mouseUp(); // FIXME: This is a hack to work around Scrollbar not invalidating correctly when it doesn't have a parent widget RECT r = scrollBarRect; ::InvalidateRect(m_popup, &r, TRUE); return 0; } } // Only hide the popup if the mouse is inside the popup window. RECT bounds; ::GetClientRect(m_popup, &bounds); if (::PtInRect(&bounds, mousePoint)) { hide(); int index = focusedIndex(); if (index >= 0) { // FIXME: Do we need to send back the index right away? m_newSelectedIndex = index; } } return 0; } LRESULT WebPopupMenuProxyWin::onMouseWheel(HWND hWnd, UINT message, WPARAM wParam, LPARAM, bool& handled) { handled = true; if (!scrollbar()) return 0; 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)); return 0; } LRESULT WebPopupMenuProxyWin::onPaint(HWND hWnd, UINT message, WPARAM, LPARAM, bool& handled) { handled = true; PAINTSTRUCT paintStruct; ::BeginPaint(m_popup, &paintStruct); paint(paintStruct.rcPaint, paintStruct.hdc); ::EndPaint(m_popup, &paintStruct); return 0; } LRESULT WebPopupMenuProxyWin::onPrintClient(HWND hWnd, UINT, WPARAM wParam, LPARAM, bool& handled) { handled = true; HDC hdc = reinterpret_cast<HDC>(wParam); paint(clientRect(), hdc); return 0; } bool WebPopupMenuProxyWin::down(unsigned lines) { int size = m_items.size(); int lastSelectableIndex, selectedListIndex; lastSelectableIndex = selectedListIndex = focusedIndex(); for (int i = selectedListIndex + 1; i >= 0 && i < size; ++i) { if (m_items[i].m_isEnabled) { lastSelectableIndex = i; if (i >= selectedListIndex + (int)lines) break; } } return setFocusedIndex(lastSelectableIndex); } bool WebPopupMenuProxyWin::up(unsigned lines) { int size = m_items.size(); int lastSelectableIndex, selectedListIndex; lastSelectableIndex = selectedListIndex = focusedIndex(); for (int i = selectedListIndex - 1; i >= 0 && i < size; --i) { if (m_items[i].m_isEnabled) { lastSelectableIndex = i; if (i <= selectedListIndex - (int)lines) break; } } return setFocusedIndex(lastSelectableIndex); } void WebPopupMenuProxyWin::paint(const IntRect& damageRect, HDC hdc) { if (!m_popup) return; if (!m_DC) { m_DC = ::CreateCompatibleDC(::GetDC(m_popup)); if (!m_DC) return; } if (m_bmp) { bool keepBitmap = false; BITMAP bitmap; if (::GetObject(m_bmp, sizeof(bitmap), &bitmap)) keepBitmap = bitmap.bmWidth == clientRect().width() && bitmap.bmHeight == clientRect().height(); if (!keepBitmap) { ::DeleteObject(m_bmp); m_bmp = 0; } } if (!m_bmp) { BitmapInfo bitmapInfo = BitmapInfo::createBottomUp(clientRect().size()); void* pixels = 0; m_bmp = ::CreateDIBSection(m_DC, &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0); if (!m_bmp) return; ::SelectObject(m_DC, m_bmp); } GraphicsContext context(m_DC); IntRect translatedDamageRect = damageRect; translatedDamageRect.move(IntSize(0, m_scrollOffset * m_itemHeight)); m_data.m_notSelectedBackingStore->paint(context, damageRect.location(), translatedDamageRect); IntRect selectedIndexRectInBackingStore(0, focusedIndex() * m_itemHeight, m_data.m_selectedBackingStore->size().width(), m_itemHeight); IntPoint selectedIndexDstPoint = selectedIndexRectInBackingStore.location(); selectedIndexDstPoint.move(0, -m_scrollOffset * m_itemHeight); m_data.m_selectedBackingStore->paint(context, selectedIndexDstPoint, selectedIndexRectInBackingStore); if (m_scrollbar) m_scrollbar->paint(&context, damageRect); HDC localDC = hdc ? hdc : ::GetDC(m_popup); ::BitBlt(localDC, damageRect.x(), damageRect.y(), damageRect.width(), damageRect.height(), m_DC, damageRect.x(), damageRect.y(), SRCCOPY); if (!hdc) ::ReleaseDC(m_popup, localDC); } bool WebPopupMenuProxyWin::setFocusedIndex(int i, bool hotTracking) { if (i < 0 || i >= m_items.size() || i == focusedIndex()) return false; if (!m_items[i].m_isEnabled) return false; invalidateItem(focusedIndex()); invalidateItem(i); m_focusedIndex = i; if (!hotTracking) { if (m_client) m_client->setTextFromItemForPopupMenu(this, i); } if (!scrollToRevealSelection()) ::UpdateWindow(m_popup); return true; } int WebPopupMenuProxyWin::visibleItems() const { return clientRect().height() / m_itemHeight; } int WebPopupMenuProxyWin::listIndexAtPoint(const IntPoint& point) const { return m_scrollOffset + point.y() / m_itemHeight; } int WebPopupMenuProxyWin::focusedIndex() const { return m_focusedIndex; } void WebPopupMenuProxyWin::focusFirst() { int size = m_items.size(); for (int i = 0; i < size; ++i) { if (m_items[i].m_isEnabled) { setFocusedIndex(i); break; } } } void WebPopupMenuProxyWin::focusLast() { int size = m_items.size(); for (int i = size - 1; i > 0; --i) { if (m_items[i].m_isEnabled) { setFocusedIndex(i); break; } } } void WebPopupMenuProxyWin::incrementWheelDelta(int delta) { m_wheelDelta += delta; } void WebPopupMenuProxyWin::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 WebPopupMenuProxyWin::scrollToRevealSelection() { if (!m_scrollbar) return false; int index = focusedIndex(); if (index < m_scrollOffset) { ScrollableArea::scrollToYOffsetWithoutAnimation(index); return true; } if (index >= m_scrollOffset + visibleItems()) { ScrollableArea::scrollToYOffsetWithoutAnimation(index - visibleItems() + 1); return true; } return false; } } // namespace WebKit