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