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