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