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