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