1 /*
2 * Copyright (C) 2010 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 // NOTE: This implementation is very similar to the implementation of popups in WebCore::PopupMenuWin.
27 // We should try and factor out the common bits and share them.
28
29 #include "config.h"
30 #include "WebPopupMenuProxyWin.h"
31
32 #include "NativeWebMouseEvent.h"
33 #include "WebView.h"
34 #include <WebCore/WebCoreInstanceHandle.h>
35 #include <WebCore/ScrollbarTheme.h>
36 #include <WebCore/BitmapInfo.h>
37 #include <WebCore/PlatformMouseEvent.h>
38 #include <windowsx.h>
39
40 using namespace WebCore;
41 using namespace std;
42
43 namespace WebKit {
44
45 static const LPCWSTR kWebKit2WebPopupMenuProxyWindowClassName = L"WebKit2WebPopupMenuProxyWindowClass";
46
47 static const int defaultAnimationDuration = 200;
48 static const int maxPopupHeight = 320;
49 static const int popupWindowBorderWidth = 1;
50 static const int separatorPadding = 4;
51 static const int separatorHeight = 1;
52
53 // This is used from within our custom message pump when we want to send a
54 // message to the web view and not have our message stolen and sent to
55 // the popup window.
56 static const UINT WM_HOST_WINDOW_FIRST = WM_USER;
57 static const UINT WM_HOST_WINDOW_CHAR = WM_USER + WM_CHAR;
58 static const UINT WM_HOST_WINDOW_MOUSEMOVE = WM_USER + WM_MOUSEMOVE;
59
isASCIIPrintable(unsigned c)60 static inline bool isASCIIPrintable(unsigned c)
61 {
62 return c >= 0x20 && c <= 0x7E;
63 }
64
translatePoint(LPARAM & lParam,HWND from,HWND to)65 static void translatePoint(LPARAM& lParam, HWND from, HWND to)
66 {
67 POINT pt;
68 pt.x = static_cast<short>(GET_X_LPARAM(lParam));
69 pt.y = static_cast<short>(GET_Y_LPARAM(lParam));
70 ::MapWindowPoints(from, to, &pt, 1);
71 lParam = MAKELPARAM(pt.x, pt.y);
72 }
73
WebPopupMenuProxyWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)74 LRESULT CALLBACK WebPopupMenuProxyWin::WebPopupMenuProxyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
75 {
76 LONG_PTR longPtr = ::GetWindowLongPtr(hWnd, 0);
77
78 if (WebPopupMenuProxyWin* popupMenuProxy = reinterpret_cast<WebPopupMenuProxyWin*>(longPtr))
79 return popupMenuProxy->wndProc(hWnd, message, wParam, lParam);
80
81 if (message == WM_CREATE) {
82 LPCREATESTRUCT createStruct = reinterpret_cast<LPCREATESTRUCT>(lParam);
83
84 // Associate the WebView with the window.
85 ::SetWindowLongPtr(hWnd, 0, (LONG_PTR)createStruct->lpCreateParams);
86 return 0;
87 }
88
89 return ::DefWindowProc(hWnd, message, wParam, lParam);
90 }
91
wndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)92 LRESULT WebPopupMenuProxyWin::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
93 {
94 LRESULT lResult = 0;
95 bool handled = true;
96
97 switch (message) {
98 case WM_MOUSEACTIVATE:
99 lResult = onMouseActivate(hWnd, message, wParam, lParam, handled);
100 break;
101 case WM_SIZE:
102 lResult = onSize(hWnd, message, wParam, lParam, handled);
103 break;
104 case WM_KEYDOWN:
105 lResult = onKeyDown(hWnd, message, wParam, lParam, handled);
106 break;
107 case WM_CHAR:
108 lResult = onChar(hWnd, message, wParam, lParam, handled);
109 break;
110 case WM_MOUSEMOVE:
111 lResult = onMouseMove(hWnd, message, wParam, lParam, handled);
112 break;
113 case WM_LBUTTONDOWN:
114 lResult = onLButtonDown(hWnd, message, wParam, lParam, handled);
115 break;
116 case WM_LBUTTONUP:
117 lResult = onLButtonUp(hWnd, message, wParam, lParam, handled);
118 break;
119 case WM_MOUSEWHEEL:
120 lResult = onMouseWheel(hWnd, message, wParam, lParam, handled);
121 break;
122 case WM_PAINT:
123 lResult = onPaint(hWnd, message, wParam, lParam, handled);
124 break;
125 case WM_PRINTCLIENT:
126 lResult = onPrintClient(hWnd, message, wParam, lParam, handled);
127 break;
128 default:
129 handled = false;
130 break;
131 }
132
133 if (!handled)
134 lResult = ::DefWindowProc(hWnd, message, wParam, lParam);
135
136 return lResult;
137 }
138
registerWindowClass()139 bool WebPopupMenuProxyWin::registerWindowClass()
140 {
141 static bool haveRegisteredWindowClass = false;
142 if (haveRegisteredWindowClass)
143 return true;
144 haveRegisteredWindowClass = true;
145
146 WNDCLASSEX wcex;
147 wcex.cbSize = sizeof(WNDCLASSEX);
148 wcex.style = CS_DROPSHADOW;
149 wcex.lpfnWndProc = WebPopupMenuProxyWin::WebPopupMenuProxyWndProc;
150 wcex.cbClsExtra = 0;
151 wcex.cbWndExtra = sizeof(WebPopupMenuProxyWin*);
152 wcex.hInstance = instanceHandle();
153 wcex.hIcon = 0;
154 wcex.hCursor = ::LoadCursor(0, IDC_ARROW);
155 wcex.hbrBackground = 0;
156 wcex.lpszMenuName = 0;
157 wcex.lpszClassName = kWebKit2WebPopupMenuProxyWindowClassName;
158 wcex.hIconSm = 0;
159
160 return !!::RegisterClassEx(&wcex);
161 }
162
WebPopupMenuProxyWin(WebView * webView,WebPopupMenuProxy::Client * client)163 WebPopupMenuProxyWin::WebPopupMenuProxyWin(WebView* webView, WebPopupMenuProxy::Client* client)
164 : WebPopupMenuProxy(client)
165 , m_webView(webView)
166 , m_newSelectedIndex(0)
167 , m_popup(0)
168 , m_DC(0)
169 , m_bmp(0)
170 , m_itemHeight(0)
171 , m_scrollOffset(0)
172 , m_wheelDelta(0)
173 , m_focusedIndex(0)
174 , m_wasClicked(false)
175 , m_scrollbarCapturingMouse(false)
176 , m_showPopup(false)
177 {
178 }
179
~WebPopupMenuProxyWin()180 WebPopupMenuProxyWin::~WebPopupMenuProxyWin()
181 {
182 if (m_bmp)
183 ::DeleteObject(m_bmp);
184 if (m_DC)
185 ::DeleteDC(m_DC);
186 if (m_popup)
187 ::DestroyWindow(m_popup);
188 if (m_scrollbar)
189 m_scrollbar->setParent(0);
190 }
191
showPopupMenu(const IntRect & rect,TextDirection,double,const Vector<WebPopupItem> & items,const PlatformPopupMenuData & data,int32_t selectedIndex)192 void WebPopupMenuProxyWin::showPopupMenu(const IntRect& rect, TextDirection, double, const Vector<WebPopupItem>& items, const PlatformPopupMenuData& data, int32_t selectedIndex)
193 {
194 m_items = items;
195 m_data = data;
196 m_newSelectedIndex = selectedIndex;
197
198 calculatePositionAndSize(rect);
199 if (clientRect().isEmpty())
200 return;
201
202 HWND hostWindow = m_webView->window();
203
204 if (!m_scrollbar && visibleItems() < m_items.size()) {
205 m_scrollbar = Scrollbar::createNativeScrollbar(this, VerticalScrollbar, SmallScrollbar);
206 m_scrollbar->styleChanged();
207 }
208
209 if (!m_popup) {
210 registerWindowClass();
211
212 DWORD exStyle = WS_EX_LTRREADING;
213
214 m_popup = ::CreateWindowEx(exStyle, kWebKit2WebPopupMenuProxyWindowClassName, TEXT("PopupMenu"),
215 WS_POPUP | WS_BORDER,
216 m_windowRect.x(), m_windowRect.y(), m_windowRect.width(), m_windowRect.height(),
217 hostWindow, 0, instanceHandle(), this);
218
219 if (!m_popup)
220 return;
221 }
222
223 BOOL shouldAnimate = FALSE;
224 ::SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, &shouldAnimate, 0);
225
226 if (shouldAnimate) {
227 RECT viewRect = {0};
228 ::GetWindowRect(hostWindow, &viewRect);
229
230 if (!::IsRectEmpty(&viewRect)) {
231 // Popups should slide into view away from the <select> box
232 // NOTE: This may have to change for Vista
233 DWORD slideDirection = (m_windowRect.y() < viewRect.top + rect.location().y()) ? AW_VER_NEGATIVE : AW_VER_POSITIVE;
234
235 ::AnimateWindow(m_popup, defaultAnimationDuration, AW_SLIDE | slideDirection);
236 }
237 } else
238 ::ShowWindow(m_popup, SW_SHOWNOACTIVATE);
239
240
241 int index = selectedIndex;
242 if (index >= 0)
243 setFocusedIndex(index);
244
245 m_showPopup = true;
246
247 // Protect the popup menu in case its owner is destroyed while we're running the message pump.
248 RefPtr<WebPopupMenuProxyWin> protect(this);
249
250 ::SetCapture(hostWindow);
251
252 MSG msg;
253 HWND activeWindow;
254
255 while (::GetMessage(&msg, 0, 0, 0)) {
256 switch (msg.message) {
257 case WM_HOST_WINDOW_MOUSEMOVE:
258 case WM_HOST_WINDOW_CHAR:
259 if (msg.hwnd == m_popup) {
260 // This message should be sent to the host window.
261 msg.hwnd = hostWindow;
262 msg.message -= WM_HOST_WINDOW_FIRST;
263 }
264 break;
265
266 // Steal mouse messages.
267 case WM_NCMOUSEMOVE:
268 case WM_NCLBUTTONDOWN:
269 case WM_NCLBUTTONUP:
270 case WM_NCLBUTTONDBLCLK:
271 case WM_NCRBUTTONDOWN:
272 case WM_NCRBUTTONUP:
273 case WM_NCRBUTTONDBLCLK:
274 case WM_NCMBUTTONDOWN:
275 case WM_NCMBUTTONUP:
276 case WM_NCMBUTTONDBLCLK:
277 case WM_MOUSEWHEEL:
278 msg.hwnd = m_popup;
279 break;
280
281 // These mouse messages use client coordinates so we need to convert them.
282 case WM_MOUSEMOVE:
283 case WM_LBUTTONDOWN:
284 case WM_LBUTTONUP:
285 case WM_LBUTTONDBLCLK:
286 case WM_RBUTTONDOWN:
287 case WM_RBUTTONUP:
288 case WM_RBUTTONDBLCLK:
289 case WM_MBUTTONDOWN:
290 case WM_MBUTTONUP:
291 case WM_MBUTTONDBLCLK: {
292 // Translate the coordinate.
293 translatePoint(msg.lParam, msg.hwnd, m_popup);
294 msg.hwnd = m_popup;
295 break;
296 }
297
298 // Steal all keyboard messages.
299 case WM_KEYDOWN:
300 case WM_KEYUP:
301 case WM_CHAR:
302 case WM_DEADCHAR:
303 case WM_SYSKEYUP:
304 case WM_SYSCHAR:
305 case WM_SYSDEADCHAR:
306 msg.hwnd = m_popup;
307 break;
308 }
309
310 ::TranslateMessage(&msg);
311 ::DispatchMessage(&msg);
312
313 if (!m_showPopup)
314 break;
315 activeWindow = ::GetActiveWindow();
316 if (activeWindow != hostWindow && !::IsChild(activeWindow, hostWindow))
317 break;
318 if (::GetCapture() != hostWindow)
319 break;
320 }
321
322 if (::GetCapture() == hostWindow)
323 ::ReleaseCapture();
324
325 m_showPopup = false;
326 ::ShowWindow(m_popup, SW_HIDE);
327
328 if (!m_client)
329 return;
330
331 m_client->valueChangedForPopupMenu(this, m_newSelectedIndex);
332
333 // <https://bugs.webkit.org/show_bug.cgi?id=57904> In order to properly call the onClick()
334 // handler on a <select> element, we need to fake a mouse up event in the main window.
335 // The main window already received the mouse down, which showed this popup, but upon
336 // selection of an item the mouse up gets eaten by the popup menu. So we take the mouse down
337 // event, change the message type to a mouse up event, and post that in the message queue.
338 // Thus, we are virtually clicking at the
339 // same location where the mouse down event occurred. This allows the hit test to select
340 // the correct element, and thereby call the onClick() JS handler.
341 if (!m_client->currentlyProcessedMouseDownEvent())
342 return;
343
344 const MSG* initiatingWinEvent = m_client->currentlyProcessedMouseDownEvent()->nativeEvent();
345 MSG fakeEvent = *initiatingWinEvent;
346 fakeEvent.message = WM_LBUTTONUP;
347 ::PostMessage(fakeEvent.hwnd, fakeEvent.message, fakeEvent.wParam, fakeEvent.lParam);
348 }
349
hidePopupMenu()350 void WebPopupMenuProxyWin::hidePopupMenu()
351 {
352 if (!m_showPopup)
353 return;
354 m_showPopup = false;
355
356 ::ShowWindow(m_popup, SW_HIDE);
357
358 // Post a WM_NULL message to wake up the message pump if necessary.
359 ::PostMessage(m_popup, WM_NULL, 0, 0);
360 }
361
calculatePositionAndSize(const IntRect & rect)362 void WebPopupMenuProxyWin::calculatePositionAndSize(const IntRect& rect)
363 {
364 // Convert the rect (which is in view cooridates) into screen coordinates.
365 IntRect rectInScreenCoords = rect;
366 POINT location(rectInScreenCoords .location());
367 if (!::ClientToScreen(m_webView->window(), &location))
368 return;
369 rectInScreenCoords.setLocation(location);
370
371 int itemCount = m_items.size();
372 m_itemHeight = m_data.m_itemHeight;
373
374 int naturalHeight = m_itemHeight * itemCount;
375 int popupHeight = min(maxPopupHeight, naturalHeight);
376
377 // The popup should show an integral number of items (i.e. no partial items should be visible)
378 popupHeight -= popupHeight % m_itemHeight;
379
380 // Next determine its width
381 int popupWidth = m_data.m_popupWidth;
382
383 if (naturalHeight > maxPopupHeight) {
384 // We need room for a scrollbar
385 popupWidth += ScrollbarTheme::nativeTheme()->scrollbarThickness(SmallScrollbar);
386 }
387
388 popupHeight += 2 * popupWindowBorderWidth;
389
390 // The popup should be at least as wide as the control on the page
391 popupWidth = max(rectInScreenCoords.width() - m_data.m_clientInsetLeft - m_data.m_clientInsetRight, popupWidth);
392
393 // Always left-align items in the popup. This matches popup menus on the mac.
394 int popupX = rectInScreenCoords.x() + m_data.m_clientInsetLeft;
395
396 IntRect popupRect(popupX, rectInScreenCoords.maxY(), popupWidth, popupHeight);
397
398 // The popup needs to stay within the bounds of the screen and not overlap any toolbars
399 HMONITOR monitor = ::MonitorFromWindow(m_webView->window(), MONITOR_DEFAULTTOPRIMARY);
400 MONITORINFOEX monitorInfo;
401 monitorInfo.cbSize = sizeof(MONITORINFOEX);
402 ::GetMonitorInfo(monitor, &monitorInfo);
403 FloatRect screen = monitorInfo.rcWork;
404
405 // Check that we don't go off the screen vertically
406 if (popupRect.maxY() > screen.height()) {
407 // The popup will go off the screen, so try placing it above the client
408 if (rectInScreenCoords.y() - popupRect.height() < 0) {
409 // The popup won't fit above, either, so place it whereever's bigger and resize it to fit
410 if ((rectInScreenCoords.y() + rectInScreenCoords.height() / 2) < (screen.height() / 2)) {
411 // Below is bigger
412 popupRect.setHeight(screen.height() - popupRect.y());
413 } else {
414 // Above is bigger
415 popupRect.setY(0);
416 popupRect.setHeight(rectInScreenCoords.y());
417 }
418 } else {
419 // The popup fits above, so reposition it
420 popupRect.setY(rectInScreenCoords.y() - popupRect.height());
421 }
422 }
423
424 // Check that we don't go off the screen horizontally
425 if (popupRect.x() < screen.x()) {
426 popupRect.setWidth(popupRect.width() - (screen.x() - popupRect.x()));
427 popupRect.setX(screen.x());
428 }
429
430 m_windowRect = popupRect;
431 }
432
clientRect() const433 IntRect WebPopupMenuProxyWin::clientRect() const
434 {
435 IntRect clientRect = m_windowRect;
436 clientRect.inflate(-popupWindowBorderWidth);
437 clientRect.setLocation(IntPoint(0, 0));
438 return clientRect;
439 }
440
invalidateItem(int index)441 void WebPopupMenuProxyWin::invalidateItem(int index)
442 {
443 if (!m_popup)
444 return;
445
446 IntRect damageRect(clientRect());
447 damageRect.setY(m_itemHeight * (index - m_scrollOffset));
448 damageRect.setHeight(m_itemHeight);
449 if (m_scrollbar)
450 damageRect.setWidth(damageRect.width() - m_scrollbar->frameRect().width());
451
452 RECT r = damageRect;
453 ::InvalidateRect(m_popup, &r, TRUE);
454 }
455
scrollSize(ScrollbarOrientation orientation) const456 int WebPopupMenuProxyWin::scrollSize(ScrollbarOrientation orientation) const
457 {
458 return ((orientation == VerticalScrollbar) && m_scrollbar) ? (m_scrollbar->totalSize() - m_scrollbar->visibleSize()) : 0;
459 }
460
scrollPosition(Scrollbar *) const461 int WebPopupMenuProxyWin::scrollPosition(Scrollbar*) const
462 {
463 return m_scrollOffset;
464 }
465
setScrollOffset(const IntPoint & offset)466 void WebPopupMenuProxyWin::setScrollOffset(const IntPoint& offset)
467 {
468 scrollTo(offset.y());
469 }
470
scrollTo(int offset)471 void WebPopupMenuProxyWin::scrollTo(int offset)
472 {
473 ASSERT(m_scrollbar);
474
475 if (!m_popup)
476 return;
477
478 if (m_scrollOffset == offset)
479 return;
480
481 int scrolledLines = m_scrollOffset - offset;
482 m_scrollOffset = offset;
483
484 UINT flags = SW_INVALIDATE;
485
486 #ifdef CAN_SET_SMOOTH_SCROLLING_DURATION
487 BOOL shouldSmoothScroll = FALSE;
488 ::SystemParametersInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, &shouldSmoothScroll, 0);
489 if (shouldSmoothScroll)
490 flags |= MAKEWORD(SW_SMOOTHSCROLL, smoothScrollAnimationDuration);
491 #endif
492
493 IntRect listRect = clientRect();
494 if (m_scrollbar)
495 listRect.setWidth(listRect.width() - m_scrollbar->frameRect().width());
496 RECT r = listRect;
497 ::ScrollWindowEx(m_popup, 0, scrolledLines * m_itemHeight, &r, 0, 0, 0, flags);
498 if (m_scrollbar) {
499 r = m_scrollbar->frameRect();
500 ::InvalidateRect(m_popup, &r, TRUE);
501 }
502 ::UpdateWindow(m_popup);
503 }
504
invalidateScrollbarRect(Scrollbar * scrollbar,const IntRect & rect)505 void WebPopupMenuProxyWin::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
506 {
507 IntRect scrollRect = rect;
508 scrollRect.move(scrollbar->x(), scrollbar->y());
509 RECT r = scrollRect;
510 ::InvalidateRect(m_popup, &r, false);
511 }
512
513 // Message pump messages.
514
onMouseActivate(HWND hWnd,UINT message,WPARAM,LPARAM,bool & handled)515 LRESULT WebPopupMenuProxyWin::onMouseActivate(HWND hWnd, UINT message, WPARAM, LPARAM, bool& handled)
516 {
517 handled = true;
518 return MA_NOACTIVATE;
519 }
520
onSize(HWND hWnd,UINT message,WPARAM,LPARAM lParam,bool & handled)521 LRESULT WebPopupMenuProxyWin::onSize(HWND hWnd, UINT message, WPARAM, LPARAM lParam, bool& handled)
522 {
523 handled = true;
524 if (!scrollbar())
525 return 0;
526
527 IntSize size(LOWORD(lParam), HIWORD(lParam));
528 scrollbar()->setFrameRect(IntRect(size.width() - scrollbar()->width(), 0, scrollbar()->width(), size.height()));
529
530 int visibleItems = this->visibleItems();
531 scrollbar()->setEnabled(visibleItems < m_items.size());
532 scrollbar()->setSteps(1, max(1, visibleItems - 1));
533 scrollbar()->setProportion(visibleItems, m_items.size());
534 return 0;
535 }
536
onKeyDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam,bool & handled)537 LRESULT WebPopupMenuProxyWin::onKeyDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
538 {
539 handled = true;
540
541 LRESULT lResult = 0;
542 switch (LOWORD(wParam)) {
543 case VK_DOWN:
544 case VK_RIGHT:
545 down();
546 break;
547 case VK_UP:
548 case VK_LEFT:
549 up();
550 break;
551 case VK_HOME:
552 focusFirst();
553 break;
554 case VK_END:
555 focusLast();
556 break;
557 case VK_PRIOR:
558 if (focusedIndex() != scrollOffset()) {
559 // Set the selection to the first visible item
560 int firstVisibleItem = scrollOffset();
561 up(focusedIndex() - firstVisibleItem);
562 } else {
563 // The first visible item is selected, so move the selection back one page
564 up(visibleItems());
565 }
566 break;
567 case VK_NEXT: {
568 int lastVisibleItem = scrollOffset() + visibleItems() - 1;
569 if (focusedIndex() != lastVisibleItem) {
570 // Set the selection to the last visible item
571 down(lastVisibleItem - focusedIndex());
572 } else {
573 // The last visible item is selected, so move the selection forward one page
574 down(visibleItems());
575 }
576 break;
577 }
578 case VK_TAB:
579 ::SendMessage(m_webView->window(), message, wParam, lParam);
580 hide();
581 break;
582 case VK_ESCAPE:
583 hide();
584 break;
585 default:
586 if (isASCIIPrintable(wParam)) {
587 // Send the keydown to the WebView so it can be used for type-to-select.
588 // Since we know that the virtual key is ASCII printable, it's OK to convert this to
589 // a WM_CHAR message. (We don't want to call TranslateMessage because that will post a
590 // WM_CHAR message that will be stolen and redirected to the popup HWND.
591 ::PostMessage(m_popup, WM_HOST_WINDOW_CHAR, wParam, lParam);
592 } else
593 lResult = 1;
594 break;
595 }
596
597 return lResult;
598 }
599
onChar(HWND hWnd,UINT message,WPARAM wParam,LPARAM,bool & handled)600 LRESULT WebPopupMenuProxyWin::onChar(HWND hWnd, UINT message, WPARAM wParam, LPARAM, bool& handled)
601 {
602 handled = true;
603
604 LRESULT lResult = 0;
605 int index;
606 switch (wParam) {
607 case 0x0D: // Enter/Return
608 hide();
609 index = focusedIndex();
610 ASSERT(index >= 0);
611 // FIXME: Do we need to send back the index right away?
612 m_newSelectedIndex = index;
613 break;
614 case 0x1B: // Escape
615 hide();
616 break;
617 case 0x09: // TAB
618 case 0x08: // Backspace
619 case 0x0A: // Linefeed
620 default: // Character
621 lResult = 1;
622 break;
623 }
624
625 return lResult;
626 }
627
onMouseMove(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam,bool & handled)628 LRESULT WebPopupMenuProxyWin::onMouseMove(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
629 {
630 handled = true;
631
632 IntPoint mousePoint(MAKEPOINTS(lParam));
633 if (scrollbar()) {
634 IntRect scrollBarRect = scrollbar()->frameRect();
635 if (scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) {
636 // Put the point into coordinates relative to the scroll bar
637 mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y());
638 PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y()));
639 scrollbar()->mouseMoved(event);
640 return 0;
641 }
642 }
643
644 BOOL shouldHotTrack = FALSE;
645 ::SystemParametersInfo(SPI_GETHOTTRACKING, 0, &shouldHotTrack, 0);
646
647 RECT bounds;
648 ::GetClientRect(m_popup, &bounds);
649 if (!::PtInRect(&bounds, mousePoint) && !(wParam & MK_LBUTTON)) {
650 // When the mouse is not inside the popup menu and the left button isn't down, just
651 // repost the message to the web view.
652
653 // Translate the coordinate.
654 translatePoint(lParam, m_popup, m_webView->window());
655
656 ::PostMessage(m_popup, WM_HOST_WINDOW_MOUSEMOVE, wParam, lParam);
657 return 0;
658 }
659
660 if ((shouldHotTrack || wParam & MK_LBUTTON) && ::PtInRect(&bounds, mousePoint))
661 setFocusedIndex(listIndexAtPoint(mousePoint), true);
662
663 return 0;
664 }
665
onLButtonDown(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam,bool & handled)666 LRESULT WebPopupMenuProxyWin::onLButtonDown(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
667 {
668 handled = true;
669
670 IntPoint mousePoint(MAKEPOINTS(lParam));
671 if (scrollbar()) {
672 IntRect scrollBarRect = scrollbar()->frameRect();
673 if (scrollBarRect.contains(mousePoint)) {
674 // Put the point into coordinates relative to the scroll bar
675 mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y());
676 PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y()));
677 scrollbar()->mouseDown(event);
678 setScrollbarCapturingMouse(true);
679 return 0;
680 }
681 }
682
683 // If the mouse is inside the window, update the focused index. Otherwise,
684 // hide the popup.
685 RECT bounds;
686 ::GetClientRect(m_popup, &bounds);
687 if (::PtInRect(&bounds, mousePoint))
688 setFocusedIndex(listIndexAtPoint(mousePoint), true);
689 else
690 hide();
691
692 return 0;
693 }
694
695
onLButtonUp(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam,bool & handled)696 LRESULT WebPopupMenuProxyWin::onLButtonUp(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, bool& handled)
697 {
698 handled = true;
699
700 IntPoint mousePoint(MAKEPOINTS(lParam));
701 if (scrollbar()) {
702 IntRect scrollBarRect = scrollbar()->frameRect();
703 if (scrollbarCapturingMouse() || scrollBarRect.contains(mousePoint)) {
704 setScrollbarCapturingMouse(false);
705 // Put the point into coordinates relative to the scroll bar
706 mousePoint.move(-scrollBarRect.x(), -scrollBarRect.y());
707 PlatformMouseEvent event(hWnd, message, wParam, MAKELPARAM(mousePoint.x(), mousePoint.y()));
708 scrollbar()->mouseUp();
709 // FIXME: This is a hack to work around Scrollbar not invalidating correctly when it doesn't have a parent widget
710 RECT r = scrollBarRect;
711 ::InvalidateRect(m_popup, &r, TRUE);
712 return 0;
713 }
714 }
715 // Only hide the popup if the mouse is inside the popup window.
716 RECT bounds;
717 ::GetClientRect(m_popup, &bounds);
718 if (::PtInRect(&bounds, mousePoint)) {
719 hide();
720 int index = focusedIndex();
721 if (index >= 0) {
722 // FIXME: Do we need to send back the index right away?
723 m_newSelectedIndex = index;
724 }
725 }
726
727 return 0;
728 }
729
onMouseWheel(HWND hWnd,UINT message,WPARAM wParam,LPARAM,bool & handled)730 LRESULT WebPopupMenuProxyWin::onMouseWheel(HWND hWnd, UINT message, WPARAM wParam, LPARAM, bool& handled)
731 {
732 handled = true;
733
734 if (!scrollbar())
735 return 0;
736
737 int i = 0;
738 for (incrementWheelDelta(GET_WHEEL_DELTA_WPARAM(wParam)); abs(wheelDelta()) >= WHEEL_DELTA; reduceWheelDelta(WHEEL_DELTA)) {
739 if (wheelDelta() > 0)
740 ++i;
741 else
742 --i;
743 }
744
745 ScrollableArea::scroll(i > 0 ? ScrollUp : ScrollDown, ScrollByLine, abs(i));
746 return 0;
747 }
748
onPaint(HWND hWnd,UINT message,WPARAM,LPARAM,bool & handled)749 LRESULT WebPopupMenuProxyWin::onPaint(HWND hWnd, UINT message, WPARAM, LPARAM, bool& handled)
750 {
751 handled = true;
752
753 PAINTSTRUCT paintStruct;
754 ::BeginPaint(m_popup, &paintStruct);
755 paint(paintStruct.rcPaint, paintStruct.hdc);
756 ::EndPaint(m_popup, &paintStruct);
757
758 return 0;
759 }
760
onPrintClient(HWND hWnd,UINT,WPARAM wParam,LPARAM,bool & handled)761 LRESULT WebPopupMenuProxyWin::onPrintClient(HWND hWnd, UINT, WPARAM wParam, LPARAM, bool& handled)
762 {
763 handled = true;
764
765 HDC hdc = reinterpret_cast<HDC>(wParam);
766 paint(clientRect(), hdc);
767
768 return 0;
769 }
770
down(unsigned lines)771 bool WebPopupMenuProxyWin::down(unsigned lines)
772 {
773 int size = m_items.size();
774
775 int lastSelectableIndex, selectedListIndex;
776 lastSelectableIndex = selectedListIndex = focusedIndex();
777 for (int i = selectedListIndex + 1; i >= 0 && i < size; ++i) {
778 if (m_items[i].m_isEnabled) {
779 lastSelectableIndex = i;
780 if (i >= selectedListIndex + (int)lines)
781 break;
782 }
783 }
784
785 return setFocusedIndex(lastSelectableIndex);
786 }
787
up(unsigned lines)788 bool WebPopupMenuProxyWin::up(unsigned lines)
789 {
790 int size = m_items.size();
791
792 int lastSelectableIndex, selectedListIndex;
793 lastSelectableIndex = selectedListIndex = focusedIndex();
794 for (int i = selectedListIndex - 1; i >= 0 && i < size; --i) {
795 if (m_items[i].m_isEnabled) {
796 lastSelectableIndex = i;
797 if (i <= selectedListIndex - (int)lines)
798 break;
799 }
800 }
801
802 return setFocusedIndex(lastSelectableIndex);
803 }
804
paint(const IntRect & damageRect,HDC hdc)805 void WebPopupMenuProxyWin::paint(const IntRect& damageRect, HDC hdc)
806 {
807 if (!m_popup)
808 return;
809
810 if (!m_DC) {
811 m_DC = ::CreateCompatibleDC(::GetDC(m_popup));
812 if (!m_DC)
813 return;
814 }
815
816 if (m_bmp) {
817 bool keepBitmap = false;
818 BITMAP bitmap;
819 if (::GetObject(m_bmp, sizeof(bitmap), &bitmap))
820 keepBitmap = bitmap.bmWidth == clientRect().width() && bitmap.bmHeight == clientRect().height();
821 if (!keepBitmap) {
822 ::DeleteObject(m_bmp);
823 m_bmp = 0;
824 }
825 }
826
827 if (!m_bmp) {
828 BitmapInfo bitmapInfo = BitmapInfo::createBottomUp(clientRect().size());
829 void* pixels = 0;
830 m_bmp = ::CreateDIBSection(m_DC, &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0);
831 if (!m_bmp)
832 return;
833 ::SelectObject(m_DC, m_bmp);
834 }
835
836 GraphicsContext context(m_DC);
837
838 IntRect translatedDamageRect = damageRect;
839 translatedDamageRect.move(IntSize(0, m_scrollOffset * m_itemHeight));
840 m_data.m_notSelectedBackingStore->paint(context, damageRect.location(), translatedDamageRect);
841
842 IntRect selectedIndexRectInBackingStore(0, focusedIndex() * m_itemHeight, m_data.m_selectedBackingStore->size().width(), m_itemHeight);
843 IntPoint selectedIndexDstPoint = selectedIndexRectInBackingStore.location();
844 selectedIndexDstPoint.move(0, -m_scrollOffset * m_itemHeight);
845
846 m_data.m_selectedBackingStore->paint(context, selectedIndexDstPoint, selectedIndexRectInBackingStore);
847
848 if (m_scrollbar)
849 m_scrollbar->paint(&context, damageRect);
850
851 HDC localDC = hdc ? hdc : ::GetDC(m_popup);
852
853 ::BitBlt(localDC, damageRect.x(), damageRect.y(), damageRect.width(), damageRect.height(), m_DC, damageRect.x(), damageRect.y(), SRCCOPY);
854
855 if (!hdc)
856 ::ReleaseDC(m_popup, localDC);
857 }
858
setFocusedIndex(int i,bool hotTracking)859 bool WebPopupMenuProxyWin::setFocusedIndex(int i, bool hotTracking)
860 {
861 if (i < 0 || i >= m_items.size() || i == focusedIndex())
862 return false;
863
864 if (!m_items[i].m_isEnabled)
865 return false;
866
867 invalidateItem(focusedIndex());
868 invalidateItem(i);
869
870 m_focusedIndex = i;
871
872 if (!hotTracking) {
873 if (m_client)
874 m_client->setTextFromItemForPopupMenu(this, i);
875 }
876
877 if (!scrollToRevealSelection())
878 ::UpdateWindow(m_popup);
879
880 return true;
881 }
882
visibleItems() const883 int WebPopupMenuProxyWin::visibleItems() const
884 {
885 return clientRect().height() / m_itemHeight;
886 }
887
listIndexAtPoint(const IntPoint & point) const888 int WebPopupMenuProxyWin::listIndexAtPoint(const IntPoint& point) const
889 {
890 return m_scrollOffset + point.y() / m_itemHeight;
891 }
892
focusedIndex() const893 int WebPopupMenuProxyWin::focusedIndex() const
894 {
895 return m_focusedIndex;
896 }
897
focusFirst()898 void WebPopupMenuProxyWin::focusFirst()
899 {
900 int size = m_items.size();
901
902 for (int i = 0; i < size; ++i) {
903 if (m_items[i].m_isEnabled) {
904 setFocusedIndex(i);
905 break;
906 }
907 }
908 }
909
focusLast()910 void WebPopupMenuProxyWin::focusLast()
911 {
912 int size = m_items.size();
913
914 for (int i = size - 1; i > 0; --i) {
915 if (m_items[i].m_isEnabled) {
916 setFocusedIndex(i);
917 break;
918 }
919 }
920 }
921
922
incrementWheelDelta(int delta)923 void WebPopupMenuProxyWin::incrementWheelDelta(int delta)
924 {
925 m_wheelDelta += delta;
926 }
927
reduceWheelDelta(int delta)928 void WebPopupMenuProxyWin::reduceWheelDelta(int delta)
929 {
930 ASSERT(delta >= 0);
931 ASSERT(delta <= abs(m_wheelDelta));
932
933 if (m_wheelDelta > 0)
934 m_wheelDelta -= delta;
935 else if (m_wheelDelta < 0)
936 m_wheelDelta += delta;
937 else
938 return;
939 }
940
scrollToRevealSelection()941 bool WebPopupMenuProxyWin::scrollToRevealSelection()
942 {
943 if (!m_scrollbar)
944 return false;
945
946 int index = focusedIndex();
947
948 if (index < m_scrollOffset) {
949 ScrollableArea::scrollToYOffsetWithoutAnimation(index);
950 return true;
951 }
952
953 if (index >= m_scrollOffset + visibleItems()) {
954 ScrollableArea::scrollToYOffsetWithoutAnimation(index - visibleItems() + 1);
955 return true;
956 }
957
958 return false;
959 }
960
961 } // namespace WebKit
962