📄 popupmenuwin.cpp
字号:
/* * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. * * 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 "PopupMenu.h"#include "Document.h"#include "FloatRect.h"#include "FontSelector.h"#include "Frame.h"#include "FrameView.h"#include "GraphicsContext.h"#include "HTMLNames.h"#include "Page.h"#include "PlatformMouseEvent.h"#include "PlatformScreen.h"#include "RenderTheme.h"#include "RenderView.h"#include "Scrollbar.h"#include "ScrollbarTheme.h"#include "SimpleFontData.h"#include <tchar.h>#include <windows.h>using std::min;namespace WebCore {using namespace HTMLNames;// Default Window animation duration in millisecondsstatic const int defaultAnimationDuration = 200;// Maximum height of a popup windowstatic const int maxPopupHeight = 320;const int optionSpacingMiddle = 1;const int popupWindowBorderWidth = 1;static LPCTSTR kPopupWindowClassName = _T("PopupWindowClass");static ATOM registerPopup();static LRESULT CALLBACK PopupWndProc(HWND, UINT, WPARAM, LPARAM);// FIXME: Remove this as soon as practical.static inline bool isASCIIPrintable(unsigned c){ return c >= 0x20 && c <= 0x7E;}PopupMenu::PopupMenu(PopupMenuClient* client) : m_popupClient(client) , m_scrollbar(0) , m_popup(0) , m_DC(0) , m_bmp(0) , m_wasClicked(false) , m_itemHeight(0) , m_scrollOffset(0) , m_wheelDelta(0) , m_focusedIndex(0) , m_scrollbarCapturingMouse(false){}PopupMenu::~PopupMenu(){ if (m_bmp) ::DeleteObject(m_bmp); if (m_DC) ::DeleteObject(m_DC); if (m_popup) ::DestroyWindow(m_popup);}void PopupMenu::show(const IntRect& r, FrameView* v, int index){ calculatePositionAndSize(r, v); if (clientRect().isEmpty()) return; if (!m_popup) { registerPopup(); DWORD exStyle = WS_EX_LTRREADING; // Even though we already know our size and location at this point, we pass (0,0,0,0) as our size/location here. // We need to wait until after the call to ::SetWindowLongPtr to set our size so that in our WM_SIZE handler we can get access to the PopupMenu object m_popup = ::CreateWindowEx(exStyle, kPopupWindowClassName, _T("PopupMenu"), WS_POPUP | WS_BORDER, 0, 0, 0, 0, v->hostWindow()->platformWindow(), 0, 0, 0); if (!m_popup) return; ::SetWindowLongPtr(m_popup, 0, (LONG_PTR)this); } if (!m_scrollbar) if (visibleItems() < client()->listSize()) { // We need a scroll bar m_scrollbar = client()->createScrollbar(this, VerticalScrollbar, SmallScrollbar); } ::SetWindowPos(m_popup, HWND_TOP, m_windowRect.x(), m_windowRect.y(), m_windowRect.width(), m_windowRect.height(), 0); // 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;#ifdef CAN_ANIMATE_TRANSPARENT_WINDOWS_SMOOTHLY ::SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, &shouldAnimate, 0);#endif if (shouldAnimate) { RECT viewRect = {0}; ::GetWindowRect(v->hostWindow()->platformWindow(), &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 + v->contentsToWindow(r.location()).y()) ? AW_VER_NEGATIVE : AW_VER_POSITIVE; ::AnimateWindow(m_popup, defaultAnimationDuration, AW_SLIDE | slideDirection | AW_ACTIVATE); } } else ::ShowWindow(m_popup, SW_SHOWNORMAL); ::SetCapture(m_popup); if (client()) { int index = client()->selectedIndex(); if (index >= 0) setFocusedIndex(index); }}void PopupMenu::hide(){ ::ShowWindow(m_popup, SW_HIDE);}const int endOfLinePadding = 2;void PopupMenu::calculatePositionAndSize(const IntRect& r, FrameView* v){ // 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()); // Then, translate to screen coordinates POINT location(rScreenCoords.location()); if (!::ClientToScreen(v->hostWindow()->platformWindow(), &location)) return; rScreenCoords.setLocation(location); // First, determine the popup's height int itemCount = client()->listSize(); m_itemHeight = client()->menuStyle().font().height() + optionSpacingMiddle; 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 = 0; for (int i = 0; i < itemCount; ++i) { String text = client()->itemText(i); if (text.isEmpty()) continue; Font itemFont = client()->menuStyle().font(); if (client()->itemIsLabel(i)) { FontDescription d = itemFont.fontDescription(); d.setWeight(d.bolderWeight()); itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing()); itemFont.update(m_popupClient->fontSelector()); } popupWidth = max(popupWidth, itemFont.width(TextRun(text.characters(), text.length()))); } if (naturalHeight > maxPopupHeight) // We need room for a scrollbar popupWidth += ScrollbarTheme::nativeTheme()->scrollbarThickness(SmallScrollbar); // Add padding to align the popup text with the <select> text // Note: We can't add paddingRight() because that value includes the width // of the dropdown button, so we must use our own endOfLinePadding constant. popupWidth += max(0, endOfLinePadding - client()->clientInsetRight()) + max(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 = 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.bottom(), popupWidth, popupHeight); // The popup needs to stay within the bounds of the screen and not overlap any toolbars FloatRect screen = screenAvailableRect(v); // Check that we don't go off the screen vertically if (popupRect.bottom() > 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() < screen.x()) { popupRect.setWidth(popupRect.width() - (screen.x() - popupRect.x())); popupRect.setX(screen.x()); } m_windowRect = popupRect; return;}bool PopupMenu::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 PopupMenu::visibleItems() const{ return clientRect().height() / m_itemHeight;}int PopupMenu::listIndexAtPoint(const IntPoint& point) const{ return m_scrollOffset + point.y() / m_itemHeight;}int PopupMenu::focusedIndex() const{ return m_focusedIndex;}void PopupMenu::focusFirst(){ if (!client()) return; int size = client()->listSize(); for (int i = 0; i < size; ++i) if (client()->itemIsEnabled(i)) { setFocusedIndex(i); break; }}void PopupMenu::focusLast(){ if (!client()) return; int size = client()->listSize(); for (int i = size - 1; i > 0; --i) if (client()->itemIsEnabled(i)) { setFocusedIndex(i); break; }}bool PopupMenu::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 PopupMenu::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 PopupMenu::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 PopupMenu::clientRect() const{ IntRect clientRect = m_windowRect; clientRect.inflate(-popupWindowBorderWidth); clientRect.setLocation(IntPoint(0, 0)); return clientRect;}void PopupMenu::incrementWheelDelta(int delta){ m_wheelDelta += delta;}void PopupMenu::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 PopupMenu::scrollToRevealSelection(){ if (!m_scrollbar) return false; int index = focusedIndex(); if (index < m_scrollOffset) { m_scrollbar->setValue(index); return true; } if (index >= m_scrollOffset + visibleItems()) { m_scrollbar->setValue(index - visibleItems() + 1); return true; } return false;}void PopupMenu::updateFromElement(){ if (!m_popup)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -