void SelectElement::deselectItems(SelectElementData& data, Element* element, Element* excludeElement) { const Vector<Element*>& items = data.listItems(element); for (unsigned i = 0; i < items.size(); ++i) { if (items[i] == excludeElement) continue; if (OptionElement* optionElement = toOptionElement(items[i])) optionElement->setSelectedState(false); } }
void SelectElement::typeAheadFind(SelectElementData& data, Element* element, KeyboardEvent* event) { if (event->timeStamp() < data.lastCharTime()) return; DOMTimeStamp delta = event->timeStamp() - data.lastCharTime(); data.setLastCharTime(event->timeStamp()); UChar c = event->charCode(); String prefix; int searchStartOffset = 1; if (delta > typeAheadTimeout) { prefix = String(&c, 1); data.setTypedString(prefix); data.setRepeatingChar(c); } else { data.typedString().append(c); if (c == data.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 { data.setRepeatingChar(0); prefix = data.typedString(); searchStartOffset = 0; } } const Vector<Element*>& items = data.listItems(element); int itemCount = items.size(); if (itemCount < 1) return; int selected = selectedIndex(data, element); int index = (optionToListIndex(data, element, selected >= 0 ? selected : 0) + searchStartOffset) % itemCount; ASSERT(index >= 0); for (int i = 0; i < itemCount; ++i, index = (index + 1) % itemCount) { OptionElement* optionElement = toOptionElement(items[index]); if (!optionElement || items[index]->disabled()) continue; String text = optionElement->textIndentedToRespectGroupLabel(); if (stripLeadingWhiteSpace(text).startsWith(prefix, false)) { setSelectedIndex(data, element, listToOptionIndex(data, element, index)); if (!data.usesMenuList()) listBoxOnChange(data, element); element->setNeedsStyleRecalc(); return; } } }
void SelectElement::recalcListItems(SelectElementData& data, const Element* element, bool updateSelectedStates) { Vector<Element*>& listItems = data.rawListItems(); listItems.clear(); OptionElement* foundSelected = 0; for (Node* currentNode = element->firstChild(); currentNode;) { if (!currentNode->isElementNode()) { currentNode = currentNode->traverseNextSibling(element); continue; } Element* current = static_cast<Element*>(currentNode); // optgroup tags may not nest. However, both FireFox and IE will // flatten the tree automatically, so we follow suit. // (http://www.w3.org/TR/html401/interact/forms.html#h-17.6) if (isOptionGroupElement(current)) { listItems.append(current); if (current->firstChild()) { currentNode = current->firstChild(); continue; } } if (OptionElement* optionElement = toOptionElement(current)) { listItems.append(current); if (updateSelectedStates) { if (!foundSelected && (data.usesMenuList() || (!data.multiple() && optionElement->selected()))) { foundSelected = optionElement; foundSelected->setSelectedState(true); } else if (foundSelected && !data.multiple() && optionElement->selected()) { foundSelected->setSelectedState(false); foundSelected = optionElement; } } } if (current->hasTagName(HTMLNames::hrTag)) listItems.append(current); // In conforming HTML code, only <optgroup> and <option> will be found // within a <select>. We call traverseNextSibling so that we only step // into those tags that we choose to. For web-compat, we should cope // with the case where odd tags like a <div> have been added but we // handle this because such tags have already been removed from the // <select>'s subtree at this point. currentNode = currentNode->traverseNextSibling(element); } data.setShouldRecalcListItems(false); }
void SelectElement::restoreFormControlState(SelectElementData& data, Element* element, const String& state) { recalcListItems(data, element); const Vector<Element*>& items = data.listItems(element); int length = items.size(); for (int i = 0; i < length; ++i) { if (OptionElement* optionElement = toOptionElement(items[i])) optionElement->setSelectedState(state[i] == 'X'); } element->setNeedsStyleRecalc(); }
void SelectElement::setActiveSelectionAnchorIndex(SelectElementData& data, Element* element, int index) { data.setActiveSelectionAnchorIndex(index); // Cache the selection state so we can restore the old selection as the new selection pivots around this anchor index Vector<bool>& cachedStateForActiveSelection = data.cachedStateForActiveSelection(); cachedStateForActiveSelection.clear(); const Vector<Element*>& items = data.listItems(element); for (unsigned i = 0; i < items.size(); ++i) { OptionElement* optionElement = toOptionElement(items[i]); cachedStateForActiveSelection.append(optionElement && optionElement->selected()); } }
void SelectElement::saveLastSelection(SelectElementData& data, Element* element) { if (data.usesMenuList()) { data.setLastOnChangeIndex(selectedIndex(data, element)); return; } Vector<bool>& lastOnChangeSelection = data.lastOnChangeSelection(); lastOnChangeSelection.clear(); const Vector<Element*>& items = data.listItems(element); for (unsigned i = 0; i < items.size(); ++i) { OptionElement* optionElement = toOptionElement(items[i]); lastOnChangeSelection.append(optionElement && optionElement->selected()); } }
bool SelectElement::saveFormControlState(const SelectElementData& data, const Element* element, String& value) { const Vector<Element*>& items = data.listItems(element); int length = items.size(); // FIXME: Change this code to use the new StringImpl::createUninitialized code path. Vector<char, 1024> characters(length); for (int i = 0; i < length; ++i) { OptionElement* optionElement = toOptionElement(items[i]); bool selected = optionElement && optionElement->selected(); characters[i] = selected ? 'X' : '.'; } value = String(characters.data(), length); return true; }
int SelectElement::selectedIndex(const SelectElementData& data, const Element* element) { unsigned index = 0; // return the number of the first option selected const Vector<Element*>& items = data.listItems(element); for (size_t i = 0; i < items.size(); ++i) { if (OptionElement* optionElement = toOptionElement(items[i])) { if (optionElement->selected()) return index; ++index; } } return -1; }
int SelectElement::lastSelectedListIndex(const SelectElementData& data, const Element* element) { // return the number of the last option selected unsigned index = 0; bool found = false; const Vector<Element*>& items = data.listItems(element); for (size_t i = 0; i < items.size(); ++i) { if (OptionElement* optionElement = toOptionElement(items[i])) { if (optionElement->selected()) { index = i; found = true; } } } return found ? (int) index : -1; }
void SelectElement::accessKeySetSelectedIndex(SelectElementData& data, Element* element, int index) { // first bring into focus the list box if (!element->focused()) element->accessKeyAction(false); // if this index is already selected, unselect. otherwise update the selected index const Vector<Element*>& items = data.listItems(element); int listIndex = optionToListIndex(data, element, index); if (OptionElement* optionElement = (listIndex >= 0 ? toOptionElement(items[listIndex]) : 0)) { if (optionElement->selected()) optionElement->setSelectedState(false); else setSelectedIndex(data, element, index, false, true); } listBoxOnChange(data, element); scrollToSelection(data, element); }
void WMLSelectElement::updateVariables() { WMLPageState* pageState = wmlPageStateForDocument(document()); if (!pageState) return; String name = this->name(); String iname = this->iname(); if (iname.isEmpty() && name.isEmpty()) return; String nameString; String inameString; unsigned optionIndex = 0; const Vector<Element*>& items = m_data.listItems(this); for (unsigned i = 0; i < items.size(); ++i) { OptionElement* optionElement = toOptionElement(items[i]); if (!optionElement) continue; ++optionIndex; if (!optionElement->selected()) continue; if (!nameString.isEmpty()) nameString += ";"; if (!inameString.isEmpty()) inameString += ";"; nameString += optionElement->value(); inameString += String::number(optionIndex); } if (!name.isEmpty()) pageState->storeVariable(name, nameString); if (!iname.isEmpty()) pageState->storeVariable(iname, inameString); }
void WMLSelectElement::initializeVariables() { ASSERT(!m_defaultOptionIndices.isEmpty()); WMLPageState* pageState = wmlPageStateForDocument(document()); if (!pageState) return; const Vector<Element*>& items = m_data.listItems(this); if (items.isEmpty()) return; // Spec: If the 'iname' attribute is specified, then the named variable is set with the default option index. String iname = this->iname(); if (!iname.isEmpty()) pageState->storeVariable(iname, optionIndicesToString()); String name = this->name(); if (name.isEmpty()) return; if (m_data.multiple()) { // Spec: If the 'name' attribute is specified and the select is a multiple-choice element, // then for each index greater than zero, the value of the 'value' attribute on the option // element at the index is added to the name variable. pageState->storeVariable(name, optionIndicesToValueString()); return; } // Spec: If the 'name' attribute is specified and the select is a single-choice element, // then the named variable is set with the value of the 'value' attribute on the option // element at the default option index. unsigned optionIndex = m_defaultOptionIndices.first(); ASSERT(optionIndex >= 1); int listIndex = optionToListIndex(optionIndex - 1); ASSERT(listIndex >= 0); ASSERT(listIndex < (int) items.size()); if (OptionElement* optionElement = toOptionElement(items[listIndex])) pageState->storeVariable(name, optionElement->value()); }
Vector<unsigned> WMLSelectElement::valueStringToOptionIndices(const String& value) const { Vector<unsigned> indices; if (value.isEmpty()) return indices; const Vector<Element*>& items = m_data.listItems(this); if (items.isEmpty()) return indices; Vector<String> indexStrings; value.split(';', indexStrings); unsigned optionIndex = 0; Vector<String>::const_iterator end = indexStrings.end(); for (Vector<String>::const_iterator it = indexStrings.begin(); it != end; ++it) { String value = *(it); // SAMSUNG_WML_FIXES+ // wml/struct/control/select/element/value/3 // reset the optionIndex = 0 for (unsigned i = 0, optionIndex = 0; i < items.size(); ++i) { // SAMSUNG_WML_FIXES- if (!isOptionElement(items[i])) continue; ++optionIndex; if (OptionElement* optionElement = toOptionElement(items[i])) { if (optionElement->value() == value) { indices.append(optionIndex); break; } } } } return indices; }
void SelectElement::setSelectedIndex(SelectElementData& data, Element* element, int optionIndex, bool deselect, bool fireOnChangeNow, bool userDrivenChange) { const Vector<Element*>& items = data.listItems(element); int listIndex = optionToListIndex(data, element, optionIndex); if (!data.multiple()) deselect = true; Element* excludeElement = 0; if (OptionElement* optionElement = (listIndex >= 0 ? toOptionElement(items[listIndex]) : 0)) { excludeElement = items[listIndex]; if (data.activeSelectionAnchorIndex() < 0 || deselect) setActiveSelectionAnchorIndex(data, element, listIndex); if (data.activeSelectionEndIndex() < 0 || deselect) setActiveSelectionEndIndex(data, listIndex); optionElement->setSelectedState(true); } if (deselect) deselectItems(data, element, excludeElement); // For the menu list case, this is what makes the selected element appear. if (RenderObject* renderer = element->renderer()) renderer->updateFromElement(); scrollToSelection(data, element); // This only gets called with fireOnChangeNow for menu lists. if (data.usesMenuList()) { data.setUserDrivenChange(userDrivenChange); if (fireOnChangeNow) menuListOnChange(data, element); } if (Frame* frame = element->document()->frame()) frame->page()->chrome()->client()->formStateDidChange(element); }
void SelectElement::listBoxDefaultEventHandler(SelectElementData& data, Element* element, Event* event, HTMLFormElement* htmlForm) { const Vector<Element*>& listItems = data.listItems(element); if (event->type() == eventNames().mousedownEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton) { element->focus(); // Convert to coords relative to the list box if needed. MouseEvent* mouseEvent = static_cast<MouseEvent*>(event); IntPoint localOffset = roundedIntPoint(element->renderer()->absoluteToLocal(mouseEvent->absoluteLocation(), false, true)); int listIndex = static_cast<RenderListBox*>(element->renderer())->listIndexAtOffset(localOffset.x(), localOffset.y()); if (listIndex >= 0) { // Save the selection so it can be compared to the new selection when dispatching change events during mouseup, or after autoscroll finishes. saveLastSelection(data, element); data.setActiveSelectionState(true); bool multiSelectKeyPressed = false; #if PLATFORM(MAC) multiSelectKeyPressed = mouseEvent->metaKey(); #else multiSelectKeyPressed = mouseEvent->ctrlKey(); #endif bool shiftSelect = data.multiple() && mouseEvent->shiftKey(); bool multiSelect = data.multiple() && multiSelectKeyPressed && !mouseEvent->shiftKey(); Element* clickedElement = listItems[listIndex]; OptionElement* option = toOptionElement(clickedElement); if (option) { // Keep track of whether an active selection (like during drag selection), should select or deselect if (option->selected() && multiSelectKeyPressed) data.setActiveSelectionState(false); if (!data.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(data, element, clickedElement); // 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 (data.activeSelectionAnchorIndex() < 0 && !multiSelect) setActiveSelectionAnchorIndex(data, element, selectedIndex(data, element)); // Set the selection state of the clicked option if (option && !clickedElement->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 && (data.activeSelectionAnchorIndex() < 0 || !shiftSelect)) setActiveSelectionAnchorIndex(data, element, listIndex); setActiveSelectionEndIndex(data, listIndex); updateListBoxSelection(data, element, !multiSelect); if (Frame* frame = element->document()->frame()) frame->eventHandler()->setMouseDownMayStartAutoscroll(); event->setDefaultHandled(); } } else if (event->type() == eventNames().mouseupEvent && event->isMouseEvent() && static_cast<MouseEvent*>(event)->button() == LeftButton && element->document()->frame()->eventHandler()->autoscrollRenderer() != element->renderer()) // This makes sure we fire dispatchFormControlChangeEvent for a single click. For drag selection, onChange will fire when the autoscroll timer stops. listBoxOnChange(data, element); else if (event->type() == eventNames().keydownEvent) { if (!event->isKeyboardEvent()) return; String keyIdentifier = static_cast<KeyboardEvent*>(event)->keyIdentifier(); int endIndex = 0; if (data.activeSelectionEndIndex() < 0) { // Initialize the end index if (keyIdentifier == "Down") endIndex = nextSelectableListIndex(data, element, lastSelectedListIndex(data, element)); else if (keyIdentifier == "Up") endIndex = previousSelectableListIndex(data, element, optionToListIndex(data, element, selectedIndex(data, element))); } else { // Set the end index based on the current end index if (keyIdentifier == "Down") endIndex = nextSelectableListIndex(data, element, data.activeSelectionEndIndex()); else if (keyIdentifier == "Up") endIndex = previousSelectableListIndex(data, element, data.activeSelectionEndIndex()); } if (keyIdentifier == "Down" || keyIdentifier == "Up") { // Save the selection so it can be compared to the new selection when dispatching change events immediately after making the new selection. saveLastSelection(data, element); ASSERT(endIndex >= 0 && (unsigned) endIndex < listItems.size()); setActiveSelectionEndIndex(data, 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 = !data.multiple() || !static_cast<KeyboardEvent*>(event)->shiftKey(); if (data.activeSelectionAnchorIndex() < 0 || deselectOthers) { data.setActiveSelectionState(true); if (deselectOthers) deselectItems(data, element); setActiveSelectionAnchorIndex(data, element, data.activeSelectionEndIndex()); } static_cast<RenderListBox*>(element->renderer())->scrollToRevealElementAtListIndex(endIndex); updateListBoxSelection(data, element, deselectOthers); listBoxOnChange(data, element); event->setDefaultHandled(); } } else if (event->type() == eventNames().keypressEvent) { if (!event->isKeyboardEvent()) return; int keyCode = static_cast<KeyboardEvent*>(event)->keyCode(); if (keyCode == '\r') { if (htmlForm) htmlForm->submitClick(event); event->setDefaultHandled(); return; } } }