void SelectElement::reset(SelectElementData& data, Element* element) { bool optionSelected = false; OptionElement* firstOption = 0; const Vector<Element*>& items = data.listItems(element); for (unsigned i = 0; i < items.size(); ++i) { OptionElement* optionElement = toOptionElement(items[i]); if (!optionElement) continue; if (!items[i]->getAttribute(HTMLNames::selectedAttr).isNull()) { optionElement->setSelectedState(true); optionSelected = true; } else optionElement->setSelectedState(false); if (!firstOption) firstOption = optionElement; } if (!optionSelected && firstOption && data.usesMenuList()) firstOption->setSelectedState(true); element->setNeedsStyleRecalc(); }
void SelectElement::updateListBoxSelection(SelectElementData& data, Element* element, bool deselectOtherOptions) { ASSERT(element->renderer() && element->renderer()->isListBox()); ASSERT(data.activeSelectionAnchorIndex() >= 0); unsigned start = min(data.activeSelectionAnchorIndex(), data.activeSelectionEndIndex()); unsigned end = max(data.activeSelectionAnchorIndex(), data.activeSelectionEndIndex()); Vector<bool>& cachedStateForActiveSelection = data.cachedStateForActiveSelection(); const Vector<Element*>& items = data.listItems(element); for (unsigned i = 0; i < items.size(); ++i) { OptionElement* optionElement = toOptionElement(items[i]); if (!optionElement || items[i]->disabled()) continue; if (i >= start && i <= end) optionElement->setSelectedState(data.activeSelectionState()); else if (deselectOtherOptions || i >= cachedStateForActiveSelection.size()) optionElement->setSelectedState(false); else optionElement->setSelectedState(cachedStateForActiveSelection[i]); } scrollToSelection(data, element); }
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::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; } } }