/* * 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 #include #include #include #include #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 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(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