OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2007, 2008, 2009 Apple Inc. All rights reserved. | 2 * Copyright (C) 2007, 2008, 2009 Apple Inc. All rights reserved. |
3 * | 3 * |
4 * This library is free software; you can redistribute it and/or | 4 * This library is free software; you can redistribute it and/or |
5 * modify it under the terms of the GNU Library General Public | 5 * modify it under the terms of the GNU Library General Public |
6 * License as published by the Free Software Foundation; either | 6 * License as published by the Free Software Foundation; either |
7 * version 2 of the License, or (at your option) any later version. | 7 * version 2 of the License, or (at your option) any later version. |
8 * | 8 * |
9 * This library is distributed in the hope that it will be useful, | 9 * This library is distributed in the hope that it will be useful, |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 * Library General Public License for more details. | 12 * Library General Public License for more details. |
13 * | 13 * |
14 * You should have received a copy of the GNU Library General Public License | 14 * You should have received a copy of the GNU Library General Public License |
15 * along with this library; see the file COPYING.LIB. If not, write to | 15 * along with this library; see the file COPYING.LIB. If not, write to |
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | 16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
17 * Boston, MA 02110-1301, USA. | 17 * Boston, MA 02110-1301, USA. |
18 * | 18 * |
19 */ | 19 */ |
20 | 20 |
21 #include "config.h" | 21 #include "config.h" |
22 #include "CheckedRadioButtons.h" | 22 #include "CheckedRadioButtons.h" |
23 | 23 |
24 #include "HTMLInputElement.h" | 24 #include "HTMLInputElement.h" |
25 #include <wtf/HashSet.h> | |
26 | 25 |
27 namespace WebCore { | 26 namespace WebCore { |
28 | 27 |
29 class RadioButtonGroup { | |
30 public: | |
31 static PassOwnPtr<RadioButtonGroup> create(); | |
32 bool isEmpty() const { return m_members.isEmpty(); } | |
33 bool isRequired() const { return m_requiredCount; } | |
34 HTMLInputElement* checkedButton() const { return m_checkedButton; } | |
35 void add(HTMLInputElement*); | |
36 void updateCheckedState(HTMLInputElement*); | |
37 void requiredAttributeChanged(HTMLInputElement*); | |
38 void remove(HTMLInputElement*); | |
39 | |
40 private: | |
41 RadioButtonGroup(); | |
42 void setNeedsValidityCheckForAllButtons(); | |
43 bool isValid() const; | |
44 void setCheckedButton(HTMLInputElement*); | |
45 | |
46 HashSet<HTMLInputElement*> m_members; | |
47 HTMLInputElement* m_checkedButton; | |
48 size_t m_requiredCount; | |
49 }; | |
50 | |
51 RadioButtonGroup::RadioButtonGroup() | |
52 : m_checkedButton(0) | |
53 , m_requiredCount(0) | |
54 { | |
55 } | |
56 | |
57 PassOwnPtr<RadioButtonGroup> RadioButtonGroup::create() | |
58 { | |
59 return adoptPtr(new RadioButtonGroup); | |
60 } | |
61 | |
62 inline bool RadioButtonGroup::isValid() const | |
63 { | |
64 return !isRequired() || m_checkedButton; | |
65 } | |
66 | |
67 void RadioButtonGroup::setCheckedButton(HTMLInputElement* button) | |
68 { | |
69 HTMLInputElement* oldCheckedButton = m_checkedButton; | |
70 if (oldCheckedButton == button) | |
71 return; | |
72 m_checkedButton = button; | |
73 if (oldCheckedButton) | |
74 oldCheckedButton->setChecked(false); | |
75 } | |
76 | |
77 void RadioButtonGroup::add(HTMLInputElement* button) | |
78 { | |
79 ASSERT(button->isRadioButton()); | |
80 if (!m_members.add(button).second) | |
81 return; | |
82 bool groupWasValid = isValid(); | |
83 if (button->required()) | |
84 ++m_requiredCount; | |
85 if (button->checked()) | |
86 setCheckedButton(button); | |
87 | |
88 bool groupIsValid = isValid(); | |
89 if (groupWasValid != groupIsValid) | |
90 setNeedsValidityCheckForAllButtons(); | |
91 else if (!groupIsValid) { | |
92 // A radio button not in a group is always valid. We need to make it | |
93 // invalid only if the group is invalid. | |
94 button->setNeedsValidityCheck(); | |
95 } | |
96 } | |
97 | |
98 void RadioButtonGroup::updateCheckedState(HTMLInputElement* button) | |
99 { | |
100 ASSERT(button->isRadioButton()); | |
101 ASSERT(m_members.contains(button)); | |
102 bool wasValid = isValid(); | |
103 if (button->checked()) | |
104 setCheckedButton(button); | |
105 else { | |
106 if (m_checkedButton == button) | |
107 m_checkedButton = 0; | |
108 } | |
109 if (wasValid != isValid()) | |
110 setNeedsValidityCheckForAllButtons(); | |
111 } | |
112 | |
113 void RadioButtonGroup::requiredAttributeChanged(HTMLInputElement* button) | |
114 { | |
115 ASSERT(button->isRadioButton()); | |
116 ASSERT(m_members.contains(button)); | |
117 bool wasValid = isValid(); | |
118 if (button->required()) | |
119 ++m_requiredCount; | |
120 else { | |
121 ASSERT(m_requiredCount); | |
122 --m_requiredCount; | |
123 } | |
124 if (wasValid != isValid()) | |
125 setNeedsValidityCheckForAllButtons(); | |
126 } | |
127 | |
128 void RadioButtonGroup::remove(HTMLInputElement* button) | |
129 { | |
130 ASSERT(button->isRadioButton()); | |
131 HashSet<HTMLInputElement*>::iterator it = m_members.find(button); | |
132 if (it == m_members.end()) | |
133 return; | |
134 bool wasValid = isValid(); | |
135 m_members.remove(it); | |
136 if (button->required()) { | |
137 ASSERT(m_requiredCount); | |
138 --m_requiredCount; | |
139 } | |
140 if (m_checkedButton == button) | |
141 m_checkedButton = 0; | |
142 | |
143 if (m_members.isEmpty()) { | |
144 ASSERT(!m_requiredCount); | |
145 ASSERT(!m_checkedButton); | |
146 } else if (wasValid != isValid()) | |
147 setNeedsValidityCheckForAllButtons(); | |
148 if (!wasValid) { | |
149 // A radio button not in a group is always valid. We need to make it | |
150 // valid only if the group was invalid. | |
151 button->setNeedsValidityCheck(); | |
152 } | |
153 } | |
154 | |
155 void RadioButtonGroup::setNeedsValidityCheckForAllButtons() | |
156 { | |
157 typedef HashSet<HTMLInputElement*>::const_iterator Iterator; | |
158 Iterator end = m_members.end(); | |
159 for (Iterator it = m_members.begin(); it != end; ++it) { | |
160 HTMLInputElement* button = *it; | |
161 ASSERT(button->isRadioButton()); | |
162 button->setNeedsValidityCheck(); | |
163 } | |
164 } | |
165 | |
166 // ---------------------------------------------------------------- | |
167 | |
168 static inline bool shouldMakeRadioGroup(HTMLInputElement* element) | 28 static inline bool shouldMakeRadioGroup(HTMLInputElement* element) |
169 { | 29 { |
170 return element->isRadioButton() && !element->name().isEmpty() && element->in
Document(); | 30 return element->isRadioButton() && !element->name().isEmpty() && element->in
Document(); |
171 } | 31 } |
172 | 32 |
173 // Explicity define empty constructor and destructor in order to prevent the | |
174 // compiler from generating them as inlines. So we don't need to to define | |
175 // RadioButtonGroup in the header. | |
176 CheckedRadioButtons::CheckedRadioButtons() | |
177 { | |
178 } | |
179 | |
180 CheckedRadioButtons::~CheckedRadioButtons() | |
181 { | |
182 } | |
183 | |
184 void CheckedRadioButtons::addButton(HTMLInputElement* element) | 33 void CheckedRadioButtons::addButton(HTMLInputElement* element) |
185 { | 34 { |
186 if (!shouldMakeRadioGroup(element)) | 35 if (!shouldMakeRadioGroup(element)) |
187 return; | 36 return; |
188 | 37 |
189 if (!m_nameToGroupMap) | 38 // We only track checked buttons. |
190 m_nameToGroupMap = adoptPtr(new NameToGroupMap); | 39 if (!element->checked()) |
| 40 return; |
191 | 41 |
192 OwnPtr<RadioButtonGroup>& group = m_nameToGroupMap->add(element->name().impl
(), PassOwnPtr<RadioButtonGroup>()).first->second; | 42 if (!m_nameToCheckedRadioButtonMap) |
193 if (!group) | 43 m_nameToCheckedRadioButtonMap = adoptPtr(new NameToInputMap); |
194 group = RadioButtonGroup::create(); | |
195 group->add(element); | |
196 } | |
197 | 44 |
198 void CheckedRadioButtons::updateCheckedState(HTMLInputElement* element) | 45 pair<NameToInputMap::iterator, bool> result = m_nameToCheckedRadioButtonMap-
>add(element->name().impl(), element); |
199 { | 46 if (result.second) |
200 if (!shouldMakeRadioGroup(element)) | |
201 return; | 47 return; |
202 ASSERT(m_nameToGroupMap); | 48 |
203 if (!m_nameToGroupMap) | 49 HTMLInputElement* oldCheckedButton = result.first->second; |
| 50 if (oldCheckedButton == element) |
204 return; | 51 return; |
205 RadioButtonGroup* group = m_nameToGroupMap->get(element->name().impl()); | |
206 ASSERT(group); | |
207 group->updateCheckedState(element); | |
208 } | |
209 | 52 |
210 void CheckedRadioButtons::requiredAttributeChanged(HTMLInputElement* element) | 53 result.first->second = element; |
211 { | 54 oldCheckedButton->setChecked(false); |
212 if (!shouldMakeRadioGroup(element)) | |
213 return; | |
214 ASSERT(m_nameToGroupMap); | |
215 if (!m_nameToGroupMap) | |
216 return; | |
217 RadioButtonGroup* group = m_nameToGroupMap->get(element->name().impl()); | |
218 ASSERT(group); | |
219 group->requiredAttributeChanged(element); | |
220 } | 55 } |
221 | 56 |
222 HTMLInputElement* CheckedRadioButtons::checkedButtonForGroup(const AtomicString&
name) const | 57 HTMLInputElement* CheckedRadioButtons::checkedButtonForGroup(const AtomicString&
name) const |
223 { | 58 { |
224 if (!m_nameToGroupMap) | 59 if (!m_nameToCheckedRadioButtonMap) |
225 return 0; | 60 return 0; |
226 m_nameToGroupMap->checkConsistency(); | |
227 RadioButtonGroup* group = m_nameToGroupMap->get(name.impl()); | |
228 return group ? group->checkedButton() : 0; | |
229 } | |
230 | 61 |
231 bool CheckedRadioButtons::isInRequiredGroup(HTMLInputElement* element) const | 62 m_nameToCheckedRadioButtonMap->checkConsistency(); |
232 { | 63 |
233 ASSERT(element->isRadioButton()); | 64 return m_nameToCheckedRadioButtonMap->get(name.impl()); |
234 if (!element->inDocument()) | |
235 return false; | |
236 if (!m_nameToGroupMap) | |
237 return false; | |
238 | |
239 RadioButtonGroup* group = m_nameToGroupMap->get(element->name().impl()); | |
240 return group && group->isRequired(); | |
241 } | 65 } |
242 | 66 |
243 void CheckedRadioButtons::removeButton(HTMLInputElement* element) | 67 void CheckedRadioButtons::removeButton(HTMLInputElement* element) |
244 { | 68 { |
245 if (!shouldMakeRadioGroup(element)) | 69 if (element->name().isEmpty() || !m_nameToCheckedRadioButtonMap) |
246 return; | 70 return; |
247 if (!m_nameToGroupMap) | 71 |
| 72 m_nameToCheckedRadioButtonMap->checkConsistency(); |
| 73 |
| 74 NameToInputMap::iterator it = m_nameToCheckedRadioButtonMap->find(element->n
ame().impl()); |
| 75 if (it == m_nameToCheckedRadioButtonMap->end() || it->second != element) |
248 return; | 76 return; |
| 77 |
| 78 ASSERT(element->shouldAppearChecked()); |
| 79 ASSERT(element->isRadioButton()); |
249 | 80 |
250 m_nameToGroupMap->checkConsistency(); | 81 m_nameToCheckedRadioButtonMap->remove(it); |
251 NameToGroupMap::iterator it = m_nameToGroupMap->find(element->name().impl())
; | 82 if (m_nameToCheckedRadioButtonMap->isEmpty()) |
252 if (it == m_nameToGroupMap->end()) | 83 m_nameToCheckedRadioButtonMap.clear(); |
253 return; | |
254 it->second->remove(element); | |
255 if (it->second->isEmpty()) { | |
256 // FIXME: We may skip deallocating the empty RadioButtonGroup for | |
257 // performance improvement. If we do so, we need to change the key type | |
258 // of m_nameToGroupMap from AtomicStringImpl* to RefPtr<AtomicStringImpl
>. | |
259 m_nameToGroupMap->remove(it); | |
260 if (m_nameToGroupMap->isEmpty()) | |
261 m_nameToGroupMap.clear(); | |
262 } | |
263 } | 84 } |
264 | 85 |
265 } // namespace | 86 } // namespace |
OLD | NEW |