Follow up the recent HTMLSelectElement improvements with a little bit more
[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 "HTMLSelectElement.h"
47 #include "HitTestResult.h"
48 #include "NodeRenderStyle.h"
49 #include "OptionGroupElement.h"
50 #include "OptionElement.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             Element* element = listItems[i];
115             String text;
116             Font itemFont = style()->font();
117             if (OptionElement* optionElement = toOptionElement(element))
118                 text = optionElement->textIndentedToRespectGroupLabel();
119             else if (OptionGroupElement* optionGroupElement = toOptionGroupElement(element)) {
120                 text = optionGroupElement->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         OptionElement* optionElement = toOptionElement(listItems[i]);
331         if (optionElement && !optionElement->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     Element* element = listItems[listIndex];
375     OptionElement* optionElement = toOptionElement(element);
376
377     RenderStyle* itemStyle = element->renderStyle();
378     if (!itemStyle)
379         itemStyle = style();
380
381     if (itemStyle->visibility() == HIDDEN)
382         return;
383
384     String itemText;
385     if (optionElement)
386         itemText = optionElement->textIndentedToRespectGroupLabel();
387     else if (OptionGroupElement* optionGroupElement = toOptionGroupElement(element))
388         itemText = optionGroupElement->groupLabelText();
389     applyTextTransform(style(), itemText, ' ');
390
391     Color textColor = element->renderStyle() ? element->renderStyle()->visitedDependentColor(CSSPropertyColor) : style()->visitedDependentColor(CSSPropertyColor);
392     if (optionElement && optionElement->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 (isOptionGroupElement(element)) {
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     Element* element = listItems[listIndex];
426     OptionElement* optionElement = toOptionElement(element);
427
428     Color backColor;
429     if (optionElement && optionElement->selected()) {
430         if (frame()->selection()->isFocusedAndActive() && document()->focusedNode() == node())
431             backColor = theme()->activeListBoxSelectionBackgroundColor();
432         else
433             backColor = theme()->inactiveListBoxSelectionBackgroundColor();
434     } else
435         backColor = element->renderStyle() ? element->renderStyle()->visitedDependentColor(CSSPropertyBackgroundColor) : style()->visitedDependentColor(CSSPropertyBackgroundColor);
436
437     // Draw the background for this list box item
438     if (!element->renderStyle() || element->renderStyle()->visibility() != HIDDEN) {
439         ColorSpace colorSpace = element->renderStyle() ? element->renderStyle()->colorSpace() : style()->colorSpace();
440         LayoutRect itemRect = itemBoundingBoxRect(paintOffset, listIndex);
441         itemRect.intersect(controlClipRect(paintOffset));
442         paintInfo.context->fillRect(itemRect, backColor, colorSpace);
443     }
444 }
445
446 bool RenderListBox::isPointInOverflowControl(HitTestResult& result, const LayoutPoint& pointInContainer, const LayoutPoint& accumulatedOffset)
447 {
448     if (!m_vBar)
449         return false;
450
451     LayoutRect vertRect(accumulatedOffset.x() + width() - borderRight() - m_vBar->width(),
452                         accumulatedOffset.y() + borderTop(),
453                         m_vBar->width(),
454                         height() - borderTop() - borderBottom());
455
456     if (vertRect.contains(pointInContainer)) {
457         result.setScrollbar(m_vBar.get());
458         return true;
459     }
460     return false;
461 }
462
463 int RenderListBox::listIndexAtOffset(const LayoutSize& offset)
464 {
465     if (!numItems())
466         return -1;
467
468     if (offset.height() < borderTop() + paddingTop() || offset.height() > height() - paddingBottom() - borderBottom())
469         return -1;
470
471     LayoutUnit scrollbarWidth = m_vBar ? m_vBar->width() : 0;
472     if (offset.width() < borderLeft() + paddingLeft() || offset.width() > width() - borderRight() - paddingRight() - scrollbarWidth)
473         return -1;
474
475     int newOffset = (offset.height() - borderTop() - paddingTop()) / itemHeight() + m_indexOffset;
476     return newOffset < numItems() ? newOffset : -1;
477 }
478
479 void RenderListBox::panScroll(const IntPoint& panStartMousePosition)
480 {
481     const int maxSpeed = 20;
482     const int iconRadius = 7;
483     const int speedReducer = 4;
484
485     // FIXME: This doesn't work correctly with transforms.
486     FloatPoint absOffset = localToAbsolute();
487
488     IntPoint currentMousePosition = roundedIntPoint(frame()->eventHandler()->currentMousePosition());
489     // 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
490     static IntPoint previousMousePosition;
491     if (currentMousePosition.y() < 0)
492         currentMousePosition = previousMousePosition;
493     else
494         previousMousePosition = currentMousePosition;
495
496     LayoutUnit yDelta = currentMousePosition.y() - panStartMousePosition.y();
497
498     // If the point is too far from the center we limit the speed
499     yDelta = max<LayoutUnit>(min<LayoutUnit>(yDelta, maxSpeed), -maxSpeed);
500     
501     if (abs(yDelta) < iconRadius) // at the center we let the space for the icon
502         return;
503
504     if (yDelta > 0)
505         //offsetY = view()->viewHeight();
506         absOffset.move(0, listHeight());
507     else if (yDelta < 0)
508         yDelta--;
509
510     // Let's attenuate the speed
511     yDelta /= speedReducer;
512
513     LayoutPoint scrollPoint(0, 0);
514     scrollPoint.setY(absOffset.y() + yDelta);
515     LayoutUnit newOffset = scrollToward(scrollPoint);
516     if (newOffset < 0) 
517         return;
518
519     m_inAutoscroll = true;
520     HTMLSelectElement* select = toHTMLSelectElement(node());
521     select->updateListBoxSelection(!select->multiple());
522     m_inAutoscroll = false;
523 }
524
525 int RenderListBox::scrollToward(const LayoutPoint& destination)
526 {
527     // FIXME: This doesn't work correctly with transforms.
528     FloatPoint absPos = localToAbsolute();
529     LayoutSize positionOffset = roundedLayoutSize(destination - absPos);
530
531     int rows = numVisibleItems();
532     int offset = m_indexOffset;
533     
534     if (positionOffset.height() < borderTop() + paddingTop() && scrollToRevealElementAtListIndex(offset - 1))
535         return offset - 1;
536     
537     if (positionOffset.height() > height() - paddingBottom() - borderBottom() && scrollToRevealElementAtListIndex(offset + rows))
538         return offset + rows - 1;
539     
540     return listIndexAtOffset(positionOffset);
541 }
542
543 void RenderListBox::autoscroll()
544 {
545     LayoutPoint pos = frame()->view()->windowToContents(frame()->eventHandler()->currentMousePosition());
546
547     int endIndex = scrollToward(pos);
548     if (endIndex >= 0) {
549         HTMLSelectElement* select = toHTMLSelectElement(node());
550         m_inAutoscroll = true;
551
552         if (!select->multiple())
553             select->setActiveSelectionAnchorIndex(endIndex);
554
555         select->setActiveSelectionEndIndex(endIndex);
556         select->updateListBoxSelection(!select->multiple());
557         m_inAutoscroll = false;
558     }
559 }
560
561 void RenderListBox::stopAutoscroll()
562 {
563     toHTMLSelectElement(node())->listBoxOnChange();
564 }
565
566 bool RenderListBox::scrollToRevealElementAtListIndex(int index)
567 {
568     if (index < 0 || index >= numItems() || listIndexIsVisible(index))
569         return false;
570
571     int newOffset;
572     if (index < m_indexOffset)
573         newOffset = index;
574     else
575         newOffset = index - numVisibleItems() + 1;
576
577     ScrollableArea::scrollToYOffsetWithoutAnimation(newOffset);
578
579     return true;
580 }
581
582 bool RenderListBox::listIndexIsVisible(int index)
583 {    
584     return index >= m_indexOffset && index < m_indexOffset + numVisibleItems();
585 }
586
587 bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier, Node**)
588 {
589     return ScrollableArea::scroll(direction, granularity, multiplier);
590 }
591
592 bool RenderListBox::logicalScroll(ScrollLogicalDirection direction, ScrollGranularity granularity, float multiplier, Node**)
593 {
594     return ScrollableArea::scroll(logicalToPhysical(direction, style()->isHorizontalWritingMode(), style()->isFlippedBlocksWritingMode()), granularity, multiplier);
595 }
596
597 void RenderListBox::valueChanged(unsigned listIndex)
598 {
599     HTMLSelectElement* element = toHTMLSelectElement(node());
600     element->setSelectedIndex(element->listToOptionIndex(listIndex));
601     element->dispatchFormControlChangeEvent();
602 }
603
604 LayoutUnit RenderListBox::scrollSize(ScrollbarOrientation orientation) const
605 {
606     return ((orientation == VerticalScrollbar) && m_vBar) ? (m_vBar->totalSize() - m_vBar->visibleSize()) : 0;
607 }
608
609 LayoutUnit RenderListBox::scrollPosition(Scrollbar*) const
610 {
611     return m_indexOffset;
612 }
613
614 void RenderListBox::setScrollOffset(const LayoutPoint& offset)
615 {
616     scrollTo(offset.y());
617 }
618
619 void RenderListBox::scrollTo(int newOffset)
620 {
621     if (newOffset == m_indexOffset)
622         return;
623
624     m_indexOffset = newOffset;
625     repaint();
626     node()->document()->eventQueue()->enqueueOrDispatchScrollEvent(node(), EventQueue::ScrollEventElementTarget);
627 }
628
629 LayoutUnit RenderListBox::itemHeight() const
630 {
631     return style()->fontMetrics().height() + rowSpacing;
632 }
633
634 LayoutUnit RenderListBox::verticalScrollbarWidth() const
635 {
636     return m_vBar && !m_vBar->isOverlayScrollbar() ? m_vBar->width() : 0;
637 }
638
639 // FIXME: We ignore padding in the vertical direction as far as these values are concerned, since that's
640 // how the control currently paints.
641 LayoutUnit RenderListBox::scrollWidth() const
642 {
643     // There is no horizontal scrolling allowed.
644     return clientWidth();
645 }
646
647 LayoutUnit RenderListBox::scrollHeight() const
648 {
649     return max(clientHeight(), listHeight());
650 }
651
652 LayoutUnit RenderListBox::scrollLeft() const
653 {
654     return 0;
655 }
656
657 void RenderListBox::setScrollLeft(LayoutUnit)
658 {
659 }
660
661 LayoutUnit RenderListBox::scrollTop() const
662 {
663     return m_indexOffset * itemHeight();
664 }
665
666 void RenderListBox::setScrollTop(LayoutUnit newTop)
667 {
668     // Determine an index and scroll to it.    
669     int index = newTop / itemHeight();
670     if (index < 0 || index >= numItems() || index == m_indexOffset)
671         return;
672     
673     ScrollableArea::scrollToYOffsetWithoutAnimation(index);
674 }
675
676 bool RenderListBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const LayoutPoint& pointInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
677 {
678     if (!RenderBlock::nodeAtPoint(request, result, pointInContainer, accumulatedOffset, hitTestAction))
679         return false;
680     const Vector<HTMLElement*>& listItems = toHTMLSelectElement(node())->listItems();
681     int size = numItems();
682     LayoutPoint adjustedLocation = accumulatedOffset + location();
683
684     for (int i = 0; i < size; ++i) {
685         if (itemBoundingBoxRect(adjustedLocation, i).contains(pointInContainer)) {
686             if (Element* node = listItems[i]) {
687                 result.setInnerNode(node);
688                 if (!result.innerNonSharedNode())
689                     result.setInnerNonSharedNode(node);
690                 result.setLocalPoint(pointInContainer - toLayoutSize(adjustedLocation));
691                 break;
692             }
693         }
694     }
695
696     return true;
697 }
698
699 LayoutRect RenderListBox::controlClipRect(const LayoutPoint& additionalOffset) const
700 {
701     LayoutRect clipRect = contentBoxRect();
702     clipRect.moveBy(additionalOffset);
703     return clipRect;
704 }
705
706 bool RenderListBox::isActive() const
707 {
708     Page* page = frame()->page();
709     return page && page->focusController()->isActive();
710 }
711
712 void RenderListBox::invalidateScrollbarRect(Scrollbar* scrollbar, const LayoutRect& rect)
713 {
714     LayoutRect scrollRect = rect;
715     scrollRect.move(width() - borderRight() - scrollbar->width(), borderTop());
716     repaintRectangle(scrollRect);
717 }
718
719 LayoutRect RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const LayoutRect& scrollbarRect) const
720 {
721     RenderView* view = this->view();
722     if (!view)
723         return scrollbarRect;
724
725     LayoutRect rect = scrollbarRect;
726
727     LayoutUnit scrollbarLeft = width() - borderRight() - scrollbar->width();
728     LayoutUnit scrollbarTop = borderTop();
729     rect.move(scrollbarLeft, scrollbarTop);
730
731     return view->frameView()->convertFromRenderer(this, rect);
732 }
733
734 LayoutRect RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const LayoutRect& parentRect) const
735 {
736     RenderView* view = this->view();
737     if (!view)
738         return parentRect;
739
740     LayoutRect rect = view->frameView()->convertToRenderer(this, parentRect);
741
742     LayoutUnit scrollbarLeft = width() - borderRight() - scrollbar->width();
743     LayoutUnit scrollbarTop = borderTop();
744     rect.move(-scrollbarLeft, -scrollbarTop);
745     return rect;
746 }
747
748 LayoutPoint RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const LayoutPoint& scrollbarPoint) const
749 {
750     RenderView* view = this->view();
751     if (!view)
752         return scrollbarPoint;
753
754     LayoutPoint point = scrollbarPoint;
755
756     LayoutUnit scrollbarLeft = width() - borderRight() - scrollbar->width();
757     LayoutUnit scrollbarTop = borderTop();
758     point.move(scrollbarLeft, scrollbarTop);
759
760     return view->frameView()->convertFromRenderer(this, point);
761 }
762
763 LayoutPoint RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const LayoutPoint& parentPoint) const
764 {
765     RenderView* view = this->view();
766     if (!view)
767         return parentPoint;
768
769     LayoutPoint point = view->frameView()->convertToRenderer(this, parentPoint);
770
771     LayoutUnit scrollbarLeft = width() - borderRight() - scrollbar->width();
772     LayoutUnit scrollbarTop = borderTop();
773     point.move(-scrollbarLeft, -scrollbarTop);
774     return point;
775 }
776
777 LayoutSize RenderListBox::contentsSize() const
778 {
779     return LayoutSize(scrollWidth(), scrollHeight());
780 }
781
782 LayoutUnit RenderListBox::visibleHeight() const
783 {
784     return height();
785 }
786
787 LayoutUnit RenderListBox::visibleWidth() const
788 {
789     return width();
790 }
791
792 LayoutPoint RenderListBox::currentMousePosition() const
793 {
794     RenderView* view = this->view();
795     if (!view)
796         return LayoutPoint();
797     return view->frameView()->currentMousePosition();
798 }
799
800 bool RenderListBox::shouldSuspendScrollAnimations() const
801 {
802     RenderView* view = this->view();
803     if (!view)
804         return true;
805     return view->frameView()->shouldSuspendScrollAnimations();
806 }
807
808 bool RenderListBox::isOnActivePage() const
809 {
810     return !document()->inPageCache();
811 }
812
813 ScrollableArea* RenderListBox::enclosingScrollableArea() const
814 {
815     // FIXME: Return a RenderLayer that's scrollable.
816     return 0;
817 }
818
819 PassRefPtr<Scrollbar> RenderListBox::createScrollbar()
820 {
821     RefPtr<Scrollbar> widget;
822     bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR);
823     if (hasCustomScrollbarStyle)
824         widget = RenderScrollbar::createCustomScrollbar(this, VerticalScrollbar, this);
825     else {
826         widget = Scrollbar::createNativeScrollbar(this, VerticalScrollbar, theme()->scrollbarControlSizeForPart(ListboxPart));
827         didAddVerticalScrollbar(widget.get());
828     }
829     document()->view()->addChild(widget.get());        
830     return widget.release();
831 }
832
833 void RenderListBox::destroyScrollbar()
834 {
835     if (!m_vBar)
836         return;
837
838     if (!m_vBar->isCustomScrollbar())
839         ScrollableArea::willRemoveVerticalScrollbar(m_vBar.get());
840     m_vBar->removeFromParent();
841     m_vBar->disconnectFromScrollableArea();
842     m_vBar = 0;
843 }
844
845 void RenderListBox::setHasVerticalScrollbar(bool hasScrollbar)
846 {
847     if (hasScrollbar == (m_vBar != 0))
848         return;
849
850     if (hasScrollbar)
851         m_vBar = createScrollbar();
852     else
853         destroyScrollbar();
854
855     if (m_vBar)
856         m_vBar->styleChanged();
857
858 #if ENABLE(DASHBOARD_SUPPORT)
859     // Force an update since we know the scrollbars have changed things.
860     if (document()->hasDashboardRegions())
861         document()->setDashboardRegionsDirty(true);
862 #endif
863 }
864
865 } // namespace WebCore