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