Home | History | Annotate | Download | only in rendering
      1 /*
      2  * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved.
      3  *               2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  *
      9  * 1.  Redistributions of source code must retain the above copyright
     10  *     notice, this list of conditions and the following disclaimer.
     11  * 2.  Redistributions in binary form must reproduce the above copyright
     12  *     notice, this list of conditions and the following disclaimer in the
     13  *     documentation and/or other materials provided with the distribution.
     14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     15  *     its contributors may be used to endorse or promote products derived
     16  *     from this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28  */
     29 
     30 #include "config.h"
     31 #include "core/rendering/RenderListBox.h"
     32 
     33 #include <math.h>
     34 #include "HTMLNames.h"
     35 #include "core/accessibility/AXObjectCache.h"
     36 #include "core/css/CSSFontSelector.h"
     37 #include "core/css/resolver/StyleResolver.h"
     38 #include "core/dom/Document.h"
     39 #include "core/dom/NodeRenderStyle.h"
     40 #include "core/editing/FrameSelection.h"
     41 #include "core/html/HTMLOptGroupElement.h"
     42 #include "core/html/HTMLOptionElement.h"
     43 #include "core/html/HTMLSelectElement.h"
     44 #include "core/page/EventHandler.h"
     45 #include "core/page/FocusController.h"
     46 #include "core/frame/Frame.h"
     47 #include "core/frame/FrameView.h"
     48 #include "core/page/Page.h"
     49 #include "core/page/SpatialNavigation.h"
     50 #include "core/rendering/HitTestResult.h"
     51 #include "core/rendering/LayoutRectRecorder.h"
     52 #include "core/rendering/PaintInfo.h"
     53 #include "core/rendering/RenderScrollbar.h"
     54 #include "core/rendering/RenderText.h"
     55 #include "core/rendering/RenderTheme.h"
     56 #include "core/rendering/RenderView.h"
     57 #include "platform/fonts/FontCache.h"
     58 #include "platform/graphics/GraphicsContext.h"
     59 #include "platform/scroll/Scrollbar.h"
     60 
     61 using namespace std;
     62 
     63 namespace WebCore {
     64 
     65 using namespace HTMLNames;
     66 
     67 const int rowSpacing = 1;
     68 
     69 const int optionsSpacingHorizontal = 2;
     70 
     71 // The minSize constant was originally defined to render scrollbars correctly.
     72 // This might vary for different platforms.
     73 const int minSize = 4;
     74 
     75 // Default size when the multiple attribute is present but size attribute is absent.
     76 const int defaultSize = 4;
     77 
     78 // FIXME: This hardcoded baselineAdjustment is what we used to do for the old
     79 // widget, but I'm not sure this is right for the new control.
     80 const int baselineAdjustment = 7;
     81 
     82 RenderListBox::RenderListBox(Element* element)
     83     : RenderBlockFlow(element)
     84     , m_optionsChanged(true)
     85     , m_scrollToRevealSelectionAfterLayout(true)
     86     , m_inAutoscroll(false)
     87     , m_optionsWidth(0)
     88     , m_indexOffset(0)
     89 {
     90     ASSERT(element);
     91     ASSERT(element->isHTMLElement());
     92     ASSERT(element->hasTagName(HTMLNames::selectTag));
     93 
     94     if (FrameView* frameView = frame()->view())
     95         frameView->addScrollableArea(this);
     96 }
     97 
     98 RenderListBox::~RenderListBox()
     99 {
    100     setHasVerticalScrollbar(false);
    101 
    102     if (FrameView* frameView = frame()->view())
    103         frameView->removeScrollableArea(this);
    104 }
    105 
    106 inline HTMLSelectElement* RenderListBox::selectElement() const
    107 {
    108     return toHTMLSelectElement(node());
    109 }
    110 
    111 void RenderListBox::updateFromElement()
    112 {
    113     FontCachePurgePreventer fontCachePurgePreventer;
    114 
    115     if (m_optionsChanged) {
    116         const Vector<HTMLElement*>& listItems = selectElement()->listItems();
    117         int size = numItems();
    118 
    119         float width = 0;
    120         for (int i = 0; i < size; ++i) {
    121             HTMLElement* element = listItems[i];
    122             String text;
    123             Font itemFont = style()->font();
    124             if (element->hasTagName(optionTag)) {
    125                 text = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
    126             } else if (isHTMLOptGroupElement(element)) {
    127                 text = toHTMLOptGroupElement(element)->groupLabelText();
    128                 FontDescription d = itemFont.fontDescription();
    129                 d.setWeight(d.bolderWeight());
    130                 itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
    131                 itemFont.update(document().styleEngine()->fontSelector());
    132             }
    133 
    134             if (!text.isEmpty()) {
    135                 applyTextTransform(style(), text, ' ');
    136                 // FIXME: Why is this always LTR? Can't text direction affect the width?
    137                 TextRun textRun = constructTextRun(this, itemFont, text, style(), TextRun::AllowTrailingExpansion);
    138                 textRun.disableRoundingHacks();
    139                 float textWidth = itemFont.width(textRun);
    140                 width = max(width, textWidth);
    141             }
    142         }
    143         m_optionsWidth = static_cast<int>(ceilf(width));
    144         m_optionsChanged = false;
    145 
    146         setHasVerticalScrollbar(true);
    147 
    148         setNeedsLayoutAndPrefWidthsRecalc();
    149     }
    150 }
    151 
    152 void RenderListBox::selectionChanged()
    153 {
    154     repaint();
    155     if (!m_inAutoscroll) {
    156         if (m_optionsChanged || needsLayout())
    157             m_scrollToRevealSelectionAfterLayout = true;
    158         else
    159             scrollToRevealSelection();
    160     }
    161 
    162     if (AXObjectCache* cache = document().existingAXObjectCache())
    163         cache->selectedChildrenChanged(this);
    164 }
    165 
    166 void RenderListBox::layout()
    167 {
    168     LayoutRectRecorder recorder(*this);
    169     RenderBlockFlow::layout();
    170 
    171     if (m_vBar) {
    172         bool enabled = numVisibleItems() < numItems();
    173         m_vBar->setEnabled(enabled);
    174         m_vBar->setProportion(numVisibleItems(), numItems());
    175         if (!enabled) {
    176             scrollToOffsetWithoutAnimation(VerticalScrollbar, 0);
    177             m_indexOffset = 0;
    178         }
    179     }
    180 
    181     if (m_scrollToRevealSelectionAfterLayout) {
    182         LayoutStateDisabler layoutStateDisabler(view());
    183         scrollToRevealSelection();
    184     }
    185 }
    186 
    187 void RenderListBox::scrollToRevealSelection()
    188 {
    189     HTMLSelectElement* select = selectElement();
    190 
    191     m_scrollToRevealSelectionAfterLayout = false;
    192 
    193     int firstIndex = select->activeSelectionStartListIndex();
    194     if (firstIndex >= 0 && !listIndexIsVisible(select->activeSelectionEndListIndex()))
    195         scrollToRevealElementAtListIndex(firstIndex);
    196 }
    197 
    198 void RenderListBox::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
    199 {
    200     maxLogicalWidth = m_optionsWidth + 2 * optionsSpacingHorizontal;
    201     if (m_vBar)
    202         maxLogicalWidth += verticalScrollbarWidth();
    203     if (!style()->width().isPercent())
    204         minLogicalWidth = maxLogicalWidth;
    205 }
    206 
    207 void RenderListBox::computePreferredLogicalWidths()
    208 {
    209     ASSERT(!m_optionsChanged);
    210 
    211     m_minPreferredLogicalWidth = 0;
    212     m_maxPreferredLogicalWidth = 0;
    213 
    214     if (style()->width().isFixed() && style()->width().value() > 0)
    215         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style()->width().value());
    216     else
    217         computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
    218 
    219     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
    220         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->minWidth().value()));
    221         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->minWidth().value()));
    222     }
    223 
    224     if (style()->maxWidth().isFixed()) {
    225         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->maxWidth().value()));
    226         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->maxWidth().value()));
    227     }
    228 
    229     LayoutUnit toAdd = borderAndPaddingWidth();
    230     m_minPreferredLogicalWidth += toAdd;
    231     m_maxPreferredLogicalWidth += toAdd;
    232 
    233     clearPreferredLogicalWidthsDirty();
    234 }
    235 
    236 int RenderListBox::size() const
    237 {
    238     int specifiedSize = selectElement()->size();
    239     if (specifiedSize > 1)
    240         return max(minSize, specifiedSize);
    241 
    242     return defaultSize;
    243 }
    244 
    245 int RenderListBox::numVisibleItems() const
    246 {
    247     // Only count fully visible rows. But don't return 0 even if only part of a row shows.
    248     return max<int>(1, (contentHeight() + rowSpacing) / itemHeight());
    249 }
    250 
    251 int RenderListBox::numItems() const
    252 {
    253     return selectElement()->listItems().size();
    254 }
    255 
    256 LayoutUnit RenderListBox::listHeight() const
    257 {
    258     return itemHeight() * numItems() - rowSpacing;
    259 }
    260 
    261 void RenderListBox::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
    262 {
    263     LayoutUnit height = itemHeight() * size() - rowSpacing + borderAndPaddingHeight();
    264     RenderBox::computeLogicalHeight(height, logicalTop, computedValues);
    265 }
    266 
    267 int RenderListBox::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode lineDirection, LinePositionMode linePositionMode) const
    268 {
    269     return RenderBox::baselinePosition(baselineType, firstLine, lineDirection, linePositionMode) - baselineAdjustment;
    270 }
    271 
    272 LayoutRect RenderListBox::itemBoundingBoxRect(const LayoutPoint& additionalOffset, int index)
    273 {
    274     // For RTL, items start after the left-side vertical scrollbar.
    275     int scrollbarOffset = style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft() ? verticalScrollbarWidth() : 0;
    276     return LayoutRect(additionalOffset.x() + borderLeft() + paddingLeft() + scrollbarOffset,
    277         additionalOffset.y() + borderTop() + paddingTop() + itemHeight() * (index - m_indexOffset),
    278         contentWidth(), itemHeight());
    279 }
    280 
    281 void RenderListBox::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
    282 {
    283     if (style()->visibility() != VISIBLE)
    284         return;
    285 
    286     int listItemsSize = numItems();
    287 
    288     if (paintInfo.phase == PaintPhaseForeground) {
    289         int index = m_indexOffset;
    290         while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
    291             paintItemForeground(paintInfo, paintOffset, index);
    292             index++;
    293         }
    294     }
    295 
    296     // Paint the children.
    297     RenderBlock::paintObject(paintInfo, paintOffset);
    298 
    299     switch (paintInfo.phase) {
    300     // Depending on whether we have overlay scrollbars they
    301     // get rendered in the foreground or background phases
    302     case PaintPhaseForeground:
    303         if (m_vBar->isOverlayScrollbar())
    304             paintScrollbar(paintInfo, paintOffset);
    305         break;
    306     case PaintPhaseBlockBackground:
    307         if (!m_vBar->isOverlayScrollbar())
    308             paintScrollbar(paintInfo, paintOffset);
    309         break;
    310     case PaintPhaseChildBlockBackground:
    311     case PaintPhaseChildBlockBackgrounds: {
    312         int index = m_indexOffset;
    313         while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
    314             paintItemBackground(paintInfo, paintOffset, index);
    315             index++;
    316         }
    317         break;
    318     }
    319     default:
    320         break;
    321     }
    322 }
    323 
    324 void RenderListBox::addFocusRingRects(Vector<IntRect>& rects, const LayoutPoint& additionalOffset, const RenderLayerModelObject* paintContainer)
    325 {
    326     if (!isSpatialNavigationEnabled(frame()))
    327         return RenderBlock::addFocusRingRects(rects, additionalOffset, paintContainer);
    328 
    329     HTMLSelectElement* select = selectElement();
    330 
    331     // Focus the last selected item.
    332     int selectedItem = select->activeSelectionEndListIndex();
    333     if (selectedItem >= 0) {
    334         rects.append(pixelSnappedIntRect(itemBoundingBoxRect(additionalOffset, selectedItem)));
    335         return;
    336     }
    337 
    338     // No selected items, find the first non-disabled item.
    339     int size = numItems();
    340     const Vector<HTMLElement*>& listItems = select->listItems();
    341     for (int i = 0; i < size; ++i) {
    342         HTMLElement* element = listItems[i];
    343         if (element->hasTagName(optionTag) && !element->isDisabledFormControl()) {
    344             rects.append(pixelSnappedIntRect(itemBoundingBoxRect(additionalOffset, i)));
    345             return;
    346         }
    347     }
    348 }
    349 
    350 int RenderListBox::scrollbarLeft() const
    351 {
    352     int scrollbarLeft = 0;
    353     if (style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
    354         scrollbarLeft = borderLeft();
    355     else
    356         scrollbarLeft = width() - borderRight() - verticalScrollbarWidth();
    357     return scrollbarLeft;
    358 }
    359 
    360 void RenderListBox::paintScrollbar(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
    361 {
    362     if (m_vBar) {
    363         IntRect scrollRect = pixelSnappedIntRect(paintOffset.x() + scrollbarLeft(),
    364             paintOffset.y() + borderTop(),
    365             verticalScrollbarWidth(),
    366             height() - (borderTop() + borderBottom()));
    367         m_vBar->setFrameRect(scrollRect);
    368         m_vBar->paint(paintInfo.context, paintInfo.rect);
    369     }
    370 }
    371 
    372 static LayoutSize itemOffsetForAlignment(TextRun textRun, RenderStyle* itemStyle, Font itemFont, LayoutRect itemBoudingBox)
    373 {
    374     ETextAlign actualAlignment = itemStyle->textAlign();
    375     // FIXME: Firefox doesn't respect JUSTIFY. Should we?
    376     // FIXME: Handle TAEND here
    377     if (actualAlignment == TASTART || actualAlignment == JUSTIFY)
    378       actualAlignment = itemStyle->isLeftToRightDirection() ? LEFT : RIGHT;
    379 
    380     LayoutSize offset = LayoutSize(0, itemFont.fontMetrics().ascent());
    381     if (actualAlignment == RIGHT || actualAlignment == WEBKIT_RIGHT) {
    382         float textWidth = itemFont.width(textRun);
    383         offset.setWidth(itemBoudingBox.width() - textWidth - optionsSpacingHorizontal);
    384     } else if (actualAlignment == CENTER || actualAlignment == WEBKIT_CENTER) {
    385         float textWidth = itemFont.width(textRun);
    386         offset.setWidth((itemBoudingBox.width() - textWidth) / 2);
    387     } else
    388         offset.setWidth(optionsSpacingHorizontal);
    389     return offset;
    390 }
    391 
    392 void RenderListBox::paintItemForeground(PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listIndex)
    393 {
    394     FontCachePurgePreventer fontCachePurgePreventer;
    395 
    396     HTMLSelectElement* select = selectElement();
    397 
    398     const Vector<HTMLElement*>& listItems = select->listItems();
    399     HTMLElement* element = listItems[listIndex];
    400 
    401     RenderStyle* itemStyle = element->renderStyle();
    402     if (!itemStyle)
    403         itemStyle = style();
    404 
    405     if (itemStyle->visibility() == HIDDEN)
    406         return;
    407 
    408     String itemText;
    409     bool isOptionElement = element->hasTagName(optionTag);
    410     if (isOptionElement)
    411         itemText = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
    412     else if (isHTMLOptGroupElement(element))
    413         itemText = toHTMLOptGroupElement(element)->groupLabelText();
    414     applyTextTransform(style(), itemText, ' ');
    415 
    416     Color textColor = element->renderStyle() ? resolveColor(element->renderStyle(), CSSPropertyColor) : resolveColor(CSSPropertyColor);
    417     if (isOptionElement && toHTMLOptionElement(element)->selected()) {
    418         if (frame()->selection().isFocusedAndActive() && document().focusedElement() == node())
    419             textColor = RenderTheme::theme().activeListBoxSelectionForegroundColor();
    420         // Honor the foreground color for disabled items
    421         else if (!element->isDisabledFormControl() && !select->isDisabledFormControl())
    422             textColor = RenderTheme::theme().inactiveListBoxSelectionForegroundColor();
    423     }
    424 
    425     paintInfo.context->setFillColor(textColor);
    426 
    427     TextRun textRun(itemText, 0, 0, TextRun::AllowTrailingExpansion, itemStyle->direction(), isOverride(itemStyle->unicodeBidi()), true, TextRun::NoRounding);
    428     Font itemFont = style()->font();
    429     LayoutRect r = itemBoundingBoxRect(paintOffset, listIndex);
    430     r.move(itemOffsetForAlignment(textRun, itemStyle, itemFont, r));
    431 
    432     if (isHTMLOptGroupElement(element)) {
    433         FontDescription d = itemFont.fontDescription();
    434         d.setWeight(d.bolderWeight());
    435         itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
    436         itemFont.update(document().styleEngine()->fontSelector());
    437     }
    438 
    439     // Draw the item text
    440     TextRunPaintInfo textRunPaintInfo(textRun);
    441     textRunPaintInfo.bounds = r;
    442     paintInfo.context->drawBidiText(itemFont, textRunPaintInfo, roundedIntPoint(r.location()));
    443 }
    444 
    445 void RenderListBox::paintItemBackground(PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listIndex)
    446 {
    447     const Vector<HTMLElement*>& listItems = selectElement()->listItems();
    448     HTMLElement* element = listItems[listIndex];
    449 
    450     Color backColor;
    451     if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected()) {
    452         if (frame()->selection().isFocusedAndActive() && document().focusedElement() == node())
    453             backColor = RenderTheme::theme().activeListBoxSelectionBackgroundColor();
    454         else
    455             backColor = RenderTheme::theme().inactiveListBoxSelectionBackgroundColor();
    456     } else {
    457         backColor = element->renderStyle() ? resolveColor(element->renderStyle(), CSSPropertyBackgroundColor) : resolveColor(CSSPropertyBackgroundColor);
    458     }
    459 
    460     // Draw the background for this list box item
    461     if (!element->renderStyle() || element->renderStyle()->visibility() != HIDDEN) {
    462         LayoutRect itemRect = itemBoundingBoxRect(paintOffset, listIndex);
    463         itemRect.intersect(controlClipRect(paintOffset));
    464         paintInfo.context->fillRect(pixelSnappedIntRect(itemRect), backColor);
    465     }
    466 }
    467 
    468 bool RenderListBox::isPointInOverflowControl(HitTestResult& result, const LayoutPoint& locationInContainer, const LayoutPoint& accumulatedOffset)
    469 {
    470     if (!m_vBar || !m_vBar->shouldParticipateInHitTesting())
    471         return false;
    472 
    473     LayoutRect vertRect(accumulatedOffset.x() + scrollbarLeft(),
    474                         accumulatedOffset.y() + borderTop(),
    475                         verticalScrollbarWidth(),
    476                         height() - borderTop() - borderBottom());
    477 
    478     if (vertRect.contains(locationInContainer)) {
    479         result.setScrollbar(m_vBar.get());
    480         return true;
    481     }
    482     return false;
    483 }
    484 
    485 int RenderListBox::listIndexAtOffset(const LayoutSize& offset)
    486 {
    487     if (!numItems())
    488         return -1;
    489 
    490     if (offset.height() < borderTop() + paddingTop() || offset.height() > height() - paddingBottom() - borderBottom())
    491         return -1;
    492 
    493     int scrollbarWidth = verticalScrollbarWidth();
    494     int rightScrollbarOffset = style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft() ? scrollbarWidth : 0;
    495     int leftScrollbarOffset = style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft() ? 0 : scrollbarWidth;
    496     if (offset.width() < borderLeft() + paddingLeft() + rightScrollbarOffset
    497         || offset.width() > width() - borderRight() - paddingRight() - leftScrollbarOffset)
    498         return -1;
    499 
    500     int newOffset = (offset.height() - borderTop() - paddingTop()) / itemHeight() + m_indexOffset;
    501     return newOffset < numItems() ? newOffset : -1;
    502 }
    503 
    504 void RenderListBox::panScroll(const IntPoint& panStartMousePosition)
    505 {
    506     const int maxSpeed = 20;
    507     const int iconRadius = 7;
    508     const int speedReducer = 4;
    509 
    510     // FIXME: This doesn't work correctly with transforms.
    511     FloatPoint absOffset = localToAbsolute();
    512 
    513     IntPoint lastKnownMousePosition = frame()->eventHandler().lastKnownMousePosition();
    514     // We need to check if the last known mouse position is out of the window. When the mouse is out of the window, the position is incoherent
    515     static IntPoint previousMousePosition;
    516     if (lastKnownMousePosition.y() < 0)
    517         lastKnownMousePosition = previousMousePosition;
    518     else
    519         previousMousePosition = lastKnownMousePosition;
    520 
    521     int yDelta = lastKnownMousePosition.y() - panStartMousePosition.y();
    522 
    523     // If the point is too far from the center we limit the speed
    524     yDelta = max<int>(min<int>(yDelta, maxSpeed), -maxSpeed);
    525 
    526     if (abs(yDelta) < iconRadius) // at the center we let the space for the icon
    527         return;
    528 
    529     if (yDelta > 0)
    530         //offsetY = view()->viewHeight();
    531         absOffset.move(0, listHeight());
    532     else if (yDelta < 0)
    533         yDelta--;
    534 
    535     // Let's attenuate the speed
    536     yDelta /= speedReducer;
    537 
    538     IntPoint scrollPoint(0, 0);
    539     scrollPoint.setY(absOffset.y() + yDelta);
    540     int newOffset = scrollToward(scrollPoint);
    541     if (newOffset < 0)
    542         return;
    543 
    544     m_inAutoscroll = true;
    545     HTMLSelectElement* select = selectElement();
    546     select->updateListBoxSelection(!select->multiple());
    547     m_inAutoscroll = false;
    548 }
    549 
    550 int RenderListBox::scrollToward(const IntPoint& destination)
    551 {
    552     // FIXME: This doesn't work correctly with transforms.
    553     FloatPoint absPos = localToAbsolute();
    554     IntSize positionOffset = roundedIntSize(destination - absPos);
    555 
    556     int rows = numVisibleItems();
    557     int offset = m_indexOffset;
    558 
    559     if (positionOffset.height() < borderTop() + paddingTop() && scrollToRevealElementAtListIndex(offset - 1))
    560         return offset - 1;
    561 
    562     if (positionOffset.height() > height() - paddingBottom() - borderBottom() && scrollToRevealElementAtListIndex(offset + rows))
    563         return offset + rows - 1;
    564 
    565     return listIndexAtOffset(positionOffset);
    566 }
    567 
    568 void RenderListBox::autoscroll(const IntPoint&)
    569 {
    570     IntPoint pos = frame()->view()->windowToContents(frame()->eventHandler().lastKnownMousePosition());
    571 
    572     int endIndex = scrollToward(pos);
    573     if (selectElement()->isDisabledFormControl())
    574         return;
    575 
    576     if (endIndex >= 0) {
    577         HTMLSelectElement* select = selectElement();
    578         m_inAutoscroll = true;
    579 
    580         if (!select->multiple())
    581             select->setActiveSelectionAnchorIndex(endIndex);
    582 
    583         select->setActiveSelectionEndIndex(endIndex);
    584         select->updateListBoxSelection(!select->multiple());
    585         m_inAutoscroll = false;
    586     }
    587 }
    588 
    589 void RenderListBox::stopAutoscroll()
    590 {
    591     if (selectElement()->isDisabledFormControl())
    592         return;
    593 
    594     selectElement()->listBoxOnChange();
    595 }
    596 
    597 bool RenderListBox::scrollToRevealElementAtListIndex(int index)
    598 {
    599     if (index < 0 || index >= numItems() || listIndexIsVisible(index))
    600         return false;
    601 
    602     int newOffset;
    603     if (index < m_indexOffset)
    604         newOffset = index;
    605     else
    606         newOffset = index - numVisibleItems() + 1;
    607 
    608     scrollToOffsetWithoutAnimation(VerticalScrollbar, newOffset);
    609 
    610     return true;
    611 }
    612 
    613 bool RenderListBox::listIndexIsVisible(int index)
    614 {
    615     return index >= m_indexOffset && index < m_indexOffset + numVisibleItems();
    616 }
    617 
    618 bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
    619 {
    620     return ScrollableArea::scroll(direction, granularity, multiplier);
    621 }
    622 
    623 void RenderListBox::valueChanged(unsigned listIndex)
    624 {
    625     HTMLSelectElement* element = selectElement();
    626     element->setSelectedIndex(element->listToOptionIndex(listIndex));
    627     element->dispatchFormControlChangeEvent();
    628 }
    629 
    630 int RenderListBox::scrollSize(ScrollbarOrientation orientation) const
    631 {
    632     return orientation == VerticalScrollbar ? (numItems() - numVisibleItems()) : 0;
    633 }
    634 
    635 IntPoint RenderListBox::scrollPosition() const
    636 {
    637     return IntPoint(0, m_indexOffset);
    638 }
    639 
    640 void RenderListBox::setScrollOffset(const IntPoint& offset)
    641 {
    642     scrollTo(offset.y());
    643 }
    644 
    645 void RenderListBox::scrollTo(int newOffset)
    646 {
    647     if (newOffset == m_indexOffset)
    648         return;
    649 
    650     m_indexOffset = newOffset;
    651     repaint();
    652     node()->document().enqueueScrollEventForNode(node());
    653 }
    654 
    655 LayoutUnit RenderListBox::itemHeight() const
    656 {
    657     return style()->fontMetrics().height() + rowSpacing;
    658 }
    659 
    660 int RenderListBox::verticalScrollbarWidth() const
    661 {
    662     return m_vBar && !m_vBar->isOverlayScrollbar() ? m_vBar->width() : 0;
    663 }
    664 
    665 // FIXME: We ignore padding in the vertical direction as far as these values are concerned, since that's
    666 // how the control currently paints.
    667 int RenderListBox::scrollWidth() const
    668 {
    669     // There is no horizontal scrolling allowed.
    670     return pixelSnappedClientWidth();
    671 }
    672 
    673 int RenderListBox::scrollHeight() const
    674 {
    675     return max(pixelSnappedClientHeight(), roundToInt(listHeight()));
    676 }
    677 
    678 int RenderListBox::scrollLeft() const
    679 {
    680     return 0;
    681 }
    682 
    683 void RenderListBox::setScrollLeft(int)
    684 {
    685 }
    686 
    687 int RenderListBox::scrollTop() const
    688 {
    689     return m_indexOffset * itemHeight();
    690 }
    691 
    692 void RenderListBox::setScrollTop(int newTop)
    693 {
    694     // Determine an index and scroll to it.
    695     int index = newTop / itemHeight();
    696     if (index < 0 || index >= numItems() || index == m_indexOffset)
    697         return;
    698 
    699     scrollToOffsetWithoutAnimation(VerticalScrollbar, index);
    700 }
    701 
    702 bool RenderListBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
    703 {
    704     if (!RenderBlock::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction))
    705         return false;
    706     const Vector<HTMLElement*>& listItems = selectElement()->listItems();
    707     int size = numItems();
    708     LayoutPoint adjustedLocation = accumulatedOffset + location();
    709 
    710     for (int i = 0; i < size; ++i) {
    711         if (itemBoundingBoxRect(adjustedLocation, i).contains(locationInContainer.point())) {
    712             if (Element* node = listItems[i]) {
    713                 result.setInnerNode(node);
    714                 if (!result.innerNonSharedNode())
    715                     result.setInnerNonSharedNode(node);
    716                 result.setLocalPoint(locationInContainer.point() - toLayoutSize(adjustedLocation));
    717                 break;
    718             }
    719         }
    720     }
    721 
    722     return true;
    723 }
    724 
    725 LayoutRect RenderListBox::controlClipRect(const LayoutPoint& additionalOffset) const
    726 {
    727     LayoutRect clipRect = contentBoxRect();
    728     if (style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
    729         clipRect.moveBy(additionalOffset + LayoutPoint(verticalScrollbarWidth(), 0));
    730     else
    731         clipRect.moveBy(additionalOffset);
    732     return clipRect;
    733 }
    734 
    735 bool RenderListBox::isActive() const
    736 {
    737     Page* page = frame()->page();
    738     return page && page->focusController().isActive();
    739 }
    740 
    741 void RenderListBox::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
    742 {
    743     IntRect scrollRect = rect;
    744     if (style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
    745         scrollRect.move(borderLeft(), borderTop());
    746     else
    747         scrollRect.move(width() - borderRight() - scrollbar->width(), borderTop());
    748     repaintRectangle(scrollRect);
    749 }
    750 
    751 IntRect RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const
    752 {
    753     RenderView* view = this->view();
    754     if (!view)
    755         return scrollbarRect;
    756 
    757     IntRect rect = scrollbarRect;
    758 
    759     int scrollbarTop = borderTop();
    760     rect.move(scrollbarLeft(), scrollbarTop);
    761 
    762     return view->frameView()->convertFromRenderer(this, rect);
    763 }
    764 
    765 IntRect RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const
    766 {
    767     RenderView* view = this->view();
    768     if (!view)
    769         return parentRect;
    770 
    771     IntRect rect = view->frameView()->convertToRenderer(this, parentRect);
    772 
    773     int scrollbarTop = borderTop();
    774     rect.move(-scrollbarLeft(), -scrollbarTop);
    775     return rect;
    776 }
    777 
    778 IntPoint RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const
    779 {
    780     RenderView* view = this->view();
    781     if (!view)
    782         return scrollbarPoint;
    783 
    784     IntPoint point = scrollbarPoint;
    785 
    786     int scrollbarTop = borderTop();
    787     point.move(scrollbarLeft(), scrollbarTop);
    788 
    789     return view->frameView()->convertFromRenderer(this, point);
    790 }
    791 
    792 IntPoint RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const
    793 {
    794     RenderView* view = this->view();
    795     if (!view)
    796         return parentPoint;
    797 
    798     IntPoint point = view->frameView()->convertToRenderer(this, parentPoint);
    799 
    800     int scrollbarTop = borderTop();
    801     point.move(-scrollbarLeft(), -scrollbarTop);
    802     return point;
    803 }
    804 
    805 IntSize RenderListBox::contentsSize() const
    806 {
    807     return IntSize(scrollWidth(), scrollHeight());
    808 }
    809 
    810 int RenderListBox::visibleHeight() const
    811 {
    812     return height();
    813 }
    814 
    815 int RenderListBox::visibleWidth() const
    816 {
    817     return width();
    818 }
    819 
    820 IntPoint RenderListBox::lastKnownMousePosition() const
    821 {
    822     RenderView* view = this->view();
    823     if (!view)
    824         return IntPoint();
    825     return view->frameView()->lastKnownMousePosition();
    826 }
    827 
    828 bool RenderListBox::shouldSuspendScrollAnimations() const
    829 {
    830     RenderView* view = this->view();
    831     if (!view)
    832         return true;
    833     return view->frameView()->shouldSuspendScrollAnimations();
    834 }
    835 
    836 bool RenderListBox::scrollbarsCanBeActive() const
    837 {
    838     RenderView* view = this->view();
    839     if (!view)
    840         return false;
    841     return view->frameView()->scrollbarsCanBeActive();
    842 }
    843 
    844 IntPoint RenderListBox::minimumScrollPosition() const
    845 {
    846     return IntPoint();
    847 }
    848 
    849 IntPoint RenderListBox::maximumScrollPosition() const
    850 {
    851     return IntPoint(0, numItems() - numVisibleItems());
    852 }
    853 
    854 bool RenderListBox::userInputScrollable(ScrollbarOrientation orientation) const
    855 {
    856     return orientation == VerticalScrollbar;
    857 }
    858 
    859 bool RenderListBox::shouldPlaceVerticalScrollbarOnLeft() const
    860 {
    861     return false;
    862 }
    863 
    864 int RenderListBox::lineStep(ScrollbarOrientation) const
    865 {
    866     return 1;
    867 }
    868 
    869 int RenderListBox::pageStep(ScrollbarOrientation orientation) const
    870 {
    871     return max(1, numVisibleItems() - 1);
    872 }
    873 
    874 float RenderListBox::pixelStep(ScrollbarOrientation) const
    875 {
    876     return 1.0f / itemHeight();
    877 }
    878 
    879 ScrollableArea* RenderListBox::enclosingScrollableArea() const
    880 {
    881     // FIXME: Return a RenderLayer that's scrollable.
    882     return 0;
    883 }
    884 
    885 IntRect RenderListBox::scrollableAreaBoundingBox() const
    886 {
    887     return absoluteBoundingBoxRect();
    888 }
    889 
    890 PassRefPtr<Scrollbar> RenderListBox::createScrollbar()
    891 {
    892     RefPtr<Scrollbar> widget;
    893     bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR);
    894     if (hasCustomScrollbarStyle)
    895         widget = RenderScrollbar::createCustomScrollbar(this, VerticalScrollbar, this->node());
    896     else {
    897         widget = Scrollbar::create(this, VerticalScrollbar, RenderTheme::theme().scrollbarControlSizeForPart(ListboxPart));
    898         didAddScrollbar(widget.get(), VerticalScrollbar);
    899     }
    900     document().view()->addChild(widget.get());
    901     return widget.release();
    902 }
    903 
    904 void RenderListBox::destroyScrollbar()
    905 {
    906     if (!m_vBar)
    907         return;
    908 
    909     if (!m_vBar->isCustomScrollbar())
    910         ScrollableArea::willRemoveScrollbar(m_vBar.get(), VerticalScrollbar);
    911     m_vBar->removeFromParent();
    912     m_vBar->disconnectFromScrollableArea();
    913     m_vBar = 0;
    914 }
    915 
    916 void RenderListBox::setHasVerticalScrollbar(bool hasScrollbar)
    917 {
    918     if (hasScrollbar == (m_vBar != 0))
    919         return;
    920 
    921     if (hasScrollbar)
    922         m_vBar = createScrollbar();
    923     else
    924         destroyScrollbar();
    925 
    926     if (m_vBar)
    927         m_vBar->styleChanged();
    928 
    929     // Force an update since we know the scrollbars have changed things.
    930     if (document().hasAnnotatedRegions())
    931         document().setAnnotatedRegionsDirty(true);
    932 }
    933 
    934 } // namespace WebCore
    935