2 * Copyright (C) 2006, 2007, 2008, 2011 Apple Inc. All rights reserved.
3 * 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
31 #include "core/rendering/RenderListBox.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"
64 using namespace HTMLNames;
66 const int rowSpacing = 1;
68 const int optionsSpacingHorizontal = 2;
70 // The minSize constant was originally defined to render scrollbars correctly.
71 // This might vary for different platforms.
72 const int minSize = 4;
74 // Default size when the multiple attribute is present but size attribute is absent.
75 const int defaultSize = 4;
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;
81 RenderListBox::RenderListBox(Element* element)
82 : RenderBlockFlow(element)
83 , m_optionsChanged(true)
84 , m_scrollToRevealSelectionAfterLayout(true)
85 , m_inAutoscroll(false)
90 ASSERT(element->isHTMLElement());
91 ASSERT(element->hasTagName(HTMLNames::selectTag));
93 if (FrameView* frameView = frame()->view())
94 frameView->addScrollableArea(this);
97 RenderListBox::~RenderListBox()
99 setHasVerticalScrollbar(false);
101 if (FrameView* frameView = frame()->view())
102 frameView->removeScrollableArea(this);
105 inline HTMLSelectElement* RenderListBox::selectElement() const
107 return toHTMLSelectElement(node());
110 void RenderListBox::updateFromElement()
112 FontCachePurgePreventer fontCachePurgePreventer;
114 if (m_optionsChanged) {
115 const Vector<HTMLElement*>& listItems = selectElement()->listItems();
116 int size = numItems();
119 for (int i = 0; i < size; ++i) {
120 HTMLElement* element = listItems[i];
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());
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);
142 m_optionsWidth = static_cast<int>(ceilf(width));
143 m_optionsChanged = false;
145 setHasVerticalScrollbar(true);
147 setNeedsLayoutAndPrefWidthsRecalc();
151 void RenderListBox::selectionChanged()
154 if (!m_inAutoscroll) {
155 if (m_optionsChanged || needsLayout())
156 m_scrollToRevealSelectionAfterLayout = true;
158 scrollToRevealSelection();
161 if (AXObjectCache* cache = document().existingAXObjectCache())
162 cache->selectedChildrenChanged(this);
165 void RenderListBox::layout()
167 RenderBlockFlow::layout();
170 bool enabled = numVisibleItems() < numItems();
171 m_vBar->setEnabled(enabled);
172 m_vBar->setProportion(numVisibleItems(), numItems());
174 scrollToOffsetWithoutAnimation(VerticalScrollbar, 0);
179 if (m_scrollToRevealSelectionAfterLayout) {
180 LayoutStateDisabler layoutStateDisabler(view());
181 scrollToRevealSelection();
185 void RenderListBox::scrollToRevealSelection()
187 HTMLSelectElement* select = selectElement();
189 m_scrollToRevealSelectionAfterLayout = false;
191 int firstIndex = select->activeSelectionStartListIndex();
192 if (firstIndex >= 0 && !listIndexIsVisible(select->activeSelectionEndListIndex()))
193 scrollToRevealElementAtListIndex(firstIndex);
196 void RenderListBox::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, LayoutUnit& maxLogicalWidth) const
198 maxLogicalWidth = m_optionsWidth + 2 * optionsSpacingHorizontal;
200 maxLogicalWidth += m_vBar->width();
201 if (!style()->width().isPercent())
202 minLogicalWidth = maxLogicalWidth;
205 void RenderListBox::computePreferredLogicalWidths()
207 ASSERT(!m_optionsChanged);
209 m_minPreferredLogicalWidth = 0;
210 m_maxPreferredLogicalWidth = 0;
212 if (style()->width().isFixed() && style()->width().value() > 0)
213 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentBoxLogicalWidthForBoxSizing(style()->width().value());
215 computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferredLogicalWidth);
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()));
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()));
227 LayoutUnit toAdd = borderAndPaddingWidth();
228 m_minPreferredLogicalWidth += toAdd;
229 m_maxPreferredLogicalWidth += toAdd;
231 clearPreferredLogicalWidthsDirty();
234 int RenderListBox::size() const
236 int specifiedSize = selectElement()->size();
237 if (specifiedSize > 1)
238 return max(minSize, specifiedSize);
243 int RenderListBox::numVisibleItems() const
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());
249 int RenderListBox::numItems() const
251 return selectElement()->listItems().size();
254 LayoutUnit RenderListBox::listHeight() const
256 return itemHeight() * numItems() - rowSpacing;
259 void RenderListBox::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, LogicalExtentComputedValues& computedValues) const
261 LayoutUnit height = itemHeight() * size() - rowSpacing + borderAndPaddingHeight();
262 RenderBox::computeLogicalHeight(height, logicalTop, computedValues);
265 int RenderListBox::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode lineDirection, LinePositionMode linePositionMode) const
267 return RenderBox::baselinePosition(baselineType, firstLine, lineDirection, linePositionMode) - baselineAdjustment;
270 LayoutRect RenderListBox::itemBoundingBoxRect(const LayoutPoint& additionalOffset, int index)
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());
279 void RenderListBox::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
281 if (style()->visibility() != VISIBLE)
284 int listItemsSize = numItems();
286 if (paintInfo.phase == PaintPhaseForeground) {
287 int index = m_indexOffset;
288 while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
289 paintItemForeground(paintInfo, paintOffset, index);
294 // Paint the children.
295 RenderBlock::paintObject(paintInfo, paintOffset);
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);
304 case PaintPhaseBlockBackground:
305 if (!m_vBar->isOverlayScrollbar())
306 paintScrollbar(paintInfo, paintOffset);
308 case PaintPhaseChildBlockBackground:
309 case PaintPhaseChildBlockBackgrounds: {
310 int index = m_indexOffset;
311 while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
312 paintItemBackground(paintInfo, paintOffset, index);
322 void RenderListBox::addFocusRingRects(Vector<IntRect>& rects, const LayoutPoint& additionalOffset, const RenderLayerModelObject* paintContainer)
324 if (!isSpatialNavigationEnabled(frame()))
325 return RenderBlock::addFocusRingRects(rects, additionalOffset, paintContainer);
327 HTMLSelectElement* select = selectElement();
329 // Focus the last selected item.
330 int selectedItem = select->activeSelectionEndListIndex();
331 if (selectedItem >= 0) {
332 rects.append(pixelSnappedIntRect(itemBoundingBoxRect(additionalOffset, selectedItem)));
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)));
348 int RenderListBox::scrollbarLeft() const
350 int scrollbarLeft = 0;
351 if (style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
352 scrollbarLeft = borderLeft();
354 scrollbarLeft = width() - borderRight() - m_vBar->width();
355 return scrollbarLeft;
358 void RenderListBox::paintScrollbar(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
361 IntRect scrollRect = pixelSnappedIntRect(paintOffset.x() + scrollbarLeft(),
362 paintOffset.y() + borderTop(),
364 height() - (borderTop() + borderBottom()));
365 m_vBar->setFrameRect(scrollRect);
366 m_vBar->paint(paintInfo.context, paintInfo.rect);
370 static LayoutSize itemOffsetForAlignment(TextRun textRun, RenderStyle* itemStyle, Font itemFont, LayoutRect itemBoudingBox)
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;
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);
386 offset.setWidth(optionsSpacingHorizontal);
390 void RenderListBox::paintItemForeground(PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listIndex)
392 FontCachePurgePreventer fontCachePurgePreventer;
394 HTMLSelectElement* select = selectElement();
396 const Vector<HTMLElement*>& listItems = select->listItems();
397 HTMLElement* element = listItems[listIndex];
399 RenderStyle* itemStyle = element->renderStyle();
403 if (itemStyle->visibility() == HIDDEN)
407 bool isOptionElement = element->hasTagName(optionTag);
409 itemText = toHTMLOptionElement(element)->textIndentedToRespectGroupLabel();
410 else if (isHTMLOptGroupElement(element))
411 itemText = toHTMLOptGroupElement(element)->groupLabelText();
412 applyTextTransform(style(), itemText, ' ');
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();
423 paintInfo.context->setFillColor(textColor);
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));
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());
437 // Draw the item text
438 TextRunPaintInfo textRunPaintInfo(textRun);
439 textRunPaintInfo.bounds = r;
440 paintInfo.context->drawBidiText(itemFont, textRunPaintInfo, roundedIntPoint(r.location()));
443 void RenderListBox::paintItemBackground(PaintInfo& paintInfo, const LayoutPoint& paintOffset, int listIndex)
445 const Vector<HTMLElement*>& listItems = selectElement()->listItems();
446 HTMLElement* element = listItems[listIndex];
449 if (element->hasTagName(optionTag) && toHTMLOptionElement(element)->selected()) {
450 if (frame()->selection().isFocusedAndActive() && document().focusedElement() == node())
451 backColor = RenderTheme::theme().activeListBoxSelectionBackgroundColor();
453 backColor = RenderTheme::theme().inactiveListBoxSelectionBackgroundColor();
455 backColor = element->renderStyle() ? resolveColor(element->renderStyle(), CSSPropertyBackgroundColor) : resolveColor(CSSPropertyBackgroundColor);
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);
466 bool RenderListBox::isPointInOverflowControl(HitTestResult& result, const LayoutPoint& locationInContainer, const LayoutPoint& accumulatedOffset)
468 if (!m_vBar || !m_vBar->shouldParticipateInHitTesting())
471 LayoutRect vertRect(accumulatedOffset.x() + scrollbarLeft(),
472 accumulatedOffset.y() + borderTop(),
474 height() - borderTop() - borderBottom());
476 if (vertRect.contains(locationInContainer)) {
477 result.setScrollbar(m_vBar.get());
483 int RenderListBox::listIndexAtOffset(const LayoutSize& offset)
488 if (offset.height() < borderTop() + paddingTop() || offset.height() > height() - paddingBottom() - borderBottom())
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)
498 int newOffset = (offset.height() - borderTop() - paddingTop()) / itemHeight() + m_indexOffset;
499 return newOffset < numItems() ? newOffset : -1;
502 void RenderListBox::panScroll(const IntPoint& panStartMousePosition)
504 const int maxSpeed = 20;
505 const int iconRadius = 7;
506 const int speedReducer = 4;
508 // FIXME: This doesn't work correctly with transforms.
509 FloatPoint absOffset = localToAbsolute();
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;
517 previousMousePosition = lastKnownMousePosition;
519 int yDelta = lastKnownMousePosition.y() - panStartMousePosition.y();
521 // If the point is too far from the center we limit the speed
522 yDelta = max<int>(min<int>(yDelta, maxSpeed), -maxSpeed);
524 if (abs(yDelta) < iconRadius) // at the center we let the space for the icon
528 //offsetY = view()->viewHeight();
529 absOffset.move(0, listHeight());
533 // Let's attenuate the speed
534 yDelta /= speedReducer;
536 IntPoint scrollPoint(0, 0);
537 scrollPoint.setY(absOffset.y() + yDelta);
538 int newOffset = scrollToward(scrollPoint);
542 m_inAutoscroll = true;
543 HTMLSelectElement* select = selectElement();
544 select->updateListBoxSelection(!select->multiple());
545 m_inAutoscroll = false;
548 int RenderListBox::scrollToward(const IntPoint& destination)
550 // FIXME: This doesn't work correctly with transforms.
551 FloatPoint absPos = localToAbsolute();
552 IntSize positionOffset = roundedIntSize(destination - absPos);
554 int rows = numVisibleItems();
555 int offset = m_indexOffset;
557 if (positionOffset.height() < borderTop() + paddingTop() && scrollToRevealElementAtListIndex(offset - 1))
560 if (positionOffset.height() > height() - paddingBottom() - borderBottom() && scrollToRevealElementAtListIndex(offset + rows))
561 return offset + rows - 1;
563 return listIndexAtOffset(positionOffset);
566 void RenderListBox::autoscroll(const IntPoint&)
568 IntPoint pos = frame()->view()->windowToContents(frame()->eventHandler().lastKnownMousePosition());
570 int endIndex = scrollToward(pos);
571 if (selectElement()->isDisabledFormControl())
575 HTMLSelectElement* select = selectElement();
576 m_inAutoscroll = true;
578 if (!select->multiple())
579 select->setActiveSelectionAnchorIndex(endIndex);
581 select->setActiveSelectionEndIndex(endIndex);
582 select->updateListBoxSelection(!select->multiple());
583 m_inAutoscroll = false;
587 void RenderListBox::stopAutoscroll()
589 if (selectElement()->isDisabledFormControl())
592 selectElement()->listBoxOnChange();
595 bool RenderListBox::scrollToRevealElementAtListIndex(int index)
597 if (index < 0 || index >= numItems() || listIndexIsVisible(index))
601 if (index < m_indexOffset)
604 newOffset = index - numVisibleItems() + 1;
606 scrollToOffsetWithoutAnimation(VerticalScrollbar, newOffset);
611 bool RenderListBox::listIndexIsVisible(int index)
613 return index >= m_indexOffset && index < m_indexOffset + numVisibleItems();
616 bool RenderListBox::scrollImpl(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
618 return ScrollableArea::scroll(direction, granularity, multiplier);
621 void RenderListBox::valueChanged(unsigned listIndex)
623 HTMLSelectElement* element = selectElement();
624 element->setSelectedIndex(element->listToOptionIndex(listIndex));
625 element->dispatchFormControlChangeEvent();
628 int RenderListBox::scrollSize(ScrollbarOrientation orientation) const
630 return orientation == VerticalScrollbar ? (numItems() - numVisibleItems()) : 0;
633 IntPoint RenderListBox::scrollPosition() const
635 return IntPoint(0, m_indexOffset);
638 void RenderListBox::setScrollOffset(const IntPoint& offset)
640 scrollTo(offset.y());
643 void RenderListBox::scrollTo(int newOffset)
645 if (newOffset == m_indexOffset)
648 m_indexOffset = newOffset;
650 node()->document().enqueueScrollEventForNode(node());
653 LayoutUnit RenderListBox::itemHeight() const
655 return style()->fontMetrics().height() + rowSpacing;
658 int RenderListBox::verticalScrollbarWidth() const
660 return m_vBar && !m_vBar->isOverlayScrollbar() ? m_vBar->width() : 0;
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
667 // There is no horizontal scrolling allowed.
668 return pixelSnappedClientWidth();
671 int RenderListBox::scrollHeight() const
673 return max(pixelSnappedClientHeight(), roundToInt(listHeight()));
676 int RenderListBox::scrollLeft() const
681 void RenderListBox::setScrollLeft(int)
685 int RenderListBox::scrollTop() const
687 return m_indexOffset * itemHeight();
690 void RenderListBox::setScrollTop(int newTop)
692 // Determine an index and scroll to it.
693 int index = newTop / itemHeight();
694 if (index < 0 || index >= numItems() || index == m_indexOffset)
697 scrollToOffsetWithoutAnimation(VerticalScrollbar, index);
700 bool RenderListBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
702 if (!RenderBlock::nodeAtPoint(request, result, locationInContainer, accumulatedOffset, hitTestAction))
704 const Vector<HTMLElement*>& listItems = selectElement()->listItems();
705 int size = numItems();
706 LayoutPoint adjustedLocation = accumulatedOffset + location();
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));
723 LayoutRect RenderListBox::controlClipRect(const LayoutPoint& additionalOffset) const
725 LayoutRect clipRect = contentBoxRect();
726 if (style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
727 clipRect.moveBy(additionalOffset + LayoutPoint(m_vBar->width(), 0));
729 clipRect.moveBy(additionalOffset);
733 bool RenderListBox::isActive() const
735 Page* page = frame()->page();
736 return page && page->focusController().isActive();
739 void RenderListBox::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
741 IntRect scrollRect = rect;
742 if (style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft())
743 scrollRect.move(borderLeft(), borderTop());
745 scrollRect.move(width() - borderRight() - scrollbar->width(), borderTop());
746 repaintRectangle(scrollRect);
749 IntRect RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const
751 RenderView* view = this->view();
753 return scrollbarRect;
755 IntRect rect = scrollbarRect;
757 int scrollbarTop = borderTop();
758 rect.move(scrollbarLeft(), scrollbarTop);
760 return view->frameView()->convertFromRenderer(this, rect);
763 IntRect RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const
765 RenderView* view = this->view();
769 IntRect rect = view->frameView()->convertToRenderer(this, parentRect);
771 int scrollbarTop = borderTop();
772 rect.move(-scrollbarLeft(), -scrollbarTop);
776 IntPoint RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const
778 RenderView* view = this->view();
780 return scrollbarPoint;
782 IntPoint point = scrollbarPoint;
784 int scrollbarTop = borderTop();
785 point.move(scrollbarLeft(), scrollbarTop);
787 return view->frameView()->convertFromRenderer(this, point);
790 IntPoint RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const
792 RenderView* view = this->view();
796 IntPoint point = view->frameView()->convertToRenderer(this, parentPoint);
798 int scrollbarTop = borderTop();
799 point.move(-scrollbarLeft(), -scrollbarTop);
803 IntSize RenderListBox::contentsSize() const
805 return IntSize(scrollWidth(), scrollHeight());
808 int RenderListBox::visibleHeight() const
813 int RenderListBox::visibleWidth() const
818 IntPoint RenderListBox::lastKnownMousePosition() const
820 RenderView* view = this->view();
823 return view->frameView()->lastKnownMousePosition();
826 bool RenderListBox::shouldSuspendScrollAnimations() const
828 RenderView* view = this->view();
831 return view->frameView()->shouldSuspendScrollAnimations();
834 bool RenderListBox::scrollbarsCanBeActive() const
836 RenderView* view = this->view();
839 return view->frameView()->scrollbarsCanBeActive();
842 IntPoint RenderListBox::minimumScrollPosition() const
847 IntPoint RenderListBox::maximumScrollPosition() const
849 return IntPoint(0, numItems() - numVisibleItems());
852 bool RenderListBox::userInputScrollable(ScrollbarOrientation orientation) const
854 return orientation == VerticalScrollbar;
857 bool RenderListBox::shouldPlaceVerticalScrollbarOnLeft() const
862 int RenderListBox::lineStep(ScrollbarOrientation) const
867 int RenderListBox::pageStep(ScrollbarOrientation orientation) const
869 return max(1, numVisibleItems() - 1);
872 float RenderListBox::pixelStep(ScrollbarOrientation) const
874 return 1.0f / itemHeight();
877 ScrollableArea* RenderListBox::enclosingScrollableArea() const
879 // FIXME: Return a RenderLayer that's scrollable.
883 IntRect RenderListBox::scrollableAreaBoundingBox() const
885 return absoluteBoundingBoxRect();
888 PassRefPtr<Scrollbar> RenderListBox::createScrollbar()
890 RefPtr<Scrollbar> widget;
891 bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR);
892 if (hasCustomScrollbarStyle)
893 widget = RenderScrollbar::createCustomScrollbar(this, VerticalScrollbar, this->node());
895 widget = Scrollbar::create(this, VerticalScrollbar, RenderTheme::theme().scrollbarControlSizeForPart(ListboxPart));
896 didAddVerticalScrollbar(widget.get());
898 document().view()->addChild(widget.get());
899 return widget.release();
902 void RenderListBox::destroyScrollbar()
907 if (!m_vBar->isCustomScrollbar())
908 ScrollableArea::willRemoveVerticalScrollbar(m_vBar.get());
909 m_vBar->removeFromParent();
910 m_vBar->disconnectFromScrollableArea();
914 void RenderListBox::setHasVerticalScrollbar(bool hasScrollbar)
916 if (hasScrollbar == (m_vBar != 0))
920 m_vBar = createScrollbar();
925 m_vBar->styleChanged();
927 // Force an update since we know the scrollbars have changed things.
928 if (document().hasAnnotatedRegions())
929 document().setAnnotatedRegionsDirty(true);
932 } // namespace WebCore