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