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