+2006-10-31 Adele Peterson <adele@apple.com>
+
+ Reviewed by Adam.
+
+ Tests for http://bugs.webkit.org/show_bug.cgi?id=11127 NativeListBox: arrow and drag selection should pivot around one list item
+ and http://bugs.webkit.org/show_bug.cgi?id=11173 REGRESSION (NativeListBox): Shift-clicking items in list box doesn't expand the current selection
+ and http://bugs.webkit.org/show_bug.cgi?id=11417 REGRESSION: onchange does not fire for list-style select elements
+
+ * fast/forms/listbox-onchange-expected.txt: Added.
+ * fast/forms/listbox-onchange.html: Added.
+ * fast/forms/listbox-selection-expected.txt: Added.
+ * fast/forms/listbox-selection.html: Added.
+
2006-10-31 Justin Garcia <justin.garcia@apple.com>
Reviewed by harrison
--- /dev/null
+EDITING DELEGATE: shouldChangeSelectedDOMRange:(null) toDOMRange:(null) affinity:NSSelectionAffinityDownstream stillSelecting:FALSE
+
+Results:
+1) Make sure onChange fires when clicking
+onChange fired
+2) Make sure onChange doesn't fire when clicking again on the same item
+3) Make sure onChange fires when clicking on a new item
+onChange fired
+4) Make sure onChange fires when changing the selection with the keyboard
+onChange fired
--- /dev/null
+<html>
+ <head>
+ <script>
+ function setup() {
+ var results = document.createElement('div');
+ results.id = "res";
+ results.appendChild(document.createTextNode("Results:"));
+ document.body.appendChild(results);
+ }
+ function test() {
+ setup();
+
+ if (window.layoutTestController) {
+ layoutTestController.dumpAsText();
+ layoutTestController.waitUntilDone();
+ }
+
+ log("1) Make sure onChange fires when clicking");
+ clickOnSelect("sl1", 2);
+
+ log("2) Make sure onChange doesn't fire when clicking again on the same item");
+ clickOnSelect("sl1", 2);
+
+ log("3) Make sure onChange fires when clicking on a new item");
+ clickOnSelect("sl1", 0);
+
+ log("4) Make sure onChange fires when changing the selection with the keyboard");
+ keyPressOnSelect("sl1", "Down", true, false);
+
+ if (window.layoutTestController)
+ layoutTestController.notifyDone();
+
+ }
+
+ function clickOnSelect(selId, index) {
+ var sl = document.getElementById(selId);
+ var itemHeight = 15;
+ var y = 10 + index * itemHeight;
+
+ if (window.layoutTestController) {
+ eventSender.mouseMoveTo(sl.offsetLeft + 10, sl.offsetTop + y);
+ eventSender.mouseDown();
+ eventSender.mouseUp();
+ }
+ }
+
+ function keyPressOnSelect(selId, identifier, shift, meta) {
+ var sl = document.getElementById(selId);
+ var event = document.createEvent("KeyboardEvents");
+ event.initKeyboardEvent("keypress", true, true, document.defaultView, identifier, 0, false, false, shift, meta, false);
+ sl.dispatchEvent(event);
+ }
+
+ function log(msg) {
+ var r = document.getElementById('res');
+ r.innerHTML = r.innerHTML + "<br>" + msg;
+ }
+ </script>
+ </head>
+ <body onload="test()">
+ <select id="sl1" size=5 multiple onchange="log('onChange fired')">
+ <option>item 0
+ <option>item 1
+ <option>item 2
+ <option>item 3
+ </select>
+ </body>
+</html>
--- /dev/null
+ 1) Select one item with mouse (no previous selection)
+ 2) Select one item with mouse (with previous selection)
+ 3) Select one item with the keyboard (no previous selection)
+ 4) Select one item with the keyboard (with previous selection)
+ 5) Attempt to select an item cmd-clicking
+ 6) Attempt to select a range shift-clicking
+ 7) Attempt to select a range with the keyboard
+ 8) Select one item with mouse (no previous selection)
+ 9) Select one item with mouse (with previous selection)
+ 10) Select one item with the keyboard (no previous selection)
+ 11) Select one item with the keyboard (with previous selection)
+ 12) Select an item cmd-clicking
+ 13) Select a range shift-clicking
+ 14) Select a range with the keyboard
+Results:
+Test 1 Passed
+Test 2 Passed
+Test 3 Passed
+Test 4 Passed
+Test 5 Passed
+Test 6 Passed
+Test 7 Passed
+Test 8 Passed
+Test 9 Passed
+Test 10 Passed
+Test 11 Passed
+Test 12 Passed
+Test 13 Passed
+Test 14 Passed
--- /dev/null
+<html>
+ <head>
+ <script>
+ function setup() {
+ createSelect("sl1", 5, false, -1, " 1) Select one item with mouse (no previous selection)");
+ createSelect("sl2", 5, false, 1, " 2) Select one item with mouse (with previous selection)");
+ createSelect("sl3", 5, false, -1, " 3) Select one item with the keyboard (no previous selection)");
+ createSelect("sl4", 5, false, 1, " 4) Select one item with the keyboard (with previous selection)");
+ createSelect("sl5", 5, false, 2, " 5) Attempt to select an item cmd-clicking");
+ createSelect("sl6", 5, false, 3, " 6) Attempt to select a range shift-clicking");
+ createSelect("sl7", 5, false, 1, " 7) Attempt to select a range with the keyboard");
+
+ createSelect("sl8", 5, true, -1, " 8) Select one item with mouse (no previous selection)");
+ createSelect("sl9", 5, true, 1, " 9) Select one item with mouse (with previous selection)");
+ createSelect("sl10", 5, true, -1, " 10) Select one item with the keyboard (no previous selection)");
+ createSelect("sl11", 5, true, 1, " 11) Select one item with the keyboard (with previous selection)");
+ createSelect("sl12", 5, true, 2, " 12) Select an item cmd-clicking");
+ createSelect("sl13", 5, true, 0, " 13) Select a range shift-clicking");
+ createSelect("sl14", 5, true, 1, " 14) Select a range with the keyboard");
+
+ var results = document.createElement('div');
+ results.id = "res";
+ results.appendChild(document.createTextNode("Results:"));
+ document.body.appendChild(results);
+ }
+ function test() {
+ setup();
+
+ if (window.layoutTestController) {
+ layoutTestController.dumpAsText();
+ layoutTestController.waitUntilDone();
+ }
+
+ // 1) Select one item with mouse (no previous selection)
+ mouseDownOnSelect("sl1", 0, false, false);
+ var expectedSelectionResults = new Array(true, false, false, false, false);
+ testResults(expectedSelectionResults, 1);
+
+ // 2) Select one item with mouse (with previous selection)
+ mouseDownOnSelect("sl2", 0, false, false);
+ expectedSelectionResults = new Array(true, false, false, false, false);
+ testResults(expectedSelectionResults, 2);
+
+ // 3) Select one item with the keyboard (no previous selection)
+ keyPressOnSelect("sl3", "Up", false, false);
+ expectedSelectionResults = new Array(false, false, false, false, true);
+ testResults(expectedSelectionResults, 3);
+
+ // 4) Select one item with the keyboard (with previous selection)
+ keyPressOnSelect("sl4", "Down", false, false);
+ expectedSelectionResults = new Array(false, false, true, false, false);
+ testResults(expectedSelectionResults, 4);
+
+ // 5) Attempt to select an item cmd-clicking
+ mouseDownOnSelect("sl5", 1, false, true);
+ expectedSelectionResults = new Array(false, true, false, false, false);
+ testResults(expectedSelectionResults, 5);
+
+ // 6) Attempt to select a range shift-clicking
+ mouseDownOnSelect("sl6", 1, true, false);
+ expectedSelectionResults = new Array(false, true, false, false, false);
+ testResults(expectedSelectionResults, 6);
+
+ // 7) Attempt to select a range with the keyboard
+ keyPressOnSelect("sl7", "Down", true, false);
+ expectedSelectionResults = new Array(false, false, true, false, false);
+ testResults(expectedSelectionResults, 7);
+
+ // Multiple selection tests
+
+ // 8) Select one item with mouse (no previous selection)
+ mouseDownOnSelect("sl8", 0, false, false);
+ expectedSelectionResults = new Array(true, false, false, false, false);
+ testResults(expectedSelectionResults, 8);
+
+ // 9) Select one item with mouse (with previous selection)
+ var sl0 = document.getElementById('sl9');
+ mouseDownOnSelect("sl9", 0, false, false);
+ expectedSelectionResults = new Array(true, false, false, false, false);
+ testResults(expectedSelectionResults, 9);
+
+ // 10) Select one item with the keyboard (no previous selection)
+ keyPressOnSelect("sl10", "Up", false, false);
+ expectedSelectionResults = new Array(false, false, false, false, true);
+ testResults(expectedSelectionResults, 10);
+
+ // 11) Select one item with the keyboard (with previous selection)
+ keyPressOnSelect("sl11", "Down", false, false);
+ expectedSelectionResults = new Array(false, false, true, false, false);
+ testResults(expectedSelectionResults, 11);
+
+ // 12) Select an item cmd-clicking
+ mouseDownOnSelect("sl12", 1, false, true);
+ expectedSelectionResults = new Array(false, true, true, false, false);
+ testResults(expectedSelectionResults, 12);
+
+ // 13) Select a range shift-clicking
+ mouseDownOnSelect("sl13", 3, true, false);
+ expectedSelectionResults = new Array(true, true, true, true, false);
+ testResults(expectedSelectionResults, 13);
+
+ // 14) Select a range with the keyboard
+ keyPressOnSelect("sl14", "Down", true, false);
+ expectedSelectionResults = new Array(false, true, true, false, false);
+ testResults(expectedSelectionResults, 14);
+
+ if (window.layoutTestController)
+ layoutTestController.notifyDone();
+
+ }
+
+ function mouseDownOnSelect(selId, index, shift, meta) {
+ var sl = document.getElementById(selId);
+ var itemHeight = 15;
+ var y = 10 + index * itemHeight - window.pageYOffset;
+ var event = document.createEvent("MouseEvent");
+ event.initMouseEvent("mousedown", true, true, document.defaultView, 1, sl.offsetLeft + 10, sl.offsetTop + y, sl.offsetLeft + 10, sl.offsetTop + y, false, false, shift, meta, 0, document);
+ sl.dispatchEvent(event);
+ }
+
+ function keyPressOnSelect(selId, identifier, shift, meta) {
+ var sl = document.getElementById(selId);
+ sl.focus();
+ var event = document.createEvent("KeyboardEvents");
+ event.initKeyboardEvent("keypress", true, true, document.defaultView, identifier, 0, false, false, shift, meta, false);
+ sl.dispatchEvent(event);
+ }
+
+ function createSelect(idName, sz, mlt, selIndex, testMsg) {
+ var sl = document.createElement("select");
+ var i = 0;
+ while (i < sz) {
+ var opt = document.createElement("option");
+ if (i == selIndex)
+ opt.selected = true;
+ opt.innerText = "item " + i;
+ sl.appendChild(opt);
+ i++;
+ }
+ sl.size = sz;
+ sl.multiple = mlt;
+ sl.id = idName;
+ document.body.appendChild(sl);
+ document.body.appendChild(document.createTextNode(testMsg));
+ document.body.appendChild(document.createElement("br"));
+ }
+
+ function testResults(expectedArr, testNum) {
+ var sl = document.getElementById("sl" + testNum);
+ var res = document.getElementById('res');
+ var resultsArr = new Array(sl.options.length);
+
+ var i;
+ for (i=0; i < sl.options.length; i++) {
+ resultsArr[i] = sl.options[i].selected;
+ }
+ var successString = "Failed";
+ var success = false;
+ if (expectedArr.join() == resultsArr.join()) {
+ success = true;
+ successString = "Passed";
+ }
+
+ log("Test " + testNum + " " + successString);
+ if (!success) {
+ log("<pre> Expected: " + expectedArr.join() + "<br>" + " Actual: " + resultsArr.join() + "</pre>");
+ }
+ }
+
+ function log(msg) {
+ var r = document.getElementById('res');
+ r.innerHTML = r.innerHTML + "<br>" + msg;
+ }
+ </script>
+ </head>
+ <body onload="test()">
+ </body>
+</html>
+2006-10-31 Adele Peterson <adele@apple.com>
+
+ Reviewed by Adam.
+
+ - Fix for http://bugs.webkit.org/show_bug.cgi?id=11127 NativeListBox: arrow and drag selection should pivot around one list item
+ and http://bugs.webkit.org/show_bug.cgi?id=11173 REGRESSION (NativeListBox): Shift-clicking items in list box doesn't expand the current selection
+ and http://bugs.webkit.org/show_bug.cgi?id=11417 REGRESSION: onchange does not fire for list-style select elements
+
+ Tests:
+ * LayoutTests/fast/forms/listbox-selection.html
+ * LayoutTests/fast/forms/listbox-onchange.html
+
+ * html/HTMLSelectElement.h: Added m_selectedListIndexBase and m_selectedListIndexExtent to track indices for the active selection in progress.
+ Added 2 vectors to cache selection state. One is kept so that the previous selection state can be restored as the active selection grows and shrinks.
+ And one for onChange, that is updated after onChange is fired.
+ Added m_activeSelectionState to keep track of whether the current drag selection is selecting or deselecting.
+
+ * html/HTMLSelectElement.cpp:
+ (WebCore::HTMLSelectElement::HTMLSelectElement): Initialized new variables.
+ (WebCore::HTMLSelectElement::setSelectedIndex): If needed, initialize m_selectedListIndexBase and m_selectedListIndexExtent.
+ (WebCore::HTMLSelectElement::dispatchBlurEvent): Only fire the onChange event here for menu lists.
+ (WebCore::HTMLSelectElement::listBoxDefaultEventHandler): Updates base and extent variables for mouse and key events.
+ (WebCore::HTMLSelectElement::setBase): Added. Also caches the selection state.
+ (WebCore::HTMLSelectElement::setExtent): Added.
+ (WebCore::HTMLSelectElement::updateListBoxSelection): Added.
+ (WebCore::HTMLSelectElement::listBoxOnChange): Added.
+
+ * rendering/RenderListBox.cpp:
+ (WebCore::RenderListBox::updateFromElement): Only scroll to reveal the first index if both the first and last indices aren't visible.
+ (WebCore::RenderListBox::listIndexAtOffset): Added. Replaces optionAtPoint, which is no longer used.
+ (WebCore::RenderListBox::autoscroll): Now sets the selection using the select's base and extent.
+ (WebCore::RenderListBox::stopAutoscroll): Added. Tells the select element to fire onChange. This is needed because the autoscroll can end from a mouseUp
+ outside of the list box, and the select element won't get a mouseUp event directly. But the frame will stop the autoscroll at that point, and now we can
+ notify the select element from here.
+ (WebCore::RenderListBox::scrollToRevealElementAtListIndex): Checks new listIndexIsVisible method.
+ (WebCore::RenderListBox::listIndexIsVisible): Added.
+ (WebCore::RenderListBox::valueChanged): Removed unnecessary printf.
+
+ * page/Frame.cpp: (WebCore::Frame::stopAutoscrollTimer): Added rendererIsBeingDestroyed argument, so when the renderer calls this during destruction,
+ we don't try to use the pointer to that renderer to call stopAutoscroll. This is done so a renderer that's still alive has a chance to do some cleanup after autoscroll.
+ * rendering/RenderListBox.h: (WebCore::RenderListBox::shouldAutoscroll): Always returns true now, since we're also updating selection from the autoscroll timer.
+ * rendering/RenderObject.h: (WebCore::RenderObject::stopAutoscroll): Added.
+ * rendering/RenderObject.cpp: (WebCore::RenderObject::destroy): Calls stopAutoscrollTimer with rendererIsBeingDestroyed argument.
+
2006-10-31 Beth Dakin <bdakin@apple.com>
Forgot to check this in a minute ago. Oops!! Thanks Mitz!
, m_multiple(false)
, m_recalcListItems(false)
, m_lastOnChangeIndex(0)
+ , m_activeSelectionAnchorIndex(-1)
+ , m_activeSelectionEndIndex(-1)
+ , m_activeSelectionState(false)
, m_repeatingChar(0)
, m_lastCharTime(0)
, m_typedString(String())
const Vector<HTMLElement*>& items = listItems();
int listIndex = optionToListIndex(optionIndex);
HTMLOptionElement* element = 0;
+
if (listIndex >= 0) {
element = static_cast<HTMLOptionElement*>(items[listIndex]);
element->setSelected(true);
}
+
if (deselect)
deselectItems(element);
- if (fireOnChange && m_lastOnChangeIndex != optionIndex) {
+
+ if (listIndex >= 0) {
+ if (m_activeSelectionAnchorIndex < 0 || deselect)
+ setActiveSelectionAnchorIndex(listIndex);
+ if (m_activeSelectionEndIndex < 0 || deselect)
+ setActiveSelectionEndIndex(listIndex);
+ }
+
+ if (usesMenuList() && fireOnChange && m_lastOnChangeIndex != optionIndex) {
m_lastOnChangeIndex = optionIndex;
onChange();
}
void HTMLSelectElement::dispatchBlurEvent()
{
#if !ARROW_KEYS_POP_MENU
- if (selectedIndex() != m_lastOnChangeIndex) {
+ if (usesMenuList() && selectedIndex() != m_lastOnChangeIndex) {
m_lastOnChangeIndex = selectedIndex();
onChange();
}
{
if (evt->type() == mousedownEvent) {
MouseEvent* mEvt = static_cast<MouseEvent*>(evt);
- if (HTMLOptionElement* element = static_cast<RenderListBox*>(renderer())->optionAtPoint(mEvt->x(), mEvt->y())) {
- bool deselectOtherOptions = true;
- bool shouldSelect = true;
+ int listIndex = static_cast<RenderListBox*>(renderer())->listIndexAtOffset(mEvt->offsetX(), mEvt->offsetY());
+ if (listIndex >= 0) {
+ m_activeSelectionState = true;
bool multiSelectKeyPressed = false;
#if PLATFORM(MAC)
#else
multiSelectKeyPressed = mEvt->ctrlKey();
#endif
- if (multiple() && multiSelectKeyPressed)
- deselectOtherOptions = false;
- if (element->selected() && multiSelectKeyPressed)
- shouldSelect = false;
+
+ bool shiftSelect = multiple() && mEvt->shiftKey();
+ bool multiSelect = multiple() && multiSelectKeyPressed && !mEvt->shiftKey();
- int optionIndex = element->index();
- if (!shouldSelect) {
- optionIndex = -1;
- element->m_selected = false;
+ 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->m_selected = false;
}
- setSelectedIndex(optionIndex, deselectOtherOptions);
+
+ // 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->m_selected = 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);
+ renderer()->repaint();
}
- } else if (evt->type() == keypressEvent) {
+ } else if (evt->type() == mouseupEvent && document()->frame()->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() == keypressEvent) {
if (!evt->isKeyboardEvent())
return;
String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
- int index = 0;
+ int endIndex = 0;
const Vector<HTMLElement*>& items = listItems();
-
- if (keyIdentifier == "Down") {
- index = nextSelectableListIndex(lastSelectedListIndex());
-
- } else if (keyIdentifier == "Up") {
- index = previousSelectableListIndex(optionToListIndex(selectedIndex()));
+
+ 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") {
- ASSERT(index >= 0 && (unsigned)index < items.size());
- HTMLOptionElement* element = static_cast<HTMLOptionElement*>(items[index]);
-
- setSelectedIndex(element->index(), !multiple() || !static_cast<KeyboardEvent*>(evt)->shiftKey());
- static_cast<RenderListBox*>(renderer())->scrollToRevealElementAtListIndex(index);
+ ASSERT(endIndex >= 0 && (unsigned)endIndex < items.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);
evt->setDefaultHandled();
- setChanged();
+ updateListBoxSelection(deselectOthers);
renderer()->repaint();
+
+ listBoxOnChange();
+ }
+ }
+}
+
+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)
+{
+ 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->m_selected = m_activeSelectionState;
+ else if (deselectOtherOptions)
+ option->m_selected = false;
+ else
+ option->m_selected = m_cachedStateForActiveSelection[i];
+ }
}
}
+ if (renderer()->isListBox())
+ static_cast<RenderListBox*>(renderer())->setSelectionChanged(true);
+}
+
+void HTMLSelectElement::listBoxOnChange()
+{
+ const Vector<HTMLElement*>& items = listItems();
+
+ // If the cached selection list is empty, or the size has changed, then rebuild the list, fire onChange, and return early.
+ if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
+ 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);
+ }
+ 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();
}
static String stripLeadingWhiteSpace(const String& string)
int selectedIndex() const;
void setSelectedIndex(int index, bool deselect = true, bool fireOnChange = false);
void notifyOptionSelected(HTMLOptionElement* selectedOption, bool selected);
-
+ int lastSelectedListIndex() const;
+
virtual bool isEnumeratable() const { return true; }
int length() const;
HTMLCollection::CollectionInfo* collectionInfo() { return &m_collectionInfo; }
+ void setActiveSelectionAnchorIndex(int index);
+ void setActiveSelectionEndIndex(int index) { m_activeSelectionEndIndex = index; }
+ void updateListBoxSelection(bool deselectOtherOptions);
+ void listBoxOnChange();
+
private:
void recalcListItems() const;
- void deselectItems(HTMLOptionElement* excludeElement);
+ void deselectItems(HTMLOptionElement* excludeElement = 0);
bool usesMenuList() const { return !m_multiple && m_size <= 1; }
- int lastSelectedListIndex() const;
int nextSelectableListIndex(int startIndex);
int previousSelectableListIndex(int startIndex);
void menuListDefaultEventHandler(Event*);
void typeAheadFind(KeyboardEvent*);
mutable Vector<HTMLElement*> m_listItems;
+ Vector<bool> m_cachedStateForActiveSelection;
+ Vector<bool> m_lastOnChangeSelection;
int m_minwidth;
int m_size;
bool m_multiple;
mutable bool m_recalcListItems;
int m_lastOnChangeIndex;
+
+ int m_activeSelectionAnchorIndex;
+ int m_activeSelectionEndIndex;
+ bool m_activeSelectionState;
// Instance variables for type-ahead find
UChar m_repeatingChar;
d->m_autoscrollTimer.startRepeating(autoscrollInterval);
}
-void Frame::stopAutoscrollTimer()
+void Frame::stopAutoscrollTimer(bool rendererIsBeingDestroyed)
{
+ if (!rendererIsBeingDestroyed && autoscrollRenderer())
+ autoscrollRenderer()->stopAutoscroll();
setAutoscrollRenderer(0);
d->m_autoscrollTimer.stop();
}
void setNeedsReapplyStyles();
- void stopAutoscrollTimer();
+ void stopAutoscrollTimer(bool rendererIsBeingDestroyed = false);
RenderObject* autoscrollRenderer() const;
protected:
setNeedsLayoutAndMinMaxRecalc();
}
- scrollToRevealElementAtListIndex(select->optionToListIndex(select->selectedIndex()));
+ int firstIndex = select->optionToListIndex(select->selectedIndex());
+ int lastIndex = select->lastSelectedListIndex();
+ if (firstIndex >= 0 && !listIndexIsVisible(firstIndex) && !listIndexIsVisible(lastIndex))
+ scrollToRevealElementAtListIndex(firstIndex);
}
void RenderListBox::calcMinMaxWidth()
return false;
}
-HTMLOptionElement* RenderListBox::optionAtPoint(int x, int y)
+int RenderListBox::listIndexAtOffset(int offsetX, int offsetY)
{
HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
const Vector<HTMLElement*>& listItems = select->listItems();
- int yOffset = y - absoluteBoundingBoxRect().y();
- int newOffset = max(0, yOffset / (style()->font().height() + optionsSpacingMiddle)) + m_indexOffset;
+
+ int newOffset = max(0, offsetY / (style()->font().height() + optionsSpacingMiddle)) + m_indexOffset;
newOffset = max(0, min((int)listItems.size() - 1, newOffset));
int scrollbarWidth = m_vBar ? m_vBar->width() : 0;
- if (x >= absoluteBoundingBoxRect().x() + borderLeft() + paddingLeft() && x < absoluteBoundingBoxRect().right() - borderRight() - paddingRight() - scrollbarWidth)
- return static_cast<HTMLOptionElement*>(listItems[newOffset]);
- return 0;
+ if (offsetX >= borderLeft() + paddingLeft() && offsetX < absoluteBoundingBoxRect().width() - borderRight() - paddingRight() - scrollbarWidth)
+ return newOffset;
+
+ return -1;
}
void RenderListBox::autoscroll()
{
IntPoint pos = document()->frame()->view()->windowToContents(document()->frame()->view()->currentMousePosition());
- IntRect bounds = absoluteBoundingBoxRect();
- HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
- const Vector<HTMLElement*>& items = select->listItems();
- HTMLOptionElement* element = 0;
+ int rx = 0;
+ int ry = 0;
+ absolutePosition(rx, ry);
+ int offsetX = pos.x() - rx;
+ int offsetY = pos.y() - ry;
+
+ int endIndex = -1;
int rows = size();
int offset = m_indexOffset;
- if (pos.y() < bounds.y() && scrollToRevealElementAtListIndex(offset - 1) && items[offset - 1]->hasTagName(optionTag))
- element = static_cast<HTMLOptionElement*>(items[offset - 1]);
- else if (pos.y() > bounds.bottom() && scrollToRevealElementAtListIndex(offset + rows) && items[offset + rows - 1]->hasTagName(optionTag))
- element = static_cast<HTMLOptionElement*>(items[offset + rows - 1]);
+ if (offsetY < 0 && scrollToRevealElementAtListIndex(offset - 1))
+ endIndex = offset - 1;
+ else if (offsetY > absoluteBoundingBoxRect().height() && scrollToRevealElementAtListIndex(offset + rows))
+ endIndex = offset + rows - 1;
else
- element = optionAtPoint(pos.x(), pos.y());
-
- if (element) {
- select->setSelectedIndex(element->index(), !select->multiple());
+ endIndex = listIndexAtOffset(offsetX, offsetY);
+
+ HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
+ if (endIndex >= 0 && select) {
+ if (!select->multiple())
+ select->setActiveSelectionAnchorIndex(endIndex);
+ select->setActiveSelectionEndIndex(endIndex);
+ select->updateListBoxSelection(!select->multiple());
repaint();
}
}
+void RenderListBox::stopAutoscroll()
+{
+ if ( HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node()))
+ select->listBoxOnChange();
+}
+
bool RenderListBox::scrollToRevealElementAtListIndex(int index)
{
HTMLSelectElement* select = static_cast<HTMLSelectElement*>(node());
const Vector<HTMLElement*>& listItems = select->listItems();
- if (index < 0 || index > (int)listItems.size() - 1 || (index >= m_indexOffset && index < m_indexOffset + size()))
+ if (index < 0 || index > (int)listItems.size() - 1 || listIndexIsVisible(index))
return false;
int newOffset;
return true;
}
+bool RenderListBox::listIndexIsVisible(int index)
+{
+ return index >= m_indexOffset && index < m_indexOffset + size();
+}
+
bool RenderListBox::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
{
return m_vBar && m_vBar->scroll(direction, granularity, multiplier);
int newOffset = max(0, m_vBar->value() / (style()->font().height() + optionsSpacingMiddle));
if (newOffset != m_indexOffset) {
m_indexOffset = newOffset;
- // printf("value changed: new offset index: %d\n", newOffset);
repaint();
// Fire the scroll DOM event.
EventTargetNodeCast(node())->dispatchHTMLEvent(scrollEvent, true, false);
namespace WebCore {
+class HTMLElement;
class HTMLSelectElement;
class HTMLOptionElement;
virtual void valueChanged(Scrollbar*);
virtual IntRect windowClipRect() const;
- HTMLOptionElement* optionAtPoint(int x, int y);
+ int listIndexAtOffset(int x, int y);
bool scrollToRevealElementAtListIndex(int index);
- virtual bool shouldAutoscroll() const { return numItems() > size(); }
+ virtual bool shouldAutoscroll() const { return true; }
virtual void autoscroll();
+ virtual void stopAutoscroll();
private:
int size() const;
void paintScrollbar(PaintInfo&);
void paintItemForeground(PaintInfo&, int tx, int ty, int listIndex);
void paintItemBackground(PaintInfo&, int tx, int ty, int listIndex);
+ bool listIndexIsVisible(int index);
bool m_optionsChanged;
int m_optionsWidth;
{
// If this renderer is being autoscrolled, stop the autoscroll timer
if (document() && document()->frame() && document()->frame()->autoscrollRenderer() == this)
- document()->frame()->stopAutoscrollTimer();
+ document()->frame()->stopAutoscrollTimer(true);
if (m_hasCounterNodeMap) {
RenderObjectsToCounterNodeMaps* objectsMap = getRenderObjectsToCounterNodeMaps();
virtual bool scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier=1.0);
virtual bool shouldAutoscroll() const;
virtual void autoscroll();
+ virtual void stopAutoscroll() {};
// The following seven functions are used to implement collapsing margins.
// All objects know their maximal positive and negative margins. The