• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * This file is part of the select element renderer in WebCore.
3  *
4  * Copyright (C) 2006, 2007, 2008, 2009, 2010 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 "AXObjectCache.h"
28 #include "CSSStyleSelector.h"
29 #include "Frame.h"
30 #include "FrameView.h"
31 #include "HTMLNames.h"
32 #include "NodeRenderStyle.h"
33 #include "OptionElement.h"
34 #include "OptionGroupElement.h"
35 #include "PopupMenu.h"
36 #include "RenderBR.h"
37 #include "RenderScrollbar.h"
38 #include "RenderTheme.h"
39 #include "SelectElement.h"
40 #include <math.h>
41 
42 using namespace std;
43 
44 namespace WebCore {
45 
46 using namespace HTMLNames;
47 
RenderMenuList(Element * element)48 RenderMenuList::RenderMenuList(Element* element)
49     : RenderFlexibleBox(element)
50     , m_buttonText(0)
51     , m_innerBlock(0)
52     , m_optionsChanged(true)
53     , m_optionsWidth(0)
54     , m_lastSelectedIndex(-1)
55     , m_popup(0)
56     , m_popupIsVisible(false)
57 {
58 }
59 
~RenderMenuList()60 RenderMenuList::~RenderMenuList()
61 {
62     if (m_popup)
63         m_popup->disconnectClient();
64     m_popup = 0;
65 }
66 
createInnerBlock()67 void RenderMenuList::createInnerBlock()
68 {
69     if (m_innerBlock) {
70         ASSERT(firstChild() == m_innerBlock);
71         ASSERT(!m_innerBlock->nextSibling());
72         return;
73     }
74 
75     // Create an anonymous block.
76     ASSERT(!firstChild());
77     m_innerBlock = createAnonymousBlock();
78     adjustInnerStyle();
79     RenderFlexibleBox::addChild(m_innerBlock);
80 }
81 
adjustInnerStyle()82 void RenderMenuList::adjustInnerStyle()
83 {
84     m_innerBlock->style()->setBoxFlex(1.0f);
85 
86     m_innerBlock->style()->setPaddingLeft(Length(theme()->popupInternalPaddingLeft(style()), Fixed));
87     m_innerBlock->style()->setPaddingRight(Length(theme()->popupInternalPaddingRight(style()), Fixed));
88     m_innerBlock->style()->setPaddingTop(Length(theme()->popupInternalPaddingTop(style()), Fixed));
89     m_innerBlock->style()->setPaddingBottom(Length(theme()->popupInternalPaddingBottom(style()), Fixed));
90 
91     if (PopupMenu::itemWritingDirectionIsNatural()) {
92         // Items in the popup will not respect the CSS text-align and direction properties,
93         // so we must adjust our own style to match.
94         m_innerBlock->style()->setTextAlign(LEFT);
95         TextDirection direction = (m_buttonText && m_buttonText->text()->defaultWritingDirection() == WTF::Unicode::RightToLeft) ? RTL : LTR;
96         m_innerBlock->style()->setDirection(direction);
97     }
98 }
99 
addChild(RenderObject * newChild,RenderObject * beforeChild)100 void RenderMenuList::addChild(RenderObject* newChild, RenderObject* beforeChild)
101 {
102     createInnerBlock();
103     m_innerBlock->addChild(newChild, beforeChild);
104 }
105 
removeChild(RenderObject * oldChild)106 void RenderMenuList::removeChild(RenderObject* oldChild)
107 {
108     if (oldChild == m_innerBlock || !m_innerBlock) {
109         RenderFlexibleBox::removeChild(oldChild);
110         m_innerBlock = 0;
111     } else
112         m_innerBlock->removeChild(oldChild);
113 }
114 
styleDidChange(StyleDifference diff,const RenderStyle * oldStyle)115 void RenderMenuList::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
116 {
117     RenderBlock::styleDidChange(diff, oldStyle);
118 
119     if (m_buttonText)
120         m_buttonText->setStyle(style());
121     if (m_innerBlock) // RenderBlock handled updating the anonymous block's style.
122         adjustInnerStyle();
123 
124     setReplaced(isInline());
125 
126     bool fontChanged = !oldStyle || oldStyle->font() != style()->font();
127     if (fontChanged)
128         updateOptionsWidth();
129 }
130 
updateOptionsWidth()131 void RenderMenuList::updateOptionsWidth()
132 {
133     float maxOptionWidth = 0;
134     const Vector<Element*>& listItems = toSelectElement(static_cast<Element*>(node()))->listItems();
135     int size = listItems.size();
136     for (int i = 0; i < size; ++i) {
137         Element* element = listItems[i];
138         OptionElement* optionElement = toOptionElement(element);
139         if (!optionElement)
140             continue;
141 
142         String text = optionElement->textIndentedToRespectGroupLabel();
143         if (theme()->popupOptionSupportsTextIndent()) {
144             // Add in the option's text indent.  We can't calculate percentage values for now.
145             float optionWidth = 0;
146             if (RenderStyle* optionStyle = element->renderStyle())
147                 optionWidth += optionStyle->textIndent().calcMinValue(0);
148             if (!text.isEmpty())
149                 optionWidth += style()->font().floatWidth(text);
150             maxOptionWidth = max(maxOptionWidth, optionWidth);
151         } else if (!text.isEmpty())
152             maxOptionWidth = max(maxOptionWidth, style()->font().floatWidth(text));
153     }
154 
155     int width = static_cast<int>(ceilf(maxOptionWidth));
156     if (m_optionsWidth == width)
157         return;
158 
159     m_optionsWidth = width;
160     if (parent())
161         setNeedsLayoutAndPrefWidthsRecalc();
162 }
163 
updateFromElement()164 void RenderMenuList::updateFromElement()
165 {
166     if (m_optionsChanged) {
167         updateOptionsWidth();
168         m_optionsChanged = false;
169     }
170 
171     if (m_popupIsVisible)
172         m_popup->updateFromElement();
173     else
174         setTextFromOption(toSelectElement(static_cast<Element*>(node()))->selectedIndex());
175 }
176 
setTextFromOption(int optionIndex)177 void RenderMenuList::setTextFromOption(int optionIndex)
178 {
179     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
180     const Vector<Element*>& listItems = select->listItems();
181     int size = listItems.size();
182 
183     int i = select->optionToListIndex(optionIndex);
184     String text = "";
185     if (i >= 0 && i < size) {
186         if (OptionElement* optionElement = toOptionElement(listItems[i]))
187             text = optionElement->textIndentedToRespectGroupLabel();
188     }
189 
190     setText(text.stripWhiteSpace());
191 }
192 
setText(const String & s)193 void RenderMenuList::setText(const String& s)
194 {
195     if (s.isEmpty()) {
196         if (!m_buttonText || !m_buttonText->isBR()) {
197             if (m_buttonText)
198                 m_buttonText->destroy();
199             m_buttonText = new (renderArena()) RenderBR(document());
200             m_buttonText->setStyle(style());
201             addChild(m_buttonText);
202         }
203     } else {
204         if (m_buttonText && !m_buttonText->isBR())
205             m_buttonText->setText(s.impl());
206         else {
207             if (m_buttonText)
208                 m_buttonText->destroy();
209             m_buttonText = new (renderArena()) RenderText(document(), s.impl());
210             m_buttonText->setStyle(style());
211             addChild(m_buttonText);
212         }
213         adjustInnerStyle();
214     }
215 }
216 
text() const217 String RenderMenuList::text() const
218 {
219     return m_buttonText ? m_buttonText->text() : 0;
220 }
221 
controlClipRect(int tx,int ty) const222 IntRect RenderMenuList::controlClipRect(int tx, int ty) const
223 {
224     // Clip to the intersection of the content box and the content box for the inner box
225     // This will leave room for the arrows which sit in the inner box padding,
226     // and if the inner box ever spills out of the outer box, that will get clipped too.
227     IntRect outerBox(tx + borderLeft() + paddingLeft(),
228                    ty + borderTop() + paddingTop(),
229                    contentWidth(),
230                    contentHeight());
231 
232     IntRect innerBox(tx + m_innerBlock->x() + m_innerBlock->paddingLeft(),
233                    ty + m_innerBlock->y() + m_innerBlock->paddingTop(),
234                    m_innerBlock->contentWidth(),
235                    m_innerBlock->contentHeight());
236 
237     return intersection(outerBox, innerBox);
238 }
239 
calcPrefWidths()240 void RenderMenuList::calcPrefWidths()
241 {
242     m_minPrefWidth = 0;
243     m_maxPrefWidth = 0;
244 
245     if (style()->width().isFixed() && style()->width().value() > 0)
246         m_minPrefWidth = m_maxPrefWidth = calcContentBoxWidth(style()->width().value());
247     else
248         m_maxPrefWidth = max(m_optionsWidth, theme()->minimumMenuListSize(style())) + m_innerBlock->paddingLeft() + m_innerBlock->paddingRight();
249 
250     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
251         m_maxPrefWidth = max(m_maxPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
252         m_minPrefWidth = max(m_minPrefWidth, calcContentBoxWidth(style()->minWidth().value()));
253     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
254         m_minPrefWidth = 0;
255     else
256         m_minPrefWidth = m_maxPrefWidth;
257 
258     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
259         m_maxPrefWidth = min(m_maxPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
260         m_minPrefWidth = min(m_minPrefWidth, calcContentBoxWidth(style()->maxWidth().value()));
261     }
262 
263     int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
264     m_minPrefWidth += toAdd;
265     m_maxPrefWidth += toAdd;
266 
267     setPrefWidthsDirty(false);
268 }
269 
showPopup()270 void RenderMenuList::showPopup()
271 {
272     if (m_popupIsVisible)
273         return;
274 
275     // Create m_innerBlock here so it ends up as the first child.
276     // This is important because otherwise we might try to create m_innerBlock
277     // inside the showPopup call and it would fail.
278     createInnerBlock();
279     if (!m_popup)
280         m_popup = PopupMenu::create(this);
281     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
282     m_popupIsVisible = true;
283 
284     // Compute the top left taking transforms into account, but use
285     // the actual width of the element to size the popup.
286     FloatPoint absTopLeft = localToAbsolute(FloatPoint(), false, true);
287     IntRect absBounds = absoluteBoundingBoxRect();
288     absBounds.setLocation(roundedIntPoint(absTopLeft));
289     m_popup->show(absBounds, document()->view(),
290         select->optionToListIndex(select->selectedIndex()));
291 }
292 
hidePopup()293 void RenderMenuList::hidePopup()
294 {
295     if (m_popup)
296         m_popup->hide();
297 }
298 
valueChanged(unsigned listIndex,bool fireOnChange)299 void RenderMenuList::valueChanged(unsigned listIndex, bool fireOnChange)
300 {
301     // Check to ensure a page navigation has not occurred while
302     // the popup was up.
303     Document* doc = static_cast<Element*>(node())->document();
304     if (!doc || doc != doc->frame()->document())
305         return;
306 
307     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
308     select->setSelectedIndexByUser(select->listToOptionIndex(listIndex), true, fireOnChange);
309 }
310 
didSetSelectedIndex()311 void RenderMenuList::didSetSelectedIndex()
312 {
313     int index = selectedIndex();
314     if (m_lastSelectedIndex == index)
315         return;
316 
317     m_lastSelectedIndex = index;
318 
319     if (AXObjectCache::accessibilityEnabled())
320         document()->axObjectCache()->postNotification(this, AXObjectCache::AXMenuListValueChanged, true, PostSynchronously);
321 }
322 
itemText(unsigned listIndex) const323 String RenderMenuList::itemText(unsigned listIndex) const
324 {
325     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
326     const Vector<Element*>& listItems = select->listItems();
327     if (listIndex >= listItems.size())
328         return String();
329     Element* element = listItems[listIndex];
330     if (OptionGroupElement* optionGroupElement = toOptionGroupElement(element))
331         return optionGroupElement->groupLabelText();
332     else if (OptionElement* optionElement = toOptionElement(element))
333         return optionElement->textIndentedToRespectGroupLabel();
334     return String();
335 }
336 
itemToolTip(unsigned listIndex) const337 String RenderMenuList::itemToolTip(unsigned listIndex) const
338 {
339     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
340     const Vector<Element*>& listItems = select->listItems();
341     if (listIndex >= listItems.size())
342         return String();
343     Element* element = listItems[listIndex];
344     return element->title();
345 }
346 
itemIsEnabled(unsigned listIndex) const347 bool RenderMenuList::itemIsEnabled(unsigned listIndex) const
348 {
349     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
350     const Vector<Element*>& listItems = select->listItems();
351     if (listIndex >= listItems.size())
352         return false;
353     Element* element = listItems[listIndex];
354     if (!isOptionElement(element))
355         return false;
356 
357     bool groupEnabled = true;
358     if (Element* parentElement = element->parentElement()) {
359         if (isOptionGroupElement(parentElement))
360             groupEnabled = parentElement->isEnabledFormControl();
361     }
362     if (!groupEnabled)
363         return false;
364 
365     return element->isEnabledFormControl();
366 }
367 
itemStyle(unsigned listIndex) const368 PopupMenuStyle RenderMenuList::itemStyle(unsigned listIndex) const
369 {
370     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
371     const Vector<Element*>& listItems = select->listItems();
372     if (listIndex >= listItems.size()) {
373         // If we are making an out of bounds access, then we want to use the style
374         // of a different option element (index 0). However, if there isn't an option element
375         // before at index 0, we fall back to the menu's style.
376         if (!listIndex)
377             return menuStyle();
378 
379         // Try to retrieve the style of an option element we know exists (index 0).
380         listIndex = 0;
381     }
382     Element* element = listItems[listIndex];
383 
384     RenderStyle* style = element->renderStyle() ? element->renderStyle() : element->computedStyle();
385     return style ? PopupMenuStyle(style->color(), itemBackgroundColor(listIndex), style->font(), style->visibility() == VISIBLE, style->textIndent(), style->direction()) : menuStyle();
386 }
387 
itemBackgroundColor(unsigned listIndex) const388 Color RenderMenuList::itemBackgroundColor(unsigned listIndex) const
389 {
390     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
391     const Vector<Element*>& listItems = select->listItems();
392     if (listIndex >= listItems.size())
393         return style()->backgroundColor();
394     Element* element = listItems[listIndex];
395 
396     Color backgroundColor;
397     if (element->renderStyle())
398         backgroundColor = element->renderStyle()->backgroundColor();
399     // If the item has an opaque background color, return that.
400     if (!backgroundColor.hasAlpha())
401         return backgroundColor;
402 
403     // Otherwise, the item's background is overlayed on top of the menu background.
404     backgroundColor = style()->backgroundColor().blend(backgroundColor);
405     if (!backgroundColor.hasAlpha())
406         return backgroundColor;
407 
408     // If the menu background is not opaque, then add an opaque white background behind.
409     return Color(Color::white).blend(backgroundColor);
410 }
411 
menuStyle() const412 PopupMenuStyle RenderMenuList::menuStyle() const
413 {
414     RenderStyle* s = m_innerBlock ? m_innerBlock->style() : style();
415     return PopupMenuStyle(s->color(), s->backgroundColor(), s->font(), s->visibility() == VISIBLE, s->textIndent(), s->direction());
416 }
417 
hostWindow() const418 HostWindow* RenderMenuList::hostWindow() const
419 {
420     return document()->view()->hostWindow();
421 }
422 
createScrollbar(ScrollbarClient * client,ScrollbarOrientation orientation,ScrollbarControlSize controlSize)423 PassRefPtr<Scrollbar> RenderMenuList::createScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, ScrollbarControlSize controlSize)
424 {
425     RefPtr<Scrollbar> widget;
426     bool hasCustomScrollbarStyle = style()->hasPseudoStyle(SCROLLBAR);
427     if (hasCustomScrollbarStyle)
428         widget = RenderScrollbar::createCustomScrollbar(client, orientation, this);
429     else
430         widget = Scrollbar::createNativeScrollbar(client, orientation, controlSize);
431     return widget.release();
432 }
433 
clientInsetLeft() const434 int RenderMenuList::clientInsetLeft() const
435 {
436     return 0;
437 }
438 
clientInsetRight() const439 int RenderMenuList::clientInsetRight() const
440 {
441     return 0;
442 }
443 
clientPaddingLeft() const444 int RenderMenuList::clientPaddingLeft() const
445 {
446     return paddingLeft();
447 }
448 
449 const int endOfLinePadding = 2;
clientPaddingRight() const450 int RenderMenuList::clientPaddingRight() const
451 {
452     if (style()->appearance() == MenulistPart || style()->appearance() == MenulistButtonPart) {
453         // For these appearance values, the theme applies padding to leave room for the
454         // drop-down button. But leaving room for the button inside the popup menu itself
455         // looks strange, so we return a small default padding to avoid having a large empty
456         // space appear on the side of the popup menu.
457         return endOfLinePadding;
458     }
459 
460     // If the appearance isn't MenulistPart, then the select is styled (non-native), so
461     // we want to return the user specified padding.
462     return paddingRight();
463 }
464 
listSize() const465 int RenderMenuList::listSize() const
466 {
467     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
468     return select->listItems().size();
469 }
470 
selectedIndex() const471 int RenderMenuList::selectedIndex() const
472 {
473     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
474     return select->optionToListIndex(select->selectedIndex());
475 }
476 
popupDidHide()477 void RenderMenuList::popupDidHide()
478 {
479     m_popupIsVisible = false;
480 }
481 
itemIsSeparator(unsigned listIndex) const482 bool RenderMenuList::itemIsSeparator(unsigned listIndex) const
483 {
484     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
485     const Vector<Element*>& listItems = select->listItems();
486     if (listIndex >= listItems.size())
487         return false;
488     Element* element = listItems[listIndex];
489     return element->hasTagName(hrTag);
490 }
491 
itemIsLabel(unsigned listIndex) const492 bool RenderMenuList::itemIsLabel(unsigned listIndex) const
493 {
494     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
495     const Vector<Element*>& listItems = select->listItems();
496     if (listIndex >= listItems.size())
497         return false;
498     Element* element = listItems[listIndex];
499     return isOptionGroupElement(element);
500 }
501 
itemIsSelected(unsigned listIndex) const502 bool RenderMenuList::itemIsSelected(unsigned listIndex) const
503 {
504     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
505     const Vector<Element*>& listItems = select->listItems();
506     if (listIndex >= listItems.size())
507         return false;
508     Element* element = listItems[listIndex];
509     if (OptionElement* optionElement = toOptionElement(element))
510         return optionElement->selected();
511     return false;
512 }
513 
setTextFromItem(unsigned listIndex)514 void RenderMenuList::setTextFromItem(unsigned listIndex)
515 {
516     SelectElement* select = toSelectElement(static_cast<Element*>(node()));
517     setTextFromOption(select->listToOptionIndex(listIndex));
518 }
519 
fontSelector() const520 FontSelector* RenderMenuList::fontSelector() const
521 {
522     return document()->styleSelector()->fontSelector();
523 }
524 
525 }
526