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