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