PopupMenuWin.cpp   [plain text]


/*
 * Copyright (C) 2006, 2007 Apple Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "config.h"
#include "PopupMenu.h"

#include "Document.h"
#include "FloatRect.h"
#include "FontData.h"
#include "Frame.h"
#include "FrameView.h"
#include "GraphicsContext.h"
#include "HTMLNames.h"
#include "Page.h"
#include "PlatformMouseEvent.h"
#include "PlatformScreen.h"
#include "PlatformScrollBar.h"
#include "RenderTheme.h"
#include "RenderView.h"
#include "TextStyle.h"
#include <tchar.h>
#include <windows.h>

using std::min;

namespace WebCore {

using namespace HTMLNames;

// Default Window animation duration in milliseconds
static const int defaultAnimationDuration = 200;
// Maximum height of a popup window
static const int maxPopupHeight = 320;

static const int popupWindowAlphaPercent = 95;

const int optionSpacingMiddle = 1;
const int popupWindowBorderWidth = 1;

static LPCTSTR kPopupWindowClassName = _T("PopupWindowClass");
static ATOM registerPopup();
static LRESULT CALLBACK PopupWndProc(HWND, UINT, WPARAM, LPARAM);

PopupMenu::PopupMenu(PopupMenuClient* client)
    : m_popupClient(client)
    , m_scrollBar(0)
    , m_popup(0)
    , m_DC(0)
    , m_bmp(0)
    , m_wasClicked(false)
    , m_itemHeight(0)
    , m_scrollOffset(0)
    , m_wheelDelta(0)
    , m_focusedIndex(0)
    , m_scrollbarCapturingMouse(false)
{
}

PopupMenu::~PopupMenu()
{
    if (m_bmp)
        ::DeleteObject(m_bmp);
    if (m_DC)
        ::DeleteObject(m_DC);
    if (m_popup)
        ::DestroyWindow(m_popup);
}

void PopupMenu::show(const IntRect& r, FrameView* v, int index)
{
    calculatePositionAndSize(r, v);
    if (clientRect().isEmpty())
        return;

    if (!m_popup) {
        registerPopup();

        DWORD exStyle = WS_EX_LAYERED | WS_EX_LTRREADING;

        // Even though we already know our size and location at this point, we pass (0,0,0,0) as our size/location here.
        // We need to wait until after the call to ::SetWindowLongPtr to set our size so that in our WM_SIZE handler we can get access to the PopupMenu object
        m_popup = ::CreateWindowEx(exStyle, kPopupWindowClassName, _T("PopupMenu"),
            WS_POPUP | WS_BORDER,
            0, 0, 0, 0,
            v->containingWindow(), 0, 0, 0);

        if (!m_popup)
            return;

        ::SetWindowLongPtr(m_popup, 0, (LONG_PTR)this);
        ::SetLayeredWindowAttributes(m_popup, 0, (255 * popupWindowAlphaPercent) / 100, LWA_ALPHA);
    }

    if (!m_scrollBar)
        if (visibleItems() < client()->listSize()) {
            // We need a scroll bar
            m_scrollBar = new PlatformScrollbar(this, VerticalScrollbar, SmallScrollbar);
            m_scrollBar->setContainingWindow(m_popup);
        }

    ::SetWindowPos(m_popup, HWND_TOP, m_windowRect.x(), m_windowRect.y(), m_windowRect.width(), m_windowRect.height(), 0);

    // Determine whether we should animate our popups
    // Note: Must use 'BOOL' and 'FALSE' instead of 'bool' and 'false' to avoid stack corruption with SystemParametersInfo
    BOOL shouldAnimate = FALSE;
#ifdef CAN_ANIMATE_TRANSPARENT_WINDOWS_SMOOTHLY
    ::SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, &shouldAnimate, 0);
#endif

    if (shouldAnimate) {
        RECT viewRect = {0};
        ::GetWindowRect(v->containingWindow(), &viewRect);

        if (!::IsRectEmpty(&viewRect)) {
            // Popups should slide into view away from the <select> box
            // NOTE: This may have to change for Vista
            DWORD slideDirection = (m_windowRect.y() < viewRect.top + v->contentsToWindow(r.location()).y()) ? AW_VER_NEGATIVE : AW_VER_POSITIVE;

            ::AnimateWindow(m_popup, defaultAnimationDuration, AW_SLIDE | slideDirection | AW_ACTIVATE);
        }
    } else
        ::ShowWindow(m_popup, SW_SHOWNORMAL);
    ::SetCapture(m_popup);

    if (client()) {
        int index = client()->selectedIndex();
        if (index >= 0)
            setFocusedIndex(index);
    }
}

void PopupMenu::hide()
{
    ::ShowWindow(m_popup, SW_HIDE);
}

const int endOfLinePadding = 2;
void PopupMenu::calculatePositionAndSize(const IntRect& r, FrameView* v)
{
    // r is in absolute document coordinates, but we want to be in screen coordinates

    // First, move to WebView coordinates
    IntRect rScreenCoords(v->contentsToWindow(r.location()), r.size());

    // Then, translate to screen coordinates
    POINT location(rScreenCoords.location());
    if (!::ClientToScreen(v->containingWindow(), &location))
        return;

    rScreenCoords.setLocation(location);

    // First, determine the popup's height
    int itemCount = client()->listSize();
    m_itemHeight = client()->clientStyle()->font().height() + optionSpacingMiddle;
    int naturalHeight = m_itemHeight * itemCount;
    int popupHeight = min(maxPopupHeight, naturalHeight);
    // The popup should show an integral number of items (i.e. no partial items should be visible)
    popupHeight -= popupHeight % m_itemHeight;
    
    // Next determine its width
    int popupWidth = 0;
    for (int i = 0; i < itemCount; ++i) {
        String text = client()->itemText(i);
        if (text.isEmpty())
            continue;

        popupWidth = max(popupWidth, client()->clientStyle()->font().width(TextRun(text.characters(), text.length())));
    }

    if (naturalHeight > maxPopupHeight)
        // We need room for a scrollbar
        popupWidth += PlatformScrollbar::verticalScrollbarWidth(SmallScrollbar);

    // Add padding to align the popup text with the <select> text
    // Note: We can't add paddingRight() because that value includes the width
    // of the dropdown button, so we must use our own endOfLinePadding constant.
    popupWidth += endOfLinePadding + client()->clientPaddingLeft();

    // Leave room for the border
    popupWidth += 2 * popupWindowBorderWidth;
    popupHeight += 2 * popupWindowBorderWidth;

    // The popup should be at least as wide as the control on the page
    popupWidth = max(rScreenCoords.width(), popupWidth);

    // Always left-align items in the popup.  This matches popup menus on the mac.
    int popupX = rScreenCoords.x();

    IntRect popupRect(popupX, rScreenCoords.bottom(), popupWidth, popupHeight);

    // The popup needs to stay within the bounds of the screen and not overlap any toolbars
    FloatRect screen = screenAvailableRect(v);

    // Check that we don't go off the screen vertically
    if (popupRect.bottom() > screen.height()) {
        // The popup will go off the screen, so try placing it above the client
        if (rScreenCoords.y() - popupRect.height() < 0) {
            // The popup won't fit above, either, so place it whereever's bigger and resize it to fit
            if ((rScreenCoords.y() + rScreenCoords.height() / 2) < (screen.height() / 2)) {
                // Below is bigger
                popupRect.setHeight(screen.height() - popupRect.y());
            } else {
                // Above is bigger
                popupRect.setY(0);
                popupRect.setHeight(rScreenCoords.y());
            }
        } else {
            // The popup fits above, so reposition it
            popupRect.setY(rScreenCoords.y() - popupRect.height());
        }
    }

    // Check that we don't go off the screen horizontally
    if (popupRect.x() < screen.x()) {
        popupRect.setWidth(popupRect.width() - (screen.x() - popupRect.x()));
        popupRect.setX(screen.x());
    }
    m_windowRect = popupRect;
    return;
}

bool PopupMenu::setFocusedIndex(int i, bool hotTracking)
{
    if (i < 0 || i >= client()->listSize() || i == focusedIndex())
        return false;

    if (!client()->itemIsEnabled(i))
        return false;

    invalidateItem(focusedIndex());
    invalidateItem(i);

    m_focusedIndex = i;

    if (!hotTracking)
        client()->setTextFromItem(i);

    if (!scrollToRevealSelection())
        ::UpdateWindow(m_popup);

    return true;
}

int PopupMenu::visibleItems() const
{
    return clientRect().height() / m_itemHeight;
}

int PopupMenu::listIndexAtPoint(const IntPoint& point) const
{
    return m_scrollOffset + point.y() / m_itemHeight;
}

int PopupMenu::focusedIndex() const
{
    return m_focusedIndex;
}

void PopupMenu::focusFirst()
{
    if (!client())
        return;

    int size = client()->listSize();

    for (int i = 0; i < size; ++i)
        if (client()->itemIsEnabled(i)) {
            setFocusedIndex(i);
            break;
        }
}

void PopupMenu::focusLast()
{
    if (!client())
        return;

    int size = client()->listSize();

    for (int i = size - 1; i > 0; --i)
        if (client()->itemIsEnabled(i)) {
            setFocusedIndex(i);
            break;
        }
}

bool PopupMenu::down(unsigned lines)
{
    if (!client())
        return false;

    int size = client()->listSize();

    int lastSelectableIndex, selectedListIndex;
    lastSelectableIndex = selectedListIndex = focusedIndex();
    for (int i = selectedListIndex + 1; i >= 0 && i < size; ++i)
        if (client()->itemIsEnabled(i)) {
            lastSelectableIndex = i;
            if (i >= selectedListIndex + (int)lines)
                break;
        }

    return setFocusedIndex(lastSelectableIndex);
}

bool PopupMenu::up(unsigned lines)
{
    if (!client())
        return false;

    int size = client()->listSize();

    int lastSelectableIndex, selectedListIndex;
    lastSelectableIndex = selectedListIndex = focusedIndex();
    for (int i = selectedListIndex - 1; i >= 0 && i < size; --i)
        if (client()->itemIsEnabled(i)) {
            lastSelectableIndex = i;
            if (i <= selectedListIndex - (int)lines)
                break;
        }

    return setFocusedIndex(lastSelectableIndex);
}

void PopupMenu::invalidateItem(int index)
{
    if (!m_popup)
        return;

    IntRect damageRect(clientRect());
    damageRect.setY(m_itemHeight * (index - m_scrollOffset));
    damageRect.setHeight(m_itemHeight);
    if (m_scrollBar)
        damageRect.setWidth(damageRect.width() - m_scrollBar->frameGeometry().width());

    RECT r = damageRect;
    ::InvalidateRect(m_popup, &r, TRUE);
}

IntRect PopupMenu::clientRect() const
{
    IntRect clientRect = m_windowRect;
    clientRect.inflate(-popupWindowBorderWidth);
    clientRect.setLocation(IntPoint(0, 0));
    return clientRect;
}

void PopupMenu::incrementWheelDelta(int delta)
{
    m_wheelDelta += delta;
}

void PopupMenu::reduceWheelDelta(int delta)
{
    ASSERT(delta >= 0);
    ASSERT(delta <= abs(m_wheelDelta));

    if (m_wheelDelta > 0)
        m_wheelDelta -= delta;
    else if (m_wheelDelta < 0)
        m_wheelDelta += delta;
    else
        return;
}

bool PopupMenu::scrollToRevealSelection()
{
    if (!m_scrollBar)
        return false;

    int index = focusedIndex();

    if (index < m_scrollOffset) {
        m_scrollBar->setValue(index);
        return true;
    }

    if (index >= m_scrollOffset + visibleItems()) {
        m_scrollBar->setValue(index - visibleItems() + 1);
        return true;
    }

    return false;
}

void PopupMenu::updateFromElement()
{
    if (!m_popup)
        return;

    ::InvalidateRect(m_popup, 0, TRUE);
    if (!scrollToRevealSelection())
        ::UpdateWindow(m_popup);
}

bool PopupMenu::itemWritingDirectionIsNatural() 
{ 
    return true; 
}

const int separatorPadding = 4;
const int separatorHeight = 1;
void PopupMenu::paint(const IntRect& damageRect, HDC hdc)
{
    if (!m_popup)
        return;

    if (!m_DC) {
        m_DC = ::CreateCompatibleDC(::GetDC(m_popup));
        if (!m_DC)
            return;
    }

    if (m_bmp) {
        bool keepBitmap = false;
        BITMAP bitmap;
        if (GetObject(m_bmp, sizeof(bitmap), &bitmap))
            keepBitmap = bitmap.bmWidth == clientRect().width()
                && bitmap.bmHeight == clientRect().height();
        if (!keepBitmap) {
            DeleteObject(m_bmp);
            m_bmp = 0;
        }
    }
    if (!m_bmp) {
        BITMAPINFO bitmapInfo;
        bitmapInfo.bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
        bitmapInfo.bmiHeader.biWidth         = clientRect().width(); 
        bitmapInfo.bmiHeader.biHeight        = -clientRect().height();
        bitmapInfo.bmiHeader.biPlanes        = 1;
        bitmapInfo.bmiHeader.biBitCount      = 32;
        bitmapInfo.bmiHeader.biCompression   = BI_RGB;
        bitmapInfo.bmiHeader.biSizeImage     = 0;
        bitmapInfo.bmiHeader.biXPelsPerMeter = 0;
        bitmapInfo.bmiHeader.biYPelsPerMeter = 0;
        bitmapInfo.bmiHeader.biClrUsed       = 0;
        bitmapInfo.bmiHeader.biClrImportant  = 0;

        void* pixels = 0;
        m_bmp = ::CreateDIBSection(m_DC, &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0);
        if (!m_bmp)
            return;

        ::SelectObject(m_DC, m_bmp);
    }

    GraphicsContext context(m_DC);

    int itemCount = client()->listSize();

    // listRect is the damageRect translated into the coordinates of the entire menu list (which is itemCount * m_itemHeight pixels tall)
    IntRect listRect = damageRect;
    listRect.move(IntSize(0, m_scrollOffset * m_itemHeight));

    RenderStyle* clientStyle = client()->clientStyle();

    for (int y = listRect.y(); y < listRect.bottom(); y += m_itemHeight) {
        int index = y / m_itemHeight;

        Color optionBackgroundColor, optionTextColor;
        RenderStyle* itemStyle = client()->itemStyle(index);
        if (index == focusedIndex()) {
            optionBackgroundColor = theme()->activeListBoxSelectionBackgroundColor();
            optionTextColor = theme()->activeListBoxSelectionForegroundColor();
        } else {
            optionBackgroundColor = client()->itemBackgroundColor(index);
            optionTextColor = itemStyle->color();
        }

        // itemRect is in client coordinates
        IntRect itemRect(0, (index - m_scrollOffset) * m_itemHeight, damageRect.width(), m_itemHeight);

        // Draw the background for this menu item
        if (itemStyle->visibility() != HIDDEN)
            context.fillRect(itemRect, optionBackgroundColor);

        if (client()->itemIsSeparator(index)) {
            IntRect separatorRect(itemRect.x() + separatorPadding, itemRect.y() + (itemRect.height() - separatorHeight) / 2, itemRect.width() - 2 * separatorPadding, separatorHeight);
            context.fillRect(separatorRect, optionTextColor);
            continue;
        }

        String itemText = client()->itemText(index);
            
        unsigned length = itemText.length();
        const UChar* string = itemText.characters();
        TextStyle textStyle(0, 0, 0, itemText.defaultWritingDirection() == WTF::Unicode::RightToLeft);
        TextRun textRun(string, length);

        context.setFillColor(optionTextColor);
        
        Font itemFont = client()->clientStyle()->font();
        if (client()->itemIsLabel(index)) {
            FontDescription d = itemFont.fontDescription();
            d.setBold(true);
            itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
            itemFont.update();
        }
        context.setFont(itemFont);
        
        // Draw the item text
        if (itemStyle->visibility() != HIDDEN) {
            int textX = client()->clientPaddingLeft();
            int textY = itemRect.y() + itemFont.ascent() + (itemRect.height() - itemFont.height()) / 2;
            context.drawBidiText(textRun, IntPoint(textX, textY), textStyle);
        }
    }

    if (m_scrollBar)
        m_scrollBar->paint(&context, damageRect);

    if (!hdc)
        hdc = ::GetDC(m_popup);

    ::BitBlt(hdc, damageRect.x(), damageRect.y(), damageRect.width(), damageRect.height(), m_DC, damageRect.x(), damageRect.y(), SRCCOPY);
}

void PopupMenu::valueChanged(Scrollbar* scrollBar)
{
    ASSERT(m_scrollBar);

    if (!m_popup)
        return;

    int offset = scrollBar->value();

    if (m_scrollOffset == offset)
        return;

    int scrolledLines = m_scrollOffset - offset;
    m_scrollOffset = offset;

    UINT flags = SW_INVALIDATE;

#ifdef CAN_SET_SMOOTH_SCROLLING_DURATION
    BOOL shouldSmoothScroll = FALSE;
    ::SystemParametersInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, &shouldSmoothScroll, 0);
    if (shouldSmoothScroll)
        flags |= MAKEWORD(SW_SMOOTHSCROLL, smoothScrollAnimationDuration);
#endif

    IntRect listRect = clientRect();
    if (m_scrollBar)
        listRect.setWidth(listRect.width() - m_scrollBar->frameGeometry().width());
    RECT r = listRect;
    ::ScrollWindowEx(m_popup, 0, scrolledLines * m_itemHeight, &r, 0, 0, 0, flags);
    if (m_scrollBar) {
        r = m_scrollBar->frameGeometry();
        ::InvalidateRect(m_popup, &r, TRUE);
    }
    ::UpdateWindow(m_popup);
}

IntRect PopupMenu::windowClipRect() const
{
    return m_windowRect;
}

static ATOM registerPopup()
{
    static bool haveRegisteredWindowClass = false;

    if (haveRegisteredWindowClass)
        return true;

    WNDCLASSEX wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = 0;
    wcex.lpfnWndProc    = PopupWndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = sizeof(PopupMenu*); // For the PopupMenu pointer
    wcex.hInstance      = Page::instanceHandle();
    wcex.hIcon          = 0;
    wcex.hCursor        = LoadCursor(0, IDC_ARROW);
    wcex.hbrBackground  = 0;
    wcex.lpszMenuName   = 0;
    wcex.lpszClassName  = kPopupWindowClassName;
    wcex.hIconSm        = 0;

    haveRegisteredWindowClass = true;

    return ::RegisterClassEx(&wcex);
}

const int smoothScrollAnimationDuration = 5000;
static LRESULT CALLBACK PopupWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    LRESULT lResult = 0;
    LONG_PTR longPtr = GetWindowLongPtr(hWnd, 0);
    PopupMenu* popup = reinterpret_cast<PopupMenu*>(longPtr);

    switch (message) {
        case WM_SIZE:
            if (popup && popup->scrollBar()) {
                IntSize size(LOWORD(lParam), HIWORD(lParam));
                popup->scrollBar()->setRect(IntRect(size.width() - popup->scrollBar()->width(), 0, popup->scrollBar()->width(), size.height()));

                int visibleItems = popup->visibleItems();
                popup->scrollBar()->setEnabled(visibleItems < popup->client()->listSize());
                popup->scrollBar()->setSteps(1, max(1, visibleItems - 1));
                popup->scrollBar()->setProportion(visibleItems, popup->client()->listSize());
            }
            break;
        case WM_ACTIVATE:
            if (popup && popup->client() && wParam == WA_INACTIVE)
                popup->client()->hidePopup();
            break;
        case WM_KILLFOCUS:
            if (popup && popup->client() && (HWND)wParam != popup->popupHandle())
                // Focus is going elsewhere, so hide
                popup->client()->hidePopup();
            break;
        case WM_KEYDOWN:
            if (popup && popup->client()) {
                lResult = 0;
                switch (LOWORD(wParam)) {
                    case VK_DOWN:
                    case VK_RIGHT:
                        popup->down();
                        break;
                    case VK_UP:
                    case VK_LEFT:
                        popup->up();
                        break;
                    case VK_HOME:
                        popup->focusFirst();
                        break;
                    case VK_END:
                        popup->focusLast();
                        break;
                    case VK_PRIOR:
                        if (popup->focusedIndex() != popup->scrollOffset()) {
                            // Set the selection to the first visible item
                            int firstVisibleItem = popup->scrollOffset();
                            popup->up(popup->focusedIndex() - firstVisibleItem);
                        } else
                            // The first visible item is selected, so move the selection back one page
                            popup->up(popup->visibleItems());
                        break;
                    case VK_NEXT:
                        if (popup) {
                            int lastVisibleItem = popup->scrollOffset() + popup->visibleItems() - 1;
                            if (popup->focusedIndex() != lastVisibleItem) {
                                // Set the selection to the last visible item
                                popup->down(lastVisibleItem - popup->focusedIndex());
                            } else
                                // The last visible item is selected, so move the selection forward one page
                                popup->down(popup->visibleItems());
                        }
                        break;
                    case VK_TAB:
                        ::SendMessage(popup->client()->clientDocument()->view()->containingWindow(), message, wParam, lParam);
                        popup->client()->hidePopup();
                        break;
                    default:
                        if (isprint(::MapVirtualKey(LOWORD(wParam), 2)))
                            // Send the keydown to the WebView so it can be used for type-ahead find
                            ::SendMessage(popup->client()->clientDocument()->view()->containingWindow(), message, wParam, lParam);
                        else
                            lResult = 1;
                        break;
                }
            }
            break;
        case WM_CHAR:
            if (popup && popup->client()) {
                lResult = 0;
                int index;
                switch (wParam) {
                    case 0x0D:   // Enter/Return
                        popup->client()->hidePopup();
                        index = popup->focusedIndex();
                        ASSERT(index >= 0);
                        popup->client()->valueChanged(index);
                        break;
                    case 0x1B:   // Escape
                        popup->client()->hidePopup();
                        break;
                    case 0x09:   // TAB
                    case 0x08:   // Backspace
                    case 0x0A:   // Linefeed
                    default:     // Character
                        lResult = 1;
                        break;
                }
            }
            break;
        case WM_MOUSEMOVE:
            if (popup) {
                IntPoint mousePoint(MAKEPOINTS(lParam));
                if (popup->scrollBar()) {
                    IntRect scrollBarRect = popup->scrollBar()->frameGeometry();
                    if (popup->scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) {
                        // Put the point into coordinates relative to the scroll bar
                        mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y());
                        PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y()));
                        popup->scrollBar()->handleMouseMoveEvent(event);
                        break;
                    }
                }

                BOOL shouldHotTrack = FALSE;
                ::SystemParametersInfo(SPI_GETHOTTRACKING, 0, &shouldHotTrack, 0);

                RECT bounds;
                GetClientRect(popup->popupHandle(), &bounds);
                if ((shouldHotTrack || wParam & MK_LBUTTON) && ::PtInRect(&bounds, mousePoint))
                    popup->setFocusedIndex(popup->listIndexAtPoint(mousePoint), true);

                // Release capture if the left button isn't down, and the mousePoint is outside the popup window.
                // This way, the WebView will get future mouse events in the rest of the window.
                if (!(wParam & MK_LBUTTON) && !::PtInRect(&bounds, mousePoint)) {
                    ::ReleaseCapture();
                    break;
                }
            }
            break;
        case WM_LBUTTONDOWN:
            if (popup) {
                ::SetCapture(popup->popupHandle());
                IntPoint mousePoint(MAKEPOINTS(lParam));
                if (popup->scrollBar()) {
                    IntRect scrollBarRect = popup->scrollBar()->frameGeometry();
                    if (scrollBarRect.contains(mousePoint)) {
                        // Put the point into coordinates relative to the scroll bar
                        mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y());
                        PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y()));
                        popup->scrollBar()->handleMousePressEvent(event);
                        popup->setScrollbarCapturingMouse(true);
                        break;
                    }
                }

                popup->setFocusedIndex(popup->listIndexAtPoint(mousePoint), true);
            }
            break;
        case WM_LBUTTONUP:
            if (popup) {
                IntPoint mousePoint(MAKEPOINTS(lParam));
                if (popup->scrollBar()) {
                    ::ReleaseCapture();
                    IntRect scrollBarRect = popup->scrollBar()->frameGeometry();
                    if (popup->scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) {
                        popup->setScrollbarCapturingMouse(false);
                        // Put the point into coordinates relative to the scroll bar
                        mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y());
                        PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y()));
                        popup->scrollBar()->handleMouseReleaseEvent(event);
                        // FIXME: This is a hack to work around PlatformScrollbar not invalidating correctly when it doesn't have a parent widget
                        RECT r = scrollBarRect;
                        ::InvalidateRect(popup->popupHandle(), &r, TRUE);
                        break;
                    }
                }
                // Only release capture and hide the popup if the mouse is inside the popup window.
                RECT bounds;
                GetClientRect(popup->popupHandle(), &bounds);
                if (popup->client() && ::PtInRect(&bounds, mousePoint)) {
                    ::ReleaseCapture();
                    popup->client()->hidePopup();
                    int index = popup->focusedIndex();
                    if (index >= 0)
                        popup->client()->valueChanged(index);
                }
            }
            break;
        case WM_MOUSEWHEEL:
            if (popup && popup->scrollBar()) {
                int i = 0;
                for (popup->incrementWheelDelta(GET_WHEEL_DELTA_WPARAM(wParam)); abs(popup->wheelDelta()) >= WHEEL_DELTA; popup->reduceWheelDelta(WHEEL_DELTA))
                    if (popup->wheelDelta() > 0)
                        ++i;
                    else
                        --i;

                popup->scrollBar()->scroll(i > 0 ? ScrollUp : ScrollDown, ScrollByLine, abs(i));
            }
            break;
        case WM_PAINT:
            if (popup) {
                PAINTSTRUCT paintInfo;
                ::BeginPaint(popup->popupHandle(), &paintInfo);
                popup->paint(paintInfo.rcPaint, paintInfo.hdc);
                ::EndPaint(popup->popupHandle(), &paintInfo);
                lResult = 0;
            }
            break;
        case WM_PRINTCLIENT:
            if (popup)
                popup->paint(popup->clientRect(), (HDC)wParam);
            break;
        default:
            lResult = DefWindowProc(hWnd, message, wParam, lParam);
    }

    return lResult;
}

}