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