1/* 2 * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved. 3 * Copyright (C) 2007-2009 Torch Mobile Inc. 4 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Library General Public 8 * License as published by the Free Software Foundation; either 9 * version 2 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Library General Public License for more details. 15 * 16 * You should have received a copy of the GNU Library General Public License 17 * along with this library; see the file COPYING.LIB. If not, write to 18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA. 20 * 21 */ 22 23#include "config.h" 24#include "PopupMenuWin.h" 25 26#include "BitmapInfo.h" 27#include "Document.h" 28#include "FloatRect.h" 29#include "FontSelector.h" 30#include "Frame.h" 31#include "FrameView.h" 32#include "GraphicsContext.h" 33#include "HTMLNames.h" 34#include "HWndDC.h" 35#include "HostWindow.h" 36#include "LengthFunctions.h" 37#include "Page.h" 38#include "PlatformMouseEvent.h" 39#include "PlatformScreen.h" 40#include "RenderMenuList.h" 41#include "RenderTheme.h" 42#include "RenderView.h" 43#include "Scrollbar.h" 44#include "ScrollbarTheme.h" 45#include "SimpleFontData.h" 46#include "TextRun.h" 47#include "WebCoreInstanceHandle.h" 48#include <wtf/WindowsExtras.h> 49 50#include <windows.h> 51#include <windowsx.h> 52#if OS(WINCE) 53#include <ResDefCE.h> 54#define MAKEPOINTS(l) (*((POINTS FAR *)&(l))) 55#endif 56 57#define HIGH_BIT_MASK_SHORT 0x8000 58 59using std::min; 60 61namespace WebCore { 62 63using namespace HTMLNames; 64 65// Default Window animation duration in milliseconds 66static const int defaultAnimationDuration = 200; 67// Maximum height of a popup window 68static const int maxPopupHeight = 320; 69 70const int optionSpacingMiddle = 1; 71const int popupWindowBorderWidth = 1; 72 73static LPCWSTR kPopupWindowClassName = L"PopupWindowClass"; 74 75// This is used from within our custom message pump when we want to send a 76// message to the web view and not have our message stolen and sent to 77// the popup window. 78static const UINT WM_HOST_WINDOW_FIRST = WM_USER; 79static const UINT WM_HOST_WINDOW_CHAR = WM_USER + WM_CHAR; 80static const UINT WM_HOST_WINDOW_MOUSEMOVE = WM_USER + WM_MOUSEMOVE; 81 82// FIXME: Remove this as soon as practical. 83static inline bool isASCIIPrintable(unsigned c) 84{ 85 return c >= 0x20 && c <= 0x7E; 86} 87 88static void translatePoint(LPARAM& lParam, HWND from, HWND to) 89{ 90 POINT pt; 91 pt.x = (short)GET_X_LPARAM(lParam); 92 pt.y = (short)GET_Y_LPARAM(lParam); 93 ::MapWindowPoints(from, to, &pt, 1); 94 lParam = MAKELPARAM(pt.x, pt.y); 95} 96 97static FloatRect monitorFromHwnd(HWND hwnd) 98{ 99 HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); 100 MONITORINFOEX monitorInfo; 101 monitorInfo.cbSize = sizeof(MONITORINFOEX); 102 GetMonitorInfo(monitor, &monitorInfo); 103 return monitorInfo.rcWork; 104} 105 106PopupMenuWin::PopupMenuWin(PopupMenuClient* client) 107 : m_popupClient(client) 108 , m_scrollbar(0) 109 , m_popup(0) 110 , m_wasClicked(false) 111 , m_itemHeight(0) 112 , m_scrollOffset(0) 113 , m_wheelDelta(0) 114 , m_focusedIndex(0) 115 , m_hoveredIndex(0) 116 , m_scrollbarCapturingMouse(false) 117 , m_showPopup(false) 118{ 119} 120 121PopupMenuWin::~PopupMenuWin() 122{ 123 if (m_popup) 124 ::DestroyWindow(m_popup); 125 if (m_scrollbar) 126 m_scrollbar->setParent(0); 127} 128 129void PopupMenuWin::disconnectClient() 130{ 131 m_popupClient = 0; 132} 133 134LPCWSTR PopupMenuWin::popupClassName() 135{ 136 return kPopupWindowClassName; 137} 138 139void PopupMenuWin::show(const IntRect& r, FrameView* view, int index) 140{ 141 calculatePositionAndSize(r, view); 142 if (clientRect().isEmpty()) 143 return; 144 145 HWND hostWindow = view->hostWindow()->platformPageClient(); 146 147 if (!m_scrollbar && visibleItems() < client()->listSize()) { 148 // We need a scroll bar 149 m_scrollbar = client()->createScrollbar(this, VerticalScrollbar, SmallScrollbar); 150 m_scrollbar->styleChanged(); 151 } 152 153 // We need to reposition the popup window to its final coordinates. 154 // 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. 155 ::MoveWindow(m_popup, m_windowRect.x(), m_windowRect.y(), m_windowRect.width(), m_windowRect.height(), false); 156 157 // Determine whether we should animate our popups 158 // Note: Must use 'BOOL' and 'FALSE' instead of 'bool' and 'false' to avoid stack corruption with SystemParametersInfo 159 BOOL shouldAnimate = FALSE; 160 161 if (client()) { 162 int index = client()->selectedIndex(); 163 if (index >= 0) 164 setFocusedIndex(index); 165 } 166 167#if !OS(WINCE) 168 ::SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, &shouldAnimate, 0); 169 170 if (shouldAnimate) { 171 RECT viewRect = {0}; 172 ::GetWindowRect(hostWindow, &viewRect); 173 if (!::IsRectEmpty(&viewRect)) 174 ::AnimateWindow(m_popup, defaultAnimationDuration, AW_BLEND); 175 } else 176#endif 177 178 ::ShowWindow(m_popup, SW_SHOWNOACTIVATE); 179 180 m_showPopup = true; 181 182 // Protect the popup menu in case its owner is destroyed while we're running the message pump. 183 RefPtr<PopupMenu> protect(this); 184 185 ::SetCapture(hostWindow); 186 187 MSG msg; 188 HWND activeWindow; 189 190 while (::GetMessage(&msg, 0, 0, 0)) { 191 switch (msg.message) { 192 case WM_HOST_WINDOW_MOUSEMOVE: 193 case WM_HOST_WINDOW_CHAR: 194 if (msg.hwnd == m_popup) { 195 // This message should be sent to the host window. 196 msg.hwnd = hostWindow; 197 msg.message -= WM_HOST_WINDOW_FIRST; 198 } 199 break; 200 201 // Steal mouse messages. 202#if !OS(WINCE) 203 case WM_NCMOUSEMOVE: 204 case WM_NCLBUTTONDOWN: 205 case WM_NCLBUTTONUP: 206 case WM_NCLBUTTONDBLCLK: 207 case WM_NCRBUTTONDOWN: 208 case WM_NCRBUTTONUP: 209 case WM_NCRBUTTONDBLCLK: 210 case WM_NCMBUTTONDOWN: 211 case WM_NCMBUTTONUP: 212 case WM_NCMBUTTONDBLCLK: 213#endif 214 case WM_MOUSEWHEEL: 215 msg.hwnd = m_popup; 216 break; 217 218 // These mouse messages use client coordinates so we need to convert them. 219 case WM_MOUSEMOVE: 220 case WM_LBUTTONDOWN: 221 case WM_LBUTTONUP: 222 case WM_LBUTTONDBLCLK: 223 case WM_RBUTTONDOWN: 224 case WM_RBUTTONUP: 225 case WM_RBUTTONDBLCLK: 226 case WM_MBUTTONDOWN: 227 case WM_MBUTTONUP: 228 case WM_MBUTTONDBLCLK: { 229 // Translate the coordinate. 230 translatePoint(msg.lParam, msg.hwnd, m_popup); 231 232 msg.hwnd = m_popup; 233 break; 234 } 235 236 // Steal all keyboard messages. 237 case WM_KEYDOWN: 238 case WM_KEYUP: 239 case WM_CHAR: 240 case WM_DEADCHAR: 241 case WM_SYSKEYDOWN: 242 case WM_SYSKEYUP: 243 case WM_SYSCHAR: 244 case WM_SYSDEADCHAR: 245 msg.hwnd = m_popup; 246 break; 247 } 248 249 ::TranslateMessage(&msg); 250 ::DispatchMessage(&msg); 251 252 if (!m_popupClient) 253 break; 254 255 if (!m_showPopup) 256 break; 257 activeWindow = ::GetActiveWindow(); 258 if (activeWindow != hostWindow && !::IsChild(activeWindow, hostWindow)) 259 break; 260 if (::GetCapture() != hostWindow) 261 break; 262 } 263 264 if (::GetCapture() == hostWindow) 265 ::ReleaseCapture(); 266 267 // We're done, hide the popup if necessary. 268 hide(); 269} 270 271void PopupMenuWin::hide() 272{ 273 if (!m_showPopup) 274 return; 275 276 m_showPopup = false; 277 278 ::ShowWindow(m_popup, SW_HIDE); 279 280 if (client()) 281 client()->popupDidHide(); 282 283 // Post a WM_NULL message to wake up the message pump if necessary. 284 ::PostMessage(m_popup, WM_NULL, 0, 0); 285} 286 287// The screen that the popup is placed on should be whichever one the popup menu button lies on. 288// 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. 289// We can then proceed with our final position/size calculations. 290void PopupMenuWin::calculatePositionAndSize(const IntRect& r, FrameView* v) 291{ 292 // First get the screen coordinates of the popup menu client. 293 HWND hostWindow = v->hostWindow()->platformPageClient(); 294 IntRect absoluteBounds = ((RenderMenuList*)m_popupClient)->absoluteBoundingBoxRect(); 295 IntRect absoluteScreenCoords(v->contentsToWindow(absoluteBounds.location()), absoluteBounds.size()); 296 POINT absoluteLocation(absoluteScreenCoords.location()); 297 if (!::ClientToScreen(v->hostWindow()->platformPageClient(), &absoluteLocation)) 298 return; 299 absoluteScreenCoords.setLocation(absoluteLocation); 300 301 // Now set the popup menu's location temporarily to these coordinates so we can determine which screen the popup should lie on. 302 // We create or move m_popup as necessary. 303 if (!m_popup) { 304 registerClass(); 305 DWORD exStyle = WS_EX_LTRREADING; 306 m_popup = ::CreateWindowExW(exStyle, kPopupWindowClassName, L"PopupMenu", 307 WS_POPUP | WS_BORDER, 308 absoluteScreenCoords.x(), absoluteScreenCoords.y(), absoluteScreenCoords.width(), absoluteScreenCoords.height(), 309 hostWindow, 0, WebCore::instanceHandle(), this); 310 311 if (!m_popup) 312 return; 313 } else 314 ::MoveWindow(m_popup, absoluteScreenCoords.x(), absoluteScreenCoords.y(), absoluteScreenCoords.width(), absoluteScreenCoords.height(), false); 315 316 FloatRect screen = monitorFromHwnd(m_popup); 317 318 // Now we determine the actual location and measurements of the popup itself. 319 // r is in absolute document coordinates, but we want to be in screen coordinates. 320 321 // First, move to WebView coordinates 322 IntRect rScreenCoords(v->contentsToWindow(r.location()), r.size()); 323 324 // Then, translate to screen coordinates 325 POINT location(rScreenCoords.location()); 326 if (!::ClientToScreen(v->hostWindow()->platformPageClient(), &location)) 327 return; 328 329 rScreenCoords.setLocation(location); 330 331 // First, determine the popup's height 332 int itemCount = client()->listSize(); 333 m_itemHeight = client()->menuStyle().font().fontMetrics().height() + optionSpacingMiddle; 334 int naturalHeight = m_itemHeight * itemCount; 335 int popupHeight = std::min(maxPopupHeight, naturalHeight); 336 // The popup should show an integral number of items (i.e. no partial items should be visible) 337 popupHeight -= popupHeight % m_itemHeight; 338 339 // Next determine its width 340 int popupWidth = 0; 341 for (int i = 0; i < itemCount; ++i) { 342 String text = client()->itemText(i); 343 if (text.isEmpty()) 344 continue; 345 346 Font itemFont = client()->menuStyle().font(); 347 if (client()->itemIsLabel(i)) { 348 FontDescription d = itemFont.fontDescription(); 349 d.setWeight(d.bolderWeight()); 350 itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing()); 351 itemFont.update(m_popupClient->fontSelector()); 352 } 353 354 popupWidth = std::max(popupWidth, static_cast<int>(ceilf(itemFont.width(TextRun(text))))); 355 } 356 357 if (naturalHeight > maxPopupHeight) 358 // We need room for a scrollbar 359 popupWidth += ScrollbarTheme::theme()->scrollbarThickness(SmallScrollbar); 360 361 // Add padding to align the popup text with the <select> text 362 popupWidth += std::max<int>(0, client()->clientPaddingRight() - client()->clientInsetRight()) + std::max<int>(0, client()->clientPaddingLeft() - client()->clientInsetLeft()); 363 364 // Leave room for the border 365 popupWidth += 2 * popupWindowBorderWidth; 366 popupHeight += 2 * popupWindowBorderWidth; 367 368 // The popup should be at least as wide as the control on the page 369 popupWidth = std::max(rScreenCoords.width() - client()->clientInsetLeft() - client()->clientInsetRight(), popupWidth); 370 371 // Always left-align items in the popup. This matches popup menus on the mac. 372 int popupX = rScreenCoords.x() + client()->clientInsetLeft(); 373 374 IntRect popupRect(popupX, rScreenCoords.maxY(), popupWidth, popupHeight); 375 376 // Check that we don't go off the screen vertically 377 if (popupRect.maxY() > screen.height()) { 378 // The popup will go off the screen, so try placing it above the client 379 if (rScreenCoords.y() - popupRect.height() < 0) { 380 // The popup won't fit above, either, so place it whereever's bigger and resize it to fit 381 if ((rScreenCoords.y() + rScreenCoords.height() / 2) < (screen.height() / 2)) { 382 // Below is bigger 383 popupRect.setHeight(screen.height() - popupRect.y()); 384 } else { 385 // Above is bigger 386 popupRect.setY(0); 387 popupRect.setHeight(rScreenCoords.y()); 388 } 389 } else { 390 // The popup fits above, so reposition it 391 popupRect.setY(rScreenCoords.y() - popupRect.height()); 392 } 393 } 394 395 // Check that we don't go off the screen horizontally 396 if (popupRect.x() + popupRect.width() > screen.width() + screen.x()) 397 popupRect.setX(screen.x() + screen.width() - popupRect.width()); 398 if (popupRect.x() < screen.x()) 399 popupRect.setX(screen.x()); 400 401 m_windowRect = popupRect; 402 return; 403} 404 405bool PopupMenuWin::setFocusedIndex(int i, bool hotTracking) 406{ 407 if (i < 0 || i >= client()->listSize() || i == focusedIndex()) 408 return false; 409 410 if (!client()->itemIsEnabled(i)) 411 return false; 412 413 invalidateItem(focusedIndex()); 414 invalidateItem(i); 415 416 m_focusedIndex = i; 417 418 if (!hotTracking) 419 client()->setTextFromItem(i); 420 421 if (!scrollToRevealSelection()) 422 ::UpdateWindow(m_popup); 423 424 return true; 425} 426 427int PopupMenuWin::visibleItems() const 428{ 429 return clientRect().height() / m_itemHeight; 430} 431 432int PopupMenuWin::listIndexAtPoint(const IntPoint& point) const 433{ 434 return m_scrollOffset + point.y() / m_itemHeight; 435} 436 437int PopupMenuWin::focusedIndex() const 438{ 439 return m_focusedIndex; 440} 441 442void PopupMenuWin::focusFirst() 443{ 444 if (!client()) 445 return; 446 447 int size = client()->listSize(); 448 449 for (int i = 0; i < size; ++i) 450 if (client()->itemIsEnabled(i)) { 451 setFocusedIndex(i); 452 break; 453 } 454} 455 456void PopupMenuWin::focusLast() 457{ 458 if (!client()) 459 return; 460 461 int size = client()->listSize(); 462 463 for (int i = size - 1; i > 0; --i) 464 if (client()->itemIsEnabled(i)) { 465 setFocusedIndex(i); 466 break; 467 } 468} 469 470bool PopupMenuWin::down(unsigned lines) 471{ 472 if (!client()) 473 return false; 474 475 int size = client()->listSize(); 476 477 int lastSelectableIndex, selectedListIndex; 478 lastSelectableIndex = selectedListIndex = focusedIndex(); 479 for (int i = selectedListIndex + 1; i >= 0 && i < size; ++i) 480 if (client()->itemIsEnabled(i)) { 481 lastSelectableIndex = i; 482 if (i >= selectedListIndex + (int)lines) 483 break; 484 } 485 486 return setFocusedIndex(lastSelectableIndex); 487} 488 489bool PopupMenuWin::up(unsigned lines) 490{ 491 if (!client()) 492 return false; 493 494 int size = client()->listSize(); 495 496 int lastSelectableIndex, selectedListIndex; 497 lastSelectableIndex = selectedListIndex = focusedIndex(); 498 for (int i = selectedListIndex - 1; i >= 0 && i < size; --i) 499 if (client()->itemIsEnabled(i)) { 500 lastSelectableIndex = i; 501 if (i <= selectedListIndex - (int)lines) 502 break; 503 } 504 505 return setFocusedIndex(lastSelectableIndex); 506} 507 508void PopupMenuWin::invalidateItem(int index) 509{ 510 if (!m_popup) 511 return; 512 513 IntRect damageRect(clientRect()); 514 damageRect.setY(m_itemHeight * (index - m_scrollOffset)); 515 damageRect.setHeight(m_itemHeight); 516 if (m_scrollbar) 517 damageRect.setWidth(damageRect.width() - m_scrollbar->frameRect().width()); 518 519 RECT r = damageRect; 520 ::InvalidateRect(m_popup, &r, TRUE); 521} 522 523IntRect PopupMenuWin::clientRect() const 524{ 525 IntRect clientRect = m_windowRect; 526 clientRect.inflate(-popupWindowBorderWidth); 527 clientRect.setLocation(IntPoint(0, 0)); 528 return clientRect; 529} 530 531void PopupMenuWin::incrementWheelDelta(int delta) 532{ 533 m_wheelDelta += delta; 534} 535 536void PopupMenuWin::reduceWheelDelta(int delta) 537{ 538 ASSERT(delta >= 0); 539 ASSERT(delta <= abs(m_wheelDelta)); 540 541 if (m_wheelDelta > 0) 542 m_wheelDelta -= delta; 543 else if (m_wheelDelta < 0) 544 m_wheelDelta += delta; 545 else 546 return; 547} 548 549bool PopupMenuWin::scrollToRevealSelection() 550{ 551 if (!m_scrollbar) 552 return false; 553 554 int index = focusedIndex(); 555 556 if (index < m_scrollOffset) { 557 ScrollableArea::scrollToOffsetWithoutAnimation(VerticalScrollbar, index); 558 return true; 559 } 560 561 if (index >= m_scrollOffset + visibleItems()) { 562 ScrollableArea::scrollToOffsetWithoutAnimation(VerticalScrollbar, index - visibleItems() + 1); 563 return true; 564 } 565 566 return false; 567} 568 569void PopupMenuWin::updateFromElement() 570{ 571 if (!m_popup) 572 return; 573 574 m_focusedIndex = client()->selectedIndex(); 575 576 ::InvalidateRect(m_popup, 0, TRUE); 577 if (!scrollToRevealSelection()) 578 ::UpdateWindow(m_popup); 579} 580 581const int separatorPadding = 4; 582const int separatorHeight = 1; 583void PopupMenuWin::paint(const IntRect& damageRect, HDC hdc) 584{ 585 if (!m_popup) 586 return; 587 588 if (!m_DC) { 589 m_DC = adoptGDIObject(::CreateCompatibleDC(HWndDC(m_popup))); 590 if (!m_DC) 591 return; 592 } 593 594 if (m_bmp) { 595 bool keepBitmap = false; 596 BITMAP bitmap; 597 if (::GetObject(m_bmp.get(), sizeof(bitmap), &bitmap)) 598 keepBitmap = bitmap.bmWidth == clientRect().width() 599 && bitmap.bmHeight == clientRect().height(); 600 if (!keepBitmap) 601 m_bmp.clear(); 602 } 603 if (!m_bmp) { 604#if OS(WINCE) 605 BitmapInfo bitmapInfo = BitmapInfo::createBottomUp(clientRect().size(), BitmapInfo::BitCount16); 606#else 607 BitmapInfo bitmapInfo = BitmapInfo::createBottomUp(clientRect().size()); 608#endif 609 void* pixels = 0; 610 m_bmp = adoptGDIObject(::CreateDIBSection(m_DC.get(), &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0)); 611 if (!m_bmp) 612 return; 613 614 ::SelectObject(m_DC.get(), m_bmp.get()); 615 } 616 617 GraphicsContext context(m_DC.get()); 618 619 int itemCount = client()->listSize(); 620 621 // listRect is the damageRect translated into the coordinates of the entire menu list (which is itemCount * m_itemHeight pixels tall) 622 IntRect listRect = damageRect; 623 listRect.move(IntSize(0, m_scrollOffset * m_itemHeight)); 624 625 for (int y = listRect.y(); y < listRect.maxY(); y += m_itemHeight) { 626 int index = y / m_itemHeight; 627 628 Color optionBackgroundColor, optionTextColor; 629 PopupMenuStyle itemStyle = client()->itemStyle(index); 630 if (index == focusedIndex()) { 631 optionBackgroundColor = RenderTheme::defaultTheme()->activeListBoxSelectionBackgroundColor(); 632 optionTextColor = RenderTheme::defaultTheme()->activeListBoxSelectionForegroundColor(); 633 } else { 634 optionBackgroundColor = itemStyle.backgroundColor(); 635 optionTextColor = itemStyle.foregroundColor(); 636 } 637 638 // itemRect is in client coordinates 639 IntRect itemRect(0, (index - m_scrollOffset) * m_itemHeight, damageRect.width(), m_itemHeight); 640 641 // Draw the background for this menu item 642 if (itemStyle.isVisible()) 643 context.fillRect(itemRect, optionBackgroundColor, ColorSpaceDeviceRGB); 644 645 if (client()->itemIsSeparator(index)) { 646 IntRect separatorRect(itemRect.x() + separatorPadding, itemRect.y() + (itemRect.height() - separatorHeight) / 2, itemRect.width() - 2 * separatorPadding, separatorHeight); 647 context.fillRect(separatorRect, optionTextColor, ColorSpaceDeviceRGB); 648 continue; 649 } 650 651 String itemText = client()->itemText(index); 652 653 TextRun textRun(itemText, 0, 0, TextRun::AllowTrailingExpansion, itemStyle.textDirection(), itemStyle.hasTextDirectionOverride()); 654 context.setFillColor(optionTextColor, ColorSpaceDeviceRGB); 655 656 Font itemFont = client()->menuStyle().font(); 657 if (client()->itemIsLabel(index)) { 658 FontDescription d = itemFont.fontDescription(); 659 d.setWeight(d.bolderWeight()); 660 itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing()); 661 itemFont.update(m_popupClient->fontSelector()); 662 } 663 664 // Draw the item text 665 if (itemStyle.isVisible()) { 666 int textX = 0; 667 if (client()->menuStyle().textDirection() == LTR) { 668 textX = std::max<int>(0, client()->clientPaddingLeft() - client()->clientInsetLeft()); 669 if (RenderTheme::defaultTheme()->popupOptionSupportsTextIndent()) 670 textX += minimumIntValueForLength(itemStyle.textIndent(), itemRect.width()); 671 } else { 672 textX = itemRect.width() - client()->menuStyle().font().width(textRun); 673 textX = std::min<int>(textX, textX - client()->clientPaddingRight() + client()->clientInsetRight()); 674 if (RenderTheme::defaultTheme()->popupOptionSupportsTextIndent()) 675 textX -= minimumIntValueForLength(itemStyle.textIndent(), itemRect.width()); 676 } 677 int textY = itemRect.y() + itemFont.fontMetrics().ascent() + (itemRect.height() - itemFont.fontMetrics().height()) / 2; 678 context.drawBidiText(itemFont, textRun, IntPoint(textX, textY)); 679 } 680 } 681 682 if (m_scrollbar) 683 m_scrollbar->paint(&context, damageRect); 684 685 HWndDC hWndDC; 686 HDC localDC = hdc ? hdc : hWndDC.setHWnd(m_popup); 687 688 ::BitBlt(localDC, damageRect.x(), damageRect.y(), damageRect.width(), damageRect.height(), m_DC.get(), damageRect.x(), damageRect.y(), SRCCOPY); 689} 690 691int PopupMenuWin::scrollSize(ScrollbarOrientation orientation) const 692{ 693 return ((orientation == VerticalScrollbar) && m_scrollbar) ? (m_scrollbar->totalSize() - m_scrollbar->visibleSize()) : 0; 694} 695 696int PopupMenuWin::scrollPosition(Scrollbar*) const 697{ 698 return m_scrollOffset; 699} 700 701void PopupMenuWin::setScrollOffset(const IntPoint& offset) 702{ 703 scrollTo(offset.y()); 704} 705 706void PopupMenuWin::scrollTo(int offset) 707{ 708 ASSERT(m_scrollbar); 709 710 if (!m_popup) 711 return; 712 713 if (m_scrollOffset == offset) 714 return; 715 716 int scrolledLines = m_scrollOffset - offset; 717 m_scrollOffset = offset; 718 719 UINT flags = SW_INVALIDATE; 720 721#ifdef CAN_SET_SMOOTH_SCROLLING_DURATION 722 BOOL shouldSmoothScroll = FALSE; 723 ::SystemParametersInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, &shouldSmoothScroll, 0); 724 if (shouldSmoothScroll) 725 flags |= MAKEWORD(SW_SMOOTHSCROLL, smoothScrollAnimationDuration); 726#endif 727 728 IntRect listRect = clientRect(); 729 if (m_scrollbar) 730 listRect.setWidth(listRect.width() - m_scrollbar->frameRect().width()); 731 RECT r = listRect; 732 ::ScrollWindowEx(m_popup, 0, scrolledLines * m_itemHeight, &r, 0, 0, 0, flags); 733 if (m_scrollbar) { 734 r = m_scrollbar->frameRect(); 735 ::InvalidateRect(m_popup, &r, TRUE); 736 } 737 ::UpdateWindow(m_popup); 738} 739 740void PopupMenuWin::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect) 741{ 742 IntRect scrollRect = rect; 743 scrollRect.move(scrollbar->x(), scrollbar->y()); 744 RECT r = scrollRect; 745 ::InvalidateRect(m_popup, &r, false); 746} 747 748IntSize PopupMenuWin::visibleSize() const 749{ 750 return IntSize(m_windowRect.width(), m_scrollbar ? m_scrollbar->visibleSize() : m_windowRect.height()); 751} 752 753IntSize PopupMenuWin::contentsSize() const 754{ 755 return m_windowRect.size(); 756} 757 758IntRect PopupMenuWin::scrollableAreaBoundingBox() const 759{ 760 return m_windowRect; 761} 762 763void PopupMenuWin::registerClass() 764{ 765 static bool haveRegisteredWindowClass = false; 766 767 if (haveRegisteredWindowClass) 768 return; 769 770#if OS(WINCE) 771 WNDCLASS wcex; 772#else 773 WNDCLASSEX wcex; 774 wcex.cbSize = sizeof(WNDCLASSEX); 775 wcex.hIconSm = 0; 776 wcex.style = CS_DROPSHADOW; 777#endif 778 779 wcex.lpfnWndProc = PopupMenuWndProc; 780 wcex.cbClsExtra = 0; 781 wcex.cbWndExtra = sizeof(PopupMenu*); // For the PopupMenu pointer 782 wcex.hInstance = WebCore::instanceHandle(); 783 wcex.hIcon = 0; 784 wcex.hCursor = LoadCursor(0, IDC_ARROW); 785 wcex.hbrBackground = 0; 786 wcex.lpszMenuName = 0; 787 wcex.lpszClassName = kPopupWindowClassName; 788 789 haveRegisteredWindowClass = true; 790 791#if OS(WINCE) 792 RegisterClass(&wcex); 793#else 794 RegisterClassEx(&wcex); 795#endif 796} 797 798 799LRESULT CALLBACK PopupMenuWin::PopupMenuWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 800{ 801 if (PopupMenuWin* popup = static_cast<PopupMenuWin*>(getWindowPointer(hWnd, 0))) 802 return popup->wndProc(hWnd, message, wParam, lParam); 803 804 if (message == WM_CREATE) { 805 LPCREATESTRUCT createStruct = reinterpret_cast<LPCREATESTRUCT>(lParam); 806 807 // Associate the PopupMenu with the window. 808 setWindowPointer(hWnd, 0, createStruct->lpCreateParams); 809 return 0; 810 } 811 812 return ::DefWindowProc(hWnd, message, wParam, lParam); 813} 814 815const int smoothScrollAnimationDuration = 5000; 816 817LRESULT PopupMenuWin::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 818{ 819 LRESULT lResult = 0; 820 821 switch (message) { 822#if !OS(WINCE) 823 case WM_MOUSEACTIVATE: 824 return MA_NOACTIVATE; 825#endif 826 case WM_SIZE: { 827 if (!scrollbar()) 828 break; 829 830 IntSize size(LOWORD(lParam), HIWORD(lParam)); 831 scrollbar()->setFrameRect(IntRect(size.width() - scrollbar()->width(), 0, scrollbar()->width(), size.height())); 832 833 int visibleItems = this->visibleItems(); 834 scrollbar()->setEnabled(visibleItems < client()->listSize()); 835 scrollbar()->setSteps(1, std::max(1, visibleItems - 1)); 836 scrollbar()->setProportion(visibleItems, client()->listSize()); 837 838 break; 839 } 840 case WM_SYSKEYDOWN: 841 case WM_KEYDOWN: { 842 if (!client()) 843 break; 844 845 bool altKeyPressed = GetKeyState(VK_MENU) & HIGH_BIT_MASK_SHORT; 846 bool ctrlKeyPressed = GetKeyState(VK_CONTROL) & HIGH_BIT_MASK_SHORT; 847 848 lResult = 0; 849 switch (LOWORD(wParam)) { 850 case VK_F4: { 851 if (!altKeyPressed && !ctrlKeyPressed) { 852 int index = focusedIndex(); 853 ASSERT(index >= 0); 854 client()->valueChanged(index); 855 hide(); 856 } 857 break; 858 } 859 case VK_DOWN: 860 if (altKeyPressed) { 861 int index = focusedIndex(); 862 ASSERT(index >= 0); 863 client()->valueChanged(index); 864 hide(); 865 } else 866 down(); 867 break; 868 case VK_RIGHT: 869 down(); 870 break; 871 case VK_UP: 872 if (altKeyPressed) { 873 int index = focusedIndex(); 874 ASSERT(index >= 0); 875 client()->valueChanged(index); 876 hide(); 877 } else 878 up(); 879 break; 880 case VK_LEFT: 881 up(); 882 break; 883 case VK_HOME: 884 focusFirst(); 885 break; 886 case VK_END: 887 focusLast(); 888 break; 889 case VK_PRIOR: 890 if (focusedIndex() != scrollOffset()) { 891 // Set the selection to the first visible item 892 int firstVisibleItem = scrollOffset(); 893 up(focusedIndex() - firstVisibleItem); 894 } else { 895 // The first visible item is selected, so move the selection back one page 896 up(visibleItems()); 897 } 898 break; 899 case VK_NEXT: { 900 int lastVisibleItem = scrollOffset() + visibleItems() - 1; 901 if (focusedIndex() != lastVisibleItem) { 902 // Set the selection to the last visible item 903 down(lastVisibleItem - focusedIndex()); 904 } else { 905 // The last visible item is selected, so move the selection forward one page 906 down(visibleItems()); 907 } 908 break; 909 } 910 case VK_TAB: 911 ::SendMessage(client()->hostWindow()->platformPageClient(), message, wParam, lParam); 912 hide(); 913 break; 914 case VK_ESCAPE: 915 hide(); 916 break; 917 default: 918 if (isASCIIPrintable(wParam)) 919 // Send the keydown to the WebView so it can be used for type-to-select. 920 // Since we know that the virtual key is ASCII printable, it's OK to convert this to 921 // a WM_CHAR message. (We don't want to call TranslateMessage because that will post a 922 // WM_CHAR message that will be stolen and redirected to the popup HWND. 923 ::PostMessage(m_popup, WM_HOST_WINDOW_CHAR, wParam, lParam); 924 else 925 lResult = 1; 926 break; 927 } 928 break; 929 } 930 case WM_CHAR: { 931 if (!client()) 932 break; 933 934 lResult = 0; 935 int index; 936 switch (wParam) { 937 case 0x0D: // Enter/Return 938 hide(); 939 index = focusedIndex(); 940 ASSERT(index >= 0); 941 client()->valueChanged(index); 942 break; 943 case 0x1B: // Escape 944 hide(); 945 break; 946 case 0x09: // TAB 947 case 0x08: // Backspace 948 case 0x0A: // Linefeed 949 default: // Character 950 lResult = 1; 951 break; 952 } 953 break; 954 } 955 case WM_MOUSEMOVE: { 956 IntPoint mousePoint(MAKEPOINTS(lParam)); 957 if (scrollbar()) { 958 IntRect scrollBarRect = scrollbar()->frameRect(); 959 if (scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) { 960 // Put the point into coordinates relative to the scroll bar 961 mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y()); 962 PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y())); 963 scrollbar()->mouseMoved(event); 964 break; 965 } 966 } 967 968 BOOL shouldHotTrack = FALSE; 969#if !OS(WINCE) 970 ::SystemParametersInfo(SPI_GETHOTTRACKING, 0, &shouldHotTrack, 0); 971#endif 972 973 RECT bounds; 974 GetClientRect(popupHandle(), &bounds); 975 if (!::PtInRect(&bounds, mousePoint) && !(wParam & MK_LBUTTON) && client()) { 976 // When the mouse is not inside the popup menu and the left button isn't down, just 977 // repost the message to the web view. 978 979 // Translate the coordinate. 980 translatePoint(lParam, m_popup, client()->hostWindow()->platformPageClient()); 981 982 ::PostMessage(m_popup, WM_HOST_WINDOW_MOUSEMOVE, wParam, lParam); 983 break; 984 } 985 986 if ((shouldHotTrack || wParam & MK_LBUTTON) && ::PtInRect(&bounds, mousePoint)) { 987 setFocusedIndex(listIndexAtPoint(mousePoint), true); 988 m_hoveredIndex = listIndexAtPoint(mousePoint); 989 } 990 991 break; 992 } 993 case WM_LBUTTONDOWN: { 994 IntPoint mousePoint(MAKEPOINTS(lParam)); 995 if (scrollbar()) { 996 IntRect scrollBarRect = scrollbar()->frameRect(); 997 if (scrollBarRect.contains(mousePoint)) { 998 // Put the point into coordinates relative to the scroll bar 999 mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y()); 1000 PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y())); 1001 scrollbar()->mouseDown(event); 1002 setScrollbarCapturingMouse(true); 1003 break; 1004 } 1005 } 1006 1007 // If the mouse is inside the window, update the focused index. Otherwise, 1008 // hide the popup. 1009 RECT bounds; 1010 GetClientRect(m_popup, &bounds); 1011 if (::PtInRect(&bounds, mousePoint)) { 1012 setFocusedIndex(listIndexAtPoint(mousePoint), true); 1013 m_hoveredIndex = listIndexAtPoint(mousePoint); 1014 } 1015 else 1016 hide(); 1017 break; 1018 } 1019 case WM_LBUTTONUP: { 1020 IntPoint mousePoint(MAKEPOINTS(lParam)); 1021 if (scrollbar()) { 1022 IntRect scrollBarRect = scrollbar()->frameRect(); 1023 if (scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) { 1024 setScrollbarCapturingMouse(false); 1025 // Put the point into coordinates relative to the scroll bar 1026 mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y()); 1027 PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y())); 1028 scrollbar()->mouseUp(event); 1029 // FIXME: This is a hack to work around Scrollbar not invalidating correctly when it doesn't have a parent widget 1030 RECT r = scrollBarRect; 1031 ::InvalidateRect(popupHandle(), &r, TRUE); 1032 break; 1033 } 1034 } 1035 // Only hide the popup if the mouse is inside the popup window. 1036 RECT bounds; 1037 GetClientRect(popupHandle(), &bounds); 1038 if (client() && ::PtInRect(&bounds, mousePoint)) { 1039 hide(); 1040 int index = m_hoveredIndex; 1041 if (!client()->itemIsEnabled(index)) 1042 index = client()->selectedIndex(); 1043 if (index >= 0) 1044 client()->valueChanged(index); 1045 } 1046 break; 1047 } 1048 1049 case WM_MOUSEWHEEL: { 1050 if (!scrollbar()) 1051 break; 1052 1053 int i = 0; 1054 for (incrementWheelDelta(GET_WHEEL_DELTA_WPARAM(wParam)); abs(wheelDelta()) >= WHEEL_DELTA; reduceWheelDelta(WHEEL_DELTA)) { 1055 if (wheelDelta() > 0) 1056 ++i; 1057 else 1058 --i; 1059 } 1060 1061 ScrollableArea::scroll(i > 0 ? ScrollUp : ScrollDown, ScrollByLine, abs(i)); 1062 break; 1063 } 1064 1065 case WM_PAINT: { 1066 PAINTSTRUCT paintInfo; 1067 ::BeginPaint(popupHandle(), &paintInfo); 1068 paint(paintInfo.rcPaint, paintInfo.hdc); 1069 ::EndPaint(popupHandle(), &paintInfo); 1070 lResult = 0; 1071 break; 1072 } 1073#if !OS(WINCE) 1074 case WM_PRINTCLIENT: 1075 paint(clientRect(), (HDC)wParam); 1076 break; 1077#endif 1078 default: 1079 lResult = DefWindowProc(hWnd, message, wParam, lParam); 1080 } 1081 1082 return lResult; 1083} 1084 1085} 1086