2 * This file is part of the select element renderer in WebCore.
4 * Copyright (C) 2006 Apple Computer, Inc.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB. If not, write to
18 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
24 #include "RenderListBox.h"
27 #include "EventNames.h"
29 #include "FrameView.h"
30 #include "GraphicsContext.h"
31 #include "HTMLNames.h"
32 #include "HTMLOptionElement.h"
33 #include "HTMLOptGroupElement.h"
34 #include "HTMLSelectElement.h"
35 #include "PlatformScrollBar.h"
37 #include "RenderText.h"
38 #include "RenderTheme.h"
39 #include "TextStyle.h"
46 using namespace HTMLNames;
47 using namespace EventNames;
49 const int optionsSpacingMiddle = 1;
50 const int optionsSpacingLeft = 2;
52 RenderListBox::RenderListBox(HTMLSelectElement* element)
53 : RenderBlock(element)
54 , m_optionsChanged(true)
57 , m_selectionChanged(true)
62 RenderListBox::~RenderListBox()
64 // If this list box is being autoscrolled, stop the autoscroll timer
65 if (document() && document()->frame() && document()->frame()->autoscrollRenderer() == this)
66 document()->frame()->stopAutoscrollTimer();
68 if (m_vBar && m_vBar->isWidget()) {
69 element()->document()->view()->removeChild(static_cast<PlatformScrollbar*>(m_vBar));
75 void RenderListBox::setStyle(RenderStyle* style)
77 RenderBlock::setStyle(style);
78 setReplaced(isInline());
81 void RenderListBox::updateFromElement()
83 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
84 const Vector<HTMLElement*>& listItems = select->listItems();
85 int size = listItems.size();
86 if (m_optionsChanged) {
88 TextStyle textStyle(0, 0, 0, false, false, false, false);
89 for (int i = 0; i < size; ++i) {
90 HTMLElement* element = listItems[i];
92 Font itemFont = style()->font();
93 if (element->hasTagName(optionTag))
94 text = static_cast<HTMLOptionElement*>(element)->optionText();
95 else if (element->hasTagName(optgroupTag)) {
96 text = static_cast<HTMLOptGroupElement*>(element)->groupLabelText();
97 FontDescription d = itemFont.fontDescription();
99 itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
103 if (!text.isEmpty()) {
104 float textWidth = itemFont.floatWidth(TextRun(text.impl()), textStyle);
105 width = max(width, textWidth);
108 m_optionsWidth = static_cast<int>(ceilf(width));
109 m_optionsChanged = false;
113 void RenderListBox::calcMinMaxWidth()
116 if (Scrollbar::hasPlatformScrollbars()) {
117 PlatformScrollbar* widget = new PlatformScrollbar(this, VerticalScrollbar, SmallScrollbar);
119 node()->document()->view()->addChild(widget);
123 if (m_optionsChanged)
129 if (style()->width().isFixed() && style()->width().value() > 0)
130 m_minWidth = m_maxWidth = calcContentBoxWidth(style()->width().value());
132 m_maxWidth = m_optionsWidth;
134 m_maxWidth += m_vBar->width();
137 if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
138 m_maxWidth = max(m_maxWidth, calcContentBoxWidth(style()->minWidth().value()));
139 m_minWidth = max(m_minWidth, calcContentBoxWidth(style()->minWidth().value()));
140 } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
143 m_minWidth = m_maxWidth;
145 if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
146 m_maxWidth = min(m_maxWidth, calcContentBoxWidth(style()->maxWidth().value()));
147 m_minWidth = min(m_minWidth, calcContentBoxWidth(style()->maxWidth().value()));
150 int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
157 const int minSize = 4;
158 const int minDefaultSize = 10;
159 int RenderListBox::size() const
161 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
162 int specifiedSize = select->size();
163 if (specifiedSize > 1)
164 return max(minSize, specifiedSize);
166 return min(max(numItems(), minSize), minDefaultSize);
169 int RenderListBox::numItems() const
171 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
172 return select->listItems().size();
175 void RenderListBox::calcHeight()
177 int toAdd = paddingTop() + paddingBottom() + borderTop() + borderBottom();
179 int itemHeight = style()->font().height() + optionsSpacingMiddle;
180 m_height = itemHeight * size() + toAdd;
182 RenderBlock::calcHeight();
185 m_vBar->setEnabled(size() < numItems());
186 m_vBar->setSteps(itemHeight, itemHeight);
187 m_vBar->setProportion(m_height - toAdd, itemHeight * numItems());
191 const int baselineAdjustment = 7;
192 short RenderListBox::baselinePosition(bool b, bool isRootLineBox) const
194 // FIXME: This hardcoded baselineAdjustment is what we used to do for the old widget, but I'm not sure this is right for the new control.
195 return height() + marginTop() + marginBottom() - baselineAdjustment;
198 IntRect RenderListBox::itemBoundingBoxRect(int tx, int ty, int index)
200 return IntRect (tx + borderLeft() + paddingLeft(),
201 ty + borderTop() + paddingTop() + ((style()->font().height() + optionsSpacingMiddle) * (index - m_indexOffset)),
202 absoluteBoundingBoxRect().width() - borderLeft() - borderRight() - paddingLeft() - paddingRight(),
203 style()->font().height() + optionsSpacingMiddle);
206 void RenderListBox::paintObject(PaintInfo& i, int tx, int ty)
209 IntRect clipRect(tx + borderLeft(), ty + borderTop(),
210 width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop());
211 if (i.phase == PaintPhaseForeground || i.phase == PaintPhaseChildBlockBackgrounds) {
212 if (clipRect.width() == 0 || clipRect.height() == 0)
218 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
219 int listItemsSize = select->listItems().size();
221 if (i.phase == PaintPhaseForeground) {
222 int index = m_indexOffset;
223 while (index < listItemsSize && index < m_indexOffset + size()) {
224 paintItemForeground(i, tx, ty, index);
229 // Paint the children.
230 RenderBlock::paintObject(i, tx, ty);
232 if (i.phase == PaintPhaseBlockBackground) {
233 int index = m_indexOffset;
234 while (index < listItemsSize && index < m_indexOffset + size()) {
235 paintItemBackground(i, tx, ty, index);
241 if (i.phase == PaintPhaseForeground || i.phase == PaintPhaseChildBlockBackgrounds)
245 void RenderListBox::paintScrollbar(PaintInfo& i)
248 IntRect absBounds = absoluteBoundingBoxRect();
249 IntRect scrollRect(absBounds.right() - borderRight() - m_vBar->width(),
250 absBounds.y() + borderTop(),
252 absBounds.height() - (borderTop() + borderBottom()));
253 m_vBar->setRect(scrollRect);
254 m_vBar->paint(i.p, scrollRect);
258 void RenderListBox::paintItemForeground(PaintInfo& i, int tx, int ty, int listIndex)
260 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
261 const Vector<HTMLElement*>& listItems = select->listItems();
262 HTMLElement* element = listItems[listIndex];
265 if (element->hasTagName(optionTag))
266 itemText = static_cast<HTMLOptionElement*>(element)->optionText();
267 else if (element->hasTagName(optgroupTag))
268 itemText = static_cast<HTMLOptGroupElement*>(element)->groupLabelText();
270 TextRun textRun(itemText.characters(), itemText.length());
272 // Determine where the item text should be placed
273 IntRect r = itemBoundingBoxRect(tx, ty, listIndex);
274 r.move(optionsSpacingLeft, style()->font().ascent());
276 RenderStyle* itemStyle = element->renderStyle();
280 Color textColor = element->renderStyle() ? element->renderStyle()->color() : style()->color();
281 if (element->hasTagName(optionTag) && static_cast<HTMLOptionElement*>(element)->selected()) {
282 if (document()->frame()->isActive() && document()->focusNode() == node())
283 textColor = theme()->activeListBoxSelectionForegroundColor();
285 FIXME: Decide what the desired behavior is for inactive foreground color.
286 For example, disabled items have a dark grey foreground color defined in CSS.
287 If we don't honor that, the selected disabled items will have a weird black-on-grey look.
289 textColor = theme()->inactiveListBoxSelectionForegroundColor();
294 i.p->setPen(textColor);
296 Font itemFont = style()->font();
297 if (element->hasTagName(optgroupTag)) {
298 FontDescription d = itemFont.fontDescription();
300 itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
303 i.p->setFont(itemFont);
305 // Draw the item text
306 if (itemStyle->visibility() != HIDDEN)
307 i.p->drawText(textRun, r.location());
310 void RenderListBox::paintItemBackground(PaintInfo& i, int tx, int ty, int listIndex)
312 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
313 const Vector<HTMLElement*>& listItems = select->listItems();
314 HTMLElement* element = listItems[listIndex];
317 if (element->hasTagName(optionTag) && static_cast<HTMLOptionElement*>(element)->selected()) {
318 if (document()->frame()->isActive() && document()->focusNode() == node())
319 backColor = theme()->activeListBoxSelectionBackgroundColor();
321 backColor = theme()->inactiveListBoxSelectionBackgroundColor();
323 backColor = element->renderStyle() ? element->renderStyle()->backgroundColor() : style()->backgroundColor();
325 // Draw the background for this list box item
326 if (!element->renderStyle() || element->renderStyle()->visibility() != HIDDEN)
327 i.p->fillRect(itemBoundingBoxRect(tx, ty, listIndex), backColor);
330 bool RenderListBox::isPointInScrollbar(NodeInfo& info, int _x, int _y, int _tx, int _ty)
335 IntRect vertRect(_tx + width() - borderRight() - m_vBar->width(),
336 _ty + borderTop() - borderTopExtra(),
338 height() + borderTopExtra() + borderBottomExtra() - borderTop() - borderBottom());
340 if (vertRect.contains(_x, _y)) {
341 info.setScrollbar(m_vBar->isWidget() ? static_cast<PlatformScrollbar*>(m_vBar) : 0);
347 HTMLOptionElement* RenderListBox::optionAtPoint(int x, int y)
349 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
350 const Vector<HTMLElement*>& listItems = select->listItems();
351 int yOffset = y - absoluteBoundingBoxRect().y();
352 int newOffset = max(0, yOffset / (style()->font().height() + optionsSpacingMiddle)) + m_indexOffset;
353 newOffset = max(0, min((int)listItems.size() - 1, newOffset));
354 int scrollbarWidth = m_vBar ? m_vBar->width() : 0;
355 if (x >= absoluteBoundingBoxRect().x() + borderLeft() + paddingLeft() && x < absoluteBoundingBoxRect().right() - borderRight() - paddingRight() - scrollbarWidth)
356 return static_cast<HTMLOptionElement*>(listItems[newOffset]);
360 void RenderListBox::autoscroll()
362 int mouseX = document()->frame()->view()->currentMousePosition().x();
363 int mouseY = document()->frame()->view()->currentMousePosition().y();
364 IntRect bounds = absoluteBoundingBoxRect();
366 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
367 const Vector<HTMLElement*>& items = select->listItems();
368 HTMLOptionElement* element = 0;
370 int offset = m_indexOffset;
371 if (mouseY < bounds.y() && scrollToRevealElementAtListIndex(offset - 1) && items[offset - 1]->hasTagName(optionTag))
372 element = static_cast<HTMLOptionElement*>(items[offset - 1]);
373 else if (mouseY > bounds.bottom() && scrollToRevealElementAtListIndex(offset + rows) && items[offset + rows - 1]->hasTagName(optionTag))
374 element = static_cast<HTMLOptionElement*>(items[offset + rows - 1]);
376 element = optionAtPoint(mouseX, mouseY);
379 select->setSelectedIndex(element->index(), !select->multiple());
384 bool RenderListBox::scrollToRevealElementAtListIndex(int index)
386 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
387 const Vector<HTMLElement*>& listItems = select->listItems();
389 if (index < 0 || index > (int)listItems.size() - 1 || (index >= m_indexOffset && index < m_indexOffset + size()))
393 if (index < m_indexOffset)
396 newOffset = index - size() + 1;
399 IntRect rect = absoluteBoundingBoxRect();
400 m_vBar->setValue(itemBoundingBoxRect(rect.x(), rect.y(), newOffset + m_indexOffset).y() - rect.y());
402 m_indexOffset = newOffset;
407 bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
409 return m_vBar && m_vBar->scroll(direction, granularity, multiplier);
412 void RenderListBox::valueChanged(unsigned listIndex)
414 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
415 select->setSelectedIndex(select->listToOptionIndex(listIndex));
419 void RenderListBox::valueChanged(Scrollbar*)
422 int newOffset = max(0, m_vBar->value() / (style()->font().height() + optionsSpacingMiddle));
423 if (newOffset != m_indexOffset) {
424 m_indexOffset = newOffset;
425 // printf("value changed: new offset index: %d\n", newOffset);
427 // Fire the scroll DOM event.
428 EventTargetNodeCast(node())->dispatchHTMLEvent(scrollEvent, true, false);