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/DocumentEventQueue.h"
     40 #include "core/dom/NodeRenderStyle.h"
     41 #include "core/editing/FrameSelection.h"
     42 #include "core/html/HTMLOptGroupElement.h"
     43 #include "core/html/HTMLOptionElement.h"
     44 #include "core/html/HTMLSelectElement.h"
     45 #include "core/page/EventHandler.h"
     46 #include "core/page/FocusController.h"
     47 #include "core/page/Frame.h"
     48 #include "core/page/FrameView.h"
     49 #include "core/page/Page.h"
     50 #include "core/page/SpatialNavigation.h"
     51 #include "core/platform/Scrollbar.h"
     52 #include "core/platform/graphics/FontCache.h"
     53 #include "core/platform/graphics/GraphicsContext.h"
     54 #include "core/rendering/HitTestResult.h"
     55 #include "core/rendering/PaintInfo.h"
     56 #include "core/rendering/RenderScrollbar.h"
     57 #include "core/rendering/RenderText.h"
     58 #include "core/rendering/RenderTheme.h"
     59 #include "core/rendering/RenderView.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     : RenderBlock(element)
     84     , m_optionsChanged(true)
     85     , m_scrollToRevealSelectionAfterLayout(false)
     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()->styleResolver()->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 bool RenderListBox::canBeReplacedWithInlineRunIn() const
    153 {
    154     return false;
    155 }
    156 
    157 void RenderListBox::selectionChanged()
    158 {
    159     repaint();
    160     if (!m_inAutoscroll) {
    161         if (m_optionsChanged || needsLayout())
    162             m_scrollToRevealSelectionAfterLayout = true;
    163         else
    164             scrollToRevealSelection();
    165     }
    166 
    167     if (AXObjectCache* cache = document()->existingAXObjectCache())
    168         cache->selectedChildrenChanged(this);
    169 }
    170 
    171 void RenderListBox::layout()
    172 {
    173     StackStats::LayoutCheckPoint layoutCheckPoint;
    174     RenderBlock::layout();
    175 
    176     if (m_vBar) {
    177         bool enabled = numVisibleItems() < numItems();
    178         m_vBar->setEnabled(enabled);
    179         m_vBar->setProportion(numVisibleItems(), numItems());
    180         if (!enabled) {
    181             scrollToOffsetWithoutAnimation(VerticalScrollbar, 0);
    182             m_indexOffset = 0;
    183         }
    184     }
    185 
    186     if (m_scrollToRevealSelectionAfterLayout) {
    187         LayoutStateDisabler layoutStateDisabler(view());
    188         scrollToRevealSelection();
    189     }
    190 }
    191 
    192 void RenderListBox::scrollToRevealSelection()
    193 {
    194     HTMLSelectElement* select = selectElement();
    195 
    196     m_scrollToRevealSelectionAfterLayout = false;
    197 
    198     int firstIndex = select->activeSelectionStartListIndex();
    199     if (firstIndex >= 0 && !listIndexIsVisible(select->activeSelectionEndListIndex()))
    200         scrollToRevealElementAtListIndex(firstIndex);
    201 }
    202 
    203 void RenderListBox::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
    204 {
    205     maxLogicalWidth = m_optionsWidth + 2 * optionsSpacingHorizontal;
    206     if (m_vBar)
    207         maxLogicalWidth += m_vBar->width();
    208     if (!style()->width().isPercent())
    209         minLogicalWidth = maxLogicalWidth;
    210 }
    211 
    212 void RenderListBox::computePreferredLogicalWidths()
    213 {
    214     ASSERT(!m_optionsChanged);
    215 
    216     m_minPreferredLogicalWidth = 0;
    217     m_maxPreferredLogicalWidth = 0;
    218 
    219     if (style()->width().isFixed() && style()->width().value() > 0)
    220         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style()->width().value());
    221     else
    222         computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
    223 
    224     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
    225         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->minWidth().value()));
    226         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->minWidth().value()));
    227     }
    228 
    229     if (style()->maxWidth().isFixed()) {
    230         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->maxWidth().value()));
    231         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, adjustContentBoxLogicalWidthForBoxSizing(style()->maxWidth().value()));
    232     }
    233 
    234     LayoutUnit toAdd = borderAndPaddingWidth();
    235     m_minPreferredLogicalWidth += toAdd;
    236     m_maxPreferredLogicalWidth += toAdd;
    237 
    238     setPreferredLogicalWidthsDirty(false);
    239 }
    240 
    241 int RenderListBox::size() const
    242 {
    243     int specifiedSize = selectElement()->size();
    244     if (specifiedSize > 1)
    245         return max(minSize, specifiedSize);
    246 
    247     return defaultSize;
    248 }
    249 
    250 int RenderListBox::numVisibleItems() const
    251 {
    252     // Only count fully visible rows. But don't return 0 even if only part of a row shows.
    253     return max<int>(1, (contentHeight() + rowSpacing) / itemHeight());
    254 }
    255 
    256 int RenderListBox::numItems() const
    257 {
    258     return selectElement()->listItems().size();
    259 }
    260 
    261 LayoutUnit RenderListBox::listHeight() const
    262 {
    263     return itemHeight() * numItems() - rowSpacing;
    264 }
    265 
    266 void RenderListBox::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
    267 {
    268     LayoutUnit height = itemHeight() * size() - rowSpacing + borderAndPaddingHeight();
    269     RenderBox::computeLogicalHeight(height, logicalTop, computedValues);
    270 }
    271 
    272 int RenderListBox::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode lineDirection, LinePositionMode linePositionMode) const
    273 {
    274     return RenderBox::baselinePosition(baselineType, firstLine, lineDirection, linePositionMode) - baselineAdjustment;
    275 }
    276 
    277 LayoutRect RenderListBox::itemBoundingBoxRect(const LayoutPoint& additionalOffset, int index)
    278 {
    279     return LayoutRect(additionalOffset.x() + borderLeft() + paddingLeft(),
    280                    additionalOffset.y() + borderTop() + paddingTop() + itemHeight() * (index - m_indexOffset),
    281                    contentWidth(), itemHeight());
    282 }
    283 
    284 void RenderListBox::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
    285 {
    286     if (style()->visibility() != VISIBLE)
    287         return;
    288 
    289     int listItemsSize = numItems();
    290 
    291     if (paintInfo.phase == PaintPhaseForeground) {
    292         int index = m_indexOffset;
    293         while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
    294             paintItemForeground(paintInfo, paintOffset, index);
    295             index++;
    296         }
    297     }
    298 
    299     // Paint the children.
    300     RenderBlock::paintObject(paintInfo, paintOffset);
    301 
    302     switch (paintInfo.phase) {
    303     // Depending on whether we have overlay scrollbars they
    304     // get rendered in the foreground or background phases
    305     case PaintPhaseForeground:
    306         if (m_vBar->isOverlayScrollbar())
    307             paintScrollbar(paintInfo, paintOffset);
    308         break;
    309     case PaintPhaseBlockBackground:
    310         if (!m_vBar->isOverlayScrollbar())
    311             paintScrollbar(paintInfo, paintOffset);
    312         break;
    313     case PaintPhaseChildBlockBackground:
    314     case PaintPhaseChildBlockBackgrounds: {
    315         int index = m_indexOffset;
    316         while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
    317             paintItemBackground(paintInfo, paintOffset, index);
    318             index++;
    319         }
    320         break;
    321     }
    322     default:
    323         break;
    324     }
    325 }
    326 
    327 void RenderListBox::addFocusRingRects(Vector<IntRect>& rects, const LayoutPoint& additionalOffset, const RenderLayerModelObject* paintContainer)
    328 {
    329     if (!isSpatialNavigationEnabled(frame()))
    330         return RenderBlock::addFocusRingRects(rects, additionalOffset, paintContainer);
    331 
    332     HTMLSelectElement* select = selectElement();
    333 
    334     // Focus the last selected item.
    335     int selectedItem = select->activeSelectionEndListIndex();
    336     if (selectedItem >= 0) {
    337         rects.append(pixelSnappedIntRect(itemBoundingBoxRect(additionalOffset, selectedItem)));
    338         return;
    339     }
    340 
    341     // No selected items, find the first non-disabled item.
    342     int size = numItems();
    343     const Vector<HTMLElement*>& listItems = select->listItems();
    344     for (int i = 0; i < size; ++i) {
    345         HTMLElement* element = listItems[i];
    346         if (element->hasTagName(optionTag) && !element->isDisabledFormControl()) {
    347             rects.append(pixelSnappedIntRect(itemBoundingBoxRect(additionalOffset, i)));
    348             return;
    349         }
    350     }
    351 }
    352 
    353 void RenderListBox::paintScrollbar(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
    354 {
    355     if (m_vBar) {
    356         IntRect scrollRect = pixelSnappedIntRect(paintOffset.x() + width() - borderRight() - m_vBar->width(),
    357             paintOffset.y() + borderTop(),
    358             m_vBar->width(),
    359             height() - (borderTop() + borderBottom()));
    360         m_vBar->setFrameRect(scrollRect);
    361         m_vBar->paint(paintInfo.context, paintInfo.rect);
    362     }
    363 }
    364 
    365 static LayoutSize itemOffsetForAlignment(TextRun textRun, RenderStyle* itemStyle, Font itemFont, LayoutRect itemBoudingBox)
    366 {
    367     ETextAlign actualAlignment = itemStyle->textAlign();
    368     // FIXME: Firefox doesn't respect JUSTIFY. Should we?
    369     // FIXME: Handle TAEND here
    370     if (actualAlignment == TASTART || actualAlignment == JUSTIFY)
    371       actualAlignment = itemStyle->isLeftToRightDirection() ? LEFT : RIGHT;
    372 
    373     LayoutSize offset = LayoutSize(0, itemFont.fontMetrics().ascent());
    374     if (actualAlignment == RIGHT || actualAlignment == WEBKIT_RIGHT) {
    375         float textWidth = itemFont.width(textRun);
    376         offset.setWidth(itemBoudingBox.width() - textWidth - optionsSpacingHorizontal);
    377     } else if (actualAlignment == CENTER || actualAlignment == WEBKIT_CENTER) {
    378         float textWidth = itemFont.width(textRun);
    379         offset.setWidth((itemBoudingBox.width() - textWidth) / 2);
    380     } else
    381         offset.setWidth(optionsSpacingHorizontal);
    382     return offset;
    383 }
    384 
    385 void RenderListBox::paintItemForeground(PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listIndex)
    386 {
    387     FontCachePurgePreventer fontCachePurgePreventer;
    388 
    389     HTMLSelectElement* select = selectElement();
    390 
    391     const Vector<HTMLElement*>& listItems = select->listItems();
    392     HTMLElement* element = listItems[listIndex];
    393 
    394     RenderStyle* itemStyle = element->renderStyle();
    395     if (!itemStyle)
    396         itemStyle = style();
    397 
    398     if (itemStyle->visibility() == HIDDEN)
    399         return;
    400 
    401     String itemText;
    402     bool isOptionElement = element->hasTagName(optionTag);
    403     if (isOptionElement)
    404         itemText = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
    405     else if (isHTMLOptGroupElement(element))
    406         itemText = toHTMLOptGroupElement(element)->groupLabelText();
    407     applyTextTransform(style(), itemText, ' ');
    408 
    409     Color textColor = element->renderStyle() ? resolveColor(element->renderStyle(), CSSPropertyColor) : resolveColor(CSSPropertyColor);
    410     if (isOptionElement && toHTMLOptionElement(element)->selected()) {
    411         if (frame()->selection()->isFocusedAndActive() && document()->focusedElement() == node())
    412             textColor = theme()->activeListBoxSelectionForegroundColor();
    413         // Honor the foreground color for disabled items
    414         else if (!element->isDisabledFormControl() && !select->isDisabledFormControl())
    415             textColor = theme()->inactiveListBoxSelectionForegroundColor();
    416     }
    417 
    418     paintInfo.context->setFillColor(textColor);
    419 
    420     TextRun textRun(itemText, 0, 0, TextRun::AllowTrailingExpansion, itemStyle->direction(), isOverride(itemStyle->unicodeBidi()), true, TextRun::NoRounding);
    421     Font itemFont = style()->font();
    422     LayoutRect r = itemBoundingBoxRect(paintOffset, listIndex);
    423     r.move(itemOffsetForAlignment(textRun, itemStyle, itemFont, r));
    424 
    425     if (isHTMLOptGroupElement(element)) {
    426         FontDescription d = itemFont.fontDescription();
    427         d.setWeight(d.bolderWeight());
    428         itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
    429         itemFont.update(document()->styleResolver()->fontSelector());
    430     }
    431 
    432     // Draw the item text
    433     TextRunPaintInfo textRunPaintInfo(textRun);
    434     textRunPaintInfo.bounds = r;
    435     paintInfo.context->drawBidiText(itemFont, textRunPaintInfo, roundedIntPoint(r.location()));
    436 }
    437 
    438 void RenderListBox::paintItemBackground(PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listIndex)
    439 {
    440     const Vector<HTMLElement*>& listItems = selectElement()->listItems();
    441     HTMLElement* element = listItems[listIndex];
    442 
    443     Color backColor;
    444     if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected()) {
    445         if (frame()->selection()->isFocusedAndActive() && document()->focusedElement() == node())
    446             backColor = theme()->activeListBoxSelectionBackgroundColor();
    447         else
    448             backColor = theme()->inactiveListBoxSelectionBackgroundColor();
    449     } else {
    450         backColor = element->renderStyle() ? resolveColor(element->renderStyle(), CSSPropertyBackgroundColor) : resolveColor(CSSPropertyBackgroundColor);
    451     }
    452 
    453     // Draw the background for this list box item
    454     if (!element->renderStyle() || element->renderStyle()->visibility() != HIDDEN) {
    455         LayoutRect itemRect = itemBoundingBoxRect(paintOffset, listIndex);
    456         itemRect.intersect(controlClipRect(paintOffset));
    457         paintInfo.context->fillRect(pixelSnappedIntRect(itemRect), backColor);
    458     }
    459 }
    460 
    461 bool RenderListBox::isPointInOverflowControl(HitTestResult& result, const LayoutPoint& locationInContainer, const LayoutPoint& accumulatedOffset)
    462 {
    463     if (!m_vBar || !m_vBar->shouldParticipateInHitTesting())
    464         return false;
    465 
    466     LayoutRect vertRect(accumulatedOffset.x() + width() - borderRight() - m_vBar->width(),
    467                         accumulatedOffset.y() + borderTop(),
    468                         m_vBar->width(),
    469                         height() - borderTop() - borderBottom());
    470 
    471     if (vertRect.contains(locationInContainer)) {
    472         result.setScrollbar(m_vBar.get());
    473         return true;
    474     }
    475     return false;
    476 }
    477 
    478 int RenderListBox::listIndexAtOffset(const LayoutSize& offset)
    479 {
    480     if (!numItems())
    481         return -1;
    482 
    483     if (offset.height() < borderTop() + paddingTop() || offset.height() > height() - paddingBottom() - borderBottom())
    484         return -1;
    485 
    486     int scrollbarWidth = m_vBar ? m_vBar->width() : 0;
    487     if (offset.width() < borderLeft() + paddingLeft() || offset.width() > width() - borderRight() - paddingRight() - scrollbarWidth)
    488         return -1;
    489 
    490     int newOffset = (offset.height() - borderTop() - paddingTop()) / itemHeight() + m_indexOffset;
    491     return newOffset < numItems() ? newOffset : -1;
    492 }
    493 
    494 void RenderListBox::panScroll(const IntPoint& panStartMousePosition)
    495 {
    496     const int maxSpeed = 20;
    497     const int iconRadius = 7;
    498     const int speedReducer = 4;
    499 
    500     // FIXME: This doesn't work correctly with transforms.
    501     FloatPoint absOffset = localToAbsolute();
    502 
    503     IntPoint lastKnownMousePosition = frame()->eventHandler()->lastKnownMousePosition();
    504     // 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
    505     static IntPoint previousMousePosition;
    506     if (lastKnownMousePosition.y() < 0)
    507         lastKnownMousePosition = previousMousePosition;
    508     else
    509         previousMousePosition = lastKnownMousePosition;
    510 
    511     int yDelta = lastKnownMousePosition.y() - panStartMousePosition.y();
    512 
    513     // If the point is too far from the center we limit the speed
    514     yDelta = max<int>(min<int>(yDelta, maxSpeed), -maxSpeed);
    515 
    516     if (abs(yDelta) < iconRadius) // at the center we let the space for the icon
    517         return;
    518 
    519     if (yDelta > 0)
    520         //offsetY = view()->viewHeight();
    521         absOffset.move(0, listHeight());
    522     else if (yDelta < 0)
    523         yDelta--;
    524 
    525     // Let's attenuate the speed
    526     yDelta /= speedReducer;
    527 
    528     IntPoint scrollPoint(0, 0);
    529     scrollPoint.setY(absOffset.y() + yDelta);
    530     int newOffset = scrollToward(scrollPoint);
    531     if (newOffset < 0)
    532         return;
    533 
    534     m_inAutoscroll = true;
    535     HTMLSelectElement* select = selectElement();
    536     select->updateListBoxSelection(!select->multiple());
    537     m_inAutoscroll = false;
    538 }
    539 
    540 int RenderListBox::scrollToward(const IntPoint& destination)
    541 {
    542     // FIXME: This doesn't work correctly with transforms.
    543     FloatPoint absPos = localToAbsolute();
    544     IntSize positionOffset = roundedIntSize(destination - absPos);
    545 
    546     int rows = numVisibleItems();
    547     int offset = m_indexOffset;
    548 
    549     if (positionOffset.height() < borderTop() + paddingTop() && scrollToRevealElementAtListIndex(offset - 1))
    550         return offset - 1;
    551 
    552     if (positionOffset.height() > height() - paddingBottom() - borderBottom() && scrollToRevealElementAtListIndex(offset + rows))
    553         return offset + rows - 1;
    554 
    555     return listIndexAtOffset(positionOffset);
    556 }
    557 
    558 void RenderListBox::autoscroll(const IntPoint&)
    559 {
    560     IntPoint pos = frame()->view()->windowToContents(frame()->eventHandler()->lastKnownMousePosition());
    561 
    562     int endIndex = scrollToward(pos);
    563     if (selectElement()->isDisabledFormControl())
    564         return;
    565 
    566     if (endIndex >= 0) {
    567         HTMLSelectElement* select = selectElement();
    568         m_inAutoscroll = true;
    569 
    570         if (!select->multiple())
    571             select->setActiveSelectionAnchorIndex(endIndex);
    572 
    573         select->setActiveSelectionEndIndex(endIndex);
    574         select->updateListBoxSelection(!select->multiple());
    575         m_inAutoscroll = false;
    576     }
    577 }
    578 
    579 void RenderListBox::stopAutoscroll()
    580 {
    581     if (selectElement()->isDisabledFormControl())
    582         return;
    583 
    584     selectElement()->listBoxOnChange();
    585 }
    586 
    587 bool RenderListBox::scrollToRevealElementAtListIndex(int index)
    588 {
    589     if (index < 0 || index >= numItems() || listIndexIsVisible(index))
    590         return false;
    591 
    592     int newOffset;
    593     if (index < m_indexOffset)
    594         newOffset = index;
    595     else
    596         newOffset = index - numVisibleItems() + 1;
    597 
    598     scrollToOffsetWithoutAnimation(VerticalScrollbar, newOffset);
    599 
    600     return true;
    601 }
    602 
    603 bool RenderListBox::listIndexIsVisible(int index)
    604 {
    605     return index >= m_indexOffset && index < m_indexOffset + numVisibleItems();
    606 }
    607 
    608 bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier, Node**)
    609 {
    610     return ScrollableArea::scroll(direction, granularity, multiplier);
    611 }
    612 
    613 bool RenderListBox::logicalScroll(ScrollLogicalDirection direction, ScrollGranularity granularity, float multiplier, Node**)
    614 {
    615     return ScrollableArea::scroll(logicalToPhysical(direction, style()->isHorizontalWritingMode(), style()->isFlippedBlocksWritingMode()), granularity, multiplier);
    616 }
    617 
    618 void RenderListBox::valueChanged(unsigned listIndex)
    619 {
    620     HTMLSelectElement* element = selectElement();
    621     element->setSelectedIndex(element->listToOptionIndex(listIndex));
    622     element->dispatchFormControlChangeEvent();
    623 }
    624 
    625 int RenderListBox::scrollSize(ScrollbarOrientation orientation) const
    626 {
    627     return orientation == VerticalScrollbar ? (numItems() - numVisibleItems()) : 0;
    628 }
    629 
    630 IntPoint RenderListBox::scrollPosition() const
    631 {
    632     return IntPoint(0, m_indexOffset);
    633 }
    634 
    635 void RenderListBox::setScrollOffset(const IntPoint& offset)
    636 {
    637     scrollTo(offset.y());
    638 }
    639 
    640 void RenderListBox::scrollTo(int newOffset)
    641 {
    642     if (newOffset == m_indexOffset)
    643         return;
    644 
    645     m_indexOffset = newOffset;
    646     repaint();
    647     node()->document()->eventQueue()->enqueueOrDispatchScrollEvent(node(), DocumentEventQueue::ScrollEventElementTarget);
    648 }
    649 
    650 LayoutUnit RenderListBox::itemHeight() const
    651 {
    652     return style()->fontMetrics().height() + rowSpacing;
    653 }
    654 
    655 int RenderListBox::verticalScrollbarWidth() const
    656 {
    657     return m_vBar && !m_vBar->isOverlayScrollbar() ? m_vBar->width() : 0;
    658 }
    659 
    660 // FIXME: We ignore padding in the vertical direction as far as these values are concerned, since that's
    661 // how the control currently paints.
    662 int RenderListBox::scrollWidth() const
    663 {
    664     // There is no horizontal scrolling allowed.
    665     return pixelSnappedClientWidth();
    666 }
    667 
    668 int RenderListBox::scrollHeight() const
    669 {
    670     return max(pixelSnappedClientHeight(), roundToInt(listHeight()));
    671 }
    672 
    673 int RenderListBox::scrollLeft() const
    674 {
    675     return 0;
    676 }
    677 
    678 void RenderListBox::setScrollLeft(int)
    679 {
    680 }
    681 
    682 int RenderListBox::scrollTop() const
    683 {
    684     return m_indexOffset * itemHeight();
    685 }
    686 
    687 void RenderListBox::setScrollTop(int newTop)
    688 {
    689     // Determine an index and scroll to it.
    690     int index = newTop / itemHeight();
    691     if (index < 0 || index >= numItems() || index == m_indexOffset)
    692         return;
    693 
    694     scrollToOffsetWithoutAnimation(VerticalScrollbar, index);
    695 }
    696 
    697 bool RenderListBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
    698 {
    699     if (!RenderBlock::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction))
    700         return false;
    701     const Vector<HTMLElement*>& listItems = selectElement()->listItems();
    702     int size = numItems();
    703     LayoutPoint adjustedLocation = accumulatedOffset + location();
    704 
    705     for (int i = 0; i < size; ++i) {
    706         if (itemBoundingBoxRect(adjustedLocation, i).contains(locationInContainer.point())) {
    707             if (Element* node = listItems[i]) {
    708                 result.setInnerNode(node);
    709                 if (!result.innerNonSharedNode())
    710                     result.setInnerNonSharedNode(node);
    711                 result.setLocalPoint(locationInContainer.point() - toLayoutSize(adjustedLocation));
    712                 break;
    713             }
    714         }
    715     }
    716 
    717     return true;
    718 }
    719 
    720 LayoutRect RenderListBox::controlClipRect(const LayoutPoint& additionalOffset) const
    721 {
    722     LayoutRect clipRect = contentBoxRect();
    723     clipRect.moveBy(additionalOffset);
    724     return clipRect;
    725 }
    726 
    727 bool RenderListBox::isActive() const
    728 {
    729     Page* page = frame()->page();
    730     return page && page->focusController().isActive();
    731 }
    732 
    733 void RenderListBox::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
    734 {
    735     IntRect scrollRect = rect;
    736     scrollRect.move(width() - borderRight() - scrollbar->width(), borderTop());
    737     repaintRectangle(scrollRect);
    738 }
    739 
    740 IntRect RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const
    741 {
    742     RenderView* view = this->view();
    743     if (!view)
    744         return scrollbarRect;
    745 
    746     IntRect rect = scrollbarRect;
    747 
    748     int scrollbarLeft = width() - borderRight() - scrollbar->width();
    749     int scrollbarTop = borderTop();
    750     rect.move(scrollbarLeft, scrollbarTop);
    751 
    752     return view->frameView()->convertFromRenderer(this, rect);
    753 }
    754 
    755 IntRect RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const
    756 {
    757     RenderView* view = this->view();
    758     if (!view)
    759         return parentRect;
    760 
    761     IntRect rect = view->frameView()->convertToRenderer(this, parentRect);
    762 
    763     int scrollbarLeft = width() - borderRight() - scrollbar->width();
    764     int scrollbarTop = borderTop();
    765     rect.move(-scrollbarLeft, -scrollbarTop);
    766     return rect;
    767 }
    768 
    769 IntPoint RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const
    770 {
    771     RenderView* view = this->view();
    772     if (!view)
    773         return scrollbarPoint;
    774 
    775     IntPoint point = scrollbarPoint;
    776 
    777     int scrollbarLeft = width() - borderRight() - scrollbar->width();
    778     int scrollbarTop = borderTop();
    779     point.move(scrollbarLeft, scrollbarTop);
    780 
    781     return view->frameView()->convertFromRenderer(this, point);
    782 }
    783 
    784 IntPoint RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const
    785 {
    786     RenderView* view = this->view();
    787     if (!view)
    788         return parentPoint;
    789 
    790     IntPoint point = view->frameView()->convertToRenderer(this, parentPoint);
    791 
    792     int scrollbarLeft = width() - borderRight() - scrollbar->width();
    793     int scrollbarTop = borderTop();
    794     point.move(-scrollbarLeft, -scrollbarTop);
    795     return point;
    796 }
    797 
    798 IntSize RenderListBox::contentsSize() const
    799 {
    800     return IntSize(scrollWidth(), scrollHeight());
    801 }
    802 
    803 int RenderListBox::visibleHeight() const
    804 {
    805     return height();
    806 }
    807 
    808 int RenderListBox::visibleWidth() const
    809 {
    810     return width();
    811 }
    812 
    813 IntPoint RenderListBox::lastKnownMousePosition() const
    814 {
    815     RenderView* view = this->view();
    816     if (!view)
    817         return IntPoint();
    818     return view->frameView()->lastKnownMousePosition();
    819 }
    820 
    821 bool RenderListBox::shouldSuspendScrollAnimations() const
    822 {
    823     RenderView* view = this->view();
    824     if (!view)
    825         return true;
    826     return view->frameView()->shouldSuspendScrollAnimations();
    827 }
    828 
    829 bool RenderListBox::scrollbarsCanBeActive() const
    830 {
    831     RenderView* view = this->view();
    832     if (!view)
    833         return false;
    834     return view->frameView()->scrollbarsCanBeActive();
    835 }
    836 
    837 IntPoint RenderListBox::minimumScrollPosition() const
    838 {
    839     return IntPoint();
    840 }
    841 
    842 IntPoint RenderListBox::maximumScrollPosition() const
    843 {
    844     return IntPoint(0, numItems() - numVisibleItems());
    845 }
    846 
    847 bool RenderListBox::userInputScrollable(ScrollbarOrientation orientation) const
    848 {
    849     return orientation == VerticalScrollbar;
    850 }
    851 
    852 int RenderListBox::lineStep(ScrollbarOrientation) const
    853 {
    854     return 1;
    855 }
    856 
    857 int RenderListBox::pageStep(ScrollbarOrientation orientation) const
    858 {
    859     return max(1, numVisibleItems() - 1);
    860 }
    861 
    862 float RenderListBox::pixelStep(ScrollbarOrientation) const
    863 {
    864     return 1.0f / itemHeight();
    865 }
    866 
    867 ScrollableArea* RenderListBox::enclosingScrollableArea() const
    868 {
    869     // FIXME: Return a RenderLayer that's scrollable.
    870     return 0;
    871 }
    872 
    873 IntRect RenderListBox::scrollableAreaBoundingBox() const
    874 {
    875     return absoluteBoundingBoxRect();
    876 }
    877 
    878 PassRefPtr<Scrollbar> RenderListBox::createScrollbar()
    879 {
    880     RefPtr<Scrollbar> widget;
    881     bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR);
    882     if (hasCustomScrollbarStyle)
    883         widget = RenderScrollbar::createCustomScrollbar(this, VerticalScrollbar, this->node());
    884     else {
    885         widget = Scrollbar::createNativeScrollbar(this, VerticalScrollbar, theme()->scrollbarControlSizeForPart(ListboxPart));
    886         didAddVerticalScrollbar(widget.get());
    887     }
    888     document()->view()->addChild(widget.get());
    889     return widget.release();
    890 }
    891 
    892 void RenderListBox::destroyScrollbar()
    893 {
    894     if (!m_vBar)
    895         return;
    896 
    897     if (!m_vBar->isCustomScrollbar())
    898         ScrollableArea::willRemoveVerticalScrollbar(m_vBar.get());
    899     m_vBar->removeFromParent();
    900     m_vBar->disconnectFromScrollableArea();
    901     m_vBar = 0;
    902 }
    903 
    904 void RenderListBox::setHasVerticalScrollbar(bool hasScrollbar)
    905 {
    906     if (hasScrollbar == (m_vBar != 0))
    907         return;
    908 
    909     if (hasScrollbar)
    910         m_vBar = createScrollbar();
    911     else
    912         destroyScrollbar();
    913 
    914     if (m_vBar)
    915         m_vBar->styleChanged();
    916 
    917     // Force an update since we know the scrollbars have changed things.
    918     if (document()->hasAnnotatedRegions())
    919         document()->setAnnotatedRegionsDirty(true);
    920 }
    921 
    922 } // namespace WebCore
    923