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