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