blob: a4651d1dfa6b4bec949f9d8a46684b440c633adb [file] [log] [blame]
/*
* This file is part of the DOM implementation for KDE.
*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2001 Dirk Mueller (mueller@kde.org)
* Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
* (C) 2006 Alexey Proskuryakov (ap@nypop.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#include "config.h"
#include "HTMLSelectElementImpl.h"
#include "DocumentImpl.h"
#include "EventNames.h"
#include "FormDataList.h"
#include "HTMLCollectionImpl.h"
#include "HTMLFormElementImpl.h"
#include "HTMLOptionElementImpl.h"
#include "HTMLOptionsCollectionImpl.h"
#include "cssproperties.h"
#include "cssstyleselector.h"
#include "dom2_eventsimpl.h"
#include "render_form.h"
namespace WebCore {
using namespace EventNames;
using namespace HTMLNames;
HTMLSelectElementImpl::HTMLSelectElementImpl(DocumentImpl *doc, HTMLFormElementImpl *f)
: HTMLGenericFormElementImpl(selectTag, doc, f), m_minwidth(0), m_size(0), m_multiple(false), m_recalcListItems(false)
{
}
HTMLSelectElementImpl::HTMLSelectElementImpl(const QualifiedName& tagName, DocumentImpl *doc, HTMLFormElementImpl *f)
: HTMLGenericFormElementImpl(tagName, doc, f)
{
}
HTMLSelectElementImpl::~HTMLSelectElementImpl()
{
getDocument()->deregisterMaintainsState(this);
}
bool HTMLSelectElementImpl::checkDTD(const NodeImpl* newChild)
{
return newChild->isTextNode() || newChild->hasTagName(optionTag) || newChild->hasTagName(optgroupTag) || newChild->hasTagName(hrTag) ||
newChild->hasTagName(scriptTag);
}
void HTMLSelectElementImpl::recalcStyle( StyleChange ch )
{
if (hasChangedChild() && renderer())
static_cast<RenderSelect*>(renderer())->setOptionsChanged(true);
HTMLGenericFormElementImpl::recalcStyle( ch );
}
DOMString HTMLSelectElementImpl::type() const
{
return (m_multiple ? "select-multiple" : "select-one");
}
int HTMLSelectElementImpl::selectedIndex() const
{
// return the number of the first option selected
uint o = 0;
Array<HTMLElementImpl*> items = listItems();
for (unsigned int i = 0; i < items.size(); i++) {
if (items[i]->hasLocalName(optionTag)) {
if (static_cast<HTMLOptionElementImpl*>(items[i])->selected())
return o;
o++;
}
}
return -1;
}
void HTMLSelectElementImpl::setSelectedIndex( int index )
{
// deselect all other options and select only the new one
Array<HTMLElementImpl*> items = listItems();
int listIndex;
for (listIndex = 0; listIndex < int(items.size()); listIndex++) {
if (items[listIndex]->hasLocalName(optionTag))
static_cast<HTMLOptionElementImpl*>(items[listIndex])->setSelected(false);
}
listIndex = optionToListIndex(index);
if (listIndex >= 0)
static_cast<HTMLOptionElementImpl*>(items[listIndex])->setSelected(true);
setChanged(true);
}
int HTMLSelectElementImpl::length() const
{
int len = 0;
uint i;
Array<HTMLElementImpl*> items = listItems();
for (i = 0; i < items.size(); i++) {
if (items[i]->hasLocalName(optionTag))
len++;
}
return len;
}
void HTMLSelectElementImpl::add( HTMLElementImpl *element, HTMLElementImpl *before, ExceptionCode& ec)
{
RefPtr<HTMLElementImpl> protectNewChild(element); // make sure the element is ref'd and deref'd so we don't leak it
if (!element || !(element->hasLocalName(optionTag) || element->hasLocalName(hrTag)))
return;
insertBefore(element, before, ec);
if (!ec)
setRecalcListItems();
}
void HTMLSelectElementImpl::remove(int index)
{
ExceptionCode ec = 0;
int listIndex = optionToListIndex(index);
Array<HTMLElementImpl*> items = listItems();
if (listIndex < 0 || index >= int(items.size()))
return; // ### what should we do ? remove the last item?
removeChild(items[listIndex], ec);
if (!ec)
setRecalcListItems();
}
DOMString HTMLSelectElementImpl::value()
{
uint i;
Array<HTMLElementImpl*> items = listItems();
for (i = 0; i < items.size(); i++) {
if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElementImpl*>(items[i])->selected())
return static_cast<HTMLOptionElementImpl*>(items[i])->value();
}
return DOMString("");
}
void HTMLSelectElementImpl::setValue(const DOMString &value)
{
if (value.isNull())
return;
// find the option with value() matching the given parameter
// and make it the current selection.
Array<HTMLElementImpl*> items = listItems();
for (unsigned i = 0; i < items.size(); i++)
if (items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElementImpl*>(items[i])->value() == value) {
static_cast<HTMLOptionElementImpl*>(items[i])->setSelected(true);
return;
}
}
QString HTMLSelectElementImpl::state()
{
Array<HTMLElementImpl*> items = listItems();
int l = items.count();
QString state;
for(int i = 0; i < l; i++)
if(items[i]->hasLocalName(optionTag) && static_cast<HTMLOptionElementImpl*>(items[i])->selected())
state += 'X';
else
state += '.';
return HTMLGenericFormElementImpl::state() + state;
}
void HTMLSelectElementImpl::restoreState(QStringList &_states)
{
QString _state = HTMLGenericFormElementImpl::findMatchingState(_states);
if (_state.isNull()) return;
recalcListItems();
QString state = _state;
if(!state.isEmpty() && !state.contains('X') && !m_multiple) {
// KWQString doesn't support this operation. Should never get here anyway.
//state[0] = 'X';
}
Array<HTMLElementImpl*> items = listItems();
int l = items.count();
for (int i = 0; i < l; i++) {
if (items[i]->hasLocalName(optionTag)) {
HTMLOptionElementImpl* oe = static_cast<HTMLOptionElementImpl*>(items[i]);
oe->setSelected(state[i] == 'X');
}
}
setChanged(true);
}
bool HTMLSelectElementImpl::insertBefore(PassRefPtr<NodeImpl> newChild, NodeImpl* refChild, ExceptionCode& ec)
{
bool result = HTMLGenericFormElementImpl::insertBefore(newChild, refChild, ec);
if (result)
setRecalcListItems();
return result;
}
bool HTMLSelectElementImpl::replaceChild(PassRefPtr<NodeImpl> newChild, NodeImpl *oldChild, ExceptionCode& ec)
{
bool result = HTMLGenericFormElementImpl::replaceChild(newChild, oldChild, ec);
if (result)
setRecalcListItems();
return result;
}
bool HTMLSelectElementImpl::removeChild(NodeImpl* oldChild, ExceptionCode& ec)
{
bool result = HTMLGenericFormElementImpl::removeChild(oldChild, ec);
if (result)
setRecalcListItems();
return result;
}
bool HTMLSelectElementImpl::appendChild(PassRefPtr<NodeImpl> newChild, ExceptionCode& ec)
{
bool result = HTMLGenericFormElementImpl::appendChild(newChild, ec);
if (result)
setRecalcListItems();
return result;
}
ContainerNodeImpl* HTMLSelectElementImpl::addChild(PassRefPtr<NodeImpl> newChild)
{
ContainerNodeImpl* result = HTMLGenericFormElementImpl::addChild(newChild);
if (result)
setRecalcListItems();
return result;
}
void HTMLSelectElementImpl::parseMappedAttribute(MappedAttributeImpl *attr)
{
if (attr->name() == sizeAttr) {
m_size = kMax(attr->value().toInt(), 1);
} else if (attr->name() == widthAttr) {
m_minwidth = kMax(attr->value().toInt(), 0);
} else if (attr->name() == multipleAttr) {
m_multiple = (!attr->isNull());
} else if (attr->name() == accesskeyAttr) {
// FIXME: ignore for the moment
} else if (attr->name() == onfocusAttr) {
setHTMLEventListener(focusEvent, attr);
} else if (attr->name() == onblurAttr) {
setHTMLEventListener(blurEvent, attr);
} else if (attr->name() == onchangeAttr) {
setHTMLEventListener(changeEvent, attr);
} else
HTMLGenericFormElementImpl::parseMappedAttribute(attr);
}
RenderObject *HTMLSelectElementImpl::createRenderer(RenderArena *arena, RenderStyle *style)
{
return new (arena) RenderSelect(this);
}
bool HTMLSelectElementImpl::appendFormData(FormDataList& list, bool)
{
bool successful = false;
Array<HTMLElementImpl*> items = listItems();
uint i;
for (i = 0; i < items.size(); i++) {
if (items[i]->hasLocalName(optionTag)) {
HTMLOptionElementImpl *option = static_cast<HTMLOptionElementImpl*>(items[i]);
if (option->selected()) {
list.appendData(name(), option->value());
successful = true;
}
}
}
// ### this case should not happen. make sure that we select the first option
// in any case. otherwise we have no consistency with the DOM interface. FIXME!
// we return the first one if it was a combobox select
if (!successful && !m_multiple && m_size <= 1 && items.size() &&
(items[0]->hasLocalName(optionTag))) {
HTMLOptionElementImpl *option = static_cast<HTMLOptionElementImpl*>(items[0]);
if (option->value().isNull())
list.appendData(name(), option->text().qstring().stripWhiteSpace());
else
list.appendData(name(), option->value());
successful = true;
}
return successful;
}
int HTMLSelectElementImpl::optionToListIndex(int optionIndex) const
{
Array<HTMLElementImpl*> items = listItems();
if (optionIndex < 0 || optionIndex >= int(items.size()))
return -1;
int listIndex = 0;
int optionIndex2 = 0;
for (;
optionIndex2 < int(items.size()) && optionIndex2 <= optionIndex;
listIndex++) { // not a typo!
if (items[listIndex]->hasLocalName(optionTag))
optionIndex2++;
}
listIndex--;
return listIndex;
}
int HTMLSelectElementImpl::listToOptionIndex(int listIndex) const
{
Array<HTMLElementImpl*> items = listItems();
if (listIndex < 0 || listIndex >= int(items.size()) ||
!items[listIndex]->hasLocalName(optionTag))
return -1;
int optionIndex = 0; // actual index of option not counting OPTGROUP entries that may be in list
int i;
for (i = 0; i < listIndex; i++)
if (items[i]->hasLocalName(optionTag))
optionIndex++;
return optionIndex;
}
PassRefPtr<HTMLOptionsCollectionImpl> HTMLSelectElementImpl::options()
{
return new HTMLOptionsCollectionImpl(this);
}
void HTMLSelectElementImpl::recalcListItems()
{
NodeImpl* current = firstChild();
m_listItems.resize(0);
HTMLOptionElementImpl* foundSelected = 0;
while(current) {
if (current->hasTagName(optgroupTag) && current->firstChild()) {
// ### what if optgroup contains just comments? don't want one of no options in it...
m_listItems.resize(m_listItems.size()+1);
m_listItems[m_listItems.size()-1] = static_cast<HTMLElementImpl*>(current);
current = current->firstChild();
}
if (current->hasTagName(optionTag)) {
m_listItems.resize(m_listItems.size()+1);
m_listItems[m_listItems.size()-1] = static_cast<HTMLElementImpl*>(current);
if (!foundSelected && !m_multiple && m_size <= 1) {
foundSelected = static_cast<HTMLOptionElementImpl*>(current);
foundSelected->m_selected = true;
}
else if (foundSelected && !m_multiple && static_cast<HTMLOptionElementImpl*>(current)->selected()) {
foundSelected->m_selected = false;
foundSelected = static_cast<HTMLOptionElementImpl*>(current);
}
}
if (current->hasTagName(hrTag)) {
m_listItems.resize(m_listItems.size()+1);
m_listItems[m_listItems.size()-1] = static_cast<HTMLElementImpl*>(current);
}
NodeImpl *parent = current->parentNode();
current = current->nextSibling();
if (!current) {
if (parent != this)
current = parent->nextSibling();
}
}
m_recalcListItems = false;
}
void HTMLSelectElementImpl::childrenChanged()
{
setRecalcListItems();
HTMLGenericFormElementImpl::childrenChanged();
}
void HTMLSelectElementImpl::setRecalcListItems()
{
m_recalcListItems = true;
if (renderer())
static_cast<RenderSelect*>(renderer())->setOptionsChanged(true);
setChanged();
}
void HTMLSelectElementImpl::reset()
{
Array<HTMLElementImpl*> items = listItems();
uint i;
for (i = 0; i < items.size(); i++) {
if (items[i]->hasLocalName(optionTag)) {
HTMLOptionElementImpl *option = static_cast<HTMLOptionElementImpl*>(items[i]);
bool selected = (!option->getAttribute(selectedAttr).isNull());
option->setSelected(selected);
}
}
if (renderer())
static_cast<RenderSelect*>(renderer())->setSelectionChanged(true);
setChanged(true);
}
void HTMLSelectElementImpl::notifyOptionSelected(HTMLOptionElementImpl *selectedOption, bool selected)
{
if (selected && !m_multiple) {
// deselect all other options
Array<HTMLElementImpl*> items = listItems();
uint i;
for (i = 0; i < items.size(); i++) {
if (items[i]->hasLocalName(optionTag))
static_cast<HTMLOptionElementImpl*>(items[i])->m_selected = (items[i] == selectedOption);
}
}
if (renderer())
static_cast<RenderSelect*>(renderer())->setSelectionChanged(true);
setChanged(true);
}
void HTMLSelectElementImpl::defaultEventHandler(EventImpl *evt)
{
// Use key press event here since sending simulated mouse events
// on key down blocks the proper sending of the key press event.
if (evt->type() == keypressEvent) {
if (!m_form || !renderer() || !evt->isKeyboardEvent())
return;
DOMString key = static_cast<KeyboardEventImpl *>(evt)->keyIdentifier();
if (key == "Enter") {
m_form->submitClick();
evt->setDefaultHandled();
}
}
HTMLGenericFormElementImpl::defaultEventHandler(evt);
}
void HTMLSelectElementImpl::accessKeyAction(bool)
{
focus();
}
void HTMLSelectElementImpl::setMultiple(bool multiple)
{
setAttribute(multipleAttr, multiple ? "" : 0);
}
void HTMLSelectElementImpl::setSize(int size)
{
setAttribute(sizeAttr, QString::number(size));
}
} // namespace