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