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