• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  *
19  */
20 
21 #include "config.h"
22 #include "PopupMenu.h"
23 
24 #include "Document.h"
25 #include "FloatRect.h"
26 #include "FontSelector.h"
27 #include "Frame.h"
28 #include "FrameView.h"
29 #include "GraphicsContext.h"
30 #include "HTMLNames.h"
31 #include "Page.h"
32 #include "PlatformMouseEvent.h"
33 #include "PlatformScreen.h"
34 #include "RenderTheme.h"
35 #include "RenderView.h"
36 #include "Scrollbar.h"
37 #include "ScrollbarTheme.h"
38 #include "SimpleFontData.h"
39 #include <tchar.h>
40 #include <windows.h>
41 
42 using std::min;
43 
44 namespace WebCore {
45 
46 using namespace HTMLNames;
47 
48 // Default Window animation duration in milliseconds
49 static const int defaultAnimationDuration = 200;
50 // Maximum height of a popup window
51 static const int maxPopupHeight = 320;
52 
53 const int optionSpacingMiddle = 1;
54 const int popupWindowBorderWidth = 1;
55 
56 static LPCTSTR kPopupWindowClassName = _T("PopupWindowClass");
57 static ATOM registerPopup();
58 static LRESULT CALLBACK PopupWndProc(HWND, UINT, WPARAM, LPARAM);
59 
60 // FIXME: Remove this as soon as practical.
isASCIIPrintable(unsigned c)61 static inline bool isASCIIPrintable(unsigned c)
62 {
63     return c >= 0x20 && c <= 0x7E;
64 }
65 
PopupMenu(PopupMenuClient * client)66 PopupMenu::PopupMenu(PopupMenuClient* client)
67     : m_popupClient(client)
68     , m_scrollbar(0)
69     , m_popup(0)
70     , m_DC(0)
71     , m_bmp(0)
72     , m_wasClicked(false)
73     , m_itemHeight(0)
74     , m_scrollOffset(0)
75     , m_wheelDelta(0)
76     , m_focusedIndex(0)
77     , m_scrollbarCapturingMouse(false)
78 {
79 }
80 
~PopupMenu()81 PopupMenu::~PopupMenu()
82 {
83     if (m_bmp)
84         ::DeleteObject(m_bmp);
85     if (m_DC)
86         ::DeleteObject(m_DC);
87     if (m_popup)
88         ::DestroyWindow(m_popup);
89 }
90 
show(const IntRect & r,FrameView * v,int index)91 void PopupMenu::show(const IntRect& r, FrameView* v, int index)
92 {
93     calculatePositionAndSize(r, v);
94     if (clientRect().isEmpty())
95         return;
96 
97     if (!m_popup) {
98         registerPopup();
99 
100         DWORD exStyle = WS_EX_LTRREADING;
101 
102         // Even though we already know our size and location at this point, we pass (0,0,0,0) as our size/location here.
103         // We need to wait until after the call to ::SetWindowLongPtr to set our size so that in our WM_SIZE handler we can get access to the PopupMenu object
104         m_popup = ::CreateWindowEx(exStyle, kPopupWindowClassName, _T("PopupMenu"),
105             WS_POPUP | WS_BORDER,
106             0, 0, 0, 0,
107             v->hostWindow()->platformWindow(), 0, 0, 0);
108 
109         if (!m_popup)
110             return;
111 
112         ::SetWindowLongPtr(m_popup, 0, (LONG_PTR)this);
113     }
114 
115     if (!m_scrollbar)
116         if (visibleItems() < client()->listSize()) {
117             // We need a scroll bar
118             m_scrollbar = client()->createScrollbar(this, VerticalScrollbar, SmallScrollbar);
119         }
120 
121     ::SetWindowPos(m_popup, HWND_TOP, m_windowRect.x(), m_windowRect.y(), m_windowRect.width(), m_windowRect.height(), 0);
122 
123     // Determine whether we should animate our popups
124     // Note: Must use 'BOOL' and 'FALSE' instead of 'bool' and 'false' to avoid stack corruption with SystemParametersInfo
125     BOOL shouldAnimate = FALSE;
126 #ifdef CAN_ANIMATE_TRANSPARENT_WINDOWS_SMOOTHLY
127     ::SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, &shouldAnimate, 0);
128 #endif
129 
130     if (shouldAnimate) {
131         RECT viewRect = {0};
132         ::GetWindowRect(v->hostWindow()->platformWindow(), &viewRect);
133 
134         if (!::IsRectEmpty(&viewRect)) {
135             // Popups should slide into view away from the <select> box
136             // NOTE: This may have to change for Vista
137             DWORD slideDirection = (m_windowRect.y() < viewRect.top + v->contentsToWindow(r.location()).y()) ? AW_VER_NEGATIVE : AW_VER_POSITIVE;
138 
139             ::AnimateWindow(m_popup, defaultAnimationDuration, AW_SLIDE | slideDirection | AW_ACTIVATE);
140         }
141     } else
142         ::ShowWindow(m_popup, SW_SHOWNORMAL);
143     ::SetCapture(m_popup);
144 
145     if (client()) {
146         int index = client()->selectedIndex();
147         if (index >= 0)
148             setFocusedIndex(index);
149     }
150 }
151 
hide()152 void PopupMenu::hide()
153 {
154     ::ShowWindow(m_popup, SW_HIDE);
155 }
156 
157 const int endOfLinePadding = 2;
calculatePositionAndSize(const IntRect & r,FrameView * v)158 void PopupMenu::calculatePositionAndSize(const IntRect& r, FrameView* v)
159 {
160     // r is in absolute document coordinates, but we want to be in screen coordinates
161 
162     // First, move to WebView coordinates
163     IntRect rScreenCoords(v->contentsToWindow(r.location()), r.size());
164 
165     // Then, translate to screen coordinates
166     POINT location(rScreenCoords.location());
167     if (!::ClientToScreen(v->hostWindow()->platformWindow(), &location))
168         return;
169 
170     rScreenCoords.setLocation(location);
171 
172     // First, determine the popup's height
173     int itemCount = client()->listSize();
174     m_itemHeight = client()->menuStyle().font().height() + optionSpacingMiddle;
175     int naturalHeight = m_itemHeight * itemCount;
176     int popupHeight = min(maxPopupHeight, naturalHeight);
177     // The popup should show an integral number of items (i.e. no partial items should be visible)
178     popupHeight -= popupHeight % m_itemHeight;
179 
180     // Next determine its width
181     int popupWidth = 0;
182     for (int i = 0; i < itemCount; ++i) {
183         String text = client()->itemText(i);
184         if (text.isEmpty())
185             continue;
186 
187         Font itemFont = client()->menuStyle().font();
188         if (client()->itemIsLabel(i)) {
189             FontDescription d = itemFont.fontDescription();
190             d.setWeight(d.bolderWeight());
191             itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
192             itemFont.update(m_popupClient->fontSelector());
193         }
194 
195         popupWidth = max(popupWidth, itemFont.width(TextRun(text.characters(), text.length())));
196     }
197 
198     if (naturalHeight > maxPopupHeight)
199         // We need room for a scrollbar
200         popupWidth += ScrollbarTheme::nativeTheme()->scrollbarThickness(SmallScrollbar);
201 
202     // Add padding to align the popup text with the <select> text
203     // Note: We can't add paddingRight() because that value includes the width
204     // of the dropdown button, so we must use our own endOfLinePadding constant.
205     popupWidth += max(0, endOfLinePadding - client()->clientInsetRight()) + max(0, client()->clientPaddingLeft() - client()->clientInsetLeft());
206 
207     // Leave room for the border
208     popupWidth += 2 * popupWindowBorderWidth;
209     popupHeight += 2 * popupWindowBorderWidth;
210 
211     // The popup should be at least as wide as the control on the page
212     popupWidth = max(rScreenCoords.width() - client()->clientInsetLeft() - client()->clientInsetRight(), popupWidth);
213 
214     // Always left-align items in the popup.  This matches popup menus on the mac.
215     int popupX = rScreenCoords.x() + client()->clientInsetLeft();
216 
217     IntRect popupRect(popupX, rScreenCoords.bottom(), popupWidth, popupHeight);
218 
219     // The popup needs to stay within the bounds of the screen and not overlap any toolbars
220     FloatRect screen = screenAvailableRect(v);
221 
222     // Check that we don't go off the screen vertically
223     if (popupRect.bottom() > screen.height()) {
224         // The popup will go off the screen, so try placing it above the client
225         if (rScreenCoords.y() - popupRect.height() < 0) {
226             // The popup won't fit above, either, so place it whereever's bigger and resize it to fit
227             if ((rScreenCoords.y() + rScreenCoords.height() / 2) < (screen.height() / 2)) {
228                 // Below is bigger
229                 popupRect.setHeight(screen.height() - popupRect.y());
230             } else {
231                 // Above is bigger
232                 popupRect.setY(0);
233                 popupRect.setHeight(rScreenCoords.y());
234             }
235         } else {
236             // The popup fits above, so reposition it
237             popupRect.setY(rScreenCoords.y() - popupRect.height());
238         }
239     }
240 
241     // Check that we don't go off the screen horizontally
242     if (popupRect.x() < screen.x()) {
243         popupRect.setWidth(popupRect.width() - (screen.x() - popupRect.x()));
244         popupRect.setX(screen.x());
245     }
246     m_windowRect = popupRect;
247     return;
248 }
249 
setFocusedIndex(int i,bool hotTracking)250 bool PopupMenu::setFocusedIndex(int i, bool hotTracking)
251 {
252     if (i < 0 || i >= client()->listSize() || i == focusedIndex())
253         return false;
254 
255     if (!client()->itemIsEnabled(i))
256         return false;
257 
258     invalidateItem(focusedIndex());
259     invalidateItem(i);
260 
261     m_focusedIndex = i;
262 
263     if (!hotTracking)
264         client()->setTextFromItem(i);
265 
266     if (!scrollToRevealSelection())
267         ::UpdateWindow(m_popup);
268 
269     return true;
270 }
271 
visibleItems() const272 int PopupMenu::visibleItems() const
273 {
274     return clientRect().height() / m_itemHeight;
275 }
276 
listIndexAtPoint(const IntPoint & point) const277 int PopupMenu::listIndexAtPoint(const IntPoint& point) const
278 {
279     return m_scrollOffset + point.y() / m_itemHeight;
280 }
281 
focusedIndex() const282 int PopupMenu::focusedIndex() const
283 {
284     return m_focusedIndex;
285 }
286 
focusFirst()287 void PopupMenu::focusFirst()
288 {
289     if (!client())
290         return;
291 
292     int size = client()->listSize();
293 
294     for (int i = 0; i < size; ++i)
295         if (client()->itemIsEnabled(i)) {
296             setFocusedIndex(i);
297             break;
298         }
299 }
300 
focusLast()301 void PopupMenu::focusLast()
302 {
303     if (!client())
304         return;
305 
306     int size = client()->listSize();
307 
308     for (int i = size - 1; i > 0; --i)
309         if (client()->itemIsEnabled(i)) {
310             setFocusedIndex(i);
311             break;
312         }
313 }
314 
down(unsigned lines)315 bool PopupMenu::down(unsigned lines)
316 {
317     if (!client())
318         return false;
319 
320     int size = client()->listSize();
321 
322     int lastSelectableIndex, selectedListIndex;
323     lastSelectableIndex = selectedListIndex = focusedIndex();
324     for (int i = selectedListIndex + 1; i >= 0 && i < size; ++i)
325         if (client()->itemIsEnabled(i)) {
326             lastSelectableIndex = i;
327             if (i >= selectedListIndex + (int)lines)
328                 break;
329         }
330 
331     return setFocusedIndex(lastSelectableIndex);
332 }
333 
up(unsigned lines)334 bool PopupMenu::up(unsigned lines)
335 {
336     if (!client())
337         return false;
338 
339     int size = client()->listSize();
340 
341     int lastSelectableIndex, selectedListIndex;
342     lastSelectableIndex = selectedListIndex = focusedIndex();
343     for (int i = selectedListIndex - 1; i >= 0 && i < size; --i)
344         if (client()->itemIsEnabled(i)) {
345             lastSelectableIndex = i;
346             if (i <= selectedListIndex - (int)lines)
347                 break;
348         }
349 
350     return setFocusedIndex(lastSelectableIndex);
351 }
352 
invalidateItem(int index)353 void PopupMenu::invalidateItem(int index)
354 {
355     if (!m_popup)
356         return;
357 
358     IntRect damageRect(clientRect());
359     damageRect.setY(m_itemHeight * (index - m_scrollOffset));
360     damageRect.setHeight(m_itemHeight);
361     if (m_scrollbar)
362         damageRect.setWidth(damageRect.width() - m_scrollbar->frameRect().width());
363 
364     RECT r = damageRect;
365     ::InvalidateRect(m_popup, &r, TRUE);
366 }
367 
clientRect() const368 IntRect PopupMenu::clientRect() const
369 {
370     IntRect clientRect = m_windowRect;
371     clientRect.inflate(-popupWindowBorderWidth);
372     clientRect.setLocation(IntPoint(0, 0));
373     return clientRect;
374 }
375 
incrementWheelDelta(int delta)376 void PopupMenu::incrementWheelDelta(int delta)
377 {
378     m_wheelDelta += delta;
379 }
380 
reduceWheelDelta(int delta)381 void PopupMenu::reduceWheelDelta(int delta)
382 {
383     ASSERT(delta >= 0);
384     ASSERT(delta <= abs(m_wheelDelta));
385 
386     if (m_wheelDelta > 0)
387         m_wheelDelta -= delta;
388     else if (m_wheelDelta < 0)
389         m_wheelDelta += delta;
390     else
391         return;
392 }
393 
scrollToRevealSelection()394 bool PopupMenu::scrollToRevealSelection()
395 {
396     if (!m_scrollbar)
397         return false;
398 
399     int index = focusedIndex();
400 
401     if (index < m_scrollOffset) {
402         m_scrollbar->setValue(index);
403         return true;
404     }
405 
406     if (index >= m_scrollOffset + visibleItems()) {
407         m_scrollbar->setValue(index - visibleItems() + 1);
408         return true;
409     }
410 
411     return false;
412 }
413 
updateFromElement()414 void PopupMenu::updateFromElement()
415 {
416     if (!m_popup)
417         return;
418 
419     m_focusedIndex = client()->selectedIndex();
420 
421     ::InvalidateRect(m_popup, 0, TRUE);
422     if (!scrollToRevealSelection())
423         ::UpdateWindow(m_popup);
424 }
425 
itemWritingDirectionIsNatural()426 bool PopupMenu::itemWritingDirectionIsNatural()
427 {
428     return true;
429 }
430 
431 const int separatorPadding = 4;
432 const int separatorHeight = 1;
paint(const IntRect & damageRect,HDC hdc)433 void PopupMenu::paint(const IntRect& damageRect, HDC hdc)
434 {
435     if (!m_popup)
436         return;
437 
438     if (!m_DC) {
439         m_DC = ::CreateCompatibleDC(::GetDC(m_popup));
440         if (!m_DC)
441             return;
442     }
443 
444     if (m_bmp) {
445         bool keepBitmap = false;
446         BITMAP bitmap;
447         if (GetObject(m_bmp, sizeof(bitmap), &bitmap))
448             keepBitmap = bitmap.bmWidth == clientRect().width()
449                 && bitmap.bmHeight == clientRect().height();
450         if (!keepBitmap) {
451             DeleteObject(m_bmp);
452             m_bmp = 0;
453         }
454     }
455     if (!m_bmp) {
456         BITMAPINFO bitmapInfo;
457         bitmapInfo.bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
458         bitmapInfo.bmiHeader.biWidth         = clientRect().width();
459         bitmapInfo.bmiHeader.biHeight        = -clientRect().height();
460         bitmapInfo.bmiHeader.biPlanes        = 1;
461         bitmapInfo.bmiHeader.biBitCount      = 32;
462         bitmapInfo.bmiHeader.biCompression   = BI_RGB;
463         bitmapInfo.bmiHeader.biSizeImage     = 0;
464         bitmapInfo.bmiHeader.biXPelsPerMeter = 0;
465         bitmapInfo.bmiHeader.biYPelsPerMeter = 0;
466         bitmapInfo.bmiHeader.biClrUsed       = 0;
467         bitmapInfo.bmiHeader.biClrImportant  = 0;
468 
469         void* pixels = 0;
470         m_bmp = ::CreateDIBSection(m_DC, &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0);
471         if (!m_bmp)
472             return;
473 
474         ::SelectObject(m_DC, m_bmp);
475     }
476 
477     GraphicsContext context(m_DC);
478 
479     int itemCount = client()->listSize();
480 
481     // listRect is the damageRect translated into the coordinates of the entire menu list (which is itemCount * m_itemHeight pixels tall)
482     IntRect listRect = damageRect;
483     listRect.move(IntSize(0, m_scrollOffset * m_itemHeight));
484 
485     for (int y = listRect.y(); y < listRect.bottom(); y += m_itemHeight) {
486         int index = y / m_itemHeight;
487 
488         Color optionBackgroundColor, optionTextColor;
489         PopupMenuStyle itemStyle = client()->itemStyle(index);
490         if (index == focusedIndex()) {
491             optionBackgroundColor = theme()->activeListBoxSelectionBackgroundColor();
492             optionTextColor = theme()->activeListBoxSelectionForegroundColor();
493         } else {
494             optionBackgroundColor = itemStyle.backgroundColor();
495             optionTextColor = itemStyle.foregroundColor();
496         }
497 
498         // itemRect is in client coordinates
499         IntRect itemRect(0, (index - m_scrollOffset) * m_itemHeight, damageRect.width(), m_itemHeight);
500 
501         // Draw the background for this menu item
502         if (itemStyle.isVisible())
503             context.fillRect(itemRect, optionBackgroundColor);
504 
505         if (client()->itemIsSeparator(index)) {
506             IntRect separatorRect(itemRect.x() + separatorPadding, itemRect.y() + (itemRect.height() - separatorHeight) / 2, itemRect.width() - 2 * separatorPadding, separatorHeight);
507             context.fillRect(separatorRect, optionTextColor);
508             continue;
509         }
510 
511         String itemText = client()->itemText(index);
512 
513         unsigned length = itemText.length();
514         const UChar* string = itemText.characters();
515         TextRun textRun(string, length, false, 0, 0, itemText.defaultWritingDirection() == WTF::Unicode::RightToLeft);
516 
517         context.setFillColor(optionTextColor);
518 
519         Font itemFont = client()->menuStyle().font();
520         if (client()->itemIsLabel(index)) {
521             FontDescription d = itemFont.fontDescription();
522             d.setWeight(d.bolderWeight());
523             itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
524             itemFont.update(m_popupClient->fontSelector());
525         }
526 
527         // Draw the item text
528         if (itemStyle.isVisible()) {
529             int textX = max(0, client()->clientPaddingLeft() - client()->clientInsetLeft());
530             int textY = itemRect.y() + itemFont.ascent() + (itemRect.height() - itemFont.height()) / 2;
531             context.drawBidiText(itemFont, textRun, IntPoint(textX, textY));
532         }
533     }
534 
535     if (m_scrollbar)
536         m_scrollbar->paint(&context, damageRect);
537 
538     if (!hdc)
539         hdc = ::GetDC(m_popup);
540 
541     ::BitBlt(hdc, damageRect.x(), damageRect.y(), damageRect.width(), damageRect.height(), m_DC, damageRect.x(), damageRect.y(), SRCCOPY);
542 }
543 
valueChanged(Scrollbar * scrollBar)544 void PopupMenu::valueChanged(Scrollbar* scrollBar)
545 {
546     ASSERT(m_scrollbar);
547 
548     if (!m_popup)
549         return;
550 
551     int offset = scrollBar->value();
552 
553     if (m_scrollOffset == offset)
554         return;
555 
556     int scrolledLines = m_scrollOffset - offset;
557     m_scrollOffset = offset;
558 
559     UINT flags = SW_INVALIDATE;
560 
561 #ifdef CAN_SET_SMOOTH_SCROLLING_DURATION
562     BOOL shouldSmoothScroll = FALSE;
563     ::SystemParametersInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, &shouldSmoothScroll, 0);
564     if (shouldSmoothScroll)
565         flags |= MAKEWORD(SW_SMOOTHSCROLL, smoothScrollAnimationDuration);
566 #endif
567 
568     IntRect listRect = clientRect();
569     if (m_scrollbar)
570         listRect.setWidth(listRect.width() - m_scrollbar->frameRect().width());
571     RECT r = listRect;
572     ::ScrollWindowEx(m_popup, 0, scrolledLines * m_itemHeight, &r, 0, 0, 0, flags);
573     if (m_scrollbar) {
574         r = m_scrollbar->frameRect();
575         ::InvalidateRect(m_popup, &r, TRUE);
576     }
577     ::UpdateWindow(m_popup);
578 }
579 
invalidateScrollbarRect(Scrollbar * scrollbar,const IntRect & rect)580 void PopupMenu::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
581 {
582     IntRect scrollRect = rect;
583     scrollRect.move(scrollbar->x(), scrollbar->y());
584     RECT r = scrollRect;
585     ::InvalidateRect(m_popup, &r, false);
586 }
587 
registerPopup()588 static ATOM registerPopup()
589 {
590     static bool haveRegisteredWindowClass = false;
591 
592     if (haveRegisteredWindowClass)
593         return true;
594 
595     WNDCLASSEX wcex;
596 
597     wcex.cbSize = sizeof(WNDCLASSEX);
598 
599     wcex.style          = 0;
600     wcex.lpfnWndProc    = PopupWndProc;
601     wcex.cbClsExtra     = 0;
602     wcex.cbWndExtra     = sizeof(PopupMenu*); // For the PopupMenu pointer
603     wcex.hInstance      = Page::instanceHandle();
604     wcex.hIcon          = 0;
605     wcex.hCursor        = LoadCursor(0, IDC_ARROW);
606     wcex.hbrBackground  = 0;
607     wcex.lpszMenuName   = 0;
608     wcex.lpszClassName  = kPopupWindowClassName;
609     wcex.hIconSm        = 0;
610 
611     haveRegisteredWindowClass = true;
612 
613     return ::RegisterClassEx(&wcex);
614 }
615 
616 const int smoothScrollAnimationDuration = 5000;
PopupWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)617 static LRESULT CALLBACK PopupWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
618 {
619     LRESULT lResult = 0;
620     LONG_PTR longPtr = GetWindowLongPtr(hWnd, 0);
621     PopupMenu* popup = reinterpret_cast<PopupMenu*>(longPtr);
622 
623     switch (message) {
624         case WM_SIZE:
625             if (popup && popup->scrollbar()) {
626                 IntSize size(LOWORD(lParam), HIWORD(lParam));
627                 popup->scrollbar()->setFrameRect(IntRect(size.width() - popup->scrollbar()->width(), 0, popup->scrollbar()->width(), size.height()));
628 
629                 int visibleItems = popup->visibleItems();
630                 popup->scrollbar()->setEnabled(visibleItems < popup->client()->listSize());
631                 popup->scrollbar()->setSteps(1, max(1, visibleItems - 1));
632                 popup->scrollbar()->setProportion(visibleItems, popup->client()->listSize());
633             }
634             break;
635         case WM_ACTIVATE:
636             if (popup && popup->client() && wParam == WA_INACTIVE)
637                 popup->client()->hidePopup();
638             break;
639         case WM_KILLFOCUS:
640             if (popup && popup->client() && (HWND)wParam != popup->popupHandle())
641                 // Focus is going elsewhere, so hide
642                 popup->client()->hidePopup();
643             break;
644         case WM_KEYDOWN:
645             if (popup && popup->client()) {
646                 lResult = 0;
647                 switch (LOWORD(wParam)) {
648                     case VK_DOWN:
649                     case VK_RIGHT:
650                         popup->down();
651                         break;
652                     case VK_UP:
653                     case VK_LEFT:
654                         popup->up();
655                         break;
656                     case VK_HOME:
657                         popup->focusFirst();
658                         break;
659                     case VK_END:
660                         popup->focusLast();
661                         break;
662                     case VK_PRIOR:
663                         if (popup->focusedIndex() != popup->scrollOffset()) {
664                             // Set the selection to the first visible item
665                             int firstVisibleItem = popup->scrollOffset();
666                             popup->up(popup->focusedIndex() - firstVisibleItem);
667                         } else
668                             // The first visible item is selected, so move the selection back one page
669                             popup->up(popup->visibleItems());
670                         break;
671                     case VK_NEXT:
672                         if (popup) {
673                             int lastVisibleItem = popup->scrollOffset() + popup->visibleItems() - 1;
674                             if (popup->focusedIndex() != lastVisibleItem) {
675                                 // Set the selection to the last visible item
676                                 popup->down(lastVisibleItem - popup->focusedIndex());
677                             } else
678                                 // The last visible item is selected, so move the selection forward one page
679                                 popup->down(popup->visibleItems());
680                         }
681                         break;
682                     case VK_TAB:
683                         ::SendMessage(popup->client()->hostWindow()->platformWindow(), message, wParam, lParam);
684                         popup->client()->hidePopup();
685                         break;
686                     default:
687                         if (isASCIIPrintable(wParam))
688                             // Send the keydown to the WebView so it can be used for type-to-select.
689                             ::PostMessage(popup->client()->hostWindow()->platformWindow(), message, wParam, lParam);
690                         else
691                             lResult = 1;
692                         break;
693                 }
694             }
695             break;
696         case WM_CHAR:
697             if (popup && popup->client()) {
698                 lResult = 0;
699                 int index;
700                 switch (wParam) {
701                     case 0x0D:   // Enter/Return
702                         popup->client()->hidePopup();
703                         index = popup->focusedIndex();
704                         ASSERT(index >= 0);
705                         popup->client()->valueChanged(index);
706                         break;
707                     case 0x1B:   // Escape
708                         popup->client()->hidePopup();
709                         break;
710                     case 0x09:   // TAB
711                     case 0x08:   // Backspace
712                     case 0x0A:   // Linefeed
713                     default:     // Character
714                         lResult = 1;
715                         break;
716                 }
717             }
718             break;
719         case WM_MOUSEMOVE:
720             if (popup) {
721                 IntPoint mousePoint(MAKEPOINTS(lParam));
722                 if (popup->scrollbar()) {
723                     IntRect scrollBarRect = popup->scrollbar()->frameRect();
724                     if (popup->scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) {
725                         // Put the point into coordinates relative to the scroll bar
726                         mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y());
727                         PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y()));
728                         popup->scrollbar()->mouseMoved(event);
729                         break;
730                     }
731                 }
732 
733                 BOOL shouldHotTrack = FALSE;
734                 ::SystemParametersInfo(SPI_GETHOTTRACKING, 0, &shouldHotTrack, 0);
735 
736                 RECT bounds;
737                 GetClientRect(popup->popupHandle(), &bounds);
738                 if ((shouldHotTrack || wParam & MK_LBUTTON) && ::PtInRect(&bounds, mousePoint))
739                     popup->setFocusedIndex(popup->listIndexAtPoint(mousePoint), true);
740 
741                 // Release capture if the left button isn't down, and the mousePoint is outside the popup window.
742                 // This way, the WebView will get future mouse events in the rest of the window.
743                 if (!(wParam & MK_LBUTTON) && !::PtInRect(&bounds, mousePoint)) {
744                     ::ReleaseCapture();
745                     break;
746                 }
747             }
748             break;
749         case WM_LBUTTONDOWN:
750             if (popup) {
751                 ::SetCapture(popup->popupHandle());
752                 IntPoint mousePoint(MAKEPOINTS(lParam));
753                 if (popup->scrollbar()) {
754                     IntRect scrollBarRect = popup->scrollbar()->frameRect();
755                     if (scrollBarRect.contains(mousePoint)) {
756                         // Put the point into coordinates relative to the scroll bar
757                         mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y());
758                         PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y()));
759                         popup->scrollbar()->mouseDown(event);
760                         popup->setScrollbarCapturingMouse(true);
761                         break;
762                     }
763                 }
764 
765                 popup->setFocusedIndex(popup->listIndexAtPoint(mousePoint), true);
766             }
767             break;
768         case WM_LBUTTONUP:
769             if (popup) {
770                 IntPoint mousePoint(MAKEPOINTS(lParam));
771                 if (popup->scrollbar()) {
772                     ::ReleaseCapture();
773                     IntRect scrollBarRect = popup->scrollbar()->frameRect();
774                     if (popup->scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) {
775                         popup->setScrollbarCapturingMouse(false);
776                         // Put the point into coordinates relative to the scroll bar
777                         mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y());
778                         PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y()));
779                         popup->scrollbar()->mouseUp();
780                         // FIXME: This is a hack to work around Scrollbar not invalidating correctly when it doesn't have a parent widget
781                         RECT r = scrollBarRect;
782                         ::InvalidateRect(popup->popupHandle(), &r, TRUE);
783                         break;
784                     }
785                 }
786                 // Only release capture and hide the popup if the mouse is inside the popup window.
787                 RECT bounds;
788                 GetClientRect(popup->popupHandle(), &bounds);
789                 if (popup->client() && ::PtInRect(&bounds, mousePoint)) {
790                     ::ReleaseCapture();
791                     popup->client()->hidePopup();
792                     int index = popup->focusedIndex();
793                     if (index >= 0)
794                         popup->client()->valueChanged(index);
795                 }
796             }
797             break;
798         case WM_MOUSEWHEEL:
799             if (popup && popup->scrollbar()) {
800                 int i = 0;
801                 for (popup->incrementWheelDelta(GET_WHEEL_DELTA_WPARAM(wParam)); abs(popup->wheelDelta()) >= WHEEL_DELTA; popup->reduceWheelDelta(WHEEL_DELTA))
802                     if (popup->wheelDelta() > 0)
803                         ++i;
804                     else
805                         --i;
806 
807                 popup->scrollbar()->scroll(i > 0 ? ScrollUp : ScrollDown, ScrollByLine, abs(i));
808             }
809             break;
810         case WM_PAINT:
811             if (popup) {
812                 PAINTSTRUCT paintInfo;
813                 ::BeginPaint(popup->popupHandle(), &paintInfo);
814                 popup->paint(paintInfo.rcPaint, paintInfo.hdc);
815                 ::EndPaint(popup->popupHandle(), &paintInfo);
816                 lResult = 0;
817             }
818             break;
819         case WM_PRINTCLIENT:
820             if (popup)
821                 popup->paint(popup->clientRect(), (HDC)wParam);
822             break;
823         default:
824             lResult = DefWindowProc(hWnd, message, wParam, lParam);
825     }
826 
827     return lResult;
828 }
829 
830 }
831