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