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