/* * 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) 2003 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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, 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/csshelper.h" #include "xml/dom_textimpl.h" #include "xml/dom2_eventsimpl.h" #include "khtml_ext.h" #include "rendering/render_form.h" #include #include #include #include #include #include #include #include #include #include // for keygen #include #include #include using namespace DOM; using namespace khtml; HTMLFormElementImpl::HTMLFormElementImpl(DocumentPtr *doc) : HTMLElementImpl(doc) { 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 = "----------0xKhTmLbOuNdArY"; m_acceptcharset = "UNKNOWN"; m_malformed = false; } HTMLFormElementImpl::~HTMLFormElementImpl() { QPtrListIterator it(formElements); for (; it.current(); ++it) it.current()->m_form = 0; } NodeImpl::Id HTMLFormElementImpl::id() const { return ID_FORM; } #if APPLE_CHANGES bool HTMLFormElementImpl::formWouldHaveSecureSubmission(DOMString url) { if (url.isNull()) { return false; } else { QString fullUrl = getDocument()->completeURL( url.string() ); return strncmp(fullUrl.latin1(), "https:", 6) == 0; } } #endif void HTMLFormElementImpl::attach() { HTMLElementImpl::attach(); if (getDocument()->isHTMLDocument()) { HTMLDocumentImpl *document = static_cast(getDocument()); document->addNamedImageOrForm(oldNameAttr); document->addNamedImageOrForm(oldIdAttr); } #if APPLE_CHANGES // note we don't deal with calling secureFormRemoved() on detach, because the timing // was such that it cleared our state too early if (formWouldHaveSecureSubmission(m_url)) getDocument()->secureFormAdded(); #endif } void HTMLFormElementImpl::detach() { if (getDocument()->isHTMLDocument()) { HTMLDocumentImpl *document = static_cast(getDocument()); document->removeNamedImageOrForm(oldNameAttr); document->removeNamedImageOrForm(oldIdAttr); } HTMLElementImpl::detach(); } long HTMLFormElementImpl::length() const { int len = 0; QPtrListIterator 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 = "-._*"; int elen = e.length(); QCString encoded(( elen+e.contains( '\n' ) )*3+1); int enclen = 0; //QCString orig(e.data(), e.size()); for(int pos = 0; pos < elen; pos++) { unsigned char c = e[pos]; 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' || ( c == '\r' && e[pos+1] != '\n' ) ) { encoded[enclen++] = '%'; encoded[enclen++] = '0'; encoded[enclen++] = 'D'; encoded[enclen++] = '%'; encoded[enclen++] = '0'; encoded[enclen++] = 'A'; } 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; } // Change plain CR and plain LF to CRLF pairs. static QCString fixLineBreaks(const QCString &s) { // Compute the length. unsigned newLen = 0; const char *p = s.data(); while (char c = *p++) { if (c == '\r') { // Safe to look ahead because of trailing '\0'. if (*p != '\n') { // Turn CR into CRLF. newLen += 2; } } else if (c == '\n') { // Turn LF into CRLF. newLen += 2; } else { // Leave other characters alone. newLen += 1; } } if (newLen == s.length()) { return s; } // Make a copy of the string. p = s.data(); QCString result(newLen + 1); char *q = result.data(); while (char c = *p++) { if (c == '\r') { // Safe to look ahead because of trailing '\0'. if (*p != '\n') { // Turn CR into CRLF. *q++ = '\r'; *q++ = '\n'; } } else if (c == '\n') { // Turn LF into CRLF. *q++ = '\r'; *q++ = '\n'; } else { // Leave other characters alone. *q++ = c; } } return result; } inline static QCString fixUpfromUnicode(const QTextCodec* codec, const QString& s) { QCString str = fixLineBreaks(codec->fromUnicode(s)); str.truncate(str.length()); return str; } #if !APPLE_CHANGES void HTMLFormElementImpl::i18nData() { QString foo1 = i18n( "You're about to send data to the Internet " "via an unencrypted connection. It might be possible " "for others to see this information.\n" "Do you want to continue?"); QString foo2 = i18n("KDE Web browser"); QString foo3 = i18n("When you send a password unencrypted to the Internet, " "it might be possible for others to capture it as plain text.\n" "Do you want to continue?"); QString foo5 = i18n("Your data submission is redirected to " "an insecure site. The data is sent unencrypted.\n" "Do you want to continue?"); QString foo6 = i18n("The page contents expired. You can repost the form" "data by using Reload"); } #endif 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 QString str = m_acceptcharset.string(); str.replace(',', ' '); QStringList charsets = QStringList::split(' ', str); QTextCodec* codec = 0; KHTMLView *view = getDocument()->view(); for ( QStringList::Iterator it = charsets.begin(); it != charsets.end(); ++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(); QStringList fileUploads; for (QPtrListIterator it(formElements); it.current(); ++it) { HTMLGenericFormElementImpl* 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::Iterator it; for( it = lst.begin(); it != lst.end(); ++it ) { if (!m_multipart) { // handle 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.string().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->nodeType() == Node::ELEMENT_NODE && current->id() == ID_INPUT && static_cast(current)->inputType() == HTMLInputElementImpl::FILE) { QString path = static_cast(current)->value().string(); if (path.length()) fileUploads << path; // Turn non-ASCII characters into &-escaped form. // This doesn't work perfectly, because it doesn't escape &, for example. // But it seems to be what Gecko does. QString onlyfilename; for (uint i = path.findRev('/') + 1; i < path.length(); ++i) { QChar c = path.at(i).unicode(); if (c.unicode() >= 0x20 && c.unicode() <= 0x7F) { onlyfilename.append(&c, 1); } else { QString ampersandEscape; ampersandEscape.sprintf("&#%hu;", c.unicode()); onlyfilename.append(ampersandEscape); } } // FIXME: This won't work if the filename includes a " mark, // or control characters like CR or LF. hstr += ("; filename=\"" + onlyfilename + "\"").ascii(); if(!static_cast(current)->value().isEmpty()) { #if APPLE_CHANGES KWQKHTMLPart *part = KWQ(current->getDocument()->view()->part()); QString mimeType = part->mimeTypeForFileName(onlyfilename); #else KMimeType::Ptr ptr = KMimeType::findByURL(KURL(path)); QString mimeType = ptr->name(); #endif if (!mimeType.isEmpty()) { hstr += "\r\nContent-Type: "; hstr += mimeType.ascii(); } } } hstr += "\r\n\r\n"; ++it; // append body 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'; } } } } #if !APPLE_CHANGES if (fileUploads.count()) { 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); if (result == KMessageBox::Cancel) { ok = false; return QByteArray(); } } #endif if (m_multipart) enc_string = ("--" + m_boundary.string() + "--\r\n").ascii(); 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; } } void HTMLFormElementImpl::setBoundary( const DOMString& bound ) { m_boundary = bound; } bool HTMLFormElementImpl::prepareSubmit() { KHTMLView *view = getDocument()->view(); if(m_insubmit || !view || !view->part() || view->part()->onlyLocalReferences()) return m_insubmit; m_insubmit = true; m_doingsubmit = false; if ( dispatchHTMLEvent(EventImpl::SUBMIT_EVENT,false,true) && !m_doingsubmit ) m_doingsubmit = true; m_insubmit = false; if ( m_doingsubmit ) submit(true); return m_doingsubmit; } void HTMLFormElementImpl::submit( bool activateSubmitButton ) { KHTMLView *view = getDocument()->view(); if (!view || !view->part()) { return; } if ( m_insubmit ) { m_doingsubmit = true; return; } m_insubmit = true; #ifdef FORMS_DEBUG kdDebug( 6030 ) << "submitting!" << endl; #endif HTMLGenericFormElementImpl* firstSuccessfulSubmitButton = 0; bool needButtonActivation = activateSubmitButton; // do we need to activate a submit button? #if APPLE_CHANGES KWQ(view->part())->clearRecordedFormValues(); #endif for (QPtrListIterator it(formElements); it.current(); ++it) { HTMLGenericFormElementImpl* current = it.current(); #if APPLE_CHANGES // Our app needs to get form values for password fields for doing password autocomplete, // so we are more lenient in pushing values, and let the app decide what to save when. if (current->id() == ID_INPUT) { HTMLInputElementImpl *input = static_cast(current); if (input->inputType() == HTMLInputElementImpl::TEXT || input->inputType() == HTMLInputElementImpl::PASSWORD) { KWQ(view->part())->recordFormValue(input->name().string(), input->value().string(), this); } } #else if (current->id() == ID_INPUT && static_cast(current)->inputType() == HTMLInputElementImpl::TEXT && static_cast(current)->autoComplete() ) { HTMLInputElementImpl *input = static_cast(current); view->addFormCompletionItem(input->name().string(), input->value().string()); } #endif if (needButtonActivation) { if (current->isActivatedSubmit()) { needButtonActivation = false; } else if (firstSuccessfulSubmitButton == 0 && current->isSuccessfulSubmitButton()) { firstSuccessfulSubmitButton = current; } } } if (needButtonActivation && firstSuccessfulSubmitButton) { firstSuccessfulSubmitButton->setActivatedSubmit(true); } bool ok; QByteArray form_data = formData(ok); if (ok) { if(m_post) { view->part()->submitForm( "post", m_url.string(), form_data, m_target.string(), enctype().string(), boundary().string() ); } else { view->part()->submitForm( "get", m_url.string(), form_data, m_target.string() ); } } if (needButtonActivation && firstSuccessfulSubmitButton) { firstSuccessfulSubmitButton->setActivatedSubmit(false); } m_doingsubmit = m_insubmit = false; } void HTMLFormElementImpl::reset( ) { KHTMLView *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 it(formElements); it.current(); ++it) it.current()->reset(); m_inreset = false; } void HTMLFormElementImpl::parseAttribute(AttributeImpl *attr) { switch(attr->id()) { case ATTR_ACTION: #if APPLE_CHANGES { bool oldURLWasSecure = formWouldHaveSecureSubmission(m_url); #endif m_url = khtml::parseURL(attr->value()); #if APPLE_CHANGES bool newURLIsSecure = formWouldHaveSecureSubmission(m_url); if (m_attached && (oldURLWasSecure != newURLIsSecure)) if (newURLIsSecure) getDocument()->secureFormAdded(); else getDocument()->secureFormRemoved(); } #endif break; case ATTR_TARGET: m_target = attr->value(); break; case ATTR_METHOD: if ( strcasecmp( attr->value(), "post" ) == 0 ) m_post = true; else if ( strcasecmp( attr->value(), "get" ) == 0 ) m_post = false; 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())); break; case ATTR_ONRESET: setHTMLEventListener(EventImpl::RESET_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; case ATTR_NAME: { QString newNameAttr = attr->value().string(); if (attached() && getDocument()->isHTMLDocument()) { HTMLDocumentImpl *document = static_cast(getDocument()); document->removeNamedImageOrForm(oldNameAttr); document->addNamedImageOrForm(newNameAttr); } oldNameAttr = newNameAttr; } break; case ATTR_ID: { QString newIdAttr = attr->value().string(); if (attached() && getDocument()->isHTMLDocument()) { HTMLDocumentImpl *document = static_cast(getDocument()); document->removeNamedImageOrForm(oldIdAttr); document->addNamedImageOrForm(newIdAttr); } oldIdAttr = newIdAttr; } // fall through default: HTMLElementImpl::parseAttribute(attr); } } void HTMLFormElementImpl::radioClicked( HTMLGenericFormElementImpl *caller ) { for (QPtrListIterator it(formElements); it.current(); ++it) { HTMLGenericFormElementImpl *current = it.current(); if (current->id() == ID_INPUT && static_cast(current)->inputType() == HTMLInputElementImpl::RADIO && current != caller && current->form() == caller->form() && current->name() == caller->name()) { static_cast(current)->setChecked(false); } } } void HTMLFormElementImpl::registerFormElement(HTMLGenericFormElementImpl *e) { formElements.append(e); } void HTMLFormElementImpl::removeFormElement(HTMLGenericFormElementImpl *e) { formElements.remove(e); } // ------------------------------------------------------------------------- HTMLGenericFormElementImpl::HTMLGenericFormElementImpl(DocumentPtr *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); } HTMLGenericFormElementImpl::~HTMLGenericFormElementImpl() { if (m_form) m_form->removeFormElement(this); } void HTMLGenericFormElementImpl::parseAttribute(AttributeImpl *attr) { switch(attr->id()) { case ATTR_NAME: break; case ATTR_DISABLED: setDisabled( attr->val() != 0 ); break; case ATTR_READONLY: { 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()); // 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(p); p = p->parentNode(); } #ifdef FORMS_DEBUG kdDebug( 6030 ) << "couldn't find form!" << 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); 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(); 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); } bool HTMLGenericFormElementImpl::disabled() const { return m_disabled; } void HTMLGenericFormElementImpl::setDisabled( bool _disabled ) { if ( m_disabled != _disabled ) { m_disabled = _disabled; setChanged(); } } void HTMLGenericFormElementImpl::recalcStyle( StyleChange ch ) { //bool changed = changed(); HTMLElementImpl::recalcStyle( ch ); if (m_render /*&& changed*/) m_render->updateFromElement(); } bool HTMLGenericFormElementImpl::isSelectable() const { return m_render && m_render->isWidget() && static_cast(m_render)->widget() && static_cast(m_render)->widget()->focusPolicy() >= QWidget::TabFocus; } void HTMLGenericFormElementImpl::defaultEventHandler(EventImpl *evt) { if (evt->target()==this) { // Report focus in/out changes to the browser extension (editable widgets only) KHTMLView *view = getDocument()->view(); if (evt->id()==EventImpl::DOMFOCUSIN_EVENT && isEditable() && view && m_render && m_render->isWidget()) { KHTMLPartBrowserExtension *ext = static_cast(view->part()->browserExtension()); QWidget *widget = static_cast(m_render)->widget(); if (ext) ext->editableWidgetFocused(widget); } if (evt->id()==EventImpl::MOUSEDOWN_EVENT || evt->id()==EventImpl::KHTML_KEYDOWN_EVENT) { setActive(); } else if (evt->id() == EventImpl::MOUSEUP_EVENT || evt->id()==EventImpl::KHTML_KEYUP_EVENT) { if (m_active) { setActive(false); setFocus(); } else { setActive(false); } } #if APPLE_CHANGES // We don't want this default key event handling, we'll count on // Cocoa event dispatch if the event doesn't get blocked. #else if (evt->id()==EventImpl::KHTML_KEYDOWN_EVENT || evt->id()==EventImpl::KHTML_KEYUP_EVENT) { KeyEventImpl * k = static_cast(evt); if (k->keyVal() == QChar('\n').unicode() && m_render && m_render->isWidget() && k->qKeyEvent) QApplication::sendEvent(static_cast(m_render)->widget(), k->qKeyEvent); } #endif if (evt->id()==EventImpl::DOMFOCUSOUT_EVENT && isEditable() && view && m_render && m_render->isWidget()) { KHTMLPartBrowserExtension *ext = static_cast(view->part()->browserExtension()); QWidget *widget = static_cast(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) } } HTMLElementImpl::defaultEventHandler(evt); } bool HTMLGenericFormElementImpl::isEditable() { return false; } // Special chars used to encode form state strings. // We pick chars that are unlikely to be used in an HTML attr, so we rarely have to really encode. const char stateSeparator = '&'; const char stateEscape = '<'; static const char stateSeparatorMarker[] = "= 0 || escLoc >= 0) { QString newStr = str; // replace "<" with "<<" while (escLoc >= 0) { newStr.replace(escLoc, 1, stateEscapeMarker); escLoc = str.find(stateSeparator, escLoc+1); } // replace "&" with "= 0) { newStr.replace(sepLoc, 1, stateSeparatorMarker); sepLoc = str.find(stateSeparator, sepLoc+1); } return newStr; } else { return str; } } QString HTMLGenericFormElementImpl::state( ) { // Build a string that contains ElementName&ElementType& return encodedElementName(name().string()) + stateSeparator + type().string() + stateSeparator; } QString HTMLGenericFormElementImpl::findMatchingState(QStringList &states) { QString encName = encodedElementName(name().string()); QString typeStr = type().string(); for (QStringList::Iterator it = states.begin(); it != states.end(); ++it) { QString state = *it; int sep1 = state.find(stateSeparator); int sep2 = state.find(stateSeparator, sep1+1); assert(sep1 >= 0); assert(sep2 >= 0); QString nameAndType = state.left(sep2); if (encName.length() + typeStr.length() + 1 == (uint)sep2 && nameAndType.startsWith(encName) && nameAndType.endsWith(typeStr)) { states.remove(it); return state.mid(sep2+1); } } return QString::null; } // ------------------------------------------------------------------------- HTMLButtonElementImpl::HTMLButtonElementImpl(DocumentPtr *doc, HTMLFormElementImpl *f) : HTMLGenericFormElementImpl(doc, f) { 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::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_ONFOCUS: setHTMLEventListener(EventImpl::FOCUS_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; case ATTR_ONBLUR: setHTMLEventListener(EventImpl::BLUR_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; default: HTMLGenericFormElementImpl::parseAttribute(attr); } } void HTMLButtonElementImpl::attach() { // FIXME: This code is repeated here because this method does not call // HTMLGenericFormElementImpl::attach(). But it's not clear why the call // to updateFromElement() is a problem for the HTMLButtonElementImpl case. // If we determine that it's not, then we should remove this and call // HTMLGenericFormElementImpl::attach() instead of HTMLElementImpl::attach(). if (!m_form) { m_form = getForm(); if (m_form) m_form->registerFormElement(this); } // skip the generic handler HTMLElementImpl::attach(); // doesn't work yet in the renderer ### fixme // if (renderer()) // renderer()->setReplaced(true); } void HTMLButtonElementImpl::defaultEventHandler(EventImpl *evt) { if (m_type != BUTTON && (evt->id() == EventImpl::DOMACTIVATE_EVENT)) { 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(); } HTMLGenericFormElementImpl::defaultEventHandler(evt); } bool HTMLButtonElementImpl::isSuccessfulSubmitButton() const { return m_type == SUBMIT && !m_disabled && !name().isEmpty(); } bool HTMLButtonElementImpl::isActivatedSubmit() const { return m_activeSubmit; } void HTMLButtonElementImpl::setActivatedSubmit(bool flag) { m_activeSubmit = flag; } 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()); QString enc_str = m_currValue.isNull() ? QString("") : m_currValue; encoding += fixUpfromUnicode(codec, enc_str); return true; } // ------------------------------------------------------------------------- HTMLFieldSetElementImpl::HTMLFieldSetElementImpl(DocumentPtr *doc, HTMLFormElementImpl *f) : HTMLGenericFormElementImpl(doc, f) { } HTMLFieldSetElementImpl::~HTMLFieldSetElementImpl() { } NodeImpl::Id HTMLFieldSetElementImpl::id() const { return ID_FIELDSET; } DOMString HTMLFieldSetElementImpl::type() const { return "fieldset"; } void HTMLFieldSetElementImpl::attach() { createRendererIfNeeded(); HTMLGenericFormElementImpl::attach(); } RenderObject* HTMLFieldSetElementImpl::createRenderer(RenderArena* arena, RenderStyle* style) { return new (arena) RenderFieldset(this); } // ------------------------------------------------------------------------- HTMLInputElementImpl::HTMLInputElementImpl(DocumentPtr *doc, HTMLFormElementImpl *f) : HTMLGenericFormElementImpl(doc, f) { m_type = TEXT; m_maxLen = -1; m_size = 20; m_checked = false; m_defaultChecked = false; m_useDefaultChecked = true; m_haveType = false; m_activeSubmit = false; m_autocomplete = true; m_inited = 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; } void HTMLInputElementImpl::setType(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. // Useful in case we were called from inside parseAttribute. setAttribute(ATTR_TYPE, type()); } else { m_type = newType; } } 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( ) { assert(m_type != PASSWORD); // should never save/restore password fields QString state = HTMLGenericFormElementImpl::state(); switch (m_type) { case CHECKBOX: case RADIO: return state + (checked() ? "on" : "off"); default: return state + value().string()+'.'; // Make sure the string is not empty! } } void HTMLInputElementImpl::restoreState(QStringList &states) { assert(m_type != PASSWORD); // should never save/restore password fields QString state = HTMLGenericFormElementImpl::findMatchingState(states); if (state.isNull()) return; switch (m_type) { case CHECKBOX: case RADIO: setChecked((state == "on")); break; default: setValue(DOMString(state.left(state.length()-1))); break; } } void HTMLInputElementImpl::select( ) { if(!m_render) return; if (m_type == TEXT || m_type == PASSWORD) static_cast(m_render)->select(); else if (m_type == FILE) static_cast(m_render)->select(); } void HTMLInputElementImpl::click( ) { // ### #ifdef FORMS_DEBUG kdDebug( 6030 ) << " HTMLInputElementImpl::click( )" << endl; #endif } void HTMLInputElementImpl::parseAttribute(AttributeImpl *attr) { switch(attr->id()) { case ATTR_AUTOCOMPLETE: m_autocomplete = strcasecmp( attr->value(), "off" ); break; case ATTR_TYPE: setType(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. 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 = attr->val() ? attr->val()->toInt() : -1; setChanged(); break; case ATTR_SIZE: m_size = attr->val() ? attr->val()->toInt() : 20; break; case ATTR_ALT: case ATTR_SRC: if (m_render && m_type == IMAGE) m_render->updateFromElement(); break; case ATTR_USEMAP: case ATTR_ACCESSKEY: // ### ignore for the moment break; case ATTR_VSPACE: addCSSLength(CSS_PROP_MARGIN_TOP, attr->value()); addCSSLength(CSS_PROP_MARGIN_BOTTOM, attr->value()); break; case ATTR_HSPACE: addCSSLength(CSS_PROP_MARGIN_LEFT, attr->value()); addCSSLength(CSS_PROP_MARGIN_RIGHT, attr->value()); break; case ATTR_ALIGN: addHTMLAlignment( attr->value() ); break; case ATTR_WIDTH: // ignore this attribute, do _not_ add // a CSS_PROP_WIDTH here (we will honor this attribute for input type=image // in the attach call)! // webdesigner are stupid - and IE/NS behave the same ( Dirk ) break; case ATTR_HEIGHT: addCSSLength(CSS_PROP_HEIGHT, attr->value() ); break; case ATTR_ONFOCUS: setHTMLEventListener(EventImpl::FOCUS_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; case ATTR_ONBLUR: setHTMLEventListener(EventImpl::BLUR_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; case ATTR_ONSELECT: setHTMLEventListener(EventImpl::SELECT_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; case ATTR_ONCHANGE: setHTMLEventListener(EventImpl::CHANGE_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; default: HTMLGenericFormElementImpl::parseAttribute(attr); } } bool HTMLInputElementImpl::rendererIsNeeded(RenderStyle *style) { switch(m_type) { case TEXT: case PASSWORD: case ISINDEX: case CHECKBOX: case RADIO: case SUBMIT: case IMAGE: case RESET: case FILE: case BUTTON: return HTMLGenericFormElementImpl::rendererIsNeeded(style); case HIDDEN: return false; } assert(false); return false; } RenderObject *HTMLInputElementImpl::createRenderer(RenderArena *arena, RenderStyle *style) { switch(m_type) { case TEXT: case PASSWORD: case ISINDEX: return new (arena) RenderLineEdit(this); case CHECKBOX: return new (arena) RenderCheckBox(this); case RADIO: return new (arena) RenderRadioButton(this); case SUBMIT: return new (arena) RenderSubmitButton(this); case IMAGE: return new (arena) RenderImageButton(this); case RESET: return new (arena) RenderResetButton(this); case FILE: return new (arena) RenderFileButton(this); case BUTTON: return new (arena) RenderPushButton(this); case HIDDEN: break; } assert(false); return 0; } void HTMLInputElementImpl::attach() { if (!m_inited) { if (!m_haveType) setType(getAttribute(ATTR_TYPE)); // FIXME: This needs to be dynamic, doesn't it, since someone could set this // after attachment? DOMString val = getAttribute(ATTR_VALUE); if ((uint) m_type <= ISINDEX && !val.isEmpty()) { // remove newline stuff.. QString nvalue; for (unsigned int i = 0; i < val.length(); ++i) if (val[i] >= ' ') nvalue += val[i]; if (val.length() != nvalue.length()) setAttribute(ATTR_VALUE, nvalue); } m_defaultChecked = (getAttribute(ATTR_CHECKED) != 0); m_inited = true; } // make sure we don't inherit a color to the form elements // by adding a non-CSS color property. this his higher // priority than inherited color, but lesser priority than // any color specified by CSS for the elements. switch( m_type ) { case TEXT: case PASSWORD: #if !APPLE_CHANGES addCSSProperty(CSS_PROP_FONT_FAMILY, CSS_VAL_MONOSPACE); #endif /* nobreak */ case ISINDEX: case FILE: addCSSProperty(CSS_PROP_COLOR, "text"); break; case SUBMIT: case RESET: case BUTTON: case CHECKBOX: case RADIO: addCSSProperty(CSS_PROP_COLOR, "buttontext"); // FIXME: There was no break here in the original KHTML. Was that intentional? break; case HIDDEN: case IMAGE: if (!getAttribute(ATTR_WIDTH).isEmpty()) addCSSLength(CSS_PROP_WIDTH, getAttribute(ATTR_WIDTH)); break; } createRendererIfNeeded(); HTMLGenericFormElementImpl::attach(); #if APPLE_CHANGES // note we don't deal with calling passwordFieldRemoved() on detach, because the timing // was such that it cleared our state too early if (m_type == PASSWORD) getDocument()->passwordFieldAdded(); #endif } 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() ) #if APPLE_CHANGES alt = inputElementAltText(); #else alt = i18n( "Submit" ); #endif return alt; } bool HTMLInputElementImpl::isSuccessfulSubmitButton() const { return !m_disabled && (m_type == IMAGE || (m_type == SUBMIT && !name().isEmpty())); } bool HTMLInputElementImpl::isActivatedSubmit() const { return m_activeSubmit; } void HTMLInputElementImpl::setActivatedSubmit(bool flag) { m_activeSubmit = flag; } bool HTMLInputElementImpl::encoding(const QTextCodec* codec, khtml::encodingList& encoding, bool multipart) { 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 HIDDEN: case TEXT: case PASSWORD: // always successful encoding += fixUpfromUnicode(codec, value().string()); return true; 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_activeSubmit) { QString astr(nme.isEmpty() ? QString::fromLatin1("x") : nme + ".x"); encoding += fixUpfromUnicode(codec, astr); astr.setNum(clickX()); encoding += fixUpfromUnicode(codec, astr); astr = nme.isEmpty() ? QString::fromLatin1("y") : nme + ".y"; encoding += fixUpfromUnicode(codec, astr); astr.setNum(clickY()); encoding += fixUpfromUnicode(codec, astr); return true; } break; case SUBMIT: if (m_activeSubmit) { QString enc_str = value().string(); if (enc_str.isEmpty()) enc_str = static_cast(m_render)->defaultLabel(); if (!enc_str.isEmpty()) { encoding += fixUpfromUnicode(codec, enc_str); return true; } } break; case FILE: { // can't submit file on GET // don't submit if display: none or display: hidden if(!multipart || !renderer() || renderer()->style()->visibility() != khtml::VISIBLE) return false; QString local; QCString dummy(""); // if no filename at all is entered, return successful, however empty // null would be more logical but netscape posts an empty file. argh. if(value().isEmpty()) { encoding += dummy; return true; } KURL fileurl("file:///"); fileurl.setPath(value().string()); KIO::UDSEntry filestat; if (!KIO::NetAccess::stat(fileurl, filestat)) { #if APPLE_CHANGES // FIXME: Figure out how to report this error. #else KMessageBox::sorry(0L, i18n("Error fetching file for submission:\n%1").arg(KIO::NetAccess::lastErrorString())); #endif return false; } KFileItem fileitem(filestat, fileurl, true, false); if(fileitem.isDir()) { encoding += dummy; return false; } if ( KIO::NetAccess::download(fileurl, local) ) { QFile file(local); if (file.open(IO_ReadOnly)) { QCString filearray(file.size()+1); int readbytes = file.readBlock( filearray.data(), file.size()); if ( readbytes >= 0 ) filearray[readbytes] = '\0'; file.close(); encoding += filearray; KIO::NetAccess::removeTempFile( local ); return true; } return false; } else { #if APPLE_CHANGES // FIXME: Figure out how to report this error. #else KMessageBox::sorry(0L, i18n("Error fetching file for submission:\n%1").arg(KIO::NetAccess::lastErrorString())); #endif return false; } break; } case ISINDEX: encoding += fixUpfromUnicode(codec, value().string()); return true; } return false; } void HTMLInputElementImpl::reset() { setValue(DOMString()); m_useDefaultChecked = true; m_checked = m_defaultChecked; } void HTMLInputElementImpl::setChecked(bool _checked) { if (checked() == _checked) return; if (m_form && m_type == RADIO && _checked && !name().isEmpty()) m_form->radioClicked(this); m_useDefaultChecked = false; m_checked = _checked; setChanged(); } DOMString HTMLInputElementImpl::value() const { if (m_type == CHECKBOX || m_type == RADIO) { DOMString val = getAttribute(ATTR_VALUE); // If no attribute exists, then just use "on" or "" based off the checked() state // of the control. if (val.isNull()) { if (checked()) return DOMString("on"); else return DOMString(""); } return val; } if (m_value.isNull()) return getAttribute(ATTR_VALUE); return m_value; } void HTMLInputElementImpl::setValue(DOMString val) { if (m_type == FILE) return; m_value = val; setChanged(); } void HTMLInputElementImpl::blur() { if(getDocument()->focusNode() == this) getDocument()->setFocusNode(0); } void HTMLInputElementImpl::focus() { getDocument()->setFocusNode(this); } void HTMLInputElementImpl::defaultEventHandler(EventImpl *evt) { if (evt->isMouseEvent() && ( evt->id() == EventImpl::KHTML_CLICK_EVENT || evt->id() == EventImpl::KHTML_DBLCLICK_EVENT ) && m_type == IMAGE && m_render) { // record the mouse position for when we get the DOMActivate event MouseEventImpl *me = static_cast(evt); int offsetX, offsetY; m_render->absolutePosition(offsetX,offsetY); xPos = me->clientX()-offsetX; yPos = me->clientY()-offsetY; me->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. Javacsript code wishing to activate the element // must dispatch a DOMActivate event - a click event will not do the job. if ((evt->id() == EventImpl::DOMACTIVATE_EVENT) && (m_type == IMAGE || m_type == SUBMIT || m_type == RESET)){ if (!m_form || !m_render) return; if (m_type == RESET) { m_form->reset(); } else { m_activeSubmit = true; if (!m_form->prepareSubmit()) { xPos = 0; yPos = 0; } m_activeSubmit = false; } } HTMLGenericFormElementImpl::defaultEventHandler(evt); } bool HTMLInputElementImpl::isEditable() { return ((m_type == TEXT) || (m_type == PASSWORD) || (m_type == ISINDEX) || (m_type == FILE)); } // ------------------------------------------------------------------------- HTMLLabelElementImpl::HTMLLabelElementImpl(DocumentPtr *doc) : HTMLElementImpl(doc) { } HTMLLabelElementImpl::~HTMLLabelElementImpl() { } NodeImpl::Id HTMLLabelElementImpl::id() const { return ID_LABEL; } void HTMLLabelElementImpl::parseAttribute(AttributeImpl *attr) { switch(attr->id()) { case ATTR_ONFOCUS: setHTMLEventListener(EventImpl::FOCUS_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; case ATTR_ONBLUR: setHTMLEventListener(EventImpl::BLUR_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; default: HTMLElementImpl::parseAttribute(attr); } } ElementImpl *HTMLLabelElementImpl::formElement() { DOMString formElementId = getAttribute(ATTR_FOR); if (formElementId.isNull() || formElementId.isEmpty()) return 0; return getDocument()->getElementById(formElementId); } // ------------------------------------------------------------------------- HTMLLegendElementImpl::HTMLLegendElementImpl(DocumentPtr *doc, HTMLFormElementImpl *f) : HTMLGenericFormElementImpl(doc, f) { } HTMLLegendElementImpl::~HTMLLegendElementImpl() { } NodeImpl::Id HTMLLegendElementImpl::id() const { return ID_LEGEND; } void HTMLLegendElementImpl::attach() { createRendererIfNeeded(); HTMLGenericFormElementImpl::attach(); } RenderObject* HTMLLegendElementImpl::createRenderer(RenderArena* arena, RenderStyle* style) { return new (arena) RenderLegend(this); } DOMString HTMLLegendElementImpl::type() const { return "legend"; } // ------------------------------------------------------------------------- HTMLSelectElementImpl::HTMLSelectElementImpl(DocumentPtr *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; } HTMLSelectElementImpl::~HTMLSelectElementImpl() { if (getDocument()) getDocument()->deregisterMaintainsState(this); } NodeImpl::Id HTMLSelectElementImpl::id() const { return ID_SELECT; } void HTMLSelectElementImpl::recalcStyle( StyleChange ch ) { if (hasChangedChild() && m_render) { static_cast(m_render)->setOptionsChanged(true); } HTMLGenericFormElementImpl::recalcStyle( ch ); } 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 items = listItems(); for (unsigned int i = 0; i < items.size(); i++) { if (items[i]->id() == ID_OPTION) { if (static_cast(items[i])->selected()) return o; o++; } } Q_ASSERT(m_multiple); return -1; } void HTMLSelectElementImpl::setSelectedIndex( long index ) { // deselect all other options and select only the new one QMemArray items = listItems(); int listIndex; for (listIndex = 0; listIndex < int(items.size()); listIndex++) { if (items[listIndex]->id() == ID_OPTION) static_cast(items[listIndex])->setSelected(false); } listIndex = optionToListIndex(index); if (listIndex >= 0) static_cast(items[listIndex])->setSelected(true); setChanged(true); } long HTMLSelectElementImpl::length() const { int len = 0; uint i; QMemArray items = listItems(); for (i = 0; i < items.size(); i++) { if (items[i]->id() == ID_OPTION) len++; } return len; } void HTMLSelectElementImpl::add( const HTMLElement &element, const HTMLElement &before ) { if(element.isNull() || element.handle()->id() != ID_OPTION) return; int exceptioncode = 0; insertBefore(element.handle(), before.handle(), exceptioncode ); if (!exceptioncode) setRecalcListItems(); } void HTMLSelectElementImpl::remove( long index ) { int exceptioncode = 0; int listIndex = optionToListIndex(index); QMemArray items = listItems(); if(listIndex < 0 || index >= int(items.size())) return; // ### what should we do ? remove the last item? removeChild(items[listIndex], exceptioncode); if( !exceptioncode ) setRecalcListItems(); } void HTMLSelectElementImpl::blur() { if(getDocument()->focusNode() == this) getDocument()->setFocusNode(0); } void HTMLSelectElementImpl::focus() { getDocument()->setFocusNode(this); } DOMString HTMLSelectElementImpl::value( ) { uint i; QMemArray items = listItems(); for (i = 0; i < items.size(); i++) { if ( items[i]->id() == ID_OPTION && static_cast(items[i])->selected()) return static_cast(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. kdWarning() << "Unimplemented HTMLSelectElementImpl::setValue called" << endl; } QString HTMLSelectElementImpl::state( ) { #if !APPLE_CHANGES QString state; #endif QMemArray items = listItems(); int l = items.count(); #if APPLE_CHANGES QChar stateChars[l]; for(int i = 0; i < l; i++) if(items[i]->id() == ID_OPTION && static_cast(items[i])->selected()) stateChars[i] = 'X'; else stateChars[i] = '.'; QString state(stateChars, l); #else /* APPLE_CHANGES not defined */ state.fill('.', l); for(int i = 0; i < l; i++) if(items[i]->id() == ID_OPTION && static_cast(items[i])->selected()) state[i] = 'X'; #endif /* APPLE_CHANGES not defined */ 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) { qWarning("should not happen in restoreState!"); #if APPLE_CHANGES // KWQString doesn't support this operation. Should never get here anyway. //state[0] = 'X'; #else state[0] = 'X'; #endif } QMemArray items = listItems(); int l = items.count(); for(int i = 0; i < l; i++) { if(items[i]->id() == ID_OPTION) { HTMLOptionElementImpl* oe = static_cast(items[i]); oe->setSelected(state[i] == 'X'); } } setChanged(true); } NodeImpl *HTMLSelectElementImpl::insertBefore ( NodeImpl *newChild, NodeImpl *refChild, int &exceptioncode ) { NodeImpl *result = HTMLGenericFormElementImpl::insertBefore(newChild,refChild, exceptioncode ); if (!exceptioncode) setRecalcListItems(); return result; } NodeImpl *HTMLSelectElementImpl::replaceChild ( NodeImpl *newChild, NodeImpl *oldChild, int &exceptioncode ) { NodeImpl *result = HTMLGenericFormElementImpl::replaceChild(newChild,oldChild, exceptioncode); if( !exceptioncode ) setRecalcListItems(); return result; } NodeImpl *HTMLSelectElementImpl::removeChild ( NodeImpl *oldChild, int &exceptioncode ) { NodeImpl *result = HTMLGenericFormElementImpl::removeChild(oldChild, exceptioncode); if( !exceptioncode ) setRecalcListItems(); return result; } NodeImpl *HTMLSelectElementImpl::appendChild ( NodeImpl *newChild, int &exceptioncode ) { NodeImpl *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 = QMAX( attr->val()->toInt(), 1 ); break; case ATTR_WIDTH: m_minwidth = QMAX( attr->val()->toInt(), 0 ); break; case ATTR_MULTIPLE: m_multiple = (attr->val() != 0); break; case ATTR_ACCESSKEY: // ### ignore for the moment break; case ATTR_ONFOCUS: setHTMLEventListener(EventImpl::FOCUS_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; case ATTR_ONBLUR: setHTMLEventListener(EventImpl::BLUR_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; case ATTR_ONCHANGE: setHTMLEventListener(EventImpl::CHANGE_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; default: HTMLGenericFormElementImpl::parseAttribute(attr); } } RenderObject *HTMLSelectElementImpl::createRenderer(RenderArena *arena, RenderStyle *style) { return new (arena) RenderSelect(this); } void HTMLSelectElementImpl::attach() { addCSSProperty(CSS_PROP_COLOR, "text"); createRendererIfNeeded(); HTMLGenericFormElementImpl::attach(); } bool HTMLSelectElementImpl::encoding(const QTextCodec* codec, khtml::encodingList& encoded_values, bool) { bool successful = false; QCString enc_name = fixUpfromUnicode(codec, name().string()); QMemArray items = listItems(); uint i; for (i = 0; i < items.size(); i++) { if (items[i]->id() == ID_OPTION) { HTMLOptionElementImpl *option = static_cast(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 && items.size() && (items[0]->id() == ID_OPTION) ) { HTMLOptionElementImpl *option = static_cast(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 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]->id() == ID_OPTION) optionIndex2++; } listIndex--; return listIndex; } int HTMLSelectElementImpl::listToOptionIndex(int listIndex) const { QMemArray 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() { NodeImpl* current = firstChild(); m_listItems.resize(0); HTMLOptionElementImpl* foundSelected = 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(current); current = current->firstChild(); } if (current->id() == ID_OPTION) { m_listItems.resize(m_listItems.size()+1); m_listItems[m_listItems.size()-1] = static_cast(current); if (!foundSelected && !m_multiple && m_size <= 1) { foundSelected = static_cast(current); foundSelected->m_selected = true; } else if (foundSelected && !m_multiple && static_cast(current)->selected()) { foundSelected->m_selected = false; foundSelected = static_cast(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 (m_render) static_cast(m_render)->setOptionsChanged(true); setChanged(); } void HTMLSelectElementImpl::reset() { QMemArray items = listItems(); uint i; for (i = 0; i < items.size(); i++) { if (items[i]->id() == ID_OPTION) { HTMLOptionElementImpl *option = static_cast(items[i]); bool selected = (!option->getAttribute(ATTR_SELECTED).isNull()); option->setSelected(selected); } } if ( m_render ) static_cast(m_render)->setSelectionChanged(true); setChanged( true ); } void HTMLSelectElementImpl::notifyOptionSelected(HTMLOptionElementImpl *selectedOption, bool selected) { if (selected && !m_multiple) { // deselect all other options QMemArray items = listItems(); uint i; for (i = 0; i < items.size(); i++) { if (items[i]->id() == ID_OPTION) static_cast(items[i])->m_selected = (items[i] == selectedOption); } } if (m_render) static_cast(m_render)->setSelectionChanged(true); setChanged(true); } // ------------------------------------------------------------------------- HTMLKeygenElementImpl::HTMLKeygenElementImpl(DocumentPtr* doc, HTMLFormElementImpl* f) : HTMLSelectElementImpl(doc, f) { QStringList keys = KSSLKeyGen::supportedKeySizes(); for (QStringList::Iterator i = keys.begin(); i != keys.end(); ++i) { HTMLOptionElementImpl* o = new HTMLOptionElementImpl(doc, form()); addChild(o); o->addChild(new TextImpl(doc, DOMString(*i))); } } NodeImpl::Id HTMLKeygenElementImpl::id() const { return ID_KEYGEN; } DOMString HTMLKeygenElementImpl::type() const { return "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; QCString enc_name = fixUpfromUnicode(codec, name().string()); encoded_values += enc_name; // pop up the fancy certificate creation dialog here KSSLKeyGen *kg = new KSSLKeyGen(static_cast(m_render)->widget(), "Key Generator", true); kg->setKeySize(0); successful = (QDialog::Accepted == kg->exec()); delete kg; encoded_values += "deadbeef"; return successful; } // ------------------------------------------------------------------------- HTMLOptGroupElementImpl::HTMLOptGroupElementImpl(DocumentPtr *doc, HTMLFormElementImpl *f) : HTMLGenericFormElementImpl(doc, f) { } HTMLOptGroupElementImpl::~HTMLOptGroupElementImpl() { } NodeImpl::Id HTMLOptGroupElementImpl::id() const { return ID_OPTGROUP; } DOMString HTMLOptGroupElementImpl::type() const { return "optgroup"; } NodeImpl *HTMLOptGroupElementImpl::insertBefore ( NodeImpl *newChild, NodeImpl *refChild, int &exceptioncode ) { NodeImpl *result = HTMLGenericFormElementImpl::insertBefore(newChild,refChild, exceptioncode); if ( !exceptioncode ) recalcSelectOptions(); return result; } NodeImpl *HTMLOptGroupElementImpl::replaceChild ( NodeImpl *newChild, NodeImpl *oldChild, int &exceptioncode ) { NodeImpl *result = HTMLGenericFormElementImpl::replaceChild(newChild,oldChild, exceptioncode); if(!exceptioncode) recalcSelectOptions(); return result; } NodeImpl *HTMLOptGroupElementImpl::removeChild ( NodeImpl *oldChild, int &exceptioncode ) { NodeImpl *result = HTMLGenericFormElementImpl::removeChild(oldChild, exceptioncode); if( !exceptioncode ) recalcSelectOptions(); return result; } NodeImpl *HTMLOptGroupElementImpl::appendChild ( NodeImpl *newChild, int &exceptioncode ) { NodeImpl *result = HTMLGenericFormElementImpl::appendChild(newChild, exceptioncode); if( !exceptioncode ) recalcSelectOptions(); return result; } NodeImpl* HTMLOptGroupElementImpl::addChild(NodeImpl* newChild) { recalcSelectOptions(); return HTMLGenericFormElementImpl::addChild(newChild); } void HTMLOptGroupElementImpl::parseAttribute(AttributeImpl *attr) { HTMLGenericFormElementImpl::parseAttribute(attr); recalcSelectOptions(); } void HTMLOptGroupElementImpl::recalcSelectOptions() { NodeImpl *select = parentNode(); while (select && select->id() != ID_SELECT) select = select->parentNode(); if (select) static_cast(select)->setRecalcListItems(); } // ------------------------------------------------------------------------- HTMLOptionElementImpl::HTMLOptionElementImpl(DocumentPtr *doc, HTMLFormElementImpl *f) : HTMLGenericFormElementImpl(doc, f) { m_selected = false; } NodeImpl::Id HTMLOptionElementImpl::id() const { return ID_OPTION; } DOMString HTMLOptionElementImpl::type() const { return "option"; } DOMString HTMLOptionElementImpl::text() const { DOMString label = getAttribute(ATTR_LABEL); if (label.isEmpty() && 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(); } else return label; } 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 items = getSelect()->listItems(); int l = items.count(); int optionIndex = 0; for(int i = 0; i < l; i++) { if(items[i]->id() == ID_OPTION) { if (static_cast(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().stripWhiteSpace(); } void HTMLOptionElementImpl::setValue(DOMStringImpl* value) { setAttribute(ATTR_VALUE, value); } void HTMLOptionElementImpl::setSelected(bool _selected) { if(m_selected == _selected) return; m_selected = _selected; HTMLSelectElementImpl *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(select); } // ------------------------------------------------------------------------- HTMLTextAreaElementImpl::HTMLTextAreaElementImpl(DocumentPtr *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_dirtyvalue = true; } HTMLTextAreaElementImpl::~HTMLTextAreaElementImpl() { if (getDocument()) getDocument()->deregisterMaintainsState(this); } NodeImpl::Id HTMLTextAreaElementImpl::id() const { return ID_TEXTAREA; } DOMString HTMLTextAreaElementImpl::type() const { return "textarea"; } QString HTMLTextAreaElementImpl::state( ) { // Make sure the string is not empty! return HTMLGenericFormElementImpl::state() + value().string()+'.'; } void HTMLTextAreaElementImpl::restoreState(QStringList &states) { QString state = HTMLGenericFormElementImpl::findMatchingState(states); if (state.isNull()) return; setDefaultValue(state.left(state.length()-1)); // the close() in the rendertree will take care of transferring defaultvalue to 'value' } void HTMLTextAreaElementImpl::select( ) { if (m_render) static_cast(m_render)->select(); onSelect(); } void HTMLTextAreaElementImpl::parseAttribute(AttributeImpl *attr) { switch(attr->id()) { case ATTR_ROWS: m_rows = attr->val() ? attr->val()->toInt() : 3; break; case ATTR_COLS: m_cols = attr->val() ? attr->val()->toInt() : 60; 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: // ignore for the moment break; case ATTR_ONFOCUS: setHTMLEventListener(EventImpl::FOCUS_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; case ATTR_ONBLUR: setHTMLEventListener(EventImpl::BLUR_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; case ATTR_ONSELECT: setHTMLEventListener(EventImpl::SELECT_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; case ATTR_ONCHANGE: setHTMLEventListener(EventImpl::CHANGE_EVENT, getDocument()->createHTMLEventListener(attr->value().string())); break; default: HTMLGenericFormElementImpl::parseAttribute(attr); } } RenderObject *HTMLTextAreaElementImpl::createRenderer(RenderArena *arena, RenderStyle *style) { return new (arena) RenderTextArea(this); } void HTMLTextAreaElementImpl::attach() { addCSSProperty(CSS_PROP_COLOR, "text"); createRendererIfNeeded(); HTMLGenericFormElementImpl::attach(); } bool HTMLTextAreaElementImpl::encoding(const QTextCodec* codec, encodingList& encoding, bool) { if (name().isEmpty() || !m_render) return false; encoding += fixUpfromUnicode(codec, name().string()); encoding += fixUpfromUnicode(codec, value().string()); return true; } void HTMLTextAreaElementImpl::reset() { setValue(defaultValue()); } DOMString HTMLTextAreaElementImpl::value() { if ( m_dirtyvalue) { if ( m_render ) m_value = static_cast( m_render )->text(); m_dirtyvalue = false; } if ( m_value.isNull() ) return ""; return m_value; } void HTMLTextAreaElementImpl::setValue(DOMString _value) { m_value = _value.string(); m_dirtyvalue = false; 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(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 toRemove; NodeImpl *n; for (n = firstChild(); n; n = n->nextSibling()) if (n->isTextNode()) toRemove.append(n); QPtrListIterator it(toRemove); int exceptioncode = 0; for (; it.current(); ++it) { removeChild(it.current(), exceptioncode); } insertBefore(getDocument()->createTextNode(_defaultValue),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; } // ------------------------------------------------------------------------- HTMLIsIndexElementImpl::HTMLIsIndexElementImpl(DocumentPtr *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) { switch(attr->id()) { case ATTR_PROMPT: setValue(attr->value()); default: // don't call HTMLInputElement::parseAttribute here, as it would // accept attributes this element does not support HTMLGenericFormElementImpl::parseAttribute(attr); } } // -------------------------------------------------------------------------