Replace PassRef with Ref/Ref&& across the board.
[WebKit-https.git] / Source / WebCore / rendering / RenderListBox.cpp
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
65 namespace WebCore {
66
67 using namespace HTMLNames;
68  
69 const int rowSpacing = 1;
70
71 const int optionsSpacingHorizontal = 2;
72
73 // The minSize constant was originally defined to render scrollbars correctly.
74 // This might vary for different platforms.
75 const int minSize = 4;
76
77 // Default size when the multiple attribute is present but size attribute is absent.
78 const 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.
82 const int baselineAdjustment = 7;
83
84 RenderListBox::RenderListBox(HTMLSelectElement& element, Ref<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
95 RenderListBox::~RenderListBox()
96 {
97     setHasVerticalScrollbar(false);
98     view().frameView().removeScrollableArea(this);
99 }
100
101 HTMLSelectElement& RenderListBox::selectElement() const
102 {
103     return downcast<HTMLSelectElement>(nodeForNonAnonymous());
104 }
105
106 void 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 (is<HTMLOptionElement>(*element))
120                 text = downcast<HTMLOptionElement>(*element).textIndentedToRespectGroupLabel();
121             else if (is<HTMLOptGroupElement>(*element)) {
122                 text = downcast<HTMLOptGroupElement>(*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
147 void 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
161 void 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
183 void 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
192 void 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
201 void 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
230 int 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
239 int 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
245 int RenderListBox::numItems() const
246 {
247     return selectElement().listItems().size();
248 }
249
250 LayoutUnit RenderListBox::listHeight() const
251 {
252     return itemHeight() * numItems() - rowSpacing;
253 }
254
255 void RenderListBox::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
256 {
257     LayoutUnit height = itemHeight() * size() - rowSpacing + verticalBorderAndPaddingExtent();
258     RenderBox::computeLogicalHeight(height, logicalTop, computedValues);
259 }
260
261 int RenderListBox::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode lineDirection, LinePositionMode linePositionMode) const
262 {
263     return RenderBox::baselinePosition(baselineType, firstLine, lineDirection, linePositionMode) - baselineAdjustment;
264 }
265
266 LayoutRect 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     
273 void 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
316 void 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(snappedIntRect(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 (is<HTMLOptionElement>(*element) && !element->isDisabledFormControl()) {
334             selectElement().setActiveSelectionEndIndex(i);
335             rects.append(snappedIntRect(itemBoundingBoxRect(additionalOffset, i)));
336             return;
337         }
338     }
339 }
340
341 void RenderListBox::paintScrollbar(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
342 {
343     if (m_vBar) {
344         IntRect scrollRect = snappedIntRect(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, snappedIntRect(paintInfo.rect));
350     }
351 }
352
353 static 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
373 void 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 = is<HTMLOptionElement>(*listItemElement);
389     if (isOptionElement)
390         itemText = downcast<HTMLOptionElement>(*listItemElement).textIndentedToRespectGroupLabel();
391     else if (is<HTMLOptGroupElement>(*listItemElement))
392         itemText = downcast<HTMLOptGroupElement>(*listItemElement).groupLabelText();
393     applyTextTransform(style(), itemText, ' ');
394
395     Color textColor = listItemElement->renderStyle() ? listItemElement->renderStyle()->visitedDependentColor(CSSPropertyColor) : style().visitedDependentColor(CSSPropertyColor);
396     if (isOptionElement && downcast<HTMLOptionElement>(*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 (is<HTMLOptGroupElement>(*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
423 void 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 (is<HTMLOptionElement>(*listItemElement) && downcast<HTMLOptionElement>(*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(snappedIntRect(itemRect), backColor, colorSpace);
443     }
444 }
445
446 bool 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
463 int 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
479 void 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
524 int 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
542 void 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
562 void RenderListBox::stopAutoscroll()
563 {
564     if (selectElement().isDisabledFormControl())
565         return;
566
567     selectElement().listBoxOnChange();
568 }
569
570 bool 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
586 bool RenderListBox::listIndexIsVisible(int index)
587 {    
588     return index >= m_indexOffset && index < m_indexOffset + numVisibleItems();
589 }
590
591 bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier, Element**, RenderBox*, const IntPoint&)
592 {
593     return ScrollableArea::scroll(direction, granularity, multiplier);
594 }
595
596 bool RenderListBox::logicalScroll(ScrollLogicalDirection direction, ScrollGranularity granularity, float multiplier, Element**)
597 {
598     return ScrollableArea::scroll(logicalToPhysical(direction, style().isHorizontalWritingMode(), style().isFlippedBlocksWritingMode()), granularity, multiplier);
599 }
600
601 void RenderListBox::valueChanged(unsigned listIndex)
602 {
603     selectElement().setSelectedIndex(selectElement().listToOptionIndex(listIndex));
604     selectElement().dispatchFormControlChangeEvent();
605 }
606
607 int RenderListBox::scrollSize(ScrollbarOrientation orientation) const
608 {
609     return ((orientation == VerticalScrollbar) && m_vBar) ? (m_vBar->totalSize() - m_vBar->visibleSize()) : 0;
610 }
611
612 int RenderListBox::scrollPosition(Scrollbar*) const
613 {
614     return m_indexOffset;
615 }
616
617 void RenderListBox::setScrollOffset(const IntPoint& offset)
618 {
619     scrollTo(offset.y());
620 }
621
622 void 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
632 LayoutUnit RenderListBox::itemHeight() const
633 {
634     return style().fontMetrics().height() + rowSpacing;
635 }
636
637 int 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.
644 int RenderListBox::scrollWidth() const
645 {
646     // There is no horizontal scrolling allowed.
647     return pixelSnappedClientWidth();
648 }
649
650 int RenderListBox::scrollHeight() const
651 {
652     return std::max(pixelSnappedClientHeight(), roundToInt(listHeight()));
653 }
654
655 int RenderListBox::scrollLeft() const
656 {
657     return 0;
658 }
659
660 void RenderListBox::setScrollLeft(int)
661 {
662 }
663
664 int RenderListBox::scrollTop() const
665 {
666     return m_indexOffset * itemHeight();
667 }
668
669 void 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
679 bool 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
702 LayoutRect RenderListBox::controlClipRect(const LayoutPoint& additionalOffset) const
703 {
704     LayoutRect clipRect = contentBoxRect();
705     clipRect.moveBy(additionalOffset);
706     return clipRect;
707 }
708
709 bool RenderListBox::isActive() const
710 {
711     Page* page = frame().page();
712     return page && page->focusController().isActive();
713 }
714
715 void 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
722 IntRect 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
731 IntRect 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
740 IntPoint 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
749 IntPoint 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
758 IntSize RenderListBox::contentsSize() const
759 {
760     return IntSize(scrollWidth(), scrollHeight());
761 }
762
763 IntPoint RenderListBox::lastKnownMousePosition() const
764 {
765     return view().frameView().lastKnownMousePosition();
766 }
767
768 bool RenderListBox::isHandlingWheelEvent() const
769 {
770     return view().frameView().isHandlingWheelEvent();
771 }
772
773 bool RenderListBox::shouldSuspendScrollAnimations() const
774 {
775     return view().frameView().shouldSuspendScrollAnimations();
776 }
777
778 bool RenderListBox::forceUpdateScrollbarsOnMainThreadForPerformanceTesting() const
779 {
780     Page* page = frame().page();
781     return page && page->settings().forceUpdateScrollbarsOnMainThreadForPerformanceTesting();
782 }
783
784 ScrollableArea* RenderListBox::enclosingScrollableArea() const
785 {
786     // FIXME: Return a RenderLayer that's scrollable.
787     return 0;
788 }
789
790 bool RenderListBox::isScrollableOrRubberbandable()
791 {
792     return m_vBar;
793 }
794
795 bool RenderListBox::hasScrollableOrRubberbandableAncestor()
796 {
797     return enclosingLayer() && enclosingLayer()->hasScrollableOrRubberbandableAncestor();
798 }
799
800 IntRect RenderListBox::scrollableAreaBoundingBox() const
801 {
802     return absoluteBoundingBoxRect();
803 }
804
805 PassRefPtr<Scrollbar> RenderListBox::createScrollbar()
806 {
807     RefPtr<Scrollbar> widget;
808     bool hasCustomScrollbarStyle = style().hasPseudoStyle(SCROLLBAR);
809     if (hasCustomScrollbarStyle)
810         widget = RenderScrollbar::createCustomScrollbar(this, VerticalScrollbar, &selectElement());
811     else {
812         widget = Scrollbar::createNativeScrollbar(this, VerticalScrollbar, theme().scrollbarControlSizeForPart(ListboxPart));
813         didAddScrollbar(widget.get(), VerticalScrollbar);
814     }
815     view().frameView().addChild(widget.get());
816     return widget.release();
817 }
818
819 void RenderListBox::destroyScrollbar()
820 {
821     if (!m_vBar)
822         return;
823
824     if (!m_vBar->isCustomScrollbar())
825         ScrollableArea::willRemoveScrollbar(m_vBar.get(), VerticalScrollbar);
826     m_vBar->removeFromParent();
827     m_vBar->disconnectFromScrollableArea();
828     m_vBar = 0;
829 }
830
831 void RenderListBox::setHasVerticalScrollbar(bool hasScrollbar)
832 {
833     if (hasScrollbar == (m_vBar != 0))
834         return;
835
836     if (hasScrollbar)
837         m_vBar = createScrollbar();
838     else
839         destroyScrollbar();
840
841     if (m_vBar)
842         m_vBar->styleChanged();
843
844     // Force an update since we know the scrollbars have changed things.
845 #if ENABLE(DASHBOARD_SUPPORT)
846     if (document().hasAnnotatedRegions())
847         document().setAnnotatedRegionsDirty(true);
848 #endif
849 }
850
851 bool RenderListBox::scrolledToTop() const
852 {
853     Scrollbar* vbar = verticalScrollbar();
854     if (!vbar)
855         return true;
856     
857     return vbar->value() <= 0;
858 }
859
860 bool RenderListBox::scrolledToBottom() const
861 {
862     Scrollbar* vbar = verticalScrollbar();
863     if (!vbar)
864         return true;
865
866     return vbar->value() >= vbar->maximum();
867 }
868
869 bool RenderListBox::scrolledToLeft() const
870 {
871     // We do not scroll horizontally in a select element, so always report
872     // that we are at the full extent of the scroll.
873     return true;
874 }
875
876 bool RenderListBox::scrolledToRight() const
877 {
878     // We do not scroll horizontally in a select element, so always report
879     // that we are at the full extent of the scroll.
880     return true;
881 }
882     
883 } // namespace WebCore