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"
32 #include "DeprecatedRenderSelect.h"
34 #include "EventNames.h"
35 #include "FormDataList.h"
37 #include "HTMLFormElement.h"
38 #include "HTMLNames.h"
39 #include "HTMLOptionElement.h"
40 #include "HTMLOptionsCollection.h"
41 #include "KeyboardEvent.h"
42 #include "MouseEvent.h"
43 #include "RenderListBox.h"
44 #include "RenderMenuList.h"
45 #include "RenderPopupMenu.h"
46 #include "cssstyleselector.h"
47 #include <wtf/Vector.h>
51 #define ARROW_KEYS_POP_MENU 1
53 #define ARROW_KEYS_POP_MENU 0
60 using namespace EventNames;
61 using namespace HTMLNames;
63 HTMLSelectElement::HTMLSelectElement(Document* doc, HTMLFormElement* f)
64 : HTMLGenericFormElement(selectTag, doc, f)
68 , m_recalcListItems(false)
70 document()->registerFormElementWithState(this);
73 HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* f)
74 : HTMLGenericFormElement(tagName, doc, f), m_minwidth(0), m_size(0), m_multiple(false), m_recalcListItems(false)
76 document()->registerFormElementWithState(this);
79 HTMLSelectElement::~HTMLSelectElement()
81 document()->deregisterFormElementWithState(this);
84 bool HTMLSelectElement::checkDTD(const Node* newChild)
86 return newChild->isTextNode() || newChild->hasTagName(optionTag) || newChild->hasTagName(optgroupTag) || newChild->hasTagName(hrTag) ||
87 newChild->hasTagName(scriptTag);
90 void HTMLSelectElement::recalcStyle( StyleChange ch )
92 if (hasChangedChild() && renderer()) {
94 static_cast<RenderMenuList*>(renderer())->setOptionsChanged(true);
95 else if (renderer() && renderer()->style()->appearance() == ListboxAppearance)
96 static_cast<RenderListBox*>(renderer())->setOptionsChanged(true);
98 static_cast<DeprecatedRenderSelect*>(renderer())->setOptionsChanged(true);
101 HTMLGenericFormElement::recalcStyle( ch );
104 const AtomicString& HTMLSelectElement::type() const
106 static const AtomicString selectMultiple("select-multiple");
107 static const AtomicString selectOne("select-one");
108 return m_multiple ? selectMultiple : selectOne;
111 int HTMLSelectElement::selectedIndex() const
113 // return the number of the first option selected
115 const Vector<HTMLElement*>& items = listItems();
116 for (unsigned int i = 0; i < items.size(); i++) {
117 if (items[i]->hasLocalName(optionTag)) {
118 if (static_cast<HTMLOptionElement*>(items[i])->selected())
126 int HTMLSelectElement::lastSelectedListIndex() const
128 // return the number of the last option selected
131 const Vector<HTMLElement*>& items = listItems();
132 for (unsigned int i = 0; i < items.size(); i++) {
133 if (items[i]->hasLocalName(optionTag)) {
134 if (static_cast<HTMLOptionElement*>(items[i])->selected()) {
140 return found ? (int) index : -1;
143 void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement)
145 const Vector<HTMLElement*>& items = listItems();
147 for (i = 0; i < items.size(); i++) {
148 if (items[i]->hasLocalName(optionTag) && (items[i] != excludeElement)) {
149 HTMLOptionElement* element = static_cast<HTMLOptionElement*>(items[i]);
150 element->m_selected = false;
151 element->setChanged();
156 void HTMLSelectElement::setSelectedIndex(int index, bool deselect)
158 const Vector<HTMLElement*>& items = listItems();
159 int listIndex = optionToListIndex(index);
160 HTMLOptionElement* element = 0;
161 if (listIndex >= 0) {
162 element = static_cast<HTMLOptionElement*>(items[listIndex]);
163 element->setSelected(true);
166 deselectItems(element);
169 int HTMLSelectElement::length() const
173 const Vector<HTMLElement*>& items = listItems();
174 for (i = 0; i < items.size(); i++) {
175 if (items[i]->hasLocalName(optionTag))
181 void HTMLSelectElement::add( HTMLElement *element, HTMLElement *before, ExceptionCode& ec)
183 RefPtr<HTMLElement> protectNewChild(element); // make sure the element is ref'd and deref'd so we don't leak it
185 if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
188 insertBefore(element, before, ec);
190 setRecalcListItems();
193 void HTMLSelectElement::remove(int index)
195 ExceptionCode ec = 0;
196 int listIndex = optionToListIndex(index);
198 const Vector<HTMLElement*>& items = listItems();
199 if (listIndex < 0 || index >= int(items.size()))
200 return; // ### what should we do ? remove the last item?
202 removeChild(items[listIndex], ec);
204 setRecalcListItems();
207 String HTMLSelectElement::value()
210 const Vector<HTMLElement*>& items = listItems();
211 for (i = 0; i < items.size(); i++) {
212 if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected())
213 return static_cast<HTMLOptionElement*>(items[i])->value();
218 void HTMLSelectElement::setValue(const String &value)
222 // find the option with value() matching the given parameter
223 // and make it the current selection.
224 const Vector<HTMLElement*>& items = listItems();
225 for (unsigned i = 0; i < items.size(); i++)
226 if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->value() == value) {
227 static_cast<HTMLOptionElement*>(items[i])->setSelected(true);
232 String HTMLSelectElement::stateValue() const
234 const Vector<HTMLElement*>& items = listItems();
235 int l = items.size();
236 Vector<char, 1024> characters(l);
237 for (int i = 0; i < l; ++i) {
238 HTMLElement* e = items[i];
239 bool selected = e->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(e)->selected();
240 characters[i] = selected ? 'X' : '.';
242 return String(characters, l);
245 void HTMLSelectElement::restoreState(const String& state)
249 const Vector<HTMLElement*>& items = listItems();
250 int l = items.size();
251 for (int i = 0; i < l; i++)
252 if (items[i]->hasLocalName(optionTag))
253 static_cast<HTMLOptionElement*>(items[i])->setSelected(state[i] == 'X');
257 bool HTMLSelectElement::insertBefore(PassRefPtr<Node> newChild, Node* refChild, ExceptionCode& ec)
259 bool result = HTMLGenericFormElement::insertBefore(newChild, refChild, ec);
261 setRecalcListItems();
265 bool HTMLSelectElement::replaceChild(PassRefPtr<Node> newChild, Node *oldChild, ExceptionCode& ec)
267 bool result = HTMLGenericFormElement::replaceChild(newChild, oldChild, ec);
269 setRecalcListItems();
273 bool HTMLSelectElement::removeChild(Node* oldChild, ExceptionCode& ec)
275 bool result = HTMLGenericFormElement::removeChild(oldChild, ec);
277 setRecalcListItems();
281 bool HTMLSelectElement::appendChild(PassRefPtr<Node> newChild, ExceptionCode& ec)
283 bool result = HTMLGenericFormElement::appendChild(newChild, ec);
285 setRecalcListItems();
289 ContainerNode* HTMLSelectElement::addChild(PassRefPtr<Node> newChild)
291 ContainerNode* result = HTMLGenericFormElement::addChild(newChild);
293 setRecalcListItems();
297 void HTMLSelectElement::parseMappedAttribute(MappedAttribute *attr)
299 bool oldUsesMenuList = usesMenuList();
300 if (attr->name() == sizeAttr) {
301 m_size = max(attr->value().toInt(), 1);
302 if (oldUsesMenuList != usesMenuList() && attached()) {
306 } else if (attr->name() == widthAttr) {
307 m_minwidth = max(attr->value().toInt(), 0);
308 } else if (attr->name() == multipleAttr) {
309 m_multiple = (!attr->isNull());
310 if (oldUsesMenuList != usesMenuList() && attached()) {
314 } else if (attr->name() == accesskeyAttr) {
315 // FIXME: ignore for the moment
316 } else if (attr->name() == onfocusAttr) {
317 setHTMLEventListener(focusEvent, attr);
318 } else if (attr->name() == onblurAttr) {
319 setHTMLEventListener(blurEvent, attr);
320 } else if (attr->name() == onchangeAttr) {
321 setHTMLEventListener(changeEvent, attr);
323 HTMLGenericFormElement::parseMappedAttribute(attr);
326 bool HTMLSelectElement::isKeyboardFocusable() const
328 if (renderer() && (usesMenuList() || renderer()->style()->appearance() == ListboxAppearance))
329 return isFocusable();
330 return HTMLGenericFormElement::isKeyboardFocusable();
333 bool HTMLSelectElement::isMouseFocusable() const
335 if (renderer() && (usesMenuList() || renderer()->style()->appearance() == ListboxAppearance))
336 return isFocusable();
337 return HTMLGenericFormElement::isMouseFocusable();
340 RenderObject *HTMLSelectElement::createRenderer(RenderArena *arena, RenderStyle *style)
343 return new (arena) RenderMenuList(this);
344 if (style->appearance() == ListboxAppearance)
345 return new (arena) RenderListBox(this);
346 return new (arena) DeprecatedRenderSelect(this);
349 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
351 bool successful = false;
352 const Vector<HTMLElement*>& items = listItems();
355 for (i = 0; i < items.size(); i++) {
356 if (items[i]->hasLocalName(optionTag)) {
357 HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
358 if (option->selected()) {
359 list.appendData(name(), option->value());
365 // ### this case should not happen. make sure that we select the first option
366 // in any case. otherwise we have no consistency with the DOM interface. FIXME!
367 // we return the first one if it was a combobox select
368 if (!successful && !m_multiple && m_size <= 1 && items.size() &&
369 (items[0]->hasLocalName(optionTag))) {
370 HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[0]);
371 if (option->value().isNull())
372 list.appendData(name(), option->text().stripWhiteSpace());
374 list.appendData(name(), option->value());
381 int HTMLSelectElement::optionToListIndex(int optionIndex) const
383 const Vector<HTMLElement*>& items = listItems();
384 if (optionIndex < 0 || optionIndex >= int(items.size()))
388 int optionIndex2 = 0;
390 optionIndex2 < int(items.size()) && optionIndex2 <= optionIndex;
391 listIndex++) { // not a typo!
392 if (items[listIndex]->hasLocalName(optionTag))
399 int HTMLSelectElement::listToOptionIndex(int listIndex) const
401 const Vector<HTMLElement*>& items = listItems();
402 if (listIndex < 0 || listIndex >= int(items.size()) ||
403 !items[listIndex]->hasLocalName(optionTag))
406 int optionIndex = 0; // actual index of option not counting OPTGROUP entries that may be in list
407 for (int i = 0; i < listIndex; i++)
408 if (items[i]->hasLocalName(optionTag))
413 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
415 return new HTMLOptionsCollection(this);
418 void HTMLSelectElement::recalcListItems() const
420 Node* current = firstChild();
422 HTMLOptionElement* foundSelected = 0;
424 if (current->hasTagName(optgroupTag) && current->firstChild()) {
425 // ### what if optgroup contains just comments? don't want one of no options in it...
426 m_listItems.append(static_cast<HTMLElement*>(current));
427 current = current->firstChild();
429 if (current->hasTagName(optionTag)) {
430 m_listItems.append(static_cast<HTMLElement*>(current));
431 if (!foundSelected && (usesMenuList() || (!m_multiple && static_cast<HTMLOptionElement*>(current)->selected()))) {
432 foundSelected = static_cast<HTMLOptionElement*>(current);
433 foundSelected->m_selected = true;
434 } else if (foundSelected && !m_multiple && static_cast<HTMLOptionElement*>(current)->selected()) {
435 foundSelected->m_selected = false;
436 foundSelected = static_cast<HTMLOptionElement*>(current);
439 if (current->hasTagName(hrTag))
440 m_listItems.append(static_cast<HTMLElement*>(current));
442 Node* parent = current->parentNode();
443 current = current->nextSibling();
446 current = parent->nextSibling();
449 m_recalcListItems = false;
452 void HTMLSelectElement::childrenChanged()
454 setRecalcListItems();
456 HTMLGenericFormElement::childrenChanged();
459 void HTMLSelectElement::setRecalcListItems()
461 m_recalcListItems = true;
464 static_cast<RenderMenuList*>(renderer())->setOptionsChanged(true);
465 else if (renderer() && renderer()->style()->appearance() == ListboxAppearance)
466 static_cast<RenderListBox*>(renderer())->setOptionsChanged(true);
468 static_cast<DeprecatedRenderSelect*>(renderer())->setOptionsChanged(true);
473 void HTMLSelectElement::reset()
475 bool optionSelected = false;
476 HTMLOptionElement* firstOption = 0;
477 const Vector<HTMLElement*>& items = listItems();
479 for (i = 0; i < items.size(); i++) {
480 if (items[i]->hasLocalName(optionTag)) {
481 HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
482 if (!option->getAttribute(selectedAttr).isNull()) {
483 option->setSelected(true);
484 optionSelected = true;
486 option->setSelected(false);
488 firstOption = option;
491 if (!optionSelected && firstOption)
492 firstOption->setSelected(true);
496 void HTMLSelectElement::notifyOptionSelected(HTMLOptionElement *selectedOption, bool selected)
498 if (selected && !m_multiple)
499 deselectItems(selectedOption);
501 if (renderer() && !usesMenuList()) {
502 if (renderer()->style()->appearance() == ListboxAppearance)
503 static_cast<RenderListBox*>(renderer())->setSelectionChanged(true);
505 static_cast<DeprecatedRenderSelect*>(renderer())->setSelectionChanged(true);
511 void HTMLSelectElement::defaultEventHandler(Event* evt)
514 menuListDefaultEventHandler(evt);
515 else if (renderer() && renderer()->isListBox() && renderer()->style()->appearance() == ListboxAppearance)
516 listBoxDefaultEventHandler(evt);
517 HTMLGenericFormElement::defaultEventHandler(evt);
520 void HTMLSelectElement::menuListDefaultEventHandler(Event* evt)
522 RenderMenuList* menuList = static_cast<RenderMenuList*>(renderer());
524 // Use key press event here since sending simulated mouse events
525 // on key down blocks the proper sending of the key press event.
526 if (evt->type() == keypressEvent) {
527 if (!renderer() || !evt->isKeyboardEvent())
529 String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
530 bool handled = false;
531 #if ARROW_KEYS_POP_MENU
532 if (form() && keyIdentifier == "Enter") {
534 // Make sure the form hasn't been destroyed during the blur.
536 form()->submitClick();
539 if ((keyIdentifier == "Down" || keyIdentifier == "Up" || keyIdentifier == "U+000020") && renderer() && usesMenuList()) {
541 menuList->showPopup();
545 int index = optionToListIndex(selectedIndex());
546 if (keyIdentifier == "Down" || keyIdentifier == "Right") {
547 if (index < listItems().size() - 1)
548 setSelectedIndex(++index);
550 } else if (keyIdentifier == "Up" || keyIdentifier == "Left") {
552 setSelectedIndex(--index);
557 evt->setDefaultHandled();
560 if (evt->type() == mousedownEvent) {
562 if (menuList->popupIsVisible())
563 menuList->hidePopup();
565 menuList->showPopup();
566 evt->setDefaultHandled();
570 void HTMLSelectElement::listBoxDefaultEventHandler(Event* evt)
572 if (evt->type() == mousedownEvent) {
573 MouseEvent* mEvt = static_cast<MouseEvent*>(evt);
574 if (HTMLOptionElement* element = static_cast<RenderListBox*>(renderer())->optionAtPoint(mEvt->x(), mEvt->y())) {
575 bool deselectOtherOptions = true;
576 bool shouldSelect = true;
578 bool multiSelectKeyPressed = false;
580 multiSelectKeyPressed = mEvt->metaKey();
582 multiSelectKeyPressed = mEvt->ctrlKey();
584 if (multiple() && multiSelectKeyPressed)
585 deselectOtherOptions = false;
586 if (element->selected() && multiSelectKeyPressed)
587 shouldSelect = false;
589 int optionIndex = element->index();
592 element->m_selected = false;
594 setSelectedIndex(optionIndex, deselectOtherOptions);
598 if (evt->type() == keypressEvent) {
599 if (!evt->isKeyboardEvent())
601 String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
604 const Vector<HTMLElement*>& items = listItems();
606 if (keyIdentifier == "Down") {
607 index = nextSelectableListIndex(lastSelectedListIndex());
609 } else if (keyIdentifier == "Up") {
610 index = previousSelectableListIndex(optionToListIndex(selectedIndex()));
612 if (keyIdentifier == "Down" || keyIdentifier == "Up") {
613 ASSERT(index >= 0 && (unsigned)index < items.size());
614 HTMLOptionElement* element = static_cast<HTMLOptionElement*>(items[index]);
616 setSelectedIndex(element->index(), !multiple() || !static_cast<KeyboardEvent*>(evt)->shiftKey());
617 static_cast<RenderListBox*>(renderer())->scrollToRevealElementAtListIndex(index);
619 evt->setDefaultHandled();
621 renderer()->repaint();
626 int HTMLSelectElement::nextSelectableListIndex(int startIndex)
628 const Vector<HTMLElement*>& items = listItems();
629 int index = startIndex + 1;
630 while (index >= 0 && (unsigned)index < items.size() && (!items[index]->hasLocalName(optionTag) || items[index]->disabled()))
632 if ((unsigned) index == items.size())
637 int HTMLSelectElement::previousSelectableListIndex(int startIndex)
639 const Vector<HTMLElement*>& items = listItems();
640 if (startIndex == -1)
641 startIndex = items.size();
642 int index = startIndex - 1;
643 while (index >= 0 && (unsigned)index < items.size() && (!items[index]->hasLocalName(optionTag) || items[index]->disabled()))
650 void HTMLSelectElement::accessKeyAction(bool sendToAnyElement)
652 // send the mouse button events iff the
653 // caller specified sendToAnyElement
654 click(sendToAnyElement);
657 void HTMLSelectElement::setMultiple(bool multiple)
659 setAttribute(multipleAttr, multiple ? "" : 0);
662 void HTMLSelectElement::setSize(int size)
664 setAttribute(sizeAttr, String::number(size));
667 Node* HTMLSelectElement::namedItem(const String &name, bool caseSensitive)
669 return (options()->namedItem(name, caseSensitive));
672 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
677 int diff = index - length();
678 HTMLElement* before = 0;
679 // out of array bounds ? first insert empty dummies
681 setLength(index, ec);
682 // replace an existing entry ?
683 } else if (diff < 0) {
684 before = static_cast<HTMLElement*>(options()->item(index+1));
687 // finally add the new element
689 add(option, before, ec);
690 if (diff >= 0 && option->selected())
691 setSelectedIndex(index, !m_multiple);
695 void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
698 if (newLen > INT_MAX)
700 int diff = length() - newLen;
702 if (diff < 0) { // add dummy elements
704 RefPtr<Element> option = ownerDocument()->createElement("option", ec);
707 add(static_cast<HTMLElement*>(option.get()), 0, ec);
712 else // remove elements