diff options
Diffstat (limited to 'WebCore/html/HTMLSelectElement.cpp')
-rw-r--r-- | WebCore/html/HTMLSelectElement.cpp | 1134 |
1 files changed, 0 insertions, 1134 deletions
diff --git a/WebCore/html/HTMLSelectElement.cpp b/WebCore/html/HTMLSelectElement.cpp deleted file mode 100644 index e95bfd3..0000000 --- a/WebCore/html/HTMLSelectElement.cpp +++ /dev/null @@ -1,1134 +0,0 @@ -/* - * Copyright (C) 1999 Lars Knoll (knoll@kde.org) - * (C) 1999 Antti Koivisto (koivisto@kde.org) - * (C) 2001 Dirk Mueller (mueller@kde.org) - * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. - * (C) 2006 Alexey Proskuryakov (ap@nypop.com) - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public License - * along with this library; see the file COPYING.LIB. If not, write to - * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - * - */ - -#include "config.h" -#include "HTMLSelectElement.h" - -#include "AXObjectCache.h" -#include "CSSPropertyNames.h" -#include "CSSStyleSelector.h" -#include "CharacterNames.h" -#include "Document.h" -#include "Event.h" -#include "EventHandler.h" -#include "EventNames.h" -#include "FormDataList.h" -#include "Frame.h" -#include "HTMLFormElement.h" -#include "HTMLNames.h" -#include "HTMLOptionElement.h" -#include "HTMLOptionsCollection.h" -#include "KeyboardEvent.h" -#include "MouseEvent.h" -#include "RenderListBox.h" -#include "RenderMenuList.h" -#include <math.h> -#include <wtf/Vector.h> - -#if PLATFORM(MAC) -#define ARROW_KEYS_POP_MENU 1 -#else -#define ARROW_KEYS_POP_MENU 0 -#endif - -using namespace std; -using namespace WTF; -using namespace Unicode; - -namespace WebCore { - -using namespace HTMLNames; - -static const DOMTimeStamp typeAheadTimeout = 1000; - -HTMLSelectElement::HTMLSelectElement(Document* doc, HTMLFormElement* f) - : HTMLFormControlElementWithState(selectTag, doc, f) - , m_minwidth(0) - , m_size(0) - , m_multiple(false) - , m_recalcListItems(false) - , m_lastOnChangeIndex(-1) - , m_activeSelectionAnchorIndex(-1) - , m_activeSelectionEndIndex(-1) - , m_activeSelectionState(false) - , m_repeatingChar(0) - , m_lastCharTime(0) -{ -} - -HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* f) - : HTMLFormControlElementWithState(tagName, doc, f) - , m_minwidth(0) - , m_size(0) - , m_multiple(false) - , m_recalcListItems(false) - , m_lastOnChangeIndex(-1) - , m_activeSelectionAnchorIndex(-1) - , m_activeSelectionEndIndex(-1) - , m_activeSelectionState(false) - , m_repeatingChar(0) - , m_lastCharTime(0) -{ -} - -bool HTMLSelectElement::checkDTD(const Node* newChild) -{ - // Make sure to keep <optgroup> in sync with this. - return newChild->isTextNode() || newChild->hasTagName(optionTag) || newChild->hasTagName(optgroupTag) || newChild->hasTagName(hrTag) || - newChild->hasTagName(scriptTag); -} - -void HTMLSelectElement::recalcStyle( StyleChange ch ) -{ - if (hasChangedChild() && renderer()) { - if (usesMenuList()) - static_cast<RenderMenuList*>(renderer())->setOptionsChanged(true); - else - static_cast<RenderListBox*>(renderer())->setOptionsChanged(true); - } else if (m_recalcListItems) - recalcListItems(); - - HTMLFormControlElementWithState::recalcStyle(ch); -} - -const AtomicString& HTMLSelectElement::type() const -{ - static const AtomicString selectMultiple("select-multiple"); - static const AtomicString selectOne("select-one"); - return m_multiple ? selectMultiple : selectOne; -} - -int HTMLSelectElement::selectedIndex() const -{ - // return the number of the first option selected - unsigned index = 0; - const Vector<HTMLElement*>& items = listItems(); - for (unsigned int i = 0; i < items.size(); i++) { - if (items[i]->hasLocalName(optionTag)) { - if (static_cast<HTMLOptionElement*>(items[i])->selected()) - return index; - index++; - } - } - return -1; -} - -int HTMLSelectElement::lastSelectedListIndex() const -{ - // return the number of the last option selected - unsigned index = 0; - bool found = false; - const Vector<HTMLElement*>& items = listItems(); - for (unsigned int i = 0; i < items.size(); i++) { - if (items[i]->hasLocalName(optionTag)) { - if (static_cast<HTMLOptionElement*>(items[i])->selected()) { - index = i; - found = true; - } - } - } - return found ? (int) index : -1; -} - -void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement) -{ - const Vector<HTMLElement*>& items = listItems(); - unsigned i; - for (i = 0; i < items.size(); i++) { - if (items[i]->hasLocalName(optionTag) && (items[i] != excludeElement)) { - HTMLOptionElement* element = static_cast<HTMLOptionElement*>(items[i]); - element->setSelectedState(false); - } - } -} - -void HTMLSelectElement::setSelectedIndex(int optionIndex, bool deselect, bool fireOnChange) -{ - const Vector<HTMLElement*>& items = listItems(); - int listIndex = optionToListIndex(optionIndex); - HTMLOptionElement* element = 0; - - if (!multiple()) - deselect = true; - - if (listIndex >= 0) { - if (m_activeSelectionAnchorIndex < 0 || deselect) - setActiveSelectionAnchorIndex(listIndex); - if (m_activeSelectionEndIndex < 0 || deselect) - setActiveSelectionEndIndex(listIndex); - element = static_cast<HTMLOptionElement*>(items[listIndex]); - element->setSelectedState(true); - } - - if (deselect) - deselectItems(element); - - scrollToSelection(); - - // This only gets called with fireOnChange for menu lists. - if (fireOnChange && usesMenuList()) - menuListOnChange(); -} - -int HTMLSelectElement::activeSelectionStartListIndex() const -{ - if (m_activeSelectionAnchorIndex >= 0) - return m_activeSelectionAnchorIndex; - return optionToListIndex(selectedIndex()); -} - -int HTMLSelectElement::activeSelectionEndListIndex() const -{ - if (m_activeSelectionEndIndex >= 0) - return m_activeSelectionEndIndex; - return lastSelectedListIndex(); -} - -unsigned HTMLSelectElement::length() const -{ - unsigned len = 0; - const Vector<HTMLElement*>& items = listItems(); - for (unsigned i = 0; i < items.size(); ++i) { - if (items[i]->hasLocalName(optionTag)) - ++len; - } - return len; -} - -void HTMLSelectElement::add(HTMLElement *element, HTMLElement *before, ExceptionCode& ec) -{ - RefPtr<HTMLElement> protectNewChild(element); // make sure the element is ref'd and deref'd so we don't leak it - - if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag))) - return; - - ec = 0; - insertBefore(element, before, ec); - if (!ec) - setRecalcListItems(); -} - -void HTMLSelectElement::remove(int index) -{ - ExceptionCode ec = 0; - int listIndex = optionToListIndex(index); - - const Vector<HTMLElement*>& items = listItems(); - if (listIndex < 0 || index >= int(items.size())) - return; // ### what should we do ? remove the last item? - - Element *item = items[listIndex]; - ASSERT(item->parentNode()); - item->parentNode()->removeChild(item, ec); - if (!ec) - setRecalcListItems(); -} - -String HTMLSelectElement::value() -{ - unsigned i; - const Vector<HTMLElement*>& items = listItems(); - for (i = 0; i < items.size(); i++) { - if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected()) - return static_cast<HTMLOptionElement*>(items[i])->value(); - } - return String(""); -} - -void HTMLSelectElement::setValue(const String &value) -{ - if (value.isNull()) - return; - // find the option with value() matching the given parameter - // and make it the current selection. - const Vector<HTMLElement*>& items = listItems(); - unsigned optionIndex = 0; - for (unsigned i = 0; i < items.size(); i++) - if (items[i]->hasLocalName(optionTag)) { - if (static_cast<HTMLOptionElement*>(items[i])->value() == value) { - setSelectedIndex(optionIndex, true); - return; - } - optionIndex++; - } -} - -bool HTMLSelectElement::saveState(String& value) const -{ - const Vector<HTMLElement*>& items = listItems(); - int l = items.size(); - Vector<char, 1024> characters(l); - for (int i = 0; i < l; ++i) { - HTMLElement* e = items[i]; - bool selected = e->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(e)->selected(); - characters[i] = selected ? 'X' : '.'; - } - value = String(characters.data(), l); - return true; -} - -void HTMLSelectElement::restoreState(const String& state) -{ - recalcListItems(); - - const Vector<HTMLElement*>& items = listItems(); - int l = items.size(); - for (int i = 0; i < l; i++) - if (items[i]->hasLocalName(optionTag)) - static_cast<HTMLOptionElement*>(items[i])->setSelectedState(state[i] == 'X'); - - setChanged(); -} - -bool HTMLSelectElement::insertBefore(PassRefPtr<Node> newChild, Node* refChild, ExceptionCode& ec, bool shouldLazyAttach) -{ - bool result = HTMLFormControlElementWithState::insertBefore(newChild, refChild, ec, shouldLazyAttach); - if (result) - setRecalcListItems(); - return result; -} - -bool HTMLSelectElement::replaceChild(PassRefPtr<Node> newChild, Node *oldChild, ExceptionCode& ec, bool shouldLazyAttach) -{ - bool result = HTMLFormControlElementWithState::replaceChild(newChild, oldChild, ec, shouldLazyAttach); - if (result) - setRecalcListItems(); - return result; -} - -bool HTMLSelectElement::removeChild(Node* oldChild, ExceptionCode& ec) -{ - bool result = HTMLFormControlElementWithState::removeChild(oldChild, ec); - if (result) - setRecalcListItems(); - return result; -} - -bool HTMLSelectElement::appendChild(PassRefPtr<Node> newChild, ExceptionCode& ec, bool shouldLazyAttach) -{ - bool result = HTMLFormControlElementWithState::appendChild(newChild, ec, shouldLazyAttach); - if (result) - setRecalcListItems(); - return result; -} - -bool HTMLSelectElement::removeChildren() -{ - bool result = HTMLFormControlElementWithState::removeChildren(); - if (result) - setRecalcListItems(); - return result; -} - -void HTMLSelectElement::parseMappedAttribute(MappedAttribute *attr) -{ - bool oldUsesMenuList = usesMenuList(); - if (attr->name() == sizeAttr) { - int oldSize = m_size; - // Set the attribute value to a number. - // This is important since the style rules for this attribute can determine the appearance property. - int size = attr->value().toInt(); - String attrSize = String::number(size); - if (attrSize != attr->value()) - attr->setValue(attrSize); - - m_size = max(size, 1); - if ((oldUsesMenuList != usesMenuList() || !oldUsesMenuList && m_size != oldSize) && attached()) { - detach(); - attach(); - setRecalcListItems(); - } - } else if (attr->name() == widthAttr) { - m_minwidth = max(attr->value().toInt(), 0); - } else if (attr->name() == multipleAttr) { - m_multiple = (!attr->isNull()); - if (oldUsesMenuList != usesMenuList() && attached()) { - detach(); - attach(); - } - } else if (attr->name() == accesskeyAttr) { - // FIXME: ignore for the moment - } else if (attr->name() == alignAttr) { - // Don't map 'align' attribute. This matches what Firefox, Opera and IE do. - // See http://bugs.webkit.org/show_bug.cgi?id=12072 - } else if (attr->name() == onfocusAttr) { - setInlineEventListenerForTypeAndAttribute(eventNames().focusEvent, attr); - } else if (attr->name() == onblurAttr) { - setInlineEventListenerForTypeAndAttribute(eventNames().blurEvent, attr); - } else if (attr->name() == onchangeAttr) { - setInlineEventListenerForTypeAndAttribute(eventNames().changeEvent, attr); - } else - HTMLFormControlElementWithState::parseMappedAttribute(attr); -} - -bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const -{ - if (renderer()) - return isFocusable(); - return HTMLFormControlElementWithState::isKeyboardFocusable(event); -} - -bool HTMLSelectElement::isMouseFocusable() const -{ - if (renderer()) - return isFocusable(); - return HTMLFormControlElementWithState::isMouseFocusable(); -} - -bool HTMLSelectElement::canSelectAll() const -{ - return !usesMenuList(); -} - -void HTMLSelectElement::selectAll() -{ - ASSERT(!usesMenuList()); - if (!renderer() || !multiple()) - return; - - // Save the selection so it can be compared to the new selectAll selection when we call onChange - saveLastSelection(); - - m_activeSelectionState = true; - setActiveSelectionAnchorIndex(nextSelectableListIndex(-1)); - setActiveSelectionEndIndex(previousSelectableListIndex(-1)); - - updateListBoxSelection(false); - listBoxOnChange(); -} - -RenderObject *HTMLSelectElement::createRenderer(RenderArena *arena, RenderStyle *style) -{ - if (usesMenuList()) - return new (arena) RenderMenuList(this); - return new (arena) RenderListBox(this); -} - -bool HTMLSelectElement::appendFormData(FormDataList& list, bool) -{ - bool successful = false; - const Vector<HTMLElement*>& items = listItems(); - - unsigned i; - for (i = 0; i < items.size(); i++) { - if (items[i]->hasLocalName(optionTag)) { - HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]); - if (option->selected()) { - list.appendData(name(), option->value()); - successful = true; - } - } - } - - // ### this case should not happen. make sure that we select the first option - // in any case. otherwise we have no consistency with the DOM interface. FIXME! - // we return the first one if it was a combobox select - if (!successful && !m_multiple && m_size <= 1 && items.size() && - (items[0]->hasLocalName(optionTag))) { - HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[0]); - if (option->value().isNull()) - list.appendData(name(), option->text().stripWhiteSpace()); - else - list.appendData(name(), option->value()); - successful = true; - } - - return successful; -} - -int HTMLSelectElement::optionToListIndex(int optionIndex) const -{ - const Vector<HTMLElement*>& items = listItems(); - int listSize = (int)items.size(); - if (optionIndex < 0 || optionIndex >= listSize) - return -1; - - int optionIndex2 = -1; - for (int listIndex = 0; listIndex < listSize; listIndex++) { - if (items[listIndex]->hasLocalName(optionTag)) { - optionIndex2++; - if (optionIndex2 == optionIndex) - return listIndex; - } - } - return -1; -} - -int HTMLSelectElement::listToOptionIndex(int listIndex) const -{ - const Vector<HTMLElement*>& items = listItems(); - if (listIndex < 0 || listIndex >= int(items.size()) || - !items[listIndex]->hasLocalName(optionTag)) - return -1; - - int optionIndex = 0; // actual index of option not counting OPTGROUP entries that may be in list - for (int i = 0; i < listIndex; i++) - if (items[i]->hasLocalName(optionTag)) - optionIndex++; - return optionIndex; -} - -PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options() -{ - return HTMLOptionsCollection::create(this); -} - -void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const -{ - m_listItems.clear(); - HTMLOptionElement* foundSelected = 0; - for (Node* current = firstChild(); current; current = current->traverseNextSibling(this)) { - if (current->hasTagName(optgroupTag) && current->firstChild()) { - // FIXME: It doesn't make sense to add an optgroup to the list items, - // when it has children, but not to add it if it happens to have, - // children (say some comment nodes or text nodes), yet that's what - // this code does! - m_listItems.append(static_cast<HTMLElement*>(current)); - current = current->firstChild(); - // FIXME: It doesn't make sense to handle an <optgroup> inside another <optgroup> - // if it's not the first child, but not handle it if it happens to be the first - // child, yet that's what this code does! - } - - if (current->hasTagName(optionTag)) { - m_listItems.append(static_cast<HTMLElement*>(current)); - if (updateSelectedStates) { - if (!foundSelected && (usesMenuList() || (!m_multiple && static_cast<HTMLOptionElement*>(current)->selected()))) { - foundSelected = static_cast<HTMLOptionElement*>(current); - foundSelected->setSelectedState(true); - } else if (foundSelected && !m_multiple && static_cast<HTMLOptionElement*>(current)->selected()) { - foundSelected->setSelectedState(false); - foundSelected = static_cast<HTMLOptionElement*>(current); - } - } - } - if (current->hasTagName(hrTag)) - m_listItems.append(static_cast<HTMLElement*>(current)); - } - m_recalcListItems = false; -} - -void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta) -{ - setRecalcListItems(); - HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta); - - if (AXObjectCache::accessibilityEnabled() && renderer()) - renderer()->document()->axObjectCache()->childrenChanged(renderer()); -} - -void HTMLSelectElement::setRecalcListItems() -{ - m_recalcListItems = true; - if (renderer()) { - if (usesMenuList()) - static_cast<RenderMenuList*>(renderer())->setOptionsChanged(true); - else - static_cast<RenderListBox*>(renderer())->setOptionsChanged(true); - } - if (!inDocument()) - m_collectionInfo.reset(); - setChanged(); -} - -void HTMLSelectElement::reset() -{ - bool optionSelected = false; - HTMLOptionElement* firstOption = 0; - const Vector<HTMLElement*>& items = listItems(); - unsigned i; - for (i = 0; i < items.size(); i++) { - if (items[i]->hasLocalName(optionTag)) { - HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]); - if (!option->getAttribute(selectedAttr).isNull()) { - option->setSelectedState(true); - optionSelected = true; - } else - option->setSelectedState(false); - if (!firstOption) - firstOption = option; - } - } - if (!optionSelected && firstOption && usesMenuList()) - firstOption->setSelectedState(true); - - setChanged(); -} - -void HTMLSelectElement::dispatchFocusEvent() -{ - if (usesMenuList()) - // Save the selection so it can be compared to the new selection when we call onChange during dispatchBlurEvent. - saveLastSelection(); - HTMLFormControlElementWithState::dispatchFocusEvent(); -} - -void HTMLSelectElement::dispatchBlurEvent() -{ - // We only need to fire onChange here for menu lists, because we fire onChange for list boxes whenever the selection change is actually made. - // This matches other browsers' behavior. - if (usesMenuList()) - menuListOnChange(); - HTMLFormControlElementWithState::dispatchBlurEvent(); -} - -void HTMLSelectElement::defaultEventHandler(Event* evt) -{ - if (!renderer()) - return; - - if (usesMenuList()) - menuListDefaultEventHandler(evt); - else - listBoxDefaultEventHandler(evt); - - if (evt->defaultHandled()) - return; - - if (evt->type() == eventNames().keypressEvent && evt->isKeyboardEvent()) { - KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(evt); - - if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() && - isPrintableChar(keyboardEvent->charCode())) { - typeAheadFind(keyboardEvent); - evt->setDefaultHandled(); - return; - } - } - - HTMLFormControlElementWithState::defaultEventHandler(evt); -} - -void HTMLSelectElement::menuListDefaultEventHandler(Event* evt) -{ - RenderMenuList* menuList = static_cast<RenderMenuList*>(renderer()); - - if (evt->type() == eventNames().keydownEvent) { - if (!renderer() || !evt->isKeyboardEvent()) - return; - String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier(); - bool handled = false; -#if ARROW_KEYS_POP_MENU - if (keyIdentifier == "Down" || keyIdentifier == "Up") { - focus(); - // Save the selection so it can be compared to the new selection when we call onChange during setSelectedIndex, - // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu. - saveLastSelection(); - menuList->showPopup(); - handled = true; - } -#elif defined ANDROID_KEYBOARD_NAVIGATION - if ("Enter" == keyIdentifier && usesMenuList()) { - menuList->showPopup(); - handled = true; - } -#else - int listIndex = optionToListIndex(selectedIndex()); - if (keyIdentifier == "Down" || keyIdentifier == "Right") { - int size = listItems().size(); - for (listIndex += 1; - listIndex >= 0 && listIndex < size && (listItems()[listIndex]->disabled() || !listItems()[listIndex]->hasTagName(optionTag)); - ++listIndex) { } - - if (listIndex >= 0 && listIndex < size) - setSelectedIndex(listToOptionIndex(listIndex)); - handled = true; - } else if (keyIdentifier == "Up" || keyIdentifier == "Left") { - int size = listItems().size(); - for (listIndex -= 1; - listIndex >= 0 && listIndex < size && (listItems()[listIndex]->disabled() || !listItems()[listIndex]->hasTagName(optionTag)); - --listIndex) { } - - if (listIndex >= 0 && listIndex < size) - setSelectedIndex(listToOptionIndex(listIndex)); - handled = true; - } -#endif - if (handled) - evt->setDefaultHandled(); - } - - // Use key press event here since sending simulated mouse events - // on key down blocks the proper sending of the key press event. - if (evt->type() == eventNames().keypressEvent) { - if (!renderer() || !evt->isKeyboardEvent()) - return; - int keyCode = static_cast<KeyboardEvent*>(evt)->keyCode(); - bool handled = false; -#if ARROW_KEYS_POP_MENU - if (keyCode == ' ') { - focus(); - // Save the selection so it can be compared to the new selection when we call onChange during setSelectedIndex, - // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu. - saveLastSelection(); - menuList->showPopup(); - handled = true; - } - if (keyCode == '\r') { - menuListOnChange(); - if (form()) - form()->submitClick(evt); - handled = true; - } -#else - int listIndex = optionToListIndex(selectedIndex()); - if (keyCode == '\r') { - // listIndex should already be selected, but this will fire the onchange handler. - setSelectedIndex(listToOptionIndex(listIndex), true, true); - handled = true; - } -#endif - if (handled) - evt->setDefaultHandled(); - } - - if (evt->type() == eventNames().mousedownEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton) { - focus(); - if (menuList->popupIsVisible()) - menuList->hidePopup(); - else { - // Save the selection so it can be compared to the new selection when we call onChange during setSelectedIndex, - // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu. - saveLastSelection(); - menuList->showPopup(); - } - evt->setDefaultHandled(); - } -} - -void HTMLSelectElement::listBoxDefaultEventHandler(Event* evt) -{ - if (evt->type() == eventNames().mousedownEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton) { - focus(); - - MouseEvent* mEvt = static_cast<MouseEvent*>(evt); - int listIndex = static_cast<RenderListBox*>(renderer())->listIndexAtOffset(mEvt->offsetX(), mEvt->offsetY()); - if (listIndex >= 0) { - // Save the selection so it can be compared to the new selection when we call onChange during mouseup, or after autoscroll finishes. - saveLastSelection(); - - m_activeSelectionState = true; - - bool multiSelectKeyPressed = false; -#if PLATFORM(MAC) - multiSelectKeyPressed = mEvt->metaKey(); -#else - multiSelectKeyPressed = mEvt->ctrlKey(); -#endif - - bool shiftSelect = multiple() && mEvt->shiftKey(); - bool multiSelect = multiple() && multiSelectKeyPressed && !mEvt->shiftKey(); - - HTMLElement* clickedElement = listItems()[listIndex]; - HTMLOptionElement* option = 0; - if (clickedElement->hasLocalName(optionTag)) { - option = static_cast<HTMLOptionElement*>(clickedElement); - - // Keep track of whether an active selection (like during drag selection), should select or deselect - if (option->selected() && multiSelectKeyPressed) - m_activeSelectionState = false; - - if (!m_activeSelectionState) - option->setSelectedState(false); - } - - // If we're not in any special multiple selection mode, then deselect all other items, excluding the clicked option. - // If no option was clicked, then this will deselect all items in the list. - if (!shiftSelect && !multiSelect) - deselectItems(option); - - // 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. - if (m_activeSelectionAnchorIndex < 0 && !multiSelect) - setActiveSelectionAnchorIndex(selectedIndex()); - - // Set the selection state of the clicked option - if (option && !option->disabled()) - option->setSelectedState(true); - - // If there was no selectedIndex() for the previous initialization, or - // 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. - if (listIndex >= 0 && (m_activeSelectionAnchorIndex < 0 || !shiftSelect)) - setActiveSelectionAnchorIndex(listIndex); - - setActiveSelectionEndIndex(listIndex); - updateListBoxSelection(!multiSelect); - - if (Frame* frame = document()->frame()) - frame->eventHandler()->setMouseDownMayStartAutoscroll(); - - evt->setDefaultHandled(); - } - } else if (evt->type() == eventNames().mouseupEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton && document()->frame()->eventHandler()->autoscrollRenderer() != renderer()) - // This makes sure we fire onChange for a single click. For drag selection, onChange will fire when the autoscroll timer stops. - listBoxOnChange(); - else if (evt->type() == eventNames().keydownEvent) { - if (!evt->isKeyboardEvent()) - return; - String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier(); - - int endIndex = 0; - if (m_activeSelectionEndIndex < 0) { - // Initialize the end index - if (keyIdentifier == "Down") - endIndex = nextSelectableListIndex(lastSelectedListIndex()); - else if (keyIdentifier == "Up") - endIndex = previousSelectableListIndex(optionToListIndex(selectedIndex())); - } else { - // Set the end index based on the current end index - if (keyIdentifier == "Down") - endIndex = nextSelectableListIndex(m_activeSelectionEndIndex); - else if (keyIdentifier == "Up") - endIndex = previousSelectableListIndex(m_activeSelectionEndIndex); - } - - if (keyIdentifier == "Down" || keyIdentifier == "Up") { - // Save the selection so it can be compared to the new selection when we call onChange immediately after making the new selection. - saveLastSelection(); - - ASSERT(endIndex >= 0 && (unsigned)endIndex < listItems().size()); - setActiveSelectionEndIndex(endIndex); - - // 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. - bool deselectOthers = !multiple() || !static_cast<KeyboardEvent*>(evt)->shiftKey(); - if (m_activeSelectionAnchorIndex < 0 || deselectOthers) { - m_activeSelectionState = true; - if (deselectOthers) - deselectItems(); - setActiveSelectionAnchorIndex(m_activeSelectionEndIndex); - } - - static_cast<RenderListBox*>(renderer())->scrollToRevealElementAtListIndex(endIndex); - updateListBoxSelection(deselectOthers); - listBoxOnChange(); - evt->setDefaultHandled(); - } - } else if (evt->type() == eventNames().keypressEvent) { - if (!evt->isKeyboardEvent()) - return; - int keyCode = static_cast<KeyboardEvent*>(evt)->keyCode(); - - if (keyCode == '\r') { - if (form()) - form()->submitClick(evt); - evt->setDefaultHandled(); - return; - } - } -} - -void HTMLSelectElement::setActiveSelectionAnchorIndex(int index) -{ - m_activeSelectionAnchorIndex = index; - - // Cache the selection state so we can restore the old selection as the new selection pivots around this anchor index - const Vector<HTMLElement*>& items = listItems(); - m_cachedStateForActiveSelection.clear(); - for (unsigned i = 0; i < items.size(); i++) { - if (items[i]->hasLocalName(optionTag)) { - HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]); - m_cachedStateForActiveSelection.append(option->selected()); - } else - m_cachedStateForActiveSelection.append(false); - } -} - -void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions) -{ - ASSERT(renderer() && renderer()->isListBox()); - - unsigned start; - unsigned end; - ASSERT(m_activeSelectionAnchorIndex >= 0); - start = min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex); - end = max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex); - - const Vector<HTMLElement*>& items = listItems(); - for (unsigned i = 0; i < items.size(); i++) { - if (items[i]->hasLocalName(optionTag)) { - HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]); - if (!option->disabled()) { - if (i >= start && i <= end) - option->setSelectedState(m_activeSelectionState); - else if (deselectOtherOptions) - option->setSelectedState(false); - else - option->setSelectedState(m_cachedStateForActiveSelection[i]); - } - } - } - - scrollToSelection(); -} - -void HTMLSelectElement::menuListOnChange() -{ - ASSERT(usesMenuList()); - int selected = selectedIndex(); - if (m_lastOnChangeIndex != selected) { - m_lastOnChangeIndex = selected; - onChange(); - } -} - -void HTMLSelectElement::listBoxOnChange() -{ - ASSERT(!usesMenuList()); - - const Vector<HTMLElement*>& items = listItems(); - - // If the cached selection list is empty, or the size has changed, then fire onChange, and return early. - if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) { - onChange(); - return; - } - - // Update m_lastOnChangeSelection and fire onChange - bool fireOnChange = false; - for (unsigned i = 0; i < items.size(); i++) { - bool selected = false; - if (items[i]->hasLocalName(optionTag)) - selected = static_cast<HTMLOptionElement*>(items[i])->selected(); - if (selected != m_lastOnChangeSelection[i]) - fireOnChange = true; - m_lastOnChangeSelection[i] = selected; - } - if (fireOnChange) - onChange(); -} - -void HTMLSelectElement::saveLastSelection() -{ - const Vector<HTMLElement*>& items = listItems(); - - if (usesMenuList()) { - m_lastOnChangeIndex = selectedIndex(); - return; - } - - m_lastOnChangeSelection.clear(); - for (unsigned i = 0; i < items.size(); i++) { - if (items[i]->hasLocalName(optionTag)) { - HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]); - m_lastOnChangeSelection.append(option->selected()); - } else - m_lastOnChangeSelection.append(false); - } -} - -static String stripLeadingWhiteSpace(const String& string) -{ - int length = string.length(); - int i; - for (i = 0; i < length; ++i) - if (string[i] != noBreakSpace && - (string[i] <= 0x7F ? !isASCIISpace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral))) - break; - - return string.substring(i, length - i); -} - -void HTMLSelectElement::typeAheadFind(KeyboardEvent* event) -{ - if (event->timeStamp() < m_lastCharTime) - return; - - DOMTimeStamp delta = event->timeStamp() - m_lastCharTime; - - m_lastCharTime = event->timeStamp(); - - UChar c = event->charCode(); - - String prefix; - int searchStartOffset = 1; - if (delta > typeAheadTimeout) { - m_typedString = prefix = String(&c, 1); - m_repeatingChar = c; - } else { - m_typedString.append(c); - - if (c == m_repeatingChar) - // The user is likely trying to cycle through all the items starting with this character, so just search on the character - prefix = String(&c, 1); - else { - m_repeatingChar = 0; - prefix = m_typedString; - searchStartOffset = 0; - } - } - - const Vector<HTMLElement*>& items = listItems(); - int itemCount = items.size(); - if (itemCount < 1) - return; - - int selected = selectedIndex(); - int index = (optionToListIndex(selected >= 0 ? selected : 0) + searchStartOffset) % itemCount; - ASSERT(index >= 0); - for (int i = 0; i < itemCount; i++, index = (index + 1) % itemCount) { - if (!items[index]->hasTagName(optionTag) || items[index]->disabled()) - continue; - - if (stripLeadingWhiteSpace(static_cast<HTMLOptionElement*>(items[index])->optionText()).startsWith(prefix, false)) { - setSelectedIndex(listToOptionIndex(index)); - if(!usesMenuList()) - listBoxOnChange(); - setChanged(); - return; - } - } -} - -int HTMLSelectElement::nextSelectableListIndex(int startIndex) -{ - const Vector<HTMLElement*>& items = listItems(); - int index = startIndex + 1; - while (index >= 0 && (unsigned)index < items.size() && (!items[index]->hasLocalName(optionTag) || items[index]->disabled())) - index++; - if ((unsigned) index == items.size()) - return startIndex; - return index; -} - -int HTMLSelectElement::previousSelectableListIndex(int startIndex) -{ - const Vector<HTMLElement*>& items = listItems(); - if (startIndex == -1) - startIndex = items.size(); - int index = startIndex - 1; - while (index >= 0 && (unsigned)index < items.size() && (!items[index]->hasLocalName(optionTag) || items[index]->disabled())) - index--; - if (index == -1) - return startIndex; - return index; -} - -void HTMLSelectElement::accessKeyAction(bool sendToAnyElement) -{ - focus(); - dispatchSimulatedClick(0, sendToAnyElement); -} - -void HTMLSelectElement::accessKeySetSelectedIndex(int index) -{ - // first bring into focus the list box - if (!focused()) - accessKeyAction(false); - - // if this index is already selected, unselect. otherwise update the selected index - Node* listNode = item(index); - if (listNode && listNode->hasTagName(optionTag)) { - HTMLOptionElement* listElement = static_cast<HTMLOptionElement*>(listNode); - if (listElement->selected()) - listElement->setSelectedState(false); - else - setSelectedIndex(index, false, true); - } - - listBoxOnChange(); - scrollToSelection(); -} - -void HTMLSelectElement::setMultiple(bool multiple) -{ - setAttribute(multipleAttr, multiple ? "" : 0); -} - -void HTMLSelectElement::setSize(int size) -{ - setAttribute(sizeAttr, String::number(size)); -} - -Node* HTMLSelectElement::namedItem(const String &name, bool caseSensitive) -{ - return options()->namedItem(name, caseSensitive); -} - -Node* HTMLSelectElement::item(unsigned index) -{ - return options()->item(index); -} - -void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec) -{ - ec = 0; - if (index > INT_MAX) - index = INT_MAX; - int diff = index - length(); - HTMLElement* before = 0; - // out of array bounds ? first insert empty dummies - if (diff > 0) { - setLength(index, ec); - // replace an existing entry ? - } else if (diff < 0) { - before = static_cast<HTMLElement*>(options()->item(index+1)); - remove(index); - } - // finally add the new element - if (!ec) { - add(option, before, ec); - if (diff >= 0 && option->selected()) - setSelectedIndex(index, !m_multiple); - } -} - -void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec) -{ - ec = 0; - if (newLen > INT_MAX) - newLen = INT_MAX; - int diff = length() - newLen; - - if (diff < 0) { // add dummy elements - do { - RefPtr<Element> option = document()->createElement("option", ec); - if (!option) - break; - add(static_cast<HTMLElement*>(option.get()), 0, ec); - if (ec) - break; - } while (++diff); - } - else // remove elements - while (diff-- > 0) - remove(newLen); -} - -void HTMLSelectElement::scrollToSelection() -{ - if (renderer() && !usesMenuList()) - static_cast<RenderListBox*>(renderer())->selectionChanged(); -} - -#ifndef NDEBUG - -void HTMLSelectElement::checkListItems() const -{ - Vector<HTMLElement*> items = m_listItems; - recalcListItems(false); - ASSERT(items == m_listItems); -} - -#endif - -} // namespace |