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 |