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