summaryrefslogtreecommitdiff
path: root/src/3rdparty/webkit/WebCore/html/HTMLSelectElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/3rdparty/webkit/WebCore/html/HTMLSelectElement.cpp')
-rw-r--r--src/3rdparty/webkit/WebCore/html/HTMLSelectElement.cpp881
1 files changed, 94 insertions, 787 deletions
diff --git a/src/3rdparty/webkit/WebCore/html/HTMLSelectElement.cpp b/src/3rdparty/webkit/WebCore/html/HTMLSelectElement.cpp
index f347698180..95038e6244 100644
--- a/src/3rdparty/webkit/WebCore/html/HTMLSelectElement.cpp
+++ b/src/3rdparty/webkit/WebCore/html/HTMLSelectElement.cpp
@@ -2,7 +2,7 @@
* 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.
+ * Copyright (C) 2004, 2005, 2006, 2007, 2009 Apple Inc. All rights reserved.
* (C) 2006 Alexey Proskuryakov (ap@nypop.com)
*
* This library is free software; you can redistribute it and/or
@@ -21,64 +21,31 @@
* Boston, MA 02110-1301, USA.
*
*/
-
+
#include "config.h"
#include "HTMLSelectElement.h"
#include "AXObjectCache.h"
-#include "CSSPropertyNames.h"
-#include "CSSStyleSelector.h"
-#include "CharacterNames.h"
-#include "ChromeClient.h"
-#include "Document.h"
-#include "Event.h"
-#include "EventHandler.h"
#include "EventNames.h"
-#include "FormDataList.h"
-#include "Frame.h"
-#include "FrameLoader.h"
-#include "FrameLoaderClient.h"
-#include "HTMLFormElement.h"
#include "HTMLNames.h"
#include "HTMLOptionElement.h"
#include "HTMLOptionsCollection.h"
-#include "KeyboardEvent.h"
-#include "MouseEvent.h"
-#include "Page.h"
+#include "MappedAttribute.h"
#include "RenderListBox.h"
#include "RenderMenuList.h"
-#include <math.h>
-#include <wtf/StdLibExtras.h>
-#include <wtf/Vector.h>
-
-#if PLATFORM(MAC)
-#define ARROW_KEYS_POP_MENU 1
-#else
-#define ARROW_KEYS_POP_MENU 0
-#endif
+#include "ScriptEventListener.h"
using namespace std;
-using namespace WTF;
-using namespace Unicode;
namespace WebCore {
using namespace HTMLNames;
-static const DOMTimeStamp typeAheadTimeout = 1000;
+// Upper limit agreed upon with representatives of Opera and Mozilla.
+static const unsigned maxSelectItems = 10000;
-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)
+HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* document, HTMLFormElement* form)
+ : HTMLFormControlElementWithState(tagName, document, form)
{
ASSERT(hasTagName(selectTag) || hasTagName(keygenTag));
}
@@ -90,120 +57,52 @@ bool HTMLSelectElement::checkDTD(const Node* newChild)
newChild->hasTagName(scriptTag);
}
-void HTMLSelectElement::recalcStyle( StyleChange ch )
+void HTMLSelectElement::recalcStyle(StyleChange change)
{
- 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);
+ SelectElement::recalcStyle(m_data, this);
+ HTMLFormControlElementWithState::recalcStyle(change);
}
-const AtomicString& HTMLSelectElement::type() const
+const AtomicString& HTMLSelectElement::formControlType() const
{
DEFINE_STATIC_LOCAL(const AtomicString, selectMultiple, ("select-multiple"));
DEFINE_STATIC_LOCAL(const AtomicString, selectOne, ("select-one"));
- return m_multiple ? selectMultiple : selectOne;
+ return m_data.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;
+ return SelectElement::selectedIndex(m_data, this);
}
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);
- }
- }
+ SelectElement::deselectItems(m_data, this, excludeElement);
}
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();
-
- Frame* frame = document()->frame();
- if (frame)
- frame->page()->chrome()->client()->formStateDidChange(this);
+ SelectElement::setSelectedIndex(m_data, this, optionIndex, deselect, fireOnChange);
}
int HTMLSelectElement::activeSelectionStartListIndex() const
{
- if (m_activeSelectionAnchorIndex >= 0)
- return m_activeSelectionAnchorIndex;
+ if (m_data.activeSelectionAnchorIndex() >= 0)
+ return m_data.activeSelectionAnchorIndex();
return optionToListIndex(selectedIndex());
}
int HTMLSelectElement::activeSelectionEndListIndex() const
{
- if (m_activeSelectionEndIndex >= 0)
- return m_activeSelectionEndIndex;
- return lastSelectedListIndex();
+ if (m_data.activeSelectionEndIndex() >= 0)
+ return m_data.activeSelectionEndIndex();
+ return SelectElement::lastSelectedListIndex(m_data, this);
}
unsigned HTMLSelectElement::length() const
{
unsigned len = 0;
- const Vector<HTMLElement*>& items = listItems();
+ const Vector<Element*>& items = listItems();
for (unsigned i = 0; i < items.size(); ++i) {
if (items[i]->hasLocalName(optionTag))
++len;
@@ -218,37 +117,29 @@ void HTMLSelectElement::add(HTMLElement *element, HTMLElement *before, Exception
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);
+ if (listIndex < 0)
+ return;
- 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];
+ Element* item = listItems()[listIndex];
ASSERT(item->parentNode());
+ ExceptionCode ec;
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++) {
+ const Vector<Element*>& items = listItems();
+ for (unsigned 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("");
+ return "";
}
void HTMLSelectElement::setValue(const String &value)
@@ -257,9 +148,9 @@ void HTMLSelectElement::setValue(const String &value)
return;
// find the option with value() matching the given parameter
// and make it the current selection.
- const Vector<HTMLElement*>& items = listItems();
+ const Vector<Element*>& items = listItems();
unsigned optionIndex = 0;
- for (unsigned i = 0; i < items.size(); i++)
+ for (unsigned i = 0; i < items.size(); i++) {
if (items[i]->hasLocalName(optionTag)) {
if (static_cast<HTMLOptionElement*>(items[i])->value() == value) {
setSelectedIndex(optionIndex, true);
@@ -267,80 +158,24 @@ void HTMLSelectElement::setValue(const String &value)
}
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)
+bool HTMLSelectElement::saveFormControlState(String& value) const
{
- 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;
+ return SelectElement::saveFormControlState(m_data, this, value);
}
-bool HTMLSelectElement::replaceChild(PassRefPtr<Node> newChild, Node *oldChild, ExceptionCode& ec, bool shouldLazyAttach)
+void HTMLSelectElement::restoreFormControlState(const String& state)
{
- bool result = HTMLFormControlElementWithState::replaceChild(newChild, oldChild, ec, shouldLazyAttach);
- if (result)
- setRecalcListItems();
- return result;
+ SelectElement::restoreFormControlState(m_data, this, state);
}
-bool HTMLSelectElement::removeChild(Node* oldChild, ExceptionCode& ec)
+void HTMLSelectElement::parseMappedAttribute(MappedAttribute* attr)
{
- 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();
+ bool oldUsesMenuList = m_data.usesMenuList();
if (attr->name() == sizeAttr) {
- int oldSize = m_size;
+ int oldSize = m_data.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();
@@ -348,31 +183,25 @@ void HTMLSelectElement::parseMappedAttribute(MappedAttribute *attr)
if (attrSize != attr->value())
attr->setValue(attrSize);
- m_size = max(size, 1);
- if ((oldUsesMenuList != usesMenuList() || !oldUsesMenuList && m_size != oldSize) && attached()) {
+ m_data.setSize(max(size, 1));
+ if ((oldUsesMenuList != m_data.usesMenuList() || (!oldUsesMenuList && m_data.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) {
+ } else if (attr->name() == multipleAttr)
+ SelectElement::parseMultipleAttribute(m_data, this, attr);
+ 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);
+ setAttributeEventListener(eventNames().focusEvent, createAttributeEventListener(this, attr));
} else if (attr->name() == onblurAttr) {
- setInlineEventListenerForTypeAndAttribute(eventNames().blurEvent, attr);
+ setAttributeEventListener(eventNames().blurEvent, createAttributeEventListener(this, attr));
} else if (attr->name() == onchangeAttr) {
- setInlineEventListenerForTypeAndAttribute(eventNames().changeEvent, attr);
+ setAttributeEventListener(eventNames().changeEvent, createAttributeEventListener(this, attr));
} else
HTMLFormControlElementWithState::parseMappedAttribute(attr);
}
@@ -393,95 +222,34 @@ bool HTMLSelectElement::isMouseFocusable() const
bool HTMLSelectElement::canSelectAll() const
{
- return !usesMenuList();
+ return !m_data.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();
+ SelectElement::selectAll(m_data, this);
}
RenderObject* HTMLSelectElement::createRenderer(RenderArena* arena, RenderStyle*)
{
- if (usesMenuList())
+ if (m_data.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;
+ return SelectElement::appendFormData(m_data, this, list);
}
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;
+ return SelectElement::optionToListIndex(m_data, this, optionIndex);
}
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;
+ return SelectElement::listToOptionIndex(m_data, this, listIndex);
}
PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
@@ -491,37 +259,7 @@ PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
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;
+ SelectElement::recalcListItems(const_cast<SelectElementData&>(m_data), this, updateSelectedStates);
}
void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
@@ -535,482 +273,65 @@ void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange
void HTMLSelectElement::setRecalcListItems()
{
- m_recalcListItems = true;
- if (renderer()) {
- if (usesMenuList())
- static_cast<RenderMenuList*>(renderer())->setOptionsChanged(true);
- else
- static_cast<RenderListBox*>(renderer())->setOptionsChanged(true);
- }
+ SelectElement::setRecalcListItems(m_data, this);
+
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();
+ SelectElement::reset(m_data, this);
}
void HTMLSelectElement::dispatchFocusEvent()
{
- if (usesMenuList())
- // Save the selection so it can be compared to the new selection when we call onChange during dispatchBlurEvent.
- saveLastSelection();
+ SelectElement::dispatchFocusEvent(m_data, this);
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();
+ SelectElement::dispatchBlurEvent(m_data, this);
HTMLFormControlElementWithState::dispatchBlurEvent();
}
-void HTMLSelectElement::defaultEventHandler(Event* evt)
+void HTMLSelectElement::defaultEventHandler(Event* event)
{
- if (!renderer())
+ SelectElement::defaultEventHandler(m_data, this, event, form());
+ if (event->defaultHandled())
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);
+ HTMLFormControlElementWithState::defaultEventHandler(event);
}
-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;
- }
-#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)
+void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
{
- 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;
- }
- }
+ SelectElement::setActiveSelectionAnchorIndex(m_data, this, index);
}
-void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
+void HTMLSelectElement::setActiveSelectionEndIndex(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);
- }
+ SelectElement::setActiveSelectionEndIndex(m_data, index);
}
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 || i >= m_cachedStateForActiveSelection.size())
- option->setSelectedState(false);
- else
- option->setSelectedState(m_cachedStateForActiveSelection[i]);
- }
- }
- }
-
- scrollToSelection();
+ SelectElement::updateListBoxSelection(m_data, this, deselectOtherOptions);
}
void HTMLSelectElement::menuListOnChange()
{
- ASSERT(usesMenuList());
- int selected = selectedIndex();
- if (m_lastOnChangeIndex != selected) {
- m_lastOnChangeIndex = selected;
- onChange();
- }
+ SelectElement::menuListOnChange(m_data, this);
}
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();
+ SelectElement::listBoxOnChange(m_data, this);
}
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;
+ SelectElement::saveLastSelection(m_data, this);
}
void HTMLSelectElement::accessKeyAction(bool sendToAnyElement)
@@ -1020,23 +341,8 @@ void HTMLSelectElement::accessKeyAction(bool 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();
+{
+ SelectElement::accessKeySetSelectedIndex(m_data, this, index);
}
void HTMLSelectElement::setMultiple(bool multiple)
@@ -1062,8 +368,8 @@ Node* HTMLSelectElement::item(unsigned index)
void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
{
ec = 0;
- if (index > INT_MAX)
- index = INT_MAX;
+ if (index > maxSelectItems - 1)
+ index = maxSelectItems - 1;
int diff = index - length();
HTMLElement* before = 0;
// out of array bounds ? first insert empty dummies
@@ -1078,47 +384,48 @@ void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, Exc
if (!ec) {
add(option, before, ec);
if (diff >= 0 && option->selected())
- setSelectedIndex(index, !m_multiple);
+ setSelectedIndex(index, !m_data.multiple());
}
}
void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
{
ec = 0;
- if (newLen > INT_MAX)
- newLen = INT_MAX;
+ if (newLen > maxSelectItems)
+ newLen = maxSelectItems;
int diff = length() - newLen;
if (diff < 0) { // add dummy elements
do {
- RefPtr<Element> option = document()->createElement("option", ec);
- if (!option)
- break;
+ RefPtr<Element> option = document()->createElement(optionTag, false);
+ ASSERT(option);
add(static_cast<HTMLElement*>(option.get()), 0, ec);
if (ec)
break;
} while (++diff);
+ } else {
+ const Vector<Element*>& items = listItems();
+
+ size_t optionIndex = 0;
+ for (size_t listIndex = 0; listIndex < items.size(); listIndex++) {
+ if (items[listIndex]->hasLocalName(optionTag) && optionIndex++ >= newLen) {
+ Element *item = items[listIndex];
+ ASSERT(item->parentNode());
+ item->parentNode()->removeChild(item, ec);
+ }
+ }
}
- else // remove elements
- while (diff-- > 0)
- remove(newLen);
}
void HTMLSelectElement::scrollToSelection()
{
- if (renderer() && !usesMenuList())
- static_cast<RenderListBox*>(renderer())->selectionChanged();
+ SelectElement::scrollToSelection(m_data, this);
}
-#ifndef NDEBUG
-
-void HTMLSelectElement::checkListItems() const
+void HTMLSelectElement::insertedIntoTree(bool deep)
{
- Vector<HTMLElement*> items = m_listItems;
- recalcListItems(false);
- ASSERT(items == m_listItems);
+ SelectElement::insertedIntoTree(m_data, this);
+ HTMLFormControlElementWithState::insertedIntoTree(deep);
}
-#endif
-
} // namespace