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