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