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