1/* 2 * Copyright (C) 2010 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26// NOTE: This implementation is very similar to the implementation of popups in WebCore::PopupMenuWin. 27// We should try and factor out the common bits and share them. 28 29#include "config.h" 30#include "WebPopupMenuProxyWin.h" 31 32#include "NativeWebMouseEvent.h" 33#include "WebView.h" 34#include <WebCore/WebCoreInstanceHandle.h> 35#include <WebCore/ScrollbarTheme.h> 36#include <WebCore/BitmapInfo.h> 37#include <WebCore/PlatformMouseEvent.h> 38#include <windowsx.h> 39 40using namespace WebCore; 41using namespace std; 42 43namespace WebKit { 44 45static const LPCWSTR kWebKit2WebPopupMenuProxyWindowClassName = L"WebKit2WebPopupMenuProxyWindowClass"; 46 47static const int defaultAnimationDuration = 200; 48static const int maxPopupHeight = 320; 49static const int popupWindowBorderWidth = 1; 50static const int separatorPadding = 4; 51static const int separatorHeight = 1; 52 53// This is used from within our custom message pump when we want to send a 54// message to the web view and not have our message stolen and sent to 55// the popup window. 56static const UINT WM_HOST_WINDOW_FIRST = WM_USER; 57static const UINT WM_HOST_WINDOW_CHAR = WM_USER + WM_CHAR; 58static const UINT WM_HOST_WINDOW_MOUSEMOVE = WM_USER + WM_MOUSEMOVE; 59 60static inline bool isASCIIPrintable(unsigned c) 61{ 62 return c >= 0x20 && c <= 0x7E; 63} 64 65static void translatePoint(LPARAM& lParam, HWND from, HWND to) 66{ 67 POINT pt; 68 pt.x = static_cast<short>(GET_X_LPARAM(lParam)); 69 pt.y = static_cast<short>(GET_Y_LPARAM(lParam)); 70 ::MapWindowPoints(from, to, &pt, 1); 71 lParam = MAKELPARAM(pt.x, pt.y); 72} 73 74LRESULT CALLBACK WebPopupMenuProxyWin::WebPopupMenuProxyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 75{ 76 LONG_PTR longPtr = ::GetWindowLongPtr(hWnd, 0); 77 78 if (WebPopupMenuProxyWin* popupMenuProxy = reinterpret_cast<WebPopupMenuProxyWin*>(longPtr)) 79 return popupMenuProxy->wndProc(hWnd, message, wParam, lParam); 80 81 if (message == WM_CREATE) { 82 LPCREATESTRUCT createStruct = reinterpret_cast<LPCREATESTRUCT>(lParam); 83 84 // Associate the WebView with the window. 85 ::SetWindowLongPtr(hWnd, 0, (LONG_PTR)createStruct->lpCreateParams); 86 return 0; 87 } 88 89 return ::DefWindowProc(hWnd, message, wParam, lParam); 90} 91 92LRESULT WebPopupMenuProxyWin::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 93{ 94 LRESULT lResult = 0; 95 bool handled = true; 96 97 switch (message) { 98 case WM_MOUSEACTIVATE: 99 lResult = onMouseActivate(hWnd, message, wParam, lParam, handled); 100 break; 101 case WM_SIZE: 102 lResult = onSize(hWnd, message, wParam, lParam, handled); 103 break; 104 case WM_KEYDOWN: 105 lResult = onKeyDown(hWnd, message, wParam, lParam, handled); 106 break; 107 case WM_CHAR: 108 lResult = onChar(hWnd, message, wParam, lParam, handled); 109 break; 110 case WM_MOUSEMOVE: 111 lResult = onMouseMove(hWnd, message, wParam, lParam, handled); 112 break; 113 case WM_LBUTTONDOWN: 114 lResult = onLButtonDown(hWnd, message, wParam, lParam, handled); 115 break; 116 case WM_LBUTTONUP: 117 lResult = onLButtonUp(hWnd, message, wParam, lParam, handled); 118 break; 119 case WM_MOUSEWHEEL: 120 lResult = onMouseWheel(hWnd, message, wParam, lParam, handled); 121 break; 122 case WM_PAINT: 123 lResult = onPaint(hWnd, message, wParam, lParam, handled); 124 break; 125 case WM_PRINTCLIENT: 126 lResult = onPrintClient(hWnd, message, wParam, lParam, handled); 127 break; 128 default: 129 handled = false; 130 break; 131 } 132 133 if (!handled) 134 lResult = ::DefWindowProc(hWnd, message, wParam, lParam); 135 136 return lResult; 137} 138 139bool WebPopupMenuProxyWin::registerWindowClass() 140{ 141 static bool haveRegisteredWindowClass = false; 142 if (haveRegisteredWindowClass) 143 return true; 144 haveRegisteredWindowClass = true; 145 146 WNDCLASSEX wcex; 147 wcex.cbSize = sizeof(WNDCLASSEX); 148 wcex.style = CS_DROPSHADOW; 149 wcex.lpfnWndProc = WebPopupMenuProxyWin::WebPopupMenuProxyWndProc; 150 wcex.cbClsExtra = 0; 151 wcex.cbWndExtra = sizeof(WebPopupMenuProxyWin*); 152 wcex.hInstance = instanceHandle(); 153 wcex.hIcon = 0; 154 wcex.hCursor = ::LoadCursor(0, IDC_ARROW); 155 wcex.hbrBackground = 0; 156 wcex.lpszMenuName = 0; 157 wcex.lpszClassName = kWebKit2WebPopupMenuProxyWindowClassName; 158 wcex.hIconSm = 0; 159 160 return !!::RegisterClassEx(&wcex); 161} 162 163WebPopupMenuProxyWin::WebPopupMenuProxyWin(WebView* webView, WebPopupMenuProxy::Client* client) 164 : WebPopupMenuProxy(client) 165 , m_webView(webView) 166 , m_newSelectedIndex(0) 167 , m_popup(0) 168 , m_DC(0) 169 , m_bmp(0) 170 , m_itemHeight(0) 171 , m_scrollOffset(0) 172 , m_wheelDelta(0) 173 , m_focusedIndex(0) 174 , m_wasClicked(false) 175 , m_scrollbarCapturingMouse(false) 176 , m_showPopup(false) 177{ 178} 179 180WebPopupMenuProxyWin::~WebPopupMenuProxyWin() 181{ 182 if (m_bmp) 183 ::DeleteObject(m_bmp); 184 if (m_DC) 185 ::DeleteDC(m_DC); 186 if (m_popup) 187 ::DestroyWindow(m_popup); 188 if (m_scrollbar) 189 m_scrollbar->setParent(0); 190} 191 192void WebPopupMenuProxyWin::showPopupMenu(const IntRect& rect, TextDirection, double, const Vector<WebPopupItem>& items, const PlatformPopupMenuData& data, int32_t selectedIndex) 193{ 194 m_items = items; 195 m_data = data; 196 m_newSelectedIndex = selectedIndex; 197 198 calculatePositionAndSize(rect); 199 if (clientRect().isEmpty()) 200 return; 201 202 HWND hostWindow = m_webView->window(); 203 204 if (!m_scrollbar && visibleItems() < m_items.size()) { 205 m_scrollbar = Scrollbar::createNativeScrollbar(this, VerticalScrollbar, SmallScrollbar); 206 m_scrollbar->styleChanged(); 207 } 208 209 if (!m_popup) { 210 registerWindowClass(); 211 212 DWORD exStyle = WS_EX_LTRREADING; 213 214 m_popup = ::CreateWindowEx(exStyle, kWebKit2WebPopupMenuProxyWindowClassName, TEXT("PopupMenu"), 215 WS_POPUP | WS_BORDER, 216 m_windowRect.x(), m_windowRect.y(), m_windowRect.width(), m_windowRect.height(), 217 hostWindow, 0, instanceHandle(), this); 218 219 if (!m_popup) 220 return; 221 } 222 223 BOOL shouldAnimate = FALSE; 224 ::SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, &shouldAnimate, 0); 225 226 if (shouldAnimate) { 227 RECT viewRect = {0}; 228 ::GetWindowRect(hostWindow, &viewRect); 229 230 if (!::IsRectEmpty(&viewRect)) { 231 // Popups should slide into view away from the <select> box 232 // NOTE: This may have to change for Vista 233 DWORD slideDirection = (m_windowRect.y() < viewRect.top + rect.location().y()) ? AW_VER_NEGATIVE : AW_VER_POSITIVE; 234 235 ::AnimateWindow(m_popup, defaultAnimationDuration, AW_SLIDE | slideDirection); 236 } 237 } else 238 ::ShowWindow(m_popup, SW_SHOWNOACTIVATE); 239 240 241 int index = selectedIndex; 242 if (index >= 0) 243 setFocusedIndex(index); 244 245 m_showPopup = true; 246 247 // Protect the popup menu in case its owner is destroyed while we're running the message pump. 248 RefPtr<WebPopupMenuProxyWin> protect(this); 249 250 ::SetCapture(hostWindow); 251 252 MSG msg; 253 HWND activeWindow; 254 255 while (::GetMessage(&msg, 0, 0, 0)) { 256 switch (msg.message) { 257 case WM_HOST_WINDOW_MOUSEMOVE: 258 case WM_HOST_WINDOW_CHAR: 259 if (msg.hwnd == m_popup) { 260 // This message should be sent to the host window. 261 msg.hwnd = hostWindow; 262 msg.message -= WM_HOST_WINDOW_FIRST; 263 } 264 break; 265 266 // Steal mouse messages. 267 case WM_NCMOUSEMOVE: 268 case WM_NCLBUTTONDOWN: 269 case WM_NCLBUTTONUP: 270 case WM_NCLBUTTONDBLCLK: 271 case WM_NCRBUTTONDOWN: 272 case WM_NCRBUTTONUP: 273 case WM_NCRBUTTONDBLCLK: 274 case WM_NCMBUTTONDOWN: 275 case WM_NCMBUTTONUP: 276 case WM_NCMBUTTONDBLCLK: 277 case WM_MOUSEWHEEL: 278 msg.hwnd = m_popup; 279 break; 280 281 // These mouse messages use client coordinates so we need to convert them. 282 case WM_MOUSEMOVE: 283 case WM_LBUTTONDOWN: 284 case WM_LBUTTONUP: 285 case WM_LBUTTONDBLCLK: 286 case WM_RBUTTONDOWN: 287 case WM_RBUTTONUP: 288 case WM_RBUTTONDBLCLK: 289 case WM_MBUTTONDOWN: 290 case WM_MBUTTONUP: 291 case WM_MBUTTONDBLCLK: { 292 // Translate the coordinate. 293 translatePoint(msg.lParam, msg.hwnd, m_popup); 294 msg.hwnd = m_popup; 295 break; 296 } 297 298 // Steal all keyboard messages. 299 case WM_KEYDOWN: 300 case WM_KEYUP: 301 case WM_CHAR: 302 case WM_DEADCHAR: 303 case WM_SYSKEYUP: 304 case WM_SYSCHAR: 305 case WM_SYSDEADCHAR: 306 msg.hwnd = m_popup; 307 break; 308 } 309 310 ::TranslateMessage(&msg); 311 ::DispatchMessage(&msg); 312 313 if (!m_showPopup) 314 break; 315 activeWindow = ::GetActiveWindow(); 316 if (activeWindow != hostWindow && !::IsChild(activeWindow, hostWindow)) 317 break; 318 if (::GetCapture() != hostWindow) 319 break; 320 } 321 322 if (::GetCapture() == hostWindow) 323 ::ReleaseCapture(); 324 325 m_showPopup = false; 326 ::ShowWindow(m_popup, SW_HIDE); 327 328 if (!m_client) 329 return; 330 331 m_client->valueChangedForPopupMenu(this, m_newSelectedIndex); 332 333 // <https://bugs.webkit.org/show_bug.cgi?id=57904> In order to properly call the onClick() 334 // handler on a <select> element, we need to fake a mouse up event in the main window. 335 // The main window already received the mouse down, which showed this popup, but upon 336 // selection of an item the mouse up gets eaten by the popup menu. So we take the mouse down 337 // event, change the message type to a mouse up event, and post that in the message queue. 338 // Thus, we are virtually clicking at the 339 // same location where the mouse down event occurred. This allows the hit test to select 340 // the correct element, and thereby call the onClick() JS handler. 341 if (!m_client->currentlyProcessedMouseDownEvent()) 342 return; 343 344 const MSG* initiatingWinEvent = m_client->currentlyProcessedMouseDownEvent()->nativeEvent(); 345 MSG fakeEvent = *initiatingWinEvent; 346 fakeEvent.message = WM_LBUTTONUP; 347 ::PostMessage(fakeEvent.hwnd, fakeEvent.message, fakeEvent.wParam, fakeEvent.lParam); 348} 349 350void WebPopupMenuProxyWin::hidePopupMenu() 351{ 352 if (!m_showPopup) 353 return; 354 m_showPopup = false; 355 356 ::ShowWindow(m_popup, SW_HIDE); 357 358 // Post a WM_NULL message to wake up the message pump if necessary. 359 ::PostMessage(m_popup, WM_NULL, 0, 0); 360} 361 362void WebPopupMenuProxyWin::calculatePositionAndSize(const IntRect& rect) 363{ 364 // Convert the rect (which is in view cooridates) into screen coordinates. 365 IntRect rectInScreenCoords = rect; 366 POINT location(rectInScreenCoords .location()); 367 if (!::ClientToScreen(m_webView->window(), &location)) 368 return; 369 rectInScreenCoords.setLocation(location); 370 371 int itemCount = m_items.size(); 372 m_itemHeight = m_data.m_itemHeight; 373 374 int naturalHeight = m_itemHeight * itemCount; 375 int popupHeight = min(maxPopupHeight, naturalHeight); 376 377 // The popup should show an integral number of items (i.e. no partial items should be visible) 378 popupHeight -= popupHeight % m_itemHeight; 379 380 // Next determine its width 381 int popupWidth = m_data.m_popupWidth; 382 383 if (naturalHeight > maxPopupHeight) { 384 // We need room for a scrollbar 385 popupWidth += ScrollbarTheme::nativeTheme()->scrollbarThickness(SmallScrollbar); 386 } 387 388 popupHeight += 2 * popupWindowBorderWidth; 389 390 // The popup should be at least as wide as the control on the page 391 popupWidth = max(rectInScreenCoords.width() - m_data.m_clientInsetLeft - m_data.m_clientInsetRight, popupWidth); 392 393 // Always left-align items in the popup. This matches popup menus on the mac. 394 int popupX = rectInScreenCoords.x() + m_data.m_clientInsetLeft; 395 396 IntRect popupRect(popupX, rectInScreenCoords.maxY(), popupWidth, popupHeight); 397 398 // The popup needs to stay within the bounds of the screen and not overlap any toolbars 399 HMONITOR monitor = ::MonitorFromWindow(m_webView->window(), MONITOR_DEFAULTTOPRIMARY); 400 MONITORINFOEX monitorInfo; 401 monitorInfo.cbSize = sizeof(MONITORINFOEX); 402 ::GetMonitorInfo(monitor, &monitorInfo); 403 FloatRect screen = monitorInfo.rcWork; 404 405 // Check that we don't go off the screen vertically 406 if (popupRect.maxY() > screen.height()) { 407 // The popup will go off the screen, so try placing it above the client 408 if (rectInScreenCoords.y() - popupRect.height() < 0) { 409 // The popup won't fit above, either, so place it whereever's bigger and resize it to fit 410 if ((rectInScreenCoords.y() + rectInScreenCoords.height() / 2) < (screen.height() / 2)) { 411 // Below is bigger 412 popupRect.setHeight(screen.height() - popupRect.y()); 413 } else { 414 // Above is bigger 415 popupRect.setY(0); 416 popupRect.setHeight(rectInScreenCoords.y()); 417 } 418 } else { 419 // The popup fits above, so reposition it 420 popupRect.setY(rectInScreenCoords.y() - popupRect.height()); 421 } 422 } 423 424 // Check that we don't go off the screen horizontally 425 if (popupRect.x() < screen.x()) { 426 popupRect.setWidth(popupRect.width() - (screen.x() - popupRect.x())); 427 popupRect.setX(screen.x()); 428 } 429 430 m_windowRect = popupRect; 431} 432 433IntRect WebPopupMenuProxyWin::clientRect() const 434{ 435 IntRect clientRect = m_windowRect; 436 clientRect.inflate(-popupWindowBorderWidth); 437 clientRect.setLocation(IntPoint(0, 0)); 438 return clientRect; 439} 440 441void WebPopupMenuProxyWin::invalidateItem(int index) 442{ 443 if (!m_popup) 444 return; 445 446 IntRect damageRect(clientRect()); 447 damageRect.setY(m_itemHeight * (index - m_scrollOffset)); 448 damageRect.setHeight(m_itemHeight); 449 if (m_scrollbar) 450 damageRect.setWidth(damageRect.width() - m_scrollbar->frameRect().width()); 451 452 RECT r = damageRect; 453 ::InvalidateRect(m_popup, &r, TRUE); 454} 455 456int WebPopupMenuProxyWin::scrollSize(ScrollbarOrientation orientation) const 457{ 458 return ((orientation == VerticalScrollbar) && m_scrollbar) ? (m_scrollbar->totalSize() - m_scrollbar->visibleSize()) : 0; 459} 460 461int WebPopupMenuProxyWin::scrollPosition(Scrollbar*) const 462{ 463 return m_scrollOffset; 464} 465 466void WebPopupMenuProxyWin::setScrollOffset(const IntPoint& offset) 467{ 468 scrollTo(offset.y()); 469} 470 471void WebPopupMenuProxyWin::scrollTo(int offset) 472{ 473 ASSERT(m_scrollbar); 474 475 if (!m_popup) 476 return; 477 478 if (m_scrollOffset == offset) 479 return; 480 481 int scrolledLines = m_scrollOffset - offset; 482 m_scrollOffset = offset; 483 484 UINT flags = SW_INVALIDATE; 485 486#ifdef CAN_SET_SMOOTH_SCROLLING_DURATION 487 BOOL shouldSmoothScroll = FALSE; 488 ::SystemParametersInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, &shouldSmoothScroll, 0); 489 if (shouldSmoothScroll) 490 flags |= MAKEWORD(SW_SMOOTHSCROLL, smoothScrollAnimationDuration); 491#endif 492 493 IntRect listRect = clientRect(); 494 if (m_scrollbar) 495 listRect.setWidth(listRect.width() - m_scrollbar->frameRect().width()); 496 RECT r = listRect; 497 ::ScrollWindowEx(m_popup, 0, scrolledLines * m_itemHeight, &r, 0, 0, 0, flags); 498 if (m_scrollbar) { 499 r = m_scrollbar->frameRect(); 500 ::InvalidateRect(m_popup, &r, TRUE); 501 } 502 ::UpdateWindow(m_popup); 503} 504 505void WebPopupMenuProxyWin::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect) 506{ 507 IntRect scrollRect = rect; 508 scrollRect.move(scrollbar->x(), scrollbar->y()); 509 RECT r = scrollRect; 510 ::InvalidateRect(m_popup, &r, false); 511} 512 513// Message pump messages. 514 515LRESULT WebPopupMenuProxyWin::onMouseActivate(HWND hWnd, UINT message, WPARAM, LPARAM, bool& handled) 516{ 517 handled = true; 518 return MA_NOACTIVATE; 519} 520 521LRESULT WebPopupMenuProxyWin::onSize(HWND hWnd, UINT message, WPARAM, LPARAM lParam, bool& handled) 522{ 523 handled = true; 524 if (!scrollbar()) 525 return 0; 526 527 IntSize size(LOWORD(lParam), HIWORD(lParam)); 528 scrollbar()->setFrameRect(IntRect(size.width() - scrollbar()->width(), 0, scrollbar()->width(), size.height())); 529 530 int visibleItems = this->visibleItems(); 531 scrollbar()->setEnabled(visibleItems < m_items.size()); 532 scrollbar()->setSteps(1, max(1, visibleItems - 1)); 533 scrollbar()->setProportion(visibleItems, m_items.size()); 534 return 0; 535} 536 537LRESULT WebPopupMenuProxyWin::onKeyDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled) 538{ 539 handled = true; 540 541 LRESULT lResult = 0; 542 switch (LOWORD(wParam)) { 543 case VK_DOWN: 544 case VK_RIGHT: 545 down(); 546 break; 547 case VK_UP: 548 case VK_LEFT: 549 up(); 550 break; 551 case VK_HOME: 552 focusFirst(); 553 break; 554 case VK_END: 555 focusLast(); 556 break; 557 case VK_PRIOR: 558 if (focusedIndex() != scrollOffset()) { 559 // Set the selection to the first visible item 560 int firstVisibleItem = scrollOffset(); 561 up(focusedIndex() - firstVisibleItem); 562 } else { 563 // The first visible item is selected, so move the selection back one page 564 up(visibleItems()); 565 } 566 break; 567 case VK_NEXT: { 568 int lastVisibleItem = scrollOffset() + visibleItems() - 1; 569 if (focusedIndex() != lastVisibleItem) { 570 // Set the selection to the last visible item 571 down(lastVisibleItem - focusedIndex()); 572 } else { 573 // The last visible item is selected, so move the selection forward one page 574 down(visibleItems()); 575 } 576 break; 577 } 578 case VK_TAB: 579 ::SendMessage(m_webView->window(), message, wParam, lParam); 580 hide(); 581 break; 582 case VK_ESCAPE: 583 hide(); 584 break; 585 default: 586 if (isASCIIPrintable(wParam)) { 587 // Send the keydown to the WebView so it can be used for type-to-select. 588 // Since we know that the virtual key is ASCII printable, it's OK to convert this to 589 // a WM_CHAR message. (We don't want to call TranslateMessage because that will post a 590 // WM_CHAR message that will be stolen and redirected to the popup HWND. 591 ::PostMessage(m_popup, WM_HOST_WINDOW_CHAR, wParam, lParam); 592 } else 593 lResult = 1; 594 break; 595 } 596 597 return lResult; 598} 599 600LRESULT WebPopupMenuProxyWin::onChar(HWND hWnd, UINT message, WPARAM wParam, LPARAM, bool& handled) 601{ 602 handled = true; 603 604 LRESULT lResult = 0; 605 int index; 606 switch (wParam) { 607 case 0x0D: // Enter/Return 608 hide(); 609 index = focusedIndex(); 610 ASSERT(index >= 0); 611 // FIXME: Do we need to send back the index right away? 612 m_newSelectedIndex = index; 613 break; 614 case 0x1B: // Escape 615 hide(); 616 break; 617 case 0x09: // TAB 618 case 0x08: // Backspace 619 case 0x0A: // Linefeed 620 default: // Character 621 lResult = 1; 622 break; 623 } 624 625 return lResult; 626} 627 628LRESULT WebPopupMenuProxyWin::onMouseMove(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled) 629{ 630 handled = true; 631 632 IntPoint mousePoint(MAKEPOINTS(lParam)); 633 if (scrollbar()) { 634 IntRect scrollBarRect = scrollbar()->frameRect(); 635 if (scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) { 636 // Put the point into coordinates relative to the scroll bar 637 mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y()); 638 PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y())); 639 scrollbar()->mouseMoved(event); 640 return 0; 641 } 642 } 643 644 BOOL shouldHotTrack = FALSE; 645 ::SystemParametersInfo(SPI_GETHOTTRACKING, 0, &shouldHotTrack, 0); 646 647 RECT bounds; 648 ::GetClientRect(m_popup, &bounds); 649 if (!::PtInRect(&bounds, mousePoint) && !(wParam & MK_LBUTTON)) { 650 // When the mouse is not inside the popup menu and the left button isn't down, just 651 // repost the message to the web view. 652 653 // Translate the coordinate. 654 translatePoint(lParam, m_popup, m_webView->window()); 655 656 ::PostMessage(m_popup, WM_HOST_WINDOW_MOUSEMOVE, wParam, lParam); 657 return 0; 658 } 659 660 if ((shouldHotTrack || wParam & MK_LBUTTON) && ::PtInRect(&bounds, mousePoint)) 661 setFocusedIndex(listIndexAtPoint(mousePoint), true); 662 663 return 0; 664} 665 666LRESULT WebPopupMenuProxyWin::onLButtonDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled) 667{ 668 handled = true; 669 670 IntPoint mousePoint(MAKEPOINTS(lParam)); 671 if (scrollbar()) { 672 IntRect scrollBarRect = scrollbar()->frameRect(); 673 if (scrollBarRect.contains(mousePoint)) { 674 // Put the point into coordinates relative to the scroll bar 675 mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y()); 676 PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y())); 677 scrollbar()->mouseDown(event); 678 setScrollbarCapturingMouse(true); 679 return 0; 680 } 681 } 682 683 // If the mouse is inside the window, update the focused index. Otherwise, 684 // hide the popup. 685 RECT bounds; 686 ::GetClientRect(m_popup, &bounds); 687 if (::PtInRect(&bounds, mousePoint)) 688 setFocusedIndex(listIndexAtPoint(mousePoint), true); 689 else 690 hide(); 691 692 return 0; 693} 694 695 696LRESULT WebPopupMenuProxyWin::onLButtonUp(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled) 697{ 698 handled = true; 699 700 IntPoint mousePoint(MAKEPOINTS(lParam)); 701 if (scrollbar()) { 702 IntRect scrollBarRect = scrollbar()->frameRect(); 703 if (scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) { 704 setScrollbarCapturingMouse(false); 705 // Put the point into coordinates relative to the scroll bar 706 mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y()); 707 PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y())); 708 scrollbar()->mouseUp(); 709 // FIXME: This is a hack to work around Scrollbar not invalidating correctly when it doesn't have a parent widget 710 RECT r = scrollBarRect; 711 ::InvalidateRect(m_popup, &r, TRUE); 712 return 0; 713 } 714 } 715 // Only hide the popup if the mouse is inside the popup window. 716 RECT bounds; 717 ::GetClientRect(m_popup, &bounds); 718 if (::PtInRect(&bounds, mousePoint)) { 719 hide(); 720 int index = focusedIndex(); 721 if (index >= 0) { 722 // FIXME: Do we need to send back the index right away? 723 m_newSelectedIndex = index; 724 } 725 } 726 727 return 0; 728} 729 730LRESULT WebPopupMenuProxyWin::onMouseWheel(HWND hWnd, UINT message, WPARAM wParam, LPARAM, bool& handled) 731{ 732 handled = true; 733 734 if (!scrollbar()) 735 return 0; 736 737 int i = 0; 738 for (incrementWheelDelta(GET_WHEEL_DELTA_WPARAM(wParam)); abs(wheelDelta()) >= WHEEL_DELTA; reduceWheelDelta(WHEEL_DELTA)) { 739 if (wheelDelta() > 0) 740 ++i; 741 else 742 --i; 743 } 744 745 ScrollableArea::scroll(i > 0 ? ScrollUp : ScrollDown, ScrollByLine, abs(i)); 746 return 0; 747} 748 749LRESULT WebPopupMenuProxyWin::onPaint(HWND hWnd, UINT message, WPARAM, LPARAM, bool& handled) 750{ 751 handled = true; 752 753 PAINTSTRUCT paintStruct; 754 ::BeginPaint(m_popup, &paintStruct); 755 paint(paintStruct.rcPaint, paintStruct.hdc); 756 ::EndPaint(m_popup, &paintStruct); 757 758 return 0; 759} 760 761LRESULT WebPopupMenuProxyWin::onPrintClient(HWND hWnd, UINT, WPARAM wParam, LPARAM, bool& handled) 762{ 763 handled = true; 764 765 HDC hdc = reinterpret_cast<HDC>(wParam); 766 paint(clientRect(), hdc); 767 768 return 0; 769} 770 771bool WebPopupMenuProxyWin::down(unsigned lines) 772{ 773 int size = m_items.size(); 774 775 int lastSelectableIndex, selectedListIndex; 776 lastSelectableIndex = selectedListIndex = focusedIndex(); 777 for (int i = selectedListIndex + 1; i >= 0 && i < size; ++i) { 778 if (m_items[i].m_isEnabled) { 779 lastSelectableIndex = i; 780 if (i >= selectedListIndex + (int)lines) 781 break; 782 } 783 } 784 785 return setFocusedIndex(lastSelectableIndex); 786} 787 788bool WebPopupMenuProxyWin::up(unsigned lines) 789{ 790 int size = m_items.size(); 791 792 int lastSelectableIndex, selectedListIndex; 793 lastSelectableIndex = selectedListIndex = focusedIndex(); 794 for (int i = selectedListIndex - 1; i >= 0 && i < size; --i) { 795 if (m_items[i].m_isEnabled) { 796 lastSelectableIndex = i; 797 if (i <= selectedListIndex - (int)lines) 798 break; 799 } 800 } 801 802 return setFocusedIndex(lastSelectableIndex); 803} 804 805void WebPopupMenuProxyWin::paint(const IntRect& damageRect, HDC hdc) 806{ 807 if (!m_popup) 808 return; 809 810 if (!m_DC) { 811 m_DC = ::CreateCompatibleDC(::GetDC(m_popup)); 812 if (!m_DC) 813 return; 814 } 815 816 if (m_bmp) { 817 bool keepBitmap = false; 818 BITMAP bitmap; 819 if (::GetObject(m_bmp, sizeof(bitmap), &bitmap)) 820 keepBitmap = bitmap.bmWidth == clientRect().width() && bitmap.bmHeight == clientRect().height(); 821 if (!keepBitmap) { 822 ::DeleteObject(m_bmp); 823 m_bmp = 0; 824 } 825 } 826 827 if (!m_bmp) { 828 BitmapInfo bitmapInfo = BitmapInfo::createBottomUp(clientRect().size()); 829 void* pixels = 0; 830 m_bmp = ::CreateDIBSection(m_DC, &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0); 831 if (!m_bmp) 832 return; 833 ::SelectObject(m_DC, m_bmp); 834 } 835 836 GraphicsContext context(m_DC); 837 838 IntRect translatedDamageRect = damageRect; 839 translatedDamageRect.move(IntSize(0, m_scrollOffset * m_itemHeight)); 840 m_data.m_notSelectedBackingStore->paint(context, damageRect.location(), translatedDamageRect); 841 842 IntRect selectedIndexRectInBackingStore(0, focusedIndex() * m_itemHeight, m_data.m_selectedBackingStore->size().width(), m_itemHeight); 843 IntPoint selectedIndexDstPoint = selectedIndexRectInBackingStore.location(); 844 selectedIndexDstPoint.move(0, -m_scrollOffset * m_itemHeight); 845 846 m_data.m_selectedBackingStore->paint(context, selectedIndexDstPoint, selectedIndexRectInBackingStore); 847 848 if (m_scrollbar) 849 m_scrollbar->paint(&context, damageRect); 850 851 HDC localDC = hdc ? hdc : ::GetDC(m_popup); 852 853 ::BitBlt(localDC, damageRect.x(), damageRect.y(), damageRect.width(), damageRect.height(), m_DC, damageRect.x(), damageRect.y(), SRCCOPY); 854 855 if (!hdc) 856 ::ReleaseDC(m_popup, localDC); 857} 858 859bool WebPopupMenuProxyWin::setFocusedIndex(int i, bool hotTracking) 860{ 861 if (i < 0 || i >= m_items.size() || i == focusedIndex()) 862 return false; 863 864 if (!m_items[i].m_isEnabled) 865 return false; 866 867 invalidateItem(focusedIndex()); 868 invalidateItem(i); 869 870 m_focusedIndex = i; 871 872 if (!hotTracking) { 873 if (m_client) 874 m_client->setTextFromItemForPopupMenu(this, i); 875 } 876 877 if (!scrollToRevealSelection()) 878 ::UpdateWindow(m_popup); 879 880 return true; 881} 882 883int WebPopupMenuProxyWin::visibleItems() const 884{ 885 return clientRect().height() / m_itemHeight; 886} 887 888int WebPopupMenuProxyWin::listIndexAtPoint(const IntPoint& point) const 889{ 890 return m_scrollOffset + point.y() / m_itemHeight; 891} 892 893int WebPopupMenuProxyWin::focusedIndex() const 894{ 895 return m_focusedIndex; 896} 897 898void WebPopupMenuProxyWin::focusFirst() 899{ 900 int size = m_items.size(); 901 902 for (int i = 0; i < size; ++i) { 903 if (m_items[i].m_isEnabled) { 904 setFocusedIndex(i); 905 break; 906 } 907 } 908} 909 910void WebPopupMenuProxyWin::focusLast() 911{ 912 int size = m_items.size(); 913 914 for (int i = size - 1; i > 0; --i) { 915 if (m_items[i].m_isEnabled) { 916 setFocusedIndex(i); 917 break; 918 } 919 } 920} 921 922 923void WebPopupMenuProxyWin::incrementWheelDelta(int delta) 924{ 925 m_wheelDelta += delta; 926} 927 928void WebPopupMenuProxyWin::reduceWheelDelta(int delta) 929{ 930 ASSERT(delta >= 0); 931 ASSERT(delta <= abs(m_wheelDelta)); 932 933 if (m_wheelDelta > 0) 934 m_wheelDelta -= delta; 935 else if (m_wheelDelta < 0) 936 m_wheelDelta += delta; 937 else 938 return; 939} 940 941bool WebPopupMenuProxyWin::scrollToRevealSelection() 942{ 943 if (!m_scrollbar) 944 return false; 945 946 int index = focusedIndex(); 947 948 if (index < m_scrollOffset) { 949 ScrollableArea::scrollToYOffsetWithoutAnimation(index); 950 return true; 951 } 952 953 if (index >= m_scrollOffset + visibleItems()) { 954 ScrollableArea::scrollToYOffsetWithoutAnimation(index - visibleItems() + 1); 955 return true; 956 } 957 958 return false; 959} 960 961} // namespace WebKit 962