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