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