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