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