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