86fafa72354897abe21285f46209e283873ff20a
[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 void RenderMenuList::paintObject(PaintInfo& i, int x, int y)
142 {
143     // Push a clip.
144     if (i.phase == PaintPhaseForeground) {
145         IntRect clipRect(x + borderLeft() + paddingLeft(), y + borderTop() + paddingTop(),
146             width() - borderLeft() - borderRight() - paddingLeft() - paddingRight(),
147             height() - borderBottom() - borderTop() - paddingTop() - paddingBottom());
148         if (clipRect.isEmpty())
149             return;
150         i.p->save();
151         i.p->addClip(clipRect);
152     }
153
154     // Paint the children.
155     RenderBlock::paintObject(i, x, y);
156
157     // Pop the clip.
158     if (i.phase == PaintPhaseForeground)
159         i.p->restore();
160 }
161
162 void RenderMenuList::calcMinMaxWidth()
163 {
164     if (m_optionsChanged)
165         updateFromElement();
166
167     m_minWidth = 0;
168     m_maxWidth = 0;
169
170     if (style()->width().isFixed() && style()->width().value() > 0)
171         m_minWidth = m_maxWidth = calcContentBoxWidth(style()->width().value());
172     else
173         m_maxWidth = max(m_optionsWidth, theme()->minimumMenuListSize(style()));
174
175     if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) {
176         m_maxWidth = max(m_maxWidth, calcContentBoxWidth(style()->minWidth().value()));
177         m_minWidth = max(m_minWidth, calcContentBoxWidth(style()->minWidth().value()));
178     } else if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent()))
179         m_minWidth = 0;
180     else
181         m_minWidth = m_maxWidth;
182
183     if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) {
184         m_maxWidth = min(m_maxWidth, calcContentBoxWidth(style()->maxWidth().value()));
185         m_minWidth = min(m_minWidth, calcContentBoxWidth(style()->maxWidth().value()));
186     }
187
188     int toAdd = paddingLeft() + paddingRight() + borderLeft() + borderRight();
189     m_minWidth += toAdd;
190     m_maxWidth += toAdd;
191
192     setMinMaxKnown();
193 }
194
195 void RenderMenuList::showPopup()
196 {
197     // Create m_innerBlock here so it ends up as the first child.
198     // This is important because otherwise we might try to create m_innerBlock
199     // inside the showPopup call and it would fail.
200     createInnerBlock();
201     RenderPopupMenu* menu = theme()->createPopupMenu(renderArena(), document(), this);
202     RenderStyle* newStyle = new (renderArena()) RenderStyle;
203     newStyle->inheritFrom(style());
204     menu->setStyle(newStyle);
205     HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
206     menu->showPopup(absoluteBoundingBoxRect(), document()->view(),
207         select->optionToListIndex(select->selectedIndex()));
208     menu->destroy();
209 }
210
211 void RenderMenuList::valueChanged(unsigned listIndex)
212 {
213     HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
214     select->setSelectedIndex(select->listToOptionIndex(listIndex));
215     select->onChange();
216 }
217
218 }