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