summaryrefslogtreecommitdiffstats
path: root/WebCore/html/HTMLSelectElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/html/HTMLSelectElement.cpp')
-rw-r--r--WebCore/html/HTMLSelectElement.cpp1134
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