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