~ubuntu-branches/ubuntu/raring/qtwebkit-source/raring-proposed

« back to all changes in this revision

Viewing changes to Source/WebCore/platform/win/PopupMenuWin.cpp

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2013-02-18 14:24:18 UTC
  • Revision ID: package-import@ubuntu.com-20130218142418-eon0jmjg3nj438uy
Tags: upstream-2.3
ImportĀ upstreamĀ versionĀ 2.3

Show diffs side-by-side

added added

removed removed

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