2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2001 Dirk Mueller (mueller@kde.org)
5 * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
6 * (C) 2006 Alexey Proskuryakov (ap@nypop.com)
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
26 #include "HTMLSelectElement.h"
28 #include "CSSPropertyNames.h"
29 #include "CSSStyleSelector.h"
30 #include "CharacterNames.h"
33 #include "EventHandler.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"
46 #include <wtf/Vector.h>
49 #define ARROW_KEYS_POP_MENU 1
51 #define ARROW_KEYS_POP_MENU 0
56 using namespace Unicode;
60 using namespace EventNames;
61 using namespace HTMLNames;
63 static const DOMTimeStamp typeAheadTimeout = 1000;
65 HTMLSelectElement::HTMLSelectElement(Document* doc, HTMLFormElement* f)
66 : HTMLFormControlElementWithState(selectTag, doc, f)
70 , m_recalcListItems(false)
71 , m_lastOnChangeIndex(-1)
72 , m_activeSelectionAnchorIndex(-1)
73 , m_activeSelectionEndIndex(-1)
74 , m_activeSelectionState(false)
80 HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* f)
81 : HTMLFormControlElementWithState(tagName, doc, f)
85 , m_recalcListItems(false)
86 , m_lastOnChangeIndex(-1)
87 , m_activeSelectionAnchorIndex(-1)
88 , m_activeSelectionEndIndex(-1)
89 , m_activeSelectionState(false)
95 bool HTMLSelectElement::checkDTD(const Node* newChild)
97 // Make sure to keep <optgroup> in sync with this.
98 return newChild->isTextNode() || newChild->hasTagName(optionTag) || newChild->hasTagName(optgroupTag) || newChild->hasTagName(hrTag) ||
99 newChild->hasTagName(scriptTag);
102 void HTMLSelectElement::recalcStyle( StyleChange ch )
104 if (hasChangedChild() && renderer()) {
106 static_cast<RenderMenuList*>(renderer())->setOptionsChanged(true);
108 static_cast<RenderListBox*>(renderer())->setOptionsChanged(true);
109 } else if (m_recalcListItems)
112 HTMLFormControlElementWithState::recalcStyle(ch);
115 const AtomicString& HTMLSelectElement::type() const
117 static const AtomicString selectMultiple("select-multiple");
118 static const AtomicString selectOne("select-one");
119 return m_multiple ? selectMultiple : selectOne;
122 int HTMLSelectElement::selectedIndex() const
124 // return the number of the first option selected
126 const Vector<HTMLElement*>& items = listItems();
127 for (unsigned int i = 0; i < items.size(); i++) {
128 if (items[i]->hasLocalName(optionTag)) {
129 if (static_cast<HTMLOptionElement*>(items[i])->selected())
137 int HTMLSelectElement::lastSelectedListIndex() const
139 // return the number of the last option selected
142 const Vector<HTMLElement*>& items = listItems();
143 for (unsigned int i = 0; i < items.size(); i++) {
144 if (items[i]->hasLocalName(optionTag)) {
145 if (static_cast<HTMLOptionElement*>(items[i])->selected()) {
151 return found ? (int) index : -1;
154 void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement)
156 const Vector<HTMLElement*>& items = listItems();
158 for (i = 0; i < items.size(); i++) {
159 if (items[i]->hasLocalName(optionTag) && (items[i] != excludeElement)) {
160 HTMLOptionElement* element = static_cast<HTMLOptionElement*>(items[i]);
161 element->setSelectedState(false);
166 void HTMLSelectElement::setSelectedIndex(int optionIndex, bool deselect, bool fireOnChange)
168 const Vector<HTMLElement*>& items = listItems();
169 int listIndex = optionToListIndex(optionIndex);
170 HTMLOptionElement* element = 0;
175 if (listIndex >= 0) {
176 if (m_activeSelectionAnchorIndex < 0 || deselect)
177 setActiveSelectionAnchorIndex(listIndex);
178 if (m_activeSelectionEndIndex < 0 || deselect)
179 setActiveSelectionEndIndex(listIndex);
180 element = static_cast<HTMLOptionElement*>(items[listIndex]);
181 element->setSelectedState(true);
185 deselectItems(element);
189 // This only gets called with fireOnChange for menu lists.
190 if (fireOnChange && usesMenuList())
194 int HTMLSelectElement::activeSelectionStartListIndex() const
196 if (m_activeSelectionAnchorIndex >= 0)
197 return m_activeSelectionAnchorIndex;
198 return optionToListIndex(selectedIndex());
201 int HTMLSelectElement::activeSelectionEndListIndex() const
203 if (m_activeSelectionEndIndex >= 0)
204 return m_activeSelectionEndIndex;
205 return lastSelectedListIndex();
208 unsigned HTMLSelectElement::length() const
211 const Vector<HTMLElement*>& items = listItems();
212 for (unsigned i = 0; i < items.size(); ++i) {
213 if (items[i]->hasLocalName(optionTag))
219 void HTMLSelectElement::add(HTMLElement *element, HTMLElement *before, ExceptionCode& ec)
221 RefPtr<HTMLElement> protectNewChild(element); // make sure the element is ref'd and deref'd so we don't leak it
223 if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
227 insertBefore(element, before, ec);
229 setRecalcListItems();
232 void HTMLSelectElement::remove(int index)
234 ExceptionCode ec = 0;
235 int listIndex = optionToListIndex(index);
237 const Vector<HTMLElement*>& items = listItems();
238 if (listIndex < 0 || index >= int(items.size()))
239 return; // ### what should we do ? remove the last item?
241 Element *item = items[listIndex];
242 ASSERT(item->parentNode());
243 item->parentNode()->removeChild(item, ec);
245 setRecalcListItems();
248 String HTMLSelectElement::value()
251 const Vector<HTMLElement*>& items = listItems();
252 for (i = 0; i < items.size(); i++) {
253 if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected())
254 return static_cast<HTMLOptionElement*>(items[i])->value();
259 void HTMLSelectElement::setValue(const String &value)
263 // find the option with value() matching the given parameter
264 // and make it the current selection.
265 const Vector<HTMLElement*>& items = listItems();
266 unsigned optionIndex = 0;
267 for (unsigned i = 0; i < items.size(); i++)
268 if (items[i]->hasLocalName(optionTag)) {
269 if (static_cast<HTMLOptionElement*>(items[i])->value() == value) {
270 setSelectedIndex(optionIndex, true);
277 bool HTMLSelectElement::saveState(String& value) const
279 const Vector<HTMLElement*>& items = listItems();
280 int l = items.size();
281 Vector<char, 1024> characters(l);
282 for (int i = 0; i < l; ++i) {
283 HTMLElement* e = items[i];
284 bool selected = e->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(e)->selected();
285 characters[i] = selected ? 'X' : '.';
287 value = String(characters.data(), l);
291 void HTMLSelectElement::restoreState(const String& state)
295 const Vector<HTMLElement*>& items = listItems();
296 int l = items.size();
297 for (int i = 0; i < l; i++)
298 if (items[i]->hasLocalName(optionTag))
299 static_cast<HTMLOptionElement*>(items[i])->setSelectedState(state[i] == 'X');
304 bool HTMLSelectElement::insertBefore(PassRefPtr<Node> newChild, Node* refChild, ExceptionCode& ec)
306 bool result = HTMLFormControlElementWithState::insertBefore(newChild, refChild, ec);
308 setRecalcListItems();
312 bool HTMLSelectElement::replaceChild(PassRefPtr<Node> newChild, Node *oldChild, ExceptionCode& ec)
314 bool result = HTMLFormControlElementWithState::replaceChild(newChild, oldChild, ec);
316 setRecalcListItems();
320 bool HTMLSelectElement::removeChild(Node* oldChild, ExceptionCode& ec)
322 bool result = HTMLFormControlElementWithState::removeChild(oldChild, ec);
324 setRecalcListItems();
328 bool HTMLSelectElement::appendChild(PassRefPtr<Node> newChild, ExceptionCode& ec)
330 bool result = HTMLFormControlElementWithState::appendChild(newChild, ec);
332 setRecalcListItems();
336 bool HTMLSelectElement::removeChildren()
338 bool result = HTMLFormControlElementWithState::removeChildren();
340 setRecalcListItems();
344 void HTMLSelectElement::parseMappedAttribute(MappedAttribute *attr)
346 bool oldUsesMenuList = usesMenuList();
347 if (attr->name() == sizeAttr) {
348 int oldSize = m_size;
349 // Set the attribute value to a number.
350 // This is important since the style rules for this attribute can determine the appearance property.
351 int size = attr->value().toInt();
352 String attrSize = String::number(size);
353 if (attrSize != attr->value())
354 attr->setValue(attrSize);
356 m_size = max(size, 1);
357 if ((oldUsesMenuList != usesMenuList() || !oldUsesMenuList && m_size != oldSize) && attached()) {
360 setRecalcListItems();
362 } else if (attr->name() == widthAttr) {
363 m_minwidth = max(attr->value().toInt(), 0);
364 } else if (attr->name() == multipleAttr) {
365 m_multiple = (!attr->isNull());
366 if (oldUsesMenuList != usesMenuList() && attached()) {
370 } else if (attr->name() == accesskeyAttr) {
371 // FIXME: ignore for the moment
372 } else if (attr->name() == alignAttr) {
373 // Don't map 'align' attribute. This matches what Firefox, Opera and IE do.
374 // See http://bugs.webkit.org/show_bug.cgi?id=12072
375 } else if (attr->name() == onfocusAttr) {
376 setHTMLEventListener(focusEvent, attr);
377 } else if (attr->name() == onblurAttr) {
378 setHTMLEventListener(blurEvent, attr);
379 } else if (attr->name() == onchangeAttr) {
380 setHTMLEventListener(changeEvent, attr);
382 HTMLFormControlElementWithState::parseMappedAttribute(attr);
385 bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const
388 return isFocusable();
389 return HTMLFormControlElementWithState::isKeyboardFocusable(event);
392 bool HTMLSelectElement::isMouseFocusable() const
395 return isFocusable();
396 return HTMLFormControlElementWithState::isMouseFocusable();
399 bool HTMLSelectElement::canSelectAll() const
401 return !usesMenuList();
404 void HTMLSelectElement::selectAll()
406 ASSERT(!usesMenuList());
407 if (!renderer() || !multiple())
410 // Save the selection so it can be compared to the new selectAll selection when we call onChange
413 m_activeSelectionState = true;
414 setActiveSelectionAnchorIndex(nextSelectableListIndex(-1));
415 setActiveSelectionEndIndex(previousSelectableListIndex(-1));
417 updateListBoxSelection(false);
421 RenderObject *HTMLSelectElement::createRenderer(RenderArena *arena, RenderStyle *style)
424 return new (arena) RenderMenuList(this);
425 return new (arena) RenderListBox(this);
428 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
430 bool successful = false;
431 const Vector<HTMLElement*>& items = listItems();
434 for (i = 0; i < items.size(); i++) {
435 if (items[i]->hasLocalName(optionTag)) {
436 HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
437 if (option->selected()) {
438 list.appendData(name(), option->value());
444 // ### this case should not happen. make sure that we select the first option
445 // in any case. otherwise we have no consistency with the DOM interface. FIXME!
446 // we return the first one if it was a combobox select
447 if (!successful && !m_multiple && m_size <= 1 && items.size() &&
448 (items[0]->hasLocalName(optionTag))) {
449 HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[0]);
450 if (option->value().isNull())
451 list.appendData(name(), option->text().stripWhiteSpace());
453 list.appendData(name(), option->value());
460 int HTMLSelectElement::optionToListIndex(int optionIndex) const
462 const Vector<HTMLElement*>& items = listItems();
463 int listSize = (int)items.size();
464 if (optionIndex < 0 || optionIndex >= listSize)
467 int optionIndex2 = -1;
468 for (int listIndex = 0; listIndex < listSize; listIndex++) {
469 if (items[listIndex]->hasLocalName(optionTag)) {
471 if (optionIndex2 == optionIndex)
478 int HTMLSelectElement::listToOptionIndex(int listIndex) const
480 const Vector<HTMLElement*>& items = listItems();
481 if (listIndex < 0 || listIndex >= int(items.size()) ||
482 !items[listIndex]->hasLocalName(optionTag))
485 int optionIndex = 0; // actual index of option not counting OPTGROUP entries that may be in list
486 for (int i = 0; i < listIndex; i++)
487 if (items[i]->hasLocalName(optionTag))
492 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
494 return new HTMLOptionsCollection(this);
497 void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
500 HTMLOptionElement* foundSelected = 0;
501 for (Node* current = firstChild(); current; current = current->traverseNextSibling(this)) {
502 if (current->hasTagName(optgroupTag) && current->firstChild()) {
503 // FIXME: It doesn't make sense to add an optgroup to the list items,
504 // when it has children, but not to add it if it happens to have,
505 // children (say some comment nodes or text nodes), yet that's what
507 m_listItems.append(static_cast<HTMLElement*>(current));
508 current = current->firstChild();
509 // FIXME: It doesn't make sense to handle an <optgroup> inside another <optgroup>
510 // if it's not the first child, but not handle it if it happens to be the first
511 // child, yet that's what this code does!
514 if (current->hasTagName(optionTag)) {
515 m_listItems.append(static_cast<HTMLElement*>(current));
516 if (updateSelectedStates) {
517 if (!foundSelected && (usesMenuList() || (!m_multiple && static_cast<HTMLOptionElement*>(current)->selected()))) {
518 foundSelected = static_cast<HTMLOptionElement*>(current);
519 foundSelected->setSelectedState(true);
520 } else if (foundSelected && !m_multiple && static_cast<HTMLOptionElement*>(current)->selected()) {
521 foundSelected->setSelectedState(false);
522 foundSelected = static_cast<HTMLOptionElement*>(current);
526 if (current->hasTagName(hrTag))
527 m_listItems.append(static_cast<HTMLElement*>(current));
529 m_recalcListItems = false;
532 void HTMLSelectElement::childrenChanged()
534 setRecalcListItems();
535 HTMLFormControlElementWithState::childrenChanged();
538 void HTMLSelectElement::setRecalcListItems()
540 m_recalcListItems = true;
543 static_cast<RenderMenuList*>(renderer())->setOptionsChanged(true);
545 static_cast<RenderListBox*>(renderer())->setOptionsChanged(true);
548 m_collectionInfo.reset();
552 void HTMLSelectElement::reset()
554 bool optionSelected = false;
555 HTMLOptionElement* firstOption = 0;
556 const Vector<HTMLElement*>& items = listItems();
558 for (i = 0; i < items.size(); i++) {
559 if (items[i]->hasLocalName(optionTag)) {
560 HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
561 if (!option->getAttribute(selectedAttr).isNull()) {
562 option->setSelectedState(true);
563 optionSelected = true;
565 option->setSelectedState(false);
567 firstOption = option;
570 if (!optionSelected && firstOption && usesMenuList())
571 firstOption->setSelectedState(true);
576 void HTMLSelectElement::dispatchFocusEvent()
579 // Save the selection so it can be compared to the new selection when we call onChange during dispatchBlurEvent.
581 HTMLFormControlElementWithState::dispatchFocusEvent();
584 void HTMLSelectElement::dispatchBlurEvent()
586 // We only need to fire onChange here for menu lists, because we fire onChange for list boxes whenever the selection change is actually made.
587 // This matches other browsers' behavior.
590 HTMLFormControlElementWithState::dispatchBlurEvent();
593 void HTMLSelectElement::defaultEventHandler(Event* evt)
599 menuListDefaultEventHandler(evt);
601 listBoxDefaultEventHandler(evt);
603 if (evt->defaultHandled())
606 if (evt->type() == keypressEvent && evt->isKeyboardEvent()) {
607 KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(evt);
609 if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() &&
610 isPrintableChar(keyboardEvent->charCode())) {
611 typeAheadFind(keyboardEvent);
612 evt->setDefaultHandled();
617 HTMLFormControlElementWithState::defaultEventHandler(evt);
620 void HTMLSelectElement::menuListDefaultEventHandler(Event* evt)
622 RenderMenuList* menuList = static_cast<RenderMenuList*>(renderer());
624 if (evt->type() == keydownEvent) {
625 if (!renderer() || !evt->isKeyboardEvent())
627 String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
628 bool handled = false;
629 #if ARROW_KEYS_POP_MENU
630 if (keyIdentifier == "Down" || keyIdentifier == "Up") {
632 // Save the selection so it can be compared to the new selection when we call onChange during setSelectedIndex,
633 // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
635 menuList->showPopup();
639 int listIndex = optionToListIndex(selectedIndex());
640 if (keyIdentifier == "Down" || keyIdentifier == "Right") {
641 int size = listItems().size();
643 listIndex >= 0 && listIndex < size && (listItems()[listIndex]->disabled() || !listItems()[listIndex]->hasTagName(optionTag));
646 if (listIndex >= 0 && listIndex < size)
647 setSelectedIndex(listToOptionIndex(listIndex));
649 } else if (keyIdentifier == "Up" || keyIdentifier == "Left") {
650 int size = listItems().size();
652 listIndex >= 0 && listIndex < size && (listItems()[listIndex]->disabled() || !listItems()[listIndex]->hasTagName(optionTag));
655 if (listIndex >= 0 && listIndex < size)
656 setSelectedIndex(listToOptionIndex(listIndex));
661 evt->setDefaultHandled();
664 // Use key press event here since sending simulated mouse events
665 // on key down blocks the proper sending of the key press event.
666 if (evt->type() == keypressEvent) {
667 if (!renderer() || !evt->isKeyboardEvent())
669 int keyCode = static_cast<KeyboardEvent*>(evt)->keyCode();
670 bool handled = false;
671 #if ARROW_KEYS_POP_MENU
672 if (keyCode == ' ') {
674 // Save the selection so it can be compared to the new selection when we call onChange during setSelectedIndex,
675 // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
677 menuList->showPopup();
680 if (keyCode == '\r') {
683 form()->submitClick(evt);
687 int listIndex = optionToListIndex(selectedIndex());
688 if (keyCode == '\r') {
689 // listIndex should already be selected, but this will fire the onchange handler.
690 setSelectedIndex(listToOptionIndex(listIndex), true, true);
695 evt->setDefaultHandled();
698 if (evt->type() == mousedownEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton) {
700 if (menuList->popupIsVisible())
701 menuList->hidePopup();
703 // Save the selection so it can be compared to the new selection when we call onChange during setSelectedIndex,
704 // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
706 menuList->showPopup();
708 evt->setDefaultHandled();
712 void HTMLSelectElement::listBoxDefaultEventHandler(Event* evt)
714 if (evt->type() == mousedownEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton) {
717 MouseEvent* mEvt = static_cast<MouseEvent*>(evt);
718 int listIndex = static_cast<RenderListBox*>(renderer())->listIndexAtOffset(mEvt->offsetX(), mEvt->offsetY());
719 if (listIndex >= 0) {
720 // Save the selection so it can be compared to the new selection when we call onChange during mouseup, or after autoscroll finishes.
723 m_activeSelectionState = true;
725 bool multiSelectKeyPressed = false;
727 multiSelectKeyPressed = mEvt->metaKey();
729 multiSelectKeyPressed = mEvt->ctrlKey();
732 bool shiftSelect = multiple() && mEvt->shiftKey();
733 bool multiSelect = multiple() && multiSelectKeyPressed && !mEvt->shiftKey();
735 HTMLElement* clickedElement = listItems()[listIndex];
736 HTMLOptionElement* option = 0;
737 if (clickedElement->hasLocalName(optionTag)) {
738 option = static_cast<HTMLOptionElement*>(clickedElement);
740 // Keep track of whether an active selection (like during drag selection), should select or deselect
741 if (option->selected() && multiSelectKeyPressed)
742 m_activeSelectionState = false;
744 if (!m_activeSelectionState)
745 option->setSelectedState(false);
748 // If we're not in any special multiple selection mode, then deselect all other items, excluding the clicked option.
749 // If no option was clicked, then this will deselect all items in the list.
750 if (!shiftSelect && !multiSelect)
751 deselectItems(option);
753 // If the anchor hasn't been set, and we're doing a single selection or a shift selection, then initialize the anchor to the first selected index.
754 if (m_activeSelectionAnchorIndex < 0 && !multiSelect)
755 setActiveSelectionAnchorIndex(selectedIndex());
757 // Set the selection state of the clicked option
758 if (option && !option->disabled())
759 option->setSelectedState(true);
761 // If there was no selectedIndex() for the previous initialization, or
762 // If we're doing a single selection, or a multiple selection (using cmd or ctrl), then initialize the anchor index to the listIndex that just got clicked.
763 if (listIndex >= 0 && (m_activeSelectionAnchorIndex < 0 || !shiftSelect))
764 setActiveSelectionAnchorIndex(listIndex);
766 setActiveSelectionEndIndex(listIndex);
767 updateListBoxSelection(!multiSelect);
769 if (Frame* frame = document()->frame())
770 frame->eventHandler()->setMouseDownMayStartAutoscroll();
772 evt->setDefaultHandled();
774 } else if (evt->type() == mouseupEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton && document()->frame()->eventHandler()->autoscrollRenderer() != renderer())
775 // This makes sure we fire onChange for a single click. For drag selection, onChange will fire when the autoscroll timer stops.
777 else if (evt->type() == keydownEvent) {
778 if (!evt->isKeyboardEvent())
780 String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
783 if (m_activeSelectionEndIndex < 0) {
784 // Initialize the end index
785 if (keyIdentifier == "Down")
786 endIndex = nextSelectableListIndex(lastSelectedListIndex());
787 else if (keyIdentifier == "Up")
788 endIndex = previousSelectableListIndex(optionToListIndex(selectedIndex()));
790 // Set the end index based on the current end index
791 if (keyIdentifier == "Down")
792 endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
793 else if (keyIdentifier == "Up")
794 endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);
797 if (keyIdentifier == "Down" || keyIdentifier == "Up") {
798 // Save the selection so it can be compared to the new selection when we call onChange immediately after making the new selection.
801 ASSERT(endIndex >= 0 && (unsigned)endIndex < listItems().size());
802 setActiveSelectionEndIndex(endIndex);
804 // If the anchor is unitialized, or if we're going to deselect all other options, then set the anchor index equal to the end index.
805 bool deselectOthers = !multiple() || !static_cast<KeyboardEvent*>(evt)->shiftKey();
806 if (m_activeSelectionAnchorIndex < 0 || deselectOthers) {
807 m_activeSelectionState = true;
810 setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
813 static_cast<RenderListBox*>(renderer())->scrollToRevealElementAtListIndex(endIndex);
814 updateListBoxSelection(deselectOthers);
816 evt->setDefaultHandled();
818 } else if (evt->type() == keypressEvent) {
819 if (!evt->isKeyboardEvent())
821 int keyCode = static_cast<KeyboardEvent*>(evt)->keyCode();
823 if (keyCode == '\r') {
825 form()->submitClick(evt);
826 evt->setDefaultHandled();
832 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
834 m_activeSelectionAnchorIndex = index;
836 // Cache the selection state so we can restore the old selection as the new selection pivots around this anchor index
837 const Vector<HTMLElement*>& items = listItems();
838 m_cachedStateForActiveSelection.clear();
839 for (unsigned i = 0; i < items.size(); i++) {
840 if (items[i]->hasLocalName(optionTag)) {
841 HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
842 m_cachedStateForActiveSelection.append(option->selected());
844 m_cachedStateForActiveSelection.append(false);
848 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
850 ASSERT(renderer() && renderer()->isListBox());
854 ASSERT(m_activeSelectionAnchorIndex >= 0);
855 start = min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
856 end = max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
858 const Vector<HTMLElement*>& items = listItems();
859 for (unsigned i = 0; i < items.size(); i++) {
860 if (items[i]->hasLocalName(optionTag)) {
861 HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
862 if (!option->disabled()) {
863 if (i >= start && i <= end)
864 option->setSelectedState(m_activeSelectionState);
865 else if (deselectOtherOptions)
866 option->setSelectedState(false);
868 option->setSelectedState(m_cachedStateForActiveSelection[i]);
876 void HTMLSelectElement::menuListOnChange()
878 ASSERT(usesMenuList());
879 int selected = selectedIndex();
880 if (m_lastOnChangeIndex != selected) {
881 m_lastOnChangeIndex = selected;
886 void HTMLSelectElement::listBoxOnChange()
888 ASSERT(!usesMenuList());
890 const Vector<HTMLElement*>& items = listItems();
892 // If the cached selection list is empty, or the size has changed, then fire onChange, and return early.
893 if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
898 // Update m_lastOnChangeSelection and fire onChange
899 bool fireOnChange = false;
900 for (unsigned i = 0; i < items.size(); i++) {
901 bool selected = false;
902 if (items[i]->hasLocalName(optionTag))
903 selected = static_cast<HTMLOptionElement*>(items[i])->selected();
904 if (selected != m_lastOnChangeSelection[i])
906 m_lastOnChangeSelection[i] = selected;
912 void HTMLSelectElement::saveLastSelection()
914 const Vector<HTMLElement*>& items = listItems();
916 if (usesMenuList()) {
917 m_lastOnChangeIndex = selectedIndex();
921 m_lastOnChangeSelection.clear();
922 for (unsigned i = 0; i < items.size(); i++) {
923 if (items[i]->hasLocalName(optionTag)) {
924 HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
925 m_lastOnChangeSelection.append(option->selected());
927 m_lastOnChangeSelection.append(false);
931 static String stripLeadingWhiteSpace(const String& string)
933 int length = string.length();
935 for (i = 0; i < length; ++i)
936 if (string[i] != noBreakSpace &&
937 (string[i] <= 0x7F ? !isASCIISpace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral)))
940 return string.substring(i, length - i);
943 void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
945 if (event->timeStamp() < m_lastCharTime)
948 DOMTimeStamp delta = event->timeStamp() - m_lastCharTime;
950 m_lastCharTime = event->timeStamp();
952 UChar c = event->charCode();
955 int searchStartOffset = 1;
956 if (delta > typeAheadTimeout) {
957 m_typedString = prefix = String(&c, 1);
960 m_typedString.append(c);
962 if (c == m_repeatingChar)
963 // The user is likely trying to cycle through all the items starting with this character, so just search on the character
964 prefix = String(&c, 1);
967 prefix = m_typedString;
968 searchStartOffset = 0;
972 const Vector<HTMLElement*>& items = listItems();
973 int itemCount = items.size();
977 int index = (optionToListIndex(selectedIndex()) + searchStartOffset) % itemCount;
978 for (int i = 0; i < itemCount; i++, index = (index + 1) % itemCount) {
979 if (!items[index]->hasTagName(optionTag) || items[index]->disabled())
982 if (stripLeadingWhiteSpace(static_cast<HTMLOptionElement*>(items[index])->optionText()).startsWith(prefix, false)) {
983 setSelectedIndex(listToOptionIndex(index));
990 int HTMLSelectElement::nextSelectableListIndex(int startIndex)
992 const Vector<HTMLElement*>& items = listItems();
993 int index = startIndex + 1;
994 while (index >= 0 && (unsigned)index < items.size() && (!items[index]->hasLocalName(optionTag) || items[index]->disabled()))
996 if ((unsigned) index == items.size())
1001 int HTMLSelectElement::previousSelectableListIndex(int startIndex)
1003 const Vector<HTMLElement*>& items = listItems();
1004 if (startIndex == -1)
1005 startIndex = items.size();
1006 int index = startIndex - 1;
1007 while (index >= 0 && (unsigned)index < items.size() && (!items[index]->hasLocalName(optionTag) || items[index]->disabled()))
1014 void HTMLSelectElement::accessKeyAction(bool sendToAnyElement)
1017 dispatchSimulatedClick(0, sendToAnyElement);
1020 void HTMLSelectElement::setMultiple(bool multiple)
1022 setAttribute(multipleAttr, multiple ? "" : 0);
1025 void HTMLSelectElement::setSize(int size)
1027 setAttribute(sizeAttr, String::number(size));
1030 Node* HTMLSelectElement::namedItem(const String &name, bool caseSensitive)
1032 return options()->namedItem(name, caseSensitive);
1035 Node* HTMLSelectElement::item(unsigned index)
1037 return options()->item(index);
1040 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
1043 if (index > INT_MAX)
1045 int diff = index - length();
1046 HTMLElement* before = 0;
1047 // out of array bounds ? first insert empty dummies
1049 setLength(index, ec);
1050 // replace an existing entry ?
1051 } else if (diff < 0) {
1052 before = static_cast<HTMLElement*>(options()->item(index+1));
1055 // finally add the new element
1057 add(option, before, ec);
1058 if (diff >= 0 && option->selected())
1059 setSelectedIndex(index, !m_multiple);
1063 void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
1066 if (newLen > INT_MAX)
1068 int diff = length() - newLen;
1070 if (diff < 0) { // add dummy elements
1072 RefPtr<Element> option = document()->createElement("option", ec);
1075 add(static_cast<HTMLElement*>(option.get()), 0, ec);
1080 else // remove elements
1085 void HTMLSelectElement::scrollToSelection()
1087 if (renderer() && !usesMenuList())
1088 static_cast<RenderListBox*>(renderer())->selectionChanged();
1093 void HTMLSelectElement::checkListItems() const
1095 Vector<HTMLElement*> items = m_listItems;
1096 recalcListItems(false);
1097 ASSERT(items == m_listItems);