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