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