b164d993a6e79b1ebda0332d48fe60d96449775d
[WebKit-https.git] / WebCore / html / HTMLSelectElement.cpp
1 /*
2  * This file is part of the DOM implementation for KDE.
3  *
4  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
5  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
6  *           (C) 2001 Dirk Mueller (mueller@kde.org)
7  * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
8  *           (C) 2006 Alexey Proskuryakov (ap@nypop.com)
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public License
21  * along with this library; see the file COPYING.LIB.  If not, write to
22  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23  * Boston, MA 02111-1307, USA.
24  *
25  */
26  
27 #include "config.h"
28 #include "HTMLSelectElement.h"
29
30 #include "CSSPropertyNames.h"
31 #include "Document.h"
32 #include "DeprecatedRenderSelect.h"
33 #include "Event.h"
34 #include "EventNames.h"
35 #include "FormDataList.h"
36 #include "HTMLFormElement.h"
37 #include "HTMLNames.h"
38 #include "HTMLOptionElement.h"
39 #include "HTMLOptionsCollection.h"
40 #include "KeyboardEvent.h"
41 #include "RenderMenuList.h"
42 #include "cssstyleselector.h"
43 #include <wtf/Vector.h>
44
45 namespace WebCore {
46
47 using namespace EventNames;
48 using namespace HTMLNames;
49
50 HTMLSelectElement::HTMLSelectElement(Document* doc, HTMLFormElement* f)
51     : HTMLGenericFormElement(selectTag, doc, f)
52     , m_minwidth(0)
53     , m_size(0)
54     , m_multiple(false)
55     , m_recalcListItems(false)
56 {
57     document()->registerFormElementWithState(this);
58 }
59
60 HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* f)
61     : HTMLGenericFormElement(tagName, doc, f), m_minwidth(0), m_size(0), m_multiple(false), m_recalcListItems(false)
62 {
63     document()->registerFormElementWithState(this);
64 }
65
66 HTMLSelectElement::~HTMLSelectElement()
67 {
68     document()->deregisterFormElementWithState(this);
69 }
70
71 bool HTMLSelectElement::checkDTD(const Node* newChild)
72 {
73     return newChild->isTextNode() || newChild->hasTagName(optionTag) || newChild->hasTagName(optgroupTag) || newChild->hasTagName(hrTag) ||
74            newChild->hasTagName(scriptTag);
75 }
76
77 void HTMLSelectElement::recalcStyle( StyleChange ch )
78 {
79     if (hasChangedChild() && renderer()) {
80         if (shouldUseMenuList())
81             static_cast<RenderMenuList*>(renderer())->setOptionsChanged(true);
82         else
83             static_cast<DeprecatedRenderSelect*>(renderer())->setOptionsChanged(true);
84     }
85
86     HTMLGenericFormElement::recalcStyle( ch );
87 }
88
89
90 const AtomicString& HTMLSelectElement::type() const
91 {
92     static const AtomicString selectMultiple("select-multiple");
93     static const AtomicString selectOne("select-one");
94     return m_multiple ? selectMultiple : selectOne;
95 }
96
97 int HTMLSelectElement::selectedIndex() const
98 {
99     // return the number of the first option selected
100     unsigned o = 0;
101     const Vector<HTMLElement*>& items = listItems();
102     for (unsigned int i = 0; i < items.size(); i++) {
103         if (items[i]->hasLocalName(optionTag)) {
104             if (static_cast<HTMLOptionElement*>(items[i])->selected())
105                 return o;
106             o++;
107         }
108     }
109     return -1;
110 }
111
112 void HTMLSelectElement::setSelectedIndex(int index, bool deselect)
113 {
114     // deselect all other options and select only the new one
115     const Vector<HTMLElement*>& items = listItems();
116     int listIndex;
117     if (deselect) {
118         for (listIndex = 0; listIndex < int(items.size()); listIndex++) {
119             if (items[listIndex]->hasLocalName(optionTag))
120                 static_cast<HTMLOptionElement*>(items[listIndex])->setSelected(false);
121         }
122     }
123     listIndex = optionToListIndex(index);
124     if (listIndex >= 0)
125         static_cast<HTMLOptionElement*>(items[listIndex])->setSelected(true);
126
127     setChanged(true);
128 }
129
130 int HTMLSelectElement::length() const
131 {
132     int len = 0;
133     unsigned i;
134     const Vector<HTMLElement*>& items = listItems();
135     for (i = 0; i < items.size(); i++) {
136         if (items[i]->hasLocalName(optionTag))
137             len++;
138     }
139     return len;
140 }
141
142 void HTMLSelectElement::add( HTMLElement *element, HTMLElement *before, ExceptionCode& ec)
143 {
144     RefPtr<HTMLElement> protectNewChild(element); // make sure the element is ref'd and deref'd so we don't leak it
145
146     if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
147         return;
148
149     insertBefore(element, before, ec);
150     if (!ec)
151         setRecalcListItems();
152 }
153
154 void HTMLSelectElement::remove(int index)
155 {
156     ExceptionCode ec = 0;
157     int listIndex = optionToListIndex(index);
158
159     const Vector<HTMLElement*>& items = listItems();
160     if (listIndex < 0 || index >= int(items.size()))
161         return; // ### what should we do ? remove the last item?
162
163     removeChild(items[listIndex], ec);
164     if (!ec)
165         setRecalcListItems();
166 }
167
168 String HTMLSelectElement::value()
169 {
170     unsigned i;
171     const Vector<HTMLElement*>& items = listItems();
172     for (i = 0; i < items.size(); i++) {
173         if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected())
174             return static_cast<HTMLOptionElement*>(items[i])->value();
175     }
176     return String("");
177 }
178
179 void HTMLSelectElement::setValue(const String &value)
180 {
181     if (value.isNull())
182         return;
183     // find the option with value() matching the given parameter
184     // and make it the current selection.
185     const Vector<HTMLElement*>& items = listItems();
186     for (unsigned i = 0; i < items.size(); i++)
187         if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->value() == value) {
188             static_cast<HTMLOptionElement*>(items[i])->setSelected(true);
189             return;
190         }
191 }
192
193 String HTMLSelectElement::stateValue() const
194 {
195     const Vector<HTMLElement*>& items = listItems();
196     int l = items.size();
197     Vector<char, 1024> characters(l);
198     for (int i = 0; i < l; ++i) {
199         HTMLElement* e = items[i];
200         bool selected = e->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(e)->selected();
201         characters[i] = selected ? 'X' : '.';
202     }
203     return String(characters, l);
204 }
205
206 void HTMLSelectElement::restoreState(const String& state)
207 {
208     recalcListItems();
209
210     const Vector<HTMLElement*>& items = listItems();
211     int l = items.size();
212     for (int i = 0; i < l; i++)
213         if (items[i]->hasLocalName(optionTag))
214             static_cast<HTMLOptionElement*>(items[i])->setSelected(state[i] == 'X');
215     setChanged(true);
216 }
217
218 bool HTMLSelectElement::insertBefore(PassRefPtr<Node> newChild, Node* refChild, ExceptionCode& ec)
219 {
220     bool result = HTMLGenericFormElement::insertBefore(newChild, refChild, ec);
221     if (result)
222         setRecalcListItems();
223     return result;
224 }
225
226 bool HTMLSelectElement::replaceChild(PassRefPtr<Node> newChild, Node *oldChild, ExceptionCode& ec)
227 {
228     bool result = HTMLGenericFormElement::replaceChild(newChild, oldChild, ec);
229     if (result)
230         setRecalcListItems();
231     return result;
232 }
233
234 bool HTMLSelectElement::removeChild(Node* oldChild, ExceptionCode& ec)
235 {
236     bool result = HTMLGenericFormElement::removeChild(oldChild, ec);
237     if (result)
238         setRecalcListItems();
239     return result;
240 }
241
242 bool HTMLSelectElement::appendChild(PassRefPtr<Node> newChild, ExceptionCode& ec)
243 {
244     bool result = HTMLGenericFormElement::appendChild(newChild, ec);
245     if (result)
246         setRecalcListItems();
247     return result;
248 }
249
250 ContainerNode* HTMLSelectElement::addChild(PassRefPtr<Node> newChild)
251 {
252     ContainerNode* result = HTMLGenericFormElement::addChild(newChild);
253     if (result)
254         setRecalcListItems();
255     return result;
256 }
257
258 void HTMLSelectElement::parseMappedAttribute(MappedAttribute *attr)
259 {
260     bool oldShouldUseMenuList = shouldUseMenuList();
261     if (attr->name() == sizeAttr) {
262         m_size = max(attr->value().toInt(), 1);
263         if (oldShouldUseMenuList != shouldUseMenuList() && attached()) {
264             detach();
265             attach();
266         }
267     } else if (attr->name() == widthAttr) {
268         m_minwidth = max(attr->value().toInt(), 0);
269     } else if (attr->name() == multipleAttr) {
270         m_multiple = (!attr->isNull());
271         if (oldShouldUseMenuList != shouldUseMenuList() && attached()) {
272             detach();
273             attach();
274         }
275     } else if (attr->name() == accesskeyAttr) {
276         // FIXME: ignore for the moment
277     } else if (attr->name() == onfocusAttr) {
278         setHTMLEventListener(focusEvent, attr);
279     } else if (attr->name() == onblurAttr) {
280         setHTMLEventListener(blurEvent, attr);
281     } else if (attr->name() == onchangeAttr) {
282         setHTMLEventListener(changeEvent, attr);
283     } else
284         HTMLGenericFormElement::parseMappedAttribute(attr);
285 }
286
287 bool HTMLSelectElement::isKeyboardFocusable() const
288 {
289     if (renderer() && shouldUseMenuList())
290         return isFocusable();
291     return HTMLGenericFormElement::isKeyboardFocusable();
292 }
293
294 bool HTMLSelectElement::isMouseFocusable() const
295 {
296     if (renderer() && shouldUseMenuList())
297         return isFocusable();
298     return HTMLGenericFormElement::isMouseFocusable();
299 }
300
301 RenderObject *HTMLSelectElement::createRenderer(RenderArena *arena, RenderStyle *style)
302 {
303     if (shouldUseMenuList())
304         return new (arena) RenderMenuList(this);
305     return new (arena) DeprecatedRenderSelect(this);
306 }
307
308 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
309 {
310     bool successful = false;
311     const Vector<HTMLElement*>& items = listItems();
312
313     unsigned i;
314     for (i = 0; i < items.size(); i++) {
315         if (items[i]->hasLocalName(optionTag)) {
316             HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
317             if (option->selected()) {
318                 list.appendData(name(), option->value());
319                 successful = true;
320             }
321         }
322     }
323
324     // ### this case should not happen. make sure that we select the first option
325     // in any case. otherwise we have no consistency with the DOM interface. FIXME!
326     // we return the first one if it was a combobox select
327     if (!successful && !m_multiple && m_size <= 1 && items.size() &&
328         (items[0]->hasLocalName(optionTag))) {
329         HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[0]);
330         if (option->value().isNull())
331             list.appendData(name(), option->text().deprecatedString().stripWhiteSpace());
332         else
333             list.appendData(name(), option->value());
334         successful = true;
335     }
336
337     return successful;
338 }
339
340 int HTMLSelectElement::optionToListIndex(int optionIndex) const
341 {
342     const Vector<HTMLElement*>& items = listItems();
343     if (optionIndex < 0 || optionIndex >= int(items.size()))
344         return -1;
345
346     int listIndex = 0;
347     int optionIndex2 = 0;
348     for (;
349          optionIndex2 < int(items.size()) && optionIndex2 <= optionIndex;
350          listIndex++) { // not a typo!
351         if (items[listIndex]->hasLocalName(optionTag))
352             optionIndex2++;
353     }
354     listIndex--;
355     return listIndex;
356 }
357
358 int HTMLSelectElement::listToOptionIndex(int listIndex) const
359 {
360     const Vector<HTMLElement*>& items = listItems();
361     if (listIndex < 0 || listIndex >= int(items.size()) ||
362         !items[listIndex]->hasLocalName(optionTag))
363         return -1;
364
365     int optionIndex = 0; // actual index of option not counting OPTGROUP entries that may be in list
366     for (int i = 0; i < listIndex; i++)
367         if (items[i]->hasLocalName(optionTag))
368             optionIndex++;
369     return optionIndex;
370 }
371
372 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
373 {
374     return new HTMLOptionsCollection(this);
375 }
376
377 void HTMLSelectElement::recalcListItems() const
378 {
379     Node* current = firstChild();
380     m_listItems.clear();
381     HTMLOptionElement* foundSelected = 0;
382     while (current) {
383         if (current->hasTagName(optgroupTag) && current->firstChild()) {
384             // ### what if optgroup contains just comments? don't want one of no options in it...
385             m_listItems.append(static_cast<HTMLElement*>(current));
386             current = current->firstChild();
387         }
388         if (current->hasTagName(optionTag)) {
389             m_listItems.append(static_cast<HTMLElement*>(current));
390             if (!foundSelected && !m_multiple && m_size <= 1) {
391                 foundSelected = static_cast<HTMLOptionElement*>(current);
392                 foundSelected->m_selected = true;
393             } else if (foundSelected && !m_multiple && static_cast<HTMLOptionElement*>(current)->selected()) {
394                 foundSelected->m_selected = false;
395                 foundSelected = static_cast<HTMLOptionElement*>(current);
396             }
397         }
398         if (current->hasTagName(hrTag))
399             m_listItems.append(static_cast<HTMLElement*>(current));
400
401         Node* parent = current->parentNode();
402         current = current->nextSibling();
403         if (!current) {
404             if (parent != this)
405                 current = parent->nextSibling();
406         }
407     }
408     m_recalcListItems = false;
409 }
410
411 void HTMLSelectElement::childrenChanged()
412 {
413     setRecalcListItems();
414
415     HTMLGenericFormElement::childrenChanged();
416 }
417
418 void HTMLSelectElement::setRecalcListItems()
419 {
420     m_recalcListItems = true;
421     if (renderer()) {
422         if (shouldUseMenuList())
423             static_cast<RenderMenuList*>(renderer())->setOptionsChanged(true);
424         else
425             static_cast<DeprecatedRenderSelect*>(renderer())->setOptionsChanged(true);
426     }
427     setChanged();
428 }
429
430 void HTMLSelectElement::reset()
431 {
432     bool optionSelected = false;
433     HTMLOptionElement* firstOption = 0;
434     const Vector<HTMLElement*>& items = listItems();
435     unsigned i;
436     for (i = 0; i < items.size(); i++) {
437         if (items[i]->hasLocalName(optionTag)) {
438             HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
439             if (!option->getAttribute(selectedAttr).isNull()) {
440                 option->setSelected(true);
441                 optionSelected = true;
442             } else
443                 option->setSelected(false);
444             if (!firstOption)
445                 firstOption = option;
446         }
447     }
448     if (!optionSelected && firstOption)
449         firstOption->setSelected(true);
450     if (renderer() && !shouldUseMenuList())
451         static_cast<DeprecatedRenderSelect*>(renderer())->setSelectionChanged(true);
452     setChanged(true);
453 }
454
455 void HTMLSelectElement::notifyOptionSelected(HTMLOptionElement *selectedOption, bool selected)
456 {
457     if (selected && !m_multiple) {
458         // deselect all other options
459         const Vector<HTMLElement*>& items = listItems();
460         unsigned i;
461         for (i = 0; i < items.size(); i++) {
462             if (items[i]->hasLocalName(optionTag))
463                 static_cast<HTMLOptionElement*>(items[i])->m_selected = (items[i] == selectedOption);
464         }
465     }
466     if (renderer() && !shouldUseMenuList())
467         static_cast<DeprecatedRenderSelect*>(renderer())->setSelectionChanged(true);
468
469     setChanged(true);
470 }
471
472 void HTMLSelectElement::defaultEventHandler(Event *evt)
473 {
474     // Use key press event here since sending simulated mouse events
475     // on key down blocks the proper sending of the key press event.
476     if (evt->type() == keypressEvent) {
477         if (!renderer() || !evt->isKeyboardEvent())
478             return;
479         String keyIdentifier = static_cast<KeyboardEvent*>(evt)->keyIdentifier();
480         if (form() && keyIdentifier == "Enter") {
481             blur();
482             // Make sure the form hasn't been destroyed during the blur.
483             if (form())
484                 form()->submitClick();
485             evt->setDefaultHandled();
486         }
487         if ((keyIdentifier == "Down" || keyIdentifier == "Up" || keyIdentifier == "U+000020") && renderer() && shouldUseMenuList()) {
488             focus();
489             static_cast<RenderMenuList*>(renderer())->showPopup();
490             evt->setDefaultHandled();
491         }
492     }
493     if (evt->type() == mousedownEvent && renderer() && shouldUseMenuList()) {
494         focus();
495         static_cast<RenderMenuList*>(renderer())->showPopup();
496         evt->setDefaultHandled();
497     }
498
499     HTMLGenericFormElement::defaultEventHandler(evt);
500 }
501
502 void HTMLSelectElement::accessKeyAction(bool)
503 {
504     focus();
505 }
506
507 void HTMLSelectElement::setMultiple(bool multiple)
508 {
509     setAttribute(multipleAttr, multiple ? "" : 0);
510 }
511
512 void HTMLSelectElement::setSize(int size)
513 {
514     setAttribute(sizeAttr, String::number(size));
515 }
516
517 Node* HTMLSelectElement::namedItem(const String &name, bool caseSensitive)
518 {
519     return (options()->namedItem(name, caseSensitive));
520 }
521
522 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
523 {
524     ec = 0;
525     if (index > INT_MAX)
526         index = INT_MAX;
527     int diff = index  - length();
528     HTMLElement* before = 0;
529     // out of array bounds ? first insert empty dummies
530     if (diff > 0) {
531         setLength(index, ec);
532         // replace an existing entry ?
533     } else if (diff < 0) {
534         before = static_cast<HTMLElement*>(options()->item(index+1));
535         remove(index);
536     }
537     // finally add the new element
538     if (ec == 0) {
539         add(option, before, ec);
540         if (diff >= 0 && option->selected())
541             setSelectedIndex(index, !m_multiple);
542     }
543 }
544
545 void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
546 {
547     ec = 0;
548     if (newLen > INT_MAX)
549         newLen = INT_MAX;
550     int diff = length() - newLen;
551
552     if (diff < 0) { // add dummy elements
553         do {
554             RefPtr<Element> option = ownerDocument()->createElement("option", ec);
555             if (!option)
556                 break;
557             add(static_cast<HTMLElement*>(option.get()), 0, ec);
558             if (ec)
559                 break;
560         } while (++diff);
561     }
562     else // remove elements
563         while (diff-- > 0)
564             remove(newLen);
565 }
566
567 } // namespace