1 /*
2 * This file is part of the select element renderer in WebCore.
3 *
4 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
5 * 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
21 *
22 */
23
24 #include "config.h"
25 #include "RenderMenuList.h"
26
27 #include "CSSStyleSelector.h"
28 #include "Frame.h"
29 #include "FrameView.h"
30 #include "HTMLNames.h"
31 #include "NodeRenderStyle.h"
32 #include "OptionElement.h"
33 #include "OptionGroupElement.h"
34 #include "PopupMenu.h"
35 #include "RenderBR.h"
36 #include "RenderScrollbar.h"
37 #include "RenderTheme.h"
38 #include "SelectElement.h"
39 #include <math.h>
40
41 using namespace std;
42
43 namespace WebCore {
44
45 using namespace HTMLNames;
46
RenderMenuList(Element * element)47 RenderMenuList::RenderMenuList(Element* element)
48 : RenderFlexibleBox(element)
49 , m_buttonText(0)
50 , m_innerBlock(0)
51 , m_optionsChanged(true)
52 , m_optionsWidth(0)
53 , m_popup(0)
54 , m_popupIsVisible(false)
55 {
56 }
57
~RenderMenuList()58 RenderMenuList::~RenderMenuList()
59 {
60 if (m_popup)
61 m_popup->disconnectClient();
62 m_popup = 0;
63 }
64
createInnerBlock()65 void RenderMenuList::createInnerBlock()
66 {
67 if (m_innerBlock) {
68 ASSERT(firstChild() == m_innerBlock);
69 ASSERT(!m_innerBlock->nextSibling());
70 return;
71 }
72
73 // Create an anonymous block.
74 ASSERT(!firstChild());
75 m_innerBlock = createAnonymousBlock();
76 adjustInnerStyle();
77 RenderFlexibleBox::addChild(m_innerBlock);
78 }
79
adjustInnerStyle()80 void RenderMenuList::adjustInnerStyle()
81 {
82 m_innerBlock->style()->setBoxFlex(1.0f);
83
84 m_innerBlock->style()->setPaddingLeft(Length(theme()->popupInternalPaddingLeft(style()), Fixed));
85 m_innerBlock->style()->setPaddingRight(Length(theme()->popupInternalPaddingRight(style()), Fixed));
86 m_innerBlock->style()->setPaddingTop(Length(theme()->popupInternalPaddingTop(style()), Fixed));
87 m_innerBlock->style()->setPaddingBottom(Length(theme()->popupInternalPaddingBottom(style()), Fixed));
88
89 if (PopupMenu::itemWritingDirectionIsNatural()) {
90 // Items in the popup will not respect the CSS text-align and direction properties,
91 // so we must adjust our own style to match.
92 m_innerBlock->style()->setTextAlign(LEFT);
93 TextDirection direction = (m_buttonText && m_buttonText->text()->defaultWritingDirection() == WTF::Unicode::RightToLeft) ? RTL : LTR;
94 m_innerBlock->style()->setDirection(direction);
95 }
96 }
97
addChild(RenderObject * newChild,RenderObject * beforeChild)98 void RenderMenuList::addChild(RenderObject* newChild, RenderObject* beforeChild)
99 {
100 createInnerBlock();
101 m_innerBlock->addChild(newChild, beforeChild);
102 }
103
removeChild(RenderObject * oldChild)104 void RenderMenuList::removeChild(RenderObject* oldChild)
105 {
106 if (oldChild == m_innerBlock || !m_innerBlock) {
107 RenderFlexibleBox::removeChild(oldChild);
108 m_innerBlock = 0;
109 } else
110 m_innerBlock->removeChild(oldChild);
111 }
112
styleDidChange(StyleDifference diff,const RenderStyle * oldStyle)113 void RenderMenuList::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
114 {
115 RenderBlock::styleDidChange(diff, oldStyle);
116
117 if (m_buttonText)
118 m_buttonText->setStyle(style());
119 if (m_innerBlock) // RenderBlock handled updating the anonymous block's style.
120 adjustInnerStyle();
121
122 setReplaced(isInline());
123
124 bool fontChanged = !oldStyle || oldStyle->font() != style()->font();
125 if (fontChanged)
126 updateOptionsWidth();
127 }
128
updateOptionsWidth()129 void RenderMenuList::updateOptionsWidth()
130 {
131 float maxOptionWidth = 0;
132 const Vector<Element*>& listItems = toSelectElement(static_cast<Element*>(node()))->listItems();
133 int size = listItems.size();
134 for (int i = 0; i < size; ++i) {
135 Element* element = listItems[i];
136 OptionElement* optionElement = toOptionElement(element);
137 if (!optionElement)
138 continue;
139
140 String text = optionElement->textIndentedToRespectGroupLabel();
141 if (theme()->popupOptionSupportsTextIndent()) {
142 // Add in the option's text indent. We can't calculate percentage values for now.
143 float optionWidth = 0;
144 if (RenderStyle* optionStyle = element->renderStyle())
145 optionWidth += optionStyle->textIndent().calcMinValue(0);
146 if (!text.isEmpty())
147 optionWidth += style()->font().floatWidth(text);
148 maxOptionWidth = max(maxOptionWidth, optionWidth);
149 } else if (!text.isEmpty())
150 maxOptionWidth = max(maxOptionWidth, style()->font().floatWidth(text));
151 }
152
153 int width = static_cast<int>(ceilf(maxOptionWidth));
154 if (m_optionsWidth == width)
155 return;
156
157 m_optionsWidth = width;
158 if (parent())
159 setNeedsLayoutAndPrefWidthsRecalc();
160 }
161
updateFromElement()162 void RenderMenuList::updateFromElement()
163 {
164 if (m_optionsChanged) {
165 updateOptionsWidth();
166 m_optionsChanged = false;
167 }
168
169 if (m_popupIsVisible)
170 m_popup->updateFromElement();
171 else
172 setTextFromOption(toSelectElement(static_cast<Element*>(node()))->selectedIndex());
173 }
174
setTextFromOption(int optionIndex)175 void RenderMenuList::setTextFromOption(int optionIndex)
176 {
177 SelectElement* select = toSelectElement(static_cast<Element*>(node()));
178 const Vector<Element*>& listItems = select->listItems();
179 int size = listItems.size();
180
181 int i = select->optionToListIndex(optionIndex);
182 String text = "";
183 if (i >= 0 && i < size) {
184 if (OptionElement* optionElement = toOptionElement(listItems[i]))
185 text = optionElement->textIndentedToRespectGroupLabel();
186 }
187
188 setText(text.stripWhiteSpace());
189 }
190
setText(const String & s)191 void RenderMenuList::setText(const String& s)
192 {
193 if (s.isEmpty()) {
194 if (!m_buttonText || !m_buttonText->isBR()) {
195 if (m_buttonText)
196 m_buttonText->destroy();
197 m_buttonText = new (renderArena()) RenderBR(document());
198 m_buttonText->setStyle(style());
199 addChild(m_buttonText);
200 }
201 } else {
202 if (m_buttonText && !m_buttonText->isBR())
203 m_buttonText->setText(s.impl());
204 else {
205 if (m_buttonText)
206 m_buttonText->destroy();
207 m_buttonText = new (renderArena()) RenderText(document(), s.impl());
208 m_buttonText->setStyle(style());
209 addChild(m_buttonText);
210 }
211 adjustInnerStyle();
212 }
213 }
214
text() const215 String RenderMenuList::text() const
216 {
217 return m_buttonText ? m_buttonText->text() : 0;
218 }
219
controlClipRect(int tx,int ty) const220 IntRect RenderMenuList::controlClipRect(int tx, int ty) const
221 {
222 // Clip to the intersection of the content box and the content box for the inner box
223 // This will leave room for the arrows which sit in the inner box padding,
224 // and if the inner box ever spills out of the outer box, that will get clipped too.
225 IntRect outerBox(tx + borderLeft() + paddingLeft(),
226 ty + borderTop() + paddingTop(),
227 contentWidth(),
228 contentHeight());
229
230 IntRect innerBox(tx + m_innerBlock->x() + m_innerBlock->paddingLeft(),
231 ty + m_innerBlock->y() + m_innerBlock->paddingTop(),
232 m_innerBlock->contentWidth(),
233 m_innerBlock->contentHeight());
234
235 return intersection(outerBox, innerBox);
236 }
237
calcPrefWidths()238 void RenderMenuList::calcPrefWidths()
239 {
240 m_minPrefWidth = 0;
241 m_maxPrefWidth = 0;
242
243 if (style()->width().isFixed() && style()->width().value() > 0)
244 m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value());
245 else
246 m_maxPrefWidth = max(m_optionsWidth, theme()->minimumMenuListSize(style())) + m_innerBlock->paddingLeft() + m_innerBlock->paddingRight();
247
248 if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
249 m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
250 m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
251 } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
252 m_minPrefWidth = 0;
253 else
254 m_minPrefWidth = m_maxPrefWidth;
255
256 if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
257 m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
258 m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
259 }
260
261 int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
262 m_minPrefWidth += toAdd;
263 m_maxPrefWidth += toAdd;
264
265 setPrefWidthsDirty(false);
266 }
267
showPopup()268 void RenderMenuList::showPopup()
269 {
270 if (m_popupIsVisible)
271 return;
272
273 // Create m_innerBlock here so it ends up as the first child.
274 // This is important because otherwise we might try to create m_innerBlock
275 // inside the showPopup call and it would fail.
276 createInnerBlock();
277 if (!m_popup)
278 m_popup = PopupMenu::create(this);
279 SelectElement* select = toSelectElement(static_cast<Element*>(node()));
280 m_popupIsVisible = true;
281
282 // Compute the top left taking transforms into account, but use
283 // the actual width of the element to size the popup.
284 FloatPoint absTopLeft = localToAbsolute(FloatPoint(), false, true);
285 IntRect absBounds = absoluteBoundingBoxRect();
286 absBounds.setLocation(roundedIntPoint(absTopLeft));
287 m_popup->show(absBounds, document()->view(),
288 select->optionToListIndex(select->selectedIndex()));
289 }
290
hidePopup()291 void RenderMenuList::hidePopup()
292 {
293 if (m_popup)
294 m_popup->hide();
295 m_popupIsVisible = false;
296 }
297
valueChanged(unsigned listIndex,bool fireOnChange)298 void RenderMenuList::valueChanged(unsigned listIndex, bool fireOnChange)
299 {
300 // Check to ensure a page navigation has not occurred while
301 // the popup was up.
302 Document* doc = static_cast<Element*>(node())->document();
303 if (!doc || doc != doc->frame()->document())
304 return;
305
306 SelectElement* select = toSelectElement(static_cast<Element*>(node()));
307 select->setSelectedIndexByUser(select->listToOptionIndex(listIndex), true, fireOnChange);
308 }
309
itemText(unsigned listIndex) const310 String RenderMenuList::itemText(unsigned listIndex) const
311 {
312 SelectElement* select = toSelectElement(static_cast<Element*>(node()));
313 Element* element = select->listItems()[listIndex];
314 if (OptionGroupElement* optionGroupElement = toOptionGroupElement(element))
315 return optionGroupElement->groupLabelText();
316 else if (OptionElement* optionElement = toOptionElement(element))
317 return optionElement->textIndentedToRespectGroupLabel();
318 return String();
319 }
320
itemToolTip(unsigned listIndex) const321 String RenderMenuList::itemToolTip(unsigned listIndex) const
322 {
323 SelectElement* select = toSelectElement(static_cast<Element*>(node()));
324 Element* element = select->listItems()[listIndex];
325 return element->title();
326 }
327
itemIsEnabled(unsigned listIndex) const328 bool RenderMenuList::itemIsEnabled(unsigned listIndex) const
329 {
330 SelectElement* select = toSelectElement(static_cast<Element*>(node()));
331 Element* element = select->listItems()[listIndex];
332 if (!isOptionElement(element))
333 return false;
334
335 bool groupEnabled = true;
336 if (Element* parentElement = element->parentElement()) {
337 if (isOptionGroupElement(parentElement))
338 groupEnabled = parentElement->isEnabledFormControl();
339 }
340 if (!groupEnabled)
341 return false;
342
343 return element->isEnabledFormControl();
344 }
345
itemStyle(unsigned listIndex) const346 PopupMenuStyle RenderMenuList::itemStyle(unsigned listIndex) const
347 {
348 SelectElement* select = toSelectElement(static_cast<Element*>(node()));
349 Element* element = select->listItems()[listIndex];
350
351 RenderStyle* style = element->renderStyle() ? element->renderStyle() : element->computedStyle();
352 return style ? PopupMenuStyle(style->color(), itemBackgroundColor(listIndex), style->font(), style->visibility() == VISIBLE, style->textIndent(), style->direction()) : menuStyle();
353 }
354
itemBackgroundColor(unsigned listIndex) const355 Color RenderMenuList::itemBackgroundColor(unsigned listIndex) const
356 {
357 SelectElement* select = toSelectElement(static_cast<Element*>(node()));
358 Element* element = select->listItems()[listIndex];
359
360 Color backgroundColor;
361 if (element->renderStyle())
362 backgroundColor = element->renderStyle()->backgroundColor();
363 // If the item has an opaque background color, return that.
364 if (!backgroundColor.hasAlpha())
365 return backgroundColor;
366
367 // Otherwise, the item's background is overlayed on top of the menu background.
368 backgroundColor = style()->backgroundColor().blend(backgroundColor);
369 if (!backgroundColor.hasAlpha())
370 return backgroundColor;
371
372 // If the menu background is not opaque, then add an opaque white background behind.
373 return Color(Color::white).blend(backgroundColor);
374 }
375
menuStyle() const376 PopupMenuStyle RenderMenuList::menuStyle() const
377 {
378
379 RenderStyle* s = m_innerBlock ? m_innerBlock->style() : style();
380 return PopupMenuStyle(s->color(), s->backgroundColor(), s->font(), s->visibility() == VISIBLE, s->textIndent(), s->direction());
381 }
382
hostWindow() const383 HostWindow* RenderMenuList::hostWindow() const
384 {
385 return document()->view()->hostWindow();
386 }
387
createScrollbar(ScrollbarClient * client,ScrollbarOrientation orientation,ScrollbarControlSize controlSize)388 PassRefPtr<Scrollbar> RenderMenuList::createScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, ScrollbarControlSize controlSize)
389 {
390 RefPtr<Scrollbar> widget;
391 bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR);
392 if (hasCustomScrollbarStyle)
393 widget = RenderScrollbar::createCustomScrollbar(client, orientation, this);
394 else
395 widget = Scrollbar::createNativeScrollbar(client, orientation, controlSize);
396 return widget.release();
397 }
398
clientInsetLeft() const399 int RenderMenuList::clientInsetLeft() const
400 {
401 return 0;
402 }
403
clientInsetRight() const404 int RenderMenuList::clientInsetRight() const
405 {
406 return 0;
407 }
408
clientPaddingLeft() const409 int RenderMenuList::clientPaddingLeft() const
410 {
411 return paddingLeft();
412 }
413
clientPaddingRight() const414 int RenderMenuList::clientPaddingRight() const
415 {
416 return paddingRight();
417 }
418
listSize() const419 int RenderMenuList::listSize() const
420 {
421 SelectElement* select = toSelectElement(static_cast<Element*>(node()));
422 return select->listItems().size();
423 }
424
selectedIndex() const425 int RenderMenuList::selectedIndex() const
426 {
427 SelectElement* select = toSelectElement(static_cast<Element*>(node()));
428 return select->optionToListIndex(select->selectedIndex());
429 }
430
itemIsSeparator(unsigned listIndex) const431 bool RenderMenuList::itemIsSeparator(unsigned listIndex) const
432 {
433 SelectElement* select = toSelectElement(static_cast<Element*>(node()));
434 Element* element = select->listItems()[listIndex];
435 return element->hasTagName(hrTag);
436 }
437
itemIsLabel(unsigned listIndex) const438 bool RenderMenuList::itemIsLabel(unsigned listIndex) const
439 {
440 SelectElement* select = toSelectElement(static_cast<Element*>(node()));
441 Element* element = select->listItems()[listIndex];
442 return isOptionGroupElement(element);
443 }
444
itemIsSelected(unsigned listIndex) const445 bool RenderMenuList::itemIsSelected(unsigned listIndex) const
446 {
447 SelectElement* select = toSelectElement(static_cast<Element*>(node()));
448 Element* element = select->listItems()[listIndex];
449 if (OptionElement* optionElement = toOptionElement(element))
450 return optionElement->selected();
451 return false;
452 }
453
setTextFromItem(unsigned listIndex)454 void RenderMenuList::setTextFromItem(unsigned listIndex)
455 {
456 SelectElement* select = toSelectElement(static_cast<Element*>(node()));
457 setTextFromOption(select->listToOptionIndex(listIndex));
458 }
459
fontSelector() const460 FontSelector* RenderMenuList::fontSelector() const
461 {
462 return document()->styleSelector()->fontSelector();
463 }
464
465 }
466