diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | ce4a32fe52ef09d8f5ff1dd22c001110902b60a2 (patch) | |
tree | 5ac38a06f3dde268dc7927dc155896926aaf7012 /khtml/html/html_formimpl.cpp | |
download | tdelibs-ce4a32fe52ef09d8f5ff1dd22c001110902b60a2.tar.gz tdelibs-ce4a32fe52ef09d8f5ff1dd22c001110902b60a2.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdelibs@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'khtml/html/html_formimpl.cpp')
-rw-r--r-- | khtml/html/html_formimpl.cpp | 2984 |
1 files changed, 2984 insertions, 0 deletions
diff --git a/khtml/html/html_formimpl.cpp b/khtml/html/html_formimpl.cpp new file mode 100644 index 000000000..e59098499 --- /dev/null +++ b/khtml/html/html_formimpl.cpp @@ -0,0 +1,2984 @@ +/* + * 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) + * (C) 2004, 2005, 2006 Apple Computer, Inc. + * + * 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., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#undef FORMS_DEBUG +//#define FORMS_DEBUG + +#include "html/html_formimpl.h" + +#include "khtmlview.h" +#include "khtml_part.h" +#include "html/html_documentimpl.h" +#include "khtml_settings.h" +#include "misc/htmlhashes.h" + +#include "css/cssstyleselector.h" +#include "css/cssproperties.h" +#include "css/cssvalues.h" +#include "css/csshelper.h" +#include "xml/dom_textimpl.h" +#include "xml/dom_docimpl.h" +#include "xml/dom2_eventsimpl.h" +#include "xml/dom_restyler.h" +#include "khtml_ext.h" + +#include "rendering/render_form.h" + +#include <kcharsets.h> +#include <kglobal.h> +#include <kdebug.h> +#include <kmimetype.h> +#include <kmessagebox.h> +#include <kapplication.h> +#include <klocale.h> +#ifndef KHTML_NO_WALLET +#include <kwallet.h> +#endif +#include <netaccess.h> +#include <kfileitem.h> +#include <qfile.h> +#include <qdir.h> +#include <qtextcodec.h> + +// for keygen +#include <qstring.h> +#include <ksslkeygen.h> + +#include <assert.h> + + +using namespace DOM; +using namespace khtml; + +HTMLFormElementImpl::HTMLFormElementImpl(DocumentImpl *doc, bool implicit) + : HTMLElementImpl(doc) +{ + m_implicit = implicit; + m_post = false; + m_multipart = false; + m_autocomplete = true; + m_insubmit = false; + m_doingsubmit = false; + m_inreset = false; + m_enctype = "application/x-www-form-urlencoded"; + m_boundary = "----------" + KApplication::randomString( 42 + 13 ); + m_acceptcharset = "UNKNOWN"; + m_malformed = false; +} + +HTMLFormElementImpl::~HTMLFormElementImpl() +{ + if (getDocument() && getDocument()->view() && getDocument()->view()->part()) { + getDocument()->view()->part()->dequeueWallet(this); + } + QPtrListIterator<HTMLGenericFormElementImpl> it(formElements); + for (; it.current(); ++it) + it.current()->m_form = 0; + QPtrListIterator<HTMLImageElementImpl> it2(imgElements); + for (; it2.current(); ++it2) + it2.current()->m_form = 0; +} + +NodeImpl::Id HTMLFormElementImpl::id() const +{ + return ID_FORM; +} + +long HTMLFormElementImpl::length() const +{ + int len = 0; + QPtrListIterator<HTMLGenericFormElementImpl> it(formElements); + for (; it.current(); ++it) + if (it.current()->isEnumeratable()) + ++len; + + return len; +} + +static QCString encodeCString(const QCString& e) +{ + // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 + // safe characters like NS handles them for compatibility + static const char *safe = "-._*"; + QCString encoded(( e.length()+e.contains( '\n' ) )*3 + +e.contains('\r') * 3 + 1); + int enclen = 0; + bool crmissing = false; + unsigned char oldc; + unsigned char c ='\0'; + + //QCString orig(e.data(), e.size()); + + unsigned len = e.length(); + for(unsigned pos = 0; pos < len; pos++) { + oldc = c; + c = e[pos]; + + if (crmissing && c != '\n') { + encoded[enclen++] = '%'; + encoded[enclen++] = '0'; + encoded[enclen++] = 'D'; + crmissing = false; + } + + if ( (( c >= 'A') && ( c <= 'Z')) || + (( c >= 'a') && ( c <= 'z')) || + (( c >= '0') && ( c <= '9')) || + (strchr(safe, c)) + ) + encoded[enclen++] = c; + else if ( c == ' ' ) + encoded[enclen++] = '+'; + else if ( c == '\n' ) + { + encoded[enclen++] = '%'; + encoded[enclen++] = '0'; + encoded[enclen++] = 'D'; + encoded[enclen++] = '%'; + encoded[enclen++] = '0'; + encoded[enclen++] = 'A'; + crmissing = false; + } + else if (c == '\r' && oldc != '\n') { + crmissing = true; + } + else if ( c != '\r' ) + { + encoded[enclen++] = '%'; + unsigned int h = c / 16; + h += (h > 9) ? ('A' - 10) : '0'; + encoded[enclen++] = h; + + unsigned int l = c % 16; + l += (l > 9) ? ('A' - 10) : '0'; + encoded[enclen++] = l; + } + } + encoded[enclen++] = '\0'; + encoded.truncate(enclen); + + return encoded; +} + +// ### This function only encodes to numeric ampersand escapes, +// ### we could use standard ampersand values as well. +inline static QString escapeUnencodeable(const QTextCodec* codec, const QString& s) { + QString enc_string; + const int len = s.length(); + for(int i=0; i <len; ++i) { + const QChar c = s[i]; + if (codec->canEncode(c)) + enc_string.append(c); + else { + QString ampersandEscape; + ampersandEscape.sprintf("&#%u;", c.unicode()); + enc_string.append(ampersandEscape); + } + } + return enc_string; +} + +inline static QCString fixUpfromUnicode(const QTextCodec* codec, const QString& s) +{ + QCString str = codec->fromUnicode(escapeUnencodeable(codec,s)); + str.truncate(str.length()); + return str; +} + +QByteArray HTMLFormElementImpl::formData(bool& ok) +{ +#ifdef FORMS_DEBUG + kdDebug( 6030 ) << "form: formData()" << endl; +#endif + + QByteArray form_data(0); + QCString enc_string = ""; // used for non-multipart data + + // find out the QTextcodec to use + const QString str = m_acceptcharset.string(); + const QChar space(' '); + const unsigned int strLength = str.length(); + for(unsigned int i=0; i < strLength; ++i) if(str[i].latin1() == ',') str[i] = space; + const QStringList charsets = QStringList::split(' ', str); + QTextCodec* codec = 0; + KHTMLView *view = getDocument()->view(); + { + QStringList::ConstIterator it = charsets.begin(); + const QStringList::ConstIterator itEnd = charsets.end(); + + for ( ; it != itEnd; ++it ) + { + QString enc = (*it); + if(enc.contains("UNKNOWN")) + { + // use standard document encoding + enc = "ISO 8859-1"; + if(view && view->part()) + enc = view->part()->encoding(); + } + if((codec = KGlobal::charsets()->codecForName(enc.latin1()))) + break; + } + } + if(!codec) + codec = QTextCodec::codecForLocale(); + + // we need to map visual hebrew to logical hebrew, as the web + // server alsways expects responses in logical ordering + if ( codec->mibEnum() == 11 ) + codec = QTextCodec::codecForMib( 85 ); + + m_encCharset = codec->name(); + const unsigned int m_encCharsetLength = m_encCharset.length(); + for(unsigned int i=0; i < m_encCharsetLength; ++i) + m_encCharset[i] = m_encCharset[i].latin1() == ' ' ? QChar('-') : m_encCharset[i].lower(); + + QStringList fileUploads, fileNotUploads; + + for (QPtrListIterator<HTMLGenericFormElementImpl> it(formElements); it.current(); ++it) { + HTMLGenericFormElementImpl* const current = it.current(); + khtml::encodingList lst; + + if (!current->disabled() && current->encoding(codec, lst, m_multipart)) + { + //kdDebug(6030) << "adding name '" << current->name().string() << "'" << endl; + khtml::encodingList::ConstIterator it = lst.begin(); + const khtml::encodingList::ConstIterator itEnd = lst.end(); + for( it = lst.begin(); it != itEnd; ++it ) + { + if (!m_multipart) + { + // handle ISINDEX / <input name=isindex> special + // but only if its the first entry + if ( enc_string.isEmpty() && *it == "isindex" ) { + ++it; + enc_string += encodeCString( *it ); + } + else { + if(!enc_string.isEmpty()) + enc_string += '&'; + + enc_string += encodeCString(*it); + enc_string += "="; + ++it; + enc_string += encodeCString(*it); + } + } + else + { + QCString hstr("--"); + hstr += m_boundary.latin1(); + hstr += "\r\n"; + hstr += "Content-Disposition: form-data; name=\""; + hstr += (*it).data(); + hstr += "\""; + + // if the current type is FILE, then we also need to + // include the filename + if (current->id() == ID_INPUT && + static_cast<HTMLInputElementImpl*>(current)->inputType() == HTMLInputElementImpl::FILE && + current->renderer()) + { + KURL path; + QString val = static_cast<HTMLInputElementImpl*>(current)->value().string().stripWhiteSpace(); + if (!val.isEmpty() && + QDir::isRelativePath(val) && + QFile::exists(KGlobalSettings::documentPath() + val)) { + path.setPath(KGlobalSettings::documentPath() + val); + } else { + path = KURL::fromPathOrURL(val); + } + + hstr += fixUpfromUnicode(codec, "; filename=\"" + path.fileName() + "\""); + if (path.isValid()) { + fileUploads << path.prettyURL(0, KURL::StripFileProtocol); + const KMimeType::Ptr ptr = KMimeType::findByURL(path); + if (!ptr->name().isEmpty()) { + hstr += "\r\nContent-Type: "; + hstr += ptr->name().ascii(); + } + } else if (!val.isEmpty()) { + fileNotUploads << path.prettyURL(0, KURL::StripFileProtocol); + } + } + + hstr += "\r\n\r\n"; + ++it; + + // append body + const unsigned int old_size = form_data.size(); + form_data.resize( old_size + hstr.length() + (*it).size() + 1); + memcpy(form_data.data() + old_size, hstr.data(), hstr.length()); + memcpy(form_data.data() + old_size + hstr.length(), *it, (*it).size()); + form_data[form_data.size()-2] = '\r'; + form_data[form_data.size()-1] = '\n'; + + // reset unsubmittedFormChange flag + if (current->id() == ID_INPUT && + static_cast<HTMLInputElementImpl*>(current)->inputType() == HTMLInputElementImpl::TEXT) + static_cast<HTMLInputElementImpl*>(current)->setUnsubmittedFormChange(false); + + if (current->id() == ID_TEXTAREA) + static_cast<HTMLTextAreaElementImpl*>(current)->setUnsubmittedFormChange(false); + + } + } + } + } + + if (fileNotUploads.count()) { + const int result = KMessageBox::warningContinueCancelList( 0, + i18n("The following files will not be uploaded" + " because they could not be found.\n" + "Do you want to continue?"), + fileNotUploads, + i18n("Submit Confirmation"),KGuiItem(i18n("&Submit Anyway"))); + + + if (result == KMessageBox::Cancel) { + ok = false; + return QByteArray(); + } + } + + if (fileUploads.count()) { + const int result = KMessageBox::warningContinueCancelList( 0, + i18n("You're about to transfer the following files from " + "your local computer to the Internet.\n" + "Do you really want to continue?"), + fileUploads, + i18n("Send Confirmation"),KGuiItem(i18n("&Send Files"))); + + + if (result == KMessageBox::Cancel) { + ok = false; + return QByteArray(); + } + } + + if (m_multipart) + enc_string = ("--" + m_boundary + "--\r\n").ascii(); + + const int old_size = form_data.size(); + form_data.resize( form_data.size() + enc_string.length() ); + memcpy(form_data.data() + old_size, enc_string.data(), enc_string.length() ); + + ok = true; + return form_data; +} + +void HTMLFormElementImpl::setEnctype( const DOMString& type ) +{ + if(type.string().find("multipart", 0, false) != -1 || type.string().find("form-data", 0, false) != -1) + { + m_enctype = "multipart/form-data"; + m_multipart = true; + m_post = true; + } else if (type.string().find("text", 0, false) != -1 || type.string().find("plain", 0, false) != -1) + { + m_enctype = "text/plain"; + m_multipart = false; + } + else + { + m_enctype = "application/x-www-form-urlencoded"; + m_multipart = false; + } + m_encCharset = QString::null; +} + +static QString calculateAutoFillKey(const HTMLFormElementImpl& e) +{ + KURL k(e.getDocument()->URL()); + k.setRef(QString::null); + k.setQuery(QString::null); + // ensure that we have the user / password inside the url + // otherwise we might have a potential security problem + // by saving passwords under wrong lookup key. + const QString name = e.getAttribute(ATTR_NAME).string().stripWhiteSpace(); + const QRegExp re("[;,!]"); + const QStringList url = QStringList::split(re, k.url()); + return url[0] + '#' + name; +} + +void HTMLFormElementImpl::doAutoFill() +{ +#ifndef KHTML_NO_WALLET + const QString key = calculateAutoFillKey(*this); + + if (KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), + KWallet::Wallet::FormDataFolder(), + key)) + return; + + // assert(view()) + getDocument()->view()->part()->openWallet(this); +#endif // KHTML_NO_WALLET +} + + +void HTMLFormElementImpl::walletOpened(KWallet::Wallet *w) { +#ifndef KHTML_NO_WALLET + assert(w); + const QString key = calculateAutoFillKey(*this); + if (!w->hasFolder(KWallet::Wallet::FormDataFolder())) { + return; // failed + } + w->setFolder(KWallet::Wallet::FormDataFolder()); + QMap<QString, QString> map; + if (w->readMap(key, map)) + return; // failed, abort + + for (QPtrListIterator<HTMLGenericFormElementImpl> it(formElements); it.current(); ++it) { + if (it.current()->id() == ID_INPUT) { + HTMLInputElementImpl* const current = static_cast<HTMLInputElementImpl*>(it.current()); + if ((current->inputType() == HTMLInputElementImpl::PASSWORD || + current->inputType() == HTMLInputElementImpl::TEXT) && + !current->readOnly() && + map.contains(current->name().string())) { + getDocument()->setFocusNode(current); + current->setValue(map[current->name().string()]); + } + } + } +#endif // KHTML_NO_WALLET +} + +void HTMLFormElementImpl::submitFromKeyboard() +{ + // Activate the first nondisabled submit button + // if there is none, do a submit anyway if not more + // than one <input type=text> or <input type=password> + unsigned int inputtext = 0; + for (QPtrListIterator<HTMLGenericFormElementImpl> it(formElements); it.current(); ++it) { + if (it.current()->id() == ID_BUTTON) { + HTMLButtonElementImpl* const current = static_cast<HTMLButtonElementImpl *>(it.current()); + if (current->buttonType() == HTMLButtonElementImpl::SUBMIT && !current->disabled()) { + current->click(); + return; + } + } else if (it.current()->id() == ID_INPUT) { + HTMLInputElementImpl* const current = static_cast<HTMLInputElementImpl *>(it.current()); + switch(current->inputType()) { + case HTMLInputElementImpl::SUBMIT: + case HTMLInputElementImpl::IMAGE: + if(!current->disabled()) { + current->click(); + return; + } + break; + case HTMLInputElementImpl::TEXT: + case HTMLInputElementImpl::PASSWORD: + ++inputtext; + default: + break; + } + } + } + + if (inputtext <= 1) + prepareSubmit(); +} + + +void HTMLFormElementImpl::gatherWalletData() +{ +#ifndef KHTML_NO_WALLET + KHTMLView* const view = getDocument()->view(); + // check if we have any password input's + m_walletMap.clear(); + m_havePassword = false; + m_haveTextarea = false; + const KURL formUrl(getDocument()->URL()); + if (view && !view->nonPasswordStorableSite(formUrl.host())) { + for (QPtrListIterator<HTMLGenericFormElementImpl> it(formElements); it.current(); ++it) { + if (it.current()->id() == ID_INPUT) { + HTMLInputElementImpl* const c = static_cast<HTMLInputElementImpl*> (it.current()); + if ((c->inputType() == HTMLInputElementImpl::TEXT || + c->inputType() == HTMLInputElementImpl::PASSWORD) && + !c->readOnly()) { + m_walletMap.insert(c->name().string(), c->value().string()); + if (c->inputType() == HTMLInputElementImpl::PASSWORD && + !c->value().isEmpty()) + m_havePassword = true; + } + } + else if (it.current()->id() == ID_TEXTAREA) + m_haveTextarea = true; + } + } +#endif // KHTML_NO_WALLET +} + + +bool HTMLFormElementImpl::prepareSubmit() +{ + KHTMLView* const view = getDocument()->view(); + if(m_insubmit || !view || !view->part() || view->part()->onlyLocalReferences()) + return m_insubmit; + + gatherWalletData(); + + m_insubmit = true; + m_doingsubmit = false; + + if ( dispatchHTMLEvent(EventImpl::SUBMIT_EVENT,true,true) && !m_doingsubmit ) + m_doingsubmit = true; + + m_insubmit = false; + + if ( m_doingsubmit ) + submit(); + + return m_doingsubmit; +} + +void HTMLFormElementImpl::submit( ) +{ + if ( m_insubmit ) { + m_doingsubmit = true; + return; + } + + m_insubmit = true; + +#ifdef FORMS_DEBUG + kdDebug( 6030 ) << "submitting!" << endl; +#endif + + bool ok; + KHTMLView* const view = getDocument()->view(); + const QByteArray form_data = formData(ok); + const KURL formUrl(getDocument()->URL()); + + if (ok && view) { + if (m_walletMap.isEmpty()) { + gatherWalletData(); + } +#ifndef KHTML_NO_WALLET + if (m_havePassword && !m_haveTextarea && KWallet::Wallet::isEnabled()) { + const QString key = calculateAutoFillKey(*this); + const bool doesnotexist = KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::FormDataFolder(), key); + KWallet::Wallet* const w = view->part()->wallet(); + bool login_changed = false; + + if (!doesnotexist && w) { + // check if the login information changed from what + // we had so far. + if (w->hasFolder(KWallet::Wallet::FormDataFolder())) { + w->setFolder(KWallet::Wallet::FormDataFolder()); + QMap<QString, QString> map; + if (!w->readMap(key, map)) { + QMapConstIterator<QString, QString> it = map.begin(); + const QMapConstIterator<QString, QString> itEnd = map.end(); + for ( ; it != itEnd; ++it ) + if ( map[it.key()] != m_walletMap[it.key()] ) { + login_changed = true; + break; + } + } else { + login_changed = true; + } + } + } + + if ( doesnotexist || !w || login_changed ) { + // TODO use KMessageBox::questionYesNoCancel() again, if you can pass a KGuiItem for Cancel + KDialogBase* const dialog = new KDialogBase(i18n("Save Login Information"), + KDialogBase::Yes | KDialogBase::No | KDialogBase::Cancel, + KDialogBase::Yes, KDialogBase::Cancel, + 0, "questionYesNoCancel", true, true, + i18n("Store"), KGuiItem(i18n("Ne&ver for This Site")), i18n("Do Not Store")); + + bool checkboxResult = false; + const int savePassword = KMessageBox::createKMessageBox(dialog, QMessageBox::Information, + formUrl.host().isEmpty() ? // e.g. local file + i18n("Konqueror has the ability to store the password " + "in an encrypted wallet. When the wallet is unlocked, it " + "can then automatically restore the login information " + "next time you submit this form. Do you want to store " + "the information now?") : + i18n("Konqueror has the ability to store the password " + "in an encrypted wallet. When the wallet is unlocked, it " + "can then automatically restore the login information " + "next time you visit %1. Do you want to store " + "the information now?").arg(formUrl.host()), + QStringList(), QString::null, &checkboxResult, KMessageBox::Notify); + + if ( savePassword == KDialogBase::Yes ) { + // ensure that we have the user / password inside the url + // otherwise we might have a potential security problem + // by saving passwords under wrong lookup key. + + if (view->part()) { + view->part()->saveToWallet(key, m_walletMap); + } + } else if ( savePassword == KDialogBase::No ) { + view->addNonPasswordStorableSite(formUrl.host()); + } + } + } +#endif // KHTML_NO_WALLET + + const DOMString url(khtml::parseURL(getAttribute(ATTR_ACTION))); + if(m_post) { + view->part()->submitForm( "post", url.string(), form_data, + m_target.string(), + enctype().string(), + m_boundary ); + } + else { + view->part()->submitForm( "get", url.string(), form_data, + m_target.string() ); + } + } + + m_walletMap.clear(); // done with it + m_havePassword = m_haveTextarea= false; + m_doingsubmit = m_insubmit = false; +} + +void HTMLFormElementImpl::reset( ) +{ + KHTMLView* const view = getDocument()->view(); + if(m_inreset || !view || !view->part()) return; + + m_inreset = true; + +#ifdef FORMS_DEBUG + kdDebug( 6030 ) << "reset pressed!" << endl; +#endif + + // ### DOM2 labels this event as not cancelable, however + // common browsers( sick! ) allow it be cancelled. + if ( !dispatchHTMLEvent(EventImpl::RESET_EVENT,true, true) ) { + m_inreset = false; + return; + } + + for (QPtrListIterator<HTMLGenericFormElementImpl> it(formElements); it.current(); ++it) + it.current()->reset(); + + m_inreset = false; +} + +void HTMLFormElementImpl::parseAttribute(AttributeImpl *attr) +{ + switch(attr->id()) + { + case ATTR_ACTION: + break; + case ATTR_TARGET: + m_target = attr->value(); + break; + case ATTR_METHOD: + m_post = ( strcasecmp( attr->value(), "post" ) == 0 ); + break; + case ATTR_ENCTYPE: + setEnctype( attr->value() ); + break; + case ATTR_ACCEPT_CHARSET: + // space separated list of charsets the server + // accepts - see rfc2045 + m_acceptcharset = attr->value(); + break; + case ATTR_ACCEPT: + // ignore this one for the moment... + break; + case ATTR_AUTOCOMPLETE: + m_autocomplete = strcasecmp( attr->value(), "off" ); + break; + case ATTR_ONSUBMIT: + setHTMLEventListener(EventImpl::SUBMIT_EVENT, + getDocument()->createHTMLEventListener(attr->value().string(), "onsubmit", this)); + break; + case ATTR_ONRESET: + setHTMLEventListener(EventImpl::RESET_EVENT, + getDocument()->createHTMLEventListener(attr->value().string(), "onreset", this)); + break; + case ATTR_NAME: + if (inDocument() && m_name != attr->value()) { + getDocument()->underDocNamedCache().remove(m_name.string(), this); + getDocument()->underDocNamedCache().add (attr->value().string(), this); + } + m_name = attr->value(); + //Fallthrough intentional + default: + HTMLElementImpl::parseAttribute(attr); + } +} + +void HTMLFormElementImpl::removedFromDocument() +{ + getDocument()->underDocNamedCache().remove(m_name.string(), this); + HTMLElementImpl::removedFromDocument(); +} + +void HTMLFormElementImpl::insertedIntoDocument() +{ + getDocument()->underDocNamedCache().add(m_name.string(), this); + HTMLElementImpl::insertedIntoDocument(); +} + +void HTMLFormElementImpl::removeId(const QString& id) +{ + getDocument()->underDocNamedCache().remove(id, this); + HTMLElementImpl::removeId(id); +} + +void HTMLFormElementImpl::addId (const QString& id) +{ + getDocument()->underDocNamedCache().add(id, this); + HTMLElementImpl::addId(id); +} + + +void HTMLFormElementImpl::radioClicked( HTMLGenericFormElementImpl *caller ) +{ + for (QPtrListIterator<HTMLGenericFormElementImpl> it(formElements); it.current(); ++it) { + HTMLGenericFormElementImpl* const current = it.current(); + if (current->id() == ID_INPUT && + static_cast<HTMLInputElementImpl*>(current)->inputType() == HTMLInputElementImpl::RADIO && + current != caller && current->form() == caller->form() && current->name() == caller->name()) + static_cast<HTMLInputElementImpl*>(current)->setChecked(false); + } +} + +void HTMLFormElementImpl::registerFormElement(HTMLGenericFormElementImpl *e) +{ + formElements.append(e); +} + +void HTMLFormElementImpl::removeFormElement(HTMLGenericFormElementImpl *e) +{ + formElements.remove(e); +} + +void HTMLFormElementImpl::registerImgElement(HTMLImageElementImpl *e) +{ + imgElements.append(e); +} + +void HTMLFormElementImpl::removeImgElement(HTMLImageElementImpl *e) +{ + imgElements.remove(e); +} + +// ------------------------------------------------------------------------- + +HTMLGenericFormElementImpl::HTMLGenericFormElementImpl(DocumentImpl *doc, HTMLFormElementImpl *f) + : HTMLElementImpl(doc) +{ + m_disabled = m_readOnly = false; + m_name = 0; + + if (f) + m_form = f; + else + m_form = getForm(); + if (m_form) + m_form->registerFormElement(this); +} + +void HTMLGenericFormElementImpl::insertedIntoDocument() +{ + HTMLElementImpl::insertedIntoDocument(); + + if (!m_form) { + HTMLFormElementImpl* const newform = getForm(); + if (newform) { + m_form = newform; + m_form->registerFormElement(this); + } + } +} + +void HTMLGenericFormElementImpl::removedFromDocument() +{ + HTMLElementImpl::removedFromDocument(); + + if (m_form) + m_form->removeFormElement(this); + + m_form = 0; +} + +HTMLGenericFormElementImpl::~HTMLGenericFormElementImpl() +{ + if (m_form) + m_form->removeFormElement(this); + if (m_name) m_name->deref(); +} + +void HTMLGenericFormElementImpl::parseAttribute(AttributeImpl *attr) +{ + switch(attr->id()) + { + case ATTR_DISABLED: + setDisabled( attr->val() != 0 ); + break; + case ATTR_READONLY: + { + const bool m_oldreadOnly = m_readOnly; + m_readOnly = attr->val() != 0; + if (m_oldreadOnly != m_readOnly) setChanged(); + break; + } + default: + HTMLElementImpl::parseAttribute(attr); + } +} + +void HTMLGenericFormElementImpl::attach() +{ + assert(!attached()); + + if (m_render) { + assert(m_render->style()); + parentNode()->renderer()->addChild(m_render, nextRenderer()); + } + + // FIXME: This handles the case of a new form element being created by + // JavaScript and inserted inside a form. What it does not handle is + // a form element being moved from inside a form to outside, or from one + // inside one form to another. The reason this other case is hard to fix + // is that during parsing, we may have been passed a form that we are not + // inside, DOM-tree-wise. If so, it's hard for us to know when we should + // be removed from that form's element list. + if (!m_form) { + m_form = getForm(); + if (m_form) + m_form->registerFormElement(this); + } + + NodeBaseImpl::attach(); + + // The call to updateFromElement() needs to go after the call through + // to the base class's attach() because that can sometimes do a close + // on the renderer. + if (m_render) + m_render->updateFromElement(); + +} + +HTMLFormElementImpl *HTMLGenericFormElementImpl::getForm() const +{ + NodeImpl *p = parentNode(); + while(p) + { + if( p->id() == ID_FORM ) + return static_cast<HTMLFormElementImpl *>(p); + if( p->parentNode() && p->parentNode()->id() == ID_TABLE && p->previousSibling() ) + { + p = p->previousSibling(); + continue; + } + p = p->parentNode(); + } +#ifdef FORMS_DEBUG + kdDebug( 6030 ) << "couldn't find form!" << endl; + kdDebug( 6030 ) << kdBacktrace() << endl; +#endif + return 0; +} + +DOMString HTMLGenericFormElementImpl::name() const +{ + if (m_name) return m_name; + +// ### +// DOMString n = getDocument()->htmlMode() != DocumentImpl::XHtml ? +// getAttribute(ATTR_NAME) : getAttribute(ATTR_ID); + const DOMString n = getAttribute(ATTR_NAME); + if (n.isNull()) + return new DOMStringImpl(""); + + return n; +} + +void HTMLGenericFormElementImpl::setName(const DOMString& name) +{ + if (m_name) m_name->deref(); + m_name = name.implementation(); + setAttribute( ATTR_NAME, name ); + if (m_name) m_name->ref(); +} + +void HTMLGenericFormElementImpl::onSelect() +{ + // ### make this work with new form events architecture + dispatchHTMLEvent(EventImpl::SELECT_EVENT,true,false); +} + +void HTMLGenericFormElementImpl::onChange() +{ + // ### make this work with new form events architecture + dispatchHTMLEvent(EventImpl::CHANGE_EVENT,true,false); +} + +void HTMLGenericFormElementImpl::setDisabled( bool _disabled ) +{ + if ( m_disabled != _disabled ) { + m_disabled = _disabled; + // Trigger dynamic restyles + getDocument()->dynamicDomRestyler().restyleDepedent(this, OtherStateDependency); + // We need to update rendering under all circumstances + if (!changed() && m_render) { + m_render->updateFromElement(); + } + } +} + +bool HTMLGenericFormElementImpl::isFocusable() const +{ + if (disabled()) + return false; + + //Non-widget INPUT TYPE="image" and <BUTTON> support focus, too. + if (id() == ID_INPUT && static_cast<const HTMLInputElementImpl *>(this)->inputType() == HTMLInputElementImpl::IMAGE) + return true; + + if (id() == ID_BUTTON) + return true; + + if (!m_render || !m_render->isWidget()) + return false; + + QWidget* widget = static_cast<RenderWidget*>(m_render)->widget(); + return widget && widget->focusPolicy() >= QWidget::TabFocus; +} + +class FocusHandleWidget : public QWidget +{ +public: + void focusNextPrev(bool n) { + if (!focusNextPrevChild(n) && inherits("QTextEdit")) + QWidget::focusNextPrevChild(n); + } +}; + +void HTMLGenericFormElementImpl::defaultEventHandler(EventImpl *evt) +{ + if (evt->target() == this && renderer() && renderer()->isWidget()) { + switch(evt->id()) { + case EventImpl::MOUSEDOWN_EVENT: + case EventImpl::MOUSEUP_EVENT: + case EventImpl::MOUSEMOVE_EVENT: + case EventImpl::MOUSEOUT_EVENT: + case EventImpl::MOUSEOVER_EVENT: + case EventImpl::KEYDOWN_EVENT: + case EventImpl::KEYUP_EVENT: + case EventImpl::KEYPRESS_EVENT: + if (static_cast<RenderWidget*>(renderer())->handleEvent(*evt)) + evt->setDefaultHandled(); + default: + break; + } + } + + if (evt->target()==this && !m_disabled) + { + // Report focus in/out changes to the browser extension (editable widgets only) + KHTMLView* const view = getDocument()->view(); + if (view && evt->id() == EventImpl::DOMFOCUSIN_EVENT && isEditable() && m_render && m_render->isWidget()) { + KHTMLPartBrowserExtension *ext = static_cast<KHTMLPartBrowserExtension *>(view->part()->browserExtension()); + QWidget* const widget = static_cast<RenderWidget*>(m_render)->widget(); + if (ext) + ext->editableWidgetFocused(widget); + } + if (evt->id()==EventImpl::MOUSEDOWN_EVENT || evt->id()==EventImpl::KEYDOWN_EVENT) + { + setActive(); + } + else if (evt->id() == EventImpl::MOUSEUP_EVENT || evt->id()==EventImpl::KEYUP_EVENT) + { + if (m_active) + { + setActive(false); + setFocus(); + } + else { + setActive(false); + } + } + + if (!evt->defaultHandled() && m_render && m_render->isWidget()) { + // handle tabbing out, either from a single or repeated key event. + if ( evt->id() == EventImpl::KEYPRESS_EVENT && evt->isKeyRelatedEvent() ) { + QKeyEvent* const k = static_cast<KeyEventBaseImpl *>(evt)->qKeyEvent(); + if ( k && (k->key() == Qt::Key_Tab || k->key() == Qt::Key_BackTab) ) { + QWidget* const widget = static_cast<RenderWidget*>(m_render)->widget(); + QFocusEvent::setReason( k->key() == Qt::Key_Tab ? QFocusEvent::Tab : QFocusEvent::Backtab ); + if (widget) + static_cast<FocusHandleWidget *>(widget) + ->focusNextPrev(k->key() == Qt::Key_Tab); + QFocusEvent::resetReason(); + evt->setDefaultHandled(); + } + } + } + + + if (view && evt->id() == EventImpl::DOMFOCUSOUT_EVENT && isEditable() && m_render && m_render->isWidget()) { + KHTMLPartBrowserExtension* const ext = static_cast<KHTMLPartBrowserExtension *>(view->part()->browserExtension()); + QWidget* const widget = static_cast<RenderWidget*>(m_render)->widget(); + if (ext) + ext->editableWidgetBlurred(widget); + + // ### Don't count popup as a valid reason for losing the focus (example: opening the options of a select + // combobox shouldn't emit onblur) + } + } + if (evt->target() == this && evt->isMouseEvent() && renderer()) + evt->setDefaultHandled(); + + HTMLElementImpl::defaultEventHandler(evt); +} + +bool HTMLGenericFormElementImpl::isEditable() +{ + return false; +} + +// ------------------------------------------------------------------------- + +HTMLButtonElementImpl::HTMLButtonElementImpl(DocumentImpl *doc, HTMLFormElementImpl *f) + : HTMLGenericFormElementImpl(doc, f) +{ + m_clicked = false; + m_type = SUBMIT; + m_dirty = true; + m_activeSubmit = false; +} + +HTMLButtonElementImpl::~HTMLButtonElementImpl() +{ +} + +NodeImpl::Id HTMLButtonElementImpl::id() const +{ + return ID_BUTTON; +} + +DOMString HTMLButtonElementImpl::type() const +{ + return getAttribute(ATTR_TYPE); +} + +void HTMLButtonElementImpl::blur() +{ + if(getDocument()->focusNode() == this) + getDocument()->setFocusNode(0); +} + +void HTMLButtonElementImpl::focus() +{ + getDocument()->setFocusNode(this); +} + +void HTMLButtonElementImpl::parseAttribute(AttributeImpl *attr) +{ + switch(attr->id()) + { + case ATTR_TYPE: + if ( strcasecmp( attr->value(), "submit" ) == 0 ) + m_type = SUBMIT; + else if ( strcasecmp( attr->value(), "reset" ) == 0 ) + m_type = RESET; + else if ( strcasecmp( attr->value(), "button" ) == 0 ) + m_type = BUTTON; + break; + case ATTR_VALUE: + m_value = attr->value(); + m_currValue = m_value.string(); + break; + case ATTR_ACCESSKEY: + break; + case ATTR_ALIGN: + break; + default: + HTMLGenericFormElementImpl::parseAttribute(attr); + } +} + +void HTMLButtonElementImpl::defaultEventHandler(EventImpl *evt) +{ + if (m_type != BUTTON && !m_disabled) { + bool act = (evt->id() == EventImpl::DOMACTIVATE_EVENT); + if (!act && evt->id()==EventImpl::KEYUP_EVENT && evt->isKeyRelatedEvent()) { + QKeyEvent* const ke = static_cast<KeyEventBaseImpl *>(evt)->qKeyEvent(); + if (ke && active() && (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Space)) + act = true; + } + if (act) + activate(); + } + HTMLGenericFormElementImpl::defaultEventHandler(evt); +} + +void HTMLButtonElementImpl::activate() +{ + m_clicked = true; + + if(m_form && m_type == SUBMIT) { + m_activeSubmit = true; + m_form->prepareSubmit(); + m_activeSubmit = false; // in case we were canceled + } + if(m_form && m_type == RESET) + m_form->reset(); +} + +void HTMLButtonElementImpl::click() +{ + QMouseEvent me(QEvent::MouseButtonRelease, QPoint(0,0),Qt::LeftButton, 0); + dispatchMouseEvent(&me,EventImpl::CLICK_EVENT, 1); +} + +bool HTMLButtonElementImpl::encoding(const QTextCodec* codec, khtml::encodingList& encoding, bool /*multipart*/) +{ + if (m_type != SUBMIT || name().isEmpty() || !m_activeSubmit) + return false; + + encoding += fixUpfromUnicode(codec, name().string()); + const QString enc_str = m_currValue.isNull() ? QString("") : m_currValue; + encoding += fixUpfromUnicode(codec, enc_str); + + return true; +} + +void HTMLButtonElementImpl::attach() +{ + // skip the generic handler + HTMLElementImpl::attach(); +} + +// ------------------------------------------------------------------------- + +HTMLFieldSetElementImpl::HTMLFieldSetElementImpl(DocumentImpl *doc, HTMLFormElementImpl *f) + : HTMLGenericFormElementImpl(doc, f) +{ +} + +HTMLFieldSetElementImpl::~HTMLFieldSetElementImpl() +{ +} + +NodeImpl::Id HTMLFieldSetElementImpl::id() const +{ + return ID_FIELDSET; +} + +void HTMLFieldSetElementImpl::attach() +{ + assert(!attached()); + assert(!m_render); + assert(parentNode()); + + RenderStyle* const _style = getDocument()->styleSelector()->styleForElement(this); + _style->ref(); + if (parentNode()->renderer() && _style->display() != NONE) { + m_render = new (getDocument()->renderArena()) RenderFieldset(this); + m_render->setStyle(_style); + } + HTMLGenericFormElementImpl::attach(); + _style->deref(); +} + +void HTMLFieldSetElementImpl::parseAttribute(AttributeImpl *attr) +{ + HTMLElementImpl::parseAttribute(attr); +} + +// ------------------------------------------------------------------------- + +HTMLInputElementImpl::HTMLInputElementImpl(DocumentImpl *doc, HTMLFormElementImpl *f) + : HTMLGenericFormElementImpl(doc, f) +{ + m_type = TEXT; + m_maxLen = -1; + m_size = 20; + m_clicked = false; + m_checked = false; + m_defaultChecked = false; + m_useDefaultChecked = true; + m_indeterminate = false; + + m_haveType = false; + m_activeSubmit = false; + m_autocomplete = true; + m_inited = false; + m_unsubmittedFormChange = false; + + xPos = 0; + yPos = 0; + + if ( m_form ) + m_autocomplete = f->autoComplete(); +} + +HTMLInputElementImpl::~HTMLInputElementImpl() +{ + if (getDocument()) getDocument()->deregisterMaintainsState(this); +} + +NodeImpl::Id HTMLInputElementImpl::id() const +{ + return ID_INPUT; +} + +// Called from JS. Can't merge with parseType since we +// also need to actually set ATTR_TYPE, which can't be done there. +void HTMLInputElementImpl::setType(const DOMString& t) +{ + setAttribute(ATTR_TYPE, t); +} + +void HTMLInputElementImpl::parseType(const DOMString& t) +{ + typeEnum newType; + + if ( strcasecmp( t, "password" ) == 0 ) + newType = PASSWORD; + else if ( strcasecmp( t, "checkbox" ) == 0 ) + newType = CHECKBOX; + else if ( strcasecmp( t, "radio" ) == 0 ) + newType = RADIO; + else if ( strcasecmp( t, "submit" ) == 0 ) + newType = SUBMIT; + else if ( strcasecmp( t, "reset" ) == 0 ) + newType = RESET; + else if ( strcasecmp( t, "file" ) == 0 ) + newType = FILE; + else if ( strcasecmp( t, "hidden" ) == 0 ) + newType = HIDDEN; + else if ( strcasecmp( t, "image" ) == 0 ) + newType = IMAGE; + else if ( strcasecmp( t, "button" ) == 0 ) + newType = BUTTON; + else if ( strcasecmp( t, "khtml_isindex" ) == 0 ) + newType = ISINDEX; + else + newType = TEXT; + + // ### IMPORTANT: Don't allow the type to be changed to FILE after the first + // type change, otherwise a JavaScript programmer would be able to set a text + // field's value to something like /etc/passwd and then change it to a file field. + if (m_type != newType) { + if (newType == FILE && m_haveType) { + // Set the attribute back to the old value. + // Note that this calls parseAttribute again. + setAttribute(ATTR_TYPE, type()); + } else { + m_type = newType; + + // force reattach if need be. + if (attached()) { + detach(); + attach(); + } + } + } + m_haveType = true; +} + +DOMString HTMLInputElementImpl::type() const +{ + // needs to be lowercase according to DOM spec + switch (m_type) { + case TEXT: return "text"; + case PASSWORD: return "password"; + case CHECKBOX: return "checkbox"; + case RADIO: return "radio"; + case SUBMIT: return "submit"; + case RESET: return "reset"; + case FILE: return "file"; + case HIDDEN: return "hidden"; + case IMAGE: return "image"; + case BUTTON: return "button"; + default: return ""; + } +} + +QString HTMLInputElementImpl::state( ) +{ + switch (m_type) { + case PASSWORD: + return QString::fromLatin1("."); // empty string, avoid restoring + case CHECKBOX: + case RADIO: + return QString::fromLatin1(checked() ? "on" : "off"); + case TEXT: + if (autoComplete() && value() != getAttribute(ATTR_VALUE) && getDocument()->view()) + getDocument()->view()->addFormCompletionItem(name().string(), value().string()); + /* nobreak */ + default: + return value().string() + (m_unsubmittedFormChange ? 'M' : '.'); + } +} + +void HTMLInputElementImpl::restoreState(const QString &state) +{ + switch (m_type) { + case CHECKBOX: + case RADIO: + setChecked((state == QString::fromLatin1("on"))); + break; + case FILE: + m_value = DOMString(state.left(state.length()-1)); + setChanged(); + break; + default: + setValue(DOMString(state.left(state.length()-1))); + m_unsubmittedFormChange = state.endsWith("M"); + break; + } +} + +void HTMLInputElementImpl::select( ) +{ + if(!m_render) return; + + if (m_type == TEXT || m_type == PASSWORD) + static_cast<RenderLineEdit*>(m_render)->select(); + else if (m_type == FILE) + static_cast<RenderFileButton*>(m_render)->select(); +} + +void HTMLInputElementImpl::click() +{ + QMouseEvent me(QEvent::MouseButtonRelease, QPoint(0,0),Qt::LeftButton, 0); + dispatchMouseEvent(&me,0, 1); + dispatchMouseEvent(&me,EventImpl::CLICK_EVENT, 1); +} + +void HTMLInputElementImpl::parseAttribute(AttributeImpl *attr) +{ + switch(attr->id()) + { + case ATTR_AUTOCOMPLETE: + m_autocomplete = strcasecmp( attr->value(), "off" ); + break; + case ATTR_TYPE: + parseType(attr->value()); + break; + case ATTR_VALUE: + if (m_value.isNull()) {// We only need to setChanged if the form is looking + setChanged(); // at the default value right now. + if (m_type == TEXT && m_render) + m_render->updateFromElement(); + } + break; + case ATTR_CHECKED: + m_defaultChecked = attr->val(); + if (m_useDefaultChecked) // We only need to setChanged if the form is looking + setChanged(); // at the default checked state right now. + break; + case ATTR_MAXLENGTH: + { + m_maxLen = -1; + if (!attr->val()) break; + bool ok; + const int ml = attr->val()->toInt(&ok); + if (ml > 0 && ml < 32767) + m_maxLen = ml; + else if (ok && ml <= 0) + m_maxLen = 0; + setChanged(); + } + break; + case ATTR_SIZE: + m_size = attr->val() ? attr->val()->toInt() : 20; + break; + case ATTR_ALT: + case ATTR_SRC: + if (m_type == IMAGE) + setChanged(); + break; + case ATTR_USEMAP: + // ### ignore for the moment + break; + case ATTR_ALIGN: + if ( m_inited && m_type == IMAGE ) + addHTMLAlignment( attr->value() ); + break; + case ATTR_ACCESSKEY: + break; + case ATTR_WIDTH: + if ( m_type == IMAGE ) + addCSSLength(CSS_PROP_WIDTH, attr->value() ); + break; + case ATTR_HEIGHT: + if ( m_type == IMAGE ) + addCSSLength(CSS_PROP_HEIGHT, attr->value() ); + break; + case ATTR_ONSELECT: + setHTMLEventListener(EventImpl::SELECT_EVENT, + getDocument()->createHTMLEventListener(attr->value().string(), "onselect", this)); + break; + case ATTR_ONCHANGE: + setHTMLEventListener(EventImpl::CHANGE_EVENT, + getDocument()->createHTMLEventListener(attr->value().string(), "onchange", this)); + break; + default: + HTMLGenericFormElementImpl::parseAttribute(attr); + } +} + +void HTMLInputElementImpl::attach() +{ + assert(!attached()); + assert(!m_render); + assert(parentNode()); + + if (!m_inited) { + // FIXME: This needs to be dynamic, doesn't it, since someone could set this + // after attachment? + if ((uint) m_type <= ISINDEX && !m_value.isEmpty()) { + const QString value = m_value.string(); + // remove newline stuff.. + QString nvalue; + unsigned int valueLength = value.length(); + for (unsigned int i = 0; i < valueLength; ++i) + if (value[i] >= ' ') + nvalue += value[i]; + m_value = nvalue; + } + m_defaultChecked = (getAttribute(ATTR_CHECKED) != 0); + if ( m_type == IMAGE ) + addHTMLAlignment( getAttribute( ATTR_ALIGN ) ); + m_inited = true; + } + + switch( m_type ) { + case PASSWORD: + if (getDocument()->isHTMLDocument()) + static_cast<HTMLDocumentImpl*>(getDocument())->setAutoFill(); + break; + case HIDDEN: + case IMAGE: + if (!getAttribute(ATTR_WIDTH).isNull()) + addCSSLength(CSS_PROP_WIDTH, getAttribute(ATTR_WIDTH)); + if (!getAttribute(ATTR_HEIGHT).isNull()) + addCSSLength(CSS_PROP_HEIGHT, getAttribute(ATTR_HEIGHT)); + default: + break; + }; + + RenderStyle* const _style = getDocument()->styleSelector()->styleForElement(this); + _style->ref(); + if (parentNode()->renderer() && _style->display() != NONE) { + switch(m_type) + { + case TEXT: + case PASSWORD: + case ISINDEX: m_render = new (getDocument()->renderArena()) RenderLineEdit(this); break; + case CHECKBOX: m_render = new (getDocument()->renderArena()) RenderCheckBox(this); break; + case RADIO: m_render = new (getDocument()->renderArena()) RenderRadioButton(this); break; + case SUBMIT: m_render = new (getDocument()->renderArena()) RenderSubmitButton(this); break; + case IMAGE: m_render = new (getDocument()->renderArena()) RenderImageButton(this); break; + case RESET: m_render = new (getDocument()->renderArena()) RenderResetButton(this); break; + case FILE: m_render = new (getDocument()->renderArena()) RenderFileButton(this); break; + case BUTTON: m_render = new (getDocument()->renderArena()) RenderPushButton(this); + case HIDDEN: break; + } + } + + // Let check and radio boxes start indeterminate + setIndeterminate(true); + + if (m_render) + m_render->setStyle(_style); + + HTMLGenericFormElementImpl::attach(); + _style->deref(); +} + +DOMString HTMLInputElementImpl::altText() const +{ + // http://www.w3.org/TR/1998/REC-html40-19980424/appendix/notes.html#altgen + // also heavily discussed by Hixie on bugzilla + // note this is intentionally different to HTMLImageElementImpl::altText() + DOMString alt = getAttribute( ATTR_ALT ); + // fall back to title attribute + if ( alt.isNull() ) + alt = getAttribute( ATTR_TITLE ); + if ( alt.isNull() ) + alt = getAttribute( ATTR_VALUE ); + if ( alt.isEmpty() ) + alt = i18n( "Submit" ); + + return alt; +} + +bool HTMLInputElementImpl::encoding(const QTextCodec* codec, khtml::encodingList& encoding, bool multipart) +{ + const QString nme = name().string(); + + // image generates its own name's + if (nme.isEmpty() && m_type != IMAGE) return false; + + // IMAGE needs special handling later + if(m_type != IMAGE) encoding += fixUpfromUnicode(codec, nme); + + switch (m_type) { + case CHECKBOX: + + if( checked() ) { + encoding += fixUpfromUnicode(codec, value().string()); + return true; + } + break; + + case RADIO: + + if( checked() ) { + encoding += fixUpfromUnicode(codec, value().string()); + return true; + } + break; + + case BUTTON: + case RESET: + // those buttons are never successful + return false; + + case IMAGE: + + if(m_clicked) + { + m_clicked = false; + QString astr(nme.isEmpty() ? QString::fromLatin1("x") : nme + ".x"); + + encoding += fixUpfromUnicode(codec, astr); + astr.setNum(KMAX( clickX(), 0 )); + encoding += fixUpfromUnicode(codec, astr); + astr = nme.isEmpty() ? QString::fromLatin1("y") : nme + ".y"; + encoding += fixUpfromUnicode(codec, astr); + astr.setNum(KMAX( clickY(), 0 ) ); + encoding += fixUpfromUnicode(codec, astr); + astr = value().string(); + if(astr.length() > 0) { + encoding += fixUpfromUnicode(codec, nme); + encoding += fixUpfromUnicode(codec, astr); + } + + return true; + } + break; + + case SUBMIT: + + if (m_activeSubmit) + { + QString enc_str = valueWithDefault().string(); + if(!enc_str.isEmpty()) + { + encoding += fixUpfromUnicode(codec, enc_str); + return true; + } + } + break; + + case FILE: // hmm, we have the type FILE also. bad choice here... + { + // don't submit if display: none or display: hidden + if(!renderer() || renderer()->style()->visibility() != khtml::VISIBLE) + return false; + + QString local; + KURL fileurl; + QString val = value().string(); + if (!val.isEmpty() && + QDir::isRelativePath(val) && + QFile::exists(KGlobalSettings::documentPath() + val)) { + fileurl.setPath(KGlobalSettings::documentPath() + val); + } else { + fileurl = KURL::fromPathOrURL(val); + } + + KIO::UDSEntry filestat; + + // can't submit file in www-url-form encoded + QWidget* const toplevel = static_cast<RenderSubmitButton*>(m_render)->widget()->topLevelWidget(); + if (multipart) { + QCString filearray( "" ); + if ( KIO::NetAccess::stat(fileurl, filestat, toplevel)) { + const KFileItem fileitem(filestat, fileurl, true, false); + if ( fileitem.isFile() && + KIO::NetAccess::download(fileurl, local, toplevel) ) { + QFile file(local); + filearray.resize(file.size()+1); + if ( file.open( IO_ReadOnly ) ) { + const int readbytes = file.readBlock( filearray.data(), file.size()); + if ( readbytes >= 0 ) + filearray[readbytes] = '\0'; + file.close(); + } + KIO::NetAccess::removeTempFile( local ); + } + } + encoding += filearray; + return true; + } + // else fall through + } + case HIDDEN: + case TEXT: + case PASSWORD: + // always successful + encoding += fixUpfromUnicode(codec, value().string()); + return true; + case ISINDEX: + encoding += fixUpfromUnicode(codec, value().string()); + return true; + } + return false; +} + +void HTMLInputElementImpl::reset() +{ + if (m_type == FILE) { + // set directly to bypass security check. emptying the value + // should mean no risk. + if (!m_value.isEmpty()) { + m_value = DOMString(); + setChanged(); + } + } else { + setValue(getAttribute(ATTR_VALUE)); + } + m_useDefaultChecked = true; + m_checked = m_defaultChecked; + setIndeterminate(true); +} + +void HTMLInputElementImpl::setChecked(bool _checked) +{ + if (m_form && m_type == RADIO && _checked && !name().isEmpty()) + m_form->radioClicked(this); + + if (checked() == _checked) return; + m_useDefaultChecked = false; + m_checked = _checked; + +// setIndeterminate(false); + + // Trigger dynamic restyles + getDocument()->dynamicDomRestyler().restyleDepedent(this, OtherStateDependency); + // We need to update rendering under all circumstances + if (!changed() && m_render) { + m_render->updateFromElement(); + } +} + +void HTMLInputElementImpl::setIndeterminate(bool _indeterminate) +{ + // Only checkboxes and radio-boxes honor indeterminate. + if (inputType() != CHECKBOX || inputType() != RADIO || indeterminate() == _indeterminate) + return; + + m_indeterminate = _indeterminate; + + // Trigger dynamic restyles +// getDocument()->dynamicDomRestyler().restyleDepedent(this, OtherStateDependency); + // We need to update rendering under all circumstances + if (!changed() && m_render) { + m_render->updateFromElement(); + } +} + +DOMString HTMLInputElementImpl::value() const +{ + if (m_type == CHECKBOX || m_type == RADIO) { + const DOMString val = getAttribute(ATTR_VALUE); + // If no attribute exists, then we'll just return "on" as + // other browsers strangely seem to do without respecting the + // checked() state of the control. + if (val.isNull()) + return DOMString("on"); + return val; + } + + DOMString val = m_value; + // It's important *not* to fall back to the value attribute for file inputs, + // because that would allow a malicious web page to upload files by setting the + // value attribute in markup. + if (val.isNull() && m_type != FILE) + val = getAttribute(ATTR_VALUE); + + return val.isNull() ? DOMString("") : val; +} + + +void HTMLInputElementImpl::setValue(DOMString val) +{ + if (m_type == FILE) return; + + m_value = (val.isNull() ? DOMString("") : val); + // ### set attribute for other types, too. no need for m_value + // ### in those cases. + if (m_type == RADIO || m_type == CHECKBOX) + setAttribute(ATTR_VALUE, m_value); + if (m_type == TEXT && m_render) + m_render->updateFromElement(); + setChanged(); +} + +void HTMLInputElementImpl::blur() +{ + if(getDocument()->focusNode() == this) + getDocument()->setFocusNode(0); +} + +void HTMLInputElementImpl::focus() +{ + getDocument()->setFocusNode(this); +} + +void HTMLInputElementImpl::defaultEventHandler(EventImpl *evt) +{ + if ( !m_disabled ) + { + + if (evt->isMouseEvent()) { + MouseEventImpl* const me = static_cast<MouseEventImpl*>(evt); + if ((m_type == RADIO || m_type == CHECKBOX) + && me->id() == EventImpl::MOUSEUP_EVENT && me->detail() > 0) { + // click will follow + setChecked(m_type == RADIO ? true : !checked()); + } + if (evt->id() == EventImpl::CLICK_EVENT && m_type == IMAGE && m_render) { + // record the mouse position for when we get the DOMActivate event + int offsetX, offsetY; + m_render->absolutePosition(offsetX,offsetY); + xPos = me->clientX()-offsetX; + yPos = me->clientY()-offsetY; + KHTMLView* v = getDocument()->view(); + if ( v ) { + xPos += v->contentsX(); + yPos += v->contentsY(); + } + } + } + + if (m_type == RADIO || m_type == CHECKBOX || m_type == SUBMIT || m_type == RESET || m_type == BUTTON ) { + bool check = false; + if (active() && ( evt->id() == EventImpl::KEYUP_EVENT || + evt->id() == EventImpl::KEYPRESS_EVENT ) ) { + TextEventImpl* const te = static_cast<TextEventImpl *>(evt); + if (te->keyVal() == ' ') + check = true; + else if (te->keyVal() == '\r' && (m_type == BUTTON || m_type == RESET || m_type == SUBMIT)) + check = true; + } + if (check) { + if (evt->id() == EventImpl::KEYUP_EVENT) + click(); + // Tell the parent that we handle this key (keyup and keydown), even though only keyup activates (#70478) + evt->setDefaultHandled(); + } + } + + + // DOMActivate events cause the input to be "activated" - in the case of image and submit inputs, this means + // actually submitting the form. For reset inputs, the form is reset. These events are sent when the user clicks + // on the element, or presses enter while it is the active element. Javascript code wishing to activate the element + // must dispatch a DOMActivate event - a click event will not do the job. + if (m_type == IMAGE || m_type == SUBMIT || m_type == RESET) { + bool act = (evt->id() == EventImpl::DOMACTIVATE_EVENT); + if (!act && evt->id() == EventImpl::KEYUP_EVENT && evt->isKeyRelatedEvent()) { + QKeyEvent* const ke = static_cast<KeyEventBaseImpl *>(evt)->qKeyEvent(); + if (ke && active() && (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Space)) + act = true; + } + if (act) + activate(); + } + } + HTMLGenericFormElementImpl::defaultEventHandler(evt); +} + +void HTMLInputElementImpl::activate() +{ + if (!m_form) + return; + + m_clicked = true; + if (m_type == RESET) { + m_form->reset(); + } + else { + m_activeSubmit = true; + if (!m_form->prepareSubmit()) { + xPos = 0; + yPos = 0; + } + m_activeSubmit = false; + } +} + +bool HTMLInputElementImpl::isEditable() +{ + return ((m_type == TEXT) || (m_type == PASSWORD) || (m_type == ISINDEX) || (m_type == FILE)); +} + +long HTMLInputElementImpl::selectionStart() +{ + if (m_type != TEXT || !m_render) return -1; + return static_cast<RenderLineEdit*>(m_render)->selectionStart(); +} + +long HTMLInputElementImpl::selectionEnd() +{ + if (m_type != TEXT || !m_render) return -1; + return static_cast<RenderLineEdit*>(m_render)->selectionEnd(); +} + +void HTMLInputElementImpl::setSelectionStart(long pos) +{ + if (m_type != TEXT || !m_render) return; + static_cast<RenderLineEdit*>(m_render)->setSelectionStart(pos); +} + +void HTMLInputElementImpl::setSelectionEnd (long pos) +{ + if (m_type != TEXT || !m_render) return; + static_cast<RenderLineEdit*>(m_render)->setSelectionEnd(pos); +} + +void HTMLInputElementImpl::setSelectionRange(long start, long end) +{ + if (m_type != TEXT || !m_render) return; + static_cast<RenderLineEdit*>(m_render)->setSelectionRange(start, end); +} + +// ------------------------------------------------------------------------- + +HTMLLabelElementImpl::HTMLLabelElementImpl(DocumentImpl *doc) + : HTMLGenericFormElementImpl(doc) +{ +} + +HTMLLabelElementImpl::~HTMLLabelElementImpl() +{ +} + +NodeImpl::Id HTMLLabelElementImpl::id() const +{ + return ID_LABEL; +} + +void HTMLLabelElementImpl::attach() +{ + // skip the generic handler + HTMLElementImpl::attach(); +} + +NodeImpl* HTMLLabelElementImpl::getFormElement() +{ + const DOMString formElementId = getAttribute(ATTR_FOR); + NodeImpl *newNode=0L; + if (!formElementId.isEmpty()) + newNode=getDocument()->getElementById(formElementId); + if (!newNode){ + const uint children=childNodeCount(); + if (children>1) + for (unsigned int i=0;i<children;++i){ + const uint nodeId=childNode(i)->id(); + if (nodeId==ID_INPUT || nodeId==ID_SELECT || nodeId==ID_TEXTAREA){ + newNode=childNode(i); + break; + } + } + } +return newNode; +} + +void HTMLLabelElementImpl::defaultEventHandler(EventImpl *evt) +{ + if ( !m_disabled ) { + bool act = false; + if ( evt->id() == EventImpl::CLICK_EVENT ) { + act = true; + } + else if ( evt->isKeyRelatedEvent() && ( evt->id() == EventImpl::KEYUP_EVENT || + evt->id() == EventImpl::KEYPRESS_EVENT ) ) { + QKeyEvent* const ke = static_cast<KeyEventBaseImpl *>(evt)->qKeyEvent(); + if (ke && active() && (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Space)) + act = true; + } + + if (act) { + NodeImpl* const formNode=getFormElement(); + if (formNode && evt->target() != formNode) { + getDocument()->setFocusNode(formNode); + if (formNode->id()==ID_INPUT) + static_cast<DOM::HTMLInputElementImpl*>(formNode)->click(); + evt->setDefaultHandled(); + } + } + } + HTMLGenericFormElementImpl::defaultEventHandler(evt); +} + +// ------------------------------------------------------------------------- + +HTMLLegendElementImpl::HTMLLegendElementImpl(DocumentImpl *doc, HTMLFormElementImpl *f) + : HTMLGenericFormElementImpl(doc, f) +{ +} + +HTMLLegendElementImpl::~HTMLLegendElementImpl() +{ +} + +NodeImpl::Id HTMLLegendElementImpl::id() const +{ + return ID_LEGEND; +} + +void HTMLLegendElementImpl::attach() +{ + assert(!attached()); + assert(!m_render); + assert(parentNode()); + RenderStyle* const _style = getDocument()->styleSelector()->styleForElement(this); + _style->ref(); + if (parentNode()->renderer() && _style->display() != NONE) { + m_render = new (getDocument()->renderArena()) RenderLegend(this); + m_render->setStyle(_style); + } + HTMLGenericFormElementImpl::attach(); + _style->deref(); +} + +void HTMLLegendElementImpl::parseAttribute(AttributeImpl *attr) +{ + switch(attr->id()) + { + case ATTR_ACCESSKEY: + break; + default: + HTMLElementImpl::parseAttribute(attr); + } +} + +// ------------------------------------------------------------------------- + +HTMLSelectElementImpl::HTMLSelectElementImpl(DocumentImpl *doc, HTMLFormElementImpl *f) + : HTMLGenericFormElementImpl(doc, f) +{ + m_multiple = false; + m_recalcListItems = false; + // 0 means invalid (i.e. not set) + m_size = 0; + m_minwidth = 0; + m_length = 0; +} + +HTMLSelectElementImpl::~HTMLSelectElementImpl() +{ + if (getDocument()) getDocument()->deregisterMaintainsState(this); +} + +NodeImpl::Id HTMLSelectElementImpl::id() const +{ + return ID_SELECT; +} + +DOMString HTMLSelectElementImpl::type() const +{ + return (m_multiple ? "select-multiple" : "select-one"); +} + +long HTMLSelectElementImpl::selectedIndex() const +{ + // return the number of the first option selected + uint o = 0; + QMemArray<HTMLGenericFormElementImpl*> items = listItems(); + const unsigned int itemsSize = items.size(); + for (unsigned int i = 0; i < itemsSize; ++i) { + if (items[i]->id() == ID_OPTION) { + if (static_cast<HTMLOptionElementImpl*>(items[i])->selected()) + return o; + o++; + } + } + Q_ASSERT(m_multiple || items.isEmpty()); + return -1; +} + +void HTMLSelectElementImpl::setSelectedIndex( long index ) +{ + // deselect all other options and select only the new one + QMemArray<HTMLGenericFormElementImpl*> items = listItems(); + int listIndex; + const int itemsSize = int(items.size()); + for (listIndex = 0; listIndex < itemsSize; ++listIndex) { + if (items[listIndex]->id() == ID_OPTION) + static_cast<HTMLOptionElementImpl*>(items[listIndex])->setSelected(false); + } + listIndex = optionToListIndex(index); + if (listIndex >= 0) + static_cast<HTMLOptionElementImpl*>(items[listIndex])->setSelected(true); + + setChanged(true); +} + +long HTMLSelectElementImpl::length() const +{ + if (m_recalcListItems) + recalcListItems(); + return m_length; +} + +void HTMLSelectElementImpl::add( const HTMLElement &element, const HTMLElement &before, int& exceptioncode ) +{ + if(element.isNull() || element.handle()->id() != ID_OPTION) + return; + + HTMLOptionElementImpl* option = static_cast<HTMLOptionElementImpl*>(element.handle());; + //Fast path for appending an item. Can't be done if it is selected and + //we're single-select, since we may need to drop an implicitly-selected item + bool fastAppendLast = false; + if (before.handle() == 0 && (m_multiple || !option->selected()) && !m_recalcListItems) + fastAppendLast = true; + + insertBefore(option, before.handle(), exceptioncode ); + + if (fastAppendLast) { + m_listItems.resize(m_listItems.size() + 1); + m_listItems[m_listItems.size() - 1] = option; + ++m_length; + m_recalcListItems = false; + } else if (!exceptioncode) + setRecalcListItems(); +} + +void HTMLSelectElementImpl::remove( long index ) +{ + int exceptioncode = 0; + const int listIndex = optionToListIndex(index); + + QMemArray<HTMLGenericFormElementImpl*> items = listItems(); + if(listIndex < 0 || index >= int(items.size())) + return; // ### what should we do ? remove the last item? + + //Fast path for last element, for e.g. clearing the box + //Note that if this is a single-select, we may have to recompute + //anyway if the item was selected, since we may want to set + //a different one + bool fastRemoveLast = false; + if ((listIndex == (signed)items.size() - 1) && !m_recalcListItems && + (m_multiple || !static_cast<HTMLOptionElementImpl*>(items[listIndex])->selected())) + fastRemoveLast = true; + + removeChild(items[listIndex], exceptioncode); + + if (fastRemoveLast) { + m_listItems.resize(m_listItems.size() - 1); + --m_length; + m_recalcListItems = false; + } else if( !exceptioncode) + setRecalcListItems(); +} + +void HTMLSelectElementImpl::blur() +{ + if(getDocument()->focusNode() == this) + getDocument()->setFocusNode(0); +} + +void HTMLSelectElementImpl::focus() +{ + getDocument()->setFocusNode(this); +} + +DOMString HTMLInputElementImpl::valueWithDefault() const +{ + DOMString v = value(); + if (v.isEmpty()) { + switch (m_type) { + case RESET: +#ifdef APPLE_CHANGES + v = resetButtonDefaultLabel(); +#else + v = i18n("Reset"); +#endif + break; + + case SUBMIT: +#ifdef APPLE_CHANGES + v = submitButtonDefaultLabel(); +#else + v = i18n("Submit"); +#endif + break; + + case BUTTON: + case CHECKBOX: + case FILE: + case HIDDEN: + case IMAGE: + case ISINDEX: + case PASSWORD: + case RADIO: + #ifdef APPLE_CHANGES + case RANGE: + case SEARCH: + #endif + case TEXT: + break; + } + } + return v; +} + +DOMString HTMLSelectElementImpl::value( ) const +{ + uint i; + QMemArray<HTMLGenericFormElementImpl*> items = listItems(); + const uint itemsSize = items.size(); + for (i = 0; i < itemsSize; ++i) { + if ( items[i]->id() == ID_OPTION + && static_cast<HTMLOptionElementImpl*>(items[i])->selected()) + return static_cast<HTMLOptionElementImpl*>(items[i])->value(); + } + return DOMString(""); +} + +void HTMLSelectElementImpl::setValue(DOMStringImpl* value) +{ + // find the option with value() matching the given parameter + // and make it the current selection. + QMemArray<HTMLGenericFormElementImpl*> items = listItems(); + for (unsigned i = 0; i < items.size(); i++) + if (items[i]->id() == ID_OPTION && static_cast<HTMLOptionElementImpl*>(items[i])->value() == value) { + static_cast<HTMLOptionElementImpl*>(items[i])->setSelected(true); + return; + } +} + +QString HTMLSelectElementImpl::state( ) +{ + QString state; + QMemArray<HTMLGenericFormElementImpl*> items = listItems(); + + const int l = items.count(); + + state.fill('.', l); + for(int i = 0; i < l; ++i) + if(items[i]->id() == ID_OPTION && static_cast<HTMLOptionElementImpl*>(items[i])->selected()) + state[i] = 'X'; + + return state; +} + +void HTMLSelectElementImpl::restoreState(const QString &_state) +{ + recalcListItems(); + + QString state = _state; + if(!state.isEmpty() && !state.contains('X') && !m_multiple && m_size <= 1) { + qWarning("should not happen in restoreState!"); + state[0] = 'X'; + } + + QMemArray<HTMLGenericFormElementImpl*> items = listItems(); + + const int l = items.count(); + for(int i = 0; i < l; ++i) { + if(items[i]->id() == ID_OPTION) { + HTMLOptionElementImpl* const oe = static_cast<HTMLOptionElementImpl*>(items[i]); + oe->setSelected(state[i] == 'X'); + } + } + setChanged(true); +} + +NodeImpl *HTMLSelectElementImpl::insertBefore ( NodeImpl *newChild, NodeImpl *refChild, int &exceptioncode ) +{ + NodeImpl* const result = HTMLGenericFormElementImpl::insertBefore(newChild,refChild, exceptioncode ); + if (!exceptioncode) + setRecalcListItems(); + return result; +} + +void HTMLSelectElementImpl::replaceChild ( NodeImpl *newChild, NodeImpl *oldChild, int &exceptioncode ) +{ + HTMLGenericFormElementImpl::replaceChild(newChild,oldChild, exceptioncode); + if( !exceptioncode ) + setRecalcListItems(); +} + +void HTMLSelectElementImpl::removeChild ( NodeImpl *oldChild, int &exceptioncode ) +{ + HTMLGenericFormElementImpl::removeChild(oldChild, exceptioncode); + if( !exceptioncode ) + setRecalcListItems(); +} + +NodeImpl *HTMLSelectElementImpl::appendChild ( NodeImpl *newChild, int &exceptioncode ) +{ + NodeImpl* const result = HTMLGenericFormElementImpl::appendChild(newChild, exceptioncode); + if( !exceptioncode ) + setRecalcListItems(); + setChanged(true); + return result; +} + +NodeImpl* HTMLSelectElementImpl::addChild(NodeImpl* newChild) +{ + setRecalcListItems(); + return HTMLGenericFormElementImpl::addChild(newChild); +} + +void HTMLSelectElementImpl::parseAttribute(AttributeImpl *attr) +{ + switch(attr->id()) + { + case ATTR_SIZE: + m_size = kMax( attr->val()->toInt(), 1 ); + setChanged(); + break; + case ATTR_WIDTH: + m_minwidth = kMax( attr->val()->toInt(), 0 ); + break; + case ATTR_MULTIPLE: + m_multiple = (attr->val() != 0); + break; + case ATTR_ACCESSKEY: + break; + case ATTR_ALIGN: + addHTMLAlignment( attr->value() ); + break; + case ATTR_ONCHANGE: + setHTMLEventListener(EventImpl::CHANGE_EVENT, + getDocument()->createHTMLEventListener(attr->value().string(), "onchange", this)); + break; + default: + HTMLGenericFormElementImpl::parseAttribute(attr); + } +} + +void HTMLSelectElementImpl::attach() +{ + assert(!attached()); + assert(parentNode()); + assert(!renderer()); + + RenderStyle* const _style = getDocument()->styleSelector()->styleForElement(this); + _style->ref(); + if (parentNode()->renderer() && _style->display() != NONE) { + m_render = new (getDocument()->renderArena()) RenderSelect(this); + m_render->setStyle(_style); + } + + HTMLGenericFormElementImpl::attach(); + _style->deref(); +} + +bool HTMLSelectElementImpl::encoding(const QTextCodec* codec, khtml::encodingList& encoded_values, bool) +{ + bool successful = false; + const QCString enc_name = fixUpfromUnicode(codec, name().string()); + QMemArray<HTMLGenericFormElementImpl*> items = listItems(); + + uint i; + const uint itemsSize = items.size(); + for (i = 0; i < itemsSize; ++i) { + if (items[i]->id() == ID_OPTION) { + HTMLOptionElementImpl* const option = static_cast<HTMLOptionElementImpl*>(items[i]); + if (option->selected()) { + encoded_values += enc_name; + encoded_values += fixUpfromUnicode(codec, option->value().string()); + 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 && itemsSize && + (items[0]->id() == ID_OPTION) ) { + HTMLOptionElementImpl* const option = static_cast<HTMLOptionElementImpl*>(items[0]); + encoded_values += enc_name; + if (option->value().isNull()) + encoded_values += fixUpfromUnicode(codec, option->text().string().stripWhiteSpace()); + else + encoded_values += fixUpfromUnicode(codec, option->value().string()); + successful = true; + } + + return successful; +} + +int HTMLSelectElementImpl::optionToListIndex(int optionIndex) const +{ + QMemArray<HTMLGenericFormElementImpl*> items = listItems(); + const int itemsSize = int(items.size()); + if (optionIndex < 0 || optionIndex >= itemsSize) + return -1; + + //See if we're asked for the very last item, and check whether it's an <option> + //to fastpath clear + if ((unsigned int)optionIndex == (m_length - 1) && items[itemsSize - 1]->id() == ID_OPTION) + return itemsSize - 1; + + int listIndex = 0; + int optionIndex2 = 0; + for (; + optionIndex2 < itemsSize && optionIndex2 <= optionIndex; + ++listIndex) { // not a typo! + if (items[listIndex]->id() == ID_OPTION) + ++optionIndex2; + } + --listIndex; + return listIndex; +} + +int HTMLSelectElementImpl::listToOptionIndex(int listIndex) const +{ + QMemArray<HTMLGenericFormElementImpl*> items = listItems(); + if (listIndex < 0 || listIndex >= int(items.size()) || + items[listIndex]->id() != ID_OPTION) + 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]->id() == ID_OPTION) + optionIndex++; + return optionIndex; +} + +void HTMLSelectElementImpl::recalcListItems() const +{ + NodeImpl* current = firstChild(); + m_listItems.resize(0); + HTMLOptionElementImpl* foundSelected = 0; + m_length = 0; + while(current) { + if (current->id() == ID_OPTGROUP && 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<HTMLGenericFormElementImpl*>(current); + current = current->firstChild(); + } + if (current->id() == ID_OPTION) { + ++m_length; + m_listItems.resize(m_listItems.size()+1); + m_listItems[m_listItems.size()-1] = static_cast<HTMLGenericFormElementImpl*>(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); + } + } + NodeImpl* const parent = current->parentNode(); + current = current->nextSibling(); + if (!current) { + if (static_cast<const NodeImpl *>(parent) != this) + current = parent->nextSibling(); + } + } + m_recalcListItems = false; +} + +void HTMLSelectElementImpl::childrenChanged() +{ + setRecalcListItems(); + + HTMLGenericFormElementImpl::childrenChanged(); +} + +void HTMLSelectElementImpl::setRecalcListItems() +{ + m_recalcListItems = true; + if (m_render) + static_cast<khtml::RenderSelect*>(m_render)->setOptionsChanged(true); + setChanged(); +} + +void HTMLSelectElementImpl::reset() +{ + QMemArray<HTMLGenericFormElementImpl*> items = listItems(); + uint i; + const uint itemsSize = items.size(); + bool anySelected = false; + for (i = 0; i < itemsSize; ++i) { + if (items[i]->id() == ID_OPTION) { + HTMLOptionElementImpl* const option = static_cast<HTMLOptionElementImpl*>(items[i]); + const bool selected = (!option->getAttribute(ATTR_SELECTED).isNull()); + option->setSelected(selected); + if (selected) + anySelected = true; + } + } + // If this is a single-row SELECT and there is no default selection, jump to first option. + if ( !anySelected && m_size <= 1 ) { + for (i = 0; i < itemsSize; ++i) { + if (items[i]->id() == ID_OPTION) { + static_cast<HTMLOptionElementImpl*>(items[i])->setSelected(true); + break; + } + } + } + if ( m_render ) + static_cast<RenderSelect*>(m_render)->setSelectionChanged(true); + setChanged( true ); +} + +void HTMLSelectElementImpl::notifyOptionSelected(HTMLOptionElementImpl *selectedOption, bool selected) +{ + if (selected && !m_multiple) { + // deselect all other options + QMemArray<HTMLGenericFormElementImpl*> items = listItems(); + uint i; + const uint itemsSize = items.size(); + for (i = 0; i < itemsSize; ++i) { + if (items[i]->id() == ID_OPTION) + static_cast<HTMLOptionElementImpl*>(items[i])->m_selected = (items[i] == selectedOption); + } + } + if (m_render) + static_cast<RenderSelect*>(m_render)->setSelectionChanged(true); + + setChanged(true); +} + +// ------------------------------------------------------------------------- + +HTMLKeygenElementImpl::HTMLKeygenElementImpl(DocumentImpl* doc, HTMLFormElementImpl* f) + : HTMLSelectElementImpl(doc, f) +{ + const QStringList keys = KSSLKeyGen::supportedKeySizes(); + QStringList::ConstIterator i = keys.begin(); + const QStringList::ConstIterator iEnd = keys.end(); + for ( ; i != iEnd; ++i) { + HTMLOptionElementImpl* const o = new HTMLOptionElementImpl(doc, form()); + addChild(o); + o->addChild(doc->createTextNode(DOMString(*i).implementation())); + } +} + +NodeImpl::Id HTMLKeygenElementImpl::id() const +{ + return ID_KEYGEN; +} + +void HTMLKeygenElementImpl::parseAttribute(AttributeImpl* attr) +{ + switch(attr->id()) + { + case ATTR_CHALLENGE: + break; + default: + // skip HTMLSelectElementImpl parsing! + HTMLGenericFormElementImpl::parseAttribute(attr); + } +} + +bool HTMLKeygenElementImpl::encoding(const QTextCodec* codec, khtml::encodingList& encoded_values, bool) +{ + bool successful = false; + const QCString enc_name = fixUpfromUnicode(codec, name().string()); + + encoded_values += enc_name; + + // pop up the fancy certificate creation dialog here + KSSLKeyGen* const kg = new KSSLKeyGen(getDocument()->view(), "Key Generator", true); + + kg->setKeySize(0); + successful = (QDialog::Accepted == kg->exec()); + + delete kg; + + encoded_values += "deadbeef"; + + return successful; +} + +// ------------------------------------------------------------------------- + +NodeImpl::Id HTMLOptGroupElementImpl::id() const +{ + return ID_OPTGROUP; +} + +// ------------------------------------------------------------------------- + +HTMLOptionElementImpl::HTMLOptionElementImpl(DocumentImpl *doc, HTMLFormElementImpl *f) + : HTMLGenericFormElementImpl(doc, f) +{ + m_selected = false; +} + +NodeImpl::Id HTMLOptionElementImpl::id() const +{ + return ID_OPTION; +} + +DOMString HTMLOptionElementImpl::text() const +{ + if (firstChild() && firstChild()->nodeType() == Node::TEXT_NODE) { + if (firstChild()->nextSibling()) { + DOMString ret = ""; + NodeImpl *n = firstChild(); + for (; n; n = n->nextSibling()) { + if (n->nodeType() == Node::TEXT_NODE || + n->nodeType() == Node::CDATA_SECTION_NODE) + ret += n->nodeValue(); + } + return ret; + } + else + return firstChild()->nodeValue(); + } + return ""; +} + +long HTMLOptionElementImpl::index() const +{ + // Let's do this dynamically. Might be a bit slow, but we're sure + // we won't forget to update a member variable in some cases... + QMemArray<HTMLGenericFormElementImpl*> items = getSelect()->listItems(); + const int l = items.count(); + int optionIndex = 0; + for(int i = 0; i < l; ++i) { + if(items[i]->id() == ID_OPTION) + { + if (static_cast<HTMLOptionElementImpl*>(items[i]) == this) + return optionIndex; + ++optionIndex; + } + } + kdWarning() << "HTMLOptionElementImpl::index(): option not found!" << endl; + return 0; +} + +void HTMLOptionElementImpl::setIndex( long ) +{ + kdWarning() << "Unimplemented HTMLOptionElementImpl::setIndex(long) called" << endl; + // ### +} + +void HTMLOptionElementImpl::parseAttribute(AttributeImpl *attr) +{ + switch(attr->id()) + { + case ATTR_SELECTED: + m_selected = (attr->val() != 0); + break; + case ATTR_VALUE: + m_value = attr->value(); + break; + default: + HTMLGenericFormElementImpl::parseAttribute(attr); + } +} + +DOMString HTMLOptionElementImpl::value() const +{ + if ( !m_value.isNull() ) + return m_value; + // Use the text if the value wasn't set. + return text().string().simplifyWhiteSpace(); +} + +void HTMLOptionElementImpl::setValue(DOMStringImpl* value) +{ + setAttribute(ATTR_VALUE, value); +} + +void HTMLOptionElementImpl::setSelected(bool _selected) +{ + if(m_selected == _selected) + return; + m_selected = _selected; + HTMLSelectElementImpl* const select = getSelect(); + if (select) + select->notifyOptionSelected(this,_selected); +} + +HTMLSelectElementImpl *HTMLOptionElementImpl::getSelect() const +{ + NodeImpl *select = parentNode(); + while (select && select->id() != ID_SELECT) + select = select->parentNode(); + return static_cast<HTMLSelectElementImpl*>(select); +} + +// ------------------------------------------------------------------------- + +HTMLTextAreaElementImpl::HTMLTextAreaElementImpl(DocumentImpl *doc, HTMLFormElementImpl *f) + : HTMLGenericFormElementImpl(doc, f) +{ + // DTD requires rows & cols be specified, but we will provide reasonable defaults + m_rows = 2; + m_cols = 20; + m_wrap = ta_Virtual; + m_changed = false; + m_dirtyvalue = true; + m_initialized = false; + m_unsubmittedFormChange = false; +} + +HTMLTextAreaElementImpl::~HTMLTextAreaElementImpl() +{ + if (getDocument()) getDocument()->deregisterMaintainsState(this); +} + +NodeImpl::Id HTMLTextAreaElementImpl::id() const +{ + return ID_TEXTAREA; +} + +DOMString HTMLTextAreaElementImpl::type() const +{ + return "textarea"; +} + +QString HTMLTextAreaElementImpl::state( ) +{ + return value().string() + (m_unsubmittedFormChange ? 'M' : '.'); +} + +void HTMLTextAreaElementImpl::restoreState(const QString &state) +{ + setDefaultValue(state.left(state.length()-1)); + m_unsubmittedFormChange = state.endsWith("M"); + // the close() in the rendertree will take care of transferring defaultvalue to 'value' +} + +void HTMLTextAreaElementImpl::select( ) +{ + if (m_render) + static_cast<RenderTextArea*>(m_render)->select(); + onSelect(); +} + +void HTMLTextAreaElementImpl::parseAttribute(AttributeImpl *attr) +{ + switch(attr->id()) + { + case ATTR_ROWS: + m_rows = 0; + if (attr->val()) + m_rows = DOMString(attr->val()).string().toInt(); + if (!m_rows) m_rows = 2; + if (renderer()) + renderer()->setNeedsLayoutAndMinMaxRecalc(); + break; + case ATTR_COLS: + m_cols = 0; + if (attr->val()) + m_cols = DOMString(attr->val()).string().toInt(); + if (!m_cols) m_cols = 20; + if (renderer()) + renderer()->setNeedsLayoutAndMinMaxRecalc(); + break; + case ATTR_WRAP: + // virtual / physical is Netscape extension of HTML 3.0, now deprecated + // soft/ hard / off is recommendation for HTML 4 extension by IE and NS 4 + if ( strcasecmp( attr->value(), "virtual" ) == 0 || strcasecmp( attr->value(), "soft") == 0) + m_wrap = ta_Virtual; + else if ( strcasecmp ( attr->value(), "physical" ) == 0 || strcasecmp( attr->value(), "hard") == 0) + m_wrap = ta_Physical; + else if(strcasecmp( attr->value(), "on" ) == 0) + m_wrap = ta_Physical; + else if(strcasecmp( attr->value(), "off") == 0) + m_wrap = ta_NoWrap; + break; + case ATTR_ACCESSKEY: + break; + case ATTR_ALIGN: + break; + case ATTR_ONSELECT: + setHTMLEventListener(EventImpl::SELECT_EVENT, + getDocument()->createHTMLEventListener(attr->value().string(), "onselect", this)); + break; + case ATTR_ONCHANGE: + setHTMLEventListener(EventImpl::CHANGE_EVENT, + getDocument()->createHTMLEventListener(attr->value().string(), "onchange", this)); + break; + default: + HTMLGenericFormElementImpl::parseAttribute(attr); + } +} + +void HTMLTextAreaElementImpl::attach() +{ + assert(!attached()); + assert(!m_render); + assert(parentNode()); + + RenderStyle* const _style = getDocument()->styleSelector()->styleForElement(this); + _style->ref(); + if (parentNode()->renderer() && _style->display() != NONE) { + m_render = new (getDocument()->renderArena()) RenderTextArea(this); + m_render->setStyle(_style); + } + + HTMLGenericFormElementImpl::attach(); + _style->deref(); +} + + +static QString expandLF(const QString& s) +{ + // LF -> CRLF + unsigned crs = s.contains( '\n' ); + if (crs == 0) + return s; + unsigned len = s.length(); + + QString r; + r.reserve(len + crs + 1); + unsigned pos2 = 0; + for(unsigned pos = 0; pos < len; pos++) + { + QChar c = s.at(pos); + switch(c.unicode()) + { + case '\n': + r[pos2++] = '\r'; + r[pos2++] = '\n'; + break; + + case '\r': + break; + + default: + r[pos2++]= c; + break; + } + } + r.squeeze(); + return r; +} + + +bool HTMLTextAreaElementImpl::encoding(const QTextCodec* codec, encodingList& encoding, bool) +{ + if (name().isEmpty()) return false; + + encoding += fixUpfromUnicode(codec, name().string()); + encoding += fixUpfromUnicode(codec, expandLF(value().string())); + + return true; +} + +void HTMLTextAreaElementImpl::reset() +{ + setValue(defaultValue()); +} + + +DOMString HTMLTextAreaElementImpl::value() +{ + if ( m_dirtyvalue) { + if ( m_render && m_initialized ) { + RenderTextArea* renderArea = static_cast<RenderTextArea*>( m_render ); + m_value = renderArea->text(); + m_dirtyvalue = false; + } else { + m_value = defaultValue().string(); + m_initialized = true; + m_dirtyvalue = false; + } + } + + if ( m_value.isNull() ) return ""; + + return m_value; +} + +void HTMLTextAreaElementImpl::setValue(DOMString _value) +{ + // \r\n -> \n, \r -> \n + QString str = _value.string().replace( "\r\n", "\n" ); + m_value = str.replace( '\r', '\n' ); + m_dirtyvalue = false; + m_initialized = true; + setChanged(true); +} + +DOMString HTMLTextAreaElementImpl::defaultValue() +{ + DOMString val = ""; + // there may be comments - just grab the text nodes + NodeImpl *n; + for (n = firstChild(); n; n = n->nextSibling()) + if (n->isTextNode()) + val += static_cast<TextImpl*>(n)->data(); + + if (val[0] == '\r' && val[1] == '\n') { + val = val.copy(); + val.remove(0,2); + } + else if (val[0] == '\r' || val[0] == '\n') { + val = val.copy(); + val.remove(0,1); + } + + return val; +} + +void HTMLTextAreaElementImpl::setDefaultValue(DOMString _defaultValue) +{ + // there may be comments - remove all the text nodes and replace them with one + QPtrList<NodeImpl> toRemove; + NodeImpl *n; + for (n = firstChild(); n; n = n->nextSibling()) + if (n->isTextNode()) + toRemove.append(n); + QPtrListIterator<NodeImpl> it(toRemove); + int exceptioncode = 0; + for (; it.current(); ++it) { + removeChild(it.current(), exceptioncode); + } + insertBefore(getDocument()->createTextNode(_defaultValue.implementation()),firstChild(), exceptioncode); + setValue(_defaultValue); +} + +void HTMLTextAreaElementImpl::blur() +{ + if(getDocument()->focusNode() == this) + getDocument()->setFocusNode(0); +} + +void HTMLTextAreaElementImpl::focus() +{ + getDocument()->setFocusNode(this); +} + +bool HTMLTextAreaElementImpl::isEditable() +{ + return true; +} + +//Mozilla extensions. +long HTMLTextAreaElementImpl::selectionStart() +{ + if (m_render) { + RenderTextArea* renderArea = static_cast<RenderTextArea*>( m_render ); + return renderArea->selectionStart(); + } + + return 0; +} + +long HTMLTextAreaElementImpl::selectionEnd() +{ + if (m_render) { + RenderTextArea* renderArea = static_cast<RenderTextArea*>( m_render ); + return renderArea->selectionEnd(); + } + + return 0; +} + +void HTMLTextAreaElementImpl::setSelectionStart(long pos) +{ + if (m_render) { + RenderTextArea* renderArea = static_cast<RenderTextArea*>( m_render ); + renderArea->setSelectionStart( pos ); + } +} + +void HTMLTextAreaElementImpl::setSelectionEnd(long pos) +{ + if (m_render) { + RenderTextArea* renderArea = static_cast<RenderTextArea*>( m_render ); + renderArea->setSelectionEnd( pos ); + } +} + +void HTMLTextAreaElementImpl::setSelectionRange(long start, long end) +{ + if (m_render) { + RenderTextArea* renderArea = static_cast<RenderTextArea*>( m_render ); + renderArea->setSelectionRange( start, end ); + } +} + +long HTMLTextAreaElementImpl::textLength() +{ + return value().length(); +} + +// ------------------------------------------------------------------------- + +HTMLIsIndexElementImpl::HTMLIsIndexElementImpl(DocumentImpl *doc, HTMLFormElementImpl *f) + : HTMLInputElementImpl(doc, f) +{ + m_type = TEXT; + setName("isindex"); +} + +HTMLIsIndexElementImpl::~HTMLIsIndexElementImpl() +{ +} + +NodeImpl::Id HTMLIsIndexElementImpl::id() const +{ + return ID_ISINDEX; +} + +void HTMLIsIndexElementImpl::parseAttribute(AttributeImpl* attr) +{ + // don't call HTMLInputElement::parseAttribute here, as it would + // accept attributes this element does not support + HTMLGenericFormElementImpl::parseAttribute(attr); +} + +DOMString HTMLIsIndexElementImpl::prompt() const +{ + // When IsIndex is parsed, <HR/>Prompt: <ISINDEX/><HR/> is created. + // So we have to look at the previous sibling to find the prompt text + DOM::NodeImpl* const prev = previousSibling(); + if ( prev && prev->nodeType() == DOM::Node::TEXT_NODE) + return prev->nodeValue(); + return ""; +} + +void HTMLIsIndexElementImpl::setPrompt(const DOMString& str) +{ + // When IsIndex is parsed, <HR/>Prompt: <ISINDEX/><HR/> is created. + // So we have to look at the previous sibling to find the prompt text + int exceptioncode = 0; + DOM::NodeImpl* const prev = previousSibling(); + if ( prev && prev->nodeType() == DOM::Node::TEXT_NODE) + static_cast<DOM::TextImpl *>(prev)->setData(str, exceptioncode); +} + +// ------------------------------------------------------------------------- + |