• 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 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