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