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 "CheckedRadioButtons.h" 23 24#include "HTMLInputElement.h" 25#include <wtf/HashSet.h> 26 27namespace WebCore { 28 29class RadioButtonGroup { 30 WTF_MAKE_FAST_ALLOCATED; 31public: 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 42private: 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 53RadioButtonGroup::RadioButtonGroup() 54 : m_checkedButton(0) 55 , m_requiredCount(0) 56{ 57} 58 59PassOwnPtr<RadioButtonGroup> RadioButtonGroup::create() 60{ 61 return adoptPtr(new RadioButtonGroup); 62} 63 64inline bool RadioButtonGroup::isValid() const 65{ 66 return !isRequired() || m_checkedButton; 67} 68 69void 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 79void 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 100void 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 115void 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 130void 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 if (!wasValid) { 151 // A radio button not in a group is always valid. We need to make it 152 // valid only if the group was invalid. 153 button->setNeedsValidityCheck(); 154 } 155} 156 157void RadioButtonGroup::setNeedsValidityCheckForAllButtons() 158{ 159 typedef HashSet<HTMLInputElement*>::const_iterator Iterator; 160 Iterator end = m_members.end(); 161 for (Iterator it = m_members.begin(); it != end; ++it) { 162 HTMLInputElement* button = *it; 163 ASSERT(button->isRadioButton()); 164 button->setNeedsValidityCheck(); 165 } 166} 167 168bool RadioButtonGroup::contains(HTMLInputElement* button) const 169{ 170 return m_members.contains(button); 171} 172 173// ---------------------------------------------------------------- 174 175// Explicity define empty constructor and destructor in order to prevent the 176// compiler from generating them as inlines. So we don't need to to define 177// RadioButtonGroup in the header. 178CheckedRadioButtons::CheckedRadioButtons() 179{ 180} 181 182CheckedRadioButtons::~CheckedRadioButtons() 183{ 184} 185 186void CheckedRadioButtons::addButton(HTMLInputElement* element) 187{ 188 ASSERT(element->isRadioButton()); 189 if (element->name().isEmpty()) 190 return; 191 192 if (!m_nameToGroupMap) 193 m_nameToGroupMap = adoptPtr(new NameToGroupMap); 194 195 OwnPtr<RadioButtonGroup>& group = m_nameToGroupMap->add(element->name().impl(), PassOwnPtr<RadioButtonGroup>()).iterator->value; 196 if (!group) 197 group = RadioButtonGroup::create(); 198 group->add(element); 199} 200 201void CheckedRadioButtons::updateCheckedState(HTMLInputElement* element) 202{ 203 ASSERT(element->isRadioButton()); 204 if (element->name().isEmpty()) 205 return; 206 ASSERT(m_nameToGroupMap); 207 if (!m_nameToGroupMap) 208 return; 209 RadioButtonGroup* group = m_nameToGroupMap->get(element->name().impl()); 210 ASSERT(group); 211 group->updateCheckedState(element); 212} 213 214void CheckedRadioButtons::requiredAttributeChanged(HTMLInputElement* element) 215{ 216 ASSERT(element->isRadioButton()); 217 if (element->name().isEmpty()) 218 return; 219 ASSERT(m_nameToGroupMap); 220 if (!m_nameToGroupMap) 221 return; 222 RadioButtonGroup* group = m_nameToGroupMap->get(element->name().impl()); 223 ASSERT(group); 224 group->requiredAttributeChanged(element); 225} 226 227HTMLInputElement* CheckedRadioButtons::checkedButtonForGroup(const AtomicString& name) const 228{ 229 if (!m_nameToGroupMap) 230 return 0; 231 m_nameToGroupMap->checkConsistency(); 232 RadioButtonGroup* group = m_nameToGroupMap->get(name.impl()); 233 return group ? group->checkedButton() : 0; 234} 235 236bool 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 247void CheckedRadioButtons::removeButton(HTMLInputElement* element) 248{ 249 ASSERT(element->isRadioButton()); 250 if (element->name().isEmpty()) 251 return; 252 if (!m_nameToGroupMap) 253 return; 254 255 m_nameToGroupMap->checkConsistency(); 256 NameToGroupMap::iterator it = m_nameToGroupMap->find(element->name().impl()); 257 if (it == m_nameToGroupMap->end()) 258 return; 259 it->value->remove(element); 260 if (it->value->isEmpty()) { 261 // FIXME: We may skip deallocating the empty RadioButtonGroup for 262 // performance improvement. If we do so, we need to change the key type 263 // of m_nameToGroupMap from AtomicStringImpl* to RefPtr<AtomicStringImpl>. 264 m_nameToGroupMap->remove(it); 265 if (m_nameToGroupMap->isEmpty()) 266 m_nameToGroupMap.clear(); 267 } 268} 269 270} // namespace 271