Home | History | Annotate | Download | only in forms
      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