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 (m_vBar && m_vBar->isWidget()) {
65 element()->document()->view()->removeChild(static_cast<PlatformScrollbar*>(m_vBar));
71 void RenderListBox::setStyle(RenderStyle* style)
73 RenderBlock::setStyle(style);
74 setReplaced(isInline());
77 void RenderListBox::updateFromElement()
79 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
80 const Vector<HTMLElement*>& listItems = select->listItems();
81 int size = listItems.size();
82 if (m_optionsChanged) {
84 TextStyle textStyle(0, 0, 0, false, false, false, false);
85 for (int i = 0; i < size; ++i) {
86 HTMLElement* element = listItems[i];
88 Font itemFont = style()->font();
89 if (element->hasTagName(optionTag))
90 text = static_cast<HTMLOptionElement*>(element)->optionText();
91 else if (element->hasTagName(optgroupTag)) {
92 text = static_cast<HTMLOptGroupElement*>(element)->groupLabelText();
93 FontDescription d = itemFont.fontDescription();
95 itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
99 if (!text.isEmpty()) {
100 float textWidth = itemFont.floatWidth(TextRun(text.impl()), textStyle);
101 width = max(width, textWidth);
104 m_optionsWidth = static_cast<int>(ceilf(width));
105 m_optionsChanged = false;
109 void RenderListBox::calcMinMaxWidth()
112 if (Scrollbar::hasPlatformScrollbars()) {
113 PlatformScrollbar* widget = new PlatformScrollbar(this, VerticalScrollbar, SmallScrollbar);
115 node()->document()->view()->addChild(widget);
119 if (m_optionsChanged)
125 if (style()->width().isFixed() && style()->width().value() > 0)
126 m_minWidth = m_maxWidth = calcContentBoxWidth(style()->width().value());
128 m_maxWidth = m_optionsWidth;
130 m_maxWidth += m_vBar->width();
133 if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
134 m_maxWidth = max(m_maxWidth, calcContentBoxWidth(style()->minWidth().value()));
135 m_minWidth = max(m_minWidth, calcContentBoxWidth(style()->minWidth().value()));
136 } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
139 m_minWidth = m_maxWidth;
141 if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
142 m_maxWidth = min(m_maxWidth, calcContentBoxWidth(style()->maxWidth().value()));
143 m_minWidth = min(m_minWidth, calcContentBoxWidth(style()->maxWidth().value()));
146 int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
153 const int minSize = 4;
154 const int minDefaultSize = 10;
155 int RenderListBox::size() const
157 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
158 int specifiedSize = select->size();
159 if (specifiedSize > 1)
160 return max(minSize, specifiedSize);
162 return min(max(numItems(), minSize), minDefaultSize);
165 int RenderListBox::numItems() const
167 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
168 return select->listItems().size();
171 void RenderListBox::calcHeight()
173 int toAdd = paddingTop() + paddingBottom() + borderTop() + borderBottom();
175 int itemHeight = style()->font().height() + optionsSpacingMiddle;
176 m_height = itemHeight * size() + toAdd;
178 RenderBlock::calcHeight();
181 m_vBar->setEnabled(size() < numItems());
182 m_vBar->setSteps(itemHeight, itemHeight);
183 m_vBar->setProportion(m_height - toAdd, itemHeight * numItems());
187 const int baselineAdjustment = 7;
188 short RenderListBox::baselinePosition(bool b, bool isRootLineBox) const
190 // 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.
191 return height() + marginTop() + marginBottom() - baselineAdjustment;
194 IntRect RenderListBox::itemBoundingBoxRect(int tx, int ty, int index)
196 return IntRect (tx + borderLeft() + paddingLeft(),
197 ty + borderTop() + paddingTop() + ((style()->font().height() + optionsSpacingMiddle) * (index - m_indexOffset)),
198 absoluteBoundingBoxRect().width() - borderLeft() - borderRight() - paddingLeft() - paddingRight(),
199 style()->font().height() + optionsSpacingMiddle);
202 void RenderListBox::paintObject(PaintInfo& i, int tx, int ty)
205 IntRect clipRect(tx + borderLeft(), ty + borderTop(),
206 width() - borderLeft() - borderRight(), height() - borderBottom() - borderTop());
207 if (i.phase == PaintPhaseForeground || i.phase == PaintPhaseChildBlockBackgrounds) {
208 if (clipRect.width() == 0 || clipRect.height() == 0)
214 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
215 int listItemsSize = select->listItems().size();
217 if (i.phase == PaintPhaseForeground) {
218 int index = m_indexOffset;
219 while (index < listItemsSize && index < m_indexOffset + size()) {
220 paintItemForeground(i, tx, ty, index);
225 // Paint the children.
226 RenderBlock::paintObject(i, tx, ty);
228 if (i.phase == PaintPhaseBlockBackground) {
229 int index = m_indexOffset;
230 while (index < listItemsSize && index < m_indexOffset + size()) {
231 paintItemBackground(i, tx, ty, index);
237 if (i.phase == PaintPhaseForeground || i.phase == PaintPhaseChildBlockBackgrounds)
241 void RenderListBox::paintScrollbar(PaintInfo& i)
244 IntRect absBounds = absoluteBoundingBoxRect();
245 IntRect scrollRect(absBounds.right() - borderRight() - m_vBar->width(),
246 absBounds.y() + borderTop(),
248 absBounds.height() - (borderTop() + borderBottom()));
249 m_vBar->setRect(scrollRect);
250 m_vBar->paint(i.p, scrollRect);
254 void RenderListBox::paintItemForeground(PaintInfo& i, int tx, int ty, int listIndex)
256 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
257 const Vector<HTMLElement*>& listItems = select->listItems();
258 HTMLElement* element = listItems[listIndex];
261 if (element->hasTagName(optionTag))
262 itemText = static_cast<HTMLOptionElement*>(element)->optionText();
263 else if (element->hasTagName(optgroupTag))
264 itemText = static_cast<HTMLOptGroupElement*>(element)->groupLabelText();
266 TextRun textRun(itemText.characters(), itemText.length());
268 // Determine where the item text should be placed
269 IntRect r = itemBoundingBoxRect(tx, ty, listIndex);
270 r.move(optionsSpacingLeft, style()->font().ascent());
272 RenderStyle* itemStyle = element->renderStyle();
276 Color textColor = element->renderStyle() ? element->renderStyle()->color() : style()->color();
277 if (element->hasTagName(optionTag) && static_cast<HTMLOptionElement*>(element)->selected()) {
278 if (document()->frame()->isActive() && document()->focusNode() == node())
279 textColor = theme()->activeListBoxSelectionForegroundColor();
281 FIXME: Decide what the desired behavior is for inactive foreground color.
282 For example, disabled items have a dark grey foreground color defined in CSS.
283 If we don't honor that, the selected disabled items will have a weird black-on-grey look.
285 textColor = theme()->inactiveListBoxSelectionForegroundColor();
290 i.p->setPen(textColor);
292 Font itemFont = style()->font();
293 if (element->hasTagName(optgroupTag)) {
294 FontDescription d = itemFont.fontDescription();
296 itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
299 i.p->setFont(itemFont);
301 // Draw the item text
302 if (itemStyle->visibility() != HIDDEN)
303 i.p->drawText(textRun, r.location());
306 void RenderListBox::paintItemBackground(PaintInfo& i, int tx, int ty, int listIndex)
308 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
309 const Vector<HTMLElement*>& listItems = select->listItems();
310 HTMLElement* element = listItems[listIndex];
313 if (element->hasTagName(optionTag) && static_cast<HTMLOptionElement*>(element)->selected()) {
314 if (document()->frame()->isActive() && document()->focusNode() == node())
315 backColor = theme()->activeListBoxSelectionBackgroundColor();
317 backColor = theme()->inactiveListBoxSelectionBackgroundColor();
319 backColor = element->renderStyle() ? element->renderStyle()->backgroundColor() : style()->backgroundColor();
321 // Draw the background for this list box item
322 if (!element->renderStyle() || element->renderStyle()->visibility() != HIDDEN)
323 i.p->fillRect(itemBoundingBoxRect(tx, ty, listIndex), backColor);
326 bool RenderListBox::isPointInScrollbar(NodeInfo& info, int _x, int _y, int _tx, int _ty)
331 IntRect vertRect(_tx + width() - borderRight() - m_vBar->width(),
332 _ty + borderTop() - borderTopExtra(),
334 height() + borderTopExtra() + borderBottomExtra() - borderTop() - borderBottom());
336 if (vertRect.contains(_x, _y)) {
337 info.setScrollbar(m_vBar->isWidget() ? static_cast<PlatformScrollbar*>(m_vBar) : 0);
343 HTMLOptionElement* RenderListBox::optionAtPoint(int x, int y)
345 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
346 const Vector<HTMLElement*>& listItems = select->listItems();
347 int yOffset = y - absoluteBoundingBoxRect().y();
348 int newOffset = max(0, yOffset / (style()->font().height() + optionsSpacingMiddle)) + m_indexOffset;
349 newOffset = max(0, min((int)listItems.size() - 1, newOffset));
350 int scrollbarWidth = m_vBar ? m_vBar->width() : 0;
351 if (x >= absoluteBoundingBoxRect().x() + borderLeft() + paddingLeft() && x < absoluteBoundingBoxRect().right() - borderRight() - paddingRight() - scrollbarWidth)
352 return static_cast<HTMLOptionElement*>(listItems[newOffset]);
356 void RenderListBox::autoscroll()
358 int mouseX = document()->frame()->view()->currentMousePosition().x();
359 int mouseY = document()->frame()->view()->currentMousePosition().y();
360 IntRect bounds = absoluteBoundingBoxRect();
362 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
363 const Vector<HTMLElement*>& items = select->listItems();
364 HTMLOptionElement* element = 0;
366 int offset = m_indexOffset;
367 if (mouseY < bounds.y() && scrollToRevealElementAtListIndex(offset - 1) && items[offset - 1]->hasTagName(optionTag))
368 element = static_cast<HTMLOptionElement*>(items[offset - 1]);
369 else if (mouseY > bounds.bottom() && scrollToRevealElementAtListIndex(offset + rows) && items[offset + rows - 1]->hasTagName(optionTag))
370 element = static_cast<HTMLOptionElement*>(items[offset + rows - 1]);
372 element = optionAtPoint(mouseX, mouseY);
375 select->setSelectedIndex(element->index(), !select->multiple());
380 bool RenderListBox::scrollToRevealElementAtListIndex(int index)
382 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
383 const Vector<HTMLElement*>& listItems = select->listItems();
385 if (index < 0 || index > (int)listItems.size() - 1 || (index >= m_indexOffset && index < m_indexOffset + size()))
389 if (index < m_indexOffset)
392 newOffset = index - size() + 1;
395 IntRect rect = absoluteBoundingBoxRect();
396 m_vBar->setValue(itemBoundingBoxRect(rect.x(), rect.y(), newOffset + m_indexOffset).y() - rect.y());
398 m_indexOffset = newOffset;
403 bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
405 return m_vBar && m_vBar->scroll(direction, granularity, multiplier);
408 void RenderListBox::valueChanged(unsigned listIndex)
410 HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
411 select->setSelectedIndex(select->listToOptionIndex(listIndex));
415 void RenderListBox::valueChanged(Scrollbar*)
418 int newOffset = max(0, m_vBar->value() / (style()->font().height() + optionsSpacingMiddle));
419 if (newOffset != m_indexOffset) {
420 m_indexOffset = newOffset;
421 // printf("value changed: new offset index: %d\n", newOffset);
423 // Fire the scroll DOM event.
424 EventTargetNodeCast(node())->dispatchHTMLEvent(scrollEvent, true, false);