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