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