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()); } GraphicsContext context(m_DC.get()); int itemCount = client()->listSize(); // listRect is the damageRect translated into the coordinates of the entire menu list (which is itemCount * 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::defaultTheme()->activeListBoxSelectionBackgroundColor(); optionTextColor = RenderTheme::defaultTheme()->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, AllowTrailingExpansion, itemStyle.textDirection(), itemStyle.hasTextDirectionOverride()); context.setFillColor(optionTextColor); FontCascade itemFont = client()->menuStyle().font(); if (client()->itemIsLabel(index)) { auto d = itemFont.fontDescription(); d.setWeight(d.bolderWeight()); itemFont = FontCascade(d, itemFont.letterSpacing(), itemFont.wordSpacing()); itemFont.update(m_popupClient->fontSelector()); } // Draw the item text if (itemStyle.isVisible()) { int textX = 0; if (client()->menuStyle().textDirection() == LTR) { textX = std::max<int>(0, client()->clientPaddingLeft() - client()->clientInsetLeft()); if (RenderTheme::defaultTheme()->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::defaultTheme()->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); }
// 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); // First, determine the popup's height int itemCount = client()->listSize(); m_itemHeight = client()->menuStyle().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 = client()->menuStyle().font(); if (client()->itemIsLabel(i)) { auto d = itemFont.fontDescription(); d.setWeight(d.bolderWeight()); itemFont = FontCascade(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(SmallScrollbar); // 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; }