• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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