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