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