Reviewed by Maciej.
[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     //ASSERT(m_lastOnChangeIndex == -1 || m_lastOnChangeIndex == optionIndex);
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     saveLastSelection();
408     
409     m_activeSelectionState = true;
410     setActiveSelectionAnchorIndex(nextSelectableListIndex(-1));
411     setActiveSelectionEndIndex(previousSelectableListIndex(-1));
412     
413     updateListBoxSelection(false);
414     listBoxOnChange();
415 }
416
417 RenderObject *HTMLSelectElement::createRenderer(RenderArena *arena, RenderStyle *style)
418 {
419     if (usesMenuList())
420         return new (arena) RenderMenuList(this);
421     return new (arena) RenderListBox(this);
422 }
423
424 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
425 {
426     bool successful = false;
427     const Vector<HTMLElement*>& items = listItems();
428
429     unsigned i;
430     for (i = 0; i < items.size(); i++) {
431         if (items[i]->hasLocalName(optionTag)) {
432             HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
433             if (option->selected()) {
434                 list.appendData(name(), option->value());
435                 successful = true;
436             }
437         }
438     }
439
440     // ### this case should not happen. make sure that we select the first option
441     // in any case. otherwise we have no consistency with the DOM interface. FIXME!
442     // we return the first one if it was a combobox select
443     if (!successful && !m_multiple && m_size <= 1 && items.size() &&
444         (items[0]->hasLocalName(optionTag))) {
445         HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[0]);
446         if (option->value().isNull())
447             list.appendData(name(), option->text().stripWhiteSpace());
448         else
449             list.appendData(name(), option->value());
450         successful = true;
451     }
452
453     return successful;
454 }
455
456 int HTMLSelectElement::optionToListIndex(int optionIndex) const
457 {
458     const Vector<HTMLElement*>& items = listItems();
459     int listSize = (int)items.size();
460     if (optionIndex < 0 || optionIndex >= listSize)
461         return -1;
462
463     int optionIndex2 = -1;
464     for (int listIndex = 0; listIndex < listSize; listIndex++) {
465         if (items[listIndex]->hasLocalName(optionTag)) {
466             optionIndex2++;
467             if (optionIndex2 == optionIndex)
468                 return listIndex;
469         }
470     }
471     return -1;
472 }
473
474 int HTMLSelectElement::listToOptionIndex(int listIndex) const
475 {
476     const Vector<HTMLElement*>& items = listItems();
477     if (listIndex < 0 || listIndex >= int(items.size()) ||
478         !items[listIndex]->hasLocalName(optionTag))
479         return -1;
480
481     int optionIndex = 0; // actual index of option not counting OPTGROUP entries that may be in list
482     for (int i = 0; i < listIndex; i++)
483         if (items[i]->hasLocalName(optionTag))
484             optionIndex++;
485     return optionIndex;
486 }
487
488 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
489 {
490     return new HTMLOptionsCollection(this);
491 }
492
493 void HTMLSelectElement::recalcListItems() const
494 {
495     Node* current = firstChild();
496     m_listItems.clear();
497     HTMLOptionElement* foundSelected = 0;
498     while (current) {
499         if (current->hasTagName(optgroupTag) && current->firstChild()) {
500             // ### what if optgroup contains just comments? don't want one of no options in it...
501             m_listItems.append(static_cast<HTMLElement*>(current));
502             current = current->firstChild();
503         }
504         if (current->hasTagName(optionTag)) {
505             m_listItems.append(static_cast<HTMLElement*>(current));
506             if (!foundSelected && (usesMenuList() || (!m_multiple && static_cast<HTMLOptionElement*>(current)->selected()))) {
507                 foundSelected = static_cast<HTMLOptionElement*>(current);
508                 foundSelected->setSelectedState(true);
509             } else if (foundSelected && !m_multiple && static_cast<HTMLOptionElement*>(current)->selected()) {
510                 foundSelected->setSelectedState(false);
511                 foundSelected = static_cast<HTMLOptionElement*>(current);
512             }
513         }
514         if (current->hasTagName(hrTag))
515             m_listItems.append(static_cast<HTMLElement*>(current));
516
517         Node* parent = current->parentNode();
518         current = current->nextSibling();
519         if (!current) {
520             if (parent != this)
521                 current = parent->nextSibling();
522         }
523     }
524     m_recalcListItems = false;
525 }
526
527 void HTMLSelectElement::childrenChanged()
528 {
529     setRecalcListItems();
530
531     HTMLGenericFormElement::childrenChanged();
532 }
533
534 void HTMLSelectElement::setRecalcListItems()
535 {
536     m_recalcListItems = true;
537     if (renderer()) {
538         if (usesMenuList())
539             static_cast<RenderMenuList*>(renderer())->setOptionsChanged(true);
540         else
541             static_cast<RenderListBox*>(renderer())->setOptionsChanged(true);
542     }
543     if (!inDocument())
544         m_collectionInfo.reset();
545     setChanged();
546 }
547
548 void HTMLSelectElement::reset()
549 {
550     bool optionSelected = false;
551     HTMLOptionElement* firstOption = 0;
552     const Vector<HTMLElement*>& items = listItems();
553     unsigned i;
554     for (i = 0; i < items.size(); i++) {
555         if (items[i]->hasLocalName(optionTag)) {
556             HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
557             if (!option->getAttribute(selectedAttr).isNull()) {
558                 option->setSelectedState(true);
559                 optionSelected = true;
560             } else
561                 option->setSelectedState(false);
562             if (!firstOption)
563                 firstOption = option;
564         }
565     }
566     if (!optionSelected && firstOption && usesMenuList())
567         firstOption->setSelectedState(true);
568     
569     setChanged(true);
570 }
571
572 void HTMLSelectElement::dispatchFocusEvent()
573 {
574 #if !ARROW_KEYS_POP_MENU
575     if (usesMenuList())
576         saveLastSelection();
577 #endif
578     HTMLGenericFormElement::dispatchFocusEvent();
579 }
580
581 void HTMLSelectElement::dispatchBlurEvent()
582 {
583 #if !ARROW_KEYS_POP_MENU
584     if (usesMenuList())
585         menuListOnChange();
586 #endif
587     HTMLGenericFormElement::dispatchBlurEvent();
588 }
589
590 void HTMLSelectElement::defaultEventHandler(Event* evt)
591 {
592     if (usesMenuList())
593         menuListDefaultEventHandler(evt);
594     else 
595         listBoxDefaultEventHandler(evt);
596     
597     if (evt->defaultHandled())
598         return;
599
600     if (evt->type() == keypressEvent && evt->isKeyboardEvent()) {
601         KeyboardEvent* keyboardEvent = static_cast<KeyboardEvent*>(evt);
602     
603         if (!keyboardEvent->ctrlKey() && !keyboardEvent->altKey() && !keyboardEvent->metaKey() &&
604             isPrintableChar(keyboardEvent->charCode())) {
605             typeAheadFind(keyboardEvent);
606             evt->setDefaultHandled();
607             return;
608         }
609     }
610
611     HTMLGenericFormElement::defaultEventHandler(evt);
612 }
613
614 void HTMLSelectElement::menuListDefaultEventHandler(Event* evt)
615 {
616     RenderMenuList* menuList = static_cast<RenderMenuList*>(renderer());
617
618     // Use key press event here since sending simulated mouse events
619     // on key down blocks the proper sending of the key press event.
620     if (evt->type() == keypressEvent) {
621         if (!renderer() || !evt->isKeyboardEvent())
622             return;
623         String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
624         bool handled = false;
625 #if ARROW_KEYS_POP_MENU
626         if (form() && keyIdentifier == "Enter") {
627             blur();
628             // Make sure the form hasn't been destroyed during the blur.
629             if (form())
630                 form()->submitClick(evt);
631             handled = true;
632         }
633         if ((keyIdentifier == "Down" || keyIdentifier == "Up" || keyIdentifier == "U+000020") && renderer() && usesMenuList()) {
634             focus();
635             saveLastSelection();
636             menuList->showPopup();
637             handled = true;
638         }
639 #else
640         int listIndex = optionToListIndex(selectedIndex());
641         if (keyIdentifier == "Down" || keyIdentifier == "Right") {
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 == "Up" || keyIdentifier == "Left") {
651             size_t size = listItems().size();
652             for (listIndex -= 1;
653                  listIndex >= 0 && listIndex < size && (listItems()[listIndex]->disabled() || !listItems()[listIndex]->hasTagName(optionTag));
654                  --listIndex);
655             
656             if (listIndex >= 0 && listIndex < size)
657                 setSelectedIndex(listToOptionIndex(listIndex));
658             handled = true;
659         } else if (keyIdentifier == "Enter") {
660             saveLastSelection();
661             setSelectedIndex(listToOptionIndex(listIndex), true, true);
662         }
663 #endif
664         if (handled)
665             evt->setDefaultHandled();
666
667     }
668     if (evt->type() == mousedownEvent) {
669         focus();
670         if (menuList->popupIsVisible())
671             menuList->hidePopup();
672         else {
673             saveLastSelection();
674             menuList->showPopup();
675         }
676         evt->setDefaultHandled();
677     }
678 }
679
680 void HTMLSelectElement::listBoxDefaultEventHandler(Event* evt)
681 {
682     if (!renderer() || !renderer()->canSelect())
683         return;
684
685     if (evt->type() == mousedownEvent) {
686         focus();
687         
688         MouseEvent* mEvt = static_cast<MouseEvent*>(evt);
689         int listIndex = static_cast<RenderListBox*>(renderer())->listIndexAtOffset(mEvt->offsetX(), mEvt->offsetY());
690         if (listIndex >= 0) {
691             saveLastSelection();
692
693             m_activeSelectionState = true;
694             
695             bool multiSelectKeyPressed = false;
696 #if PLATFORM(MAC)
697             multiSelectKeyPressed = mEvt->metaKey();
698 #else
699             multiSelectKeyPressed = mEvt->ctrlKey();
700 #endif
701
702             bool shiftSelect = multiple() && mEvt->shiftKey();
703             bool multiSelect = multiple() && multiSelectKeyPressed && !mEvt->shiftKey();
704             
705             HTMLElement* clickedElement = listItems()[listIndex];            
706             HTMLOptionElement* option = 0;
707             if (clickedElement->hasLocalName(optionTag)) {
708                 option = static_cast<HTMLOptionElement*>(clickedElement);
709                 
710                 // Keep track of whether an active selection (like during drag selection), should select or deselect
711                 if (option->selected() && multiSelectKeyPressed)
712                     m_activeSelectionState = false;
713
714                 if (!m_activeSelectionState)
715                     option->setSelectedState(false);
716             }
717             
718             // If we're not in any special multiple selection mode, then deselect all other items, excluding the clicked option.
719             // If no option was clicked, then this will deselect all items in the list.
720             if (!shiftSelect && !multiSelect)
721                 deselectItems(option);
722
723             // 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.
724             if (m_activeSelectionAnchorIndex < 0 && !multiSelect)
725                 setActiveSelectionAnchorIndex(selectedIndex());
726
727             // Set the selection state of the clicked option
728             if (option && !option->disabled())
729                 option->setSelectedState(true);
730             
731             // If there was no selectedIndex() for the previous initialization, or
732             // 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.
733             if (listIndex >= 0 && (m_activeSelectionAnchorIndex < 0 || !shiftSelect))
734                 setActiveSelectionAnchorIndex(listIndex);
735             
736             setActiveSelectionEndIndex(listIndex);
737             updateListBoxSelection(!multiSelect);
738
739             if (Frame* frame = document()->frame())
740                 frame->eventHandler()->setMouseDownMayStartAutoscroll();
741
742             evt->setDefaultHandled();
743         }
744     } else if (evt->type() == mouseupEvent && document()->frame()->eventHandler()->autoscrollRenderer() != renderer())
745         // This makes sure we fire onChange for a single click.  For drag selection, onChange will fire when the autoscroll timer stops.
746         listBoxOnChange();
747     else if (evt->type() == keypressEvent) {
748         if (!evt->isKeyboardEvent())
749             return;
750         String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
751
752         if (form() && keyIdentifier == "Enter") {
753             blur();
754             // Make sure the form hasn't been destroyed during the blur.
755             if (form())
756                 form()->submitClick(evt);
757             evt->setDefaultHandled();
758             return;
759         }
760
761         int endIndex = 0;        
762         if (m_activeSelectionEndIndex < 0) {
763             // Initialize the end index
764             if (keyIdentifier == "Down")
765                 endIndex = nextSelectableListIndex(lastSelectedListIndex());
766             else if (keyIdentifier == "Up")
767                 endIndex = previousSelectableListIndex(optionToListIndex(selectedIndex()));
768         } else {
769             // Set the end index based on the current end index
770             if (keyIdentifier == "Down")
771                 endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
772             else if (keyIdentifier == "Up")
773                 endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);    
774         }
775         
776         if (keyIdentifier == "Down" || keyIdentifier == "Up") {
777             saveLastSelection();
778
779             ASSERT(endIndex >= 0 && (unsigned)endIndex < listItems().size()); 
780             setActiveSelectionEndIndex(endIndex);
781             
782             // 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.
783             bool deselectOthers = !multiple() || !static_cast<KeyboardEvent*>(evt)->shiftKey();
784             if (m_activeSelectionAnchorIndex < 0 || deselectOthers) {
785                 m_activeSelectionState = true;
786                 if (deselectOthers)
787                     deselectItems();
788                 setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
789             }
790
791             static_cast<RenderListBox*>(renderer())->scrollToRevealElementAtListIndex(endIndex);
792             updateListBoxSelection(deselectOthers);
793             listBoxOnChange();
794             evt->setDefaultHandled();
795         }
796     }
797 }
798
799 void HTMLSelectElement::setActiveSelectionAnchorIndex(int index)
800 {
801     m_activeSelectionAnchorIndex = index;
802     
803     // Cache the selection state so we can restore the old selection as the new selection pivots around this anchor index
804     const Vector<HTMLElement*>& items = listItems();
805     m_cachedStateForActiveSelection.clear();
806     for (unsigned i = 0; i < items.size(); i++) {
807         if (items[i]->hasLocalName(optionTag)) {
808             HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
809             m_cachedStateForActiveSelection.append(option->selected());
810         } else
811             m_cachedStateForActiveSelection.append(false);
812     }
813 }
814
815 void HTMLSelectElement::updateListBoxSelection(bool deselectOtherOptions)
816 {
817     ASSERT(renderer() && renderer()->isListBox());
818     
819     unsigned start;
820     unsigned end;
821     ASSERT(m_activeSelectionAnchorIndex >= 0);
822     start = min(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
823     end = max(m_activeSelectionAnchorIndex, m_activeSelectionEndIndex);
824
825     const Vector<HTMLElement*>& items = listItems();
826     for (unsigned i = 0; i < items.size(); i++) {
827         if (items[i]->hasLocalName(optionTag)) {
828             HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
829             if (!option->disabled()) {
830                 if (i >= start && i <= end)
831                     option->setSelectedState(m_activeSelectionState);
832                 else if (deselectOtherOptions)
833                     option->setSelectedState(false);
834                 else
835                     option->setSelectedState(m_cachedStateForActiveSelection[i]);
836             }
837         }
838     }
839
840     scrollToSelection();
841 }
842
843 void HTMLSelectElement::menuListOnChange()
844 {
845     if (m_lastOnChangeIndex != selectedIndex())
846         onChange();
847 }
848
849 void HTMLSelectElement::listBoxOnChange()
850 {
851     const Vector<HTMLElement*>& items = listItems();
852     
853     // If the cached selection list is empty, or the size has changed, then fire onChange, and return early.
854     if (m_lastOnChangeSelection.isEmpty() || m_lastOnChangeSelection.size() != items.size()) {
855         onChange();
856         return;
857     }
858     
859     // Update m_lastOnChangeSelection and fire onChange
860     bool fireOnChange = false;
861     for (unsigned i = 0; i < items.size(); i++) {
862         bool selected = false;
863         if (items[i]->hasLocalName(optionTag))
864             selected = static_cast<HTMLOptionElement*>(items[i])->selected();
865         if (selected != m_lastOnChangeSelection[i])      
866             fireOnChange = true;
867         m_lastOnChangeSelection[i] = selected;
868     }
869     if (fireOnChange)
870         onChange();
871 }
872
873 void HTMLSelectElement::saveLastSelection()
874 {
875     const Vector<HTMLElement*>& items = listItems();
876
877     if (usesMenuList()) {
878         m_lastOnChangeIndex = selectedIndex();
879     } else {
880         m_lastOnChangeSelection.clear();
881         for (unsigned i = 0; i < items.size(); i++) {
882             if (items[i]->hasLocalName(optionTag)) {
883                 HTMLOptionElement* option = static_cast<HTMLOptionElement*>(items[i]);
884                 m_lastOnChangeSelection.append(option->selected());
885             } else
886                 m_lastOnChangeSelection.append(false);
887         }
888     }
889 }
890
891 static String stripLeadingWhiteSpace(const String& string)
892 {
893     int length = string.length();
894     int i;
895     for (i = 0; i < length; ++i)
896         if (string[i] != noBreakSpace &&
897             (string[i] <= 0x7F ? !isspace(string[i]) : (direction(string[i]) != WhiteSpaceNeutral)))
898             break;
899
900     return string.substring(i, length - i);
901 }
902
903 void HTMLSelectElement::typeAheadFind(KeyboardEvent* event)
904 {
905     if (event->timeStamp() < m_lastCharTime)
906         return;
907
908     DOMTimeStamp delta = event->timeStamp() - m_lastCharTime;
909
910     m_lastCharTime = event->timeStamp();
911
912     UChar c = event->charCode();
913
914     String prefix;
915     int searchStartOffset = 1;
916     if (delta > typeAheadTimeout) {
917         m_typedString = prefix = String(&c, 1);
918         m_repeatingChar = c;
919     } else {
920         m_typedString.append(c);
921
922         if (c == m_repeatingChar)
923             // The user is likely trying to cycle through all the items starting with this character, so just search on the character
924             prefix = String(&c, 1);
925         else {
926             m_repeatingChar = 0;
927             prefix = m_typedString;
928             searchStartOffset = 0;
929         }
930     }
931
932     const Vector<HTMLElement*>& items = listItems();
933     int itemCount = items.size();
934
935     int index = (optionToListIndex(selectedIndex()) + searchStartOffset) % itemCount;
936     for (int i = 0; i < itemCount; i++, index = (index + 1) % itemCount) {
937         if (!items[index]->hasTagName(optionTag) || items[index]->disabled())
938             continue;
939
940         if (stripLeadingWhiteSpace(static_cast<HTMLOptionElement*>(items[index])->optionText()).startsWith(prefix, false)) {
941             setSelectedIndex(listToOptionIndex(index));
942             setChanged();
943             return;
944         }
945     }
946 }
947
948 int HTMLSelectElement::nextSelectableListIndex(int startIndex)
949 {
950     const Vector<HTMLElement*>& items = listItems();
951     int index = startIndex + 1;
952     while (index >= 0 && (unsigned)index < items.size() && (!items[index]->hasLocalName(optionTag) || items[index]->disabled()))
953         index++;
954     if ((unsigned) index == items.size())
955         return startIndex;
956     return index;
957 }
958
959 int HTMLSelectElement::previousSelectableListIndex(int startIndex)
960 {
961     const Vector<HTMLElement*>& items = listItems();
962     if (startIndex == -1)
963         startIndex = items.size();
964     int index = startIndex - 1;
965     while (index >= 0 && (unsigned)index < items.size() && (!items[index]->hasLocalName(optionTag) || items[index]->disabled()))
966         index--;
967     if (index == -1)
968         return startIndex;
969     return index;
970 }
971
972 void HTMLSelectElement::accessKeyAction(bool sendToAnyElement)
973 {
974     focus();
975     dispatchSimulatedClick(0, sendToAnyElement);
976 }
977
978 void HTMLSelectElement::setMultiple(bool multiple)
979 {
980     setAttribute(multipleAttr, multiple ? "" : 0);
981 }
982
983 void HTMLSelectElement::setSize(int size)
984 {
985     setAttribute(sizeAttr, String::number(size));
986 }
987
988 Node* HTMLSelectElement::namedItem(const String &name, bool caseSensitive)
989 {
990     return options()->namedItem(name, caseSensitive);
991 }
992
993 Node* HTMLSelectElement::item(unsigned index)
994 {
995     return options()->item(index);
996 }
997
998 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
999 {
1000     ec = 0;
1001     if (index > INT_MAX)
1002         index = INT_MAX;
1003     int diff = index  - length();
1004     HTMLElement* before = 0;
1005     // out of array bounds ? first insert empty dummies
1006     if (diff > 0) {
1007         setLength(index, ec);
1008         // replace an existing entry ?
1009     } else if (diff < 0) {
1010         before = static_cast<HTMLElement*>(options()->item(index+1));
1011         remove(index);
1012     }
1013     // finally add the new element
1014     if (ec == 0) {
1015         add(option, before, ec);
1016         if (diff >= 0 && option->selected())
1017             setSelectedIndex(index, !m_multiple);
1018     }
1019 }
1020
1021 void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
1022 {
1023     ec = 0;
1024     if (newLen > INT_MAX)
1025         newLen = INT_MAX;
1026     int diff = length() - newLen;
1027
1028     if (diff < 0) { // add dummy elements
1029         do {
1030             RefPtr<Element> option = ownerDocument()->createElement("option", ec);
1031             if (!option)
1032                 break;
1033             add(static_cast<HTMLElement*>(option.get()), 0, ec);
1034             if (ec)
1035                 break;
1036         } while (++diff);
1037     }
1038     else // remove elements
1039         while (diff-- > 0)
1040             remove(newLen);
1041 }
1042
1043 void HTMLSelectElement::scrollToSelection()
1044 {
1045     if (renderer() && !usesMenuList())
1046         static_cast<RenderListBox*>(renderer())->selectionChanged();
1047 }
1048
1049 } // namespace