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