2008-05-14 Julien Chaffraix <jchaffraix@webkit.org>
[WebKit-https.git] / WebCore / html / HTMLSelectElement.cpp
1 /*
2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4  *           (C) 2001 Dirk Mueller (mueller@kde.org)
5  * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
6  *           (C) 2006 Alexey Proskuryakov (ap@nypop.com)
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  *
23  */
24  
25 #include "config.h"
26 #include "HTMLSelectElement.h"
27
28 #include "AXObjectCache.h"
29 #include "CSSPropertyNames.h"
30 #include "CSSStyleSelector.h"
31 #include "CharacterNames.h"
32 #include "Document.h"
33 #include "Event.h"
34 #include "EventHandler.h"
35 #include "EventNames.h"
36 #include "FormDataList.h"
37 #include "Frame.h"
38 #include "HTMLFormElement.h"
39 #include "HTMLNames.h"
40 #include "HTMLOptionElement.h"
41 #include "HTMLOptionsCollection.h"
42 #include "KeyboardEvent.h"
43 #include "MouseEvent.h"
44 #include "RenderListBox.h"
45 #include "RenderMenuList.h"
46 #include <math.h>
47 #include <wtf/Vector.h>
48
49 #if PLATFORM(MAC)
50 #define ARROW_KEYS_POP_MENU 1
51 #else
52 #define ARROW_KEYS_POP_MENU 0
53 #endif
54
55 using namespace std;
56 using namespace WTF;
57 using namespace Unicode;
58
59 namespace WebCore {
60
61 using namespace EventNames;
62 using namespace HTMLNames;
63
64 static const DOMTimeStamp typeAheadTimeout = 1000;
65
66 HTMLSelectElement::HTMLSelectElement(Document* doc, HTMLFormElement* f)
67     : HTMLFormControlElementWithState(selectTag, doc, f)
68     , m_minwidth(0)
69     , m_size(0)
70     , m_multiple(false)
71     , m_recalcListItems(false)
72     , m_lastOnChangeIndex(-1)
73     , m_activeSelectionAnchorIndex(-1)
74     , m_activeSelectionEndIndex(-1)
75     , m_activeSelectionState(false)
76     , m_repeatingChar(0)
77     , m_lastCharTime(0)
78 {
79 }
80
81 HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* f)
82     : HTMLFormControlElementWithState(tagName, doc, f)
83     , m_minwidth(0)
84     , m_size(0)
85     , m_multiple(false)
86     , m_recalcListItems(false)
87     , m_lastOnChangeIndex(-1)
88     , m_activeSelectionAnchorIndex(-1)
89     , m_activeSelectionEndIndex(-1)
90     , m_activeSelectionState(false)
91     , m_repeatingChar(0)
92     , m_lastCharTime(0)
93 {
94 }
95
96 bool HTMLSelectElement::checkDTD(const Node* newChild)
97 {
98     // Make sure to keep <optgroup> in sync with this.
99     return newChild->isTextNode() || newChild->hasTagName(optionTag) || newChild->hasTagName(optgroupTag) || newChild->hasTagName(hrTag) ||
100            newChild->hasTagName(scriptTag);
101 }
102
103 void HTMLSelectElement::recalcStyle( StyleChange ch )
104 {
105     if (hasChangedChild() && renderer()) {
106         if (usesMenuList())
107             static_cast<RenderMenuList*>(renderer())->setOptionsChanged(true);
108         else
109             static_cast<RenderListBox*>(renderer())->setOptionsChanged(true);
110     } else if (m_recalcListItems)
111         recalcListItems();
112
113     HTMLFormControlElementWithState::recalcStyle(ch);
114 }
115
116 const AtomicString& HTMLSelectElement::type() const
117 {
118     static const AtomicString selectMultiple("select-multiple");
119     static const AtomicString selectOne("select-one");
120     return m_multiple ? selectMultiple : selectOne;
121 }
122
123 int HTMLSelectElement::selectedIndex() const
124 {
125     // return the number of the first option selected
126     unsigned index = 0;
127     const Vector<HTMLElement*>& items = listItems();
128     for (unsigned int i = 0; i < items.size(); i++) {
129         if (items[i]->hasLocalName(optionTag)) {
130             if (static_cast<HTMLOptionElement*>(items[i])->selected())
131                 return index;
132             index++;
133         }
134     }
135     return -1;
136 }
137
138 int HTMLSelectElement::lastSelectedListIndex() const
139 {
140     // return the number of the last option selected
141     unsigned index = 0;
142     bool found = false;
143     const Vector<HTMLElement*>& items = listItems();
144     for (unsigned int i = 0; i < items.size(); i++) {
145         if (items[i]->hasLocalName(optionTag)) {
146             if (static_cast<HTMLOptionElement*>(items[i])->selected()) {
147                 index = i;
148                 found = true;
149             }
150         }
151     }
152     return found ? (int) index : -1;
153 }
154
155 void HTMLSelectElement::deselectItems(HTMLOptionElement* excludeElement)
156 {
157     const Vector<HTMLElement*>& items = listItems();
158     unsigned i;
159     for (i = 0; i < items.size(); i++) {
160         if (items[i]->hasLocalName(optionTag) && (items[i] != excludeElement)) {
161             HTMLOptionElement* element = static_cast<HTMLOptionElement*>(items[i]);
162             element->setSelectedState(false);
163         }
164     }
165 }
166
167 void HTMLSelectElement::setSelectedIndex(int optionIndex, bool deselect, bool fireOnChange)
168 {
169     const Vector<HTMLElement*>& items = listItems();
170     int listIndex = optionToListIndex(optionIndex);
171     HTMLOptionElement* element = 0;
172
173     if (!multiple())
174         deselect = true;
175
176     if (listIndex >= 0) {
177         if (m_activeSelectionAnchorIndex < 0 || deselect)
178             setActiveSelectionAnchorIndex(listIndex);
179         if (m_activeSelectionEndIndex < 0 || deselect)
180             setActiveSelectionEndIndex(listIndex);
181         element = static_cast<HTMLOptionElement*>(items[listIndex]);
182         element->setSelectedState(true);
183     }
184
185     if (deselect)
186         deselectItems(element);
187
188     scrollToSelection();
189
190     // This only gets called with fireOnChange for menu lists. 
191     if (fireOnChange && usesMenuList())
192         menuListOnChange();
193 }
194
195 int HTMLSelectElement::activeSelectionStartListIndex() const
196 {
197     if (m_activeSelectionAnchorIndex >= 0)
198         return m_activeSelectionAnchorIndex;
199     return optionToListIndex(selectedIndex());
200 }
201
202 int HTMLSelectElement::activeSelectionEndListIndex() const
203 {
204     if (m_activeSelectionEndIndex >= 0)
205         return m_activeSelectionEndIndex;
206     return lastSelectedListIndex();
207 }
208
209 unsigned HTMLSelectElement::length() const
210 {
211     unsigned len = 0;
212     const Vector<HTMLElement*>& items = listItems();
213     for (unsigned i = 0; i < items.size(); ++i) {
214         if (items[i]->hasLocalName(optionTag))
215             ++len;
216     }
217     return len;
218 }
219
220 void HTMLSelectElement::add(HTMLElement *element, HTMLElement *before, ExceptionCode& ec)
221 {
222     RefPtr<HTMLElement> protectNewChild(element); // make sure the element is ref'd and deref'd so we don't leak it
223
224     if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
225         return;
226
227     ec = 0;
228     insertBefore(element, before, ec);
229     if (!ec)
230         setRecalcListItems();
231 }
232
233 void HTMLSelectElement::remove(int index)
234 {
235     ExceptionCode ec = 0;
236     int listIndex = optionToListIndex(index);
237
238     const Vector<HTMLElement*>& items = listItems();
239     if (listIndex < 0 || index >= int(items.size()))
240         return; // ### what should we do ? remove the last item?
241
242     Element *item = items[listIndex];
243     ASSERT(item->parentNode());
244     item->parentNode()->removeChild(item, ec);
245     if (!ec)
246         setRecalcListItems();
247 }
248
249 String HTMLSelectElement::value()
250 {
251     unsigned i;
252     const Vector<HTMLElement*>& items = listItems();
253     for (i = 0; i < items.size(); i++) {
254         if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected())
255             return static_cast<HTMLOptionElement*>(items[i])->value();
256     }
257     return String("");
258 }
259
260 void HTMLSelectElement::setValue(const String &value)
261 {
262     if (value.isNull())
263         return;
264     // find the option with value() matching the given parameter
265     // and make it the current selection.
266     const Vector<HTMLElement*>& items = listItems();
267     unsigned optionIndex = 0;
268     for (unsigned i = 0; i < items.size(); i++)
269         if (items[i]->hasLocalName(optionTag)) {
270             if (static_cast<HTMLOptionElement*>(items[i])->value() == value) {
271                 setSelectedIndex(optionIndex, true);
272                 return;
273             }
274             optionIndex++;
275         }
276 }
277
278 bool HTMLSelectElement::saveState(String& value) const
279 {
280     const Vector<HTMLElement*>& items = listItems();
281     int l = items.size();
282     Vector<char, 1024> characters(l);
283     for (int i = 0; i < l; ++i) {
284         HTMLElement* e = items[i];
285         bool selected = e->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(e)->selected();
286         characters[i] = selected ? 'X' : '.';
287     }
288     value = String(characters.data(), l);
289     return true;
290 }
291
292 void HTMLSelectElement::restoreState(const String& state)
293 {
294     recalcListItems();
295     
296     const Vector<HTMLElement*>& items = listItems();
297     int l = items.size();
298     for (int i = 0; i < l; i++)
299         if (items[i]->hasLocalName(optionTag))
300             static_cast<HTMLOptionElement*>(items[i])->setSelectedState(state[i] == 'X');
301             
302     setChanged();
303 }
304
305 bool HTMLSelectElement::insertBefore(PassRefPtr<Node> newChild, Node* refChild, ExceptionCode& ec)
306 {
307     bool result = HTMLFormControlElementWithState::insertBefore(newChild, refChild, ec);
308     if (result)
309         setRecalcListItems();
310     return result;
311 }
312
313 bool HTMLSelectElement::replaceChild(PassRefPtr<Node> newChild, Node *oldChild, ExceptionCode& ec)
314 {
315     bool result = HTMLFormControlElementWithState::replaceChild(newChild, oldChild, ec);
316     if (result)
317         setRecalcListItems();
318     return result;
319 }
320
321 bool HTMLSelectElement::removeChild(Node* oldChild, ExceptionCode& ec)
322 {
323     bool result = HTMLFormControlElementWithState::removeChild(oldChild, ec);
324     if (result)
325         setRecalcListItems();
326     return result;
327 }
328
329 bool HTMLSelectElement::appendChild(PassRefPtr<Node> newChild, ExceptionCode& ec)
330 {
331     bool result = HTMLFormControlElementWithState::appendChild(newChild, ec);
332     if (result)
333         setRecalcListItems();
334     return result;
335 }
336
337 bool HTMLSelectElement::removeChildren()
338 {
339     bool result = HTMLFormControlElementWithState::removeChildren();
340     if (result)
341         setRecalcListItems();
342     return result;
343 }
344
345 void HTMLSelectElement::parseMappedAttribute(MappedAttribute *attr)
346 {
347     bool oldUsesMenuList = usesMenuList();
348     if (attr->name() == sizeAttr) {
349         int oldSize = m_size;
350         // Set the attribute value to a number.
351         // This is important since the style rules for this attribute can determine the appearance property.
352         int size = attr->value().toInt();
353         String attrSize = String::number(size);
354         if (attrSize != attr->value())
355             attr->setValue(attrSize);
356
357         m_size = max(size, 1);
358         if ((oldUsesMenuList != usesMenuList() || !oldUsesMenuList && m_size != oldSize) && attached()) {
359             detach();
360             attach();
361             setRecalcListItems();
362         }
363     } else if (attr->name() == widthAttr) {
364         m_minwidth = max(attr->value().toInt(), 0);
365     } else if (attr->name() == multipleAttr) {
366         m_multiple = (!attr->isNull());
367         if (oldUsesMenuList != usesMenuList() && attached()) {
368             detach();
369             attach();
370         }
371     } else if (attr->name() == accesskeyAttr) {
372         // FIXME: ignore for the moment
373     } else if (attr->name() == alignAttr) {
374         // Don't map 'align' attribute.  This matches what Firefox, Opera and IE do.
375         // See http://bugs.webkit.org/show_bug.cgi?id=12072
376     } else if (attr->name() == onfocusAttr) {
377         setHTMLEventListener(focusEvent, attr);
378     } else if (attr->name() == onblurAttr) {
379         setHTMLEventListener(blurEvent, attr);
380     } else if (attr->name() == onchangeAttr) {
381         setHTMLEventListener(changeEvent, attr);
382     } else
383         HTMLFormControlElementWithState::parseMappedAttribute(attr);
384 }
385
386 bool HTMLSelectElement::isKeyboardFocusable(KeyboardEvent* event) const
387 {
388     if (renderer())
389         return isFocusable();
390     return HTMLFormControlElementWithState::isKeyboardFocusable(event);
391 }
392
393 bool HTMLSelectElement::isMouseFocusable() const
394 {
395     if (renderer())
396         return isFocusable();
397     return HTMLFormControlElementWithState::isMouseFocusable();
398 }
399
400 bool HTMLSelectElement::canSelectAll() const
401 {
402     return !usesMenuList(); 
403 }
404
405 void HTMLSelectElement::selectAll()
406 {
407     ASSERT(!usesMenuList());
408     if (!renderer() || !multiple())
409         return;
410     
411     // Save the selection so it can be compared to the new selectAll selection when we call onChange
412     saveLastSelection();
413     
414     m_activeSelectionState = true;
415     setActiveSelectionAnchorIndex(nextSelectableListIndex(-1));
416     setActiveSelectionEndIndex(previousSelectableListIndex(-1));
417     
418     updateListBoxSelection(false);
419     listBoxOnChange();
420 }
421
422 RenderObject *HTMLSelectElement::createRenderer(RenderArena *arena, RenderStyle *style)
423 {
424     if (usesMenuList())
425         return new (arena) RenderMenuList(this);
426     return new (arena) RenderListBox(this);
427 }
428
429 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
430 {
431     bool successful = false;
432     const Vector<HTMLElement*>& items = listItems();
433
434     unsigned i;
435     for (i = 0; i < items.size(); i++) {
436         if (items[i]->hasLocalName(optionTag)) {
437             HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
438             if (option->selected()) {
439                 list.appendData(name(), option->value());
440                 successful = true;
441             }
442         }
443     }
444
445     // ### this case should not happen. make sure that we select the first option
446     // in any case. otherwise we have no consistency with the DOM interface. FIXME!
447     // we return the first one if it was a combobox select
448     if (!successful && !m_multiple && m_size <= 1 && items.size() &&
449         (items[0]->hasLocalName(optionTag))) {
450         HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[0]);
451         if (option->value().isNull())
452             list.appendData(name(), option->text().stripWhiteSpace());
453         else
454             list.appendData(name(), option->value());
455         successful = true;
456     }
457
458     return successful;
459 }
460
461 int HTMLSelectElement::optionToListIndex(int optionIndex) const
462 {
463     const Vector<HTMLElement*>& items = listItems();
464     int listSize = (int)items.size();
465     if (optionIndex < 0 || optionIndex >= listSize)
466         return -1;
467
468     int optionIndex2 = -1;
469     for (int listIndex = 0; listIndex < listSize; listIndex++) {
470         if (items[listIndex]->hasLocalName(optionTag)) {
471             optionIndex2++;
472             if (optionIndex2 == optionIndex)
473                 return listIndex;
474         }
475     }
476     return -1;
477 }
478
479 int HTMLSelectElement::listToOptionIndex(int listIndex) const
480 {
481     const Vector<HTMLElement*>& items = listItems();
482     if (listIndex < 0 || listIndex >= int(items.size()) ||
483         !items[listIndex]->hasLocalName(optionTag))
484         return -1;
485
486     int optionIndex = 0; // actual index of option not counting OPTGROUP entries that may be in list
487     for (int i = 0; i < listIndex; i++)
488         if (items[i]->hasLocalName(optionTag))
489             optionIndex++;
490     return optionIndex;
491 }
492
493 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
494 {
495     return new HTMLOptionsCollection(this);
496 }
497
498 void HTMLSelectElement::recalcListItems(bool updateSelectedStates) const
499 {
500     m_listItems.clear();
501     HTMLOptionElement* foundSelected = 0;
502     for (Node* current = firstChild(); current; current = current->traverseNextSibling(this)) {
503         if (current->hasTagName(optgroupTag) && current->firstChild()) {
504             // FIXME: It doesn't make sense to add an optgroup to the list items,
505             // when it has children, but not to add it if it happens to have,
506             // children (say some comment nodes or text nodes), yet that's what
507             // this code does!
508             m_listItems.append(static_cast<HTMLElement*>(current));
509             current = current->firstChild();
510             // FIXME: It doesn't make sense to handle an <optgroup> inside another <optgroup>
511             // if it's not the first child, but not handle it if it happens to be the first
512             // child, yet that's what this code does!
513         }
514
515         if (current->hasTagName(optionTag)) {
516             m_listItems.append(static_cast<HTMLElement*>(current));
517             if (updateSelectedStates) {
518                 if (!foundSelected && (usesMenuList() || (!m_multiple && static_cast<HTMLOptionElement*>(current)->selected()))) {
519                     foundSelected = static_cast<HTMLOptionElement*>(current);
520                     foundSelected->setSelectedState(true);
521                 } else if (foundSelected && !m_multiple && static_cast<HTMLOptionElement*>(current)->selected()) {
522                     foundSelected->setSelectedState(false);
523                     foundSelected = static_cast<HTMLOptionElement*>(current);
524                 }
525             }
526         }
527         if (current->hasTagName(hrTag))
528             m_listItems.append(static_cast<HTMLElement*>(current));
529     }
530     m_recalcListItems = false;
531 }
532
533 void HTMLSelectElement::childrenChanged(bool changedByParser, Node* beforeChange, Node* afterChange, int childCountDelta)
534 {
535     setRecalcListItems();
536     HTMLFormControlElementWithState::childrenChanged(changedByParser, beforeChange, afterChange, childCountDelta);
537     
538     if (AXObjectCache::accessibilityEnabled() && renderer())
539         renderer()->document()->axObjectCache()->childrenChanged(renderer());
540 }
541
542 void HTMLSelectElement::setRecalcListItems()
543 {
544     m_recalcListItems = true;
545     if (renderer()) {
546         if (usesMenuList())
547             static_cast<RenderMenuList*>(renderer())->setOptionsChanged(true);
548         else
549             static_cast<RenderListBox*>(renderer())->setOptionsChanged(true);
550     }
551     if (!inDocument())
552         m_collectionInfo.reset();
553     setChanged();
554 }
555
556 void HTMLSelectElement::reset()
557 {
558     bool optionSelected = false;
559     HTMLOptionElement* firstOption = 0;
560     const Vector<HTMLElement*>& items = listItems();
561     unsigned i;
562     for (i = 0; i < items.size(); i++) {
563         if (items[i]->hasLocalName(optionTag)) {
564             HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
565             if (!option->getAttribute(selectedAttr).isNull()) {
566                 option->setSelectedState(true);
567                 optionSelected = true;
568             } else
569                 option->setSelectedState(false);
570             if (!firstOption)
571                 firstOption = option;
572         }
573     }
574     if (!optionSelected && firstOption && usesMenuList())
575         firstOption->setSelectedState(true);
576     
577     setChanged();
578 }
579
580 void HTMLSelectElement::dispatchFocusEvent()
581 {
582     if (usesMenuList())
583         // Save the selection so it can be compared to the new selection when we call onChange during dispatchBlurEvent.
584         saveLastSelection();
585     HTMLFormControlElementWithState::dispatchFocusEvent();
586 }
587
588 void HTMLSelectElement::dispatchBlurEvent()
589 {
590     // We only need to fire onChange here for menu lists, because we fire onChange for list boxes whenever the selection change is actually made.
591     // This matches other browsers' behavior.
592     if (usesMenuList())
593         menuListOnChange();
594     HTMLFormControlElementWithState::dispatchBlurEvent();
595 }
596
597 void HTMLSelectElement::defaultEventHandler(Event* evt)
598 {
599     if (!renderer())
600         return;
601     
602     if (usesMenuList())
603         menuListDefaultEventHandler(evt);
604     else 
605         listBoxDefaultEventHandler(evt);
606     
607     if (evt->defaultHandled())
608         return;
609
610     if (evt->type() == keypressEvent && evt->isKeyboardEvent()) {
611         KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(evt);
612     
613         if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() &&
614             isPrintableChar(keyboardEvent->charCode())) {
615             typeAheadFind(keyboardEvent);
616             evt->setDefaultHandled();
617             return;
618         }
619     }
620
621     HTMLFormControlElementWithState::defaultEventHandler(evt);
622 }
623
624 void HTMLSelectElement::menuListDefaultEventHandler(Event* evt)
625 {
626     RenderMenuList* menuList = static_cast<RenderMenuList*>(renderer());
627
628     if (evt->type() == keydownEvent) {
629         if (!renderer() || !evt->isKeyboardEvent())
630             return;
631         String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
632         bool handled = false;
633 #if ARROW_KEYS_POP_MENU
634         if (keyIdentifier == "Down" || keyIdentifier == "Up") {
635             focus();
636             // Save the selection so it can be compared to the new selection when we call onChange during setSelectedIndex,
637             // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
638             saveLastSelection();
639             menuList->showPopup();
640             handled = true;
641         }
642 #else
643         int listIndex = optionToListIndex(selectedIndex());
644         if (keyIdentifier == "Down" || keyIdentifier == "Right") {
645             int size = listItems().size();
646             for (listIndex += 1;
647                  listIndex >= 0 && listIndex < size && (listItems()[listIndex]->disabled() || !listItems()[listIndex]->hasTagName(optionTag));
648                  ++listIndex) { }
649             
650             if (listIndex >= 0 && listIndex < size)
651                 setSelectedIndex(listToOptionIndex(listIndex));
652             handled = true;
653         } else if (keyIdentifier == "Up" || keyIdentifier == "Left") {
654             int size = listItems().size();
655             for (listIndex -= 1;
656                  listIndex >= 0 && listIndex < size && (listItems()[listIndex]->disabled() || !listItems()[listIndex]->hasTagName(optionTag));
657                  --listIndex) { }
658             
659             if (listIndex >= 0 && listIndex < size)
660                 setSelectedIndex(listToOptionIndex(listIndex));
661             handled = true;
662         }
663 #endif
664         if (handled)
665             evt->setDefaultHandled();
666     }
667
668     // Use key press event here since sending simulated mouse events
669     // on key down blocks the proper sending of the key press event.
670     if (evt->type() == keypressEvent) {
671         if (!renderer() || !evt->isKeyboardEvent())
672             return;
673         int keyCode = static_cast<KeyboardEvent*>(evt)->keyCode();
674         bool handled = false;
675 #if ARROW_KEYS_POP_MENU
676         if (keyCode == ' ') {
677             focus();
678             // Save the selection so it can be compared to the new selection when we call onChange during setSelectedIndex,
679             // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
680             saveLastSelection();
681             menuList->showPopup();
682             handled = true;
683         }
684         if (keyCode == '\r') {
685             menuListOnChange();
686             if (form())
687                 form()->submitClick(evt);
688             handled = true;
689         }
690 #else
691         int listIndex = optionToListIndex(selectedIndex());
692         if (keyCode == '\r') {
693             // listIndex should already be selected, but this will fire the onchange handler.
694             setSelectedIndex(listToOptionIndex(listIndex), true, true);
695             handled = true;
696         }
697 #endif
698         if (handled)
699             evt->setDefaultHandled();
700     }
701
702     if (evt->type() == mousedownEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton) {
703         focus();
704         if (menuList->popupIsVisible())
705             menuList->hidePopup();
706         else {
707             // Save the selection so it can be compared to the new selection when we call onChange during setSelectedIndex,
708             // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
709             saveLastSelection();
710             menuList->showPopup();
711         }
712         evt->setDefaultHandled();
713     }
714 }
715
716 void HTMLSelectElement::listBoxDefaultEventHandler(Event* evt)
717 {
718     if (evt->type() == mousedownEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton) {
719         focus();
720         
721         MouseEvent* mEvt = static_cast<MouseEvent*>(evt);
722         int listIndex = static_cast<RenderListBox*>(renderer())->listIndexAtOffset(mEvt->offsetX(), mEvt->offsetY());
723         if (listIndex >= 0) {
724             // Save the selection so it can be compared to the new selection when we call onChange during mouseup, or after autoscroll finishes.
725             saveLastSelection();
726
727             m_activeSelectionState = true;
728             
729             bool multiSelectKeyPressed = false;
730 #if PLATFORM(MAC)
731             multiSelectKeyPressed = mEvt->metaKey();
732 #else
733             multiSelectKeyPressed = mEvt->ctrlKey();
734 #endif
735
736             bool shiftSelect = multiple() && mEvt->shiftKey();
737             bool multiSelect = multiple() && multiSelectKeyPressed && !mEvt->shiftKey();
738             
739             HTMLElement* clickedElement = listItems()[listIndex];            
740             HTMLOptionElement* option = 0;
741             if (clickedElement->hasLocalName(optionTag)) {
742                 option = static_cast<HTMLOptionElement*>(clickedElement);
743                 
744                 // Keep track of whether an active selection (like during drag selection), should select or deselect
745                 if (option->selected() && multiSelectKeyPressed)
746                     m_activeSelectionState = false;
747
748                 if (!m_activeSelectionState)
749                     option->setSelectedState(false);
750             }
751             
752             // If we're not in any special multiple selection mode, then deselect all other items, excluding the clicked option.
753             // If no option was clicked, then this will deselect all items in the list.
754             if (!shiftSelect && !multiSelect)
755                 deselectItems(option);
756
757             // 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.
758             if (m_activeSelectionAnchorIndex < 0 && !multiSelect)
759                 setActiveSelectionAnchorIndex(selectedIndex());
760
761             // Set the selection state of the clicked option
762             if (option && !option->disabled())
763                 option->setSelectedState(true);
764             
765             // If there was no selectedIndex() for the previous initialization, or
766             // 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.
767             if (listIndex >= 0 && (m_activeSelectionAnchorIndex < 0 || !shiftSelect))
768                 setActiveSelectionAnchorIndex(listIndex);
769             
770             setActiveSelectionEndIndex(listIndex);
771             updateListBoxSelection(!multiSelect);
772
773             if (Frame* frame = document()->frame())
774                 frame->eventHandler()->setMouseDownMayStartAutoscroll();
775
776             evt->setDefaultHandled();
777         }
778     } else if (evt->type() == mouseupEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton && document()->frame()->eventHandler()->autoscrollRenderer() != renderer())
779         // This makes sure we fire onChange for a single click.  For drag selection, onChange will fire when the autoscroll timer stops.
780         listBoxOnChange();
781     else if (evt->type() == keydownEvent) {
782         if (!evt->isKeyboardEvent())
783             return;
784         String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
785
786         int endIndex = 0;        
787         if (m_activeSelectionEndIndex < 0) {
788             // Initialize the end index
789             if (keyIdentifier == "Down")
790                 endIndex = nextSelectableListIndex(lastSelectedListIndex());
791             else if (keyIdentifier == "Up")
792                 endIndex = previousSelectableListIndex(optionToListIndex(selectedIndex()));
793         } else {
794             // Set the end index based on the current end index
795             if (keyIdentifier == "Down")
796                 endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
797             else if (keyIdentifier == "Up")
798                 endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);    
799         }
800         
801         if (keyIdentifier == "Down" || keyIdentifier == "Up") {
802             // Save the selection so it can be compared to the new selection when we call onChange immediately after making the new selection.
803             saveLastSelection();
804
805             ASSERT(endIndex >= 0 && (unsigned)endIndex < listItems().size()); 
806             setActiveSelectionEndIndex(endIndex);
807             
808             // 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.
809             bool deselectOthers = !multiple() || !static_cast<KeyboardEvent*>(evt)->shiftKey();
810             if (m_activeSelectionAnchorIndex < 0 || deselectOthers) {
811                 m_activeSelectionState = true;
812                 if (deselectOthers)
813                     deselectItems();
814                 setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
815             }
816
817             static_cast<RenderListBox*>(renderer())->scrollToRevealElementAtListIndex(endIndex);
818             updateListBoxSelection(deselectOthers);
819             listBoxOnChange();
820             evt->setDefaultHandled();
821         }
822     } else if (evt->type() == keypressEvent) {
823         if (!evt->isKeyboardEvent())
824             return;
825         int keyCode = static_cast<KeyboardEvent*>(evt)->keyCode();
826
827         if (keyCode == '\r') {
828             if (form())
829                 form()->submitClick(evt);
830             evt->setDefaultHandled();
831             return;
832         }
833     }
834 }
835
836 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
837 {
838     m_activeSelectionAnchorIndex = index;
839     
840     // Cache the selection state so we can restore the old selection as the new selection pivots around this anchor index
841     const Vector<HTMLElement*>& items = listItems();
842     m_cachedStateForActiveSelection.clear();
843     for (unsigned i = 0; i < items.size(); i++) {
844         if (items[i]->hasLocalName(optionTag)) {
845             HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
846             m_cachedStateForActiveSelection.append(option->selected());
847         } else
848             m_cachedStateForActiveSelection.append(false);
849     }
850 }
851
852 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
853 {
854     ASSERT(renderer() && renderer()->isListBox());
855     
856     unsigned start;
857     unsigned end;
858     ASSERT(m_activeSelectionAnchorIndex >= 0);
859     start = min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
860     end = max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
861
862     const Vector<HTMLElement*>& items = listItems();
863     for (unsigned i = 0; i < items.size(); i++) {
864         if (items[i]->hasLocalName(optionTag)) {
865             HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
866             if (!option->disabled()) {
867                 if (i >= start && i <= end)
868                     option->setSelectedState(m_activeSelectionState);
869                 else if (deselectOtherOptions)
870                     option->setSelectedState(false);
871                 else
872                     option->setSelectedState(m_cachedStateForActiveSelection[i]);
873             }
874         }
875     }
876
877     scrollToSelection();
878 }
879
880 void HTMLSelectElement::menuListOnChange()
881 {
882     ASSERT(usesMenuList());
883     int selected = selectedIndex();
884     if (m_lastOnChangeIndex != selected) {
885         m_lastOnChangeIndex = selected;
886         onChange();
887     }
888 }
889
890 void HTMLSelectElement::listBoxOnChange()
891 {
892     ASSERT(!usesMenuList());
893
894     const Vector<HTMLElement*>& items = listItems();
895     
896     // If the cached selection list is empty, or the size has changed, then fire onChange, and return early.
897     if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
898         onChange();
899         return;
900     }
901     
902     // Update m_lastOnChangeSelection and fire onChange
903     bool fireOnChange = false;
904     for (unsigned i = 0; i < items.size(); i++) {
905         bool selected = false;
906         if (items[i]->hasLocalName(optionTag))
907             selected = static_cast<HTMLOptionElement*>(items[i])->selected();
908         if (selected != m_lastOnChangeSelection[i])      
909             fireOnChange = true;
910         m_lastOnChangeSelection[i] = selected;
911     }
912     if (fireOnChange)
913         onChange();
914 }
915
916 void HTMLSelectElement::saveLastSelection()
917 {
918     const Vector<HTMLElement*>& items = listItems();
919
920     if (usesMenuList()) {
921         m_lastOnChangeIndex = selectedIndex();
922         return;
923     }
924     
925     m_lastOnChangeSelection.clear();
926     for (unsigned i = 0; i < items.size(); i++) {
927         if (items[i]->hasLocalName(optionTag)) {
928             HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
929             m_lastOnChangeSelection.append(option->selected());
930         } else
931             m_lastOnChangeSelection.append(false);
932     }
933 }
934
935 static String stripLeadingWhiteSpace(const String& string)
936 {
937     int length = string.length();
938     int i;
939     for (i = 0; i < length; ++i)
940         if (string[i] != noBreakSpace &&
941             (string[i] <= 0x7F ? !isASCIISpace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral)))
942             break;
943
944     return string.substring(i, length - i);
945 }
946
947 void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
948 {
949     if (event->timeStamp() < m_lastCharTime)
950         return;
951
952     DOMTimeStamp delta = event->timeStamp() - m_lastCharTime;
953
954     m_lastCharTime = event->timeStamp();
955
956     UChar c = event->charCode();
957
958     String prefix;
959     int searchStartOffset = 1;
960     if (delta > typeAheadTimeout) {
961         m_typedString = prefix = String(&c, 1);
962         m_repeatingChar = c;
963     } else {
964         m_typedString.append(c);
965
966         if (c == m_repeatingChar)
967             // The user is likely trying to cycle through all the items starting with this character, so just search on the character
968             prefix = String(&c, 1);
969         else {
970             m_repeatingChar = 0;
971             prefix = m_typedString;
972             searchStartOffset = 0;
973         }
974     }
975
976     const Vector<HTMLElement*>& items = listItems();
977     int itemCount = items.size();
978     if (itemCount < 1)
979         return;
980
981     int selected = selectedIndex();
982     int index = (optionToListIndex(selected >= 0 ? selected : 0) + searchStartOffset) % itemCount;
983     ASSERT(index >= 0);
984     for (int i = 0; i < itemCount; i++, index = (index + 1) % itemCount) {
985         if (!items[index]->hasTagName(optionTag) || items[index]->disabled())
986             continue;
987
988         if (stripLeadingWhiteSpace(static_cast<HTMLOptionElement*>(items[index])->optionText()).startsWith(prefix, false)) {
989             setSelectedIndex(listToOptionIndex(index));
990             setChanged();
991             return;
992         }
993     }
994 }
995
996 int HTMLSelectElement::nextSelectableListIndex(int startIndex)
997 {
998     const Vector<HTMLElement*>& items = listItems();
999     int index = startIndex + 1;
1000     while (index >= 0 && (unsigned)index < items.size() && (!items[index]->hasLocalName(optionTag) || items[index]->disabled()))
1001         index++;
1002     if ((unsigned) index == items.size())
1003         return startIndex;
1004     return index;
1005 }
1006
1007 int HTMLSelectElement::previousSelectableListIndex(int startIndex)
1008 {
1009     const Vector<HTMLElement*>& items = listItems();
1010     if (startIndex == -1)
1011         startIndex = items.size();
1012     int index = startIndex - 1;
1013     while (index >= 0 && (unsigned)index < items.size() && (!items[index]->hasLocalName(optionTag) || items[index]->disabled()))
1014         index--;
1015     if (index == -1)
1016         return startIndex;
1017     return index;
1018 }
1019
1020 void HTMLSelectElement::accessKeyAction(bool sendToAnyElement)
1021 {
1022     focus();
1023     dispatchSimulatedClick(0, sendToAnyElement);
1024 }
1025
1026 void HTMLSelectElement::accessKeySetSelectedIndex(int index)
1027 {    
1028     // first bring into focus the list box
1029     if (!focused())
1030         accessKeyAction(false);
1031     
1032     // if this index is already selected, unselect. otherwise update the selected index
1033     Node* listNode = item(index);
1034     if (listNode && listNode->hasTagName(optionTag)) {
1035         HTMLOptionElement* listElement = static_cast<HTMLOptionElement*>(listNode);
1036         if (listElement->selected())
1037             listElement->setSelectedState(false);
1038         else
1039             setSelectedIndex(index, false, true);
1040     }
1041     
1042     listBoxOnChange();
1043     scrollToSelection();
1044 }
1045     
1046 void HTMLSelectElement::setMultiple(bool multiple)
1047 {
1048     setAttribute(multipleAttr, multiple ? "" : 0);
1049 }
1050
1051 void HTMLSelectElement::setSize(int size)
1052 {
1053     setAttribute(sizeAttr, String::number(size));
1054 }
1055
1056 Node* HTMLSelectElement::namedItem(const String &name, bool caseSensitive)
1057 {
1058     return options()->namedItem(name, caseSensitive);
1059 }
1060
1061 Node* HTMLSelectElement::item(unsigned index)
1062 {
1063     return options()->item(index);
1064 }
1065
1066 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
1067 {
1068     ec = 0;
1069     if (index > INT_MAX)
1070         index = INT_MAX;
1071     int diff = index  - length();
1072     HTMLElement* before = 0;
1073     // out of array bounds ? first insert empty dummies
1074     if (diff > 0) {
1075         setLength(index, ec);
1076         // replace an existing entry ?
1077     } else if (diff < 0) {
1078         before = static_cast<HTMLElement*>(options()->item(index+1));
1079         remove(index);
1080     }
1081     // finally add the new element
1082     if (!ec) {
1083         add(option, before, ec);
1084         if (diff >= 0 && option->selected())
1085             setSelectedIndex(index, !m_multiple);
1086     }
1087 }
1088
1089 void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
1090 {
1091     ec = 0;
1092     if (newLen > INT_MAX)
1093         newLen = INT_MAX;
1094     int diff = length() - newLen;
1095
1096     if (diff < 0) { // add dummy elements
1097         do {
1098             RefPtr<Element> option = document()->createElement("option", ec);
1099             if (!option)
1100                 break;
1101             add(static_cast<HTMLElement*>(option.get()), 0, ec);
1102             if (ec)
1103                 break;
1104         } while (++diff);
1105     }
1106     else // remove elements
1107         while (diff-- > 0)
1108             remove(newLen);
1109 }
1110
1111 void HTMLSelectElement::scrollToSelection()
1112 {
1113     if (renderer() && !usesMenuList())
1114         static_cast<RenderListBox*>(renderer())->selectionChanged();
1115 }
1116
1117 #ifndef NDEBUG
1118
1119 void HTMLSelectElement::checkListItems() const
1120 {
1121     Vector<HTMLElement*> items = m_listItems;
1122     recalcListItems(false);
1123     ASSERT(items == m_listItems);
1124 }
1125
1126 #endif
1127
1128 } // namespace