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                 // FIXME: Why is this always LTR? Can't text direction affect the width?
121                 float textWidth = itemFont.width(TextRun(text.impl(), false, 0, 0, TextRun::AllowTrailingExpansion, LTR));
122                 width = max(width, textWidth);
123             }
124         }
125         m_optionsWidth = static_cast<int>(ceilf(width));
126         m_optionsChanged = false;
127 
128         setHasVerticalScrollbar(true);
129 
130         setNeedsLayoutAndPrefWidthsRecalc();
131     }
132 }
133 
selectionChanged()134 void RenderListBox::selectionChanged()
135 {
136     repaint();
137     if (!m_inAutoscroll) {
138         if (m_optionsChanged || needsLayout())
139             m_scrollToRevealSelectionAfterLayout = true;
140         else
141             scrollToRevealSelection();
142     }
143 
144     if (AXObjectCache::accessibilityEnabled())
145         document()->axObjectCache()->selectedChildrenChanged(this);
146 }
147 
layout()148 void RenderListBox::layout()
149 {
150     RenderBlock::layout();
151     if (m_scrollToRevealSelectionAfterLayout) {
152         view()->disableLayoutState();
153         scrollToRevealSelection();
154         view()->enableLayoutState();
155     }
156 }
157 
scrollToRevealSelection()158 void RenderListBox::scrollToRevealSelection()
159 {
160     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
161 
162     m_scrollToRevealSelectionAfterLayout = false;
163 
164     int firstIndex = select->activeSelectionStartListIndex();
165     if (firstIndex >= 0 && !listIndexIsVisible(select->activeSelectionEndListIndex()))
166         scrollToRevealElementAtListIndex(firstIndex);
167 }
168 
computePreferredLogicalWidths()169 void RenderListBox::computePreferredLogicalWidths()
170 {
171     ASSERT(!m_optionsChanged);
172 
173     m_minPreferredLogicalWidth = 0;
174     m_maxPreferredLogicalWidth = 0;
175 
176     if (style()->width().isFixed() && style()->width().value() > 0)
177         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = computeContentBoxLogicalWidth(style()->width().value());
178     else {
179         m_maxPreferredLogicalWidth = m_optionsWidth + 2 * optionsSpacingHorizontal;
180         if (m_vBar)
181             m_maxPreferredLogicalWidth += m_vBar->width();
182     }
183 
184     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
185         m_maxPreferredLogicalWidth = max(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
186         m_minPreferredLogicalWidth = max(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->minWidth().value()));
187     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
188         m_minPreferredLogicalWidth = 0;
189     else
190         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth;
191 
192     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
193         m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
194         m_minPreferredLogicalWidth = min(m_minPreferredLogicalWidth, computeContentBoxLogicalWidth(style()->maxWidth().value()));
195     }
196 
197     int toAdd = borderAndPaddingWidth();
198     m_minPreferredLogicalWidth += toAdd;
199     m_maxPreferredLogicalWidth += toAdd;
200 
201     setPreferredLogicalWidthsDirty(false);
202 }
203 
size() const204 int RenderListBox::size() const
205 {
206     int specifiedSize = toSelectElement(static_cast<Element*>(node()))->size();
207     if (specifiedSize > 1)
208         return max(minSize, specifiedSize);
209     return min(max(minSize, numItems()), maxDefaultSize);
210 }
211 
numVisibleItems() const212 int RenderListBox::numVisibleItems() const
213 {
214     // Only count fully visible rows. But don't return 0 even if only part of a row shows.
215     return max(1, (contentHeight() + rowSpacing) / itemHeight());
216 }
217 
numItems() const218 int RenderListBox::numItems() const
219 {
220     return toSelectElement(static_cast<Element*>(node()))->listItems().size();
221 }
222 
listHeight() const223 int RenderListBox::listHeight() const
224 {
225     return itemHeight() * numItems() - rowSpacing;
226 }
227 
computeLogicalHeight()228 void RenderListBox::computeLogicalHeight()
229 {
230     int toAdd = borderAndPaddingHeight();
231 
232     int itemHeight = RenderListBox::itemHeight();
233     setHeight(itemHeight * size() - rowSpacing + toAdd);
234 
235     RenderBlock::computeLogicalHeight();
236 
237     if (m_vBar) {
238         bool enabled = numVisibleItems() < numItems();
239         m_vBar->setEnabled(enabled);
240         m_vBar->setSteps(1, min(1, numVisibleItems() - 1), itemHeight);
241         m_vBar->setProportion(numVisibleItems(), numItems());
242         if (!enabled)
243             m_indexOffset = 0;
244     }
245 }
246 
baselinePosition(FontBaseline baselineType,bool firstLine,LineDirectionMode lineDirection,LinePositionMode linePositionMode) const247 int RenderListBox::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode lineDirection, LinePositionMode linePositionMode) const
248 {
249     return RenderBox::baselinePosition(baselineType, firstLine, lineDirection, linePositionMode) - baselineAdjustment;
250 }
251 
itemBoundingBoxRect(int tx,int ty,int index)252 IntRect RenderListBox::itemBoundingBoxRect(int tx, int ty, int index)
253 {
254     return IntRect(tx + borderLeft() + paddingLeft(),
255                    ty + borderTop() + paddingTop() + itemHeight() * (index - m_indexOffset),
256                    contentWidth(), itemHeight());
257 }
258 
paintObject(PaintInfo & paintInfo,int tx,int ty)259 void RenderListBox::paintObject(PaintInfo& paintInfo, int tx, int ty)
260 {
261     if (style()->visibility() != VISIBLE)
262         return;
263 
264     int listItemsSize = numItems();
265 
266     if (paintInfo.phase == PaintPhaseForeground) {
267         int index = m_indexOffset;
268         while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
269             paintItemForeground(paintInfo, tx, ty, index);
270             index++;
271         }
272     }
273 
274     // Paint the children.
275     RenderBlock::paintObject(paintInfo, tx, ty);
276 
277     switch (paintInfo.phase) {
278     // Depending on whether we have overlay scrollbars they
279     // get rendered in the foreground or background phases
280     case PaintPhaseForeground:
281         if (m_vBar->isOverlayScrollbar())
282             paintScrollbar(paintInfo, tx, ty);
283         break;
284     case PaintPhaseBlockBackground:
285         if (!m_vBar->isOverlayScrollbar())
286             paintScrollbar(paintInfo, tx, ty);
287         break;
288     case PaintPhaseChildBlockBackground:
289     case PaintPhaseChildBlockBackgrounds: {
290         int index = m_indexOffset;
291         while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
292             paintItemBackground(paintInfo, tx, ty, index);
293             index++;
294         }
295         break;
296     }
297     default:
298         break;
299     }
300 }
301 
addFocusRingRects(Vector<IntRect> & rects,int tx,int ty)302 void RenderListBox::addFocusRingRects(Vector<IntRect>& rects, int tx, int ty)
303 {
304     if (!isSpatialNavigationEnabled(frame()))
305         return RenderBlock::addFocusRingRects(rects, tx, ty);
306 
307     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
308 
309     // Focus the last selected item.
310     int selectedItem = select->activeSelectionEndListIndex();
311     if (selectedItem >= 0) {
312         rects.append(itemBoundingBoxRect(tx, ty, selectedItem));
313         return;
314     }
315 
316     // No selected items, find the first non-disabled item.
317     int size = numItems();
318     const Vector<Element*>& listItems = select->listItems();
319     for (int i = 0; i < size; ++i) {
320         OptionElement* optionElement = toOptionElement(listItems[i]);
321         if (optionElement && !optionElement->disabled()) {
322             rects.append(itemBoundingBoxRect(tx, ty, i));
323             return;
324         }
325     }
326 }
327 
paintScrollbar(PaintInfo & paintInfo,int tx,int ty)328 void RenderListBox::paintScrollbar(PaintInfo& paintInfo, int tx, int ty)
329 {
330     if (m_vBar) {
331         IntRect scrollRect(tx + width() - borderRight() - m_vBar->width(),
332                            ty + borderTop(),
333                            m_vBar->width(),
334                            height() - (borderTop() + borderBottom()));
335         m_vBar->setFrameRect(scrollRect);
336         m_vBar->paint(paintInfo.context, paintInfo.rect);
337     }
338 }
339 
itemOffsetForAlignment(TextRun textRun,RenderStyle * itemStyle,Font itemFont,IntRect itemBoudingBox)340 static IntSize itemOffsetForAlignment(TextRun textRun, RenderStyle* itemStyle, Font itemFont, IntRect itemBoudingBox)
341 {
342     ETextAlign actualAlignment = itemStyle->textAlign();
343     // FIXME: Firefox doesn't respect JUSTIFY. Should we?
344     if (actualAlignment == TAAUTO || actualAlignment == JUSTIFY)
345       actualAlignment = itemStyle->isLeftToRightDirection() ? LEFT : RIGHT;
346 
347     IntSize offset = IntSize(0, itemFont.fontMetrics().ascent());
348     if (actualAlignment == RIGHT || actualAlignment == WEBKIT_RIGHT) {
349         float textWidth = itemFont.width(textRun);
350         offset.setWidth(itemBoudingBox.width() - textWidth - optionsSpacingHorizontal);
351     } else if (actualAlignment == CENTER || actualAlignment == WEBKIT_CENTER) {
352         float textWidth = itemFont.width(textRun);
353         offset.setWidth((itemBoudingBox.width() - textWidth) / 2);
354     } else
355         offset.setWidth(optionsSpacingHorizontal);
356     return offset;
357 }
358 
paintItemForeground(PaintInfo & paintInfo,int tx,int ty,int listIndex)359 void RenderListBox::paintItemForeground(PaintInfo& paintInfo, int tx, int ty, int listIndex)
360 {
361     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
362     const Vector<Element*>& listItems = select->listItems();
363     Element* element = listItems[listIndex];
364     OptionElement* optionElement = toOptionElement(element);
365 
366     RenderStyle* itemStyle = element->renderStyle();
367     if (!itemStyle)
368         itemStyle = style();
369 
370     if (itemStyle->visibility() == HIDDEN)
371         return;
372 
373     String itemText;
374     if (optionElement)
375         itemText = optionElement->textIndentedToRespectGroupLabel();
376     else if (OptionGroupElement* optionGroupElement = toOptionGroupElement(element))
377         itemText = optionGroupElement->groupLabelText();
378 
379     Color textColor = element->renderStyle() ? element->renderStyle()->visitedDependentColor(CSSPropertyColor) : style()->visitedDependentColor(CSSPropertyColor);
380     if (optionElement && optionElement->selected()) {
381         if (frame()->selection()->isFocusedAndActive() && document()->focusedNode() == node())
382             textColor = theme()->activeListBoxSelectionForegroundColor();
383         // Honor the foreground color for disabled items
384         else if (!element->disabled())
385             textColor = theme()->inactiveListBoxSelectionForegroundColor();
386     }
387 
388     ColorSpace colorSpace = itemStyle->colorSpace();
389     paintInfo.context->setFillColor(textColor, colorSpace);
390 
391     unsigned length = itemText.length();
392     const UChar* string = itemText.characters();
393     TextRun textRun(string, length, false, 0, 0, TextRun::AllowTrailingExpansion, itemStyle->direction(), itemStyle->unicodeBidi() == Override);
394     Font itemFont = style()->font();
395     IntRect r = itemBoundingBoxRect(tx, ty, listIndex);
396     r.move(itemOffsetForAlignment(textRun, itemStyle, itemFont, r));
397 
398     if (isOptionGroupElement(element)) {
399         FontDescription d = itemFont.fontDescription();
400         d.setWeight(d.bolderWeight());
401         itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
402         itemFont.update(document()->styleSelector()->fontSelector());
403     }
404 
405     // Draw the item text
406     if (itemStyle->visibility() != HIDDEN)
407         paintInfo.context->drawBidiText(itemFont, textRun, r.location());
408 }
409 
paintItemBackground(PaintInfo & paintInfo,int tx,int ty,int listIndex)410 void RenderListBox::paintItemBackground(PaintInfo& paintInfo, int tx, int ty, int listIndex)
411 {
412     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
413     const Vector<Element*>& listItems = select->listItems();
414     Element* element = listItems[listIndex];
415     OptionElement* optionElement = toOptionElement(element);
416 
417     Color backColor;
418     if (optionElement && optionElement->selected()) {
419         if (frame()->selection()->isFocusedAndActive() && document()->focusedNode() == node())
420             backColor = theme()->activeListBoxSelectionBackgroundColor();
421         else
422             backColor = theme()->inactiveListBoxSelectionBackgroundColor();
423     } else
424         backColor = element->renderStyle() ? element->renderStyle()->visitedDependentColor(CSSPropertyBackgroundColor) : style()->visitedDependentColor(CSSPropertyBackgroundColor);
425 
426     // Draw the background for this list box item
427     if (!element->renderStyle() || element->renderStyle()->visibility() != HIDDEN) {
428         ColorSpace colorSpace = element->renderStyle() ? element->renderStyle()->colorSpace() : style()->colorSpace();
429         IntRect itemRect = itemBoundingBoxRect(tx, ty, listIndex);
430         itemRect.intersect(controlClipRect(tx, ty));
431         paintInfo.context->fillRect(itemRect, backColor, colorSpace);
432     }
433 }
434 
isPointInOverflowControl(HitTestResult & result,int _x,int _y,int _tx,int _ty)435 bool RenderListBox::isPointInOverflowControl(HitTestResult& result, int _x, int _y, int _tx, int _ty)
436 {
437     if (!m_vBar)
438         return false;
439 
440     IntRect vertRect(_tx + width() - borderRight() - m_vBar->width(),
441                      _ty + borderTop(),
442                      m_vBar->width(),
443                      height() - borderTop() - borderBottom());
444 
445     if (vertRect.contains(_x, _y)) {
446         result.setScrollbar(m_vBar.get());
447         return true;
448     }
449     return false;
450 }
451 
listIndexAtOffset(int offsetX,int offsetY)452 int RenderListBox::listIndexAtOffset(int offsetX, int offsetY)
453 {
454     if (!numItems())
455         return -1;
456 
457     if (offsetY < borderTop() + paddingTop() || offsetY > height() - paddingBottom() - borderBottom())
458         return -1;
459 
460     int scrollbarWidth = m_vBar ? m_vBar->width() : 0;
461     if (offsetX < borderLeft() + paddingLeft() || offsetX > width() - borderRight() - paddingRight() - scrollbarWidth)
462         return -1;
463 
464     int newOffset = (offsetY - borderTop() - paddingTop()) / itemHeight() + m_indexOffset;
465     return newOffset < numItems() ? newOffset : -1;
466 }
467 
panScroll(const IntPoint & panStartMousePosition)468 void RenderListBox::panScroll(const IntPoint& panStartMousePosition)
469 {
470     const int maxSpeed = 20;
471     const int iconRadius = 7;
472     const int speedReducer = 4;
473 
474     // FIXME: This doesn't work correctly with transforms.
475     FloatPoint absOffset = localToAbsolute();
476 
477     IntPoint currentMousePosition = frame()->eventHandler()->currentMousePosition();
478     // 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
479     static IntPoint previousMousePosition;
480     if (currentMousePosition.y() < 0)
481         currentMousePosition = previousMousePosition;
482     else
483         previousMousePosition = currentMousePosition;
484 
485     int yDelta = currentMousePosition.y() - panStartMousePosition.y();
486 
487     // If the point is too far from the center we limit the speed
488     yDelta = max(min(yDelta, maxSpeed), -maxSpeed);
489 
490     if (abs(yDelta) < iconRadius) // at the center we let the space for the icon
491         return;
492 
493     if (yDelta > 0)
494         //offsetY = view()->viewHeight();
495         absOffset.move(0, listHeight());
496     else if (yDelta < 0)
497         yDelta--;
498 
499     // Let's attenuate the speed
500     yDelta /= speedReducer;
501 
502     IntPoint scrollPoint(0, 0);
503     scrollPoint.setY(absOffset.y() + yDelta);
504     int newOffset = scrollToward(scrollPoint);
505     if (newOffset < 0)
506         return;
507 
508     m_inAutoscroll = true;
509     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
510     select->updateListBoxSelection(!select->multiple());
511     m_inAutoscroll = false;
512 }
513 
scrollToward(const IntPoint & destination)514 int RenderListBox::scrollToward(const IntPoint& destination)
515 {
516     // FIXME: This doesn't work correctly with transforms.
517     FloatPoint absPos = localToAbsolute();
518     int offsetX = destination.x() - absPos.x();
519     int offsetY = destination.y() - absPos.y();
520 
521     int rows = numVisibleItems();
522     int offset = m_indexOffset;
523 
524     if (offsetY < borderTop() + paddingTop() && scrollToRevealElementAtListIndex(offset - 1))
525         return offset - 1;
526 
527     if (offsetY > height() - paddingBottom() - borderBottom() && scrollToRevealElementAtListIndex(offset + rows))
528         return offset + rows - 1;
529 
530     return listIndexAtOffset(offsetX, offsetY);
531 }
532 
autoscroll()533 void RenderListBox::autoscroll()
534 {
535     IntPoint pos = frame()->view()->windowToContents(frame()->eventHandler()->currentMousePosition());
536 
537     int endIndex = scrollToward(pos);
538     if (endIndex >= 0) {
539         SelectElement* select = toSelectElement(static_cast<Element*>(node()));
540         m_inAutoscroll = true;
541 
542         if (!select->multiple())
543             select->setActiveSelectionAnchorIndex(endIndex);
544 
545         select->setActiveSelectionEndIndex(endIndex);
546         select->updateListBoxSelection(!select->multiple());
547         m_inAutoscroll = false;
548     }
549 }
550 
stopAutoscroll()551 void RenderListBox::stopAutoscroll()
552 {
553     toSelectElement(static_cast<Element*>(node()))->listBoxOnChange();
554 }
555 
scrollToRevealElementAtListIndex(int index)556 bool RenderListBox::scrollToRevealElementAtListIndex(int index)
557 {
558     if (index < 0 || index >= numItems() || listIndexIsVisible(index))
559         return false;
560 
561     int newOffset;
562     if (index < m_indexOffset)
563         newOffset = index;
564     else
565         newOffset = index - numVisibleItems() + 1;
566 
567     ScrollableArea::scrollToYOffsetWithoutAnimation(newOffset);
568 
569     return true;
570 }
571 
listIndexIsVisible(int index)572 bool RenderListBox::listIndexIsVisible(int index)
573 {
574     return index >= m_indexOffset && index < m_indexOffset + numVisibleItems();
575 }
576 
scroll(ScrollDirection direction,ScrollGranularity granularity,float multiplier,Node **)577 bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier, Node**)
578 {
579     return ScrollableArea::scroll(direction, granularity, multiplier);
580 }
581 
logicalScroll(ScrollLogicalDirection direction,ScrollGranularity granularity,float multiplier,Node **)582 bool RenderListBox::logicalScroll(ScrollLogicalDirection direction, ScrollGranularity granularity, float multiplier, Node**)
583 {
584     return ScrollableArea::scroll(logicalToPhysical(direction, style()->isHorizontalWritingMode(), style()->isFlippedBlocksWritingMode()), granularity, multiplier);
585 }
586 
valueChanged(unsigned listIndex)587 void RenderListBox::valueChanged(unsigned listIndex)
588 {
589     Element* element = static_cast<Element*>(node());
590     SelectElement* select = toSelectElement(element);
591     select->setSelectedIndex(select->listToOptionIndex(listIndex));
592     element->dispatchFormControlChangeEvent();
593 }
594 
scrollSize(ScrollbarOrientation orientation) const595 int RenderListBox::scrollSize(ScrollbarOrientation orientation) const
596 {
597     return ((orientation == VerticalScrollbar) && m_vBar) ? (m_vBar->totalSize() - m_vBar->visibleSize()) : 0;
598 }
599 
scrollPosition(Scrollbar *) const600 int RenderListBox::scrollPosition(Scrollbar*) const
601 {
602     return m_indexOffset;
603 }
604 
setScrollOffset(const IntPoint & offset)605 void RenderListBox::setScrollOffset(const IntPoint& offset)
606 {
607     scrollTo(offset.y());
608 }
609 
scrollTo(int newOffset)610 void RenderListBox::scrollTo(int newOffset)
611 {
612     if (newOffset == m_indexOffset)
613         return;
614 
615     m_indexOffset = newOffset;
616     repaint();
617     node()->document()->eventQueue()->enqueueOrDispatchScrollEvent(node(), EventQueue::ScrollEventElementTarget);
618 }
619 
itemHeight() const620 int RenderListBox::itemHeight() const
621 {
622     return style()->fontMetrics().height() + rowSpacing;
623 }
624 
verticalScrollbarWidth() const625 int RenderListBox::verticalScrollbarWidth() const
626 {
627     return m_vBar && !m_vBar->isOverlayScrollbar() ? m_vBar->width() : 0;
628 }
629 
630 // FIXME: We ignore padding in the vertical direction as far as these values are concerned, since that's
631 // how the control currently paints.
scrollWidth() const632 int RenderListBox::scrollWidth() const
633 {
634     // There is no horizontal scrolling allowed.
635     return clientWidth();
636 }
637 
scrollHeight() const638 int RenderListBox::scrollHeight() const
639 {
640     return max(clientHeight(), listHeight());
641 }
642 
scrollLeft() const643 int RenderListBox::scrollLeft() const
644 {
645     return 0;
646 }
647 
setScrollLeft(int)648 void RenderListBox::setScrollLeft(int)
649 {
650 }
651 
scrollTop() const652 int RenderListBox::scrollTop() const
653 {
654     return m_indexOffset * itemHeight();
655 }
656 
setScrollTop(int newTop)657 void RenderListBox::setScrollTop(int newTop)
658 {
659     // Determine an index and scroll to it.
660     int index = newTop / itemHeight();
661     if (index < 0 || index >= numItems() || index == m_indexOffset)
662         return;
663 
664     ScrollableArea::scrollToYOffsetWithoutAnimation(index);
665 }
666 
nodeAtPoint(const HitTestRequest & request,HitTestResult & result,int x,int y,int tx,int ty,HitTestAction hitTestAction)667 bool RenderListBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, int x, int y, int tx, int ty, HitTestAction hitTestAction)
668 {
669     if (!RenderBlock::nodeAtPoint(request, result, x, y, tx, ty, hitTestAction))
670         return false;
671     const Vector<Element*>& listItems = toSelectElement(static_cast<Element*>(node()))->listItems();
672     int size = numItems();
673     tx += this->x();
674     ty += this->y();
675     for (int i = 0; i < size; ++i) {
676         if (itemBoundingBoxRect(tx, ty, i).contains(x, y)) {
677             if (Element* node = listItems[i]) {
678                 result.setInnerNode(node);
679                 if (!result.innerNonSharedNode())
680                     result.setInnerNonSharedNode(node);
681                 result.setLocalPoint(IntPoint(x - tx, y - ty));
682                 break;
683             }
684         }
685     }
686 
687     return true;
688 }
689 
controlClipRect(int tx,int ty) const690 IntRect RenderListBox::controlClipRect(int tx, int ty) const
691 {
692     IntRect clipRect = contentBoxRect();
693     clipRect.move(tx, ty);
694     return clipRect;
695 }
696 
isActive() const697 bool RenderListBox::isActive() const
698 {
699     Page* page = frame()->page();
700     return page && page->focusController()->isActive();
701 }
702 
invalidateScrollbarRect(Scrollbar * scrollbar,const IntRect & rect)703 void RenderListBox::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect)
704 {
705     IntRect scrollRect = rect;
706     scrollRect.move(width() - borderRight() - scrollbar->width(), borderTop());
707     repaintRectangle(scrollRect);
708 }
709 
convertFromScrollbarToContainingView(const Scrollbar * scrollbar,const IntRect & scrollbarRect) const710 IntRect RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntRect& scrollbarRect) const
711 {
712     RenderView* view = this->view();
713     if (!view)
714         return scrollbarRect;
715 
716     IntRect rect = scrollbarRect;
717 
718     int scrollbarLeft = width() - borderRight() - scrollbar->width();
719     int scrollbarTop = borderTop();
720     rect.move(scrollbarLeft, scrollbarTop);
721 
722     return view->frameView()->convertFromRenderer(this, rect);
723 }
724 
convertFromContainingViewToScrollbar(const Scrollbar * scrollbar,const IntRect & parentRect) const725 IntRect RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntRect& parentRect) const
726 {
727     RenderView* view = this->view();
728     if (!view)
729         return parentRect;
730 
731     IntRect rect = view->frameView()->convertToRenderer(this, parentRect);
732 
733     int scrollbarLeft = width() - borderRight() - scrollbar->width();
734     int scrollbarTop = borderTop();
735     rect.move(-scrollbarLeft, -scrollbarTop);
736     return rect;
737 }
738 
convertFromScrollbarToContainingView(const Scrollbar * scrollbar,const IntPoint & scrollbarPoint) const739 IntPoint RenderListBox::convertFromScrollbarToContainingView(const Scrollbar* scrollbar, const IntPoint& scrollbarPoint) const
740 {
741     RenderView* view = this->view();
742     if (!view)
743         return scrollbarPoint;
744 
745     IntPoint point = scrollbarPoint;
746 
747     int scrollbarLeft = width() - borderRight() - scrollbar->width();
748     int scrollbarTop = borderTop();
749     point.move(scrollbarLeft, scrollbarTop);
750 
751     return view->frameView()->convertFromRenderer(this, point);
752 }
753 
convertFromContainingViewToScrollbar(const Scrollbar * scrollbar,const IntPoint & parentPoint) const754 IntPoint RenderListBox::convertFromContainingViewToScrollbar(const Scrollbar* scrollbar, const IntPoint& parentPoint) const
755 {
756     RenderView* view = this->view();
757     if (!view)
758         return parentPoint;
759 
760     IntPoint point = view->frameView()->convertToRenderer(this, parentPoint);
761 
762     int scrollbarLeft = width() - borderRight() - scrollbar->width();
763     int scrollbarTop = borderTop();
764     point.move(-scrollbarLeft, -scrollbarTop);
765     return point;
766 }
767 
contentsSize() const768 IntSize RenderListBox::contentsSize() const
769 {
770     return IntSize(scrollWidth(), scrollHeight());
771 }
772 
visibleHeight() const773 int RenderListBox::visibleHeight() const
774 {
775     return height();
776 }
777 
visibleWidth() const778 int RenderListBox::visibleWidth() const
779 {
780     return width();
781 }
782 
currentMousePosition() const783 IntPoint RenderListBox::currentMousePosition() const
784 {
785     RenderView* view = this->view();
786     if (!view)
787         return IntPoint();
788     return view->frameView()->currentMousePosition();
789 }
790 
shouldSuspendScrollAnimations() const791 bool RenderListBox::shouldSuspendScrollAnimations() const
792 {
793     RenderView* view = this->view();
794     if (!view)
795         return true;
796     return view->frameView()->shouldSuspendScrollAnimations();
797 }
798 
createScrollbar()799 PassRefPtr<Scrollbar> RenderListBox::createScrollbar()
800 {
801     RefPtr<Scrollbar> widget;
802     bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR);
803     if (hasCustomScrollbarStyle)
804         widget = RenderScrollbar::createCustomScrollbar(this, VerticalScrollbar, this);
805     else {
806         widget = Scrollbar::createNativeScrollbar(this, VerticalScrollbar, theme()->scrollbarControlSizeForPart(ListboxPart));
807         didAddVerticalScrollbar(widget.get());
808     }
809     document()->view()->addChild(widget.get());
810     return widget.release();
811 }
812 
destroyScrollbar()813 void RenderListBox::destroyScrollbar()
814 {
815     if (!m_vBar)
816         return;
817 
818     if (!m_vBar->isCustomScrollbar())
819         ScrollableArea::willRemoveVerticalScrollbar(m_vBar.get());
820     m_vBar->removeFromParent();
821     m_vBar->disconnectFromScrollableArea();
822     m_vBar = 0;
823 }
824 
setHasVerticalScrollbar(bool hasScrollbar)825 void RenderListBox::setHasVerticalScrollbar(bool hasScrollbar)
826 {
827     if (hasScrollbar == (m_vBar != 0))
828         return;
829 
830     if (hasScrollbar)
831         m_vBar = createScrollbar();
832     else
833         destroyScrollbar();
834 
835     if (m_vBar)
836         m_vBar->styleChanged();
837 
838 #if ENABLE(DASHBOARD_SUPPORT)
839     // Force an update since we know the scrollbars have changed things.
840     if (document()->hasDashboardRegions())
841         document()->setDashboardRegionsDirty(true);
842 #endif
843 }
844 
845 } // namespace WebCore
846