Home | History | Annotate | Download | only in win
      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 
     40 using namespace WebCore;
     41 using namespace std;
     42 
     43 namespace WebKit {
     44 
     45 static const LPCWSTR kWebKit2WebPopupMenuProxyWindowClassName = L"WebKit2WebPopupMenuProxyWindowClass";
     46 
     47 static const int defaultAnimationDuration = 200;
     48 static const int maxPopupHeight = 320;
     49 static const int popupWindowBorderWidth = 1;
     50 static const int separatorPadding = 4;
     51 static 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.
     56 static const UINT WM_HOST_WINDOW_FIRST = WM_USER;
     57 static const UINT WM_HOST_WINDOW_CHAR = WM_USER + WM_CHAR;
     58 static const UINT WM_HOST_WINDOW_MOUSEMOVE = WM_USER + WM_MOUSEMOVE;
     59 
     60 static inline bool isASCIIPrintable(unsigned c)
     61 {
     62     return c >= 0x20 && c <= 0x7E;
     63 }
     64 
     65 static 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 
     74 LRESULT 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 
     92 LRESULT 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 
    139 bool 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 
    163 WebPopupMenuProxyWin::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 
    180 WebPopupMenuProxyWin::~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 
    192 void 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 
    350 void 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 
    362 void 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 
    433 IntRect WebPopupMenuProxyWin::clientRect() const
    434 {
    435     IntRect clientRect = m_windowRect;
    436     clientRect.inflate(-popupWindowBorderWidth);
    437     clientRect.setLocation(IntPoint(0, 0));
    438     return clientRect;
    439 }
    440 
    441 void 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 
    456 int WebPopupMenuProxyWin::scrollSize(ScrollbarOrientation orientation) const
    457 {
    458     return ((orientation == VerticalScrollbar) && m_scrollbar) ? (m_scrollbar->totalSize() - m_scrollbar->visibleSize()) : 0;
    459 }
    460 
    461 int WebPopupMenuProxyWin::scrollPosition(Scrollbar*) const
    462 {
    463     return m_scrollOffset;
    464 }
    465 
    466 void WebPopupMenuProxyWin::setScrollOffset(const IntPoint& offset)
    467 {
    468     scrollTo(offset.y());
    469 }
    470 
    471 void 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 
    505 void 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 
    515 LRESULT WebPopupMenuProxyWin::onMouseActivate(HWND hWnd, UINT message, WPARAM, LPARAM, bool& handled)
    516 {
    517     handled = true;
    518     return MA_NOACTIVATE;
    519 }
    520 
    521 LRESULT 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 
    537 LRESULT 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 
    600 LRESULT 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 
    628 LRESULT 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 
    666 LRESULT 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 
    696 LRESULT 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 
    730 LRESULT 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 
    749 LRESULT 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 
    761 LRESULT 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 
    771 bool 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 
    788 bool 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 
    805 void 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 
    859 bool 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 
    883 int WebPopupMenuProxyWin::visibleItems() const
    884 {
    885     return clientRect().height() / m_itemHeight;
    886 }
    887 
    888 int WebPopupMenuProxyWin::listIndexAtPoint(const IntPoint& point) const
    889 {
    890     return m_scrollOffset + point.y() / m_itemHeight;
    891 }
    892 
    893 int WebPopupMenuProxyWin::focusedIndex() const
    894 {
    895     return m_focusedIndex;
    896 }
    897 
    898 void 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 
    910 void 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 
    923 void WebPopupMenuProxyWin::incrementWheelDelta(int delta)
    924 {
    925     m_wheelDelta += delta;
    926 }
    927 
    928 void 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 
    941 bool 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