Reviewed by Adele.
[WebKit-https.git] / WebCore / rendering / RenderMenuList.cpp
1 /**
2  * This file is part of the select element renderer in WebCore.
3  *
4  * Copyright (C) 2006 Apple Computer, Inc.
5  *
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.
10  *
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.
15  *
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.
20  *
21  */
22
23 #include "config.h"
24 #include "RenderMenuList.h"
25
26 #include "Document.h"
27 #include "FrameView.h"
28 #include "GraphicsContext.h"
29 #include "HTMLNames.h"
30 #include "HTMLOptionElement.h"
31 #include "HTMLSelectElement.h"
32 #include "PopupMenu.h"
33 #include "RenderText.h"
34 #include "RenderTheme.h"
35 #include "TextStyle.h"
36 #include <math.h>
37
38 using namespace std;
39
40 namespace WebCore {
41
42 using namespace HTMLNames;
43
44 RenderMenuList::RenderMenuList(HTMLSelectElement* element)
45     : RenderFlexibleBox(element)
46     , m_buttonText(0)
47     , m_innerBlock(0)
48     , m_optionsChanged(true)
49     , m_optionsWidth(0)
50     , m_popup(0)
51     , m_popupIsVisible(false)
52 {
53 }
54
55 RenderMenuList::~RenderMenuList()
56 {
57     m_popup = 0;
58 }
59
60 void RenderMenuList::createInnerBlock()
61 {
62     if (m_innerBlock) {
63         ASSERT(firstChild() == m_innerBlock);
64         ASSERT(!m_innerBlock->nextSibling());
65         return;
66     }
67
68     // Create an anonymous block.
69     ASSERT(!firstChild());
70     m_innerBlock = createAnonymousBlock();
71     m_innerBlock->style()->setBoxFlex(1.0f);
72     RenderFlexibleBox::addChild(m_innerBlock);
73 }
74
75 void RenderMenuList::addChild(RenderObject* newChild, RenderObject* beforeChild)
76 {
77     createInnerBlock();
78     m_innerBlock->addChild(newChild, beforeChild);
79 }
80
81 void RenderMenuList::removeChild(RenderObject* oldChild)
82 {
83     if (oldChild == m_innerBlock || !m_innerBlock) {
84         RenderFlexibleBox::removeChild(oldChild);
85         m_innerBlock = 0;
86     } else
87         m_innerBlock->removeChild(oldChild);
88 }
89
90 void RenderMenuList::setStyle(RenderStyle* style)
91 {
92     RenderBlock::setStyle(style);
93     if (m_buttonText)
94         m_buttonText->setStyle(style);
95     if (m_innerBlock)
96         m_innerBlock->style()->setBoxFlex(1.0f);
97     setReplaced(isInline());
98 }
99
100 void RenderMenuList::updateFromElement()
101 {
102     HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
103     const Vector<HTMLElement*>& listItems = select->listItems();
104     int size = listItems.size();
105
106     if (m_optionsChanged) {        
107         float width = 0;
108         TextStyle textStyle(0, 0, 0, false, false, false, false);
109         for (int i = 0; i < size; ++i) {
110             HTMLElement* element = listItems[i];
111             if (element->hasTagName(optionTag)) {
112                 String text = static_cast<HTMLOptionElement*>(element)->optionText();
113                 if (!text.isEmpty())
114                     width = max(width, style()->font().floatWidth(TextRun(text.impl()), textStyle));
115             }
116         }
117         m_optionsWidth = static_cast<int>(ceilf(width));
118         m_optionsChanged = false;
119     }
120
121     setTextFromOption(select->selectedIndex());
122 }
123
124 void RenderMenuList::setTextFromOption(int optionIndex)
125 {
126     HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
127     const Vector<HTMLElement*>& listItems = select->listItems();
128     int size = listItems.size();
129
130     int i = select->optionToListIndex(optionIndex);
131     String text = "";
132     if (i >= 0 && i < size) {
133         HTMLElement* element = listItems[i];
134         if (element->hasTagName(optionTag))
135             text = static_cast<HTMLOptionElement*>(listItems[i])->optionText();
136     }
137     setText(text);
138 }
139
140 void RenderMenuList::setText(const String& s)
141 {
142     if (s.isEmpty()) {
143         if (m_buttonText) {
144             m_buttonText->destroy();
145             m_buttonText = 0;
146         }
147     } else {
148         if (m_buttonText)
149             m_buttonText->setText(s.impl());
150         else {
151             m_buttonText = new (renderArena()) RenderText(document(), s.impl());
152             m_buttonText->setStyle(style());
153             addChild(m_buttonText);
154         }
155     }
156 }
157
158
159 String RenderMenuList::text()
160 {
161     return m_buttonText ? m_buttonText->data() : String();
162 }
163
164 void RenderMenuList::paintObject(PaintInfo& i, int x, int y)
165 {
166     // Push a clip.
167     if (i.phase == PaintPhaseForeground) {
168         IntRect clipRect(x + borderLeft() + paddingLeft(), y + borderTop() + paddingTop(),
169             width() - borderLeft() - borderRight() - paddingLeft() - paddingRight(),
170             height() - borderBottom() - borderTop() - paddingTop() - paddingBottom());
171         if (clipRect.isEmpty())
172             return;
173         i.p->save();
174         i.p->clip(clipRect);
175     }
176
177     // Paint the children.
178     RenderBlock::paintObject(i, x, y);
179
180     // Pop the clip.
181     if (i.phase == PaintPhaseForeground)
182         i.p->restore();
183 }
184
185 void RenderMenuList::calcMinMaxWidth()
186 {
187     if (m_optionsChanged)
188         updateFromElement();
189
190     m_minWidth = 0;
191     m_maxWidth = 0;
192
193     if (style()->width().isFixed() && style()->width().value() > 0)
194         m_minWidth = m_maxWidth = calcContentBoxWidth(style()->width().value());
195     else
196         m_maxWidth = max(m_optionsWidth, theme()->minimumMenuListSize(style()));
197
198     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
199         m_maxWidth = max(m_maxWidth, calcContentBoxWidth(style()->minWidth().value()));
200         m_minWidth = max(m_minWidth, calcContentBoxWidth(style()->minWidth().value()));
201     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
202         m_minWidth = 0;
203     else
204         m_minWidth = m_maxWidth;
205
206     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
207         m_maxWidth = min(m_maxWidth, calcContentBoxWidth(style()->maxWidth().value()));
208         m_minWidth = min(m_minWidth, calcContentBoxWidth(style()->maxWidth().value()));
209     }
210
211     int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
212     m_minWidth += toAdd;
213     m_maxWidth += toAdd;
214
215     setMinMaxKnown();
216 }
217
218 void RenderMenuList::showPopup()
219 {
220     if (m_popupIsVisible)
221         return;
222
223     // Create m_innerBlock here so it ends up as the first child.
224     // This is important because otherwise we might try to create m_innerBlock
225     // inside the showPopup call and it would fail.
226     createInnerBlock();
227     if (!m_popup)
228         m_popup = PopupMenu::create(this);
229     HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
230     m_popupIsVisible = true;
231     m_popup->show(absoluteBoundingBoxRect(), document()->view(),
232         select->optionToListIndex(select->selectedIndex()));
233 }
234
235 void RenderMenuList::hidePopup()
236 {
237     if (m_popup)
238         m_popup->hide();
239     m_popupIsVisible = false;
240 }
241
242 void RenderMenuList::valueChanged(unsigned listIndex, bool fireOnChange)
243 {
244     HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
245     select->setSelectedIndex(select->listToOptionIndex(listIndex), true, fireOnChange);
246 }
247
248 }