2 * This file is part of the DOM implementation for KDE.
4 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
5 * (C) 1999 Antti Koivisto (koivisto@kde.org)
6 * (C) 2001 Dirk Mueller (mueller@kde.org)
7 * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
8 * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
20 * You should have received a copy of the GNU Library General Public License
21 * along with this library; see the file COPYING.LIB. If not, write to
22 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 * Boston, MA 02111-1307, USA.
28 #include "HTMLSelectElement.h"
30 #include "CSSPropertyNames.h"
33 #include "EventNames.h"
34 #include "FormDataList.h"
35 #include "HTMLFormElement.h"
36 #include "HTMLNames.h"
37 #include "HTMLOptionElement.h"
38 #include "HTMLOptionsCollection.h"
39 #include "KeyboardEvent.h"
40 #include "DeprecatedRenderSelect.h"
41 #include "cssstyleselector.h"
42 #include <wtf/Vector.h>
46 using namespace EventNames;
47 using namespace HTMLNames;
49 HTMLSelectElement::HTMLSelectElement(Document* doc, HTMLFormElement* f)
50 : HTMLGenericFormElement(selectTag, doc, f)
54 , m_recalcListItems(false)
56 document()->registerFormElementWithState(this);
59 HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* f)
60 : HTMLGenericFormElement(tagName, doc, f), m_minwidth(0), m_size(0), m_multiple(false), m_recalcListItems(false)
62 document()->registerFormElementWithState(this);
65 HTMLSelectElement::~HTMLSelectElement()
67 document()->deregisterFormElementWithState(this);
70 bool HTMLSelectElement::checkDTD(const Node* newChild)
72 return newChild->isTextNode() || newChild->hasTagName(optionTag) || newChild->hasTagName(optgroupTag) || newChild->hasTagName(hrTag) ||
73 newChild->hasTagName(scriptTag);
76 void HTMLSelectElement::recalcStyle( StyleChange ch )
78 if (hasChangedChild() && renderer())
79 static_cast<DeprecatedRenderSelect*>(renderer())->setOptionsChanged(true);
81 HTMLGenericFormElement::recalcStyle( ch );
85 const AtomicString& HTMLSelectElement::type() const
87 static const AtomicString selectMultiple("select-multiple");
88 static const AtomicString selectOne("select-one");
89 return m_multiple ? selectMultiple : selectOne;
92 int HTMLSelectElement::selectedIndex() const
94 // return the number of the first option selected
96 Vector<HTMLElement*> items = listItems();
97 for (unsigned int i = 0; i < items.size(); i++) {
98 if (items[i]->hasLocalName(optionTag)) {
99 if (static_cast<HTMLOptionElement*>(items[i])->selected())
107 void HTMLSelectElement::setSelectedIndex( int index, bool deselect )
109 // deselect all other options and select only the new one
110 Vector<HTMLElement*> items = listItems();
113 for (listIndex = 0; listIndex < int(items.size()); listIndex++) {
114 if (items[listIndex]->hasLocalName(optionTag))
115 static_cast<HTMLOptionElement*>(items[listIndex])->setSelected(false);
118 listIndex = optionToListIndex(index);
120 static_cast<HTMLOptionElement*>(items[listIndex])->setSelected(true);
125 int HTMLSelectElement::length() const
129 Vector<HTMLElement*> items = listItems();
130 for (i = 0; i < items.size(); i++) {
131 if (items[i]->hasLocalName(optionTag))
137 void HTMLSelectElement::add( HTMLElement *element, HTMLElement *before, ExceptionCode& ec)
139 RefPtr<HTMLElement> protectNewChild(element); // make sure the element is ref'd and deref'd so we don't leak it
141 if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
144 insertBefore(element, before, ec);
146 setRecalcListItems();
149 void HTMLSelectElement::remove(int index)
151 ExceptionCode ec = 0;
152 int listIndex = optionToListIndex(index);
154 Vector<HTMLElement*> items = listItems();
155 if (listIndex < 0 || index >= int(items.size()))
156 return; // ### what should we do ? remove the last item?
158 removeChild(items[listIndex], ec);
160 setRecalcListItems();
163 String HTMLSelectElement::value()
166 Vector<HTMLElement*> items = listItems();
167 for (i = 0; i < items.size(); i++) {
168 if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected())
169 return static_cast<HTMLOptionElement*>(items[i])->value();
174 void HTMLSelectElement::setValue(const String &value)
178 // find the option with value() matching the given parameter
179 // and make it the current selection.
180 Vector<HTMLElement*> items = listItems();
181 for (unsigned i = 0; i < items.size(); i++)
182 if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->value() == value) {
183 static_cast<HTMLOptionElement*>(items[i])->setSelected(true);
188 String HTMLSelectElement::stateValue() const
190 Vector<HTMLElement*> items = listItems();
191 int l = items.size();
192 Vector<char, 1024> characters(l);
193 for (int i = 0; i < l; ++i) {
194 HTMLElement* e = items[i];
195 bool selected = e->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(e)->selected();
196 characters[i] = selected ? 'X' : '.';
198 return String(characters, l);
201 void HTMLSelectElement::restoreState(const String& state)
205 Vector<HTMLElement*> items = listItems();
206 int l = items.size();
207 for (int i = 0; i < l; i++)
208 if (items[i]->hasLocalName(optionTag))
209 static_cast<HTMLOptionElement*>(items[i])->setSelected(state[i] == 'X');
213 bool HTMLSelectElement::insertBefore(PassRefPtr<Node> newChild, Node* refChild, ExceptionCode& ec)
215 bool result = HTMLGenericFormElement::insertBefore(newChild, refChild, ec);
217 setRecalcListItems();
221 bool HTMLSelectElement::replaceChild(PassRefPtr<Node> newChild, Node *oldChild, ExceptionCode& ec)
223 bool result = HTMLGenericFormElement::replaceChild(newChild, oldChild, ec);
225 setRecalcListItems();
229 bool HTMLSelectElement::removeChild(Node* oldChild, ExceptionCode& ec)
231 bool result = HTMLGenericFormElement::removeChild(oldChild, ec);
233 setRecalcListItems();
237 bool HTMLSelectElement::appendChild(PassRefPtr<Node> newChild, ExceptionCode& ec)
239 bool result = HTMLGenericFormElement::appendChild(newChild, ec);
241 setRecalcListItems();
245 ContainerNode* HTMLSelectElement::addChild(PassRefPtr<Node> newChild)
247 ContainerNode* result = HTMLGenericFormElement::addChild(newChild);
249 setRecalcListItems();
253 void HTMLSelectElement::parseMappedAttribute(MappedAttribute *attr)
255 if (attr->name() == sizeAttr) {
256 m_size = max(attr->value().toInt(), 1);
257 } else if (attr->name() == widthAttr) {
258 m_minwidth = max(attr->value().toInt(), 0);
259 } else if (attr->name() == multipleAttr) {
260 m_multiple = (!attr->isNull());
261 } else if (attr->name() == accesskeyAttr) {
262 // FIXME: ignore for the moment
263 } else if (attr->name() == onfocusAttr) {
264 setHTMLEventListener(focusEvent, attr);
265 } else if (attr->name() == onblurAttr) {
266 setHTMLEventListener(blurEvent, attr);
267 } else if (attr->name() == onchangeAttr) {
268 setHTMLEventListener(changeEvent, attr);
270 HTMLGenericFormElement::parseMappedAttribute(attr);
273 RenderObject *HTMLSelectElement::createRenderer(RenderArena *arena, RenderStyle *style)
275 return new (arena) DeprecatedRenderSelect(this);
278 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
280 bool successful = false;
281 Vector<HTMLElement*> items = listItems();
284 for (i = 0; i < items.size(); i++) {
285 if (items[i]->hasLocalName(optionTag)) {
286 HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
287 if (option->selected()) {
288 list.appendData(name(), option->value());
294 // ### this case should not happen. make sure that we select the first option
295 // in any case. otherwise we have no consistency with the DOM interface. FIXME!
296 // we return the first one if it was a combobox select
297 if (!successful && !m_multiple && m_size <= 1 && items.size() &&
298 (items[0]->hasLocalName(optionTag))) {
299 HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[0]);
300 if (option->value().isNull())
301 list.appendData(name(), option->text().deprecatedString().stripWhiteSpace());
303 list.appendData(name(), option->value());
310 int HTMLSelectElement::optionToListIndex(int optionIndex) const
312 Vector<HTMLElement*> items = listItems();
313 if (optionIndex < 0 || optionIndex >= int(items.size()))
317 int optionIndex2 = 0;
319 optionIndex2 < int(items.size()) && optionIndex2 <= optionIndex;
320 listIndex++) { // not a typo!
321 if (items[listIndex]->hasLocalName(optionTag))
328 int HTMLSelectElement::listToOptionIndex(int listIndex) const
330 Vector<HTMLElement*> items = listItems();
331 if (listIndex < 0 || listIndex >= int(items.size()) ||
332 !items[listIndex]->hasLocalName(optionTag))
335 int optionIndex = 0; // actual index of option not counting OPTGROUP entries that may be in list
337 for (i = 0; i < listIndex; i++)
338 if (items[i]->hasLocalName(optionTag))
343 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
345 return new HTMLOptionsCollection(this);
348 void HTMLSelectElement::recalcListItems()
350 Node* current = firstChild();
352 HTMLOptionElement* foundSelected = 0;
354 if (current->hasTagName(optgroupTag) && current->firstChild()) {
355 // ### what if optgroup contains just comments? don't want one of no options in it...
356 m_listItems.append(static_cast<HTMLElement*>(current));
357 current = current->firstChild();
359 if (current->hasTagName(optionTag)) {
360 m_listItems.append(static_cast<HTMLElement*>(current));
361 if (!foundSelected && !m_multiple && m_size <= 1) {
362 foundSelected = static_cast<HTMLOptionElement*>(current);
363 foundSelected->m_selected = true;
364 } else if (foundSelected && !m_multiple && static_cast<HTMLOptionElement*>(current)->selected()) {
365 foundSelected->m_selected = false;
366 foundSelected = static_cast<HTMLOptionElement*>(current);
369 if (current->hasTagName(hrTag))
370 m_listItems.append(static_cast<HTMLElement*>(current));
372 Node* parent = current->parentNode();
373 current = current->nextSibling();
376 current = parent->nextSibling();
379 m_recalcListItems = false;
382 void HTMLSelectElement::childrenChanged()
384 setRecalcListItems();
386 HTMLGenericFormElement::childrenChanged();
389 void HTMLSelectElement::setRecalcListItems()
391 m_recalcListItems = true;
393 static_cast<DeprecatedRenderSelect*>(renderer())->setOptionsChanged(true);
397 void HTMLSelectElement::reset()
399 bool optionSelected = false;
400 HTMLOptionElement* firstOption = 0;
401 Vector<HTMLElement*> items = listItems();
403 for (i = 0; i < items.size(); i++) {
404 if (items[i]->hasLocalName(optionTag)) {
405 HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
406 if (!option->getAttribute(selectedAttr).isNull()) {
407 option->setSelected(true);
408 optionSelected = true;
410 option->setSelected(false);
412 firstOption = option;
415 if (!optionSelected && firstOption)
416 firstOption->setSelected(true);
418 static_cast<DeprecatedRenderSelect*>(renderer())->setSelectionChanged(true);
422 void HTMLSelectElement::notifyOptionSelected(HTMLOptionElement *selectedOption, bool selected)
424 if (selected && !m_multiple) {
425 // deselect all other options
426 Vector<HTMLElement*> items = listItems();
428 for (i = 0; i < items.size(); i++) {
429 if (items[i]->hasLocalName(optionTag))
430 static_cast<HTMLOptionElement*>(items[i])->m_selected = (items[i] == selectedOption);
434 static_cast<DeprecatedRenderSelect*>(renderer())->setSelectionChanged(true);
439 void HTMLSelectElement::defaultEventHandler(Event *evt)
441 // Use key press event here since sending simulated mouse events
442 // on key down blocks the proper sending of the key press event.
443 if (evt->type() == keypressEvent) {
444 if (!form() || !renderer() || !evt->isKeyboardEvent())
446 if (static_cast<KeyboardEvent*>(evt)->keyIdentifier() == "Enter") {
447 form()->submitClick();
448 evt->setDefaultHandled();
451 HTMLGenericFormElement::defaultEventHandler(evt);
454 void HTMLSelectElement::accessKeyAction(bool)
459 void HTMLSelectElement::setMultiple(bool multiple)
461 setAttribute(multipleAttr, multiple ? "" : 0);
464 void HTMLSelectElement::setSize(int size)
466 setAttribute(sizeAttr, String::number(size));
469 Node* HTMLSelectElement::namedItem(const String &name, bool caseSensitive)
471 return (options()->namedItem(name, caseSensitive));
474 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
479 int diff = index - length();
480 HTMLElement* before = 0;
481 // out of array bounds ? first insert empty dummies
483 setLength(index, ec);
484 // replace an existing entry ?
485 } else if (diff < 0) {
486 before = static_cast<HTMLElement*>(options()->item(index+1));
489 // finally add the new element
491 add(option, before, ec);
493 setSelectedIndex(index, !m_multiple);
497 void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
500 if (newLen > INT_MAX)
502 int diff = length() - newLen;
504 if (diff < 0) { // add dummy elements
506 RefPtr<Element> option = ownerDocument()->createElement("option", ec);
509 add(static_cast<HTMLElement*>(option.get()), 0, ec);
514 else // remove elements