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