d0c045c8e1d5f14dd239015cd13373d7dd1d5c75
[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 ContainerNode* HTMLSelectElement::addChild(PassRefPtr<Node> newChild)
337 {
338     ContainerNode* result = HTMLFormControlElementWithState::addChild(newChild);
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() const
498 {
499     Node* current = firstChild();
500     m_listItems.clear();
501     HTMLOptionElement* foundSelected = 0;
502     while (current) {
503         if (current->hasTagName(optgroupTag) && current->firstChild()) {
504             // ### what if optgroup contains just comments? don't want one of no options in it...
505             m_listItems.append(static_cast<HTMLElement*>(current));
506             current = current->firstChild();
507         }
508         if (current->hasTagName(optionTag)) {
509             m_listItems.append(static_cast<HTMLElement*>(current));
510             if (!foundSelected && (usesMenuList() || (!m_multiple && static_cast<HTMLOptionElement*>(current)->selected()))) {
511                 foundSelected = static_cast<HTMLOptionElement*>(current);
512                 foundSelected->setSelectedState(true);
513             } else if (foundSelected && !m_multiple && static_cast<HTMLOptionElement*>(current)->selected()) {
514                 foundSelected->setSelectedState(false);
515                 foundSelected = static_cast<HTMLOptionElement*>(current);
516             }
517         }
518         if (current->hasTagName(hrTag))
519             m_listItems.append(static_cast<HTMLElement*>(current));
520
521         Node* parent = current->parentNode();
522         current = current->nextSibling();
523         if (!current) {
524             if (parent != this)
525                 current = parent->nextSibling();
526         }
527     }
528     m_recalcListItems = false;
529 }
530
531 void HTMLSelectElement::childrenChanged()
532 {
533     setRecalcListItems();
534
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 (usesMenuList())
596         menuListDefaultEventHandler(evt);
597     else 
598         listBoxDefaultEventHandler(evt);
599     
600     if (evt->defaultHandled())
601         return;
602
603     if (evt->type() == keypressEvent && evt->isKeyboardEvent()) {
604         KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(evt);
605     
606         if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() &&
607             isPrintableChar(keyboardEvent->charCode())) {
608             typeAheadFind(keyboardEvent);
609             evt->setDefaultHandled();
610             return;
611         }
612     }
613
614     HTMLFormControlElementWithState::defaultEventHandler(evt);
615 }
616
617 void HTMLSelectElement::menuListDefaultEventHandler(Event* evt)
618 {
619     RenderMenuList* menuList = static_cast<RenderMenuList*>(renderer());
620
621     // Use key press event here since sending simulated mouse events
622     // on key down blocks the proper sending of the key press event.
623     if (evt->type() == keypressEvent) {
624         if (!renderer() || !evt->isKeyboardEvent())
625             return;
626         String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
627         bool handled = false;
628 #if ARROW_KEYS_POP_MENU
629         if (keyIdentifier == "Enter") {
630             menuListOnChange();
631             if (form())
632                 form()->submitClick(evt);
633             handled = true;
634         }
635         if (keyIdentifier == "Down" || keyIdentifier == "Up" || keyIdentifier == "U+0020") {
636             focus();
637             // Save the selection so it can be compared to the new selection when we call onChange during setSelectedIndex,
638             // which gets called from RenderMenuList::valueChanged, which gets called after the user makes a selection from the menu.
639             saveLastSelection();
640             menuList->showPopup();
641             handled = true;
642         }
643 #else
644         int listIndex = optionToListIndex(selectedIndex());
645         if (keyIdentifier == "Down" || keyIdentifier == "Right") {
646             int size = listItems().size();
647             for (listIndex += 1;
648                  listIndex >= 0 && listIndex < size && (listItems()[listIndex]->disabled() || !listItems()[listIndex]->hasTagName(optionTag));
649                  ++listIndex);
650             
651             if (listIndex >= 0 && listIndex < size)
652                 setSelectedIndex(listToOptionIndex(listIndex));
653             handled = true;
654         } else if (keyIdentifier == "Up" || keyIdentifier == "Left") {
655             int size = listItems().size();
656             for (listIndex -= 1;
657                  listIndex >= 0 && listIndex < size && (listItems()[listIndex]->disabled() || !listItems()[listIndex]->hasTagName(optionTag));
658                  --listIndex);
659             
660             if (listIndex >= 0 && listIndex < size)
661                 setSelectedIndex(listToOptionIndex(listIndex));
662             handled = true;
663         } else if (keyIdentifier == "Enter") {
664             // listIndex should already be selected, but this will fire the onchange handler.
665             setSelectedIndex(listToOptionIndex(listIndex), true, true);
666             handled = true;
667         }
668 #endif
669         if (handled)
670             evt->setDefaultHandled();
671
672     }
673     if (evt->type() == mousedownEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton) {
674         focus();
675         if (menuList->popupIsVisible())
676             menuList->hidePopup();
677         else {
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         }
683         evt->setDefaultHandled();
684     }
685 }
686
687 void HTMLSelectElement::listBoxDefaultEventHandler(Event* evt)
688 {
689     if (!renderer())
690         return;
691
692     if (evt->type() == mousedownEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton) {
693         focus();
694         
695         MouseEvent* mEvt = static_cast<MouseEvent*>(evt);
696         int listIndex = static_cast<RenderListBox*>(renderer())->listIndexAtOffset(mEvt->offsetX(), mEvt->offsetY());
697         if (listIndex >= 0) {
698             // Save the selection so it can be compared to the new selection when we call onChange during mouseup, or after autoscroll finishes.
699             saveLastSelection();
700
701             m_activeSelectionState = true;
702             
703             bool multiSelectKeyPressed = false;
704 #if PLATFORM(MAC)
705             multiSelectKeyPressed = mEvt->metaKey();
706 #else
707             multiSelectKeyPressed = mEvt->ctrlKey();
708 #endif
709
710             bool shiftSelect = multiple() && mEvt->shiftKey();
711             bool multiSelect = multiple() && multiSelectKeyPressed && !mEvt->shiftKey();
712             
713             HTMLElement* clickedElement = listItems()[listIndex];            
714             HTMLOptionElement* option = 0;
715             if (clickedElement->hasLocalName(optionTag)) {
716                 option = static_cast<HTMLOptionElement*>(clickedElement);
717                 
718                 // Keep track of whether an active selection (like during drag selection), should select or deselect
719                 if (option->selected() && multiSelectKeyPressed)
720                     m_activeSelectionState = false;
721
722                 if (!m_activeSelectionState)
723                     option->setSelectedState(false);
724             }
725             
726             // If we're not in any special multiple selection mode, then deselect all other items, excluding the clicked option.
727             // If no option was clicked, then this will deselect all items in the list.
728             if (!shiftSelect && !multiSelect)
729                 deselectItems(option);
730
731             // 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.
732             if (m_activeSelectionAnchorIndex < 0 && !multiSelect)
733                 setActiveSelectionAnchorIndex(selectedIndex());
734
735             // Set the selection state of the clicked option
736             if (option && !option->disabled())
737                 option->setSelectedState(true);
738             
739             // If there was no selectedIndex() for the previous initialization, or
740             // 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.
741             if (listIndex >= 0 && (m_activeSelectionAnchorIndex < 0 || !shiftSelect))
742                 setActiveSelectionAnchorIndex(listIndex);
743             
744             setActiveSelectionEndIndex(listIndex);
745             updateListBoxSelection(!multiSelect);
746
747             if (Frame* frame = document()->frame())
748                 frame->eventHandler()->setMouseDownMayStartAutoscroll();
749
750             evt->setDefaultHandled();
751         }
752     } else if (evt->type() == mouseupEvent && evt->isMouseEvent() && static_cast<MouseEvent*>(evt)->button() == LeftButton && document()->frame()->eventHandler()->autoscrollRenderer() != renderer())
753         // This makes sure we fire onChange for a single click.  For drag selection, onChange will fire when the autoscroll timer stops.
754         listBoxOnChange();
755     else if (evt->type() == keypressEvent) {
756         if (!evt->isKeyboardEvent())
757             return;
758         String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
759
760         if (keyIdentifier == "Enter") {
761             if (form())
762                 form()->submitClick(evt);
763             evt->setDefaultHandled();
764             return;
765         }
766
767         int endIndex = 0;        
768         if (m_activeSelectionEndIndex < 0) {
769             // Initialize the end index
770             if (keyIdentifier == "Down")
771                 endIndex = nextSelectableListIndex(lastSelectedListIndex());
772             else if (keyIdentifier == "Up")
773                 endIndex = previousSelectableListIndex(optionToListIndex(selectedIndex()));
774         } else {
775             // Set the end index based on the current end index
776             if (keyIdentifier == "Down")
777                 endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
778             else if (keyIdentifier == "Up")
779                 endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);    
780         }
781         
782         if (keyIdentifier == "Down" || keyIdentifier == "Up") {
783             // Save the selection so it can be compared to the new selection when we call onChange immediately after making the new selection.
784             saveLastSelection();
785
786             ASSERT(endIndex >= 0 && (unsigned)endIndex < listItems().size()); 
787             setActiveSelectionEndIndex(endIndex);
788             
789             // 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.
790             bool deselectOthers = !multiple() || !static_cast<KeyboardEvent*>(evt)->shiftKey();
791             if (m_activeSelectionAnchorIndex < 0 || deselectOthers) {
792                 m_activeSelectionState = true;
793                 if (deselectOthers)
794                     deselectItems();
795                 setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
796             }
797
798             static_cast<RenderListBox*>(renderer())->scrollToRevealElementAtListIndex(endIndex);
799             updateListBoxSelection(deselectOthers);
800             listBoxOnChange();
801             evt->setDefaultHandled();
802         }
803     }
804 }
805
806 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
807 {
808     m_activeSelectionAnchorIndex = index;
809     
810     // Cache the selection state so we can restore the old selection as the new selection pivots around this anchor index
811     const Vector<HTMLElement*>& items = listItems();
812     m_cachedStateForActiveSelection.clear();
813     for (unsigned i = 0; i < items.size(); i++) {
814         if (items[i]->hasLocalName(optionTag)) {
815             HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
816             m_cachedStateForActiveSelection.append(option->selected());
817         } else
818             m_cachedStateForActiveSelection.append(false);
819     }
820 }
821
822 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
823 {
824     ASSERT(renderer() && renderer()->isListBox());
825     
826     unsigned start;
827     unsigned end;
828     ASSERT(m_activeSelectionAnchorIndex >= 0);
829     start = min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
830     end = max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
831
832     const Vector<HTMLElement*>& items = listItems();
833     for (unsigned i = 0; i < items.size(); i++) {
834         if (items[i]->hasLocalName(optionTag)) {
835             HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
836             if (!option->disabled()) {
837                 if (i >= start && i <= end)
838                     option->setSelectedState(m_activeSelectionState);
839                 else if (deselectOtherOptions)
840                     option->setSelectedState(false);
841                 else
842                     option->setSelectedState(m_cachedStateForActiveSelection[i]);
843             }
844         }
845     }
846
847     scrollToSelection();
848 }
849
850 void HTMLSelectElement::menuListOnChange()
851 {
852     ASSERT(usesMenuList());
853     int selected = selectedIndex();
854     if (m_lastOnChangeIndex != selected) {
855         m_lastOnChangeIndex = selected;
856         onChange();
857     }
858 }
859
860 void HTMLSelectElement::listBoxOnChange()
861 {
862     ASSERT(!usesMenuList());
863
864     const Vector<HTMLElement*>& items = listItems();
865     
866     // If the cached selection list is empty, or the size has changed, then fire onChange, and return early.
867     if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
868         onChange();
869         return;
870     }
871     
872     // Update m_lastOnChangeSelection and fire onChange
873     bool fireOnChange = false;
874     for (unsigned i = 0; i < items.size(); i++) {
875         bool selected = false;
876         if (items[i]->hasLocalName(optionTag))
877             selected = static_cast<HTMLOptionElement*>(items[i])->selected();
878         if (selected != m_lastOnChangeSelection[i])      
879             fireOnChange = true;
880         m_lastOnChangeSelection[i] = selected;
881     }
882     if (fireOnChange)
883         onChange();
884 }
885
886 void HTMLSelectElement::saveLastSelection()
887 {
888     const Vector<HTMLElement*>& items = listItems();
889
890     if (usesMenuList()) {
891         m_lastOnChangeIndex = selectedIndex();
892         return;
893     }
894     
895     m_lastOnChangeSelection.clear();
896     for (unsigned i = 0; i < items.size(); i++) {
897         if (items[i]->hasLocalName(optionTag)) {
898             HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
899             m_lastOnChangeSelection.append(option->selected());
900         } else
901             m_lastOnChangeSelection.append(false);
902     }
903 }
904
905 static String stripLeadingWhiteSpace(const String& string)
906 {
907     int length = string.length();
908     int i;
909     for (i = 0; i < length; ++i)
910         if (string[i] != noBreakSpace &&
911             (string[i] <= 0x7F ? !isASCIISpace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral)))
912             break;
913
914     return string.substring(i, length - i);
915 }
916
917 void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
918 {
919     if (event->timeStamp() < m_lastCharTime)
920         return;
921
922     DOMTimeStamp delta = event->timeStamp() - m_lastCharTime;
923
924     m_lastCharTime = event->timeStamp();
925
926     UChar c = event->charCode();
927
928     String prefix;
929     int searchStartOffset = 1;
930     if (delta > typeAheadTimeout) {
931         m_typedString = prefix = String(&c, 1);
932         m_repeatingChar = c;
933     } else {
934         m_typedString.append(c);
935
936         if (c == m_repeatingChar)
937             // The user is likely trying to cycle through all the items starting with this character, so just search on the character
938             prefix = String(&c, 1);
939         else {
940             m_repeatingChar = 0;
941             prefix = m_typedString;
942             searchStartOffset = 0;
943         }
944     }
945
946     const Vector<HTMLElement*>& items = listItems();
947     int itemCount = items.size();
948     if (itemCount < 1)
949         return;
950
951     int index = (optionToListIndex(selectedIndex()) + searchStartOffset) % itemCount;
952     for (int i = 0; i < itemCount; i++, index = (index + 1) % itemCount) {
953         if (!items[index]->hasTagName(optionTag) || items[index]->disabled())
954             continue;
955
956         if (stripLeadingWhiteSpace(static_cast<HTMLOptionElement*>(items[index])->optionText()).startsWith(prefix, false)) {
957             setSelectedIndex(listToOptionIndex(index));
958             setChanged();
959             return;
960         }
961     }
962 }
963
964 int HTMLSelectElement::nextSelectableListIndex(int startIndex)
965 {
966     const Vector<HTMLElement*>& items = listItems();
967     int index = startIndex + 1;
968     while (index >= 0 && (unsigned)index < items.size() && (!items[index]->hasLocalName(optionTag) || items[index]->disabled()))
969         index++;
970     if ((unsigned) index == items.size())
971         return startIndex;
972     return index;
973 }
974
975 int HTMLSelectElement::previousSelectableListIndex(int startIndex)
976 {
977     const Vector<HTMLElement*>& items = listItems();
978     if (startIndex == -1)
979         startIndex = items.size();
980     int index = startIndex - 1;
981     while (index >= 0 && (unsigned)index < items.size() && (!items[index]->hasLocalName(optionTag) || items[index]->disabled()))
982         index--;
983     if (index == -1)
984         return startIndex;
985     return index;
986 }
987
988 void HTMLSelectElement::accessKeyAction(bool sendToAnyElement)
989 {
990     focus();
991     dispatchSimulatedClick(0, sendToAnyElement);
992 }
993
994 void HTMLSelectElement::setMultiple(bool multiple)
995 {
996     setAttribute(multipleAttr, multiple ? "" : 0);
997 }
998
999 void HTMLSelectElement::setSize(int size)
1000 {
1001     setAttribute(sizeAttr, String::number(size));
1002 }
1003
1004 Node* HTMLSelectElement::namedItem(const String &name, bool caseSensitive)
1005 {
1006     return options()->namedItem(name, caseSensitive);
1007 }
1008
1009 Node* HTMLSelectElement::item(unsigned index)
1010 {
1011     return options()->item(index);
1012 }
1013
1014 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
1015 {
1016     ec = 0;
1017     if (index > INT_MAX)
1018         index = INT_MAX;
1019     int diff = index  - length();
1020     HTMLElement* before = 0;
1021     // out of array bounds ? first insert empty dummies
1022     if (diff > 0) {
1023         setLength(index, ec);
1024         // replace an existing entry ?
1025     } else if (diff < 0) {
1026         before = static_cast<HTMLElement*>(options()->item(index+1));
1027         remove(index);
1028     }
1029     // finally add the new element
1030     if (!ec) {
1031         add(option, before, ec);
1032         if (diff >= 0 && option->selected())
1033             setSelectedIndex(index, !m_multiple);
1034     }
1035 }
1036
1037 void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
1038 {
1039     ec = 0;
1040     if (newLen > INT_MAX)
1041         newLen = INT_MAX;
1042     int diff = length() - newLen;
1043
1044     if (diff < 0) { // add dummy elements
1045         do {
1046             RefPtr<Element> option = document()->createElement("option", ec);
1047             if (!option)
1048                 break;
1049             add(static_cast<HTMLElement*>(option.get()), 0, ec);
1050             if (ec)
1051                 break;
1052         } while (++diff);
1053     }
1054     else // remove elements
1055         while (diff-- > 0)
1056             remove(newLen);
1057 }
1058
1059 void HTMLSelectElement::scrollToSelection()
1060 {
1061     if (renderer() && !usesMenuList())
1062         static_cast<RenderListBox*>(renderer())->selectionChanged();
1063 }
1064
1065 } // namespace