2 * This file is part of the select element renderer in WebCore.
4 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16 * its contributors may be used to endorse or promote products derived
17 * from this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 #include "RenderListBox.h"
35 #include "EventHandler.h"
36 #include "EventNames.h"
38 #include "FrameView.h"
39 #include "GraphicsContext.h"
40 #include "HTMLNames.h"
41 #include "HTMLOptGroupElement.h"
42 #include "HTMLOptionElement.h"
43 #include "HTMLSelectElement.h"
44 #include "HitTestResult.h"
45 #include "PlatformScrollBar.h"
46 #include "RenderTheme.h"
47 #include "RenderView.h"
48 #include "TextStyle.h"
55 using namespace EventNames;
56 using namespace HTMLNames;
58 const int rowSpacing = 1;
60 const int optionsSpacingHorizontal = 2;
62 const int minSize = 4;
63 const int maxDefaultSize = 10;
65 // FIXME: This hardcoded baselineAdjustment is what we used to do for the old
66 // widget, but I'm not sure this is right for the new control.
67 const int baselineAdjustment = 7;
69 RenderListBox::RenderListBox(HTMLSelectElement* element)
70 : RenderBlock(element)
71 , m_optionsChanged(true)
72 , m_scrollToRevealSelectionAfterLayout(false)
73 , m_inAutoscroll(false)
79 RenderListBox::~RenderListBox()
81 if (m_vBar && m_vBar->isWidget())
82 if (FrameView* view = node()->document()->view())
83 view->removeChild(static_cast<PlatformScrollbar*>(m_vBar.get()));
86 void RenderListBox::setStyle(RenderStyle* style)
88 RenderBlock::setStyle(style);
89 setReplaced(isInline());
92 void RenderListBox::updateFromElement()
94 if (m_optionsChanged) {
95 const Vector<HTMLElement*>& listItems = static_cast<HTMLSelectElement*>(node())->listItems();
96 int size = numItems();
99 TextStyle textStyle(0, 0, 0, false, false, false, false);
100 for (int i = 0; i < size; ++i) {
101 HTMLElement* element = listItems[i];
103 Font itemFont = style()->font();
104 if (element->hasTagName(optionTag))
105 text = static_cast<HTMLOptionElement*>(element)->optionText();
106 else if (element->hasTagName(optgroupTag)) {
107 text = static_cast<HTMLOptGroupElement*>(element)->groupLabelText();
108 FontDescription d = itemFont.fontDescription();
110 itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
114 if (!text.isEmpty()) {
115 float textWidth = itemFont.floatWidth(TextRun(text.impl()), textStyle);
116 width = max(width, textWidth);
119 m_optionsWidth = static_cast<int>(ceilf(width));
120 m_optionsChanged = false;
121 setNeedsLayoutAndMinMaxRecalc();
125 void RenderListBox::selectionChanged()
128 if (!m_inAutoscroll) {
130 m_scrollToRevealSelectionAfterLayout = true;
132 scrollToRevealSelection();
136 void RenderListBox::layout()
138 RenderBlock::layout();
139 if (m_scrollToRevealSelectionAfterLayout)
140 scrollToRevealSelection();
143 void RenderListBox::scrollToRevealSelection()
145 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
147 m_scrollToRevealSelectionAfterLayout = false;
149 int firstIndex = select->activeSelectionStartListIndex();
150 if (firstIndex >= 0 && !listIndexIsVisible(select->activeSelectionEndListIndex()))
151 scrollToRevealElementAtListIndex(firstIndex);
154 void RenderListBox::calcMinMaxWidth()
156 if (!m_vBar && Scrollbar::hasPlatformScrollbars())
157 if (FrameView* view = node()->document()->view()) {
158 RefPtr<PlatformScrollbar> widget = new PlatformScrollbar(this, VerticalScrollbar, SmallScrollbar);
159 view->addChild(widget.get());
160 m_vBar = widget.release();
163 if (m_optionsChanged)
169 if (style()->width().isFixed() && style()->width().value() > 0)
170 m_minWidth = m_maxWidth = calcContentBoxWidth(style()->width().value());
172 m_maxWidth = m_optionsWidth + 2 * optionsSpacingHorizontal;
174 m_maxWidth += m_vBar->width();
177 if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
178 m_maxWidth = max(m_maxWidth, calcContentBoxWidth(style()->minWidth().value()));
179 m_minWidth = max(m_minWidth, calcContentBoxWidth(style()->minWidth().value()));
180 } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
183 m_minWidth = m_maxWidth;
185 if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
186 m_maxWidth = min(m_maxWidth, calcContentBoxWidth(style()->maxWidth().value()));
187 m_minWidth = min(m_minWidth, calcContentBoxWidth(style()->maxWidth().value()));
190 int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
197 int RenderListBox::size() const
199 int specifiedSize = static_cast<HTMLSelectElement*>(node())->size();
200 if (specifiedSize > 1)
201 return max(minSize, specifiedSize);
202 return min(max(minSize, numItems()), maxDefaultSize);
205 int RenderListBox::numVisibleItems() const
207 // Only count fully visible rows. But don't return 0 even if only part of a row shows.
208 return max(1, (contentHeight() + rowSpacing) / itemHeight());
211 int RenderListBox::numItems() const
213 return static_cast<HTMLSelectElement*>(node())->listItems().size();
216 int RenderListBox::listHeight() const
218 return itemHeight() * numItems() - rowSpacing;
221 void RenderListBox::calcHeight()
223 int toAdd = paddingTop() + paddingBottom() + borderTop() + borderBottom();
225 int itemHeight = RenderListBox::itemHeight();
226 m_height = itemHeight * size() - rowSpacing + toAdd;
228 RenderBlock::calcHeight();
231 m_vBar->setEnabled(numVisibleItems() < numItems());
232 m_vBar->setSteps(1, min(1, numVisibleItems() - 1), itemHeight);
233 m_vBar->setProportion(numVisibleItems(), numItems());
237 short RenderListBox::baselinePosition(bool b, bool isRootLineBox) const
239 return height() + marginTop() + marginBottom() - baselineAdjustment;
242 IntRect RenderListBox::itemBoundingBoxRect(int tx, int ty, int index)
244 return IntRect(tx + borderLeft() + paddingLeft(),
245 ty + borderTop() + paddingTop() + itemHeight() * (index - m_indexOffset),
246 contentWidth(), itemHeight());
249 void RenderListBox::paintObject(PaintInfo& paintInfo, int tx, int ty)
251 int listItemsSize = numItems();
253 if (paintInfo.phase == PaintPhaseForeground) {
254 int index = m_indexOffset;
255 while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
256 paintItemForeground(paintInfo, tx, ty, index);
261 // Paint the children.
262 RenderBlock::paintObject(paintInfo, tx, ty);
264 if (paintInfo.phase == PaintPhaseChildBlockBackground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
265 int index = m_indexOffset;
266 while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
267 paintItemBackground(paintInfo, tx, ty, index);
270 paintScrollbar(paintInfo);
274 void RenderListBox::paintScrollbar(PaintInfo& paintInfo)
277 IntRect absBounds = absoluteBoundingBoxRect();
278 IntRect scrollRect(absBounds.right() - borderRight() - m_vBar->width(),
279 absBounds.y() + borderTop(),
281 absBounds.height() - (borderTop() + borderBottom()));
282 m_vBar->setRect(scrollRect);
283 m_vBar->paint(paintInfo.context, scrollRect);
287 void RenderListBox::paintItemForeground(PaintInfo& paintInfo, int tx, int ty, int listIndex)
289 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
290 const Vector<HTMLElement*>& listItems = select->listItems();
291 HTMLElement* element = listItems[listIndex];
294 if (element->hasTagName(optionTag))
295 itemText = static_cast<HTMLOptionElement*>(element)->optionText();
296 else if (element->hasTagName(optgroupTag))
297 itemText = static_cast<HTMLOptGroupElement*>(element)->groupLabelText();
299 // Determine where the item text should be placed
300 IntRect r = itemBoundingBoxRect(tx, ty, listIndex);
301 r.move(optionsSpacingHorizontal, style()->font().ascent());
303 RenderStyle* itemStyle = element->renderStyle();
307 Color textColor = element->renderStyle() ? element->renderStyle()->color() : style()->color();
308 if (element->hasTagName(optionTag) && static_cast<HTMLOptionElement*>(element)->selected()) {
309 if (document()->frame()->isActive() && document()->focusedNode() == node())
310 textColor = theme()->activeListBoxSelectionForegroundColor();
311 // Honor the foreground color for disabled items
312 else if (!element->disabled())
313 textColor = theme()->inactiveListBoxSelectionForegroundColor();
316 paintInfo.context->setFillColor(textColor);
318 Font itemFont = style()->font();
319 if (element->hasTagName(optgroupTag)) {
320 FontDescription d = itemFont.fontDescription();
322 itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
325 paintInfo.context->setFont(itemFont);
327 unsigned length = itemText.length();
328 const UChar* string = itemText.characters();
329 TextStyle textStyle(0, 0, 0, false, true);
330 RenderBlock::CharacterBuffer characterBuffer;
332 if (itemStyle->direction() == RTL && itemStyle->unicodeBidi() == Override)
333 textStyle.setRTL(true);
334 else if ((itemStyle->direction() == RTL || itemStyle->unicodeBidi() != Override) && !itemStyle->visuallyOrdered()) {
335 // If necessary, reorder characters by running the string through the bidi algorithm
336 characterBuffer.append(string, length);
337 RenderBlock::bidiReorderCharacters(document(), itemStyle, characterBuffer);
338 string = characterBuffer.data();
340 TextRun textRun(string, length);
342 // Draw the item text
343 if (itemStyle->visibility() != HIDDEN)
344 paintInfo.context->drawText(textRun, r.location(), textStyle);
347 void RenderListBox::paintItemBackground(PaintInfo& paintInfo, int tx, int ty, int listIndex)
349 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
350 const Vector<HTMLElement*>& listItems = select->listItems();
351 HTMLElement* element = listItems[listIndex];
354 if (element->hasTagName(optionTag) && static_cast<HTMLOptionElement*>(element)->selected()) {
355 if (document()->frame()->isActive() && document()->focusedNode() == node())
356 backColor = theme()->activeListBoxSelectionBackgroundColor();
358 backColor = theme()->inactiveListBoxSelectionBackgroundColor();
360 backColor = element->renderStyle() ? element->renderStyle()->backgroundColor() : style()->backgroundColor();
362 // Draw the background for this list box item
363 if (!element->renderStyle() || element->renderStyle()->visibility() != HIDDEN) {
364 IntRect itemRect = itemBoundingBoxRect(tx, ty, listIndex);
365 itemRect.intersect(controlClipRect(tx, ty));
366 paintInfo.context->fillRect(itemRect, backColor);
370 bool RenderListBox::isPointInScrollbar(HitTestResult& result, int _x, int _y, int _tx, int _ty)
375 IntRect vertRect(_tx + width() - borderRight() - m_vBar->width(),
376 _ty + borderTop() - borderTopExtra(),
378 height() + borderTopExtra() + borderBottomExtra() - borderTop() - borderBottom());
380 if (vertRect.contains(_x, _y)) {
381 result.setScrollbar(m_vBar->isWidget() ? static_cast<PlatformScrollbar*>(m_vBar.get()) : 0);
387 int RenderListBox::listIndexAtOffset(int offsetX, int offsetY)
392 if (offsetY < borderTop() + paddingTop() || offsetY > height() - paddingBottom() - borderBottom())
395 int scrollbarWidth = m_vBar ? m_vBar->width() : 0;
396 if (offsetX < borderLeft() + paddingLeft() || offsetX > width() - borderRight() - paddingRight() - scrollbarWidth)
399 int newOffset = (offsetY - borderTop() - paddingTop()) / itemHeight() + m_indexOffset;
400 return newOffset < numItems() ? newOffset : -1;
403 void RenderListBox::autoscroll()
405 IntPoint pos = document()->frame()->view()->windowToContents(document()->frame()->eventHandler()->currentMousePosition());
409 absolutePosition(rx, ry);
410 int offsetX = pos.x() - rx;
411 int offsetY = pos.y() - ry;
414 int rows = numVisibleItems();
415 int offset = m_indexOffset;
416 if (offsetY < borderTop() + paddingTop() && scrollToRevealElementAtListIndex(offset - 1))
417 endIndex = offset - 1;
418 else if (offsetY > height() - paddingBottom() - borderBottom() && scrollToRevealElementAtListIndex(offset + rows))
419 endIndex = offset + rows - 1;
421 endIndex = listIndexAtOffset(offsetX, offsetY);
424 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
425 m_inAutoscroll = true;
426 if (!select->multiple())
427 select->setActiveSelectionAnchorIndex(endIndex);
428 select->setActiveSelectionEndIndex(endIndex);
429 select->updateListBoxSelection(!select->multiple());
430 m_inAutoscroll = false;
434 void RenderListBox::stopAutoscroll()
436 static_cast<HTMLSelectElement*>(node())->listBoxOnChange();
439 bool RenderListBox::scrollToRevealElementAtListIndex(int index)
441 if (index < 0 || index >= numItems() || listIndexIsVisible(index))
445 if (index < m_indexOffset)
448 newOffset = index - numVisibleItems() + 1;
450 m_indexOffset = newOffset;
452 m_vBar->setValue(m_indexOffset);
457 bool RenderListBox::listIndexIsVisible(int index)
459 return index >= m_indexOffset && index < m_indexOffset + numVisibleItems();
462 bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
464 return m_vBar && m_vBar->scroll(direction, granularity, multiplier);
467 void RenderListBox::valueChanged(unsigned listIndex)
469 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
470 select->setSelectedIndex(select->listToOptionIndex(listIndex));
474 void RenderListBox::valueChanged(Scrollbar*)
476 int newOffset = m_vBar->value();
477 if (newOffset != m_indexOffset) {
478 m_indexOffset = newOffset;
480 // Fire the scroll DOM event.
481 EventTargetNodeCast(node())->dispatchHTMLEvent(scrollEvent, true, false);
485 int RenderListBox::itemHeight() const
487 return style()->font().height() + rowSpacing;
490 int RenderListBox::verticalScrollbarWidth() const
492 return m_vBar ? m_vBar->width() : 0;
495 // FIXME: We ignore padding in the vertical direction as far as these values are concerned, since that's
496 // how the control currently paints.
497 int RenderListBox::scrollWidth() const
499 // There is no horizontal scrolling allowed.
500 return clientWidth();
503 int RenderListBox::scrollHeight() const
505 return max(clientHeight(), listHeight());
508 int RenderListBox::scrollLeft() const
513 void RenderListBox::setScrollLeft(int)
517 int RenderListBox::scrollTop() const
519 return m_indexOffset * itemHeight();
522 void RenderListBox::setScrollTop(int newTop)
524 // Determine an index and scroll to it.
525 int index = newTop / itemHeight();
526 if (index < 0 || index >= numItems() || index == m_indexOffset)
528 m_indexOffset = index;
530 m_vBar->setValue(index);
533 IntRect RenderListBox::controlClipRect(int tx, int ty) const
535 IntRect clipRect = contentBox();
536 clipRect.move(tx, ty);
540 IntRect RenderListBox::windowClipRect() const
542 FrameView* frameView = view()->frameView();
546 return frameView->windowClipRectForLayer(enclosingLayer(), true);
549 bool RenderListBox::isScrollable() const
551 if (numVisibleItems() < numItems())
553 return RenderObject::isScrollable();
556 } // namespace WebCore