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