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