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;
122
if (!m_vBar && Scrollbar::hasPlatformScrollbars())
123
if (FrameView* view = node()->document()->view()) {
124
RefPtr<PlatformScrollbar> widget = new PlatformScrollbar(this, VerticalScrollbar, SmallScrollbar);
125
view->addChild(widget.get());
126
m_vBar = widget.release();
129
setNeedsLayoutAndPrefWidthsRecalc();
133
void RenderListBox::selectionChanged()
136
if (!m_inAutoscroll) {
137
if (m_optionsChanged || needsLayout())
138
m_scrollToRevealSelectionAfterLayout = true;
140
scrollToRevealSelection();
144
void RenderListBox::layout()
146
RenderBlock::layout();
147
if (m_scrollToRevealSelectionAfterLayout)
148
scrollToRevealSelection();
151
void RenderListBox::scrollToRevealSelection()
153
HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
155
m_scrollToRevealSelectionAfterLayout = false;
157
int firstIndex = select->activeSelectionStartListIndex();
158
if (firstIndex >= 0 && !listIndexIsVisible(select->activeSelectionEndListIndex()))
159
scrollToRevealElementAtListIndex(firstIndex);
162
void RenderListBox::calcPrefWidths()
164
ASSERT(!m_optionsChanged);
169
if (style()->width().isFixed() && style()->width().value() > 0)
170
m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value());
172
m_maxPrefWidth = m_optionsWidth + 2 * optionsSpacingHorizontal;
174
m_maxPrefWidth += m_vBar->width();
177
if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
178
m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
179
m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
180
} else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
183
m_minPrefWidth = m_maxPrefWidth;
185
if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
186
m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
187
m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
190
int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
191
m_minPrefWidth += toAdd;
192
m_maxPrefWidth += toAdd;
194
setPrefWidthsDirty(false);
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
if (style()->visibility() != VISIBLE)
254
int listItemsSize = numItems();
256
if (paintInfo.phase == PaintPhaseForeground) {
257
int index = m_indexOffset;
258
while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
259
paintItemForeground(paintInfo, tx, ty, index);
264
// Paint the children.
265
RenderBlock::paintObject(paintInfo, tx, ty);
267
if (paintInfo.phase == PaintPhaseBlockBackground)
268
paintScrollbar(paintInfo);
269
else if (paintInfo.phase == PaintPhaseChildBlockBackground || paintInfo.phase == PaintPhaseChildBlockBackgrounds) {
270
int index = m_indexOffset;
271
while (index < listItemsSize && index <= m_indexOffset + numVisibleItems()) {
272
paintItemBackground(paintInfo, tx, ty, index);
278
void RenderListBox::paintScrollbar(PaintInfo& paintInfo)
281
IntRect absBounds = absoluteBoundingBoxRect();
282
IntRect scrollRect(absBounds.right() - borderRight() - m_vBar->width(),
283
absBounds.y() + borderTop(),
285
absBounds.height() - (borderTop() + borderBottom()));
286
m_vBar->setRect(scrollRect);
287
m_vBar->paint(paintInfo.context, scrollRect);
291
void RenderListBox::paintItemForeground(PaintInfo& paintInfo, int tx, int ty, int listIndex)
293
HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
294
const Vector<HTMLElement*>& listItems = select->listItems();
295
HTMLElement* element = listItems[listIndex];
298
if (element->hasTagName(optionTag))
299
itemText = static_cast<HTMLOptionElement*>(element)->optionText();
300
else if (element->hasTagName(optgroupTag))
301
itemText = static_cast<HTMLOptGroupElement*>(element)->groupLabelText();
303
// Determine where the item text should be placed
304
IntRect r = itemBoundingBoxRect(tx, ty, listIndex);
305
r.move(optionsSpacingHorizontal, style()->font().ascent());
307
RenderStyle* itemStyle = element->renderStyle();
311
Color textColor = element->renderStyle() ? element->renderStyle()->color() : style()->color();
312
if (element->hasTagName(optionTag) && static_cast<HTMLOptionElement*>(element)->selected()) {
313
if (document()->frame()->isActive() && document()->focusedNode() == node())
314
textColor = theme()->activeListBoxSelectionForegroundColor();
315
// Honor the foreground color for disabled items
316
else if (!element->disabled())
317
textColor = theme()->inactiveListBoxSelectionForegroundColor();
320
paintInfo.context->setFillColor(textColor);
322
Font itemFont = style()->font();
323
if (element->hasTagName(optgroupTag)) {
324
FontDescription d = itemFont.fontDescription();
326
itemFont = Font(d, itemFont.letterSpacing(), itemFont.wordSpacing());
329
paintInfo.context->setFont(itemFont);
331
unsigned length = itemText.length();
332
const UChar* string = itemText.characters();
333
TextStyle textStyle(0, 0, 0, itemStyle->direction() == RTL, itemStyle->unicodeBidi() == Override, false, false);
334
TextRun textRun(string, length);
336
// Draw the item text
337
if (itemStyle->visibility() != HIDDEN)
338
paintInfo.context->drawBidiText(textRun, r.location(), textStyle);
341
void RenderListBox::paintItemBackground(PaintInfo& paintInfo, int tx, int ty, int listIndex)
343
HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
344
const Vector<HTMLElement*>& listItems = select->listItems();
345
HTMLElement* element = listItems[listIndex];
348
if (element->hasTagName(optionTag) && static_cast<HTMLOptionElement*>(element)->selected()) {
349
if (document()->frame()->isActive() && document()->focusedNode() == node())
350
backColor = theme()->activeListBoxSelectionBackgroundColor();
352
backColor = theme()->inactiveListBoxSelectionBackgroundColor();
354
backColor = element->renderStyle() ? element->renderStyle()->backgroundColor() : style()->backgroundColor();
356
// Draw the background for this list box item
357
if (!element->renderStyle() || element->renderStyle()->visibility() != HIDDEN) {
358
IntRect itemRect = itemBoundingBoxRect(tx, ty, listIndex);
359
itemRect.intersect(controlClipRect(tx, ty));
360
paintInfo.context->fillRect(itemRect, backColor);
364
bool RenderListBox::isPointInOverflowControl(HitTestResult& result, int _x, int _y, int _tx, int _ty)
369
IntRect vertRect(_tx + width() - borderRight() - m_vBar->width(),
370
_ty + borderTop() - borderTopExtra(),
372
height() + borderTopExtra() + borderBottomExtra() - borderTop() - borderBottom());
374
if (vertRect.contains(_x, _y)) {
375
result.setScrollbar(m_vBar->isWidget() ? static_cast<PlatformScrollbar*>(m_vBar.get()) : 0);
381
int RenderListBox::listIndexAtOffset(int offsetX, int offsetY)
386
if (offsetY < borderTop() + paddingTop() || offsetY > height() - paddingBottom() - borderBottom())
389
int scrollbarWidth = m_vBar ? m_vBar->width() : 0;
390
if (offsetX < borderLeft() + paddingLeft() || offsetX > width() - borderRight() - paddingRight() - scrollbarWidth)
393
int newOffset = (offsetY - borderTop() - paddingTop()) / itemHeight() + m_indexOffset;
394
return newOffset < numItems() ? newOffset : -1;
397
void RenderListBox::autoscroll()
399
IntPoint pos = document()->frame()->view()->windowToContents(document()->frame()->eventHandler()->currentMousePosition());
403
absolutePosition(rx, ry);
404
int offsetX = pos.x() - rx;
405
int offsetY = pos.y() - ry;
408
int rows = numVisibleItems();
409
int offset = m_indexOffset;
410
if (offsetY < borderTop() + paddingTop() && scrollToRevealElementAtListIndex(offset - 1))
411
endIndex = offset - 1;
412
else if (offsetY > height() - paddingBottom() - borderBottom() && scrollToRevealElementAtListIndex(offset + rows))
413
endIndex = offset + rows - 1;
415
endIndex = listIndexAtOffset(offsetX, offsetY);
418
HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
419
m_inAutoscroll = true;
420
if (!select->multiple())
421
select->setActiveSelectionAnchorIndex(endIndex);
422
select->setActiveSelectionEndIndex(endIndex);
423
select->updateListBoxSelection(!select->multiple());
424
m_inAutoscroll = false;
428
void RenderListBox::stopAutoscroll()
430
static_cast<HTMLSelectElement*>(node())->listBoxOnChange();
433
bool RenderListBox::scrollToRevealElementAtListIndex(int index)
435
if (index < 0 || index >= numItems() || listIndexIsVisible(index))
439
if (index < m_indexOffset)
442
newOffset = index - numVisibleItems() + 1;
444
m_indexOffset = newOffset;
446
m_vBar->setValue(m_indexOffset);
451
bool RenderListBox::listIndexIsVisible(int index)
453
return index >= m_indexOffset && index < m_indexOffset + numVisibleItems();
456
bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
458
return m_vBar && m_vBar->scroll(direction, granularity, multiplier);
461
void RenderListBox::valueChanged(unsigned listIndex)
463
HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
464
select->setSelectedIndex(select->listToOptionIndex(listIndex));
468
void RenderListBox::valueChanged(Scrollbar*)
470
int newOffset = m_vBar->value();
471
if (newOffset != m_indexOffset) {
472
m_indexOffset = newOffset;
474
// Fire the scroll DOM event.
475
EventTargetNodeCast(node())->dispatchHTMLEvent(scrollEvent, true, false);
479
int RenderListBox::itemHeight() const
481
return style()->font().height() + rowSpacing;
484
int RenderListBox::verticalScrollbarWidth() const
486
return m_vBar ? m_vBar->width() : 0;
489
// FIXME: We ignore padding in the vertical direction as far as these values are concerned, since that's
490
// how the control currently paints.
491
int RenderListBox::scrollWidth() const
493
// There is no horizontal scrolling allowed.
494
return clientWidth();
497
int RenderListBox::scrollHeight() const
499
return max(clientHeight(), listHeight());
502
int RenderListBox::scrollLeft() const
507
void RenderListBox::setScrollLeft(int)
511
int RenderListBox::scrollTop() const
513
return m_indexOffset * itemHeight();
516
void RenderListBox::setScrollTop(int newTop)
518
// Determine an index and scroll to it.
519
int index = newTop / itemHeight();
520
if (index < 0 || index >= numItems() || index == m_indexOffset)
522
m_indexOffset = index;
524
m_vBar->setValue(index);
527
IntRect RenderListBox::controlClipRect(int tx, int ty) const
529
IntRect clipRect = contentBox();
530
clipRect.move(tx, ty);
534
IntRect RenderListBox::windowClipRect() const
536
FrameView* frameView = view()->frameView();
540
return frameView->windowClipRectForLayer(enclosingLayer(), true);
543
bool RenderListBox::isScrollable() const
545
if (numVisibleItems() < numItems())
547
return RenderObject::isScrollable();
550
} // namespace WebCore