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 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
56 #include "platform/fonts/FontCache.h" | 56 #include "platform/fonts/FontCache.h" |
57 #include "platform/graphics/GraphicsContext.h" | 57 #include "platform/graphics/GraphicsContext.h" |
58 #include "platform/scroll/Scrollbar.h" | 58 #include "platform/scroll/Scrollbar.h" |
59 #include "platform/text/BidiTextRun.h" | 59 #include "platform/text/BidiTextRun.h" |
60 #include <math.h> | 60 #include <math.h> |
61 | 61 |
62 namespace WebCore { | 62 namespace WebCore { |
63 | 63 |
64 using namespace HTMLNames; | 64 using namespace HTMLNames; |
65 | 65 |
66 const int rowSpacing = 1; | |
67 | |
68 const int optionsSpacingHorizontal = 2; | |
69 | |
70 // The minSize constant was originally defined to render scrollbars correctly. | 66 // The minSize constant was originally defined to render scrollbars correctly. |
71 // This might vary for different platforms. | 67 // This might vary for different platforms. |
72 const int minSize = 4; | 68 const int minSize = 4; |
73 | 69 |
74 // Default size when the multiple attribute is present but size attribute is abs
ent. | 70 // Default size when the multiple attribute is present but size attribute is abs
ent. |
75 const int defaultSize = 4; | 71 const int defaultSize = 4; |
76 | 72 |
77 // FIXME: This hardcoded baselineAdjustment is what we used to do for the old | 73 const int defaultPaddingBottom = 1; |
78 // widget, but I'm not sure this is right for the new control. | |
79 const int baselineAdjustment = 7; | |
80 | 74 |
81 RenderListBox::RenderListBox(Element* element) | 75 RenderListBox::RenderListBox(Element* element) |
82 : RenderBlockFlow(element) | 76 : RenderBlockFlow(element) |
83 , m_optionsChanged(true) | |
84 , m_scrollToRevealSelectionAfterLayout(true) | |
85 , m_inAutoscroll(false) | |
86 , m_optionsWidth(0) | |
87 , m_indexOffset(0) | |
88 , m_listItemCount(0) | |
89 { | 77 { |
90 ASSERT(element); | 78 ASSERT(element); |
91 ASSERT(element->isHTMLElement()); | 79 ASSERT(element->isHTMLElement()); |
92 ASSERT(isHTMLSelectElement(element)); | 80 ASSERT(isHTMLSelectElement(element)); |
93 | |
94 if (FrameView* frameView = frame()->view()) | |
95 frameView->addScrollableArea(this); | |
96 } | 81 } |
97 | 82 |
98 RenderListBox::~RenderListBox() | 83 RenderListBox::~RenderListBox() |
99 { | 84 { |
100 setHasVerticalScrollbar(false); | |
101 | |
102 if (FrameView* frameView = frame()->view()) | |
103 frameView->removeScrollableArea(this); | |
104 } | |
105 | |
106 // FIXME: Instead of this hack we should add a ShadowRoot to <select> with no in
sertion point | |
107 // to prevent children from rendering. | |
108 bool RenderListBox::isChildAllowed(RenderObject* object, RenderStyle*) const | |
109 { | |
110 return object->isAnonymous() && !object->isRenderFullScreen(); | |
111 } | 85 } |
112 | 86 |
113 inline HTMLSelectElement* RenderListBox::selectElement() const | 87 inline HTMLSelectElement* RenderListBox::selectElement() const |
114 { | 88 { |
115 return toHTMLSelectElement(node()); | 89 return toHTMLSelectElement(node()); |
116 } | 90 } |
117 | 91 |
118 void RenderListBox::updateFromElement() | |
119 { | |
120 FontCachePurgePreventer fontCachePurgePreventer; | |
121 if (m_optionsChanged) { | |
122 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = se
lectElement()->listItems(); | |
123 int size = static_cast<int>(listItems.size()); | |
124 | |
125 float width = 0; | |
126 m_listItemCount = 0; | |
127 for (int i = 0; i < size; ++i) { | |
128 const HTMLElement& element = *listItems[i]; | |
129 | |
130 String text; | |
131 Font itemFont = style()->font(); | |
132 if (isHTMLOptionElement(element)) { | |
133 const HTMLOptionElement& optionElement = toHTMLOptionElement(ele
ment); | |
134 if (optionElement.isDisplayNone()) | |
135 continue; | |
136 text = optionElement.textIndentedToRespectGroupLabel(); | |
137 ++m_listItemCount; | |
138 } else if (isHTMLOptGroupElement(element)) { | |
139 if (toHTMLOptGroupElement(element).isDisplayNone()) | |
140 continue; | |
141 text = toHTMLOptGroupElement(element).groupLabelText(); | |
142 FontDescription d = itemFont.fontDescription(); | |
143 d.setWeight(d.bolderWeight()); | |
144 itemFont = Font(d); | |
145 itemFont.update(document().styleEngine()->fontSelector()); | |
146 ++m_listItemCount; | |
147 } else if (isHTMLHRElement(element)) { | |
148 // HTMLSelect adds it to its list, so we will also add it to mat
ch the count. | |
149 ++m_listItemCount; | |
150 continue; | |
151 } | |
152 | |
153 if (!text.isEmpty()) { | |
154 applyTextTransform(style(), text, ' '); | |
155 | |
156 bool hasStrongDirectionality; | |
157 TextDirection direction = determineDirectionality(text, hasStron
gDirectionality); | |
158 TextRun textRun = constructTextRun(this, itemFont, text, style()
, TextRun::AllowTrailingExpansion); | |
159 if (hasStrongDirectionality) | |
160 textRun.setDirection(direction); | |
161 float textWidth = itemFont.width(textRun); | |
162 width = std::max(width, textWidth); | |
163 } | |
164 } | |
165 m_optionsWidth = static_cast<int>(ceilf(width)); | |
166 m_optionsChanged = false; | |
167 | |
168 setHasVerticalScrollbar(true); | |
169 | |
170 setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation(); | |
171 } | |
172 } | |
173 | |
174 void RenderListBox::selectionChanged() | |
175 { | |
176 paintInvalidationForWholeRenderer(); | |
177 if (!m_inAutoscroll) { | |
178 if (m_optionsChanged || needsLayout()) | |
179 m_scrollToRevealSelectionAfterLayout = true; | |
180 else | |
181 scrollToRevealSelection(); | |
182 } | |
183 | |
184 if (AXObjectCache* cache = document().existingAXObjectCache()) | |
185 cache->selectedChildrenChanged(this); | |
186 } | |
187 | |
188 void RenderListBox::layout() | |
189 { | |
190 RenderBlockFlow::layout(); | |
191 | |
192 if (m_vBar) { | |
193 bool enabled = numVisibleItems() < numItems(); | |
194 m_vBar->setEnabled(enabled); | |
195 m_vBar->setProportion(numVisibleItems(), numItems()); | |
196 if (!enabled) { | |
197 scrollToOffsetWithoutAnimation(VerticalScrollbar, 0); | |
198 m_indexOffset = 0; | |
199 } | |
200 } | |
201 | |
202 if (m_scrollToRevealSelectionAfterLayout) | |
203 scrollToRevealSelection(); | |
204 } | |
205 | |
206 void RenderListBox::invalidateTreeIfNeeded(const PaintInvalidationState& paintIn
validationState) | |
207 { | |
208 repaintScrollbarIfNeeded(); | |
209 RenderBox::invalidateTreeIfNeeded(paintInvalidationState); | |
210 } | |
211 | |
212 void RenderListBox::scrollToRevealSelection() | |
213 { | |
214 HTMLSelectElement* select = selectElement(); | |
215 | |
216 m_scrollToRevealSelectionAfterLayout = false; | |
217 | |
218 int firstIndex = listIndexToRenderListBoxIndex(select->activeSelectionStartL
istIndex()); | |
219 int lastIndex = listIndexToRenderListBoxIndex(select->activeSelectionEndList
Index()); | |
220 if (firstIndex >= 0 && !listIndexIsVisible(lastIndex)) | |
221 scrollToRevealElementAtListIndexInternal(firstIndex); | |
222 } | |
223 | |
224 void RenderListBox::computeIntrinsicLogicalWidths(LayoutUnit& minLogicalWidth, L
ayoutUnit& maxLogicalWidth) const | |
225 { | |
226 maxLogicalWidth = m_optionsWidth + 2 * optionsSpacingHorizontal + verticalSc
rollbarWidth(); | |
227 if (!style()->width().isPercent()) | |
228 minLogicalWidth = maxLogicalWidth; | |
229 } | |
230 | |
231 void RenderListBox::computePreferredLogicalWidths() | |
232 { | |
233 ASSERT(!m_optionsChanged); | |
234 | |
235 m_minPreferredLogicalWidth = 0; | |
236 m_maxPreferredLogicalWidth = 0; | |
237 RenderStyle* styleToUse = style(); | |
238 | |
239 if (styleToUse->width().isFixed() && styleToUse->width().value() > 0) | |
240 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = adjustContentB
oxLogicalWidthForBoxSizing(styleToUse->width().value()); | |
241 else | |
242 computeIntrinsicLogicalWidths(m_minPreferredLogicalWidth, m_maxPreferred
LogicalWidth); | |
243 | |
244 if (styleToUse->minWidth().isFixed() && styleToUse->minWidth().value() > 0)
{ | |
245 m_maxPreferredLogicalWidth = std::max(m_maxPreferredLogicalWidth, adjust
ContentBoxLogicalWidthForBoxSizing(styleToUse->minWidth().value())); | |
246 m_minPreferredLogicalWidth = std::max(m_minPreferredLogicalWidth, adjust
ContentBoxLogicalWidthForBoxSizing(styleToUse->minWidth().value())); | |
247 } | |
248 | |
249 if (styleToUse->maxWidth().isFixed()) { | |
250 m_maxPreferredLogicalWidth = std::min(m_maxPreferredLogicalWidth, adjust
ContentBoxLogicalWidthForBoxSizing(styleToUse->maxWidth().value())); | |
251 m_minPreferredLogicalWidth = std::min(m_minPreferredLogicalWidth, adjust
ContentBoxLogicalWidthForBoxSizing(styleToUse->maxWidth().value())); | |
252 } | |
253 | |
254 LayoutUnit toAdd = borderAndPaddingWidth(); | |
255 m_minPreferredLogicalWidth += toAdd; | |
256 m_maxPreferredLogicalWidth += toAdd; | |
257 | |
258 clearPreferredLogicalWidthsDirty(); | |
259 } | |
260 | |
261 int RenderListBox::size() const | 92 int RenderListBox::size() const |
262 { | 93 { |
263 int specifiedSize = selectElement()->size(); | 94 int specifiedSize = selectElement()->size(); |
264 if (specifiedSize > 1) | 95 if (specifiedSize > 1) |
265 return std::max(minSize, specifiedSize); | 96 return max(minSize, specifiedSize); |
266 | 97 |
267 return defaultSize; | 98 return defaultSize; |
268 } | 99 } |
269 | 100 |
270 int RenderListBox::numVisibleItems() const | 101 LayoutUnit RenderListBox::defaultItemHeight() const |
271 { | 102 { |
272 // Only count fully visible rows. But don't return 0 even if only part of a
row shows. | 103 return style()->fontMetrics().height() + defaultPaddingBottom; |
273 return std::max<int>(1, (contentHeight() + rowSpacing) / itemHeight()); | |
274 } | 104 } |
275 | 105 |
276 int RenderListBox::numItems() const | 106 LayoutUnit RenderListBox::itemHeight() const |
277 { | 107 { |
278 return m_listItemCount; | 108 HTMLSelectElement* select = selectElement(); |
279 } | 109 if (!select) |
280 | 110 return 0; |
281 LayoutUnit RenderListBox::listHeight() const | 111 RenderObject* baseItemRenderer = firstChild(); |
282 { | 112 if (!baseItemRenderer) |
283 return itemHeight() * numItems() - rowSpacing; | 113 return defaultItemHeight(); |
| 114 if (baseItemRenderer->node() && isHTMLOptGroupElement(baseItemRenderer->node
())) |
| 115 baseItemRenderer = baseItemRenderer->slowFirstChild(); |
| 116 if (!baseItemRenderer || !baseItemRenderer->isBox()) |
| 117 return defaultItemHeight(); |
| 118 return toRenderBox(baseItemRenderer)->height(); |
284 } | 119 } |
285 | 120 |
286 void RenderListBox::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, Logi
calExtentComputedValues& computedValues) const | 121 void RenderListBox::computeLogicalHeight(LayoutUnit, LayoutUnit logicalTop, Logi
calExtentComputedValues& computedValues) const |
287 { | 122 { |
288 LayoutUnit height = itemHeight() * size() - rowSpacing; | 123 LayoutUnit height = itemHeight() * size(); |
289 // FIXME: The item height should have been added before updateLogicalHeight
was called to avoid this hack. | 124 // FIXME: The item height should have been added before updateLogicalHeight
was called to avoid this hack. |
290 updateIntrinsicContentLogicalHeight(height); | 125 updateIntrinsicContentLogicalHeight(height); |
291 | 126 |
292 height += borderAndPaddingHeight(); | 127 height += borderAndPaddingHeight(); |
293 | 128 |
294 RenderBox::computeLogicalHeight(height, logicalTop, computedValues); | 129 RenderBox::computeLogicalHeight(height, logicalTop, computedValues); |
295 } | 130 } |
296 | 131 |
297 int RenderListBox::baselinePosition(FontBaseline baselineType, bool firstLine, L
ineDirectionMode lineDirection, LinePositionMode linePositionMode) const | |
298 { | |
299 return RenderBox::baselinePosition(baselineType, firstLine, lineDirection, l
inePositionMode) - baselineAdjustment; | |
300 } | |
301 | |
302 LayoutRect RenderListBox::itemBoundingBoxRectInternal(const LayoutPoint& additio
nalOffset, int index) const | |
303 { | |
304 // For RTL, items start after the left-side vertical scrollbar. | |
305 int scrollbarOffset = style()->shouldPlaceBlockDirectionScrollbarOnLogicalLe
ft() ? verticalScrollbarWidth() : 0; | |
306 return LayoutRect(additionalOffset.x() + borderLeft() + paddingLeft() + scro
llbarOffset, | |
307 additionalOffset.y() + borderTop() + paddingTop() + itemHeight() * (inde
x - m_indexOffset), | |
308 contentWidth(), itemHeight()); | |
309 } | |
310 | |
311 void RenderListBox::paintObject(PaintInfo& paintInfo, const LayoutPoint& paintOf
fset) | |
312 { | |
313 if (style()->visibility() != VISIBLE) | |
314 return; | |
315 | |
316 int listItemsSize = numItems(); | |
317 | |
318 if (paintInfo.phase == PaintPhaseForeground) { | |
319 int index = m_indexOffset; | |
320 while (index < listItemsSize && index <= m_indexOffset + numVisibleItems
()) { | |
321 paintItemForeground(paintInfo, paintOffset, index); | |
322 index++; | |
323 } | |
324 } | |
325 | |
326 // Paint the children. | |
327 RenderBlockFlow::paintObject(paintInfo, paintOffset); | |
328 | |
329 switch (paintInfo.phase) { | |
330 // Depending on whether we have overlay scrollbars they | |
331 // get rendered in the foreground or background phases | |
332 case PaintPhaseForeground: | |
333 if (m_vBar->isOverlayScrollbar()) | |
334 paintScrollbar(paintInfo, paintOffset); | |
335 break; | |
336 case PaintPhaseBlockBackground: | |
337 if (!m_vBar->isOverlayScrollbar()) | |
338 paintScrollbar(paintInfo, paintOffset); | |
339 break; | |
340 case PaintPhaseChildBlockBackground: | |
341 case PaintPhaseChildBlockBackgrounds: { | |
342 int index = m_indexOffset; | |
343 while (index < listItemsSize && index <= m_indexOffset + numVisibleItems
()) { | |
344 paintItemBackground(paintInfo, paintOffset, index); | |
345 index++; | |
346 } | |
347 break; | |
348 } | |
349 default: | |
350 break; | |
351 } | |
352 } | |
353 | |
354 void RenderListBox::addFocusRingRects(Vector<IntRect>& rects, const LayoutPoint&
additionalOffset, const RenderLayerModelObject* paintContainer) | |
355 { | |
356 if (!isSpatialNavigationEnabled(frame())) | |
357 return RenderBlockFlow::addFocusRingRects(rects, additionalOffset, paint
Container); | |
358 | |
359 HTMLSelectElement* select = selectElement(); | |
360 | |
361 // Focus the last selected item. | |
362 int selectedItem = select->activeSelectionEndListIndex(); | |
363 if (selectedItem >= 0) { | |
364 rects.append(pixelSnappedIntRect(itemBoundingBoxRectInternal(additionalO
ffset, selectedItem))); | |
365 return; | |
366 } | |
367 | |
368 // No selected items, find the first non-disabled item. | |
369 int size = numItems(); | |
370 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = select
->listItems(); | |
371 for (int i = 0; i < size; ++i) { | |
372 HTMLElement* element = listItems[renderListBoxIndexToListIndex(i)]; | |
373 if (isHTMLOptionElement(*element) && !element->isDisabledFormControl())
{ | |
374 rects.append(pixelSnappedIntRect(itemBoundingBoxRectInternal(additio
nalOffset, i))); | |
375 return; | |
376 } | |
377 } | |
378 } | |
379 | |
380 int RenderListBox::scrollbarLeft() const | |
381 { | |
382 int scrollbarLeft = 0; | |
383 if (style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) | |
384 scrollbarLeft = borderLeft(); | |
385 else | |
386 scrollbarLeft = width() - borderRight() - (m_vBar ? m_vBar->width() : 0)
; | |
387 return scrollbarLeft; | |
388 } | |
389 | |
390 void RenderListBox::paintScrollbar(PaintInfo& paintInfo, const LayoutPoint& pain
tOffset) | |
391 { | |
392 if (m_vBar) { | |
393 IntRect scrollRect = pixelSnappedIntRect(paintOffset.x() + scrollbarLeft
(), | |
394 paintOffset.y() + borderTop(), | |
395 m_vBar->width(), | |
396 height() - (borderTop() + borderBottom())); | |
397 m_vBar->setFrameRect(scrollRect); | |
398 m_vBar->paint(paintInfo.context, paintInfo.rect); | |
399 } | |
400 } | |
401 | |
402 static LayoutSize itemOffsetForAlignment(TextRun textRun, RenderStyle* itemStyle
, Font itemFont, LayoutRect itemBoudingBox) | |
403 { | |
404 ETextAlign actualAlignment = itemStyle->textAlign(); | |
405 // FIXME: Firefox doesn't respect JUSTIFY. Should we? | |
406 // FIXME: Handle TAEND here | |
407 if (actualAlignment == TASTART || actualAlignment == JUSTIFY) | |
408 actualAlignment = itemStyle->isLeftToRightDirection() ? LEFT : RIGHT; | |
409 | |
410 LayoutSize offset = LayoutSize(0, itemFont.fontMetrics().ascent()); | |
411 if (actualAlignment == RIGHT || actualAlignment == WEBKIT_RIGHT) { | |
412 float textWidth = itemFont.width(textRun); | |
413 offset.setWidth(itemBoudingBox.width() - textWidth - optionsSpacingHoriz
ontal); | |
414 } else if (actualAlignment == CENTER || actualAlignment == WEBKIT_CENTER) { | |
415 float textWidth = itemFont.width(textRun); | |
416 offset.setWidth((itemBoudingBox.width() - textWidth) / 2); | |
417 } else | |
418 offset.setWidth(optionsSpacingHorizontal); | |
419 return offset; | |
420 } | |
421 | |
422 void RenderListBox::paintItemForeground(PaintInfo& paintInfo, const LayoutPoint&
paintOffset, int listIndex) | |
423 { | |
424 FontCachePurgePreventer fontCachePurgePreventer; | |
425 | |
426 HTMLSelectElement* select = selectElement(); | |
427 | |
428 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = select
->listItems(); | |
429 HTMLElement* element = listItems[renderListBoxIndexToListIndex(listIndex)]; | |
430 | |
431 RenderStyle* itemStyle = element->renderStyle(); | |
432 if (!itemStyle) | |
433 itemStyle = style(); | |
434 | |
435 if (itemStyle->visibility() == HIDDEN) | |
436 return; | |
437 | |
438 String itemText; | |
439 bool isOptionElement = isHTMLOptionElement(*element); | |
440 if (isOptionElement) | |
441 itemText = toHTMLOptionElement(*element).textIndentedToRespectGroupLabel
(); | |
442 else if (isHTMLOptGroupElement(*element)) | |
443 itemText = toHTMLOptGroupElement(*element).groupLabelText(); | |
444 applyTextTransform(style(), itemText, ' '); | |
445 | |
446 Color textColor = element->renderStyle() ? resolveColor(element->renderStyle
(), CSSPropertyColor) : resolveColor(CSSPropertyColor); | |
447 if (isOptionElement && ((toHTMLOptionElement(*element).selected() && select-
>suggestedIndex() < 0) || listIndex == select->suggestedIndex())) { | |
448 if (frame()->selection().isFocusedAndActive() && document().focusedEleme
nt() == node()) | |
449 textColor = RenderTheme::theme().activeListBoxSelectionForegroundCol
or(); | |
450 // Honor the foreground color for disabled items | |
451 else if (!element->isDisabledFormControl() && !select->isDisabledFormCon
trol()) | |
452 textColor = RenderTheme::theme().inactiveListBoxSelectionForegroundC
olor(); | |
453 } | |
454 | |
455 paintInfo.context->setFillColor(textColor); | |
456 | |
457 TextRun textRun(itemText, 0, 0, TextRun::AllowTrailingExpansion, itemStyle->
direction(), isOverride(itemStyle->unicodeBidi()), true); | |
458 Font itemFont = style()->font(); | |
459 LayoutRect r = itemBoundingBoxRectInternal(paintOffset, listIndex); | |
460 r.move(itemOffsetForAlignment(textRun, itemStyle, itemFont, r)); | |
461 | |
462 if (isHTMLOptGroupElement(*element)) { | |
463 FontDescription d = itemFont.fontDescription(); | |
464 d.setWeight(d.bolderWeight()); | |
465 itemFont = Font(d); | |
466 itemFont.update(document().styleEngine()->fontSelector()); | |
467 } | |
468 | |
469 // Draw the item text | |
470 TextRunPaintInfo textRunPaintInfo(textRun); | |
471 textRunPaintInfo.bounds = r; | |
472 paintInfo.context->drawBidiText(itemFont, textRunPaintInfo, roundedIntPoint(
r.location())); | |
473 } | |
474 | |
475 void RenderListBox::paintItemBackground(PaintInfo& paintInfo, const LayoutPoint&
paintOffset, int listIndex) | |
476 { | |
477 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = select
Element()->listItems(); | |
478 HTMLElement* element = listItems[renderListBoxIndexToListIndex(listIndex)]; | |
479 | |
480 Color backColor; | |
481 if (isHTMLOptionElement(*element) && ((toHTMLOptionElement(*element).selecte
d() && selectElement()->suggestedIndex() < 0) || listIndex == selectElement()->s
uggestedIndex())) { | |
482 if (frame()->selection().isFocusedAndActive() && document().focusedEleme
nt() == node()) | |
483 backColor = RenderTheme::theme().activeListBoxSelectionBackgroundCol
or(); | |
484 else | |
485 backColor = RenderTheme::theme().inactiveListBoxSelectionBackgroundC
olor(); | |
486 } else { | |
487 backColor = element->renderStyle() ? resolveColor(element->renderStyle()
, CSSPropertyBackgroundColor) : resolveColor(CSSPropertyBackgroundColor); | |
488 } | |
489 | |
490 // Draw the background for this list box item | |
491 if (!element->renderStyle() || element->renderStyle()->visibility() != HIDDE
N) { | |
492 LayoutRect itemRect = itemBoundingBoxRectInternal(paintOffset, listIndex
); | |
493 itemRect.intersect(controlClipRect(paintOffset)); | |
494 paintInfo.context->fillRect(pixelSnappedIntRect(itemRect), backColor); | |
495 } | |
496 } | |
497 | |
498 bool RenderListBox::isPointInOverflowControl(HitTestResult& result, const Layout
Point& locationInContainer, const LayoutPoint& accumulatedOffset) | |
499 { | |
500 if (!m_vBar || !m_vBar->shouldParticipateInHitTesting()) | |
501 return false; | |
502 | |
503 LayoutRect vertRect(accumulatedOffset.x() + scrollbarLeft(), | |
504 accumulatedOffset.y() + borderTop(), | |
505 verticalScrollbarWidth(), | |
506 height() - borderTop() - borderBottom()); | |
507 | |
508 if (vertRect.contains(locationInContainer)) { | |
509 result.setScrollbar(m_vBar.get()); | |
510 return true; | |
511 } | |
512 return false; | |
513 } | |
514 | |
515 int RenderListBox::listIndexAtOffset(const LayoutSize& offset) const | |
516 { | |
517 if (!numItems()) | |
518 return -1; | |
519 | |
520 if (offset.height() < borderTop() + paddingTop() || offset.height() > height
() - paddingBottom() - borderBottom()) | |
521 return -1; | |
522 | |
523 int scrollbarWidth = verticalScrollbarWidth(); | |
524 int rightScrollbarOffset = style()->shouldPlaceBlockDirectionScrollbarOnLogi
calLeft() ? scrollbarWidth : 0; | |
525 int leftScrollbarOffset = style()->shouldPlaceBlockDirectionScrollbarOnLogic
alLeft() ? 0 : scrollbarWidth; | |
526 if (offset.width() < borderLeft() + paddingLeft() + rightScrollbarOffset | |
527 || offset.width() > width() - borderRight() - paddingRight() - leftScrol
lbarOffset) | |
528 return -1; | |
529 | |
530 int newOffset = (offset.height() - borderTop() - paddingTop()) / itemHeight(
) + m_indexOffset; | |
531 return newOffset < numItems() ? renderListBoxIndexToListIndex(newOffset) : -
1; | |
532 } | |
533 | |
534 void RenderListBox::panScroll(const IntPoint& panStartMousePosition) | |
535 { | |
536 const int maxSpeed = 20; | |
537 const int iconRadius = 7; | |
538 const int speedReducer = 4; | |
539 | |
540 // FIXME: This doesn't work correctly with transforms. | |
541 FloatPoint absOffset = localToAbsolute(); | |
542 | |
543 IntPoint lastKnownMousePosition = frame()->eventHandler().lastKnownMousePosi
tion(); | |
544 // 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 | |
545 static IntPoint previousMousePosition; | |
546 if (lastKnownMousePosition.y() < 0) | |
547 lastKnownMousePosition = previousMousePosition; | |
548 else | |
549 previousMousePosition = lastKnownMousePosition; | |
550 | |
551 int yDelta = lastKnownMousePosition.y() - panStartMousePosition.y(); | |
552 | |
553 // If the point is too far from the center we limit the speed | |
554 yDelta = std::max<int>(std::min<int>(yDelta, maxSpeed), -maxSpeed); | |
555 | |
556 if (abs(yDelta) < iconRadius) // at the center we let the space for the icon | |
557 return; | |
558 | |
559 if (yDelta > 0) | |
560 absOffset.move(0, listHeight().toFloat()); | |
561 else if (yDelta < 0) | |
562 yDelta--; | |
563 | |
564 // Let's attenuate the speed | |
565 yDelta /= speedReducer; | |
566 | |
567 IntPoint scrollPoint(0, 0); | |
568 scrollPoint.setY(absOffset.y() + yDelta); | |
569 int newOffset = scrollToward(scrollPoint); | |
570 if (newOffset < 0) | |
571 return; | |
572 | |
573 m_inAutoscroll = true; | |
574 HTMLSelectElement* select = selectElement(); | |
575 select->updateListBoxSelection(!select->multiple()); | |
576 m_inAutoscroll = false; | |
577 } | |
578 | |
579 int RenderListBox::scrollToward(const IntPoint& destination) | |
580 { | |
581 // FIXME: This doesn't work correctly with transforms. | |
582 FloatPoint absPos = localToAbsolute(); | |
583 IntSize positionOffset = roundedIntSize(destination - absPos); | |
584 | |
585 int rows = numVisibleItems(); | |
586 int offset = m_indexOffset; | |
587 | |
588 if (positionOffset.height() < borderTop() + paddingTop() && scrollToRevealEl
ementAtListIndexInternal(offset - 1)) | |
589 return offset - 1; | |
590 | |
591 if (positionOffset.height() > height() - paddingBottom() - borderBottom() &&
scrollToRevealElementAtListIndexInternal(offset + rows)) | |
592 return offset + rows - 1; | |
593 | |
594 return listIndexAtOffset(positionOffset); | |
595 } | |
596 | |
597 void RenderListBox::autoscroll(const IntPoint&) | |
598 { | |
599 IntPoint pos = frame()->view()->windowToContents(frame()->eventHandler().las
tKnownMousePosition()); | |
600 | |
601 int endIndex = scrollToward(pos); | |
602 if (selectElement()->isDisabledFormControl()) | |
603 return; | |
604 | |
605 if (endIndex >= 0) { | |
606 HTMLSelectElement* select = selectElement(); | |
607 m_inAutoscroll = true; | |
608 | |
609 if (!select->multiple()) | |
610 select->setActiveSelectionAnchorIndex(renderListBoxIndexToListIndex(
endIndex)); | |
611 | |
612 select->setActiveSelectionEndIndex(renderListBoxIndexToListIndex(endInde
x)); | |
613 select->updateListBoxSelection(!select->multiple()); | |
614 m_inAutoscroll = false; | |
615 } | |
616 } | |
617 | |
618 void RenderListBox::stopAutoscroll() | 132 void RenderListBox::stopAutoscroll() |
619 { | 133 { |
620 if (selectElement()->isDisabledFormControl()) | 134 HTMLSelectElement* select = selectElement(); |
| 135 if (select->isDisabledFormControl()) |
621 return; | 136 return; |
622 | 137 select->handleMouseRelease(); |
623 selectElement()->listBoxOnChange(); | |
624 } | |
625 | |
626 bool RenderListBox::scrollToRevealElementAtListIndexInternal(int index) | |
627 { | |
628 if (index < 0 || index >= numItems() || listIndexIsVisible(index)) | |
629 return false; | |
630 | |
631 int newOffset; | |
632 if (index < m_indexOffset) | |
633 newOffset = index; | |
634 else | |
635 newOffset = index - numVisibleItems() + 1; | |
636 | |
637 scrollToOffsetWithoutAnimation(VerticalScrollbar, newOffset); | |
638 | |
639 return true; | |
640 } | |
641 | |
642 bool RenderListBox::listIndexIsVisible(int index) const | |
643 { | |
644 return index >= m_indexOffset && index < m_indexOffset + numVisibleItems(); | |
645 } | |
646 | |
647 bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granular
ity, float multiplier) | |
648 { | |
649 return ScrollableArea::scroll(direction, granularity, multiplier); | |
650 } | |
651 | |
652 int RenderListBox::scrollSize(ScrollbarOrientation orientation) const | |
653 { | |
654 return orientation == VerticalScrollbar ? (numItems() - numVisibleItems()) :
0; | |
655 } | |
656 | |
657 IntPoint RenderListBox::scrollPosition() const | |
658 { | |
659 return IntPoint(0, m_indexOffset); | |
660 } | |
661 | |
662 void RenderListBox::setScrollOffset(const IntPoint& offset) | |
663 { | |
664 scrollTo(offset.y()); | |
665 } | |
666 | |
667 void RenderListBox::scrollTo(int newOffset) | |
668 { | |
669 if (newOffset == m_indexOffset) | |
670 return; | |
671 | |
672 m_indexOffset = newOffset; | |
673 | |
674 if (frameView()->isInPerformLayout()) | |
675 setShouldDoFullPaintInvalidation(true); | |
676 else | |
677 paintInvalidationForWholeRenderer(); | |
678 | |
679 node()->document().enqueueScrollEventForNode(node()); | |
680 } | |
681 | |
682 LayoutUnit RenderListBox::itemHeight() const | |
683 { | |
684 return style()->fontMetrics().height() + rowSpacing; | |
685 } | |
686 | |
687 int RenderListBox::verticalScrollbarWidth() const | |
688 { | |
689 return m_vBar && !m_vBar->isOverlayScrollbar() ? m_vBar->width() : 0; | |
690 } | |
691 | |
692 // FIXME: We ignore padding in the vertical direction as far as these values are
concerned, since that's | |
693 // how the control currently paints. | |
694 LayoutUnit RenderListBox::scrollWidth() const | |
695 { | |
696 // There is no horizontal scrolling allowed. | |
697 return clientWidth(); | |
698 } | |
699 | |
700 LayoutUnit RenderListBox::scrollHeight() const | |
701 { | |
702 return std::max(clientHeight(), listHeight()); | |
703 } | |
704 | |
705 LayoutUnit RenderListBox::scrollLeft() const | |
706 { | |
707 return 0; | |
708 } | |
709 | |
710 void RenderListBox::setScrollLeft(LayoutUnit) | |
711 { | |
712 } | |
713 | |
714 LayoutUnit RenderListBox::scrollTop() const | |
715 { | |
716 return m_indexOffset * itemHeight(); | |
717 } | |
718 | |
719 void RenderListBox::setScrollTop(LayoutUnit newTop) | |
720 { | |
721 // Determine an index and scroll to it. | |
722 int index = newTop / itemHeight(); | |
723 if (index < 0 || index >= numItems() || index == m_indexOffset) | |
724 return; | |
725 | |
726 scrollToOffsetWithoutAnimation(VerticalScrollbar, index); | |
727 } | |
728 | |
729 bool RenderListBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& re
sult, const HitTestLocation& locationInContainer, const LayoutPoint& accumulated
Offset, HitTestAction hitTestAction) | |
730 { | |
731 if (!RenderBlockFlow::nodeAtPoint(request, result, locationInContainer, accu
mulatedOffset, hitTestAction)) | |
732 return false; | |
733 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = select
Element()->listItems(); | |
734 int size = numItems(); | |
735 LayoutPoint adjustedLocation = accumulatedOffset + location(); | |
736 | |
737 for (int i = 0; i < size; ++i) { | |
738 if (itemBoundingBoxRectInternal(adjustedLocation, i).contains(locationIn
Container.point())) { | |
739 if (Element* node = listItems[renderListBoxIndexToListIndex(i)]) { | |
740 result.setInnerNode(node); | |
741 if (!result.innerNonSharedNode()) | |
742 result.setInnerNonSharedNode(node); | |
743 result.setLocalPoint(locationInContainer.point() - toLayoutSize(
adjustedLocation)); | |
744 break; | |
745 } | |
746 } | |
747 } | |
748 | |
749 return true; | |
750 } | |
751 | |
752 LayoutRect RenderListBox::controlClipRect(const LayoutPoint& additionalOffset) c
onst | |
753 { | |
754 LayoutRect clipRect = contentBoxRect(); | |
755 if (style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) | |
756 clipRect.moveBy(additionalOffset + LayoutPoint(verticalScrollbarWidth(),
0)); | |
757 else | |
758 clipRect.moveBy(additionalOffset); | |
759 return clipRect; | |
760 } | |
761 | |
762 bool RenderListBox::isActive() const | |
763 { | |
764 Page* page = frame()->page(); | |
765 return page && page->focusController().isActive(); | |
766 } | |
767 | |
768 void RenderListBox::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect&
rect) | |
769 { | |
770 IntRect scrollRect = rect; | |
771 if (style()->shouldPlaceBlockDirectionScrollbarOnLogicalLeft()) | |
772 scrollRect.move(borderLeft(), borderTop()); | |
773 else | |
774 scrollRect.move(width() - borderRight() - scrollbar->width(), borderTop(
)); | |
775 | |
776 if (frameView()->isInPerformLayout()) { | |
777 m_verticalBarDamage = scrollRect; | |
778 m_hasVerticalBarDamage = true; | |
779 } else { | |
780 invalidatePaintRectangle(scrollRect); | |
781 } | |
782 } | |
783 | |
784 void RenderListBox::repaintScrollbarIfNeeded() | |
785 { | |
786 if (!hasVerticalBarDamage()) | |
787 return; | |
788 invalidatePaintRectangle(verticalBarDamage()); | |
789 | |
790 resetScrollbarDamage(); | |
791 } | |
792 | |
793 IntRect RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scr
ollbar, const IntRect& scrollbarRect) const | |
794 { | |
795 RenderView* view = this->view(); | |
796 if (!view) | |
797 return scrollbarRect; | |
798 | |
799 IntRect rect = scrollbarRect; | |
800 | |
801 int scrollbarTop = borderTop(); | |
802 rect.move(scrollbarLeft(), scrollbarTop); | |
803 | |
804 return view->frameView()->convertFromRenderer(*this, rect); | |
805 } | |
806 | |
807 IntRect RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scr
ollbar, const IntRect& parentRect) const | |
808 { | |
809 RenderView* view = this->view(); | |
810 if (!view) | |
811 return parentRect; | |
812 | |
813 IntRect rect = view->frameView()->convertToRenderer(*this, parentRect); | |
814 | |
815 int scrollbarTop = borderTop(); | |
816 rect.move(-scrollbarLeft(), -scrollbarTop); | |
817 return rect; | |
818 } | |
819 | |
820 IntPoint RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* sc
rollbar, const IntPoint& scrollbarPoint) const | |
821 { | |
822 RenderView* view = this->view(); | |
823 if (!view) | |
824 return scrollbarPoint; | |
825 | |
826 IntPoint point = scrollbarPoint; | |
827 | |
828 int scrollbarTop = borderTop(); | |
829 point.move(scrollbarLeft(), scrollbarTop); | |
830 | |
831 return view->frameView()->convertFromRenderer(*this, point); | |
832 } | |
833 | |
834 IntPoint RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* sc
rollbar, const IntPoint& parentPoint) const | |
835 { | |
836 RenderView* view = this->view(); | |
837 if (!view) | |
838 return parentPoint; | |
839 | |
840 IntPoint point = view->frameView()->convertToRenderer(*this, parentPoint); | |
841 | |
842 int scrollbarTop = borderTop(); | |
843 point.move(-scrollbarLeft(), -scrollbarTop); | |
844 return point; | |
845 } | |
846 | |
847 IntSize RenderListBox::contentsSize() const | |
848 { | |
849 return IntSize(scrollWidth(), scrollHeight()); | |
850 } | |
851 | |
852 int RenderListBox::visibleHeight() const | |
853 { | |
854 return height(); | |
855 } | |
856 | |
857 int RenderListBox::visibleWidth() const | |
858 { | |
859 return width(); | |
860 } | |
861 | |
862 IntPoint RenderListBox::lastKnownMousePosition() const | |
863 { | |
864 RenderView* view = this->view(); | |
865 if (!view) | |
866 return IntPoint(); | |
867 return view->frameView()->lastKnownMousePosition(); | |
868 } | |
869 | |
870 bool RenderListBox::shouldSuspendScrollAnimations() const | |
871 { | |
872 RenderView* view = this->view(); | |
873 if (!view) | |
874 return true; | |
875 return view->frameView()->shouldSuspendScrollAnimations(); | |
876 } | |
877 | |
878 bool RenderListBox::scrollbarsCanBeActive() const | |
879 { | |
880 RenderView* view = this->view(); | |
881 if (!view) | |
882 return false; | |
883 return view->frameView()->scrollbarsCanBeActive(); | |
884 } | |
885 | |
886 IntPoint RenderListBox::minimumScrollPosition() const | |
887 { | |
888 return IntPoint(); | |
889 } | |
890 | |
891 IntPoint RenderListBox::maximumScrollPosition() const | |
892 { | |
893 return IntPoint(0, std::max(numItems() - numVisibleItems(), 0)); | |
894 } | |
895 | |
896 bool RenderListBox::userInputScrollable(ScrollbarOrientation orientation) const | |
897 { | |
898 return orientation == VerticalScrollbar; | |
899 } | |
900 | |
901 bool RenderListBox::shouldPlaceVerticalScrollbarOnLeft() const | |
902 { | |
903 return false; | |
904 } | |
905 | |
906 int RenderListBox::lineStep(ScrollbarOrientation) const | |
907 { | |
908 return 1; | |
909 } | |
910 | |
911 int RenderListBox::pageStep(ScrollbarOrientation orientation) const | |
912 { | |
913 return std::max(1, numVisibleItems() - 1); | |
914 } | |
915 | |
916 float RenderListBox::pixelStep(ScrollbarOrientation) const | |
917 { | |
918 return 1.0f / itemHeight(); | |
919 } | |
920 | |
921 IntRect RenderListBox::scrollableAreaBoundingBox() const | |
922 { | |
923 return absoluteBoundingBoxRect(); | |
924 } | |
925 | |
926 PassRefPtr<Scrollbar> RenderListBox::createScrollbar() | |
927 { | |
928 RefPtr<Scrollbar> widget; | |
929 bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR); | |
930 if (hasCustomScrollbarStyle) | |
931 widget = RenderScrollbar::createCustomScrollbar(this, VerticalScrollbar,
this->node()); | |
932 else { | |
933 widget = Scrollbar::create(this, VerticalScrollbar, RenderTheme::theme()
.scrollbarControlSizeForPart(ListboxPart)); | |
934 didAddScrollbar(widget.get(), VerticalScrollbar); | |
935 } | |
936 document().view()->addChild(widget.get()); | |
937 return widget.release(); | |
938 } | |
939 | |
940 void RenderListBox::destroyScrollbar() | |
941 { | |
942 if (!m_vBar) | |
943 return; | |
944 | |
945 if (!m_vBar->isCustomScrollbar()) | |
946 ScrollableArea::willRemoveScrollbar(m_vBar.get(), VerticalScrollbar); | |
947 m_vBar->removeFromParent(); | |
948 m_vBar->disconnectFromScrollableArea(); | |
949 m_vBar = nullptr; | |
950 } | |
951 | |
952 void RenderListBox::setHasVerticalScrollbar(bool hasScrollbar) | |
953 { | |
954 if (hasScrollbar == (m_vBar != 0)) | |
955 return; | |
956 | |
957 if (hasScrollbar) | |
958 m_vBar = createScrollbar(); | |
959 else | |
960 destroyScrollbar(); | |
961 | |
962 if (m_vBar) | |
963 m_vBar->styleChanged(); | |
964 | |
965 // Force an update since we know the scrollbars have changed things. | |
966 if (document().hasAnnotatedRegions()) | |
967 document().setAnnotatedRegionsDirty(true); | |
968 } | |
969 | |
970 int RenderListBox::renderListBoxIndexToListIndex(int index) const | |
971 { | |
972 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = select
Element()->listItems(); | |
973 const int size = static_cast<int>(listItems.size()); | |
974 | |
975 if (size == numItems()) | |
976 return index; | |
977 | |
978 int listBoxIndex = 0; | |
979 int listIndex = 0; | |
980 for (; listIndex < size; ++listIndex) { | |
981 const HTMLElement& element = *listItems[listIndex]; | |
982 if (isHTMLOptionElement(element) && toHTMLOptionElement(element).isDispl
ayNone()) | |
983 continue; | |
984 if (isHTMLOptGroupElement(element) && toHTMLOptGroupElement(element).isD
isplayNone()) | |
985 continue; | |
986 if (index == listBoxIndex) | |
987 break; | |
988 ++listBoxIndex; | |
989 } | |
990 return listIndex; | |
991 } | |
992 | |
993 int RenderListBox::listIndexToRenderListBoxIndex(int index) const | |
994 { | |
995 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = select
Element()->listItems(); | |
996 const int size = static_cast<int>(listItems.size()); | |
997 | |
998 if (size == numItems()) | |
999 return index; | |
1000 | |
1001 int listBoxIndex = 0; | |
1002 for (int listIndex = 0; listIndex < size; ++listIndex) { | |
1003 const HTMLElement& element = *listItems[listIndex]; | |
1004 if (isHTMLOptionElement(element) && toHTMLOptionElement(element).isDispl
ayNone()) | |
1005 continue; | |
1006 if (isHTMLOptGroupElement(element) && toHTMLOptGroupElement(element).isD
isplayNone()) | |
1007 continue; | |
1008 if (index == listIndex) | |
1009 break; | |
1010 ++listBoxIndex; | |
1011 } | |
1012 return listBoxIndex; | |
1013 } | |
1014 | |
1015 LayoutRect RenderListBox::itemBoundingBoxRect(const LayoutPoint& point, int inde
x) const | |
1016 { | |
1017 return itemBoundingBoxRectInternal(point, listIndexToRenderListBoxIndex(inde
x)); | |
1018 } | |
1019 | |
1020 bool RenderListBox::scrollToRevealElementAtListIndex(int index) | |
1021 { | |
1022 return scrollToRevealElementAtListIndexInternal(listIndexToRenderListBoxInde
x(index)); | |
1023 } | 138 } |
1024 | 139 |
1025 } // namespace WebCore | 140 } // namespace WebCore |
OLD | NEW |