ca5c736d20102f001c492231ab9184b5ac50fe77
[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 "Event.h"
33 #include "EventNames.h"
34 #include "FormDataList.h"
35 #include "HTMLFormElement.h"
36 #include "HTMLNames.h"
37 #include "HTMLOptionElement.h"
38 #include "HTMLOptionsCollection.h"
39 #include "KeyboardEvent.h"
40 #include "DeprecatedRenderSelect.h"
41 #include "cssstyleselector.h"
42 #include <wtf/Vector.h>
43
44 namespace WebCore {
45
46 using namespace EventNames;
47 using namespace HTMLNames;
48
49 HTMLSelectElement::HTMLSelectElement(Document* doc, HTMLFormElement* f)
50     : HTMLGenericFormElement(selectTag, doc, f)
51     , m_minwidth(0)
52     , m_size(0)
53     , m_multiple(false)
54     , m_recalcListItems(false)
55 {
56     document()->registerFormElementWithState(this);
57 }
58
59 HTMLSelectElement::HTMLSelectElement(const QualifiedName& tagName, Document* doc, HTMLFormElement* f)
60     : HTMLGenericFormElement(tagName, doc, f), m_minwidth(0), m_size(0), m_multiple(false), m_recalcListItems(false)
61 {
62     document()->registerFormElementWithState(this);
63 }
64
65 HTMLSelectElement::~HTMLSelectElement()
66 {
67     document()->deregisterFormElementWithState(this);
68 }
69
70 bool HTMLSelectElement::checkDTD(const Node* newChild)
71 {
72     return newChild->isTextNode() || newChild->hasTagName(optionTag) || newChild->hasTagName(optgroupTag) || newChild->hasTagName(hrTag) ||
73            newChild->hasTagName(scriptTag);
74 }
75
76 void HTMLSelectElement::recalcStyle( StyleChange ch )
77 {
78     if (hasChangedChild() && renderer())
79         static_cast<DeprecatedRenderSelect*>(renderer())->setOptionsChanged(true);
80
81     HTMLGenericFormElement::recalcStyle( ch );
82 }
83
84
85 const AtomicString& HTMLSelectElement::type() const
86 {
87     static const AtomicString selectMultiple("select-multiple");
88     static const AtomicString selectOne("select-one");
89     return m_multiple ? selectMultiple : selectOne;
90 }
91
92 int HTMLSelectElement::selectedIndex() const
93 {
94     // return the number of the first option selected
95     unsigned o = 0;
96     Vector<HTMLElement*> items = listItems();
97     for (unsigned int i = 0; i < items.size(); i++) {
98         if (items[i]->hasLocalName(optionTag)) {
99             if (static_cast<HTMLOptionElement*>(items[i])->selected())
100                 return o;
101             o++;
102         }
103     }
104     return -1;
105 }
106
107 void HTMLSelectElement::setSelectedIndex( int index, bool deselect )
108 {
109     // deselect all other options and select only the new one
110     Vector<HTMLElement*> items = listItems();
111     int listIndex;
112     if (deselect) {
113         for (listIndex = 0; listIndex < int(items.size()); listIndex++) {
114             if (items[listIndex]->hasLocalName(optionTag))
115                 static_cast<HTMLOptionElement*>(items[listIndex])->setSelected(false);
116         }
117     }
118     listIndex = optionToListIndex(index);
119     if (listIndex >= 0)
120         static_cast<HTMLOptionElement*>(items[listIndex])->setSelected(true);
121
122     setChanged(true);
123 }
124
125 int HTMLSelectElement::length() const
126 {
127     int len = 0;
128     unsigned i;
129     Vector<HTMLElement*> items = listItems();
130     for (i = 0; i < items.size(); i++) {
131         if (items[i]->hasLocalName(optionTag))
132             len++;
133     }
134     return len;
135 }
136
137 void HTMLSelectElement::add( HTMLElement *element, HTMLElement *before, ExceptionCode& ec)
138 {
139     RefPtr<HTMLElement> protectNewChild(element); // make sure the element is ref'd and deref'd so we don't leak it
140
141     if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
142         return;
143
144     insertBefore(element, before, ec);
145     if (!ec)
146         setRecalcListItems();
147 }
148
149 void HTMLSelectElement::remove(int index)
150 {
151     ExceptionCode ec = 0;
152     int listIndex = optionToListIndex(index);
153
154     Vector<HTMLElement*> items = listItems();
155     if (listIndex < 0 || index >= int(items.size()))
156         return; // ### what should we do ? remove the last item?
157
158     removeChild(items[listIndex], ec);
159     if (!ec)
160         setRecalcListItems();
161 }
162
163 String HTMLSelectElement::value()
164 {
165     unsigned i;
166     Vector<HTMLElement*> items = listItems();
167     for (i = 0; i < items.size(); i++) {
168         if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->selected())
169             return static_cast<HTMLOptionElement*>(items[i])->value();
170     }
171     return String("");
172 }
173
174 void HTMLSelectElement::setValue(const String &value)
175 {
176     if (value.isNull())
177         return;
178     // find the option with value() matching the given parameter
179     // and make it the current selection.
180     Vector<HTMLElement*> items = listItems();
181     for (unsigned i = 0; i < items.size(); i++)
182         if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(items[i])->value() == value) {
183             static_cast<HTMLOptionElement*>(items[i])->setSelected(true);
184             return;
185         }
186 }
187
188 String HTMLSelectElement::stateValue() const
189 {
190     Vector<HTMLElement*> items = listItems();
191     int l = items.size();
192     Vector<char, 1024> characters(l);
193     for (int i = 0; i < l; ++i) {
194         HTMLElement* e = items[i];
195         bool selected = e->hasLocalName(optionTag) && static_cast<HTMLOptionElement*>(e)->selected();
196         characters[i] = selected ? 'X' : '.';
197     }
198     return String(characters, l);
199 }
200
201 void HTMLSelectElement::restoreState(const String& state)
202 {
203     recalcListItems();
204
205     Vector<HTMLElement*> items = listItems();
206     int l = items.size();
207     for (int i = 0; i < l; i++)
208         if (items[i]->hasLocalName(optionTag))
209             static_cast<HTMLOptionElement*>(items[i])->setSelected(state[i] == 'X');
210     setChanged(true);
211 }
212
213 bool HTMLSelectElement::insertBefore(PassRefPtr<Node> newChild, Node* refChild, ExceptionCode& ec)
214 {
215     bool result = HTMLGenericFormElement::insertBefore(newChild, refChild, ec);
216     if (result)
217         setRecalcListItems();
218     return result;
219 }
220
221 bool HTMLSelectElement::replaceChild(PassRefPtr<Node> newChild, Node *oldChild, ExceptionCode& ec)
222 {
223     bool result = HTMLGenericFormElement::replaceChild(newChild, oldChild, ec);
224     if (result)
225         setRecalcListItems();
226     return result;
227 }
228
229 bool HTMLSelectElement::removeChild(Node* oldChild, ExceptionCode& ec)
230 {
231     bool result = HTMLGenericFormElement::removeChild(oldChild, ec);
232     if (result)
233         setRecalcListItems();
234     return result;
235 }
236
237 bool HTMLSelectElement::appendChild(PassRefPtr<Node> newChild, ExceptionCode& ec)
238 {
239     bool result = HTMLGenericFormElement::appendChild(newChild, ec);
240     if (result)
241         setRecalcListItems();
242     return result;
243 }
244
245 ContainerNode* HTMLSelectElement::addChild(PassRefPtr<Node> newChild)
246 {
247     ContainerNode* result = HTMLGenericFormElement::addChild(newChild);
248     if (result)
249         setRecalcListItems();
250     return result;
251 }
252
253 void HTMLSelectElement::parseMappedAttribute(MappedAttribute *attr)
254 {
255     if (attr->name() == sizeAttr) {
256         m_size = max(attr->value().toInt(), 1);
257     } else if (attr->name() == widthAttr) {
258         m_minwidth = max(attr->value().toInt(), 0);
259     } else if (attr->name() == multipleAttr) {
260         m_multiple = (!attr->isNull());
261     } else if (attr->name() == accesskeyAttr) {
262         // FIXME: ignore for the moment
263     } else if (attr->name() == onfocusAttr) {
264         setHTMLEventListener(focusEvent, attr);
265     } else if (attr->name() == onblurAttr) {
266         setHTMLEventListener(blurEvent, attr);
267     } else if (attr->name() == onchangeAttr) {
268         setHTMLEventListener(changeEvent, attr);
269     } else
270         HTMLGenericFormElement::parseMappedAttribute(attr);
271 }
272
273 RenderObject *HTMLSelectElement::createRenderer(RenderArena *arena, RenderStyle *style)
274 {
275     return new (arena) DeprecatedRenderSelect(this);
276 }
277
278 bool HTMLSelectElement::appendFormData(FormDataList& list, bool)
279 {
280     bool successful = false;
281     Vector<HTMLElement*> items = listItems();
282
283     unsigned i;
284     for (i = 0; i < items.size(); i++) {
285         if (items[i]->hasLocalName(optionTag)) {
286             HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
287             if (option->selected()) {
288                 list.appendData(name(), option->value());
289                 successful = true;
290             }
291         }
292     }
293
294     // ### this case should not happen. make sure that we select the first option
295     // in any case. otherwise we have no consistency with the DOM interface. FIXME!
296     // we return the first one if it was a combobox select
297     if (!successful && !m_multiple && m_size <= 1 && items.size() &&
298         (items[0]->hasLocalName(optionTag))) {
299         HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[0]);
300         if (option->value().isNull())
301             list.appendData(name(), option->text().deprecatedString().stripWhiteSpace());
302         else
303             list.appendData(name(), option->value());
304         successful = true;
305     }
306
307     return successful;
308 }
309
310 int HTMLSelectElement::optionToListIndex(int optionIndex) const
311 {
312     Vector<HTMLElement*> items = listItems();
313     if (optionIndex < 0 || optionIndex >= int(items.size()))
314         return -1;
315
316     int listIndex = 0;
317     int optionIndex2 = 0;
318     for (;
319          optionIndex2 < int(items.size()) && optionIndex2 <= optionIndex;
320          listIndex++) { // not a typo!
321         if (items[listIndex]->hasLocalName(optionTag))
322             optionIndex2++;
323     }
324     listIndex--;
325     return listIndex;
326 }
327
328 int HTMLSelectElement::listToOptionIndex(int listIndex) const
329 {
330     Vector<HTMLElement*> items = listItems();
331     if (listIndex < 0 || listIndex >= int(items.size()) ||
332         !items[listIndex]->hasLocalName(optionTag))
333         return -1;
334
335     int optionIndex = 0; // actual index of option not counting OPTGROUP entries that may be in list
336     int i;
337     for (i = 0; i < listIndex; i++)
338         if (items[i]->hasLocalName(optionTag))
339             optionIndex++;
340     return optionIndex;
341 }
342
343 PassRefPtr<HTMLOptionsCollection> HTMLSelectElement::options()
344 {
345     return new HTMLOptionsCollection(this);
346 }
347
348 void HTMLSelectElement::recalcListItems()
349 {
350     Node* current = firstChild();
351     m_listItems.clear();
352     HTMLOptionElement* foundSelected = 0;
353     while (current) {
354         if (current->hasTagName(optgroupTag) && current->firstChild()) {
355             // ### what if optgroup contains just comments? don't want one of no options in it...
356             m_listItems.append(static_cast<HTMLElement*>(current));
357             current = current->firstChild();
358         }
359         if (current->hasTagName(optionTag)) {
360             m_listItems.append(static_cast<HTMLElement*>(current));
361             if (!foundSelected && !m_multiple && m_size <= 1) {
362                 foundSelected = static_cast<HTMLOptionElement*>(current);
363                 foundSelected->m_selected = true;
364             } else if (foundSelected && !m_multiple && static_cast<HTMLOptionElement*>(current)->selected()) {
365                 foundSelected->m_selected = false;
366                 foundSelected = static_cast<HTMLOptionElement*>(current);
367             }
368         }
369         if (current->hasTagName(hrTag))
370             m_listItems.append(static_cast<HTMLElement*>(current));
371
372         Node* parent = current->parentNode();
373         current = current->nextSibling();
374         if (!current) {
375             if (parent != this)
376                 current = parent->nextSibling();
377         }
378     }
379     m_recalcListItems = false;
380 }
381
382 void HTMLSelectElement::childrenChanged()
383 {
384     setRecalcListItems();
385
386     HTMLGenericFormElement::childrenChanged();
387 }
388
389 void HTMLSelectElement::setRecalcListItems()
390 {
391     m_recalcListItems = true;
392     if (renderer())
393         static_cast<DeprecatedRenderSelect*>(renderer())->setOptionsChanged(true);
394     setChanged();
395 }
396
397 void HTMLSelectElement::reset()
398 {
399     bool optionSelected = false;
400     HTMLOptionElement* firstOption = 0;
401     Vector<HTMLElement*> items = listItems();
402     unsigned i;
403     for (i = 0; i < items.size(); i++) {
404         if (items[i]->hasLocalName(optionTag)) {
405             HTMLOptionElement *option = static_cast<HTMLOptionElement*>(items[i]);
406             if (!option->getAttribute(selectedAttr).isNull()) {
407                 option->setSelected(true);
408                 optionSelected = true;
409             } else
410                 option->setSelected(false);
411             if (!firstOption)
412                 firstOption = option;
413         }
414     }
415     if (!optionSelected && firstOption)
416         firstOption->setSelected(true);
417     if (renderer())
418         static_cast<DeprecatedRenderSelect*>(renderer())->setSelectionChanged(true);
419     setChanged(true);
420 }
421
422 void HTMLSelectElement::notifyOptionSelected(HTMLOptionElement *selectedOption, bool selected)
423 {
424     if (selected && !m_multiple) {
425         // deselect all other options
426         Vector<HTMLElement*> items = listItems();
427         unsigned i;
428         for (i = 0; i < items.size(); i++) {
429             if (items[i]->hasLocalName(optionTag))
430                 static_cast<HTMLOptionElement*>(items[i])->m_selected = (items[i] == selectedOption);
431         }
432     }
433     if (renderer())
434         static_cast<DeprecatedRenderSelect*>(renderer())->setSelectionChanged(true);
435
436     setChanged(true);
437 }
438
439 void HTMLSelectElement::defaultEventHandler(Event *evt)
440 {
441     // Use key press event here since sending simulated mouse events
442     // on key down blocks the proper sending of the key press event.
443     if (evt->type() == keypressEvent) {
444         if (!form() || !renderer() || !evt->isKeyboardEvent())
445             return;
446         if (static_cast<KeyboardEvent*>(evt)->keyIdentifier() == "Enter") {
447             form()->submitClick();
448             evt->setDefaultHandled();
449         }
450     }
451     HTMLGenericFormElement::defaultEventHandler(evt);
452 }
453
454 void HTMLSelectElement::accessKeyAction(bool)
455 {
456     focus();
457 }
458
459 void HTMLSelectElement::setMultiple(bool multiple)
460 {
461     setAttribute(multipleAttr, multiple ? "" : 0);
462 }
463
464 void HTMLSelectElement::setSize(int size)
465 {
466     setAttribute(sizeAttr, String::number(size));
467 }
468
469 Node* HTMLSelectElement::namedItem(const String &name, bool caseSensitive)
470 {
471     return (options()->namedItem(name, caseSensitive));
472 }
473
474 void HTMLSelectElement::setOption(unsigned index, HTMLOptionElement* option, ExceptionCode& ec)
475 {
476     ec = 0;
477     if (index > INT_MAX)
478         index = INT_MAX;
479     int diff = index  - length();
480     HTMLElement* before = 0;
481     // out of array bounds ? first insert empty dummies
482     if (diff > 0) {
483         setLength(index, ec);
484         // replace an existing entry ?
485     } else if (diff < 0) {
486         before = static_cast<HTMLElement*>(options()->item(index+1));
487         remove(index);
488     }
489     // finally add the new element
490     if (ec == 0) {
491         add(option, before, ec);
492         if (diff >= 0)
493             setSelectedIndex(index, !m_multiple);
494     }
495 }
496
497 void HTMLSelectElement::setLength(unsigned newLen, ExceptionCode& ec)
498 {
499     ec = 0;
500     if (newLen > INT_MAX)
501         newLen = INT_MAX;
502     int diff = length() - newLen;
503
504     if (diff < 0) { // add dummy elements
505         do {
506             RefPtr<Element> option = ownerDocument()->createElement("option", ec);
507             if (!option)
508                 break;
509             add(static_cast<HTMLElement*>(option.get()), 0, ec);
510             if (ec)
511                 break;
512         } while (++diff);
513     }
514     else // remove elements
515         while (diff-- > 0)
516             remove(newLen);
517 }
518
519 } // namespace