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